From 66713178cd9cadbc9c0523b2bdbb0af679a79aee Mon Sep 17 00:00:00 2001 From: Berenz Date: Mon, 22 May 2017 12:51:31 -0400 Subject: [PATCH] Internal loop on multiple config printing --- js/qz-tray.js | 166 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 126 insertions(+), 40 deletions(-) diff --git a/js/qz-tray.js b/js/qz-tray.js index ac4d3acfa..cc38dc590 100644 --- a/js/qz-tray.js +++ b/js/qz-tray.js @@ -509,6 +509,36 @@ var qz = (function() { return loc; }, + relative: function(data) { + for(var i = 0; i < data.length; i++) { + if (data[i].constructor === Object) { + var absolute = false; + + if (data[i].flavor) { + //if flavor is known, we can directly check for absolute flavor types + var flavor = data[i].flavor.toUpperCase(); + if (flavor === 'FILE' || flavor === 'IMAGE' || flavor === 'XML') { + absolute = true; + } + } else if (data[i].format && data[i].format.toUpperCase() !== 'COMMAND' && data[i].format.toUpperCase() !== 'DIRECT') { + //if flavor is not known, all valid pixel formats default to file flavor + absolute = true; + } else if (data[i].type && data[i].type.toUpperCase() === 'PIXEL') { + //if all we know is pixel type, then it is image's file flavor + absolute = true; + } + + if (absolute) { + //change relative links to absolute + data[i].data = _qz.tools.absolute(data[i].data); + } + if (data[i].options && typeof data[i].options.overlay === 'string') { + data[i].options.overlay = _qz.tools.absolute(data[i].options.overlay); + } + } + } + }, + /** Performs deep copy to target from remaining params */ extend: function(target) { //special case when reassigning properties as objects in a deep copy @@ -575,9 +605,9 @@ var qz = (function() { /** * Alter any of the printer options currently applied to this config. - * @param newOpts {Object} The options to change. See qz.config.setDefaults docs for available values. + * @param newOpts {Object} The options to change. See qz.configs.setDefaults docs for available values. * - * @see qz.config.setDefaults + * @see qz.configs.setDefaults */ this.reconfigure = function(newOpts) { _qz.tools.extend(this.config, newOpts); @@ -901,7 +931,7 @@ var qz = (function() { * * @returns {Config} The new config. * - * @see config.setDefaults + * @see configs.setDefaults * * @memberof qz.configs */ @@ -921,7 +951,7 @@ var qz = (function() { * following the format of the "call" and "params" keys in the API call, with the addition of a "timestamp" key in milliseconds * ex. '{"call":"","params":{...},"timestamp":1450000000}' * - * @param {Object} config Previously created config object. + * @param {Object|Array>} configs Previously created config object or objects. * @param {Array} data Array of data being sent to the printer.
* String values are interpreted as {type: 'raw', format: 'command', flavor: 'plain', data: <string>}. * @param {string} data.data @@ -941,56 +971,112 @@ var qz = (function() { * @param {string|number} [data.options.dotDensity] Optional with [raw] type + [image] format. * @param {number} [data.precision=128] Optional with [raw] type [image] format. Bit precision of the ribbons. * @param {boolean|string|Array>} [data.options.overlay=false] Optional with [raw] type [image] format. - * Boolean sets entire layer, string sets mask image, Array sets array of rectangles in format [x1,y1,x2,y2]. + * Boolean sets entire layer, string sets mask image, Array sets array of rectangles in format [x1,y1,x2,y2]. * @param {string} [data.options.xmlTag] Required with [xml] flavor. Tag name containing base64 formatted data. * @param {number} [data.options.pageWidth] Optional with [html | pdf] formats. Width of the rendering. - * Defaults to paper width. + * Defaults to paper width. * @param {number} [data.options.pageHeight] Optional with [html | pdf] formats. Height of the rendering. - * Defaults to paper height for [pdf], or auto sized for [html]. - * @param {string} [signature] Pre-signed signature of hashed JSON string containing call='print', params, and timestamp. - * @param {number} [signingTimestamp] Required with signature. Timestamp used with pre-signed content. + * Defaults to paper height for [pdf], or auto sized for [html]. + * @param {...*} [arguments] Additionally three more parameters can be specified:

+ * {boolean} [resumeOnError=false] Whether the chain should continue printing if it hits an error on one the the prints.

+ * {string|Array} [signature] Pre-signed signature(s) of the JSON string for containing call, params, and timestamp.

+ * {number|Array} [signingTimestamps] Required to match with signature. Timestamps for each of the passed pre-signed content. * * @returns {Promise} * - * @see qz.config.create + * @see qz.configs.create * * @memberof qz */ - print: function(config, data, signature, signingTimestamp) { - //change relative links to absolute - for(var i = 0; i < data.length; i++) { - if (data[i].constructor === Object) { - var absolute = false; - - if (data[i].flavor) { - //if flavor is known, we can directly check for absolute flavor types - var flavor = data[i].flavor.toUpperCase(); - if (flavor === 'FILE' || flavor === 'IMAGE' || flavor === 'XML') { - absolute = true; - } - } else if (data[i].format && data[i].format.toUpperCase() !== 'COMMAND' && data[i].format.toUpperCase() !== 'DIRECT') { - //if flavor is not known, all valid pixel formats default to file flavor - absolute = true; - } else if (data[i].type && data[i].type.toUpperCase() === 'PIXEL') { - //if all we know is pixel type, then it is image's file flavor - absolute = true; - } - - if (absolute) { - data[i].data = _qz.tools.absolute(data[i].data); - } - if (data[i].options && typeof data[i].options.overlay === 'string') { - data[i].options.overlay = _qz.tools.absolute(data[i].options.overlay); + print: function(configs, data) { + var resumeOnError = false, + signatures = null, + signaturesTimestamps = null; + + //find optional parameters + if (arguments.length >= 3) { + if (typeof arguments[2] === 'boolean') { + resumeOnError = arguments[2]; + + if (arguments.length >= 5) { + signatures = arguments[3]; + signaturesTimestamps = arguments[4]; } + } else if (arguments.length >= 4) { + signatures = arguments[2]; + signaturesTimestamps = arguments[3]; } + + //ensure values are arrays for consistency + if (signatures && !Array.isArray(signatures)) { signatures = [signatures]; } + if (signaturesTimestamps && !Array.isArray(signaturesTimestamps)) { signaturesTimestamps = [signaturesTimestamps]; } } - var params = { - printer: config.getPrinter(), - options: config.getOptions(), - data: data + if (!Array.isArray(configs)) { configs = [configs]; } + + //clean up data formatting + _qz.tools.relative(data); + + var sendToPrint = function(mapping) { + var params = { + printer: mapping.config.getPrinter(), + options: mapping.config.getOptions(), + data: mapping.data + }; + + return _qz.websocket.dataPromise('print', params, mapping.signature, mapping.timestamp); }; - return _qz.websocket.dataPromise('print', params, signature, signingTimestamp); + + //presort to group config data + var map = {}; + var signIdx = 0; + for(var i = 0; i < configs.length || i < data.length; i++) { + var cfg = configs[Math.min(i, configs.length - 1)]; + var ref = cfg.printer.name; + + if (!map[ref]) { + map[ref] = { + config: cfg, + data: [], + signature: signatures[signIdx], + timestamp: signaturesTimestamps[signIdx] + }; + signIdx++; + } + + map[ref].data.push(data[Math.min(i, data.length - 1)]); + } + + //chain instead of Promise.all, so resumeOnError can collect each error + var chain = []; + for(var set in map) { + (function(set_) { + chain.push(function() { return sendToPrint(map[set_]); }); + })(set); + } + + //setup to catch errors if needed + var fallThrough = null; + if (resumeOnError) { + var fallen = []; + fallThrough = function(err) { fallen.push(err); }; + + //final promise to reject any errors as a group + chain.push(function() { + return _qz.tools.promise(function(resolve, reject) { + fallen.length ? reject(fallen) : resolve(); + }); + }); + } + + var last = null; + chain.reduce(function(sequence, link) { + last = sequence.catch(fallThrough).then(link); //catch is ignored if fallThrough is null + return last; + }, _qz.tools.promise(function(r) { r(); })); //an immediately resolved promise to start off the chain + + //return last promise so users can chain off final action or catch when stopping on error + return last; },