routable tabs in poll view for participation and evaluation

This commit is contained in:
jelhan 2015-11-02 23:02:59 +01:00
parent 6bcf2fb088
commit 858b75e543
18 changed files with 892 additions and 723 deletions

View file

@ -1,111 +1,11 @@
import Ember from "ember";
import EmberValidations from 'ember-validations';
import moment from "moment";
/* global jstz */
export default Ember.Controller.extend(EmberValidations.Mixin, {
export default Ember.Controller.extend({
encryption: Ember.inject.service(),
encryptionKey: '',
newUserName: '',
queryParams: ['encryptionKey'],
usersSorting: ['creationDate'],
sortedUsers: Ember.computed.sort('model.users', 'usersSorting'),
actions: {
addNewUser: function(){
var newUser = {
name: this.get('newUserName'),
selections: []
};
var self = this;
// work-a-round cause value is not retrived otherwise
this.get('newUserSelections').forEach(function(selection) {
if(typeof selection.get('value') === 'string') {
newUser.selections.pushObject(
self.store.createFragment('selection', {
label: selection.get('value')
})
);
}
else {
newUser.selections.pushObject(
self.store.createFragment('selection', {
type: selection.get('value.type'),
label: selection.get('value.label'),
labelTranslation: selection.get('value.labelTranslation'),
icon: selection.get('value.icon')
})
);
}
});
// send new user to controller for saving
this.send('saveNewUser', newUser);
// clear input fields
this.set('newUserName', '');
this.get('newUserSelections').forEach(function(selection){
selection.set('value', '');
});
// reset validation erros
this.set('errors.newUserName', '');
this.set('errors.everyOptionIsAnswered', '');
Ember.run.scheduleOnce('afterRender', this, function(){
// recalculate fixedHeaders
Ember.$('.user-selections-table').floatThead('reflow');
});
Ember.run.scheduleOnce('afterRender', this, function(){
// resize top scrollbars
Ember.$('.top-scrollbar div').css('width', Ember.$('.user-selections-table').width() );
Ember.$('.top-scrollbar-floatThead').css('width', Ember.$('.table-scroll').outerWidth() );
Ember.$('.top-scrollbar-floatThead div').css('width', Ember.$('.user-selections-table').width() );
});
},
/*
* save a new user
*/
saveNewUser: function(user){
var self = this;
// create new user record in store
var newUser = this.store.createRecord('user', {
name: user.name,
creationDate: new Date(),
poll: this.get('model'),
selections: user.selections,
version: this.buildInfo.semver
});
// save new user
newUser.save().catch(function(){
// error: new user is not saved
self.send('openModal', {
template: 'save-retry',
model: {
record: newUser
}
});
});
},
submitNewUser: function(){
var self = this;
this.validate().then(function() {
self.send('addNewUser');
}).catch(function(){
Ember.$.each(Ember.View.views, function(id, view) {
if(view.isEasyForm) {
view.focusOut();
}
});
});
}
},
dateGroups: function() {
// group dates only for find a date with times
@ -189,242 +89,10 @@ export default Ember.Controller.extend(EmberValidations.Mixin, {
return dates;
}.property('model.options.@each', 'useLocalTimezone'),
/*
* evaluates poll data
* if free text answers are allowed evaluation is disabled
*/
evaluation: function() {
// disable evaluation if answer type is free text
if (this.get('model.answerType') === 'FreeText') {
return [];
}
var evaluation = [],
options = [],
lookup = [];
// init options array
this.get('model.options').forEach(function(option, index){
options[index] = 0;
});
// init array of evalutation objects
// create object for every possible answer
this.get('model.answers').forEach(function(answer){
evaluation.push({
id: answer.label,
label: answer.label,
options: Ember.$.extend([], options)
});
});
// create object for no answer if answers are not forced
if (!this.get('model.forceAnswer')){
evaluation.push({
id: null,
label: 'no answer',
options: Ember.$.extend([], options)
});
}
// create lookup array
evaluation.forEach(function(value, index){
lookup[value.id] = index;
});
// loop over all users
this.get('model.users').forEach(function(user){
// loop over all selections of the user
user.get('selections').forEach(function(selection, optionindex){
var answerindex;
// get answer index by lookup array
if (typeof lookup[selection.value.label] === 'undefined') {
answerindex = lookup[null];
}
else {
answerindex = lookup[selection.value.label];
}
// increment counter
try {
evaluation[answerindex]['options'][optionindex] = evaluation[answerindex]['options'][optionindex] + 1;
} catch (e) {
// ToDo: Throw an error
}
});
});
return evaluation;
}.property('model.users.@each'),
evaluationBestOptions: function() {
var options = [],
bestOptions = [],
self = this;
// can not evaluate answer type free text
if(this.get('model.isFreeText')) {
return [];
}
this.get('model.users').forEach(function(user){
user.get('selections').forEach(function(selection, i){
if(options.length - 1 < i) {
options.push({
answers: [],
key: i,
score: 0
});
}
if(typeof options[i].answers[selection.get('type')] === 'undefined') {
options[i].answers[selection.get('type')] = 0;
}
options[i].answers[selection.get('type')]++;
switch (selection.get('type')) {
case 'yes':
options[i].score += 2;
break;
case 'maybe':
options[i].score += 1;
break;
case 'no':
options[i].score -= 2;
break;
}
});
});
options.sort(function(a, b) {
return a.score < b.score;
});
bestOptions.push(
options[0]
);
var i = 1;
while(true) {
if (
typeof options[i] !== 'undefined' &&
bestOptions[0].score === options[i].score
) {
bestOptions.push(
options[i]
);
}
else {
break;
}
i++;
}
bestOptions.forEach(function(bestOption, i){
if (self.get('model.isFindADate')) {
bestOptions[i].title = self.get('dates')[bestOption.key].title;
}
else {
bestOptions[i].title = self.get('model.options')[bestOption.key].title;
}
});
return bestOptions;
}.property('model.users.@each'),
evaluationBestOptionsMultiple: function(){
if (this.get('evaluationBestOptions.length') > 1) {
return true;
}
else {
return false;
}
}.property('evaluationBestOptions'),
evaluationLastParticipation: function(){
return this.get('sortedUsers.lastObject.creationDate');
}.property('sortedUsers.@each'),
evaluationParticipants: function(){
return this.get('model.users.length');
}.property('model.users.@each'),
/*
* returns true if user has selected an answer for every option provided
*/
everyOptionIsAnswered: function(){
try {
var newUserSelections = this.get('newUserSelections'),
allAnswered = true;
if (typeof newUserSelections === 'undefined') {
return false;
}
newUserSelections.forEach(function(item){
if (Ember.isEmpty(item.value)) {
allAnswered = false;
}
});
return allAnswered;
}
catch (e) {
return false;
}
}.property('newUserSelections.@each.value'),
/*
* calculate colspan for a row which should use all columns in table
* used by evaluation row
*/
fullRowColspan: function(){
return this.get('model.options.length') + 2;
}.property('model.options.@each'),
isEvaluable: function() {
if(
!this.get('model.isFreeText') &&
this.get('model.users.length') > 0
) {
return true;
}
else {
return false;
}
}.property('model.users.@each', 'model.isFreeText'),
/*
* switch isValid state
* is needed for disable submit button
*/
isNotValid: function(){
return !this.get('isValid');
}.property('isValid'),
// array to store selections of new user
newUserSelections: function(){
var newUserSelections = Ember.A(),
options = this.get('model.options');
options.forEach(function(){
var newSelection = Ember.Object.create({value: ''});
newUserSelections.pushObject(newSelection);
});
return newUserSelections;
}.property('model.options'),
optionCount: function() {
return this.get('model.options.length');
}.property('model.options'),
pollUrl: function() {
return window.location.href;
}.property('currentPath', 'encryptionKey'),
preventEncryptionKeyChanges: function() {
if (
!Ember.isEmpty(this.get('encryption.key')) &&
@ -446,40 +114,5 @@ export default Ember.Controller.extend(EmberValidations.Mixin, {
useLocalTimezone: function() {
return false;
}.property(),
validations: {
everyOptionIsAnswered: {
/*
* validate if every option is answered
* if it's forced by poll settings (forceAnswer === true)
*
* using a computed property therefore which returns true / false
* in combinatoin with acceptance validator
*
* ToDo: Show validation errors
*/
acceptance: {
if: function(object){
return object.get('model.forceAnswer');
},
message: Ember.I18n.t('poll.error.newUser.everyOptionIsAnswered')
}
},
newUserName: {
presence: {
message: Ember.I18n.t('poll.error.newUserName'),
/*
* validate if a user name is given
* if it's forced by poll settings (anonymousUser === false)
*/
unless: function(object){
/* have in mind that anonymousUser is undefined on init */
return object.get('model.anonymousUser');
}
}
}
}
}.property()
});

View file

@ -0,0 +1,194 @@
import Ember from "ember";
export default Ember.Controller.extend({
usersSorting: ['creationDate'],
sortedUsers: Ember.computed.sort('pollController.model.users', 'usersSorting'),
pollController: Ember.inject.controller('poll'),
dates: Ember.computed.reads('pollController.dates'),
dateGroups: Ember.computed.reads('pollController.dateGroups'),
/*
* evaluates poll data
* if free text answers are allowed evaluation is disabled
*/
evaluation: function() {
// disable evaluation if answer type is free text
if (this.get('model.answerType') === 'FreeText') {
return [];
}
var evaluation = [],
options = [],
lookup = [];
// init options array
this.get('model.options').forEach(function(option, index){
options[index] = 0;
});
// init array of evalutation objects
// create object for every possible answer
this.get('model.answers').forEach(function(answer){
evaluation.push({
id: answer.label,
label: answer.label,
options: Ember.$.extend([], options)
});
});
// create object for no answer if answers are not forced
if (!this.get('model.forceAnswer')){
evaluation.push({
id: null,
label: 'no answer',
options: Ember.$.extend([], options)
});
}
// create lookup array
evaluation.forEach(function(value, index){
lookup[value.id] = index;
});
// loop over all users
this.get('model.users').forEach(function(user){
// loop over all selections of the user
user.get('selections').forEach(function(selection, optionindex){
var answerindex;
// get answer index by lookup array
if (typeof lookup[selection.value.label] === 'undefined') {
answerindex = lookup[null];
}
else {
answerindex = lookup[selection.value.label];
}
// increment counter
try {
evaluation[answerindex]['options'][optionindex] = evaluation[answerindex]['options'][optionindex] + 1;
} catch (e) {
// ToDo: Throw an error
}
});
});
return evaluation;
}.property('model.users.@each'),
evaluationBestOptions: function() {
var options = [],
bestOptions = [],
self = this;
// can not evaluate answer type free text
if(this.get('model.isFreeText')) {
return [];
}
this.get('model.users').forEach(function(user){
user.get('selections').forEach(function(selection, i){
if(options.length - 1 < i) {
options.push({
answers: [],
key: i,
score: 0
});
}
if(typeof options[i].answers[selection.get('type')] === 'undefined') {
options[i].answers[selection.get('type')] = 0;
}
options[i].answers[selection.get('type')]++;
switch (selection.get('type')) {
case 'yes':
options[i].score += 2;
break;
case 'maybe':
options[i].score += 1;
break;
case 'no':
options[i].score -= 2;
break;
}
});
});
options.sort(function(a, b) {
return a.score < b.score;
});
bestOptions.push(
options[0]
);
var i = 1;
while(true) {
if (
typeof options[i] !== 'undefined' &&
bestOptions[0].score === options[i].score
) {
bestOptions.push(
options[i]
);
}
else {
break;
}
i++;
}
bestOptions.forEach(function(bestOption, i){
if (self.get('model.isFindADate')) {
bestOptions[i].title = self.get('dates')[bestOption.key].title;
}
else {
bestOptions[i].title = self.get('model.options')[bestOption.key].title;
}
});
return bestOptions;
}.property('model.users.@each'),
evaluationBestOptionsMultiple: function(){
if (this.get('evaluationBestOptions.length') > 1) {
return true;
}
else {
return false;
}
}.property('evaluationBestOptions'),
evaluationLastParticipation: function(){
return this.get('sortedUsers.lastObject.creationDate');
}.property('sortedUsers.@each'),
evaluationParticipants: function(){
return this.get('model.users.length');
}.property('model.users.@each'),
/*
* calculate colspan for a row which should use all columns in table
* used by evaluation row
*/
fullRowColspan: function(){
return this.get('model.options.length') + 2;
}.property('model.options.@each'),
isEvaluable: function() {
if(
!this.get('model.isFreeText') &&
this.get('model.users.length') > 0
) {
return true;
}
else {
return false;
}
}.property('model.users.@each', 'model.isFreeText'),
optionCount: function() {
return this.get('model.options.length');
}.property('model.options')
});

View file

@ -0,0 +1,197 @@
import Ember from "ember";
import EmberValidations from 'ember-validations';
export default Ember.Controller.extend(EmberValidations.Mixin, {
dates: Ember.computed.reads('pollController.dates'),
dateGroups: Ember.computed.reads('pollController.dateGroups'),
encryption: Ember.inject.service(),
newUserName: '',
pollController: Ember.inject.controller('poll'),
actions: {
addNewUser: function(){
var newUser = {
name: this.get('newUserName'),
selections: []
};
var self = this;
// work-a-round cause value is not retrived otherwise
this.get('newUserSelections').forEach(function(selection) {
if(typeof selection.get('value') === 'string') {
newUser.selections.pushObject(
self.store.createFragment('selection', {
label: selection.get('value')
})
);
}
else {
newUser.selections.pushObject(
self.store.createFragment('selection', {
type: selection.get('value.type'),
label: selection.get('value.label'),
labelTranslation: selection.get('value.labelTranslation'),
icon: selection.get('value.icon')
})
);
}
});
// send new user to controller for saving
this.send('saveNewUser', newUser);
// clear input fields
this.set('newUserName', '');
this.get('newUserSelections').forEach(function(selection){
selection.set('value', '');
});
// reset validation erros
this.set('errors.newUserName', '');
this.set('errors.everyOptionIsAnswered', '');
Ember.run.scheduleOnce('afterRender', this, function(){
// recalculate fixedHeaders
Ember.$('.user-selections-table').floatThead('reflow');
});
Ember.run.scheduleOnce('afterRender', this, function(){
// resize top scrollbars
Ember.$('.top-scrollbar div').css('width', Ember.$('.user-selections-table').width() );
Ember.$('.top-scrollbar-floatThead').css('width', Ember.$('.table-scroll').outerWidth() );
Ember.$('.top-scrollbar-floatThead div').css('width', Ember.$('.user-selections-table').width() );
});
},
/*
* save a new user
*/
saveNewUser: function(user){
var self = this;
// create new user record in store
var newUser = this.store.createRecord('user', {
name: user.name,
creationDate: new Date(),
poll: this.get('model'),
selections: user.selections,
version: this.buildInfo.semver
});
// save new user
newUser.save()
.then(() => {
this.transitionTo('poll.evaluation', this.get('model'), {
queryParams: { encryptionKey: this.get('encryption.key') }
});
})
.catch(function(){
// error: new user is not saved
self.send('openModal', {
template: 'save-retry',
model: {
record: newUser
}
});
});
},
submitNewUser: function(){
var self = this;
this.validate().then(function() {
self.send('addNewUser');
}).catch(function(){
Ember.$.each(Ember.View.views, function(id, view) {
if(view.isEasyForm) {
view.focusOut();
}
});
});
}
},
/*
* returns true if user has selected an answer for every option provided
*/
everyOptionIsAnswered: function(){
try {
var newUserSelections = this.get('newUserSelections'),
allAnswered = true;
if (typeof newUserSelections === 'undefined') {
return false;
}
newUserSelections.forEach(function(item){
if (Ember.isEmpty(item.value)) {
allAnswered = false;
}
});
return allAnswered;
}
catch (e) {
return false;
}
}.property('newUserSelections.@each.value'),
/*
* switch isValid state
* is needed for disable submit button
*/
isNotValid: function(){
return !this.get('isValid');
}.property('isValid'),
// array to store selections of new user
newUserSelections: function(){
var newUserSelections = Ember.A(),
options = this.get('model.options');
options.forEach(function(){
var newSelection = Ember.Object.create({value: ''});
newUserSelections.pushObject(newSelection);
});
return newUserSelections;
}.property('model.options'),
optionCount: function() {
return this.get('model.options.length');
}.property('model.options'),
validations: {
everyOptionIsAnswered: {
/*
* validate if every option is answered
* if it's forced by poll settings (forceAnswer === true)
*
* using a computed property therefore which returns true / false
* in combinatoin with acceptance validator
*
* ToDo: Show validation errors
*/
acceptance: {
if: function(object){
return object.get('model.forceAnswer');
},
message: Ember.I18n.t('poll.error.newUser.everyOptionIsAnswered')
}
},
newUserName: {
presence: {
message: Ember.I18n.t('poll.error.newUserName'),
/*
* validate if a user name is given
* if it's forced by poll settings (anonymousUser === false)
*/
unless: function(object){
/* have in mind that anonymousUser is undefined on init */
return object.get('model.anonymousUser');
}
}
}
}
});

View file

@ -6,7 +6,10 @@ var Router = Ember.Router.extend({
});
Router.map(function(){
this.route('poll', { path: '/poll/:poll_id' });
this.resource('poll', { path: '/poll/:poll_id' }, function(){
this.route('participation');
this.route('evaluation');
});
this.resource('create', function(){
this.route('meta');
this.route('options');

View file

@ -11,6 +11,10 @@ export default Ember.Route.extend({
}
},
afterModel(poll) {
this.transitionTo('poll.participation', poll, {queryParams: {encryptionKey: this.get('encryption.key')}});
},
encryption: Ember.inject.service(),
model: function(params) {

View file

@ -18,216 +18,26 @@
</div>
</div>
<div class="box">
<div class="table-scroll">
<table class="user-selections-table table table-striped table-condensed">
<thead>
{{#if model.isDateTime}}
<tr class="dateGroups">
<th>&nbsp;</th>
{{#each dateGroup in dateGroups}}
<th {{bind-attr colspan="dateGroup.colspan"}}>
{{formattedDate dateGroup.value}}
</th>
{{/each}}
<th>&nbsp;</th>
</tr>
{{/if}}
<tr>
<th>&nbsp;</th>
{{#if model.isFindADate}}
{{#each date in dates}}
<th>
{{#if model.isDateTime}}
{{formattedDate date.title format="LT" times=true}}
{{else}}
{{formattedDate date.title}}
{{/if}}
</th>
{{/each}}
{{else}}
{{#each option in model.options}}
<th>
{{option.title}}
</th>
{{/each}}
{{/if}}
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr class='newUser'>
<td>
{{#form-wrapper}}
{{#input newUserName}}
{{input-field newUserName
placeholderTranslation="poll.input.newUserName.placeholder"
}}
{{#if view.showError}}
{{error-field newUserName}}
{{/if}}
{{/input}}
{{/form-wrapper}}
</td>
{{#each newUserSelection in newUserSelections}}
<td class="newUserSelection">
{{#form-wrapper}}
{{#if model.isFreeText}}
{{#input newUserSelection.value}}
{{input-field newUserSelection.value}}
{{/input}}
{{else}}
{{#each answer in model.answers}}
<div class="radio">
{{#radio-button value=answer groupValue=newUserSelection.value}}
<span {{bind-attr class="answer.type"}}>
<span {{bind-attr class="answer.icon"}}></span>
{{#if answer.labelTranslation}}
{{t answer.labelTranslation}}
{{else}}
{{answer.label}}
{{/if}}
</span>
{{/radio-button}}
</div>
{{/each}}
{{/if}}
{{/form-wrapper}}
</td>
{{/each}}
<td>
<button {{action "submitNewUser"}} class="btn btn-default btn-primary">
{{t "poll.save"}}
</button>
<span style="white-space: normal;">
{{#input everyOptionIsAnswered}}
{{#if view.showError}}
{{error-field everyOptionIsAnswered}}
{{/if}}
{{/input}}
</span>
</td>
</tr>
{{#each user in sortedUsers}}
<tr class="user">
<td>{{user.name}}</td>
{{#each selection in user.selections}}
<td>
{{#if selection.label}}
{{#if model.isFreeText}}
{{selection.label}}
{{/if}}
{{/if}}
{{#if selection.labelTranslation}}
{{#unless model.isFreeText}}
<span {{bind-attr class="selection.type"}}>
<span {{bind-attr class="selection.icon"}}></span>
{{t selection.labelTranslation}}
</span>
{{/unless}}
{{/if}}
</td>
{{/each}}
<td>&nbsp;</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
{{#if isEvaluable}}
<div class="box evaluation">
<h2>
{{t "poll.evaluation.label"}}
</h2>
<p class="participants">
{{t "poll.evaluation.participants" count=evaluationParticipants}}
</p>
<p class="best-options">
{{#if model.isFindADate}}
{{t "poll.evaluation.bestOption.label.findADate" count=evaluationBestOptions.length}}
{{else}}
{{t "poll.evaluation.bestOption.label.makeAPoll" count=evaluationBestOptions.length}}
{{/if}}
{{#if evaluationBestOptionsMultiple}}
<ul>
{{#each evaluationBestOption in evaluationBestOptions}}
<li>
{{#if model.isFindADate}}
<strong>{{formattedDate evaluationBestOption.title times=model.isDateTime}}</strong>.
{{else}}
<strong>{{evaluationBestOption.title}}</strong>.
{{/if}}
<br/>
{{#if model.isFindADate}}
{{#if evaluationBestOption.answers.yes}}
{{t "poll.evaluation.bestOptionParticipants.findADate.yes" count=evaluationBestOption.answers.yes}}
{{/if}}
{{#if evaluationBestOption.answers.maybe}}
{{t "poll.evaluation.bestOptionParticipants.findADate.maybe" count=evaluationBestOption.answers.maybe}}
{{/if}}
{{#if evaluationBestOption.answers.no}}
{{t "poll.evaluation.bestOptionParticipants.findADate.no" count=evaluationBestOption.answers.no}}
{{/if}}
{{else}}
{{#if evaluationBestOption.answers.yes}}
{{t "poll.evaluation.bestOptionParticipants.makeAPoll.yes" count=evaluationBestOption.answers.yes}}
{{/if}}
{{#if evaluationBestOption.answers.maybe}}
{{t "poll.evaluation.bestOptionParticipants.makeAPoll.maybe" count=evaluationBestOption.answers.maybe}}
{{/if}}
{{#if evaluationBestOption.answers.no}}
{{t "poll.evaluation.bestOptionParticipants.makeAPoll.no" count=evaluationBestOption.answers.no}}
{{/if}}
{{/if}}
</li>
{{/each}}
<div class="box container-fluid">
<ul class="nav nav-tabs" role="tablist">
{{#link-to "poll.participation" model tagName='li' activeClass='active' class='participation'}}
{{#link-to "poll.participation" model}}
participation
{{/link-to}}
{{/link-to}}
{{#link-to "poll.evaluation" model tagName='li' activeClass='active' class='evaluation'}}
{{#link-to "poll.evaluation" model}}
evaluation
{{/link-to}}
{{/link-to}}
</ul>
{{else}}
{{#if model.isFindADate}}
<strong>{{formattedDate evaluationBestOptions.[0].title times=model.isDateTime}}</strong>.
{{else}}
<strong>{{evaluationBestOptions.[0].title}}</strong>.
{{/if}}
{{#if model.isFindADate}}
{{#if evaluationBestOptions.[0].answers.yes}}
{{t "poll.evaluation.bestOptionParticipants.findADate.yes" count=evaluationBestOptions.[0].answers.yes}}
{{/if}}
{{#if evaluationBestOptions.[0].answers.maybe}}
{{t "poll.evaluation.bestOptionParticipants.findADate.maybe" count=evaluationBestOptions.[0].answers.maybe}}
{{/if}}
{{#if evaluationBestOptions.[0].answers.no}}
{{t "poll.evaluation.bestOptionParticipants.findADate.no" count=evaluationBestOptions.[0].answers.no}}
{{/if}}
{{else}}
{{#if evaluationBestOptions.[0].answers.yes}}
{{t "poll.evaluation.bestOptionParticipants.makeAPoll.yes" count=evaluationBestOptions.[0].answers.yes}}
{{/if}}
{{#if evaluationBestOptions.[0].answers.maybe}}
{{t "poll.evaluation.bestOptionParticipants.makeAPoll.maybe" count=evaluationBestOptions.[0].answers.maybe}}
{{/if}}
{{#if evaluationBestOptions.[0].answers.no}}
{{t "poll.evaluation.bestOptionParticipants.makeAPoll.no" count=evaluationBestOptions.[0].answers.no}}
{{/if}}
{{/if}}
{{/if}}
</p>
<p class="last-participation">
{{t "poll.evaluation.lastParticipation" ago=(moment-from-now evaluationLastParticipation)}}
</p>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active">
{{outlet}}
</div>
</div>
</div>
{{/if}}
</div>
<div class="modal fade" id="timezoneDiffers">

View file

@ -0,0 +1,155 @@
<div class="table-scroll">
<table class="user-selections-table table table-striped table-condensed">
<thead>
{{#if model.isDateTime}}
<tr class="dateGroups">
<th>&nbsp;</th>
{{#each dateGroup in dateGroups}}
<th {{bind-attr colspan="dateGroup.colspan"}}>
{{formattedDate dateGroup.value}}
</th>
{{/each}}
<th>&nbsp;</th>
</tr>
{{/if}}
<tr>
<th>&nbsp;</th>
{{#if model.isFindADate}}
{{#each date in dates}}
<th>
{{#if model.isDateTime}}
{{formattedDate date.title format="LT" times=true}}
{{else}}
{{formattedDate date.title}}
{{/if}}
</th>
{{/each}}
{{else}}
{{#each option in model.options}}
<th>
{{option.title}}
</th>
{{/each}}
{{/if}}
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{{#each user in sortedUsers}}
<tr class="user">
<td>{{user.name}}</td>
{{#each selection in user.selections}}
<td>
{{#if selection.label}}
{{#if model.isFreeText}}
{{selection.label}}
{{/if}}
{{/if}}
{{#if selection.labelTranslation}}
{{#unless model.isFreeText}}
<span {{bind-attr class="selection.type"}}>
<span {{bind-attr class="selection.icon"}}></span>
{{t selection.labelTranslation}}
</span>
{{/unless}}
{{/if}}
</td>
{{/each}}
<td>&nbsp;</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
{{#if isEvaluable}}
<div class="box evaluation">
<h2>
{{t "poll.evaluation.label"}}
</h2>
<p class="participants">
{{t "poll.evaluation.participants" count=evaluationParticipants}}
</p>
<p class="best-options">
{{#if model.isFindADate}}
{{t "poll.evaluation.bestOption.label.findADate" count=evaluationBestOptions.length}}
{{else}}
{{t "poll.evaluation.bestOption.label.makeAPoll" count=evaluationBestOptions.length}}
{{/if}}
{{#if evaluationBestOptionsMultiple}}
<ul>
{{#each evaluationBestOption in evaluationBestOptions}}
<li>
{{#if model.isFindADate}}
<strong>{{formattedDate evaluationBestOption.title times=model.isDateTime}}</strong>.
{{else}}
<strong>{{evaluationBestOption.title}}</strong>.
{{/if}}
<br/>
{{#if model.isFindADate}}
{{#if evaluationBestOption.answers.yes}}
{{t "poll.evaluation.bestOptionParticipants.findADate.yes" count=evaluationBestOption.answers.yes}}
{{/if}}
{{#if evaluationBestOption.answers.maybe}}
{{t "poll.evaluation.bestOptionParticipants.findADate.maybe" count=evaluationBestOption.answers.maybe}}
{{/if}}
{{#if evaluationBestOption.answers.no}}
{{t "poll.evaluation.bestOptionParticipants.findADate.no" count=evaluationBestOption.answers.no}}
{{/if}}
{{else}}
{{#if evaluationBestOption.answers.yes}}
{{t "poll.evaluation.bestOptionParticipants.makeAPoll.yes" count=evaluationBestOption.answers.yes}}
{{/if}}
{{#if evaluationBestOption.answers.maybe}}
{{t "poll.evaluation.bestOptionParticipants.makeAPoll.maybe" count=evaluationBestOption.answers.maybe}}
{{/if}}
{{#if evaluationBestOption.answers.no}}
{{t "poll.evaluation.bestOptionParticipants.makeAPoll.no" count=evaluationBestOption.answers.no}}
{{/if}}
{{/if}}
</li>
{{/each}}
</ul>
{{else}}
{{#if model.isFindADate}}
<strong>{{formattedDate evaluationBestOptions.[0].title times=model.isDateTime}}</strong>.
{{else}}
<strong>{{evaluationBestOptions.[0].title}}</strong>.
{{/if}}
{{#if model.isFindADate}}
{{#if evaluationBestOptions.[0].answers.yes}}
{{t "poll.evaluation.bestOptionParticipants.findADate.yes" count=evaluationBestOptions.[0].answers.yes}}
{{/if}}
{{#if evaluationBestOptions.[0].answers.maybe}}
{{t "poll.evaluation.bestOptionParticipants.findADate.maybe" count=evaluationBestOptions.[0].answers.maybe}}
{{/if}}
{{#if evaluationBestOptions.[0].answers.no}}
{{t "poll.evaluation.bestOptionParticipants.findADate.no" count=evaluationBestOptions.[0].answers.no}}
{{/if}}
{{else}}
{{#if evaluationBestOptions.[0].answers.yes}}
{{t "poll.evaluation.bestOptionParticipants.makeAPoll.yes" count=evaluationBestOptions.[0].answers.yes}}
{{/if}}
{{#if evaluationBestOptions.[0].answers.maybe}}
{{t "poll.evaluation.bestOptionParticipants.makeAPoll.maybe" count=evaluationBestOptions.[0].answers.maybe}}
{{/if}}
{{#if evaluationBestOptions.[0].answers.no}}
{{t "poll.evaluation.bestOptionParticipants.makeAPoll.no" count=evaluationBestOptions.[0].answers.no}}
{{/if}}
{{/if}}
{{/if}}
</p>
<p class="last-participation">
{{t "poll.evaluation.lastParticipation" ago=(moment-from-now evaluationLastParticipation)}}
</p>
</div>
{{/if}}

View file

@ -0,0 +1,94 @@
<div class="table-scroll">
<table class="user-selections-table table table-striped table-condensed">
<thead>
{{#if model.isDateTime}}
<tr class="dateGroups">
<th>&nbsp;</th>
{{#each dateGroup in dateGroups}}
<th {{bind-attr colspan="dateGroup.colspan"}}>
{{formattedDate dateGroup.value}}
</th>
{{/each}}
<th>&nbsp;</th>
</tr>
{{/if}}
<tr>
<th>&nbsp;</th>
{{#if model.isFindADate}}
{{#each date in dates}}
<th>
{{#if model.isDateTime}}
{{formattedDate date.title format="LT" times=true}}
{{else}}
{{formattedDate date.title}}
{{/if}}
</th>
{{/each}}
{{else}}
{{#each option in model.options}}
<th>
{{option.title}}
</th>
{{/each}}
{{/if}}
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr class='newUser'>
<td>
{{#form-wrapper}}
{{#input newUserName}}
{{input-field newUserName
placeholderTranslation="poll.input.newUserName.placeholder"
}}
{{#if view.showError}}
{{error-field newUserName}}
{{/if}}
{{/input}}
{{/form-wrapper}}
</td>
{{#each newUserSelection in newUserSelections}}
<td class="newUserSelection">
{{#form-wrapper}}
{{#if model.isFreeText}}
{{#input newUserSelection.value}}
{{input-field newUserSelection.value}}
{{/input}}
{{else}}
{{#each answer in model.answers}}
<div class="radio">
{{#radio-button value=answer groupValue=newUserSelection.value}}
<span {{bind-attr class="answer.type"}}>
<span {{bind-attr class="answer.icon"}}></span>
{{#if answer.labelTranslation}}
{{t answer.labelTranslation}}
{{else}}
{{answer.label}}
{{/if}}
</span>
{{/radio-button}}
</div>
{{/each}}
{{/if}}
{{/form-wrapper}}
</td>
{{/each}}
<td>
<button {{action "submitNewUser"}} class="btn btn-default btn-primary">
{{t "poll.save"}}
</button>
<span style="white-space: normal;">
{{#input everyOptionIsAnswered}}
{{#if view.showError}}
{{error-field everyOptionIsAnswered}}
{{/if}}
{{/input}}
</span>
</td>
</tr>
</tbody>
</table>
</div>

View file

@ -30,7 +30,8 @@
"pollHasOptionsDates",
"pollHasOptionsTimes",
"pollHasAnswers",
"pollParticipate"
"pollParticipate",
"switchTab"
],
"node": false,
"browser": false,

View file

@ -69,7 +69,7 @@ test("create a default poll", function(assert) {
click('.button-next');
andThen(function(){
assert.equal(currentPath(), 'poll');
assert.equal(currentPath(), 'poll.participation');
pollTitleEqual(assert, 'default poll');
pollDescriptionEqual(assert, '');
@ -133,7 +133,7 @@ test("create a poll for answering a question", function(assert) {
click('.button-next');
andThen(function(){
assert.equal(currentPath(), 'poll');
assert.equal(currentPath(), 'poll.participation');
pollTitleEqual(assert, 'default poll');
pollDescriptionEqual(assert, '');
@ -174,7 +174,7 @@ test("create a poll with description", function(assert) {
click('.button-next');
andThen(function(){
assert.equal(currentPath(), 'poll');
assert.equal(currentPath(), 'poll.participation');
pollTitleEqual(assert, 'default poll');
pollDescriptionEqual(assert, 'a sample description');

View file

@ -40,11 +40,12 @@ test("participate in a default poll", function(assert) {
);
visit('/poll/' + id + '?encryptionKey=' + encryptionKey).then(function() {
assert.equal(currentPath(), 'poll.participation');
pollParticipate('Max Meiner', ['yes', 'no']);
assert.equal(Ember.$('.has-error').length, 0, "there is no validation error");
andThen(function(){
assert.equal(currentPath(), 'poll.evaluation');
pollHasUsersCount(assert, 1, "user is added to user selections table");
pollHasUser(assert, 'Max Meiner', [Ember.I18n.t('answerTypes.yes.label'), Ember.I18n.t('answerTypes.no.label')]);
});
@ -73,11 +74,12 @@ test("participate in a poll using freetext", function(assert) {
);
visit('/poll/' + id + '?encryptionKey=' + encryptionKey).then(function() {
assert.equal(currentPath(), 'poll.participation');
pollParticipate('Max Manus', ['answer 1', 'answer 2']);
assert.equal(find('.has-error').length, 0, "there is no validation error");
andThen(function(){
assert.equal(currentPath(), 'poll.evaluation');
pollHasUsersCount(assert, 1, "user is added to user selections table");
pollHasUser(assert, 'Max Manus', ['answer 1', 'answer 2']);
});
@ -105,11 +107,12 @@ test("participate in a poll which doesn't force an answer to all options", funct
);
visit('/poll/' + id + '?encryptionKey=' + encryptionKey).then(function() {
assert.equal(currentPath(), 'poll.participation');
pollParticipate('Karl Käfer', ['yes', null]);
assert.equal(Ember.$('.has-error').length, 0, "there is no validation error");
andThen(function(){
assert.equal(currentPath(), 'poll.evaluation');
pollHasUsersCount(assert, 1, "user is added to user selections table");
pollHasUser(assert, "Karl Käfer", [Ember.I18n.t("answerTypes.yes.label"), ""]);
});
@ -137,11 +140,12 @@ test("participate in a poll which allows anonymous participation", function(asse
);
visit('/poll/' + id + '?encryptionKey=' + encryptionKey).then(function() {
assert.equal(currentPath(), 'poll.participation');
pollParticipate(null, ['yes', 'no']);
assert.equal(Ember.$('.has-error').length, 0, "there is no validation error");
andThen(function(){
assert.equal(currentPath(), 'poll.evaluation');
pollHasUsersCount(assert, 1, "user is added to user selections table");
pollHasUser(assert, "", [Ember.I18n.t("answerTypes.yes.label"), Ember.I18n.t("answerTypes.no.label")]);
});

View file

@ -39,7 +39,12 @@ test('evaluation is not present for poll without participants', function(assert)
visit('/poll/' + id + '?encryptionKey=' + encryptionKey);
andThen(function() {
assert.equal(find('.evaluation').length, 0);
assert.equal(currentPath(), 'poll.participation');
switchTab('evaluation');
andThen(function() {
assert.equal(find('.tab-content .evaluation').length, 0);
});
});
});
@ -116,7 +121,12 @@ test('evaluation is correct', function(assert) {
visit('/poll/' + id + '?encryptionKey=' + encryptionKey);
andThen(function() {
assert.equal(find('.evaluation').length, 1, 'evaluation is present');
assert.equal(currentPath(), 'poll.participation');
switchTab('evaluation');
andThen(function() {
assert.equal(currentPath(), 'poll.evaluation');
assert.equal(find('.tab-content .evaluation').length, 1, 'evaluation is present');
assert.notEqual(
find('.participants').text().indexOf(' 2 '),
-1,
@ -132,3 +142,4 @@ test('evaluation is correct', function(assert) {
);
});
});
});

View file

@ -22,6 +22,24 @@ module('Acceptance | view poll', {
}
});
test('view poll url', function(assert) {
var id = 'test',
encryptionKey = 'abcdefghijklmnopqrstuvwxyz012345789';
server.get('/polls/' + id, function() {
return serverGetPolls({ id: id }, encryptionKey);
});
visit('/poll/' + id + '?encryptionKey=' + encryptionKey);
andThen(function() {
assert.equal(
find('.share-link .link a').text(),
window.location.href,
'share link is shown'
);
});
});
test('view a poll with dates', function(assert) {
var id = 'test',
encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';

View file

@ -8,6 +8,7 @@ import './poll-has-users';
import './poll-has-options';
import './poll-has-answers';
import './poll-participate';
import './switch-tab';
export default function startApp(attrs) {
var application;

View file

@ -0,0 +1,5 @@
import Ember from 'ember';
export default Ember.Test.registerAsyncHelper('switchTab', function(app, tab) {
click('.nav-tabs .' + tab + ' a');
});

View file

@ -42,7 +42,7 @@ test("create a default poll and participate", function(assert) {
click('.button-next');
andThen(function(){
assert.equal(currentPath(), 'poll');
assert.equal(currentPath(), 'poll.participation');
pollTitleEqual(assert, 'default poll');
pollDescriptionEqual(assert, '');
@ -62,6 +62,7 @@ test("create a default poll and participate", function(assert) {
pollHasUsersCount(assert, 0);
pollParticipate('Max Hoelz', ['no', 'no']);
andThen(function(){
assert.equal(currentPath(), 'poll.evaluation');
pollHasUsersCount(assert, 1);
pollHasUser(assert, 'Max Hoelz', [Ember.I18n.t('answerTypes.no.label'), Ember.I18n.t('answerTypes.no.label')]);
});

View file

@ -53,6 +53,11 @@ test('show a default poll created with v0.3.0', function(assert) {
Ember.I18n.t('answerTypes.no.label')
]);
switchTab('evaluation');
andThen(function() {
assert.equal(currentPath(), 'poll.evaluation');
pollHasUser(assert,
'Fritz Bauer',
[
@ -70,8 +75,15 @@ test('show a default poll created with v0.3.0', function(assert) {
]
);
pollParticipate('Hermann Langbein', ['yes', 'maybe', 'yes']);
switchTab('participation');
andThen(function() {
assert.equal(currentPath(), 'poll.participation');
pollParticipate('Hermann Langbein', ['yes', 'maybe', 'yes']);
andThen(function() {
assert.equal(currentPath(), 'poll.evaluation');
pollHasUser(assert,
'Hermann Langbein',
[
@ -83,6 +95,8 @@ test('show a default poll created with v0.3.0', function(assert) {
});
});
});
});
});
test('find a poll using free text created with v0.3.0', function(assert) {
var id = 'PjW3XwbuRc',
@ -100,6 +114,10 @@ test('find a poll using free text created with v0.3.0', function(assert) {
'plum pie'
]);
switchTab('evaluation');
andThen(function() {
assert.equal(currentPath(), 'poll.evaluation');
pollHasUser(assert,
'Paul Levi',
[
@ -109,8 +127,14 @@ test('find a poll using free text created with v0.3.0', function(assert) {
]
);
pollParticipate('Hermann Langbein', ["I don't care", 'would be awesome', "can't imagine anything better"]);
switchTab('participation');
andThen(function() {
assert.equal(currentPath(), 'poll.participation');
pollParticipate('Hermann Langbein', ["I don't care", 'would be awesome', "can't imagine anything better"]);
andThen(function() {
assert.equal(currentPath(), 'poll.evaluation');
pollHasUser(assert,
'Hermann Langbein',
[
@ -122,3 +146,5 @@ test('find a poll using free text created with v0.3.0', function(assert) {
});
});
});
});
});

View file

@ -0,0 +1,12 @@
import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:poll/participation', {
// Specify the other units that are required for this test.
// needs: ['controller:foo']
});
// Replace this with your real tests.
test('it exists', function(assert) {
var controller = this.subject();
assert.ok(controller);
});