This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository pollen. See https://gitlab.nuiton.org/chorem/pollen.git commit 5620de37cd0a4f554c1593b1238af930bb463b45 Author: Sylvain Bavencoff <bavencoff@codelutin.com> Date: Tue May 22 16:22:17 2018 +0200 refs #118 modification de l'ordre de choix dans un sondage --- .../pollen/persistence/entity/ChoiceTopiaDao.java | 7 +- .../pollen/services/service/ChoiceService.java | 5 +- pollen-ui-riot-js/src/main/web/js/PollForm.js | 7 +- .../src/main/web/tag/components/Draggable.tag.html | 29 +++- .../src/main/web/tag/poll/Choice.tag.html | 2 + .../src/main/web/tag/poll/Choices.tag.html | 192 +++++++++++++++++---- .../src/main/web/tag/poll/EditPoll.tag.html | 2 +- 7 files changed, 190 insertions(+), 54 deletions(-) diff --git a/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/ChoiceTopiaDao.java b/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/ChoiceTopiaDao.java index 347fb590..4c8a24a9 100644 --- a/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/ChoiceTopiaDao.java +++ b/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/ChoiceTopiaDao.java @@ -27,10 +27,9 @@ public class ChoiceTopiaDao extends AbstractChoiceTopiaDao<Choice> { public List<Choice> findAll(Poll poll) { - return forPollEquals(poll). - setOrderByArguments(Choice.PROPERTY_CHOICE_ORDER). - findAll(); - + return forPollEquals(poll) + .setOrderByArguments(Choice.PROPERTY_CHOICE_ORDER) + .findAll(); } @Override diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/ChoiceService.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/ChoiceService.java index 6dc46811..5dd99413 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/service/ChoiceService.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/ChoiceService.java @@ -135,9 +135,6 @@ public class ChoiceService extends PollenServiceSupport { ErrorMap errorMap = checkChoice(existingChoices, choice); errorMap.failIfNotEmpty(); - // set the choice order - choice.setChoiceOrder(existingChoices.size()); - Choice result = saveChoice(poll, choice); commit(); @@ -257,10 +254,10 @@ public class ChoiceService extends PollenServiceSupport { toSave.setCreator(principal); toSave.setPoll(poll); - toSave.setChoiceOrder(choice.getChoiceOrder()); } + toSave.setChoiceOrder(choice.getChoiceOrder()); toSave.setChoiceType(choice.getChoiceType()); if (ChoiceType.RESOURCE.equals(toSave.getChoiceType())) { diff --git a/pollen-ui-riot-js/src/main/web/js/PollForm.js b/pollen-ui-riot-js/src/main/web/js/PollForm.js index f6401828..1e87823d 100644 --- a/pollen-ui-riot-js/src/main/web/js/PollForm.js +++ b/pollen-ui-riot-js/src/main/web/js/PollForm.js @@ -135,6 +135,7 @@ class PollForm { } this.model.participant = []; this.choices = [new Choice("TEXT"), new Choice("TEXT"), new Choice("TEXT")]; + this.choices.forEach((c, index) => {c.choiceOrder = index;}); voterListService.init(this).then(voterList => { this.mainVoterList = voterList; @@ -206,9 +207,8 @@ class PollForm { save() { - let promises = [ - pollService.save(this.model), - this._saveChoices() + let promises = [this._saveChoices() + .then(() => pollService.save(this.model)) ]; if (this.model.pollType !== "FREE") { @@ -237,6 +237,7 @@ class PollForm { } else { choice = new Choice(this.model.choiceType); } + choice.choiceOrder = this.choices.length; this.choices.push(choice); } diff --git a/pollen-ui-riot-js/src/main/web/tag/components/Draggable.tag.html b/pollen-ui-riot-js/src/main/web/tag/components/Draggable.tag.html index 52c3c035..6bb928d9 100644 --- a/pollen-ui-riot-js/src/main/web/tag/components/Draggable.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/components/Draggable.tag.html @@ -18,10 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #L% --> -<Draggable onmousedown={startDesktop} - ontouchstart={startModbil} - ontouchmove={moveMobil} - ontouchend={endMobil}> +<Draggable> <yield/> <script type="es6"> @@ -30,6 +27,15 @@ this.positionElementInit = {x: 0, y: 0}; this.positionCurent = {x: 0, y: 0}; + this.on("mount", () => { + let anchor = this.refs.anchor || this.root; + anchor.onmousedown = this.startDesktop; + anchor.ontouchstart = this.startModbil; + anchor.ontouchmove = this.moveMobil; + anchor.ontouchend = this.endMobil; + anchor.classList.add("drag-anchor"); + }); + this.setPosition = (x, y) => { this.positionCurent.x = Math.round(x); this.positionCurent.y = Math.round(y); @@ -122,6 +128,21 @@ transition: transform 500ms; } + draggable.drag-anchor, + .drag-anchor { + cursor: move; + cursor: grab; + cursor: -moz-grab; + cursor: -webkit-grab; + } + + draggable.drag.drag-anchor, + draggable.drag .drag-anchor { + cursor: grabbing; + cursor: -moz-grabbing; + cursor: -webkit-grabbing; + } + draggable.drag { z-index: 1; transition: none; diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/Choice.tag.html b/pollen-ui-riot-js/src/main/web/tag/poll/Choice.tag.html index 95953e74..38ab881e 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/Choice.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/poll/Choice.tag.html @@ -233,6 +233,8 @@ import "../components/time-picker.tag.html"; this.toggleDescription = () => { this.showDescription = !this.showDescription; + this.update(); + this.opts.onHeightChange && this.opts.onHeightChange(); }; this.submit = () => { diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/Choices.tag.html b/pollen-ui-riot-js/src/main/web/tag/poll/Choices.tag.html index 542af80b..65fae3ef 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/Choices.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/poll/Choices.tag.html @@ -18,28 +18,39 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #L% --> -import "./Choice.tag.html"; + <Choices> <h4>{_t.title}</h4> - <div each={choice, index in form.choices} - class="o-form-element"> - <div class="c-input-group"> - <div class="o-field"> - <Choice ref="choice" - class="choice c-field" - name={"choice" + index} - disabled={form.hasVotes || form.model.closed} - choice="{choice}"/> - </div> - <button type="button" - class="c-button c-button--ghost-error" - show={!form.hasVotes} - disabled={form.hasVotes || form.model.closed} - onclick="{removeChoice(index)}"> - <i class="fa fa-trash"/> - </button> + <div class="choicesList" ref="choicesList"></div> + <Draggable each={choice, index in form.choices} + on-drop={dropChoice} + ref="choicesDrag" + class="choice-draggable"> + <div class="o-form-element"> + <div class="c-input-group"> + <div class="drag-anchor o-field" ref="anchor"> + <div class="c-field"> + <i class="fa fa-arrows-v "/> + </div> + </div> + <div class="o-field"> + <Choice ref="choice" + class="choice c-field" + name={"choice" + index} + disabled={parent.form.hasVotes || parent.form.model.closed} + choice="{choice}" + on-height-change={() => parent.placeChoices();}/> + </div> + <button type="button" + class="c-button c-button--ghost-error" + show={!parent.form.hasVotes} + disabled={parent.form.hasVotes || parent.form.model.closed} + onclick="{parent.removeChoice(index)}"> + <i class="fa fa-trash"/> + </button> + </div> </div> - </div> + </Draggable> <div class="choices-actions"> <button type="button" @@ -53,6 +64,9 @@ import "./Choice.tag.html"; </div> <script type="es6"> + import "./Choice.tag.html"; + import "../components/Draggable.tag.html"; + import session from "../../js/Session"; this.session = session; @@ -61,51 +75,153 @@ import "./Choice.tag.html"; this.installBundle(this.session, "poll_choices", this.opts.emitter); this.on("mount", () => { - if (Array.isArray(this.refs.choice)) { - this.refs.choice[0].focus(); - } else { - this.refs.choice.focus(); - } + let choices = (this.refs.choicesDrag || []) + .sort((c1, c2) => Math.sign(c1.choice.choiceOrder - c2.choice.choiceOrder)) + .map(c => c.refs.choice); + choices[0] && choices[0].focus(); + this.placeChoices(true); }); + this.placeChoices = noAnim => { + if (!this.refs.choicesDrag) { + return; + } + if (noAnim) { + this.root.classList.add("no-anim"); + } + + let currentY = this.refs.choicesList.offsetTop; + + let choicesDrag = this.refs.choicesDrag + .sort((c1, c2) => Math.sign(c1.choice.choiceOrder - c2.choice.choiceOrder)); + + for (let index = 0, l = choicesDrag.length; index < l; index++) { + let choiceDrag = choicesDrag[index]; + choiceDrag.setPosition(0, currentY); + currentY += choiceDrag.root.offsetHeight; + } + + this.refs.choicesList.style.height = (currentY - this.refs.choicesList.offsetTop) + "px"; + + setTimeout(() => { + this.root.classList.remove("no-anim"); + }, 100); + + if (!noAnim) { + setTimeout(() => { + this.update(); + }, 500); + } + + }; + + this.compatChoiceOrders = () => { + let index = 0; + this.refs.choicesDrag + .sort((c1, c2) => Math.sign(c1.choice.choiceOrder - c2.choice.choiceOrder)) + .forEach(choice1 => { + choice1.choice.choiceOrder = index; + index++; + }); + }; + + this.dropChoice = (choiceElt, x, y) => { + + let choicesDrag = this.refs.choicesDrag + .sort((c1, c2) => Math.sign(c1.choice.choiceOrder - c2.choice.choiceOrder)); + + choicesDrag.forEach(c => { + c.choice.choiceOrder = 1 + c.choice.choiceOrder * 2; // on multiplie par 2 pour toujours avoir un index d'insertion de libre + }); + + let index = 0; + choicesDrag.forEach(choice1 => { + let choice1Y = choice1.getY(); + if (y > choice1Y) { // le choix est palcé strictement après + index = choice1.choice.choiceOrder + 1; + } + this.logger.log("drop :" + choice1Y + ", " + y + ", " + choice1.choice.choiceOrder + " ==> " + index); + }); + choiceElt.choice.choiceOrder = index; + + this.compatChoiceOrders(); + + choiceElt.root.style["z-index"] = "1"; + this.placeChoices(); + setTimeout(() => { + choiceElt.root.style["z-index"] = ""; + this.submit(); + this.form.choices.sort((c1, c2) => Math.sign(c1.choiceOrder - c2.choiceOrder)); + this.update(); + (this.refs.choicesDrag || []) + .map(cd => cd.refs.choice) + .forEach(choice => { + choice.updateChoice(); + }); + }, 600); + }; + this.addOneChoice = () => { if (!this.form.hasVotes) { this.submit(); this.form.addNewChoice(); this.update(); - if (Array.isArray(this.refs.choice)) { - this.refs.choice[this.form.choices.length - 1].focus(); - } else { - this.refs.choice.focus(); - } + this.placeChoices(true); + let choices = (this.refs.choicesDrag || []) + .sort((c1, c2) => Math.sign(c1.choice.choiceOrder - c2.choice.choiceOrder)) + .map(c => c.refs.choice); + choices[choices.length - 1] && choices[choices.length - 1].focus(); } }; this.removeChoice = index => () => { if (!this.form.hasVotes) { this.form.removeChoice(index); + this.compatChoiceOrders(); + this.update(); + this.placeChoices(true); } }; this.submit = () => { - if (this.refs.choice) { - if (Array.isArray(this.refs.choice)) { - this.refs.choice.forEach(choiceTag => { - choiceTag.submit(); - }); - } else { - this.refs.choice.submit(); - } - } + (this.refs.choicesDrag || []) + .map(cd => cd.refs.choice) + .forEach(choice => { + choice.submit(); + }); }; </script> <style> + choices { + display: block; + position: relative; + } h4 { margin-bottom: 1em; } + choices.no-anim .choice-draggable { + transition: none; + } + + .choice-draggable .drag-anchor.o-field { + max-width : 3em; + } + + .choice-draggable .drag-anchor.o-field .c-field{ + height: 100%; + font-size: 1.3em; + text-align: center; + line-height: 100%; + } + + .choice-draggable { + width: 100%; + max-width: 100%; + } + .choice.c-field { padding: 0 0 0 1px; } diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/EditPoll.tag.html b/pollen-ui-riot-js/src/main/web/tag/poll/EditPoll.tag.html index 06161c08..93f67d4d 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/EditPoll.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/poll/EditPoll.tag.html @@ -38,7 +38,7 @@ import "./CheckEmails.tag.html"; </h1> - <div class="tabs tabs-one-small below-h1"> + <div class="tabs below-h1"> <div class="tab-container"> <div each={step, index in form.steps} class="tab {selected : !showSummary && form.step === index}"> <button type="submit" if="{form.step > index || !form.creation}" class="cursor-pointer" onclick="{goto(index)}"> -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.