/** * Copyright (C) 2016 Premium Minds. * * This file is part of pm-wicket-utils. * * pm-wicket-utils is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * pm-wicket-utils is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with pm-wicket-utils. If not, see <http://www.gnu.org/licenses/>. */ package com.premiumminds.webapp.wicket; import java.util.List; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.behavior.Behavior; import org.apache.wicket.markup.head.IHeaderResponse; import org.apache.wicket.markup.head.JavaScriptHeaderItem; import org.apache.wicket.markup.head.OnDomReadyHeaderItem; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import org.apache.wicket.request.resource.JavaScriptResourceReference; import org.apache.wicket.request.resource.ResourceReference; public abstract class InfiniteScrollListView<T> extends WebMarkupContainer { private static final long serialVersionUID = 3906374470444741117L; private static final int MAX_ELEMENTS = 100; private static final int SCROLL_SIZE = 20; private static final ResourceReference javascriptReference = new JavaScriptResourceReference(InfiniteScrollListView.class, "InfiniteScrollListView.js"); private WebMarkupContainer listContainer; private WebMarkupContainer upLoading; private WebMarkupContainer downLoading; private ListView<T> listView; private AbstractDefaultAjaxBehavior upBehavior; private AbstractDefaultAjaxBehavior downBehavior; public InfiniteScrollListView(String id, PageableListModel<? extends List<? extends T>> model) { super(id, model); } @SuppressWarnings("serial") @Override protected void onInitialize() { super.onInitialize(); setOutputMarkupPlaceholderTag(true); add(listContainer = new WebMarkupContainer("list")); listContainer.setOutputMarkupPlaceholderTag(true); listContainer.add(upLoading = new WebMarkupContainer("upLoading")); upLoading.setOutputMarkupPlaceholderTag(true); upLoading.setVisible(false); add(upBehavior = new AbstractDefaultAjaxBehavior() { private static final long serialVersionUID = -1895111598850513557L; @Override protected void respond(AjaxRequestTarget target) { target.appendJavaScript("InfiniteScroll.getFromContainer('"+InfiniteScrollListView.this.getMarkupId()+"').scrollDownTo('"+getItemMarkupId(InfiniteScrollListView.this.getModel().getObject().get(0))+"')"); setStartIndex(InfiniteScrollListView.this.getModel().getStartIndex()-SCROLL_SIZE); refreshLoadings(); target.add(listContainer); } }); listView = new ListView<T>("list", new LoadableDetachableModel<List<T>>(){ @Override protected List<T> load() { return getModel().getObject(); } }) { private static final long serialVersionUID = -1218380580005295126L; @Override protected void populateItem(ListItem<T> item) { InfiniteScrollListView.this.populateItem(item); item.setMarkupId(getItemMarkupId(item.getModelObject())); } @Override public void renderHead(IHeaderResponse response) { String containerId = InfiniteScrollListView.this.getMarkupId(); response.render(JavaScriptHeaderItem.forReference(javascriptReference, "infinite-scroll-list-view-js")); response.render(OnDomReadyHeaderItem.forScript("InfiniteScroll.getFromContainer('"+containerId+"').changeUp("+Boolean.toString(isShowUpLoading())+")")); response.render(OnDomReadyHeaderItem.forScript("InfiniteScroll.getFromContainer('"+containerId+"').changeDown("+Boolean.toString(isShowDownLoading())+")")); super.renderHead(response); } }; listContainer.add(listView); listContainer.add(downLoading = new WebMarkupContainer("downLoading")); downLoading.setOutputMarkupPlaceholderTag(true); downLoading.setVisible(getModel().getSize()>MAX_ELEMENTS); add(downBehavior = new AbstractDefaultAjaxBehavior() { private static final long serialVersionUID = -1895111598850513557L; @Override protected void respond(AjaxRequestTarget target) { target.appendJavaScript("InfiniteScroll.getFromContainer('"+InfiniteScrollListView.this.getMarkupId()+"')" + ".scrollUpTo('"+getItemMarkupId(InfiniteScrollListView.this.getModel().getObject().get( Math.min(InfiniteScrollListView.this.getModel().getViewSize()-1, InfiniteScrollListView.this.getModel().getObject().size()-1) ))+"')"); setStartIndex(getModel().getStartIndex()+SCROLL_SIZE); refreshLoadings(); target.add(listContainer); } }); add(getScrollBehaviour()); } protected abstract void populateItem(final ListItem<T> item); protected abstract String getItemMarkupId(final T item); private Behavior getScrollBehaviour(){ return new AttributeModifier("scroll", Model.of(this.getMarkupId())){ private static final long serialVersionUID = 3523727356782417598L; @Override public void renderHead(Component component, IHeaderResponse response) { super.renderHead(component, response); response.render(OnDomReadyHeaderItem.forScript("InfiniteScroll.getFromContainer('"+getMarkupId()+"').setUrls('"+upBehavior.getCallbackUrl()+"', '"+downBehavior.getCallbackUrl()+"')")); } @Override protected String newValue(String currentValue, String replacementValue) { return "InfiniteScroll.handleScroll('"+InfiniteScrollListView.this.getMarkupId()+"')"; } }; } @Override protected void onBeforeRender() { setStartIndex(0); refreshLoadings(); super.onBeforeRender(); } private boolean isShowUpLoading(){ return getModel().getStartIndex()>0; } private boolean isShowDownLoading(){ return getModel().getSize()-getModel().getStartIndex()>MAX_ELEMENTS; } private void setStartIndex(int index){ getModel().setStartIndex(index, MAX_ELEMENTS); } @SuppressWarnings("unchecked") public PageableListModel<List<T>> getModel(){ return (PageableListModel<List<T>>) getDefaultModel(); } public void setModel(PageableListModel<? extends List<? extends T>> model){ setDefaultModel(model); } private void refreshLoadings(){ upLoading.setVisible(isShowUpLoading()); downLoading.setVisible(isShowDownLoading()); } }