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 98e57e63dc89f630e6143ecc06e344ea1329d726 Author: Sylvain Bavencoff <bavencoff@codelutin.com> Date: Wed Sep 13 10:52:59 2017 +0200 chargement en lazy des votes (ref #131) --- .../org/chorem/pollen/rest/api/v1/VoteApi.java | 11 +- pollen-services/src/main/config/PollenServices.ini | 6 + .../pollen/services/service/VoteService.java | 27 ++++- .../i18n/pollen-services_en_GB.properties | 1 + .../i18n/pollen-services_fr_FR.properties | 1 + pollen-ui-riot-js/src/main/web/js/Poll.js | 31 ++--- pollen-ui-riot-js/src/main/web/js/VoteService.js | 8 +- .../src/main/web/tag/components/LazyLoad.tag.html | 33 +++--- .../src/main/web/tag/poll/Votes.tag.html | 128 +++++++++++++++------ 9 files changed, 173 insertions(+), 73 deletions(-) diff --git a/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/VoteApi.java b/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/VoteApi.java index 1661a71c..80ac2ae5 100644 --- a/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/VoteApi.java +++ b/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/VoteApi.java @@ -23,12 +23,15 @@ package org.chorem.pollen.rest.api.v1; import org.chorem.pollen.persistence.entity.Poll; import org.chorem.pollen.persistence.entity.Vote; +import org.chorem.pollen.services.bean.PaginationParameterBean; +import org.chorem.pollen.services.bean.PaginationResultBean; import org.chorem.pollen.services.bean.PollenEntityId; import org.chorem.pollen.services.bean.PollenEntityRef; import org.chorem.pollen.services.bean.VoteBean; import org.chorem.pollen.services.service.InvalidFormException; import org.chorem.pollen.services.service.VoteService; +import javax.ws.rs.BeanParam; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -39,7 +42,6 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; -import java.util.List; /** * TODO @@ -63,10 +65,11 @@ public class VoteApi { @Path("/polls/{pollId}/votes") @GET - public List<VoteBean> getVotes(@Context VoteService voteService, - @PathParam("pollId") PollenEntityId<Poll> pollId) { + public PaginationResultBean<VoteBean> getVotes(@Context VoteService voteService, + @PathParam("pollId") PollenEntityId<Poll> pollId, + @BeanParam PaginationParameterBean paginationParameter) { - return voteService.getVotes(pollId.getEntityId()); + return voteService.getVotes(pollId.getEntityId(), paginationParameter); } diff --git a/pollen-services/src/main/config/PollenServices.ini b/pollen-services/src/main/config/PollenServices.ini index 852fd00e..f6a42182 100644 --- a/pollen-services/src/main/config/PollenServices.ini +++ b/pollen-services/src/main/config/PollenServices.ini @@ -101,6 +101,12 @@ key = pollen.default.commentPageSize type = int defaultValue = 10 +[option defaultVotePageSize] +description = pollen.configuration.defaultVotePageSize +key = pollen.default.votePageSize +type = int +defaultValue = 10 + [option defaultFavoriteListPageSize] description = pollen.configuration.defaultFavoriteListPageSize key = pollen.default.favoriteListPageSize diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/VoteService.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/VoteService.java index 4b1f2574..c2e2838e 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/service/VoteService.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/VoteService.java @@ -33,12 +33,16 @@ import org.chorem.pollen.persistence.entity.VoteToChoiceTopiaDao; import org.chorem.pollen.persistence.entity.VoterList; import org.chorem.pollen.persistence.entity.VoterListMember; import org.chorem.pollen.services.bean.ChoiceBean; +import org.chorem.pollen.services.bean.PaginationParameterBean; +import org.chorem.pollen.services.bean.PaginationResultBean; import org.chorem.pollen.services.bean.PollenEntityRef; import org.chorem.pollen.services.bean.ReportResumeBean; import org.chorem.pollen.services.bean.VoteBean; import org.chorem.pollen.services.bean.VoteToChoiceBean; import org.chorem.pollen.services.service.security.PermissionVerb; import org.chorem.pollen.votecounting.VoteCounting; +import org.nuiton.util.pagination.PaginationParameter; +import org.nuiton.util.pagination.PaginationResult; import java.util.ArrayList; import java.util.Collection; @@ -103,7 +107,7 @@ public class VoteService extends PollenServiceSupport { } - public List<VoteBean> getVotes(String pollId) { + public PaginationResultBean<VoteBean> getVotes(String pollId, PaginationParameterBean paginationParameter) { checkNotNull(pollId); @@ -117,7 +121,9 @@ public class VoteService extends PollenServiceSupport { } } - return toBeanList(VoteBean.class, votes, voteBeanFunction); + PaginationResult<Vote> votePaginationResult = PaginationResult.fromFullList(votes, getPaginationParameter(paginationParameter)); + + return toPaginationListBean(VoteBean.class, votePaginationResult, voteBeanFunction); } @@ -424,4 +430,21 @@ public class VoteService extends PollenServiceSupport { return getVoteDao().forPollEquals(poll).count(); } + + protected PaginationParameter getPaginationParameter(PaginationParameterBean paginationParameter) { + + if (paginationParameter == null) { + + int pageSize = getPollenServiceConfig().getDefaultVotePageSize(); + paginationParameter = PaginationParameterBean.of( + 0, + pageSize, + Vote.PROPERTY_TOPIA_CREATE_DATE, + true); + + } + + return paginationParameter.toPaginationParameter(); + + } } diff --git a/pollen-services/src/main/resources/i18n/pollen-services_en_GB.properties b/pollen-services/src/main/resources/i18n/pollen-services_en_GB.properties index 69f6e9af..b41d405e 100644 --- a/pollen-services/src/main/resources/i18n/pollen-services_en_GB.properties +++ b/pollen-services/src/main/resources/i18n/pollen-services_en_GB.properties @@ -19,6 +19,7 @@ pollen.configuration.defaultPollenUserPageSize=Default number of users per pages pollen.configuration.defaultResultVisibility=Default Result visibility pollen.configuration.defaultVoteCountingType=Default vote counting type used when creating a new poll pollen.configuration.defaultVoteNotification=Default notification type for the votes of a poll +pollen.configuration.defaultVotePageSize=Default number of vote per page pollen.configuration.defaultVoteVisibility=Default vote visiblity pollen.configuration.devMode=Dev mode pollen.configuration.feedback.locale=locale to send feedback diff --git a/pollen-services/src/main/resources/i18n/pollen-services_fr_FR.properties b/pollen-services/src/main/resources/i18n/pollen-services_fr_FR.properties index 9beac41c..2a17b57e 100644 --- a/pollen-services/src/main/resources/i18n/pollen-services_fr_FR.properties +++ b/pollen-services/src/main/resources/i18n/pollen-services_fr_FR.properties @@ -19,6 +19,7 @@ pollen.configuration.defaultPollenUserPageSize=Nombre d'utilisateurs par page pollen.configuration.defaultResultVisibility=Visibilité des résultats par défaut pollen.configuration.defaultVoteCountingType=Type de dépouillement par défaut lors de la création d'un nouveau sondage pollen.configuration.defaultVoteNotification=Type de notification par défaut pour les votes +pollen.configuration.defaultVotePageSize=Nombre de votes par page pollen.configuration.defaultVoteVisibility=Visibilité des votes par défaut pollen.configuration.devMode=Mode développement pollen.configuration.feedback.locale=La locale pour envoyer les retours utlisateur diff --git a/pollen-ui-riot-js/src/main/web/js/Poll.js b/pollen-ui-riot-js/src/main/web/js/Poll.js index 8815ae0f..6500acbb 100644 --- a/pollen-ui-riot-js/src/main/web/js/Poll.js +++ b/pollen-ui-riot-js/src/main/web/js/Poll.js @@ -16,7 +16,6 @@ class Poll { constructor() { this.choices = []; - this.votes = []; this.comments = []; } @@ -32,7 +31,6 @@ class Poll { this.votePermission = permission; } this.choices = undefined; - this.votes = undefined; this.comments = undefined; this.results = undefined; bus.trigger("poll", this); @@ -64,7 +62,6 @@ class Poll { delete this.permission; delete this._initPromise; this.choices = []; - this.votes = []; this.comments = []; bus.trigger("poll", this); }); @@ -124,12 +121,23 @@ class Poll { return Promise.reject("Init poll after add choice"); } - loadVotes() { + loadLazyVotes(pagination) { + if (this._initPromise) { + return voteService.getVotes(this.id, pagination, this.votePermission || this.permission).then((result) => { + if (this.voteIsVisible) { + this.voteCount = result.pagination.count; + } + return result; + }); + } + return Promise.reject("Init poll after load votes"); + } + + loadForVotes() { if (this._initPromise) { return this._initPromise.then(() => { var promises = [ choiceService.getChoices(this.id, this.permission || this.votePermission), - voteService.getVotes(this.id, this.votePermission || this.permission), voteCountingTypeService.getVoteCountingType(this.voteCountingType) ]; if (this.resultIsVisible) { @@ -143,11 +151,6 @@ class Poll { this.choices = resultsArray[indexResult++]; this.choiceCount = this.choices.length; - this.votes = resultsArray[indexResult++]; - if (this.voteIsVisible) { - this.voteCount = this.votes.length; - } - this.voteCountingTypeValue = resultsArray[indexResult++]; if (this.resultIsVisible) { @@ -295,9 +298,7 @@ class Poll { this.voteId = result.id; this.votePermission = result.permission; - return this.reloadPoll().then(() => { - return this.loadVotes(); - }); + return this.reloadPoll(); }); } return Promise.reject("Init poll after add vote"); @@ -306,7 +307,7 @@ class Poll { updateVote(vote) { if (this.id) { return voteService.updateVote(this.id, vote, this.votePermission || this.permission || vote.permission || "").then(() => { - return Promise.all([this.reloadPoll(), this.loadVotes()]); + return this.reloadPoll(); }); } return Promise.reject("Init poll after update vote"); @@ -315,7 +316,7 @@ class Poll { deleteVote(vote) { if (this.id) { return voteService.deleteVote(this.id, vote.id, this.votePermission || this.permission || vote.permission || "").then(() => { - return Promise.all([this.reloadPoll(), this.loadVotes()]); + return this.reloadPoll(); }); } return Promise.reject("Init poll after delete vote"); diff --git a/pollen-ui-riot-js/src/main/web/js/VoteService.js b/pollen-ui-riot-js/src/main/web/js/VoteService.js index 2805c44b..dee0fdf7 100644 --- a/pollen-ui-riot-js/src/main/web/js/VoteService.js +++ b/pollen-ui-riot-js/src/main/web/js/VoteService.js @@ -31,9 +31,13 @@ class VoteService extends FetchService { return url; } - getVotes(pollId, permission) { + getVotes(pollId, pagination, permission) { + let params = Object.assign({}, pagination); + if (permission) { + params.permission = permission; + } let url = this._getUrlPrefix(pollId); - return this.get(url, {permission: permission}); + return this.get(url, params); } addVote(pollId, form, permission) { diff --git a/pollen-ui-riot-js/src/main/web/tag/components/LazyLoad.tag.html b/pollen-ui-riot-js/src/main/web/tag/components/LazyLoad.tag.html index a0259021..3338d228 100644 --- a/pollen-ui-riot-js/src/main/web/tag/components/LazyLoad.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/components/LazyLoad.tag.html @@ -2,33 +2,39 @@ <virtual each={element, index in elements}> <yield from="element"/> </virtual> - <div show={nbNext > 0} ref="loading"> + <div show={nbNext > 0} ref="loading" class="loading"> <yield from="loading"/> </div> <script type="es6"> this.reload = () => { - this.elements = []; - this.nbNext = 1; - this.opts.pagination.pageSize = this.opts.loadSize; - this.opts.pagination.pageNumber = -1; - this.loadNext(); + if (!this.loading) { + this.elements = []; + this.nbNext = 1; + this.opts.pagination.pageSize = this.opts.loadSize; + this.opts.pagination.pageNumber = -1; + return this.loadNext(); + } + return Promise.reject(); }; this.loadNext = () => { if (!this.loading && this.nbNext > 0) { this.opts.pagination.pageNumber++; this.loading = true; - this.opts.onload(this.opts.pagination).then(result => { + return this.opts.onload(this.opts.pagination).then(result => { this.elements = this.elements.concat(result.elements); this.opts.pagination = result.pagination; + this.opts.pagination.pageNumber = result.pagination.currentPage; this.nbNext = this.opts.pagination.count - this.elements.length; this.nbNextGroup = Math.min(this.opts.loadSize, this.nbNext); this.loading = false; this.update(); + return Promise.resolve(); }); } + return Promise.reject(); }; this.onscroll = e => { @@ -37,15 +43,14 @@ // " - height window : " + e.currentTarget.clientHeight + // " - top loading : " + this.refs.loading.offsetTop); if (e.currentTarget.scrollTop + e.currentTarget.clientHeight > this.refs.loading.offsetTop) { - this.loadNext(); + this.loadNext().then(() => {}, () => {}); } } else { - // this.logger.info("scroll Y : " + e.currentTarget.scrollY + - // " - height : " + e.currentTarget.innerHeight + - // " - doc.height : " + e.target.body.children[0].clientHeight + - // " - loading.height : " + this.refs.loading.clientHeight); - if (e.currentTarget.scrollY + e.currentTarget.innerHeight > e.target.body.children[0].clientHeight - this.refs.loading.clientHeight) { - this.loadNext(); + let absolutTop = this.refs.loading.getBoundingClientRect().top; + // this.logger.info("absolutTop : " + absolutTop + + // " - height : " + e.currentTarget.innerHeight); + if (0 < absolutTop && absolutTop < e.currentTarget.innerHeight) { + this.loadNext().then(() => {}, () => {}); } } }; diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/Votes.tag.html b/pollen-ui-riot-js/src/main/web/tag/poll/Votes.tag.html index 27d679d7..73e1314e 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/Votes.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/poll/Votes.tag.html @@ -2,6 +2,7 @@ require("./Choice.tag.html"); require("./ChoiceView.tag.html"); require("./Podium.tag.html"); require("../components/HumanInput.tag.html"); +require("../components/LazyLoad.tag.html"); <Votes> <div class="container" show="{loaded}"> @@ -135,7 +136,7 @@ require("../components/HumanInput.tag.html"); </form> <!-- Show votes --> - <div class="voters" if="{poll.resultIsVisible && poll.votes && poll.votes.length > 0}"> + <div class="voters" if="{poll.resultIsVisible}"> <div class="row header separator-bottom"> <div></div> <div class="choices separator-left" if="{showChoiceHeader}"> @@ -145,36 +146,56 @@ require("../components/HumanInput.tag.html"); </div> </div> </div> - <div each="{vote, index in poll.votes}" class="row separator-bottom separator-right"> - <div class="name separator-left"> - <div class="voter-name" onmouseenter="{parent.showTooltip(vote)}" onclick="{parent.showTooltip(vote)}" onmouseleave="{parent.hideTooltip}"> - <i class="fa fa-user-circle c-icon"></i> - <span if="{!vote.anonymous}">{vote.voterName}</span> - <span if="{vote.anonymous}" class="anonymous-voter">{parent.__.anonymousVoter}</span> + <LazyLoad pagination={pagination} onload={lazyLoad} load-size="20" not-load-on-start="true" ref="lazyLoad" class="elements"> + <yield to="element"> + <div class="row separator-bottom separator-right"> + <div class="name separator-left"> + <div class="voter-name" onmouseenter="{parent.parent.showTooltip(element)}" onclick="{parent.parent.showTooltip(vote)}" onmouseleave="{parent.parent.hideTooltip}"> + <i class="fa fa-user-circle c-icon"></i> + <span if="{!element.anonymous}">{element.voterName}</span> + <span if="{element.anonymous}" class="anonymous-voter">{parent.__.anonymousVoter}</span> + </div> + <button type="button" + class="c-button c-button--rounded u-xsmall c-button--brand" + if="{!parent.parent.poll.closed && element.permission}" + disabled="{parent.parent.voteInEdition != null || parent.parent.voting}" + onclick="{parent.parent.onEditVote(element)}"> + <i class="fa fa-pencil-square-o"></i> + </button> + <button type="button" + class="c-button c-button--rounded u-xsmall c-button--error" + if="{!parent.parent.poll.closed && (parent.parent.poll.permission || element.permission)}" + disabled="{parent.parent.voteInEdition != null || parent.parent.voting}" + onclick="{parent.parent.deleteVote(element)}"> + <i class="fa fa-trash"></i> + </button> + </div> + <div class="results separator-left" ref="results{index}"> + <div each="{choice in parent.parent.poll.choices}" + class="result separator-right {'checkbox' : parent.parent.pollTypeCheckbox} {'selected' : parent.parent.pollTypeCheckbox && parent.parent.poll.getVoteValue(element, choice) == 1}" + onmouseenter="{parent.parent.parent.showTooltip(element, choice)}" onclick="{parent.parent.parent.showTooltip(element, choice)}" onmouseleave="{parent.parent.parent.hideTooltip}"> + <span if="{!pollTypeCheckbox}">{parent.parent.parent.poll.getVoteValue(element, choice)}</span> + </div> + </div> </div> - <button type="button" - class="c-button c-button--rounded u-xsmall c-button--brand" - if="{!poll.closed && vote.permission}" - disabled="{voteInEdition != null || voting}" - onclick="{parent.onEditVote(vote)}"> - <i class="fa fa-pencil-square-o"></i> - </button> - <button type="button" - class="c-button c-button--rounded u-xsmall c-button--error" - if="{!poll.closed && (poll.permission || vote.permission)}" - disabled="{voteInEdition != null || voting}" - onclick="{parent.deleteVote(vote)}"> - <i class="fa fa-trash"></i> - </button> - </div> - <div class="results separator-left" ref="results{index}"> - <div each="{choice in poll.choices}" - class="result separator-right {'checkbox' : pollTypeCheckbox} {'selected' : pollTypeCheckbox && poll.getVoteValue(vote, choice) == 1}" - onmouseenter="{parent.showTooltip(vote, choice)}" onclick="{parent.showTooltip(vote, choice)}" onmouseleave="{parent.hideTooltip}"> - <span if="{!pollTypeCheckbox}">{poll.getVoteValue(vote, choice)}</span> + </yield> + <yield to="loading"> + <div class="row separator-bottom separator-right"> + <div class="name separator-left"> + <div class="voter-name"> + <i class="fa fa-user-circle c-icon"></i> + <i class="fa fa-spinner fa-pulse"></i> + </div> + </div> + <div class="results separator-left" ref="results{index}"> + <div each="{choice in parent.poll.choices}" + class="result separator-right"> + <i class="fa fa-spinner fa-pulse"></i> + </div> + </div> </div> - </div> - </div> + </yield> + </LazyLoad> <div class="vote-tooltip" ref="voteTooltip" show="{voteTooltip.show}"> <ChoiceView if="{voteTooltip.choice}" choice="{voteTooltip.choice}" center="true" hideReport="true"></ChoiceView> @@ -202,15 +223,33 @@ require("../components/HumanInput.tag.html"); this.addingChoice = false; this.poll = require("../../js/Poll.js"); - this.poll.loadVotes().then(() => { - this.updateShowChoiceContainer(); + this.poll.loadForVotes().then(() => { + this.refresh(); this.update(); }); + + this.pagination = { + order: "topiaCreateDate", + desc: true + }; + + this.lazyLoad = pagination => { + return this.poll.loadLazyVotes(pagination).then((result) => { + return result; + }); + }; + + this.refresh = () => { + this.refs.lazyLoad && this.refs.lazyLoad.reload().then(() => { + this.updateShowChoiceContainer(); + }, () => {}); + }; + this.choiceToAdd = this.poll.initChoice(); this.updateShowChoiceContainer = () => { - this.showChoiceHeader = this.refs.results0 - && this.refs.results0.offsetWidth / this.poll.choices.length >= MIN_CHOICE_COLUMN_WIDTH; + let results0 = this.refs.lazyLoad.refs.results0; + this.showChoiceHeader = results0 && results0.offsetWidth / this.poll.choices.length >= MIN_CHOICE_COLUMN_WIDTH; }; this.onPollChange = poll => { @@ -219,13 +258,14 @@ require("../components/HumanInput.tag.html"); this.pollTypeCheckbox = poll.voteCountingTypeValue && poll.voteCountingTypeValue.renderType === "checkbox"; this.choiceToAdd = this.poll.initChoice(this.choiceToAdd); this.onVoteChanged(); + this.refresh(); this.update(); }; this.listen("poll", this.onPollChange); this.listen("user", (user, oldUser) => { - if (user != oldUser) { - this.poll.loadVotes(); + if (user !== oldUser) { + this.refresh(); this.update(); } }); @@ -319,6 +359,9 @@ require("../components/HumanInput.tag.html"); this.poll.addVote(vote).then(() => { this.resetPoll(); + if (this.poll.voteIsVisible) { + this.refresh(); + } if (this.poll.resultIsVisible) { this.poll.loadResults().then(() => { this.voting = false; @@ -364,6 +407,9 @@ require("../components/HumanInput.tag.html"); this.poll.updateVote(updateVote).then(() => { this.voteInEdition = null; this.resetPoll(); + if (this.poll.voteIsVisible) { + this.refresh(); + } if (this.poll.resultIsVisible) { this.poll.loadResults().then(() => { this.voting = false; @@ -585,6 +631,15 @@ require("../components/HumanInput.tag.html"); margin-top: 50px; } + .voters .elements, + .voters .loading { + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + .voters .row { display: flex; width: 95%; @@ -629,7 +684,8 @@ require("../components/HumanInput.tag.html"); overflow: visible; } - .voters .results, .voters .choices { + .voters .results, + .voters .choices { display: flex; width: 100%; } -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.