Generating and showing PDF inside NativeScript app - pdf

I want to show a PDF file generated with pdfMake with the nativescript-pdf-view plugin in a NativeScript-Vue app.
I am unable to show the file inside the plugin widget. I started from this example, but I want to display the file inside the app.
First I generate encode the PDF to base64, which works fine, then I write the encoded string to file. When I pass the file path to the widget nothing shows up. URLs are shown correctly, so the error might happen while writing to file or handling the path.
PDF generation (app/scripts/pdf.js):
import { createPdf } from "pdfmake/build/pdfmake";
import { pdfMake } from "pdfmake/build/vfs_fonts";
import { knownFolders } from "tns-core-modules/file-system";
export default {
generatePdf() {
return new Promise((resolve, reject) => {
var docDefinition = {
pageSize: "A5",
pageMargins: [40, 40, 40, 40],
content: { text: "Test", fontSize: 80 }
};
var file = knownFolders.documents().getFile("document1.pdf");
createPdf(docDefinition, "", "", pdfMake.vfs).getBase64(base64 => {
// decoding this string returns a correct pdf file
file.writeText(base64).then(() => {
// file properties are updated
resolve(file.path);
// path example: "/data/user/0/{app package}/files/document1.pdf"
}).catch(e => reject(e));
});
});
},
// other code
}
Component:
<template>
<GridLayout rows="* *">
<Button row="0" #tap="getPDF" text="get pdf" />
<PDFView row="1" :src="pdf" />
</GridLayout>
</template>
<script>
import pdfModule from "../scripts/pdf";
export default {
data() {
pdf: ""
},
methods: {
async getPDF() {
this.pdf = await pdfModule.generatePdf().catch(e => console.error(e));
}
}
}
</script>

Solved using native byte arrays.
import { createPdf } from "pdfmake/build/pdfmake";
import { pdfMake } from "pdfmake/build/vfs_fonts";
import { knownFolders } from "tns-core-modules/file-system";
import { isAndroid } from "tns-core-modules/platform";
export default {
generatePdf() {
return new Promise((resolve, reject) => {
let docDefinition = {
pageSize: "A5",
pageMargins: [40, 40, 40, 40],
content: { text: "Test", fontSize: 80 }
};
let file = knownFolders.documents().getFile("document1.pdf");
createPdf(docDefinition, "", "", pdfMake.vfs).getBuffer(result => {
file.writeSync(this._bufferToNativeArray(result), e => {
console.error(e);
reject(e);
});
resolve(file.path);
});
});
},
_bufferToNativeArray(byteArray) {
let array;
if (isAndroid) {
array = Array.create("byte", byteArray.byteLength);
for (let i = 0; i < byteArray.length; i++) {
array[i] = new java.lang.Byte(byteArray[i]);
}
} else {
array = NSData.dataWithBytesLength(byteArray, byteArray.byteLength);
}
return array;
}
}

Related

How can I define the pdf url document with axios and not static with vue-pdf

pdf and I see that this example that developer gave it's ok it worked to me but the problem it is that he indicates a static pdf and I wonder how can I fix that if you see the example is like this:
<template>
<div>
<pdf
v-for="i in numPages"
:key="i"
:src="src"
:page="i"
style="display: inline-block; width: 25%"
></pdf>
</div>
import pdf from 'vue-pdf'
var loadingTask = pdf.createLoadingTask('https://cdn.mozilla.net/pdfjs/tracemonkey.pdf');
export default {
components: {
pdf
},
data() {
return {
src: loadingTask,
numPages: undefined,
}
},
mounted() {
this.src.promise.then(pdf => {
this.numPages = pdf.numPages;
});
}
}
If you see this line var loadingTask = pdf.createLoadingTask('https://cdn.mozilla.net/pdfjs/tracemonkey.pdf'); gets the document as static but I wonder if I have this code and I am getting the name from database like this:
export default {
created() {
this.getPost();
},
components: {
pdf
},
methods: {
getPost() {
this.loading = true;
axios.get('/api/content/detail/'+ this.$route.params.id)
.then(response => {
this.post = response.data.data;
this.pdf_url = '/storage/'+this.post.pdf;
})
.catch(function (error) {
console.log(error);
})
.finally(() => {
this.loading = false;
});
}
If you see I have the name into this.pdf_url but I do not know how to add this name to this pdf.createLoadingTask(''); How can I do it? because it is a defined var Thanks

vue-pdf doesn't refresh on src change

I'm using the latest vue-pdf package to display pdf files in my app. I built this component, PdfViewer:
<template>
<div class="fill-height pdf-container">
<template v-if="src && numberOfPages">
<pdf
v-for="page in numberOfPages"
:key="`${fileName}-${page}`"
:src="src"
:page="page"
/>
</template>
</div>
</template>
import { mapGetters } from 'vuex'
import pdf from 'vue-pdf'
export default {
props: {
fileName: {
type: String,
required: true
}
},
components: {
pdf
},
data() {
return {
src: null,
numberOfPages: 0
}
},
computed: {
...mapGetters({
getAttachments: 'questions/getAttachments'
})
},
methods: {
init() {
if (this.fileName) {
let url = this.getAttachments[this.fileName]
let loadingTask = pdf.createLoadingTask(url)
this.src = loadingTask
this.src.promise.then(pdf => {
this.numberOfPages = pdf.numPages
})
}
},
},
watch: {
fileName() {
this.init()
}
},
beforeMount() {
this.init()
}
}
Basically I'm receiving a fileName as a prop, then look for its URL in the object I receive in getAttachments getter. The file names are in different list component.
It works fine on the first run and the first file is loaded and displayed successfully. But once clicked on another file name - nothing being displayed. I do receive the file name prop and it does find the URL, but the file doesn't display. Even when I click on the file that has already been displayed - now it doesn't.
I thought maybe it has something to do with src and numberOfPages property, so I tried to reset them before loading the file:
init() {
if (this.fileName) {
this.src = null
this.numberOfPages = 0
let url = this.getAttachments[this.fileName]
let loadingTask = pdf.createLoadingTask(url)
this.src = loadingTask
this.src.promise.then(pdf => {
this.numberOfPages = pdf.numPages
})
}
}
Alas, same result. And in the console I see the following warning from pdf.worker.js: Warning: TT: invalid function id: 9
Have no idea what it means.
Any help, please?
EDIT
I tried to do that with async/await and forceUpdate:
async init() {
if (this.fileName) {
this.src = null
this.numberOfPages = 0
let url = this.getAttachments[this.fileName]
let loadingTask = await pdf.createLoadingTask(url)
await loadingTask.promise.then(pdf => {
this.src = url
this.numberOfPages = pdf.numPages
})
this.$forceUpdate()
}
}
That also didn't help. But I found out that once I change the passed fileName, the code does go to the init() method, but for some reason it skips the loadingTask.promise.then part, doesn't go in. I have to idea why.
Well, apparently there's some issue with vue-pdf library. Eventually I solved it by setting timeout when assigning fileName prop and re-rendering the component:
<PdfViewer v-if="selectedFileName" :fileName="selectedFileName" />
onFileNameSelected(fileName) {
this.selectedFileName = null
setTimeout(() => {
this.selectedFileName = fileName
}, 0)
}
And then in the PdfViewer component it's just:
created() {
this.src = pdf.createLoadingTask(this.getAttachments[this.fileName])
},
mounted() {
this.src.promise.then(pdf => {
this.numberOfPages = pdf.numPages
})
}
That did the trick for me, though feels kinda hacky.

How pass data from component to external js file

I want to use my component's data within my external JavaScript file, containing my Dropzone configuration. I tried unsuccessfully to use Function.prototype.bind:
export const dropzoneConfig = {
url: api.http.baseUrl + '/files',
thumbnailWidth: 150,
maxFilesize: 5,
acceptedFiles: 'image/*',
addRemoveLinks: true,
sending: function (file, xhr, formData) {
formData.append('type', 'photo');
},
success: function (file, xhr) {
file.id = xhr.data.id;
if (this.entry.files === undefined) {
this.entry.files = [];
}
this.entry.files.push(xhr.data);
this.saveNote();
}.bind(this),
headers: api.http.authHeaders().headers
};
In the code above, this.entry and this.saveNote are unavailable because they're from my Vue component. How do I make them accessible to the external file?
A more general solution would be for the component to pass in a success-event handler that has access to the component's data and methods, as shown below. This solution decouples the configuration from the component's internals.
dropzoneConfig.js:
export const dropzoneConfig = ({ onSuccess }) => ({
//...
success(file, xhr) {
//...
onSuccess(xhr.data)
}
})
App.vue:
<script>
import Dropzone from 'dropzone'
import { dropzoneConfig } from './dropzoneConfig'
export default {
data() {
return {
entry: {
files: []
}
}
},
created() {
Dropzone.options.myComponent = dropzoneConfig({
onSuccess: fileData => this.onDropzoneSuccess(fileData)
})
},
methods: {
saveNote() {
//...
},
onDropzoneSuccess(fileData) {
this.entry.files.push(fileData)
this.saveNote()
}
}
}
</script>

VueJS CKeditor5 upload images

Having trouble with uploading images using CKeditor5 in Vuejs.
First having tried Simple upload Adapter which gave me the following error:
Reason: CKEditorError: ckeditor-duplicated-modules: Some CKEditor 5 modules are duplicated. Read more: https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html#error-ckeditor-duplicated-modules
I tried making a upload adapter. As a uploadadapter I took the example and modified the url. The uploadadapter.js file looks like the following:
export default class UploadAdapter {
constructor( loader ) {
// The file loader instance to use during the upload.
this.loader = loader;
}
// Starts the upload process.
upload() {
return this.loader.file
.then( file => new Promise( ( resolve, reject ) => {
this._initRequest();
this._initListeners( resolve, reject, file );
this._sendRequest( file );
} ) );
}
// Aborts the upload process.
abort() {
if ( this.xhr ) {
this.xhr.abort();
}
}
// Initializes the XMLHttpRequest object using the URL passed to the constructor.
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
xhr.open( 'POST', '<url here>', true );
xhr.responseType = 'json';
}
// Initializes XMLHttpRequest listeners.
_initListeners( resolve, reject, file ) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${ file.name }.`;
xhr.addEventListener( 'error', () => reject( genericErrorText ) );
xhr.addEventListener( 'abort', () => reject() );
xhr.addEventListener( 'load', () => {
const response = xhr.response;
if ( !response || response.error ) {
return reject( response && response.error ? response.error.message : genericErrorText );
}
resolve( {
default: response.url
} );
} );
if ( xhr.upload ) {
xhr.upload.addEventListener( 'progress', evt => {
if ( evt.lengthComputable ) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
} );
}
}
// Prepares the data and sends the request.
_sendRequest( file ) {
// Prepare the form data.
const data = new FormData();
data.append( 'upload', file );
// Send the request.
this.xhr.send( data );
}
}
The Vue component:
<template>
<form #submit.prevent="store">
<ckeditor
:editor="editor"
v-model="form.content"
:error-messages="errors.content"
:config="editorConfig"
/>
</form>
</template>
<script>
import CKEditor from '#ckeditor/ckeditor5-vue';
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
import UploadAdapter from '../../UploadAdapter';
export default {
data()
{
return {
form: {
content: null,
},
editor: ClassicEditor,
editorConfig: {
toolbar: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', '|', 'insertTable', '|', 'imageUpload', 'mediaEmbed', '|', 'undo', 'redo' ],
table: {
toolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
},
extraPlugin: [this.uploader],
language: 'nl',
},
}
},
methods: {
store()
{
// Some code
},
uploader(editor)
{
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
return new UploadAdapter( loader );
};
},
},
components: {
ckeditor: CKEditor.component
}
}
</script>
However each time when trying to upload a file the following warning is returned:
filerepository-no-upload-adapter: Upload adapter is not defined. Read more: https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html#error-filerepository-no-upload-adapter
Have looked at the url but it just sends me in circles thus making no progress. What I'm looking for is an example that at least sends a file to the server without errors/ warnings. If the uploadadapter can be scraped and something else except CKfinder can be used that's fine. For now I guess the problem is most likely to be in the Vue component.
use extraPlugins instead of extraPlugin.
moving uploader function to outside of vue component and then using it directly as
extraPlugin: [uploader]
worked for me.

Vue.js: progress upload doesn't render in view

I'm trying to make an uploader component in Vuejs + Firebase Storage. The method works fine, but I can't get to show the percent uploaded in the view. Here my code:
<template>
<div>
<input type="file" multiple accept="image/*" #change="detectFiles($event.target.files)">
<div>{{progressUpload}}</div>
</div>
</template>
<script>
import { storage } from '../firebase'
export default {
data () {
return {
progressUpload: 0,
file: File,
}
},
methods: {
detectFiles (fileList) {
Array.from(Array(fileList.length).keys()).map( x => {
this.upload(fileList[x])
})
},
upload (file) {
let uploadTask = storage.ref('imagenes/articulos').put(file);
uploadTask.on('state_changed', function(snapshot) {
this.progressUpload = Math.floor(snapshot.bytesTransferred / snapshot.totalBytes * 100);
})
uploadTask.then(() => {
console.info('File uploaded!');
})
}
}
}
</script>
Writing {{ progressUpload }} in the HTML template, doesn't show nothing. But in the console, the result is logged perfectly. The file is uploaded properly
Ok, I found the solution. I had to add the uploadProgress calculation into a watcher that returns the new value of the upload task async method. So, my script has been as follows:
<script>
import { storage } from '../firebase'
export default {
data () {
return {
progressUpload: 0,
file: File,
uploadTask: ''
}
},
methods: {
detectFiles (fileList) {
Array.from(Array(fileList.length).keys()).map( x => {
this.upload(fileList[x])
})
},
upload (file) {
this.uploadTask = storage.ref('imagenes/articulos').put(file);
this.uploadTask.then(() => {
console.info('Archivo guardado correctamente');
})
}
},
watch: {
uploadTask: function() {
this.uploadTask.on('state_changed', sp => {
this.progressUpload = Math.floor(sp.bytesTransferred / sp.totalBytes * 100)
})
}
}
}
</script>