Creates the Election documents using PdfMake

This commit is contained in:
Sean Engelhardt 2016-10-19 15:19:33 +02:00 committed by sean
parent dc1c958e0f
commit 14ca655aa2
7 changed files with 636 additions and 112 deletions

View File

@ -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
};
}]);
}());

View File

@ -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');

View File

@ -21,7 +21,7 @@
<i class="fa fa-pencil"></i>
</a>
<!-- pdf -->
<a ui-sref="assignments_single_pdf({pk: assignment.id})" target="_blank" class="btn btn-default btn-sm">
<a ng-click="makePDF_singleAssignment()" target="_blank" class="btn btn-default btn-sm">
<i class="fa fa-file-pdf-o fa-lg"></i>
<translate>PDF</translate>
</a>
@ -143,8 +143,7 @@
<!-- angular requires to open the link in new tab with "target='_blank'".
Otherwise the pdf url can't be open in same window; angular redirects to "/". -->
<!-- PDF -->
<a ui-sref="assignmentpoll_pdf({poll_pk: poll.id})" target="_blank"
class="btn btn-default btn-sm">
<a ng-click="makePDF_assignmentpoll(poll.id)" target="_blank" class="btn btn-default btn-sm">
<i class="fa fa-file-pdf-o"></i>
<translate>Print ballot paper</translate>
</a>

View File

@ -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,66 @@ 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
}
}
};
};
return {
getDocument: getDocument
};
};
return {
createInstance: createInstance
};
}
])
.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]
}
}
};

View File

@ -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) {

View File

@ -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',

View File

@ -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);