diff --git a/openslides/assignments/static/js/assignments/pdf.js b/openslides/assignments/static/js/assignments/pdf.js new file mode 100644 index 000000000..24c4c95fa --- /dev/null +++ b/openslides/assignments/static/js/assignments/pdf.js @@ -0,0 +1,446 @@ +(function () { + +'use strict'; + +angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf']) + +.factory('AssignmentContentProvider', [ + 'gettextCatalog', + 'PdfPredefinedFunctions', + function(gettextCatalog, PdfPredefinedFunctions) { + + var createInstance = function(scope, polls) { + + //use the Predefined Functions to create the title + var title = PdfPredefinedFunctions.createTitle( + gettextCatalog.getString("Election") + ": " + scope.assignment.title); + + //create the preamble + var createPreamble = function() { + var preambleText = gettextCatalog.getString("Number of posts to be elected") + ": "; + var memberNumber = ""+scope.assignment.open_posts; + var preamble = { + text: [ + { + text: preambleText, + bold: true, + style: 'textItem' + }, + { + text: memberNumber, + style: 'textItem' + } + ] + }; + return preamble; + }; + + //adds the description if present in the assignment + var createDescription = function() { + if (scope.assignment.description) { + var descriptionText = gettextCatalog.getString("Description") + ":"; + var description = [ + { + text: descriptionText, + bold: true, + style: 'textItem' + }, + { + text: scope.assignment.description, + style: 'textItem', + margin: [10, 0, 0, 0] + } + ]; + return description; + } else { + return ""; + } + }; + + //creates the candidate list in columns if the assignment phase is 'voting' + var createCandidateList = function() { + if (scope.assignment.phase != 2) { + var candidatesText = gettextCatalog.getString("Candidates") + ": "; + var userList = []; + + angular.forEach(scope.assignment.assignment_related_users, function(assignmentsRelatedUser) { + userList.push({ + text: assignmentsRelatedUser.user.get_full_name(), + margin: [0, 0, 0, 10], + } + ); + }); + + var cadidateList = { + columns: [ + { + text: candidatesText, + bold: true, + width: "25%", + style: 'textItem' + }, + { + ol: userList, + style: 'textItem' + } + ] + }; + return cadidateList; + } else { + return ""; + } + }; + + //handles the case if a candidate is elected or not + var electedCandidateLine = function(candidateName, pollOption, pollTableBody) { + if (pollOption.is_elected) { + return { + text: candidateName + "*", + bold: true, + style: PdfPredefinedFunctions.flipTableRowStyle(pollTableBody.length) + }; + } else { + return { + text: candidateName, + style: PdfPredefinedFunctions.flipTableRowStyle(pollTableBody.length) + }; + } + }; + + //creates the pull result table + var createPollResultTable = function() { + var resultBody = []; + angular.forEach(polls, function(poll, pollIndex) { + if (poll.published) { + var voteNrTotal = poll.votescast; + var voteNrValid = poll.votesvalid; + var voteNrInVal = poll.votesinvalid; + var pollTableBody = []; + + resultBody.push({ + text: gettextCatalog.getString("Ballot") + " " + (pollIndex+1), + bold: true, + style: 'textItem', + margin: [0,15,0,0] + }); + + pollTableBody.push([ + { + text: gettextCatalog.getString("Candidates"), + style: 'tableHeader', + }, + { + text: gettextCatalog.getString("Votes"), + style: 'tableHeader', + } + ]); + + angular.forEach(poll.options, function(pollOption, optionIndex) { + var candidateName = pollOption.candidate.get_full_name(); + var votes = pollOption.getVotes(); // 0 = yes, 1 = no, 2 = abstain + var candidateLine; + + if (poll.pollmethod == 'votes') { + pollTableBody.push([ + electedCandidateLine(candidateName, pollOption, pollTableBody), + { + text: votes[0].value + " " + votes[0].percentStr, + style: PdfPredefinedFunctions.flipTableRowStyle(pollTableBody.length) + } + ]); + } else if (poll.pollmethod == 'yn') { + pollTableBody.push([ + { + text: candidateName, + style: PdfPredefinedFunctions.flipTableRowStyle(pollTableBody.length) + }, + { + text: [ + { + text: votes[0].label + ": " + votes[0].value + " " + votes[0].percentStr + "\n" + }, + { + text: votes[1].label + ": " + votes[1].value + " " + votes[1].percentStr + } + ], + style: PdfPredefinedFunctions.flipTableRowStyle(pollTableBody.length) + } + ]); + } else if (poll.pollmethod == 'yna') { + pollTableBody.push([ + { + text: candidateName, + style: PdfPredefinedFunctions.flipTableRowStyle(pollTableBody.length) + }, + { + text: [ + { + text: votes[0].label + ": " + votes[0].value + " " + votes[0].percentStr + "\n" + }, + { + text: votes[1].label + ": " + votes[1].value + " " + votes[1].percentStr + "\n" + }, + { + text: votes[2].label + ": " + votes[2].value + " " + votes[2].percentStr + } + ], + style: PdfPredefinedFunctions.flipTableRowStyle(pollTableBody.length) + } + ]); + } + }); + + //it is technically possible to make a single push-statement + //however, the flipTableRowStyle-function needs the current table size + if (voteNrValid) { + pollTableBody.push([ + { + text: gettextCatalog.getString("Valid ballots"), + style: 'tableConclude' + }, + { + text: ""+voteNrValid, + style: 'tableConclude' + }, + ]); + } + + if (voteNrInVal) { + pollTableBody.push([ + { + text: gettextCatalog.getString("Invalid ballots"), + style: 'tableConclude' + }, + { + text: ""+voteNrInVal, + style: 'tableConclude' + }, + ]); + } + + if (voteNrTotal) { + pollTableBody.push([ + { + text: gettextCatalog.getString("Casted ballots"), + style: 'tableConclude' + }, + { + text: ""+voteNrTotal, + style: 'tableConclude' + }, + ]); + } + + var resultTableJsonSting = { + table: { + widths: ['64%','33%'], + headerRows: 1, + body: pollTableBody, + }, + layout: 'headerLineOnly', + }; + + resultBody.push(resultTableJsonSting); + } + }); + + //Add the Legend to the result body + resultBody.push({ + text: "* = " + gettextCatalog.getString("is elected"), + margin: [0,5,0,0], + }); + + return resultBody; + }; + + var getContent = function() { + return [ + title, + createPreamble(), + createDescription(), + createCandidateList(), + createPollResultTable() + ]; + }; + + return { + getContent: getContent + }; + }; + + return { + createInstance: createInstance + }; +}]) + +.factory('BallotContentProvider', [ + 'gettextCatalog', + 'PdfPredefinedFunctions', + function(gettextCatalog, PdfPredefinedFunctions) { + + var createInstance = function(scope, poll, pollNumber) { + + // use the Predefined Functions to create the title + var createTitle = function() { + return { + text: gettextCatalog.getString("Election") + ": " + scope.assignment.title, + style: 'title', + }; + }; + + //function to create the poll hint + var createPollHint = function() { + return { + text: gettextCatalog.getString("Ballot") + " " + pollNumber + ": " + poll.description, + style: 'description', + }; + }; + + //function to create the selection entries + var createYNBallotEntry = function(decision) { + var YNColumn = [ + { + width: "auto", + stack: [ + PdfPredefinedFunctions.createBallotEntry(gettextCatalog.getString("Yes")) + ] + }, + { + width: "auto", + stack: [ + PdfPredefinedFunctions.createBallotEntry(gettextCatalog.getString("No")) + ] + }, + ]; + + if (poll.pollmethod == 'yna') { + YNColumn.push({ + width: "auto", + stack: [ + PdfPredefinedFunctions.createBallotEntry(gettextCatalog.getString("Abstain")) + ] + }); + } + + return [ + { + text: decision, + margin: [40, 10, 0, 0], + }, + { + columns: YNColumn + } + ]; + }; + + var createSelectionField = function() { + var candidateBallotList = []; + + if (poll.pollmethod == 'votes') { + angular.forEach(poll.options, function(option) { + var candidate = option.candidate.get_full_name(); + candidateBallotList.push(PdfPredefinedFunctions.createBallotEntry(candidate)); + }); + } else { + angular.forEach(poll.options, function(option) { + var candidate = option.candidate.get_full_name(); + candidateBallotList.push(createYNBallotEntry(candidate)); + }); + } + return candidateBallotList; + }; + + var createSection = function(marginTop) { + + // since it is not possible to give a column a fixed height, we draw an "empty" column + // with a one px width and a fixed top-margin + + return { + columns : [ + { + width: 1, + margin: [0,marginTop], + text: "" + }, + { + width: '*', + stack: [ + createTitle(), + createPollHint(), + createSelectionField(), + ] + } + ] + }; + }; + + var createTableBody = function(numberOfRows, sheetend) { + var tableBody = []; + for (var i = 0; i < numberOfRows; i++) { + tableBody.push([createSection(sheetend), createSection(sheetend)]); + } + return tableBody; + }; + + var createContentTable = function() { + + var tableBody = []; + var sheetend; + + if (poll.pollmethod == 'votes') { + if (poll.options.length <= 4) { + sheetend = 105; + tableBody = createTableBody(4, sheetend); + } else if (poll.options.length <= 8) { + sheetend = 140; + tableBody = tableBody = createTableBody(3, sheetend); + } else if (poll.options.length <= 12) { + sheetend = 210; + tableBody = tableBody = createTableBody(2, sheetend); + } + else { //works untill ~30 people + sheetend = 418; + tableBody = createTableBody(1, sheetend); + } + } else { + if (poll.options.length <= 2) { + sheetend = 105; + tableBody = createTableBody(4, sheetend); + } else if (poll.options.length <= 4) { + sheetend = 140; + tableBody = createTableBody(3, sheetend); + } else if (poll.options.length <= 6) { + sheetend = 210; + tableBody = createTableBody(2, sheetend); + } else { + sheetend = 418; + tableBody = createTableBody(1, sheetend); + } + } + + return [{ + table: { + headerRows: 1, + widths: ['50%', '50%'], + body: tableBody + }, + layout: PdfPredefinedFunctions.getBallotLayoutLines() + }]; + }; + + var getContent = function() { + return createContentTable(); + }; + + return { + getContent: getContent + }; + }; + + return { + createInstance: createInstance + }; + +}]); + +}()); diff --git a/openslides/assignments/static/js/assignments/site.js b/openslides/assignments/static/js/assignments/site.js index bb801614b..a69b09bd6 100644 --- a/openslides/assignments/static/js/assignments/site.js +++ b/openslides/assignments/static/js/assignments/site.js @@ -2,7 +2,11 @@ 'use strict'; -angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) +angular.module('OpenSlidesApp.assignments.site', [ + 'OpenSlidesApp.assignments', + 'OpenSlidesApp.core.pdf', + 'OpenSlidesApp.assignments.pdf' +]) .config([ 'mainMenuProvider', @@ -351,8 +355,14 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) 'phases', 'Projector', 'ProjectionDefault', + 'AssignmentContentProvider', + 'BallotContentProvider', + 'PdfMakeDocumentProvider', + 'PdfMakeBallotPaperProvider', + 'gettextCatalog', function($scope, $http, filterFilter, gettext, ngDialog, AssignmentForm, operator, Assignment, User, - assignment, phases, Projector, ProjectionDefault) { + assignment, phases, Projector, ProjectionDefault, AssignmentContentProvider, BallotContentProvider, + PdfMakeDocumentProvider, PdfMakeBallotPaperProvider, gettextCatalog) { User.bindAll({}, $scope, 'users'); Assignment.bindOne(assignment.id, $scope, 'assignment'); Assignment.loadRelations(assignment, 'agenda_item'); @@ -512,6 +522,30 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) }; + //creates the document as pdf + $scope.makePDF_singleAssignment = function() { + var filename = gettextCatalog.getString("Election") + " " + $scope.assignment.title + ".pdf"; + var assignmentContentProvider = AssignmentContentProvider.createInstance($scope, assignment.polls); + var documentProvider = PdfMakeDocumentProvider.createInstance(assignmentContentProvider); + pdfMake.createPdf(documentProvider.getDocument()).download(filename); + }; + + //creates the ballotpaper as pdf + $scope.makePDF_assignmentpoll = function(pollID) { + var thePoll; + var pollNumber; + angular.forEach(assignment.polls, function(poll, pollIndex) { + if (poll.id == pollID) { + thePoll = poll; + pollNumber = pollIndex+1; + } + }); + var filename = gettextCatalog.getString("Ballot") + " " + pollNumber + " " + $scope.assignment.title + ".pdf"; + var ballotContentProvider = BallotContentProvider.createInstance($scope, thePoll, pollNumber); + var documentProvider = PdfMakeBallotPaperProvider.createInstance(ballotContentProvider); + pdfMake.createPdf(documentProvider.getDocument()).download(filename); + }; + // Just mark some vote value strings for translation. gettext('Yes'); gettext('No'); diff --git a/openslides/assignments/static/templates/assignments/assignment-detail.html b/openslides/assignments/static/templates/assignments/assignment-detail.html index aec880985..b498f4ae1 100644 --- a/openslides/assignments/static/templates/assignments/assignment-detail.html +++ b/openslides/assignments/static/templates/assignments/assignment-detail.html @@ -21,7 +21,7 @@ - + PDF @@ -143,8 +143,7 @@ - + Print ballot paper diff --git a/openslides/core/static/js/core/pdf.js b/openslides/core/static/js/core/pdf.js index 8b4f4f76c..bf0a2a16d 100644 --- a/openslides/core/static/js/core/pdf.js +++ b/openslides/core/static/js/core/pdf.js @@ -7,6 +7,10 @@ angular.module('OpenSlidesApp.core.pdf', []) .factory('PdfPredefinedFunctions', [ function() { var PdfPredefinedFunctions = {}; + var BallotCircleDimensions = { + yDistance: 6, + size: 8 + }; PdfPredefinedFunctions.createTitle = function(titleString) { return { @@ -15,6 +19,62 @@ angular.module('OpenSlidesApp.core.pdf', []) }; }; + PdfPredefinedFunctions.flipTableRowStyle = function(currentTableSize) { + if (currentTableSize % 2 === 0) { + return "tableEven"; + } else { + return "tableOdd"; + } + }; + + //draws a circle + PdfPredefinedFunctions.drawCircle = function(y, size) { + return [ + { + type: 'ellipse', + x: 0, + y: y, + lineColor: 'black', + r1: size, + r2: size + } + ]; + }; + + //Returns an entry in the ballot with a circle to draw into + PdfPredefinedFunctions.createBallotEntry = function(decision) { + return { + margin: [40+BallotCircleDimensions.size, 10, 0, 0], + columns: [ + { + width: 15, + canvas: PdfPredefinedFunctions.drawCircle(BallotCircleDimensions.yDistance, BallotCircleDimensions.size) + }, + { + width: "auto", + text: decision + } + ], + }; + }; + + PdfPredefinedFunctions.getBallotLayoutLines = function() { + return { + hLineWidth: function(i, node) { + return (i === 0 || i === node.table.body.length) ? 0 : 0.5; + }, + vLineWidth: function(i, node) { + return (i === 0 || i === node.table.widths.length) ? 0 : 0.5; + }, + hLineColor: function(i, node) { + return (i === 0 || i === node.table.body.length) ? 'none' : 'gray'; + }, + vLineColor: function(i, node) { + return (i === 0 || i === node.table.widths.length) ? 'none' : 'gray'; + }, + }; + }; + return PdfPredefinedFunctions; } ]) @@ -113,6 +173,10 @@ angular.module('OpenSlidesApp.core.pdf', []) margin: [0,0,0,20], bold: true }, + textItem: { + fontSize: 11, + margin: [0,7] + }, heading: { fontSize: 16, margin: [0,0,0,10], @@ -131,13 +195,18 @@ angular.module('OpenSlidesApp.core.pdf', []) margin: [0,5] }, tableHeader: { - bold: true + bold: true, + fillColor: 'white' }, tableEven: { fillColor: 'white' }, tableOdd: { - fillColor: '#e7e7e7' + fillColor: '#eee' + }, + tableConclude: { + fillColor: '#ddd', + bold: true } } }; @@ -152,6 +221,54 @@ angular.module('OpenSlidesApp.core.pdf', []) } ]) +.factory('PdfMakeBallotPaperProvider', [ + 'gettextCatalog', + 'Config', + function(gettextCatalog, Config) { + /** + * Provides the global Document + * @constructor + * @param {object} contentProvider - Object with on method `getContent`, which returns an array for content + * @param {string} defaultFont - Default font for the document + */ + var createInstance = function(contentProvider, defaultFont) { + /** + * Generates the document(definition) for pdfMake + * @function + */ + var getDocument = function() { + var content = contentProvider.getContent(); + return { + pageSize: 'A4', + pageMargins: [0, 0, 0, 0], + defaultStyle: { + font: defaultFont, + fontSize: 10 + }, + content: content, + styles: { + title: { + fontSize: 14, + bold: true, + margin: [30, 30, 0, 0] + }, + description: { + fontSize: 11, + margin: [30, 0, 0, 0] + } + } + }; + }; + return { + getDocument: getDocument + }; + }; + return { + createInstance: createInstance + }; + } +]) + .factory('PdfMakeConverter', [ 'HTMLValidizer', function(HTMLValidizer) { diff --git a/openslides/motions/static/js/motions/motion-services.js b/openslides/motions/static/js/motions/motion-services.js index ab49e4fe7..8ec7b21df 100644 --- a/openslides/motions/static/js/motions/motion-services.js +++ b/openslides/motions/static/js/motions/motion-services.js @@ -10,12 +10,13 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions', 'User', 'PdfMakeConverter', 'PdfMakeDocumentProvider', + 'PdfMakeBallotPaperProvider', 'MotionContentProvider', 'PollContentProvider', 'gettextCatalog', '$http', - function (HTMLValidizer, Motion, User, PdfMakeConverter, PdfMakeDocumentProvider, MotionContentProvider, - PollContentProvider, gettextCatalog, $http) { + function (HTMLValidizer, Motion, User, PdfMakeConverter, PdfMakeDocumentProvider, PdfMakeBallotPaperProvider, + MotionContentProvider, PollContentProvider, gettextCatalog, $http) { var obj = {}; var $scope; @@ -39,12 +40,12 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions', //make PDF for polls obj.createPoll = function() { - var id = $scope.motion.identifier.replace(" ", ""), - title = $scope.motion.getTitle($scope.version), - filename = gettextCatalog.getString("Motion") + "-" + id + "-" + gettextCatalog.getString("ballot-paper") + ".pdf", - content = PollContentProvider.createInstance(title, id, gettextCatalog); - - pdfMake.createPdf(content).download(filename); + var id = $scope.motion.identifier.replace(" ", ""); + var title = $scope.motion.getTitle($scope.version); + var filename = gettextCatalog.getString("Motion") + "-" + id + "-" + gettextCatalog.getString("ballot-paper") + ".pdf"; + var pollContentProvider = PollContentProvider.createInstance(title, id, gettextCatalog); + var documentProvider = PdfMakeBallotPaperProvider.createInstance(pollContentProvider); + pdfMake.createPdf(documentProvider.getDocument()).download(filename); }; obj.init = function (_scope) { diff --git a/openslides/motions/static/js/motions/pdf.js b/openslides/motions/static/js/motions/pdf.js index 6f23a158a..7f1a1ee2a 100644 --- a/openslides/motions/static/js/motions/pdf.js +++ b/openslides/motions/static/js/motions/pdf.js @@ -184,7 +184,9 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) }; }]) -.factory('PollContentProvider', function() { +.factory('PollContentProvider', [ + 'PdfPredefinedFunctions', + function(PdfPredefinedFunctions) { /** * Generates a content provider for polls * @constructor @@ -192,84 +194,25 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) * @param {string} id - if of poll * @param {object} gettextCatalog - for translation */ - var createInstance = function(title, id, gettextCatalog){ - - //left and top margin for a single sheet - var space = { - left: 30, - top: 30, - bottom: 10 - }, - //size and position of the signing circle - circle = { - yDistance: 6, - size: 8 - }, - //margin for the decision - singleItemMargin = 10, - //space between circle and dicision - columnwidth = 20, - //defines the space under a single sheet - sheetend = 65, - //defines the used fontsize - fontSize = 14; - - /** - * draws a single circle - * @function - * @param {int} y - the relative y coordinate - * @param {int} size - size of the circle in px - */ - var drawCircle = function(y, size) { - return [ - { - type: 'ellipse', - x: 0, - y: y, - lineColor: 'black', - r1: size, - r2: size - } - ]; - }; - - /** - * Returns an entry in the ballot with a circle to draw into - * @function - * @param {string} decision - the name of an entry to decide between, e.g. 'yes' or 'no' - */ - var createBallotEntry = function(decision) { - return { - margin: [space.left+circle.size, singleItemMargin, 0, 0], - columns: [ - { - width: columnwidth, - canvas: drawCircle(circle.yDistance, circle.size) - }, - { - text: decision - } - ], - }; - }; + var createInstance = function(title, id, gettextCatalog) { /** * Returns a single section on the ballot paper * @function */ var createSection = function() { + var sheetend = 75; return { stack: [{ text: gettextCatalog.getString("Motion") + " " + id, - style: 'header', - margin: [space.left, space.top, 0, 0] + style: 'title', }, { text: title, - margin: [space.left, 0, 0, space.bottom] + style: 'description' }, - createBallotEntry(gettextCatalog.getString("Yes")), - createBallotEntry(gettextCatalog.getString("No")), - createBallotEntry(gettextCatalog.getString("Abstain")), + PdfPredefinedFunctions.createBallotEntry(gettextCatalog.getString("Yes")), + PdfPredefinedFunctions.createBallotEntry(gettextCatalog.getString("No")), + PdfPredefinedFunctions.createBallotEntry(gettextCatalog.getString("Abstain")), ], margin: [0, 0, 0, sheetend] }; @@ -280,8 +223,9 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) * @function * @param {string} id - if of poll */ - return { - content: [{ + + var getContent = function() { + return [{ table: { headerRows: 1, widths: ['*', '*'], @@ -292,27 +236,18 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) [createSection(), createSection()] ], }, - layout: { - hLineWidth: function() {return 0.5;}, - vLineWidth: function() {return 0.5;}, - hLineColor: function() {return 'gray';}, - vLineColor: function() {return 'gray';}, - } - }], - pageSize: 'A4', - pageMargins: [0, 0, 0, 0], - styles: { - header: { - fontSize: fontSize, - bold: true - } - }, + layout: PdfPredefinedFunctions.getBallotLayoutLines() + }]; + }; + + return { + getContent: getContent, }; }; return { createInstance: createInstance }; -}) +}]) .factory('MotionCatalogContentProvider', [ 'gettextCatalog', diff --git a/openslides/users/static/js/users/pdf.js b/openslides/users/static/js/users/pdf.js index 8a1a9364a..a025da4ee 100644 --- a/openslides/users/static/js/users/pdf.js +++ b/openslides/users/static/js/users/pdf.js @@ -20,14 +20,6 @@ angular.module('OpenSlidesApp.users.pdf', ['OpenSlidesApp.core.pdf']) angular.forEach(userList, function (user, counter) { - //check if the row is even or odd - var rowStyle = []; - if (counter % 2 === 0) { - rowStyle = 'tableEven'; - } else { - rowStyle = 'tableOdd'; - } - //parse for the group names var userGroups = []; angular.forEach(user.groups_id, function (id) { @@ -43,19 +35,19 @@ angular.module('OpenSlidesApp.users.pdf', ['OpenSlidesApp.core.pdf']) var userJsonObj = [ { text: "" + (counter+1), - style: rowStyle + style: PdfPredefinedFunctions.flipTableRowStyle(userJsonList.length) }, { text: user.short_name, - style: rowStyle + style: PdfPredefinedFunctions.flipTableRowStyle(userJsonList.length) }, { text: user.structure_level, - style: rowStyle + style: PdfPredefinedFunctions.flipTableRowStyle(userJsonList.length) }, { text: userGroups.join(" "), - style: rowStyle + style: PdfPredefinedFunctions.flipTableRowStyle(userJsonList.length) } ]; userJsonList.push(userJsonObj);