Как применить веб-работника для рендеринга PDF с использованием makepdf
Я успешно создал PDF с помощью плагина JavaScript ( pdfmake), и это было здорово. Но когда я пытаюсь сделать распечатку из инвентаря / бухгалтерской книги на ~8000 строк, она останавливается более минуты.
Вот как я обычно объявляю свое docDefinition
var docDefinition = {
pageOrientation: orientation,
footer: function(currentPage, pageCount) { return {text: currentPage.toString() + ' / ' + pageCount, fontSize:8, alignment:'center'}; },
content:[
printHeader,
{ fontSize: 8, alignment: 'right', style: 'tableExample',
table: {
widths: width,
headerRows: 1, body: arr },
layout: 'lightHorizontalLines' }] }
где
var printHeader = [ { text: 'COMPANY NAME',alignment:'center' },
{ text: 'Address 1',alignment:'center' },
{ text: 'Address 2',alignment:'center' },
{ text: 'Additional Details,alignment:'center' },
{ text: 'document title',alignment:'center' }];
а также
var arr = [[{"text":"","alignment":"left"},"text":"Date","alignment":"left"},
{"text":"Trans #","alignment":"left"},{"text":"Description","alignment":"left"},
{"text":"Ref #","alignment":"left"},{"text":"Debit","alignment":"left"},
{"text":"Credit","alignment":"left"},{"text":"Amount","alignment":"left"},
{"text":"Balance","alignment":"left"}],[{"text":"ACCOUNT : Merchandise Inventory","alignment":"left","colSpan":8},"","","","","","","",
{"text":"1,646,101.06"}],["","10/13/2015","ST#0094",{"text":"","alignment":"left"},{"text":"","alignment":"left"},"546.94","0.00","546.94","1,646,648.00"],[{"text":"Total","alignment":"left","bold":true},"","","","",
{"text":"546.94","alignment":"right","bold":true},
{"text":"0.00","alignment":"right","bold":true},
{"text":"","alignment":"right","bold":true},
{"text":"546.94","alignment":"right","bold":true}],[{"text":"ACCOUNT : Accounts Payable-Main","alignment":"left","colSpan":8},"","","","","","","",
{"text":"-1,741,953.62"}],["","10/13/2015","ST#0094",
{"text":"","alignment":"left"},
{"text":"","alignment":"left"},"0.00","546.94","-546.94","-1,742,500.56"],
[{"text":"Total","alignment":"left","bold":true},"","","","",
{"text":"0.00","alignment":"right","bold":true},
{"text":"546.94","alignment":"right","bold":true},
{"text":"","alignment":"right","bold":true},
{"text":"-546.94","alignment":"right","bold":true}]
Я искал о веб-работников и вижу, что это может решить эту проблему замораживания пользовательского интерфейса. Поэтому я попытался создать для него веб-работника:
$('#makepdf').click(function(){
var worker = new Worker("<?php echo URL::to('/'); ?>/js/worker.js");
worker.addEventListener('message',function(e){
console.log('Worker said: ',e.data);
},false);
worker.postMessage(docDefinition);
//worker.js
self.addEventListener('message', function(e) {
self.postMessage(e.data);
}, false);
Выход из console.log()
:
Работник сказал: Object {pageOrientation: "Portrait", Content: Array[7]} правильно записывает в журнал структуру json.
Все идет нормально. Но после того, как я добавил pdfmake.min.js
а также vfs_font.js
к работнику я получаю ошибку Uncaught TypeError: Cannot read property 'createElementNS' of undefined
,
Я получаю сообщение об ошибке еще до того, как начал использовать рабочий.
Можно ли реализовать веб-работников с помощью плагина pdfmake?
1 ответ
Простой ответ
Просто предоставьте фиктивный конструктор:
var document = { 'createElementNS': function(){ return {} } };
var window = this;
importScripts( 'pdfmake.min.js', 'vfs_fonts.js' );
В качестве альтернативы, если вы считаете, что это слишком грязно, импортируйте XML-JS (всего 60 КБ) и создайте виртуальный документ для pdfmake.
importScripts( 'tinyxmlw3cdom.js' );
var window = this;
var document = new DOMDocument( new DOMImplementation() );
importScripts( 'pdfmake.min.js', 'vfs_fonts.js' );
объяснение
Известно, что pdfmake несовместим с работником.
Сам по себе pdfmake не использует createElementNS. Однако его минимизированный сценарий pdfmake.min.js
сделать, по-видимому, чтобы создать ссылку для скачивания.
В любом случае, нам не нужна ссылка на скачивание, поэтому просто оставьте ее для того, чтобы она оставалась счастливой (пока).
Если в будущем ему нужен настоящий DOM, плохая новость в том, что документ недоступен для веб-работника. Хорошая новость в том, что у нас есть чистая реализация javascript. Скачайте XML-JS, распакуйте и найдите tinyxmlw3cdom.js
, импортируйте его, и вы сможете создать функциональный документ.
В дополнение к документу, так как vfs_fonts.js
получить pdfmake через window
переменная, нам нужно ввести окно как псевдоним для глобального.
Для меня эти шаги заставляют pdfmake работать в веб-приложении.
Чтобы сохранить файл, вам необходимо преобразовать данные base64, предоставленные pdfmake, в двоичную загрузку. Доступно несколько скриптов, таких как download.js. В приведенном ниже коде я использую FileSaver.
Код
Мой работник - это строитель, который можно кэшировать. Если вы работаете с сервером на стороне сервера, вы можете избавиться от стека и построить функции и напрямую вызвать pdfmake, что значительно упрощает оба js-кода.
Основной HTML:
<script src='FileSaver.min.js'></script>
<script>
function base64ToBlob( base64, type ) {
var bytes = atob( base64 ), len = bytes.length;
var buffer = new ArrayBuffer( len ), view = new Uint8Array( buffer );
for ( var i=0 ; i < len ; i++ )
view[i] = bytes.charCodeAt(i) & 0xff;
return new Blob( [ buffer ], { type: type } );
}
//////////////////////////////////////////////////////////
var pdfworker = new Worker( 'worker.js' );
pdfworker.onmessage = function( evt ) {
// open( 'data:application/pdf;base64,' + evt.data.base64 ); // Popup PDF
saveAs( base64ToBlob( evt.data.base64, 'application/pdf' ), 'General Ledger.pdf' );
};
function pdf( action, data ) {
pdfworker.postMessage( { action: action, data: data } );
}
pdf( 'add', 'Hello WebWorker' );
pdf( 'add_table', { headerRows: 1 } );
pdf( 'add', [ 'First', 'Second', 'Third', 'The last one' ] );
pdf( 'add', [ { text: 'Bold value', bold: true }, 'Val 2', 'Val 3', 'Val 4' ] );
pdf( 'close_table' );
pdf( 'add', { text: 'This paragraph will have a bigger font', fontSize: 15 } );
pdf( 'gen_pdf' ); // Triggers onmessage when it is done
// Alternative, one-size-fit-all usage
pdf( 'set', { pageOrientation: 'landscape', footer: { text: 'copyright 2015', fontSize: 8, alignment:'center'}, content:[ "header", { fontSize: 8, alignment: 'right', table: { headerRows: 1, body: [[1,2,3],[4,5,6]] } }] } );
pdf( 'gen_pdf' );
</script>
рабочий:
//importScripts( 'tinyxmlw3cdom.js' );
//var document = new DOMDocument( new DOMImplementation() );
var document = { 'createElementNS': function(){ return {} } };
var window = this;
importScripts( 'pdfmake.min.js', 'vfs_fonts.js' );
(function() { 'use strict';
var doc, current, context_stack;
function set ( data ) {
doc = data;
if ( ! doc.content ) doc.content = [];
current = doc.content;
context_stack = [ current ];
}
set( {} );
function add ( data ) {
current.push( data );
}
function add_table ( template ) {
if ( ! template ) template = {};
if ( ! template.table ) template = { table: template };
if ( ! template.table.body ) template.table.body = [];
current.push( template ); // Append table
push( template.table.body ); // Switch context to table body
}
function push ( data ) {
context_stack.push( current );
return current = data;
}
function pop () {
if ( context_stack.length <= 1 ) return console.warn( "Cannot close pdf root" );
context_stack.length -= 1;
return current = context_stack[ context_stack.length-1 ];
}
function gen_pdf() {
pdfMake.createPdf( doc ).getBase64( function( base64 ) {
postMessage( { action: 'gen_pdf', base64: base64 } );
} );
}
onmessage = function( evt ) {
var action = evt.data.action, data = evt.data.data;
switch ( action ) {
case 'set': set( data ); break;
case 'add': add( data ); break;
case 'add_table' : add_table( data ); break;
case 'close_table': pop(); break;
case 'gen_pdf': gen_pdf(); break;
}
};
})();