This is an automated email from the git hooks/post-receive script. New commit to branch feature/pollen-riot-js in repository pollen. See https://gitlab.nuiton.org/chorem/pollen.git commit c802d85ebf3afe48f4337ee3a70e437c9e33574a Author: Tony CHEMIT <dev@tchemit.fr> Date: Tue Jan 24 17:01:50 2017 +0100 Ajout des votes --- pollen-ui-riot-js/src/main/web/i18n.json | 24 +- pollen-ui-riot-js/src/main/web/js/ChoiceService.js | 55 +++ pollen-ui-riot-js/src/main/web/js/VoteService.js | 55 +++ pollen-ui-riot-js/src/main/web/tag/poll/Poll.tag | 6 +- .../src/main/web/tag/poll/PollComments.tag | 21 +- .../src/main/web/tag/poll/PollCreated.tag | 2 +- .../src/main/web/tag/poll/PollVotes.tag | 395 ++++++++++++++++++++- 7 files changed, 541 insertions(+), 17 deletions(-) diff --git a/pollen-ui-riot-js/src/main/web/i18n.json b/pollen-ui-riot-js/src/main/web/i18n.json index 68fae6a..7cfb62e 100644 --- a/pollen-ui-riot-js/src/main/web/i18n.json +++ b/pollen-ui-riot-js/src/main/web/i18n.json @@ -1,6 +1,15 @@ { "fr": { - "comment_popup_create":"Créer un commentaire", + "poll_comments_noComment":"Pas de commentaire.", + "poll_comments_title":"Commentaires", + "poll_comments_addComment":"Ajouter un commentaire", + "poll_comments_deleteComment":"Supprimer le commentaire ?", + "poll_votes_title":"Votes", + "poll_votes_deleteVote":"Supprimer le vote ?", + "poll_votes_authorPlaceHolder":"Renseigner votre nom", + "poll_votes_vote":"Voter", + "poll_votes_delete":"Supprimer le vote ?", + "comment_popup_create":"Ajouter un commentaire", "comment_popup_edit":"Éditer un commentaire", "comment_popup_action":"Enregistrer", "comment_popup_authorPlaceholder":"Saisir votre nom", @@ -141,8 +150,17 @@ "": "" }, "en": { - "comment_popup_create":"Create a comment", - "comment_popup_edit":"edit a comment", + "poll_comments_noComment":"No comment.", + "poll_comments_title":"Comments", + "poll_comments_addComment":"Add a comment", + "poll_comments_deleteComment":"Delete comment?", + "poll_votes_title":"Votes", + "poll_votes_deleteVote":"Delete vote?", + "poll_votes_authorPlaceHolder":"Fill your name", + "poll_votes_vote":"Vote", + "poll_votes_delete":"Delete vote?", + "comment_popup_create":"Add a comment", + "comment_popup_edit":"Edit a comment", "comment_popup_action":"Save", "comment_popup_authorPlaceholder":"Fill your name", "comment_popup_textPlaceholder":"Fill your comment", diff --git a/pollen-ui-riot-js/src/main/web/js/ChoiceService.js b/pollen-ui-riot-js/src/main/web/js/ChoiceService.js new file mode 100644 index 0000000..210bad4 --- /dev/null +++ b/pollen-ui-riot-js/src/main/web/js/ChoiceService.js @@ -0,0 +1,55 @@ +/*- + * #%L + * Pollen :: UI (Riot Js) + * %% + * Copyright (C) 2009 - 2017 CodeLutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +let singleton = require("./Singleton"); +let FetchService = require("./FetchService"); + +class ChoiceService extends FetchService { + + getChoices(pollId, permission) { + let args = {}; + if (permission) { + args.permission = permission; + } + return this.getWithParams("/v1/polls/" + pollId + "/choices", args); + } + + addChoice(pollId, form) { + return this.form("/v1/polls/" + pollId + "/choices", {choice: form}); + } + + updateChoice(pollId, form, permission) { + let url = "/v1/polls/" + pollId + "/choices/" + form.id; + if (permission) { + url += "?permission=" + permission; + } + return this.form(url, {choice: form}); + } + + deleteChoice(pollId, choiceId, permission) { + let url = "/v1/polls/" + pollId + "/choices/" + choiceId; + if (permission) { + url += "?permission=" + permission; + } + return this.doDelete(url); + } +} + +module.exports = singleton(ChoiceService); diff --git a/pollen-ui-riot-js/src/main/web/js/VoteService.js b/pollen-ui-riot-js/src/main/web/js/VoteService.js new file mode 100644 index 0000000..b4029bb --- /dev/null +++ b/pollen-ui-riot-js/src/main/web/js/VoteService.js @@ -0,0 +1,55 @@ +/*- + * #%L + * Pollen :: UI (Riot Js) + * %% + * Copyright (C) 2009 - 2017 CodeLutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * #L% + */ +let singleton = require("./Singleton"); +let FetchService = require("./FetchService"); + +class VoteService extends FetchService { + + getVotes(pollId, permission) { + let args = {}; + if (permission) { + args.permission = permission; + } + return this.getWithParams("/v1/polls/" + pollId + "/votes", args); + } + + addVote(pollId, form) { + return this.form("/v1/polls/" + pollId + "/votes", {vote: form}); + } + + updateVote(pollId, form, permission) { + let url = "/v1/polls/" + pollId + "/votes/" + form.id; + if (permission) { + url += "?permission=" + permission; + } + return this.form(url, {vote: form}); + } + + deleteVote(pollId, voteId, permission) { + let url = "/v1/polls/" + pollId + "/votes/" + voteId; + if (permission) { + url += "?permission=" + permission; + } + return this.doDelete(url); + } +} + +module.exports = singleton(VoteService); diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/Poll.tag b/pollen-ui-riot-js/src/main/web/tag/poll/Poll.tag index 9ef8b61..690326d 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/Poll.tag +++ b/pollen-ui-riot-js/src/main/web/tag/poll/Poll.tag @@ -10,12 +10,12 @@ require('./PollComments.tag'); <div class="tab-header"> <div class="{selectedTab=='votes'?'tab-selected':'tab-not-selected'}"> - <a href="#poll/{pollId}/vote"> + <a href="#poll/{pollId}/vote{permission?'/' + permission : ''}"> <i class="fa fa-thumbs-o-up fa-flip-horizontal"></i>Votes </a> </div> <div class="{selectedTab=='comments'?'tab-selected':'tab-not-selected'}"> - <a href="#poll/{pollId}/comment"> + <a href="#poll/{pollId}/comment{permission?'/' + permission : ''}"> <i class="fa fa-comments-o"></i>Comments </a> </div> @@ -43,6 +43,7 @@ require('./PollComments.tag'); if (this.selectedTab == 'votes') { riot.mount(this.refs.content, "pollvotes", { pollId: this.pollId, + poll:this.poll, session: session, permission: this.permission }); @@ -73,6 +74,7 @@ require('./PollComments.tag'); padding: 15px 15px; width: 90%; margin-left: 20px; + margin-bottom: 10px; } .tab-content { diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/PollComments.tag b/pollen-ui-riot-js/src/main/web/tag/poll/PollComments.tag index b863063..978d237 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/PollComments.tag +++ b/pollen-ui-riot-js/src/main/web/tag/poll/PollComments.tag @@ -1,14 +1,15 @@ require('../Pagination.tag'); require('./CommentPopup.tag'); + <PollComments> <div class="comment-container"> <div class="legend"> - Comments <a if="{comments.length > -1}" onclick="{toggleSort}"><i ref="sortOwner" - class="fa fa-sort-amount-desc"></i></a> + {__.title} <a if="{comments.length > -1}" onclick="{toggleSort}"><i ref="sortOwner" + class="fa fa-sort-amount-desc"></i></a> </div> - <div class="body"> + <div show="{loaded}" class="body"> - <div if="{comments.length == 0}">Pas de commentaire</div> + <div show="{comments.length == 0}">{__.noComment}</div> <table show="{comments.length > 0}"> <tbody> <tr each="{comment in comments}"> @@ -48,7 +49,7 @@ require('./CommentPopup.tag'); </table> <div class="actions"> - <a class="button mainColorBackground" onclick="{openAddComment}">Ajouter un commentaire</a> + <a class="button mainColorBackground" onclick="{openAddComment}">{__.addComment}</a> </div> </div> </div> @@ -56,7 +57,7 @@ require('./CommentPopup.tag'); <CommentPopup ref="commentPopup"/> <script> - + this.loaded = false; let session = require("../../js/Session"); this.installBundle(session, "poll_comments"); let commentService = require("../../js/CommentService"); @@ -72,14 +73,14 @@ require('./CommentPopup.tag'); this.sortValue = true; // means desc this.comments = []; - this.toggleSort = (e) => { + this.toggleSort = () => { this.refs.sortOwner.classList.toggle('fa-sort-amount-desc'); this.refs.sortOwner.classList.toggle('fa-sort-amount-asc'); this.sortValue = !this.sortValue; this.pagination.onSortChange('postDate', this.sortValue); }; - this.openAddComment = (e) => { + this.openAddComment = () => { let comment = { text: '', authorName: '' @@ -123,7 +124,7 @@ require('./CommentPopup.tag'); let commentId = e.target.parentNode.parentNode.id; this.comments.forEach(comment => { if (comment.id == commentId) { - let response = confirm('Delete comment ?'); + let response = confirm(this.__deleteComment); if (response) { return commentService.deleteComment(this.pollId, commentId, this.permission || comment.permission || '') .then((result) => { @@ -149,6 +150,7 @@ require('./CommentPopup.tag'); return commentService.getComments(this.pollId, pagination, this.permission).then((result) => { this.comments = result.elements; console.info(this.comments); + this.loaded = true; this.update(); return result; }); @@ -156,6 +158,7 @@ require('./CommentPopup.tag'); </script> <style> + .danger { color: red; } diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/PollCreated.tag b/pollen-ui-riot-js/src/main/web/tag/poll/PollCreated.tag index 67fea23..7cdfaf4 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/PollCreated.tag +++ b/pollen-ui-riot-js/src/main/web/tag/poll/PollCreated.tag @@ -29,7 +29,7 @@ Le sondage «{form.model.title}» vient d'être créé. Un courriel vous a été adressé ainsi qu'aux éventuels participants. <br/> - <a href="#poll/{form.model.id}/edit">Accéder au sondage</a>. + <a href="#poll/{form.model.id}/vote">Accéder au sondage</a>. </div> </div> diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/PollVotes.tag b/pollen-ui-riot-js/src/main/web/tag/poll/PollVotes.tag index 69efb7c..42abedf 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/PollVotes.tag +++ b/pollen-ui-riot-js/src/main/web/tag/poll/PollVotes.tag @@ -1,5 +1,396 @@ <PollVotes> - <div> - Votes + <div class="vote-container"> + <div class="legend"> + {__.title} + </div> + <div show="{loaded}" class="body"> + <form ref="form" onsubmit="{addOrEditVote}"> + + <table> + <thead> + <tr> + <th></th> + <th each="{choice in choices}"> + {choice.choiceValue} + </th> + </tr> + </thead> + <tbody> + <tr ref="newVote" class="{voteId?'':'selected'}" if="{poll.canVote}"> + <td class="vote-header"> + <input ref="voterName" name="voterName" class="voter" type="text" + required="{voteId?'':'required'}" disabled="{voteId?'disabled':''}" + placeholder="{__.authorPlaceHolder}"> + <input name="newVote" disabled="{voteId?'disabled':''}" type="submit" + class="button mainColorBackground {voteId?'disabled':''}" + value="{__.vote}" onclick="{prepareNewVote}"> + </td> + <td class="vote-choice" each="{choice in choices}"> + <div> + <input class="check" type="checkbox" name="{choice.id}.voteValue" + disabled="{voteId?'disabled':''}"> + </div> + </td> + </tr> + <tr each="{vote in votes}" ref="vote_{vote.id}"> + <td class="vote-header"> + <div class="voter"> + <div show="{vote.id != voteId}"> + <i class="fa fa-user"/> + {vote.voterName} + </div> + <input ref="vote_{vote.id}_voter" class="voter" type="text" value="{vote.voterName}" + required="{vote.id == voteId?'required':''}" show="{voteId == vote.id}" + disabled="{vote.id == voteId?'':'disabled'}" + placeholder="{__.authorPlaceHolder}"> + </div> + <div class="actions vote right" if="{vote.permission && (!voteId || voteId != vote.id)}" + id="{vote.id}"> + <a onclick="{parent.onEditVote}"><i class="fa fa-pencil-square-o fa-15x"/></a> + <a onclick="{parent.deleteVote}"><i class="fa fa-trash danger fa-15x"/></a> + </div> + + <div class="actions right vote-cancel" show="{voteId == vote.id}"> + <a onclick="{cancelEditVote}" class="small danger"> + <i class="fa fa-remove fa-15x"/> + </a> + <button class="icon" ref="vote_{vote.id}_vote" type="submit" + value="{__.vote}"> + <i class="fa fa-check fa-15x"/> + </button> + </div> + + </td> + <td class="vote-choice" each="{choice in vote.choice}"> + <div> + <input ref="vote_{vote.id}_{choice.choiceId}" class="check" type="checkbox" + checked="{choice.voteValue == 1?'checked':''}" + disabled="{voteId == vote.id?'':'disabled'}"> + </div> + </td> + </tr> + </tbody> + </table> + </form> + </div> </div> + + <script> + this.voteId = null; + this.loaded = false; + let session = require("../../js/Session"); + this.installBundle(session, "poll_votes"); + let voteService = require("../../js/VoteService"); + let choiceService = require("../../js/ChoiceService"); + + this.pollId = opts.pollId; + this.permission = opts.permission; + this.poll = opts.poll; + + this.votes = []; + this.choices = []; + + this.on('mount', () => { + if (session.isConnected() && this.poll.canVote && session.user) { + this.refs.voterName.value = session.user.name; + } + this.form = this.refs.form; + }); + + this.prepareNewVote = () => { + this.voteId = null; + }; + + this.cancelEditVote = () => { + let previousVoteId = this.voteId; + this.voteId = null; + let tr = this.refs['vote_' + previousVoteId]; + tr.classList.remove("selected"); + this.votes.forEach(v => { + if (previousVoteId == v.id) { + v.choice.forEach(c => { + this.refs['vote_' + previousVoteId + "_" + c.choiceId].checked = c.voteValue == 1 ? 'checked' : ''; + }) + } + }); + + }; + + this.onEditVote = (e) => { + if (this.voteId) { + let tr = this.refs['vote_' + this.voteId]; + tr.classList.remove("selected"); + } + this.voteId = e.target.parentNode.parentNode.id; + let tr = e.target.parentNode.parentNode.parentNode.parentNode; + tr.classList.add("selected"); + this.refs['vote_' + this.voteId + "_vote"].value = this.__.vote; + this.votes.forEach(v => { + if (this.voteId == v.id) { + this.refs['vote_' + this.voteId + "_voter"].value = v.voterName; + } + }); + }; + + this.submit = e => { + console.info("submit") + this.refs.form.onsubmit(); + }; + this.addOrEditVote = e => { + e.preventDefault(); + e.stopPropagation(); + + let newVote = !this.voteId; + + let vote = {choice: []}; + + if (newVote) { + + vote.id = null; + vote.voterName = this.refs.voterName.value; + + let form = this.refs.form; + this.choices.forEach(c => { + + vote.choice.push({ + choiceId: c.id, + voteValue: form[c.id + '.voteValue'].checked ? 1 : 0 + }); + + }); + + } else { + + vote.id = this.voteId; + vote.voterName = this.refs['vote_' + this.voteId + '_voter'].value; + + this.votes.forEach(v => { + if (this.voteId == v.id) { + v.choice.forEach(c => { + vote.choice.push({ + id: c.id, + choiceId: c.choiceId, + voteValue: this.refs['vote_' + this.voteId + "_" + c.choiceId].checked ? 1 : 0 + }); + }) + } + }); + + } + + console.info("Vote to send:"); + console.info(vote); + if (newVote) { + voteService.addVote(this.pollId, vote) + .then((result) => { + console.info("created vote"); + console.info(result); + this.refresh(); + + this.refs.voterName.value = null; + let form = this.refs.form; + this.choices.forEach(c => { + form[c.id + '.voteValue'].checked = ''; + + }); + + }); + } else { + voteService.updateVote(this.pollId, vote, this.permission || vote.permission || '') + .then((result) => { + console.info("updated vote"); + console.info(result); + this.refresh(); + + this.cancelEditVote(); + }); + } + }; + + this.deleteVote = (e) => { + let voteId = e.target.parentNode.parentNode.id; + this.votes.forEach(vote => { + if (vote.id == voteId) { + let response = confirm(this.__delete); + if (response) { + return voteService.deleteVote(this.pollId, voteId, this.permission || vote.permission || '') + .then((result) => { + console.info("delete vote"); + console.info(result); + this.refresh(); + }); + } + } + }); + }; + + this.refresh = () => { + if (!this.pollId) { + return Promise.resolve([]); + } + return voteService.getVotes(this.pollId, this.permission).then((result) => { + this.votes = result; + console.info("Reload votes"); + console.info(this.votes); + this.loaded = true; + this.update(); + return result; + }); + }; + + choiceService.getChoices(this.pollId, this.permission) + .then((result) => { + console.info("get choices"); + console.info(result); + this.choices = result; + this.refresh(); + }); + + </script> + <style> + + .actions { + width: 70px; + } + + .icon:hover { + cursor: pointer; + } + + .icon { + border-style: none; + background-color: white; + } + + .fa-15x { + font-size: 1.5em; + } + + .danger { + color: red; + } + + div.voter { + width: 100%; + } + + input.voter { + width: 100%; + } + + .vote-container { + width: 800px; + } + + .right { + display: flex; + justify-content: flex-end; + flex-grow: 1; + } + + .vote > a, .vote-cancel > a, .vote-cancel > button { + padding-left: 7px; + } + + .vote-cancel > button { + color: #13a2ff; + } + + .vote-header { + display: flex; + flex-direction: row; + align-items: center; + justify-items: center; + height: 50px; + } + + .vote-choice > div { + display: flex; + flex-flow: column; + align-items: center; + } + + .vote-cancel { + display: flex; + flex-flow: row; + align-items: center; + } + + .legend { + width: 800px; + } + + input.button { + width: 60px; + margin: 0 0 0 5px; + } + + a.button { + margin: 0 0 0 5px; + } + + input.voter { + margin: 0; + } + + input.disabled { + background: #ddd; + cursor: not-allowed; + } + + th:first-child { + border-right: 1px solid #ddd; + border-top: none; + width: 400px; + } + + td:first-child { + border-left: 1px solid #ddd; + } + + tr.selected > td:first-child { + border-left: 2px solid #13a2ff; + } + + tr.selected > td:last-child { + border-right: 2px solid #13a2ff; + } + + tr.selected > td { + border-bottom: 2px solid #13a2ff; + border-right: 1px solid #ddd; + border-top: 2px solid #13a2ff; + } + + td { + border-bottom: 1px solid #ddd; + border-right: 1px solid #ddd; + vertical-align: middle; + padding-left: 5px; + padding-right: 5px; + } + + th { + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; + border-right: 1px solid #ddd; + vertical-align: middle; + height: 50px; + } + + table { + width: 800px; + border-spacing: 0; + } + + .check { + width: 20px; + height: 30px; + margin: 0 0 0 5px; + } + + .body { + margin-top: 10px; + } + </style> </PollVotes> \ No newline at end of file -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.