Author: kmorin Date: 2013-06-18 18:48:04 +0200 (Tue, 18 Jun 2013) New Revision: 3836 Url: http://chorem.org/projects/pollen/repository/revisions/3836 Log: - add user form with validation - improve vote live results and choices Added: trunk/pollen-ui-js/src/main/webapp/js/controls/user_form.js trunk/pollen-ui-js/src/main/webapp/js/models/users.js trunk/pollen-ui-js/src/main/webapp/views/user_form.ejs Modified: trunk/pollen-ui-js/src/main/resources/nuiton-js/wro.xml trunk/pollen-ui-js/src/main/webapp/bundle/Messages.properties trunk/pollen-ui-js/src/main/webapp/index.html trunk/pollen-ui-js/src/main/webapp/js/controls/poll_form.js trunk/pollen-ui-js/src/main/webapp/js/controls/vote.js trunk/pollen-ui-js/src/main/webapp/js/models/polls.js trunk/pollen-ui-js/src/main/webapp/js/models/votes.js trunk/pollen-ui-js/src/main/webapp/views/menu.ejs trunk/pollen-ui-js/src/main/webapp/views/poll_form.ejs trunk/pollen-ui-js/src/main/webapp/views/vote.ejs Modified: trunk/pollen-ui-js/src/main/resources/nuiton-js/wro.xml =================================================================== --- trunk/pollen-ui-js/src/main/resources/nuiton-js/wro.xml 2013-06-18 09:15:55 UTC (rev 3835) +++ trunk/pollen-ui-js/src/main/resources/nuiton-js/wro.xml 2013-06-18 16:48:04 UTC (rev 3836) @@ -8,6 +8,7 @@ <group-ref>can.fixture</group-ref> <group-ref>can.observe.attributes</group-ref> <group-ref>can.observe.delegate</group-ref> + <group-ref>can.observe.validations</group-ref> <group-ref>jquery.i18n.properties</group-ref> <group-ref>moment</group-ref> Modified: trunk/pollen-ui-js/src/main/webapp/bundle/Messages.properties =================================================================== --- trunk/pollen-ui-js/src/main/webapp/bundle/Messages.properties 2013-06-18 09:15:55 UTC (rev 3835) +++ trunk/pollen-ui-js/src/main/webapp/bundle/Messages.properties 2013-06-18 16:48:04 UTC (rev 3836) @@ -1,3 +1,5 @@ +pollen.app.title=Pollen + #common pollen.common.cancel=Annuler pollen.common.validate=Valider @@ -20,9 +22,22 @@ pollen.choice.name.label=Titre pollen.choice.name.placeholder=Titre +#user attributes +pollen.user.email.label=Email +pollen.user.email.placeholder=Email +pollen.user.login.label=Login +pollen.user.login.placeholder=Login +pollen.user.name.label=Nom +pollen.user.name.placeholder=Nom +pollen.user.password.label=Mot de passe +pollen.user.password.placeholder=Mot de passe +pollen.user.repeatPassword.label=Répétez le mot de passe +pollen.user.repeatPassword.placeholder=Répétez le mot de passe + #menu pollen.menu.home=Accueil pollen.menu.polls=Sondages +pollen.menu.register=Créer un compte #poll creation form pollen.poll.form.create.button.save=Créer le sondage @@ -58,4 +73,16 @@ pollen.vote.comments.list.title=Commentaires pollen.vote.comments.list.button.addComment=Ajouter un commentaire pollen.vote.comments.add.field.userName.placeholder=Votre nom -pollen.vote.comments.add.field.message.placeholder=Votre commentaire \ No newline at end of file +pollen.vote.comments.add.field.message.placeholder=Votre commentaire + +#user form +pollen.user.form.edit.title.creation=Enregistrement +pollen.user.form.edit.title.update=Compte utilisateur +pollen.user.form.edit.button.create=Créer le compte +pollen.user.form.edit.button.update=Sauvegarder + +#validation +pollen.validation.required=Le champ est obligatoire +pollen.validation.email.format.error=L''email n''est pas valide +pollen.validation.password.length.error=Le mot de passe doit faire au moins 6 caractères +pollen.validation.passwords.different.error=Les deux mots de passes ne correspondent pas \ No newline at end of file Modified: trunk/pollen-ui-js/src/main/webapp/index.html =================================================================== --- trunk/pollen-ui-js/src/main/webapp/index.html 2013-06-18 09:15:55 UTC (rev 3835) +++ trunk/pollen-ui-js/src/main/webapp/index.html 2013-06-18 16:48:04 UTC (rev 3836) @@ -11,10 +11,7 @@ <div class="navbar navbar-inverse navbar-static-top"> <div class="navbar-inner"> - <div class='container'> - <a class="brand" href="#">Pollen</a> - <ul class="nav" id="menu"> - </ul> + <div class='container' id="menu"> </div> </div> </div> @@ -25,6 +22,7 @@ <div id="pollForm"></div> <div id="pollSummary"></div> <div id="vote"></div> + <div id="userForm"></div> </div> @@ -34,11 +32,13 @@ <script src="js/models/polls.js"></script> <script src="js/models/comments.js"></script> <script src="js/models/votes.js"></script> + <script src="js/models/users.js"></script> <script src="js/controls/menu.js"></script> <script src="js/controls/poll_form.js"></script> <script src="js/controls/poll_summary.js"></script> <script src="js/controls/vote.js"></script> + <script src="js/controls/user_form.js"></script> <script type="text/javascript"> @@ -55,6 +55,7 @@ var pf = new PollForm('#pollForm'); var summary = new PollSummary('#pollSummary'); var vote = new PollVote('#vote'); + var userForm = new UserForm('#userForm'); can.route(':type/:action'); can.route(':type/:id/:action'); Modified: trunk/pollen-ui-js/src/main/webapp/js/controls/poll_form.js =================================================================== --- trunk/pollen-ui-js/src/main/webapp/js/controls/poll_form.js 2013-06-18 09:15:55 UTC (rev 3835) +++ trunk/pollen-ui-js/src/main/webapp/js/controls/poll_form.js 2013-06-18 16:48:04 UTC (rev 3836) @@ -29,10 +29,6 @@ } }, - '.save click': function() { - this.updatePoll(); - }, - '.create click': function() { can.route.attr({ type: "poll", action: "edit" }, true); }, @@ -41,24 +37,18 @@ can.route.attr({ type: "poll", action: "edit", id: el.data('id') }); }, - '{poll} created': function(data) { - can.route.attr({ type: "poll", action: "summary", id: data.id }); - }, - - '{poll} updated': function(data) { - can.route.attr({ type: "poll", action: "summary", id: data.id }); - }, - - updatePoll: function() { - var form = this.element.find('form'), - values = can.deparam(form.serialize()), + '#pollCreationForm submit': function(form) { + var values = can.deparam(form.serialize()), self = this; this.options.poll.attr(values).save(function(data) { if (data.id) { -// self.options.poll.id = data.id; + self.options.poll.id = data.id; } + can.route.attr({ type: "poll", action: "summary", id: data.id }); }); + + return false; }, editPoll: function(poll) { Copied: trunk/pollen-ui-js/src/main/webapp/js/controls/user_form.js (from rev 3834, trunk/pollen-ui-js/src/main/webapp/js/controls/poll_form.js) =================================================================== --- trunk/pollen-ui-js/src/main/webapp/js/controls/user_form.js (rev 0) +++ trunk/pollen-ui-js/src/main/webapp/js/controls/user_form.js 2013-06-18 16:48:04 UTC (rev 3836) @@ -0,0 +1,89 @@ +var UserForm = can.Control({ + + defaults: { + user: new User() + } + +}, { + init: function() { + + this.element.html(can.view('views/user_form.ejs', { + user: this.options.user + })); + + }, + + ':type/:action route': function(data) { + if (data.type === "user" && data.action === "registration") { + this.editUser(new User()); + } + }, + + ':type/:id/:action route': function(data) { + if (data.type === "user" && data.action === "edit") { + var self = this; + $.when(User.findOne({id: data.id})).then( + function(user) { + self.editUser(user); + }); + } + }, + + ':input change': function(el) { + this.options.user.attr(el.attr('name'), el.val()); + }, + + ':input keyup': function(el) { + this.options.user.attr(el.attr('name'), el.val()); + }, + + '#userEditionForm submit': function(form) { + var self = this, + user = this.options.user, + errors = user.errors(); + + if (errors == null) { + user.repeatPassword = null; + //TODO 20130618 kmorin encrypt password + user.save(function(data) { + if (data.id) { + user.id = data.id; + } + }); + + } else { + for (var error in errors) { + this.hideOrShowError(error, errors); + } + } + + return false; + }, + + '{user} change': function(user, ev, attr, type, newValue, oldValue) { + var errors = user.errors(); + this.hideOrShowError(attr, errors, type == "remove"); + }, + + hideOrShowError: function(attr, errors, forceHide) { + var error = errors && errors[attr]; + var el = this.element.find(':input[name="' + attr + '"]'); + + if (error && !forceHide) { + el.parents('.control-group').addClass('error'); + el.siblings('.help-inline').text($.i18n.prop(error, this.options.user[attr])); + el.siblings('.help-inline').show(); + + } else { + el.parents('.control-group').removeClass('error'); + el.siblings('.help-inline').hide(); + } + }, + + editUser: function(user) { + this.options.user.attr(user._data, true); + this.element.siblings().hide(); + this.element.show(); + } + +}); \ No newline at end of file Property changes on: trunk/pollen-ui-js/src/main/webapp/js/controls/user_form.js ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: svn:eol-style + native Modified: trunk/pollen-ui-js/src/main/webapp/js/controls/vote.js =================================================================== --- trunk/pollen-ui-js/src/main/webapp/js/controls/vote.js 2013-06-18 09:15:55 UTC (rev 3835) +++ trunk/pollen-ui-js/src/main/webapp/js/controls/vote.js 2013-06-18 16:48:04 UTC (rev 3836) @@ -25,12 +25,12 @@ }, '#voteForm submit': function(form) { - var voteToChoices = []; + var voteToChoices = {}; var choices = this.options.poll.choices; for (var i = 0 ; i < choices.length ; i++) { var choice = choices[i]; var voteValue = form.find("[name='" + i + "']").is(':checked'); - voteToChoices.push({ voteValue: voteValue, choice: choice.id }); + voteToChoices[choice.id] = { voteValue: voteValue, choice: choice.id }; } var voteAttributes = { @@ -42,9 +42,15 @@ var vote = new Vote(voteAttributes); var self = this; - vote.save({ pollId: this.options.poll.id }, - function(saved) { + var poll = this.options.poll; + vote.save({ pollId: poll.id }, function(saved) { self.options.votes.push(vote); + for (var choiceId in vote.voteToChoices) { + if (vote.voteToChoices[choiceId].voteValue) { + var result = poll.results[choiceId]; + poll.results.attr(choiceId, result + 1); + } + } }); form[0].reset(); @@ -56,7 +62,9 @@ var choices = this.options.poll.choices; var name = form.find("[name='name']").val(); var desc = form.find("[name='description']").val(); - choices.push({ name: name, description: desc }); + var choiceId = moment().valueOf(); + choices.push({ id: choiceId, name: name, description: desc }); + this.options.poll.results.attr(choiceId, 0); form[0].reset(); @@ -99,6 +107,27 @@ // other functions showPoll: function(poll, comments, votes) { + var results = new can.Observe.List(); + // init results + for (var i = 0 ; i < poll.choices.length ; i++) { + results[poll.choices[i].id] = 0; + } + // fill the results + for (i = 0 ; i < votes.length ; i++) { + var vote = votes[i]; + var voteToChoices = {}; + // create a map of the choices by id + for (var j = 0 ; j < vote.voteToChoices.length ; j++) { + var voteChoice = vote.voteToChoices[j]; + voteToChoices[voteChoice.choice] = voteChoice; + if (voteChoice.voteValue) { + results.attr(voteChoice.choice, results[voteChoice.choice] + 1); + } + } + vote.voteToChoices = voteToChoices; + } + poll.results = results; + this.options.poll = poll; this.options.votes = votes; this.options.comments = comments; Modified: trunk/pollen-ui-js/src/main/webapp/js/models/polls.js =================================================================== --- trunk/pollen-ui-js/src/main/webapp/js/models/polls.js 2013-06-18 09:15:55 UTC (rev 3835) +++ trunk/pollen-ui-js/src/main/webapp/js/models/polls.js 2013-06-18 16:48:04 UTC (rev 3836) @@ -33,7 +33,7 @@ description: "description très longue, mais vraiment très longue pour qu'elle passe en dessous. Enfin j'espère que ce sera assez long", creatorName: "moi", choiceAddAllowed: true, - beginChoiceDate: moment.valueOf(), + beginChoiceDate: moment().valueOf(), endChoiceDate: moment().add('m', 1).valueOf(), beginDate: moment().subtract('d', 5).valueOf(), endDate: moment().add('d', 5).valueOf(), Added: trunk/pollen-ui-js/src/main/webapp/js/models/users.js =================================================================== --- trunk/pollen-ui-js/src/main/webapp/js/models/users.js (rev 0) +++ trunk/pollen-ui-js/src/main/webapp/js/models/users.js 2013-06-18 16:48:04 UTC (rev 3836) @@ -0,0 +1,71 @@ +var User = can.Model({ + findAll: 'GET /users', + findOne: "GET /users/{id}", + create : "POST /users", + update : "PUT /users/{id}", + destroy : "DELETE /users/{id}", + + init: function() { + this.validatePresenceOf(["login", "name", "email", "password", "repeatPassword"], + { + message : "pollen.validation.required" + }); + + this.validateFormatOf( + ["email"], + /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, + { + message : "pollen.validation.email.format.error" + }); + + this.validateLengthOf(["password"], 6, 1000, { + message : "pollen.validation.password.length.error" + }); + + this.validate(["repeatPassword"], function(val) { + if (val != this.password) { + return "pollen.validation.passwords.different.error"; + } + }); + } + +}, {}); + +var USERS = [ + { + id: 1, + login: 'kmorin', + name: "Kevin Morin", + email: "kmorin@codelutin.com" + }, + { + id: 2, + login: 'tchemit', + name: "Tony Chemit", + email: "chemit@codelutin.com" + }, +]; + +can.fixture('GET /users', function(){ + return USERS; +}); + +can.fixture('GET /users/{id}', function(orig) { + return USERS[orig.data.id - 1]; +}); + +var idToInc= 3; +can.fixture("POST /users", function() { + console.log("create user " + idToInc); + return {id: (idToInc++)} +}); + +can.fixture("PUT /users/{id}", function(orig) { + console.log("update user " + orig.data.id); + return {}; +}); + +can.fixture("DELETE /users/{id}", function(orig){ + console.log("delete user " + orig.data.id); + return {}; +}); Modified: trunk/pollen-ui-js/src/main/webapp/js/models/votes.js =================================================================== --- trunk/pollen-ui-js/src/main/webapp/js/models/votes.js 2013-06-18 09:15:55 UTC (rev 3835) +++ trunk/pollen-ui-js/src/main/webapp/js/models/votes.js 2013-06-18 16:48:04 UTC (rev 3836) @@ -41,11 +41,11 @@ email: "william@dalt.on" }, voteToChoices: [{ + voteValue: 0, + choice: 2 + }, { voteValue: 1, choice: 1 - },{ - voteValue: 0, - choice: 2 }] }, { id: 4, Modified: trunk/pollen-ui-js/src/main/webapp/views/menu.ejs =================================================================== --- trunk/pollen-ui-js/src/main/webapp/views/menu.ejs 2013-06-18 09:15:55 UTC (rev 3835) +++ trunk/pollen-ui-js/src/main/webapp/views/menu.ejs 2013-06-18 16:48:04 UTC (rev 3836) @@ -1,7 +1,13 @@ -<li><%== can.route.link(pollen.menu.home, {}) %></li> -<li class="dropdown"> - <a href="#" role="button" class="dropdown-toggle" data-toggle="dropdown"><%= pollen.menu.polls %> <b class="caret"></b></a> - <ul class="dropdown-menu" role="menu"> - <li role="presentation"><%== can.route.link(pollen.menu.polls, { type: 'poll', action: 'edit' }, { role: 'menuitem', tabIndex: "-1" }, false) %></li> - </ul> -</li> \ No newline at end of file +<a class="brand" href="#"><%= pollen.app.title %></a> +<ul class="nav"> + <li><%== can.route.link(pollen.menu.home, {}) %></li> + <li class="dropdown"> + <a href="#" role="button" class="dropdown-toggle" data-toggle="dropdown"><%= pollen.menu.polls %> <b class="caret"></b></a> + <ul class="dropdown-menu" role="menu"> + <li role="presentation"><%== can.route.link(pollen.menu.polls, { type: 'poll', action: 'edit' }, { role: 'menuitem', tabIndex: "-1" }, false) %></li> + </ul> + </li> +</ul> +<ul class="nav pull-right"> +<li><%== can.route.link(pollen.menu.register, { type: 'user', action: 'registration' }) %></li> +</ul> \ No newline at end of file Modified: trunk/pollen-ui-js/src/main/webapp/views/poll_form.ejs =================================================================== --- trunk/pollen-ui-js/src/main/webapp/views/poll_form.ejs 2013-06-18 09:15:55 UTC (rev 3835) +++ trunk/pollen-ui-js/src/main/webapp/views/poll_form.ejs 2013-06-18 16:48:04 UTC (rev 3836) @@ -21,7 +21,7 @@ </div> <div class="form-actions"> - <a href="javascript://" class="btn btn-primary save"><%= pollen.poll.form.create.button.save %></a> + <button type='submit' class="btn btn-primary"><%= pollen.poll.form.create.button.save %></button> <a href="javascript://" class="btn btn-primary create">New poll</a> <a href="javascript://" data-id="1" class="btn btn">Edit poll 1</a> <a href="javascript://" data-id="2" class="btn btn">Edit poll 2</a> Added: trunk/pollen-ui-js/src/main/webapp/views/user_form.ejs =================================================================== --- trunk/pollen-ui-js/src/main/webapp/views/user_form.ejs (rev 0) +++ trunk/pollen-ui-js/src/main/webapp/views/user_form.ejs 2013-06-18 16:48:04 UTC (rev 3836) @@ -0,0 +1,75 @@ +<h1><%= user.attr('id') ? pollen.user.form.edit.title.update : pollen.user.form.edit.title.creation %></h1> + +<form id='userEditionForm' class="form-horizontal"> + + <div class="control-group"> + <label class="control-label" for="userEditionFormLogin"><%= pollen.user.login.label %></label> + <div class="controls"> + <input type='text' name='login' + id='userEditionFormLogin' + placeholder='<%= pollen.user.login.placeholder %>' + value='<%= user.attr("login")%>' + required/> + <span class="help-inline"></span> + </div> + </div> + + <div class="control-group"> + <label class="control-label" for="userEditionFormPassword"><%= pollen.user.password.label %></label> + <div class="controls"> + <input type='password' + name='password' + id='userEditionFormPassword' + placeholder='<%= pollen.user.password.placeholder %>' + value='<%= user.attr("password")%>' + required/> + <span class="help-inline"></span> + </div> + </div> + + <div class="control-group"> + <label class="control-label" for="userEditionFormRepeatPassword"><%= pollen.user.repeatPassword.label %></label> + <div class="controls"> + <input type='password' + name='repeatPassword' + id='userEditionFormRepeatPassword' + placeholder='<%= pollen.user.repeatPassword.placeholder %>' + value='<%= user.attr("repeatPassword")%>' + required/> + <span class="help-inline"></span> + </div> + </div> + + <div class="control-group"> + <label class="control-label" for="userEditionFormName"><%= pollen.user.name.label %></label> + <div class="controls"> + <input type='text' + name='name' + id='userEditionFormName' + placeholder='<%= pollen.user.name.placeholder %>' + value='<%= user.attr("name")%>' + required/> + <span class="help-inline"></span> + </div> + </div> + + <div class="control-group"> + <label class="control-label" for="userEditionFormEmail"><%= pollen.user.email.label %></label> + <div class="controls"> + <input type='email' + name='email' + id='userEditionFormEmail' + placeholder='<%= pollen.user.email.placeholder %>' + value='<%= user.attr("email")%>' + required/> + <span class="help-inline"></span> + </div> + </div> + + <div class="form-actions"> + <button type='submit' class="btn btn-primary save"> + <%= user.attr('id') ? pollen.user.form.edit.button.update : pollen.user.form.edit.button.create %> + </button> + </div> + +</form> \ No newline at end of file Modified: trunk/pollen-ui-js/src/main/webapp/views/vote.ejs =================================================================== --- trunk/pollen-ui-js/src/main/webapp/views/vote.ejs 2013-06-18 09:15:55 UTC (rev 3835) +++ trunk/pollen-ui-js/src/main/webapp/views/vote.ejs 2013-06-18 16:48:04 UTC (rev 3836) @@ -10,7 +10,7 @@ | <a id="commentSummary" class='link'><i class='icon-comment'></i> <%= comments.attr('length') %></a> <!-- poll dates --> <% if (poll.attr('beginDate') && poll.attr('endDate')) { %> - | <i class='icon-time'></i> <%= pollen.common.date.fromTo(moment(poll.attr('beginDate')).format(pollen.common.format.date), moment(poll.attr('endDate')).format(pollen.common.format.date)) %> + | <i class='icon-time'></i> <%= pollen.common.date.fromTo(moment(poll.attr('beginDate')).format(pollen.common.format.date), moment(poll.attr('endDate')).toString(pollen.common.format.date)) %> <% } else if (poll.attr('beginDate')) { %> | <i class='icon-time'></i> <%= pollen.common.date.from(moment(poll.attr('beginDate')).format(pollen.common.format.date)) %> <% } else if (poll.attr('endDate')) { %> @@ -40,7 +40,7 @@ <li><strong><%= choice.attr('name') %></strong> : <%= choice.attr('description') %></li> <% }); %> </ol> - <% var now = new Date().getTime(); + <% var now = moment().valueOf(); if (poll.attr('choiceAddAllowed') && poll.attr('beginChoiceDate') <= now && poll.attr('endChoiceDate') > now) {%> <!-- link to show/hide the new choice form --> @@ -77,14 +77,12 @@ <h2><%= pollen.vote.votes.list.title %></h2> <table id="voteTable" class="table table-bordered table-striped"> - <% var results = new can.Observe.List(new Array(poll.attr('choices.length'))); %> <thead> <tr> <th><%= pollen.vote.votes.list.header.voter %></th> <% poll.choices.each(function(choice, i) { %> <th data-container='body' data-placement='top' title='<%= choice.attr("description") %>'> <%= choice.attr('name') %> - <% results[i] = 0 %> </th> <% }); %> </tr> @@ -94,31 +92,27 @@ <tr> <td><%= vote.attr('voterListMember.name') %></td> <% - poll.choices.each(function(choice, i) { - var voteChoice = vote.voteToChoices.attr(i); - if (voteChoice == null) { - %> - <td class='choice voteBeforeChoice'></td> - <% } else { %> - <td class='choice <%= voteChoice.attr("voteValue") ? "selected" : "notSelected" %>'> - <%= voteChoice.attr("voteValue") ? "OK" : "" %> - </td> - <% - if (voteChoice.attr("voteValue")) { - // todo 20130614 kmorin check why if we do a results.attr(i, results[i] +1) ==> too much recursion - results[i] = results[i] + 1 - } - } - }); + poll.choices.each(function(choice) { + var voteChoice = vote.voteToChoices[choice.id]; + if (voteChoice == null) { + %> + <td class='choice voteBeforeChoice'></td> + <% } else { %> + <td class='choice <%= voteChoice.attr("voteValue") ? "selected" : "notSelected" %>'> + <%= voteChoice.attr("voteValue") ? "OK" : "" %> + </td> + <% + } + }); %> </tr> <% }); %> <tr> <td>Resultats</td> - <% poll.choices.each(function(choice, i) { %> + <% poll.choices.each(function(choice) { %> <td class='result'> - <%= results.attr(i) %> + <%= poll.results.attr(choice.id) %> </td> <% }); %> </tr> @@ -178,7 +172,7 @@ <%= comment.attr('text') %> </p> <div class='footer'> - <small><%= comment.attr('author') %> | <%= moment(comment.attr('postDate')).format(pollen.common.format.dateTime) %></small> + <small><%= comment.attr('author') %> | <%= new Date(comment.attr('postDate')).toString(pollen.common.format.dateTime) %></small> </div> </div>