package fr.openwide.core.wicket.more.markup.html.sort;
import java.util.Map;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.navigation.paging.IPageable;
import org.apache.wicket.markup.html.panel.IMarkupSourcingStrategy;
import org.apache.wicket.markup.html.panel.PanelMarkupSourcingStrategy;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Maps;
import fr.openwide.core.jpa.more.business.sort.ISort;
import fr.openwide.core.jpa.more.business.sort.ISort.SortOrder;
import fr.openwide.core.wicket.behavior.ClassAttributeAppender;
import fr.openwide.core.wicket.more.markup.html.sort.model.CompositeSortModel;
/**
* CAUTION when extending : this "link" uses a PANEL markup sourcing strategy, for implementation purposes.
*/
public class TableSortLink<T extends ISort<?>> extends AjaxLink<Void> {
private static final long serialVersionUID = 9088886381233297454L;
private static final Logger LOGGER = LoggerFactory.getLogger(TableSortLink.class);
private static final String SORT_BUTTON_CSS_CLASS = "btn btn-sort";
private final IPageable pageable;
private final CompositeSortModel<T> compositeSortModel;
private final T sort;
private CycleMode cycleMode = CycleMode.NONE_DEFAULT;
private ISortIconStyle sortIconCssStyle = SortIconStyle.DEFAULT;
public static enum CycleMode {
NONE_DEFAULT {
@Override
protected SortOrder getNext(ISort<?> sort, SortOrder currentOrder) {
if (currentOrder == null) {
return sort.getDefaultOrder();
} else {
return null;
}
}
},
NONE_DEFAULT_REVERSE {
@Override
protected SortOrder getNext(ISort<?> sort, SortOrder currentOrder) {
if (currentOrder == null) {
return sort.getDefaultOrder();
} else if (currentOrder.equals(sort.getDefaultOrder())){
return sort.getDefaultOrder().reverse();
} else {
return null;
}
}
},
DEFAULT_REVERSE {
@Override
protected SortOrder getNext(ISort<?> sort, SortOrder currentOrder) {
SortOrder defaultWay = sort.getDefaultOrder();
SortOrder defaultReverseWay = defaultWay.reverse();
if (currentOrder == null || currentOrder.equals(defaultReverseWay)) {
return defaultWay;
} else {
return defaultReverseWay;
}
}
};
protected abstract SortOrder getNext(ISort<?> sort, SortOrder currentOrder);
}
public TableSortLink(String id, CompositeSortModel<T> compositeSortModel, T sort) {
this(id, compositeSortModel, sort, null, null);
}
public TableSortLink(String id, CompositeSortModel<T> compositeSortModel, T sort, IModel<String> tooltipTextModel) {
this(id, compositeSortModel, sort, null, tooltipTextModel);
}
public TableSortLink(String id, CompositeSortModel<T> compositeSortModel, T sort, IPageable pageable) {
this(id, compositeSortModel, sort, pageable, null);
}
public TableSortLink(String id, CompositeSortModel<T> compositeSortModel, T sort, IPageable pageable, IModel<String> tooltipTextModel) {
super(id);
this.compositeSortModel = compositeSortModel;
this.pageable = pageable;
this.sort = sort;
add(new AttributeAppender("title", tooltipTextModel));
add(new ClassAttributeAppender(SORT_BUTTON_CSS_CLASS));
add(new ClassAttributeAppender(new LoadableDetachableModel<String>() {
private static final long serialVersionUID = 1L;
@Override
protected String load() {
if (TableSortLink.this.isSortActive()) {
return "active";
}
return null;
}
}));
WebMarkupContainer iconAscContainer = new WebMarkupContainer("iconAsc") {
private static final long serialVersionUID = 1L;
@Override
protected void onConfigure() {
super.onConfigure();
SortOrder displayedSort = TableSortLink.this.getDisplayedSort();
setVisible(SortOrder.ASC.equals(displayedSort));
}
};
iconAscContainer.add(new ClassAttributeAppender(new AbstractReadOnlyModel<String>() {
private static final long serialVersionUID = 1L;
@Override
public String getObject() {
return sortIconCssStyle.getAscIconCssClasses();
}
}));
add(iconAscContainer);
WebMarkupContainer iconDescContainer = new WebMarkupContainer("iconDesc") {
private static final long serialVersionUID = 1L;
@Override
protected void onConfigure() {
super.onConfigure();
SortOrder displayedSort = TableSortLink.this.getDisplayedSort();
setVisible(SortOrder.DESC.equals(displayedSort));
}
};
iconDescContainer.add(new ClassAttributeAppender(new AbstractReadOnlyModel<String>() {
private static final long serialVersionUID = 1L;
@Override
public String getObject() {
return sortIconCssStyle.getDescIconCssClasses();
}
}));
add(iconDescContainer);
}
@Override
protected void onDetach() {
super.onDetach();
compositeSortModel.detach();
}
@Override
protected IMarkupSourcingStrategy newMarkupSourcingStrategy() {
return new PanelMarkupSourcingStrategy(false);
}
@Override
public void onClick(AjaxRequestTarget target) {
switchSort();
if (pageable != null) {
pageable.setCurrentPage(0);
}
refreshOnSort(target);
}
protected void switchSort() {
Map<?, ?> activeSortBeforeSwitch = Maps.newLinkedHashMap(compositeSortModel.getActiveSort());
SortOrder currentOrder = compositeSortModel.getSelectedOrder(sort);
SortOrder nextOrder = cycleMode.getNext(sort, currentOrder);
compositeSortModel.setOrder(sort, nextOrder);
Map<?, ?> activeSortAfterSwitch = Maps.newLinkedHashMap(compositeSortModel.getActiveSort());
if (activeSortBeforeSwitch.equals(activeSortAfterSwitch)) {
/* In some cases, switching from "no order" to the default order will result in no change at all
* (for instance if the composite sort model uses this sort as the single default sort)
* In those cases, we try to trigger another switch so that the composite sort will change as expected
*/
if (LOGGER.isWarnEnabled()) {
activeSortBeforeSwitch = Maps.newLinkedHashMap(compositeSortModel.getActiveSort());
}
nextOrder = cycleMode.getNext(sort, nextOrder);
compositeSortModel.setOrder(sort, nextOrder);
if (LOGGER.isWarnEnabled()) {
activeSortAfterSwitch = Maps.newLinkedHashMap(compositeSortModel.getActiveSort());
if (activeSortBeforeSwitch.equals(activeSortAfterSwitch)) {
LOGGER.warn("A switch in a TableSortLink failed to trigger any change in the CompositeSortModel even after a second try.");
}
}
}
}
public TableSortLink<T> cycleMode(CycleMode cycleMode) {
this.cycleMode = cycleMode;
return this;
}
public TableSortLink<T> iconStyle(ISortIconStyle iconStyle) {
this.sortIconCssStyle = iconStyle;
return this;
}
protected boolean isSortActive() {
return compositeSortModel.getActiveOrder(sort) != null;
}
protected SortOrder getDisplayedSort() {
final SortOrder currentOrder = compositeSortModel.getActiveOrder(sort);
return sort.getDefaultOrder().asDefaultFor(currentOrder);
}
protected void refreshOnSort(AjaxRequestTarget target) {
setResponsePage(getPage());
}
}