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 ba468f475bc8f73bb89ac6aed9afbc97b83877c0 Author: Sylvain Bavencoff <bavencoff@codelutin.com> Date: Fri Jun 23 16:20:56 2017 +0200 UI des des liste de votants : recherche et chargement à la volé (ref #77) --- .../org/chorem/pollen/persistence/DaoUtils.java | 49 +++++++++ .../entity/ChildFavoriteListTopiaDao.java | 21 ++++ .../entity/FavoriteListMemberTopiaDao.java | 42 ++++++++ .../persistence/entity/FavoriteListTopiaDao.java | 15 +++ .../chorem/pollen/rest/api/v1/FavoriteListApi.java | 19 ++-- pollen-rest-api/src/main/resources/mapping | 1 + .../pollen/services/bean/PaginationResultBean.java | 47 ++++++++ .../services/service/FavoriteListService.java | 120 +++++++++++++++++---- .../services/service/FavoriteListServiceTest.java | 4 +- pollen-ui-riot-js/src/main/web/css/blaze.css | 19 ++++ pollen-ui-riot-js/src/main/web/css/custom.css | 5 + pollen-ui-riot-js/src/main/web/css/main.css | 8 ++ pollen-ui-riot-js/src/main/web/i18n.json | 2 + .../src/main/web/js/FavoriteListService.js | 16 +-- .../src/main/web/tag/components/Card.tag.html | 1 + .../src/main/web/tag/components/LazyLoad.tag.html | 59 ++++++++++ .../main/web/tag/components/LoadingCard.tag.html | 39 +++++++ .../src/main/web/tag/components/Search.tag.html | 3 +- .../web/tag/favoriteList/ChildListCard.tag.html | 4 +- .../web/tag/favoriteList/FavoriteList.tag.html | 89 +++++++-------- .../web/tag/favoriteList/FavoriteListCard.tag.html | 3 +- .../favoriteList/FavoriteListEditModal.tag.html | 2 +- .../web/tag/favoriteList/FavoriteLists.tag.html | 57 ++++++---- .../main/web/tag/favoriteList/MemberCard.tag.html | 3 +- .../web/tag/favoriteList/MemberEditModal.tag.html | 2 +- .../src/main/web/tag/popup/Modal.tag.html | 7 ++ 26 files changed, 529 insertions(+), 108 deletions(-) diff --git a/pollen-persistence/src/main/java/org/chorem/pollen/persistence/DaoUtils.java b/pollen-persistence/src/main/java/org/chorem/pollen/persistence/DaoUtils.java new file mode 100644 index 00000000..8bcf9fd0 --- /dev/null +++ b/pollen-persistence/src/main/java/org/chorem/pollen/persistence/DaoUtils.java @@ -0,0 +1,49 @@ +package org.chorem.pollen.persistence; + +import org.apache.commons.lang3.StringUtils; + +import java.util.Map; + +/** + * @author Sylvain Bavencoff - bavencoff@codelutin.com + */ +public class DaoUtils { + + protected static final String LIKE = + "TRANSLATE(LOWER( %s )," + + "'áàâãäåāăąèééêëēĕėęěìíîïìĩīĭḩóôõöōŏőùúûüũūŭůäàáâãåæçćĉčöòóôõøüùúûßéèêëýñîìíïş'," + + "'aaaaaaaaaeeeeeeeeeeiiiiiiiihooooooouuuuuuuuaaaaaaeccccoooooouuuuseeeeyniiiis')" + + "like LOWER( %s )"; + + /** + * Generate sql like operator case and accent insensitive. + * + * @param field1 entity field to search into + * @param field2 value field (must be accent escaped) + * @return sql string + */ + public static String getFieldLikeInsensitive(String field1, String field2) { + String query = String.format(LIKE, field1, field2); + return query; + } + + public static String addQueryAttribute(Map<String, Object> args, String entityAttributeName, Object value) { + String baseAttributeName = entityAttributeName.replaceAll("[.]", "_"); + + int index = 0; + String queryAttributeName; + do { + queryAttributeName = baseAttributeName + index; + index++; + } while (args.containsKey(queryAttributeName)); + + args.put(queryAttributeName, value); + return queryAttributeName; + } + + + public static String getSearchClause(String alias, Map<String, Object> parameters, String entityAttributeName, String search) { + String queryAttributeName = addQueryAttribute(parameters, entityAttributeName, "%" + StringUtils.stripAccents(search) + "%"); + return getFieldLikeInsensitive(alias + "." + entityAttributeName,":" + queryAttributeName); + } +} diff --git a/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/ChildFavoriteListTopiaDao.java b/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/ChildFavoriteListTopiaDao.java new file mode 100644 index 00000000..54381739 --- /dev/null +++ b/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/ChildFavoriteListTopiaDao.java @@ -0,0 +1,21 @@ +package org.chorem.pollen.persistence.entity; + +import org.chorem.pollen.persistence.DaoUtils; +import org.nuiton.topia.persistence.HqlAndParametersBuilder; +import org.nuiton.util.pagination.PaginationParameter; +import org.nuiton.util.pagination.PaginationResult; + +public class ChildFavoriteListTopiaDao extends AbstractChildFavoriteListTopiaDao<ChildFavoriteList> { + + public PaginationResult<ChildFavoriteList> search(FavoriteList parent, String search, PaginationParameter page) { + + HqlAndParametersBuilder<ChildFavoriteList> builder = newHqlAndParametersBuilder(); + builder.addEquals(ChildFavoriteList.PROPERTY_PARENT, parent); + builder.addWhereClause(DaoUtils.getSearchClause( + builder.getAlias(), + builder.getHqlParameters(), ChildFavoriteList.PROPERTY_CHILD + "." + FavoriteList.PROPERTY_NAME, + search)); + + return findPage(builder.getHql(), builder.getHqlParameters(), page); + } +} \ No newline at end of file diff --git a/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/FavoriteListMemberTopiaDao.java b/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/FavoriteListMemberTopiaDao.java new file mode 100644 index 00000000..fd8b6ab3 --- /dev/null +++ b/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/FavoriteListMemberTopiaDao.java @@ -0,0 +1,42 @@ +package org.chorem.pollen.persistence.entity; + +import org.chorem.pollen.persistence.DaoUtils; +import org.nuiton.topia.persistence.HqlAndParametersBuilder; +import org.nuiton.util.pagination.PaginationParameter; +import org.nuiton.util.pagination.PaginationResult; + +import java.util.List; + + +public class FavoriteListMemberTopiaDao extends AbstractFavoriteListMemberTopiaDao<FavoriteListMember> { + + public PaginationResult<FavoriteListMember> search(FavoriteList parent, String search, PaginationParameter page) { + HqlAndParametersBuilder<FavoriteListMember> builder = getSearchBuilder(parent, search); + + return findPage(builder.getHql(), builder.getHqlParameters(), page); + + } + + private HqlAndParametersBuilder<FavoriteListMember> getSearchBuilder(FavoriteList parent, String search) { + HqlAndParametersBuilder<FavoriteListMember> builder = newHqlAndParametersBuilder(); + builder.addEquals(FavoriteListMember.PROPERTY_FAVORITE_LIST, parent); + builder.addWhereClause(DaoUtils.getSearchClause( + builder.getAlias(), + builder.getHqlParameters(), FavoriteListMember.PROPERTY_NAME, + search)); + return builder; + } + + public List<FavoriteListMember> search(FavoriteList parent, String search, String order, int startIndex, int endIndex) { + HqlAndParametersBuilder<FavoriteListMember> builder = getSearchBuilder(parent, search); + builder.setOrderByArguments(order); + + return find(builder.getHql(), builder.getHqlParameters(), startIndex, endIndex); + } + + public long countSearch(FavoriteList parent, String search) { + HqlAndParametersBuilder<FavoriteListMember> builder = getSearchBuilder(parent, search); + builder.setSelectClause("select count(" + builder.getAlias() + ".topiaId)"); + return count(builder.getHql(), builder.getHqlParameters()); + } +} diff --git a/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/FavoriteListTopiaDao.java b/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/FavoriteListTopiaDao.java index a80ad5f2..d8c7fd34 100644 --- a/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/FavoriteListTopiaDao.java +++ b/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/FavoriteListTopiaDao.java @@ -21,10 +21,25 @@ package org.chorem.pollen.persistence.entity; * #L% */ +import org.chorem.pollen.persistence.DaoUtils; +import org.nuiton.topia.persistence.HqlAndParametersBuilder; +import org.nuiton.util.pagination.PaginationParameter; +import org.nuiton.util.pagination.PaginationResult; + import java.util.List; public class FavoriteListTopiaDao extends AbstractFavoriteListTopiaDao<FavoriteList> { + public PaginationResult<FavoriteList> search(PollenUser user, String search, PaginationParameter page) { + + HqlAndParametersBuilder<FavoriteList> builder = newHqlAndParametersBuilder(); + builder.addEquals(FavoriteList.PROPERTY_POLLEN_USER, user); + builder.addWhereClause(DaoUtils.getSearchClause(builder.getAlias(), builder.getHqlParameters(), FavoriteList.PROPERTY_NAME, search)); + + return findPage(builder.getHql(), builder.getHqlParameters(), page); + } + + @Override public void delete(FavoriteList entity) { diff --git a/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/FavoriteListApi.java b/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/FavoriteListApi.java index c23e9335..93233ddd 100644 --- a/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/FavoriteListApi.java +++ b/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/FavoriteListApi.java @@ -32,6 +32,7 @@ import org.chorem.pollen.services.bean.FavoriteListBean; import org.chorem.pollen.services.bean.FavoriteListMemberBean; import org.chorem.pollen.services.bean.PaginationParameterBean; import org.chorem.pollen.services.bean.PaginationResultBean; +import org.chorem.pollen.services.bean.PollenBean; import org.chorem.pollen.services.bean.PollenEntityId; import org.chorem.pollen.services.bean.PollenEntityRef; import org.chorem.pollen.services.bean.export.ExportBean; @@ -52,9 +53,9 @@ import java.io.InputStream; */ public class FavoriteListApi extends WebMotionController { - public PaginationResultBean<FavoriteListBean> getFavoriteLists(FavoriteListService favoriteListService, PaginationParameterBean paginationParameter) { + public PaginationResultBean<FavoriteListBean> getFavoriteLists(FavoriteListService favoriteListService, PaginationParameterBean paginationParameter, String search) { - return favoriteListService.getFavoriteLists(paginationParameter); + return favoriteListService.getFavoriteLists(paginationParameter, search); } @@ -113,9 +114,9 @@ public class FavoriteListApi extends WebMotionController { } - public PaginationResultBean<FavoriteListMemberBean> getMembers(FavoriteListService favoriteListService, PollenEntityId<FavoriteList> favoriteListId, PaginationParameterBean paginationParameter) { + public PaginationResultBean<FavoriteListMemberBean> getMembers(FavoriteListService favoriteListService, PollenEntityId<FavoriteList> favoriteListId, String search, PaginationParameterBean paginationParameter) { - return favoriteListService.getFavoriteListMembers(favoriteListId.getEntityId(), paginationParameter); + return favoriteListService.getFavoriteListMembers(favoriteListId.getEntityId(), search, paginationParameter); } @@ -143,9 +144,9 @@ public class FavoriteListApi extends WebMotionController { } - public PaginationResultBean<ChildFavoriteListBean> getChildrenLists(FavoriteListService favoriteListService, PollenEntityId<FavoriteList> favoriteListId, PaginationParameterBean paginationParameter) { + public PaginationResultBean<ChildFavoriteListBean> getChildrenLists(FavoriteListService favoriteListService, PollenEntityId<FavoriteList> favoriteListId, String search, PaginationParameterBean paginationParameter) { - return favoriteListService.getChildrenLists(favoriteListId.getEntityId(), paginationParameter); + return favoriteListService.getChildrenLists(favoriteListId.getEntityId(), search, paginationParameter); } @@ -172,4 +173,10 @@ public class FavoriteListApi extends WebMotionController { favoriteListService.removeChildList(favoriteListId.getEntityId(), childListId.getEntityId()); } + + public PaginationResultBean<PollenBean> getAllChildren(FavoriteListService favoriteListService, PollenEntityId<FavoriteList> favoriteListId, String search, PaginationParameterBean paginationParameter) { + + return favoriteListService.getAllChildren(favoriteListId.getEntityId(), search, paginationParameter); + + } } diff --git a/pollen-rest-api/src/main/resources/mapping b/pollen-rest-api/src/main/resources/mapping index 63300517..4461cf0a 100644 --- a/pollen-rest-api/src/main/resources/mapping +++ b/pollen-rest-api/src/main/resources/mapping @@ -107,6 +107,7 @@ GET /v1/favoriteLists/{favoriteListId}/lists/{childListId} FavoriteListApi. POST /v1/favoriteLists/{favoriteListId}/lists FavoriteListApi.addChildList PUT,POST /v1/favoriteLists/{favoriteListId}/lists/{childListId} FavoriteListApi.editChildList DELETE /v1/favoriteLists/{favoriteListId}/lists/{childListId} FavoriteListApi.removeChildList +GET /v1/favoriteLists/{favoriteListId}/all FavoriteListApi.getAllChildren # PollApi diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/bean/PaginationResultBean.java b/pollen-services/src/main/java/org/chorem/pollen/services/bean/PaginationResultBean.java index 7dcc6444..bd940631 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/bean/PaginationResultBean.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/bean/PaginationResultBean.java @@ -47,6 +47,53 @@ public class PaginationResultBean<O extends PollenBean> { protected boolean desc; + public long getCount() { + return count; + } + + public void setCount(long count) { + this.count = count; + } + + public int getCurrentPage() { + return currentPage; + } + + public void setCurrentPage(int currentPage) { + this.currentPage = currentPage; + } + + public int getLastPage() { + return lastPage; + } + + public void setLastPage(int lastPage) { + this.lastPage = lastPage; + } + + public int getPageSize() { + return pageSize; + } + + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + } + + public String getOrder() { + return order; + } + + public void setOrder(String order) { + this.order = order; + } + + public boolean isDesc() { + return desc; + } + + public void setDesc(boolean desc) { + this.desc = desc; + } } protected final PaginationResultContextBean pagination = new PaginationResultContextBean(); diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/FavoriteListService.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/FavoriteListService.java index f80d7a7d..fbdb61f2 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/service/FavoriteListService.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/FavoriteListService.java @@ -43,6 +43,7 @@ import org.chorem.pollen.services.bean.FavoriteListBean; import org.chorem.pollen.services.bean.FavoriteListMemberBean; import org.chorem.pollen.services.bean.PaginationParameterBean; import org.chorem.pollen.services.bean.PaginationResultBean; +import org.chorem.pollen.services.bean.PollenBean; import org.chorem.pollen.services.bean.PollenEntityRef; import org.chorem.pollen.services.bean.export.ChildFavoriteListExport; import org.chorem.pollen.services.bean.export.ExportBean; @@ -95,7 +96,7 @@ public class FavoriteListService extends PollenServiceSupport { return input; } - public PaginationResultBean<FavoriteListBean> getFavoriteLists(PaginationParameterBean paginationParameter) { + public PaginationResultBean<FavoriteListBean> getFavoriteLists(PaginationParameterBean paginationParameter, String search) { checkIsConnected(); @@ -103,7 +104,14 @@ public class FavoriteListService extends PollenServiceSupport { PaginationParameter page = getFavoriteListPaginationParameter(paginationParameter); - PaginationResult<FavoriteList> favoriteLists = getFavoriteListDao().forPollenUserEquals(user).findPage(page); + PaginationResult<FavoriteList> favoriteLists; + + if (StringUtils.isNotBlank(search)) { + favoriteLists = getFavoriteListDao().search(user, search, page); + } else { + favoriteLists = getFavoriteListDao().forPollenUserEquals(user).findPage(page); + } + return toPaginationListBean(FavoriteListBean.class, favoriteLists, this::favoriteListBeanFunction); } @@ -182,7 +190,15 @@ public class FavoriteListService extends PollenServiceSupport { } public PaginationResultBean<FavoriteListMemberBean> getFavoriteListMembers(String favoriteListId, + String search, PaginationParameterBean paginationParameter) { + return getFavoriteListMembers(favoriteListId, search, paginationParameter, 0); + } + + protected PaginationResultBean<FavoriteListMemberBean> getFavoriteListMembers(String favoriteListId, + String search, + PaginationParameterBean paginationParameter, + int offset) { checkIsConnected(); checkNotNull(favoriteListId); @@ -192,8 +208,36 @@ public class FavoriteListService extends PollenServiceSupport { FavoriteList favoriteList = getFavoriteList0(user, favoriteListId); PaginationParameter page = getFavoriteListPaginationParameter(paginationParameter); + PaginationResult<FavoriteListMember> members; + + if (offset == 0 || page.getPageSize() == -1) { + if (StringUtils.isNotBlank(search)) { + members = getFavoriteListMemberDao().search(favoriteList, search, page); + } else { + members = getFavoriteListMemberDao().forFavoriteListEquals(favoriteList).findPage(page); + } + } else { + int startIndex = Math.max(0, page.getStartIndex() - offset); + int endIndex = Math.max(0, page.getEndIndex() - offset); + List<FavoriteListMember> elements; + long count; + if (StringUtils.isNotBlank(search)) { + elements = getFavoriteListMemberDao().search(favoriteList, search, + paginationParameter.getOrder() + (paginationParameter.isDesc() ? " DESC": ""), + startIndex, endIndex); + count = getFavoriteListMemberDao().countSearch(favoriteList, search); + + } else { + elements = getFavoriteListMemberDao() + .forFavoriteListEquals(favoriteList) + .setOrderByArguments(paginationParameter.getOrder() + (paginationParameter.isDesc() ? " DESC": "")) + .find(startIndex, endIndex); + count = getFavoriteListMemberDao().forFavoriteListEquals(favoriteList).count(); + } + members = PaginationResult.of(elements, count, page); + + } - PaginationResult<FavoriteListMember> members = getFavoriteListMemberDao().forFavoriteListEquals(favoriteList).findPage(page); return toPaginationListBean(FavoriteListMemberBean.class, members); } @@ -448,8 +492,6 @@ public class FavoriteListService extends PollenServiceSupport { boolean nameNotBlank = checkNotBlank(errors, "name", favoriteListName, l(getLocale(), "pollen.error.favoriteList.name.empty")); - boolean favoriteListExists = favoriteList.isPersisted(); - if (nameNotBlank) { Optional<FavoriteList> sameName = existingFavoriteLists.stream() @@ -604,31 +646,33 @@ public class FavoriteListService extends PollenServiceSupport { } - protected PaginationParameter getFavoriteListMemberPaginationParameter(PaginationParameterBean paginationParameter) { + public PaginationResultBean<ChildFavoriteListBean> getChildrenLists(String favoriteListId, String search, PaginationParameterBean paginationParameter) { - if (paginationParameter == null) { + checkIsConnected(); + checkNotNull(favoriteListId); - int pageSize = getPollenServiceConfig().getDefaultFavoriteListMemberPageSize(); - paginationParameter = PaginationParameterBean.of(0, pageSize); + PollenUser user = getConnectedUser(); - } + FavoriteList favoriteList = getFavoriteList0(user, favoriteListId); - return paginationParameter.toPaginationParameter(); + PaginationParameter page = getFavoriteListPaginationParameter(paginationParameter); - } + if (!page.getOrderClauses().isEmpty() && FavoriteList.PROPERTY_NAME.equals(page.getOrderClauses().get(0).getClause())) { + page.getOrderClauses().get(0).setClause(ChildFavoriteList.PROPERTY_CHILD + "." + FavoriteList.PROPERTY_NAME); + } - public PaginationResultBean<ChildFavoriteListBean> getChildrenLists(String favoriteListId, PaginationParameterBean paginationParameter) { + PaginationResult<ChildFavoriteList> children; - checkIsConnected(); - checkNotNull(favoriteListId); + if (StringUtils.isNotBlank(search)) { - PollenUser user = getConnectedUser(); + children = getChildFavoriteListDao().search(favoriteList, search, page); - FavoriteList favoriteList = getFavoriteList0(user, favoriteListId); + } else { - PaginationParameter page = getFavoriteListPaginationParameter(paginationParameter); + children = getChildFavoriteListDao().forParentEquals(favoriteList).findPage(page); + + } - PaginationResult<ChildFavoriteList> children = getChildFavoriteListDao().forParentEquals(favoriteList).findPage(page); return toPaginationListBean(ChildFavoriteListBean.class, children, this::childFavoriteListBeanFunction); } @@ -786,7 +830,8 @@ public class FavoriteListService extends PollenServiceSupport { protected ErrorMap checkImportVoterList(VoterList voterList, List<FavoriteList> existingFavoriteLists, ErrorMap errors) { String name = voterList.getName(); - boolean nameAlreadyUsed = existingFavoriteLists.stream().filter(fl -> fl.getName().equals(name)).findFirst().isPresent(); + boolean nameAlreadyUsed = existingFavoriteLists.stream() + .anyMatch(fl -> fl.getName().equals(name)); check(errors, FavoriteList.PROPERTY_NAME, !nameAlreadyUsed, l(getLocale(), "pollen.error.favoriteList.name.already.used", name)); @@ -989,4 +1034,39 @@ public class FavoriteListService extends PollenServiceSupport { return exportBean; } + + public PaginationResultBean<PollenBean> getAllChildren(String favoriteListId, String search, PaginationParameterBean paginationParameter) { + + checkIsConnected(); + checkNotNull(favoriteListId); + + // search children list in first + PaginationParameter page = getFavoriteListPaginationParameter(paginationParameter); + + PaginationResultBean<ChildFavoriteListBean> resultChildList = getChildrenLists(favoriteListId, search, paginationParameter); + PaginationResultBean<FavoriteListMemberBean> resultMember; + + if (paginationParameter.getPageSize() == -1) { + resultMember = getFavoriteListMembers(favoriteListId, search, paginationParameter); + } else { + resultMember = getFavoriteListMembers(favoriteListId, search, paginationParameter, (int) resultChildList.getPagination().getCount()); + } + + List<PollenBean> elements = Lists.newLinkedList(resultChildList.getElements()); + elements.addAll(resultMember.getElements()); + + PaginationResultBean<PollenBean> result = new PaginationResultBean<>(); + result.setElements(elements); + long count = resultChildList.getPagination().getCount() + resultMember.getPagination().getCount(); + result.setCount(count); + result.setCurrentPage(paginationParameter.getPageNumber()); + result.setLastPage((int) Math.max(0, Math.ceil(count * 1. / paginationParameter.getPageSize()) - 1)); + result.setPageSize(paginationParameter.getPageSize()); + result.setDesc(false); + result.setOrder(FavoriteListMember.PROPERTY_NAME); + + return result; + + } + } diff --git a/pollen-services/src/test/java/org/chorem/pollen/services/service/FavoriteListServiceTest.java b/pollen-services/src/test/java/org/chorem/pollen/services/service/FavoriteListServiceTest.java index 9036181a..639020dc 100644 --- a/pollen-services/src/test/java/org/chorem/pollen/services/service/FavoriteListServiceTest.java +++ b/pollen-services/src/test/java/org/chorem/pollen/services/service/FavoriteListServiceTest.java @@ -92,14 +92,14 @@ public class FavoriteListServiceTest extends AbstractPollenServiceTest { File importFile = new File(application.getTestBasedir(), "importFavoriteListFromFile_" + System.nanoTime()); Files.write(IMPORT_FILE_CONTENT, importFile, Charsets.UTF_8); - PaginationResultBean<FavoriteListMemberBean> members = service.getFavoriteListMembers(favoriteListId, PaginationParameterBean.of(0, -1)); + PaginationResultBean<FavoriteListMemberBean> members = service.getFavoriteListMembers(favoriteListId, PaginationParameterBean.of(0, -1), search); Assert.assertEquals(0, members.getElements().size()); // import file service.importFavoriteListMembersFromCsv(favoriteListId, importFile); - members = service.getFavoriteListMembers(favoriteListId, PaginationParameterBean.of(0, -1)); + members = service.getFavoriteListMembers(favoriteListId, PaginationParameterBean.of(0, -1), search); Assert.assertEquals(2, members.getElements().size()); } diff --git a/pollen-ui-riot-js/src/main/web/css/blaze.css b/pollen-ui-riot-js/src/main/web/css/blaze.css index e51d83cb..1e01379d 100644 --- a/pollen-ui-riot-js/src/main/web/css/blaze.css +++ b/pollen-ui-riot-js/src/main/web/css/blaze.css @@ -1425,3 +1425,22 @@ h6.c-heading{ background-color:transparent; color:var(--error); } + +.c-breadcrumbs { + display:block; + margin:0; + padding:0; + list-style:none; +} + +.c-breadcrumbs__crumb { + display:inline-block; + width:auto; + padding:0; +} + +.c-breadcrumbs__crumb:not(:last-child):after { + padding:0 .5em; + color: var(--link); + content:"/"; +} diff --git a/pollen-ui-riot-js/src/main/web/css/custom.css b/pollen-ui-riot-js/src/main/web/css/custom.css index b810ad13..7a5e5d68 100644 --- a/pollen-ui-riot-js/src/main/web/css/custom.css +++ b/pollen-ui-riot-js/src/main/web/css/custom.css @@ -12,6 +12,8 @@ --poll-voting: #13a2ff; --poll-closed: #53bd41; + --link: #96a8b2; + --separator: #b2c7d3; --dropdown: #f9f9f9; @@ -55,5 +57,8 @@ --code: #e5eaec; --code-text: #111; + --favorit-list: #feffcc; + --favorit-member: #ffffff; + --title: "Pollen"; } diff --git a/pollen-ui-riot-js/src/main/web/css/main.css b/pollen-ui-riot-js/src/main/web/css/main.css index 701144db..8353d887 100644 --- a/pollen-ui-riot-js/src/main/web/css/main.css +++ b/pollen-ui-riot-js/src/main/web/css/main.css @@ -180,6 +180,14 @@ ul { color: var(--main); } +.list-card { + background-color: var(--favorit-list) +} + +.member-card { + background-color: var(--favorit-member) +} + .poll-created { color: var(--poll-created); border-color: var(--poll-created); diff --git a/pollen-ui-riot-js/src/main/web/i18n.json b/pollen-ui-riot-js/src/main/web/i18n.json index b256996f..9ae584aa 100644 --- a/pollen-ui-riot-js/src/main/web/i18n.json +++ b/pollen-ui-riot-js/src/main/web/i18n.json @@ -397,6 +397,7 @@ "favoriteList_edit": "Modifier la liste de votants", "favoriteList_delete": "Supprimer", "favoriteList_deleteMessage": "Supprimer la liste de votants ?", + "favoriteList_loading": "Chargement...", "favoriteList_member_new": "Nouveau membre", "favoriteList_member_import": "Importer", "favoriteList_member_importCsv": "Importer un fichier CSV", @@ -870,6 +871,7 @@ "favoriteList_save": "Save", "favoriteList_delete": "Delete", "favoriteList_deleteMessage": "Delete favorite list ?", + "favoriteList_loading": "Loading...", "favoriteList_member_new": "New member", "favoriteList_member_import": "Import", "favoriteList_member_importCsv": "Import from CSV file", diff --git a/pollen-ui-riot-js/src/main/web/js/FavoriteListService.js b/pollen-ui-riot-js/src/main/web/js/FavoriteListService.js index 4ef43777..5934a82c 100644 --- a/pollen-ui-riot-js/src/main/web/js/FavoriteListService.js +++ b/pollen-ui-riot-js/src/main/web/js/FavoriteListService.js @@ -23,8 +23,8 @@ let FetchService = require("./FetchService"); class FavoriteListService extends FetchService { - favoriteLists(pagination) { - return this.getWithParams("/v1/favoriteLists", {paginationParameter: pagination}); + favoriteLists(pagination, search) { + return this.getWithParams("/v1/favoriteLists", {paginationParameter: pagination, search: search || ""}); } favoriteList(favoriteListId) { @@ -55,8 +55,8 @@ class FavoriteListService extends FetchService { return this.doDelete("/v1/favoriteLists/" + favoriteListId); } - members(favoriteListId, pagination) { - return this.getWithParams("/v1/favoriteLists/" + favoriteListId + "/members", {paginationParameter: pagination}); + members(favoriteListId, search, pagination) { + return this.getWithParams("/v1/favoriteLists/" + favoriteListId + "/members", {paginationParameter: pagination, search: search || ""}); } member(favoriteListId, memberId) { @@ -75,8 +75,8 @@ class FavoriteListService extends FetchService { return this.doDelete("/v1/favoriteLists/" + favoriteListId + "/members/" + memberId); } - childrenLists(favoriteListId, pagination) { - return this.getWithParams("/v1/favoriteLists/" + favoriteListId + "/lists", {paginationParameter: pagination}); + childrenLists(favoriteListId, search, pagination) { + return this.getWithParams("/v1/favoriteLists/" + favoriteListId + "/lists", {paginationParameter: pagination, search: search || ""}); } childList(favoriteListId, childListId) { @@ -99,6 +99,10 @@ class FavoriteListService extends FetchService { return this.form("/v1/favoriteLists/importVoterList", {voterListId: voterListId}); } + allChildren(favoriteListId, pagination, search) { + return this.getWithParams("/v1/favoriteLists/" + favoriteListId + "/all", {paginationParameter: pagination, search: search || ""}); + } + } module.exports = singleton(FavoriteListService); diff --git a/pollen-ui-riot-js/src/main/web/tag/components/Card.tag.html b/pollen-ui-riot-js/src/main/web/tag/components/Card.tag.html index 4f825b02..3701b44b 100644 --- a/pollen-ui-riot-js/src/main/web/tag/components/Card.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/components/Card.tag.html @@ -84,6 +84,7 @@ require("./LetterAvatar.tag.html"); .card-detail { width: 100%; + height: 1.5rem; } .card-weight { 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 new file mode 100644 index 00000000..9ad839b4 --- /dev/null +++ b/pollen-ui-riot-js/src/main/web/tag/components/LazyLoad.tag.html @@ -0,0 +1,59 @@ +<LazyLoad> + <virtual each={element, index in elements}> + <yield from="element"/> + </virtual> + <div show={nbNext > 0} ref="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(); + }; + + this.loadNext = () => { + if (!this.loading && this.nbNext > 0) { + this.opts.pagination.pageNumber++; + this.loading = true; + this.opts.onload(this.opts.pagination).then(result => { + this.elements = this.elements.concat(result.elements); + this.opts.pagination = result.pagination; + this.nbNext = this.opts.pagination.count - this.elements.length; + this.nbNextGroup = Math.min(this.opts.loadSize, this.nbNext); + this.loading = false; + this.update(); + }); + } + }; + + this.onscroll = e => { + // 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(); + } + }; + + window.addEventListener("scroll", this.onscroll); + + this.reload(); + + this.on("unmount", () => { + window.removeEventListener("scroll", this.onscroll); + }); + + </script> + + <style> + + </style> + + +</LazyLoad> diff --git a/pollen-ui-riot-js/src/main/web/tag/components/LoadingCard.tag.html b/pollen-ui-riot-js/src/main/web/tag/components/LoadingCard.tag.html new file mode 100644 index 00000000..01032e12 --- /dev/null +++ b/pollen-ui-riot-js/src/main/web/tag/components/LoadingCard.tag.html @@ -0,0 +1,39 @@ +<LoadingCard> + <div class="spinner"> + <i class="fa fa-spinner fa-pulse"></i> + </div> + <div class="count"> + <yield/> + <div> + + <style> + loadingcard { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + padding: 10px 0; + margin: 5px; + width: 180px; + overflow: hidden; + height: 207px; + background-color: var(--disabled); + } + + .spinner { + margin: 5px 0; + font-size: 5.5em; + } + + .count { + width: 100%; + font-size: 1.5em; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + } + + </style> + + +</LoadingCard> diff --git a/pollen-ui-riot-js/src/main/web/tag/components/Search.tag.html b/pollen-ui-riot-js/src/main/web/tag/components/Search.tag.html index 7865099e..c900a66c 100644 --- a/pollen-ui-riot-js/src/main/web/tag/components/Search.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/components/Search.tag.html @@ -24,10 +24,11 @@ if (this.searchTimeoutId) { window.clearTimeout(this.searchTimeoutId); } + this.opts.search.value = this.refs.search.value; this.searchTimeoutId = window.setTimeout(() => { delete this.searchTimeoutId; if (this.opts.onsearch) { - this.opts.onsearch(); + this.opts.onsearch(this.refs.search.value); } }, 300); }; diff --git a/pollen-ui-riot-js/src/main/web/tag/favoriteList/ChildListCard.tag.html b/pollen-ui-riot-js/src/main/web/tag/favoriteList/ChildListCard.tag.html index c5001534..f9555658 100644 --- a/pollen-ui-riot-js/src/main/web/tag/favoriteList/ChildListCard.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/favoriteList/ChildListCard.tag.html @@ -6,7 +6,9 @@ require("./ChildListEditModal.tag.html"); <Card name={opts.childList.child.name} weight={opts.childList.weight} ondelete={delete} - onedit={edit}> + onedit={edit} + href="#favoriteLists/{opts.childList.child.id}" + class="list-card"> <div> {parent.opts.childList.child.countChildren} <i class="fa fa-users"></i> </div> diff --git a/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteList.tag.html b/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteList.tag.html index 6705e976..9602c7ef 100644 --- a/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteList.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteList.tag.html @@ -4,9 +4,22 @@ require("./MemberEditModal.tag.html"); require("./ImportCsvModal.tag.html"); require("./ImportLdapModal.tag.html"); require("./ChildListEditModal.tag.html"); +require("../components/LazyLoad.tag.html"); +require("../components/LoadingCard.tag.html"); <FavoriteList> <div class="container" > + <div show="{loaded}"> + + <ol class="c-breadcrumbs"> + <li class="c-breadcrumbs__crumb"> + <a class="c-link" href="#favoriteLists"> + {__.title} + </a> + </li> + <li class="c-breadcrumbs__crumb c-text--loud">{favoriteList.name}</li> + </ol> + <h1 class="c-heading">{favoriteList.name} <div class="c-heading__sub"> <span if={favoriteList.countChildren > 0}> @@ -21,14 +34,21 @@ require("./ChildListEditModal.tag.html"); </div> </h1> - <div show={members.length > 0} > - <Search onsearch={refresh}/> - <div class="elements"> - <ChildListCard each={childList in childrenLists} child-list={childList} favorite-list={parent.favoriteList} on-child-list-change={parent.refresh}/> - <MemberCard each={member in members} member={member} favorite-list={parent.favoriteList} on-member-change={parent.refresh}/> - </div> - </div> + <Search onsearch={refresh} search={search}/> + + <LazyLoad pagination={pagination} onload={lazyLoad} load-size="20" ref="lazyLoad" class="elements"> + <yield to="element"> + <ChildListCard if={element.child} child-list={element} favorite-list={parent.parent.favoriteList} on-child-list-change={parent.parent.refresh}/> + <MemberCard if={element.name} member={element} favorite-list={parent.parent.favoriteList} on-member-change={parent.parent.refresh}/> + </yield> + <yield to="loading"> + <LoadingCard loading={nbNextGroup}> + {parent.parent._l("loading", nbNext)} + </LoadingCard> + </yield> + </LazyLoad> + <ContextualMenu> <a onclick={parent.addMember} > {parent.__.member_new} @@ -69,29 +89,18 @@ require("./ChildListEditModal.tag.html"); pageSize: -1, pageNumber: 0 }; - this.paginationChildrenLists = { - order: "child.name", - desc: false, - pageSize: -1, - pageNumber: 0 - }; this.favoriteList = { id: this.opts.favoriteListId }; - this.members = []; - this.childrenLists = []; - - this.delete = () => { - if (this.favoriteList.id) { - this.confirm(this.__.deleteMessage).then((confirm) => { - if (confirm) { - favoriteListService.deleteFavoriteList(this.favoriteList.id).then(() => { - route("/favoriteLists"); - }); - } - }); - } - }; + this.children = []; + + favoriteListService.favoriteList(this.favoriteList.id).then(results => { + this.favoriteList = results; + this.loaded = true; + this.update(); + }); + + this.search = {value: ""}; this.addMember = () => { this.refs.createMemberModal.open().then(() => { @@ -124,29 +133,13 @@ require("./ChildListEditModal.tag.html"); this.update(); }; - this.refresh = () => { - if (this.favoriteList.id) { - Promise.all([ - favoriteListService.favoriteList(this.favoriteList.id), - favoriteListService.members(this.favoriteList.id, this.pagination), - favoriteListService.childrenLists(this.favoriteList.id, this.paginationChildrenLists) - ]).then((results) => { - this.favoriteList = results[0]; - this.members = results[1].elements; - Object.assign(this.pagination, results[1].pagination); - this.childrenLists = results[2].elements; - Object.assign(this.paginationChildrenLists, results[2].pagination); - this.loaded = true; - this.update(); - return this.favoriteList; - }); - } else { - this.loaded = true; - } + this.refs.lazyLoad.reload(); }; - this.refresh(); + this.lazyLoad = pagination => { + return favoriteListService.allChildren(this.favoriteList.id, pagination, this.search.value); + }; </script> @@ -162,6 +155,6 @@ require("./ChildListEditModal.tag.html"); flex-wrap: wrap; justify-content: flex-start; } - + </style> </FavoriteList> diff --git a/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteListCard.tag.html b/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteListCard.tag.html index 52d3ac0d..b79361e7 100644 --- a/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteListCard.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteListCard.tag.html @@ -5,7 +5,8 @@ require("./FavoriteListEditModal.tag.html"); <Card name={opts.favoriteList.name} href="#favoriteLists/{opts.favoriteList.id}" ondelete={delete} - onedit={edit}> + onedit={edit} + class="list-card"> <div> {parent.opts.favoriteList.countChildren} <i class="fa fa-users"></i> </div> diff --git a/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteListEditModal.tag.html b/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteListEditModal.tag.html index c122f388..a55178f4 100644 --- a/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteListEditModal.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteListEditModal.tag.html @@ -2,7 +2,7 @@ require("../popup/Modal.tag.html"); <FavoriteListEditModal> <Modal ref="modal" - header={opts.favoriteList ? __.edit : __.new} + header={opts.favoriteList.id ? __.edit : __.new} label={__.save} type="success" onsubmit={save}> diff --git a/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteLists.tag.html b/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteLists.tag.html index 37495a23..c261e97c 100644 --- a/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteLists.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/favoriteList/FavoriteLists.tag.html @@ -1,13 +1,21 @@ require("../components/Search.tag.html"); require("../components/ContextualMenu.tag.html"); +require("../components/LazyLoad.tag.html"); +require("../components/LoadingCard.tag.html"); require("./FavoriteListCard.tag.html"); require("./FavoriteListEditModal.tag.html"); <FavoriteLists> + <div class="container" > + + <ol class="c-breadcrumbs"> + <li class="c-breadcrumbs__crumb c-text--loud">{__.title}</li> + </ol> + <div show="{loaded}"> <h1 class="c-heading">{__.title} <div class="c-heading__sub"> - {pagination.count === 0 ? __.noFavoriteList : (pagination.count + " " + (pagination.count === 1 ? __.one : __.many))} + {count === 0 ? __.noFavoriteList : (count + " " + (count === 1 ? __.one : __.many))} <a class="c-button c-button--info" if={pagination.count > 0} href="{session.configuration.endPoint}/v1/favoriteLists/exports" @@ -18,12 +26,18 @@ require("./FavoriteListEditModal.tag.html"); </div> </h1> - <div show={pagination.count > 0} > - <Search onsearch={refresh}/> - <div class="elements"> - <FavoriteListCard each={favoriteList in favoriteLists} favorite-list={favoriteList} on-favorite-list-change={parent.refresh}/> - </div> - </div> + <Search onsearch={refresh} search={search}/> + + <LazyLoad pagination={pagination} onload={lazyLoad} load-size="20" ref="lazyLoad" class="elements"> + <yield to="element"> + <FavoriteListCard favorite-list={element} on-favorite-list-change={parent.parent.refresh}/> + </yield> + <yield to="loading"> + <LoadingCard loading={nbNextGroup}> + {parent.parent._l("loading", nbNext)} + </LoadingCard> + </yield> + </LazyLoad> <ContextualMenu> <a onclick={parent.addFavoriteList}> @@ -55,7 +69,7 @@ require("./FavoriteListEditModal.tag.html"); <script type="es6"> this.loaded = false; this.session = require("../../js/Session"); - let favoriteListsService = require("../../js/FavoriteListService"); + let favoriteListService = require("../../js/FavoriteListService"); this.installBundle(this.session, "favoriteList"); this.pagination = { @@ -67,26 +81,29 @@ require("./FavoriteListEditModal.tag.html"); this.favoriteLists = []; this.importErrors = {}; + this.search = {value: ""}; + this.refresh = () => { - let search = this.refs.search && this.refs.search.value; - return favoriteListsService.favoriteLists(this.pagination, search).then((result) => { - this.favoriteLists = result.elements; - Object.assign(this.pagination, result.pagination); - this.loaded = true; - this.update(); + this.refs.lazyLoad.reload(); + }; + + this.lazyLoad = pagination => { + return favoriteListService.favoriteLists(pagination, this.search.value).then(result => { + if (!this.loaded) { + this.count = result.pagination.count; + this.loaded = true; + this.update(); + } return result; }); }; + this.addFavoriteList = () => { - this.refs.createFavoriteListModal.open().then(() => { - this.refresh(); - }, () => {}); + this.refs.createFavoriteListModal.open().then(() => {}, () => {}); this.update(); }; - this.refresh(); - this.openImportModal = () => { this.refs.importModal.open(); this.update(); @@ -96,7 +113,7 @@ require("./FavoriteListEditModal.tag.html"); e.preventDefault(); e.stopPropagation(); let importFile = this.refs.importFile.files[0]; - favoriteListsService.importFavoriteLists(importFile).then(() => { + favoriteListService.importFavoriteLists(importFile).then(() => { this.importErrors = {}; this.refs.importFile.value = ""; this.refresh(); diff --git a/pollen-ui-riot-js/src/main/web/tag/favoriteList/MemberCard.tag.html b/pollen-ui-riot-js/src/main/web/tag/favoriteList/MemberCard.tag.html index 0f3881b8..2a4cfe8a 100644 --- a/pollen-ui-riot-js/src/main/web/tag/favoriteList/MemberCard.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/favoriteList/MemberCard.tag.html @@ -5,7 +5,8 @@ require("./MemberEditModal.tag.html"); <Card name={opts.member.name} weight={opts.member.weight} ondelete={delete} - onEdit={edit}> + onEdit={edit} + class="member-card"> <span title={parent.opts.member.email}> {parent.opts.member.email} </span> diff --git a/pollen-ui-riot-js/src/main/web/tag/favoriteList/MemberEditModal.tag.html b/pollen-ui-riot-js/src/main/web/tag/favoriteList/MemberEditModal.tag.html index f6d5a5b5..2e915c2e 100644 --- a/pollen-ui-riot-js/src/main/web/tag/favoriteList/MemberEditModal.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/favoriteList/MemberEditModal.tag.html @@ -2,7 +2,7 @@ require("../popup/Modal.tag.html"); <MemberEditModal> <Modal ref="modal" - header={opts.member ? __.edit : __.new} + header={opts.member.id ? __.edit : __.new} label={__.save} type="success" onsubmit={save}> diff --git a/pollen-ui-riot-js/src/main/web/tag/popup/Modal.tag.html b/pollen-ui-riot-js/src/main/web/tag/popup/Modal.tag.html index 3781f995..e83bd0ae 100644 --- a/pollen-ui-riot-js/src/main/web/tag/popup/Modal.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/popup/Modal.tag.html @@ -76,6 +76,13 @@ <style> .o-modal { width: auto; + max-width: 90%; + } + + @media (max-width: 640px) { + .o-modal { + width: 90%; + } } </style> -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.