Refactored AssignmentPoll (Fixed #1848)
Add percents and progressbars. Template fixes and improvements for polls.
This commit is contained in:
parent
af10737388
commit
494c9aee94
@ -6,9 +6,55 @@ angular.module('OpenSlidesApp.assignments', [])
|
|||||||
|
|
||||||
.factory('AssignmentPollOption', [
|
.factory('AssignmentPollOption', [
|
||||||
'DS',
|
'DS',
|
||||||
function (DS) {
|
'jsDataModel',
|
||||||
|
'gettextCatalog',
|
||||||
|
'Config',
|
||||||
|
function (DS, jsDataModel, gettextCatalog, Config) {
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: 'assignments/polloption',
|
name: 'assignments/polloption',
|
||||||
|
useClass: jsDataModel,
|
||||||
|
methods: {
|
||||||
|
getVotes: function () {
|
||||||
|
if (!this.poll.has_votes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var poll = this.poll;
|
||||||
|
var votes = [];
|
||||||
|
var config = Config.get('assignments_poll_100_percent_base').value;
|
||||||
|
angular.forEach(this.votes, function(vote) {
|
||||||
|
// check for special value
|
||||||
|
var value;
|
||||||
|
switch (vote.weight) {
|
||||||
|
case -1:
|
||||||
|
value = gettextCatalog.getString('majority');
|
||||||
|
break;
|
||||||
|
case -2:
|
||||||
|
value = gettextCatalog.getString('undocumented');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value = vote.weight;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// calculate percent value
|
||||||
|
var percentStr, percentNumber;
|
||||||
|
if (config == "WITHOUT_INVALID" && poll.votesvalid > 0 && vote.weight >= 0) {
|
||||||
|
percentNumber = Math.round(vote.weight * 100 / poll.votesvalid * 10) / 10;
|
||||||
|
} else if (config == "WITH_INVALID" && poll.votescast > 0 && vote.weight >= 0) {
|
||||||
|
percentNumber = Math.round(vote.weight * 100 / (poll.votescast) * 10) / 10;
|
||||||
|
}
|
||||||
|
if (percentNumber) {
|
||||||
|
percentStr = "(" + percentNumber + "%)";
|
||||||
|
}
|
||||||
|
votes.push({
|
||||||
|
'label': gettextCatalog.getString(vote.value),
|
||||||
|
'value': value,
|
||||||
|
'percentStr': percentStr,
|
||||||
|
'percentNumber': percentNumber
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
},
|
||||||
relations: {
|
relations: {
|
||||||
belongsTo: {
|
belongsTo: {
|
||||||
'assignments/poll': {
|
'assignments/poll': {
|
||||||
@ -27,10 +73,43 @@ angular.module('OpenSlidesApp.assignments', [])
|
|||||||
|
|
||||||
.factory('AssignmentPoll', [
|
.factory('AssignmentPoll', [
|
||||||
'DS',
|
'DS',
|
||||||
|
'gettextCatalog',
|
||||||
'AssignmentPollOption',
|
'AssignmentPollOption',
|
||||||
function (DS, AssignmentPollOption) {
|
'Config',
|
||||||
|
function (DS, gettextCatalog, AssignmentPollOption, Config) {
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: 'assignments/poll',
|
name: 'assignments/poll',
|
||||||
|
methods: {
|
||||||
|
// returns object with value and percent (for votes valid/invalid/cast only)
|
||||||
|
getVote: function (vote) {
|
||||||
|
if (!this.has_votes || !vote) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var value = '';
|
||||||
|
switch (vote) {
|
||||||
|
case -1:
|
||||||
|
value = gettextCatalog.getString('majority');
|
||||||
|
break;
|
||||||
|
case -2:
|
||||||
|
value = gettextCatalog.getString('undocumented');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value = vote;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// calculate percent value
|
||||||
|
var config = Config.get('assignments_poll_100_percent_base').value;
|
||||||
|
var percent;
|
||||||
|
if ((config == "WITHOUT_INVALID" && vote == this.votesvalid && vote >= 0) ||
|
||||||
|
(config == "WITH_INVALID" && vote == this.votescast && vote >= 0)) {
|
||||||
|
percent = '(100%)';
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'value': value,
|
||||||
|
'percent': percent
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
relations: {
|
relations: {
|
||||||
belongsTo: {
|
belongsTo: {
|
||||||
'assignments/assignment': {
|
'assignments/assignment': {
|
||||||
|
@ -254,6 +254,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|||||||
.controller('AssignmentDetailCtrl', [
|
.controller('AssignmentDetailCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'$http',
|
'$http',
|
||||||
|
'filterFilter',
|
||||||
'gettext',
|
'gettext',
|
||||||
'ngDialog',
|
'ngDialog',
|
||||||
'AssignmentForm',
|
'AssignmentForm',
|
||||||
@ -262,7 +263,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|||||||
'User',
|
'User',
|
||||||
'assignment',
|
'assignment',
|
||||||
'phases',
|
'phases',
|
||||||
function($scope, $http, gettext, ngDialog, AssignmentForm, operator, Assignment, User, assignment, phases) {
|
function($scope, $http, filterFilter, gettext, ngDialog, AssignmentForm, operator, Assignment, User, assignment, phases) {
|
||||||
User.bindAll({}, $scope, 'users');
|
User.bindAll({}, $scope, 'users');
|
||||||
Assignment.bindOne(assignment.id, $scope, 'assignment');
|
Assignment.bindOne(assignment.id, $scope, 'assignment');
|
||||||
Assignment.loadRelations(assignment, 'agenda_item');
|
Assignment.loadRelations(assignment, 'agenda_item');
|
||||||
@ -337,6 +338,9 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|||||||
$http.post('/rest/assignments/assignment/' + assignment.id + '/create_poll/')
|
$http.post('/rest/assignments/assignment/' + assignment.id + '/create_poll/')
|
||||||
.success(function(data){
|
.success(function(data){
|
||||||
$scope.alert.show = false;
|
$scope.alert.show = false;
|
||||||
|
if (assignment.phase == 0) {
|
||||||
|
$scope.updatePhase(1);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.error(function(data){
|
.error(function(data){
|
||||||
$scope.alert = { type: 'danger', msg: data.detail, show: true };
|
$scope.alert = { type: 'danger', msg: data.detail, show: true };
|
||||||
@ -390,6 +394,15 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|||||||
data: JSON.stringify({user: user})})
|
data: JSON.stringify({user: user})})
|
||||||
} else {
|
} else {
|
||||||
$http.post('/rest/assignments/assignment/' + assignment.id + '/mark_elected/', {'user': user})
|
$http.post('/rest/assignments/assignment/' + assignment.id + '/mark_elected/', {'user': user})
|
||||||
|
.then(function(success) {
|
||||||
|
var elected = filterFilter(assignment.assignment_related_users,{elected: true});
|
||||||
|
// Set phase to 'finished' if there are enough (= number of open posts) elected users.
|
||||||
|
// Note: The array 'elected' does NOT contains the candidate who is just marked as elected.
|
||||||
|
// So add 1 to length to get the real number of all elected users.
|
||||||
|
if (elected.length + 1 == assignment.open_posts ) {
|
||||||
|
$scope.updatePhase(2);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -490,8 +503,9 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|||||||
'assignmentpoll',
|
'assignmentpoll',
|
||||||
'ballot',
|
'ballot',
|
||||||
function($scope, gettextCatalog, AssignmentPoll, assignmentpoll, ballot) {
|
function($scope, gettextCatalog, AssignmentPoll, assignmentpoll, ballot) {
|
||||||
// set initial values for form model
|
// set initial values for form model by create deep copy of assignmentpoll object
|
||||||
$scope.model = assignmentpoll;
|
// so detail view is not updated while editing poll
|
||||||
|
$scope.model = angular.copy(assignmentpoll);
|
||||||
$scope.ballot = ballot;
|
$scope.ballot = ballot;
|
||||||
$scope.formFields = [];
|
$scope.formFields = [];
|
||||||
$scope.alert = {};
|
$scope.alert = {};
|
||||||
@ -499,6 +513,16 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|||||||
// add dynamic form fields
|
// add dynamic form fields
|
||||||
assignmentpoll.options.forEach(function(option) {
|
assignmentpoll.options.forEach(function(option) {
|
||||||
if (assignmentpoll.yesnoabstain) {
|
if (assignmentpoll.yesnoabstain) {
|
||||||
|
var defaultValue = {
|
||||||
|
'yes': '',
|
||||||
|
'no': '',
|
||||||
|
'abstain': ''
|
||||||
|
};
|
||||||
|
if (option.votes.length) {
|
||||||
|
defaultValue.yes = option.votes[0].weight;
|
||||||
|
defaultValue.no = option.votes[1].weight;
|
||||||
|
defaultValue.abstain = option.votes[2].weight;
|
||||||
|
}
|
||||||
$scope.formFields.push(
|
$scope.formFields.push(
|
||||||
{
|
{
|
||||||
noFormControl: true,
|
noFormControl: true,
|
||||||
@ -511,7 +535,8 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|||||||
label: gettextCatalog.getString('Yes'),
|
label: gettextCatalog.getString('Yes'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
required: true
|
required: true
|
||||||
}
|
},
|
||||||
|
defaultValue: defaultValue.yes
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'no_' + option.candidate_id,
|
key: 'no_' + option.candidate_id,
|
||||||
@ -520,7 +545,8 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|||||||
label: gettextCatalog.getString('No'),
|
label: gettextCatalog.getString('No'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
required: true
|
required: true
|
||||||
}
|
},
|
||||||
|
defaultValue: defaultValue.no
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key:'abstain_' + option.candidate_id,
|
key:'abstain_' + option.candidate_id,
|
||||||
@ -529,9 +555,14 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|||||||
label: gettextCatalog.getString('Abstain'),
|
label: gettextCatalog.getString('Abstain'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
required: true
|
required: true
|
||||||
}
|
},
|
||||||
|
defaultValue: defaultValue.abstain
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
var defaultValue;
|
||||||
|
if (option.votes.length) {
|
||||||
|
defaultValue = option.votes[0].weight;
|
||||||
|
}
|
||||||
$scope.formFields.push(
|
$scope.formFields.push(
|
||||||
{
|
{
|
||||||
key: 'vote_' + option.candidate_id,
|
key: 'vote_' + option.candidate_id,
|
||||||
@ -540,7 +571,8 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|||||||
label: option.candidate.get_full_name(),
|
label: option.candidate.get_full_name(),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
required: true
|
required: true
|
||||||
}
|
},
|
||||||
|
defaultValue: defaultValue
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -599,6 +631,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// save change poll object on server
|
||||||
poll.DSUpdate({
|
poll.DSUpdate({
|
||||||
assignment_id: poll.assignment_id,
|
assignment_id: poll.assignment_id,
|
||||||
votes: votes,
|
votes: votes,
|
||||||
|
@ -150,7 +150,9 @@
|
|||||||
<i class="fa fa-toggle-on"></i>
|
<i class="fa fa-toggle-on"></i>
|
||||||
<translate>Published</translate>
|
<translate>Published</translate>
|
||||||
</button>
|
</button>
|
||||||
<a ng-click="deleteBallot(poll)" class="btn btn-danger btn-sm">
|
<a class="btn btn-danger btn-sm"
|
||||||
|
ng-bootbox-confirm="{{ 'Are you sure you want to delete this ballot?' | translate }}"
|
||||||
|
ng-bootbox-confirm-action="deleteBallot(poll)">
|
||||||
<i class="fa fa-times"></i>
|
<i class="fa fa-times"></i>
|
||||||
<translate>Delete</translate>
|
<translate>Delete</translate>
|
||||||
</a>
|
</a>
|
||||||
@ -160,43 +162,54 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th translate>Elected
|
<th translate>Elected
|
||||||
<th translate>Candidates
|
<th translate>Candidates
|
||||||
<th ng-if="poll.has_votes" translate>Votes
|
<th ng-if="poll.has_votes" class="col-sm-6" translate>Votes
|
||||||
|
|
||||||
|
<!-- candidates (poll options) -->
|
||||||
<tr ng-repeat="option in poll.options">
|
<tr ng-repeat="option in poll.options">
|
||||||
|
|
||||||
|
<!-- elected -->
|
||||||
<td class="minimum">
|
<td class="minimum">
|
||||||
<button os-perms="assignments.can_manage"
|
<button os-perms="assignments.can_manage"
|
||||||
ng-click="markElected(option.candidate_id, option.is_elected)" class="btn btn-default btn-xs">
|
ng-click="markElected(option.candidate_id, option.is_elected)" class="btn btn-default btn-xs">
|
||||||
<i ng-if="option.is_elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
|
<i ng-if="option.is_elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
|
||||||
<i ng-if="!option.is_elected" class="fa fa-star-o" title="{{ 'is not elected' | translate }}"></i>
|
<i ng-if="!option.is_elected" class="fa fa-star-o" title="{{ 'is not elected' | translate }}"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- candidate name -->
|
||||||
<td>
|
<td>
|
||||||
<a ui-sref="users.user.detail({id: option.candidate.id})">{{ option.candidate.get_full_name() }}</a>
|
<a ui-sref="users.user.detail({id: option.candidate.id})">{{ option.candidate.get_full_name() }}</a>
|
||||||
|
|
||||||
|
<!-- votes -->
|
||||||
<td ng-if="poll.has_votes">
|
<td ng-if="poll.has_votes">
|
||||||
<span ng-if="poll.yesnoabstain && option.votes.length > 0">
|
<div ng-init="votes = option.getVotes()">
|
||||||
{{ option.votes[0].value | translate }}: {{ option.votes[0].weight }}<br>
|
<div ng-repeat="vote in votes">
|
||||||
{{ option.votes[1].value | translate }}: {{ option.votes[1].weight }}<br>
|
<span ng-if="poll.yesnoabstain">{{ vote.label }}:</span>
|
||||||
{{ option.votes[2].value | translate }}: {{ option.votes[2].weight }}
|
{{ vote.value }} {{ vote.percentStr }}
|
||||||
</span>
|
<div ng-if="vote.percentNumber">
|
||||||
<span ng-if="!poll.yesnoabstain && option.votes.length > 0">
|
<uib-progressbar value="vote.percentNumber" type="success"></uib-progressbar>
|
||||||
{{ option.votes[0].weight}}
|
</div>
|
||||||
</span>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- total votes (valid/invalid/casts) -->
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<td>
|
<td>
|
||||||
<translate>Valid votes</translate>
|
<translate>Valid votes</translate>
|
||||||
<td ng-if="poll.has_votes">
|
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote(poll.votesvalid)">
|
||||||
{{ poll.votesvalid }}
|
{{ vote.value }} {{ vote.percent }}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<td>
|
<td>
|
||||||
<translate>Invalid votes</translate>
|
<translate>Invalid votes</translate>
|
||||||
<td ng-if="poll.has_votes">
|
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote(poll.votesinvalid)">
|
||||||
{{ poll.votesinvalid }}
|
{{ vote.value }}
|
||||||
<tr class="total bg-info">
|
<tr class="total bg-info">
|
||||||
<td>
|
<td>
|
||||||
<td>
|
<td>
|
||||||
<translate>Votes cast</translate>
|
<translate>Votes cast</translate>
|
||||||
<td ng-if="poll.has_votes">
|
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote(poll.votescast)">
|
||||||
{{ poll.votescast }}
|
{{ vote.value }} {{ vote.percent }}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</uib-tab>
|
</uib-tab>
|
||||||
|
Loading…
Reference in New Issue
Block a user