com.premiumminds.webapp.wicket.repeaters.AjaxListSetView Maven / Gradle / Ivy
/**
* 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 .
*/
package com.premiumminds.webapp.wicket.repeaters;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections4.IteratorUtils;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.set.ListOrderedSet;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.AjaxRequestTarget.IJavaScriptResponse;
import org.apache.wicket.ajax.AjaxRequestTarget.IListener;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
/**
*
* An AjaxListSetView is a repeater that makes it easy to display/work with ListOrderedSets (collection with unique values, but with List order).
* This repeater is great when you have FormComponents for each row and want to change the list through AJAX, because only new elements of the list are
* created in the DOM, the rest is just reordered.
*
* Example:
*
*
* <table>
* <tr wicket:id="rows" class="even">
* <td><span wicket:id="id">Test ID</span></td>
* ...
*
*
*
* The related Java code:
*
*
* add(new AjaxListSetView<UserDetails>("rows", listModel, "tbody")
* {
* public void populateItem(final ListSetItem<UserDetails> item)
* {
* item.add(new Label("id", new PropertyModel(item.getModel, "id")));
* }
* });
*
*
*
* NOTE: AjaxListSetView creates a container between his items and the markups parent. In the example above, AjaxListSetView will
* insert the element tbody between table and tr.
*
*
*
* @author acamilo
*
* @param type of elements contained in the model's list
*/
public abstract class AjaxListSetView extends AbstractRepeater2 {
private static final long serialVersionUID = -2741997575388196264L;
/** counter to generate rows index */
private int counter = 1;
/** cache for the items already created */
private Map> elementToComponent = new HashMap>();
/** markup tag used to create new elements through ajax */
private String markupTag = null;
/**
* @param id component id
* @param model model containing the listOrderedSet of
* @param bodyTag tag name for the container that will be created
* @see org.apache.wicket.Component#Component(String, IModel)
*/
public AjaxListSetView(String id, IModel extends ListOrderedSet extends T>> model, final String bodyTag) {
super(id, model);
if (model == null){
throw new IllegalArgumentException("Null models are not allowed");
}
add(new Behavior() {
private static final long serialVersionUID = -2448715969183335401L;
@Override
public void beforeRender(Component component) {
getResponse().write("<"+bodyTag+" id='"+component.getMarkupId(true)+"'>");
}
@Override
public void afterRender(Component component) {
getResponse().write(""+bodyTag+">");
}
});
}
@Override
protected void onInitialize() {
super.onInitialize();
markupTag = ((ComponentTag) getMarkup().get(0)).getName();
}
@Override
protected Iterator extends Component> renderIterator() {
return IteratorUtils.transformedIterator(getList().iterator(), new Transformer>() {
@Override
public ListSetItem> transform(T el) {
ListSetItem> component = elementToComponent.get(el);
if(component==null) throw new IllegalStateException("could not find element '"+el+"' on computed map");
return component;
}
});
}
private void updateItems(Set> newItems, Set> removedItems){
Set componentsNotHandled = new HashSet(elementToComponent.keySet());
for(T el : getList()){
if(componentsNotHandled.contains(el)){
componentsNotHandled.remove(el);
} else {
ListSetItem item = newItem(Model.of(el));
elementToComponent.put(el, item);
newItems.add(item);
add(item);
}
}
for(T el : componentsNotHandled){
removedItems.add(elementToComponent.get(el));
remove(elementToComponent.get(el));
elementToComponent.remove(el);
}
}
@Override
protected void onPopulate() {
Set> newItems = new HashSet>();
Set> removedItems = new HashSet>();
updateItems(newItems, removedItems);
}
/**
* Create a new ListSetItem for list item.
*
* @param itemModel object in the list that the item represents
* @return ListSetItem
*/
protected ListSetItem newItem(IModel itemModel)
{
ListSetItem item = new ListSetItem(Integer.toString(counter++), itemModel);
populateItem(item);
return item;
}
/**
* Populate a given item.
*
* be careful to add any components to the list item. So, don't do:
*
*
* add(new Label("foo", "bar"));
*
*
* but:
*
*
* item.add(new Label("foo", "bar"));
*
*
*
*
* @param item
* The item to populate
*/
protected abstract void populateItem(ListSetItem item);
@SuppressWarnings("unchecked")
private ListOrderedSet getList(){
return (ListOrderedSet) getDefaultModelObject();
}
private void updateAjax(AjaxRequestTarget target){
Set> newItems = new HashSet>();
Set> removedItems = new HashSet>();
updateItems(newItems, removedItems);
for(ListSetItem item : removedItems){
target.prependJavaScript("$('#"+item.getMarkupId()+"').remove();");
}
for(ListSetItem item : newItems){
String script = String.format(
"var item = document.createElement('%s'); " +
"item.id = '%s'; "+
"var container = Wicket.$('%s'); " +
"$(container).append(item); "
, markupTag, item.getMarkupId(), getMarkupId());
target.prependJavaScript(script);
target.add(item);
}
for(T el : getList()){
ListSetItem item = elementToComponent.get(el);
target.appendJavaScript("$('#"+item.getMarkupId()+"').appendTo('#"+getMarkupId()+"');");
}
}
/**
* FOR {@link AjaxListSetView} INTERNAL USE
*
* @author acamilo
*/
public static class AjaxListener implements IListener {
@Override
public void onBeforeRespond(Map map, AjaxRequestTarget target) {
Set toRemove = new HashSet();
for(Map.Entry entry : new HashMap(map).entrySet()){
if(entry.getValue() instanceof AjaxListSetView){
AjaxListSetView> listView = (AjaxListSetView>) entry.getValue();
listView.updateAjax(target);
toRemove.add(entry.getKey());
}
}
for(String key : toRemove){
map.remove(key);
}
}
@Override
public void onAfterRespond(Map map, IJavaScriptResponse response) {
}
@Override
public void updateAjaxAttributes(AbstractDefaultAjaxBehavior behavior, AjaxRequestAttributes attributes) {
}
}
}