/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
// www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation; version 3 of the License.
//
// This community edition 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 General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////
package org.projectforge.web.tree;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.image.ContextImage;
import org.apache.wicket.markup.html.link.AbstractLink;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.Model;
import org.projectforge.web.fibu.ISelectCallerPage;
import org.projectforge.web.wicket.WicketAjaxUtils;
import org.projectforge.web.wicket.WicketUtils;
/**
* @author Kai Reinhard (k.reinhard@micromata.de)
*
*/
public abstract class DefaultTreeTablePanel<T extends TreeTableNode> extends Panel implements TreeTablePanel
{
private static final long serialVersionUID = 380527812338260483L;
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(DefaultTreeTablePanel.class);
protected ISelectCallerPage caller;
protected String selectProperty;
protected WebMarkupContainer treeTableHead;
protected WebMarkupContainer treeTableBody;
protected RepeatingView rowRepeater;
protected RepeatingView colHeadRepeater;
private List<T> treeList;
private TreeTable<T> treeTable;
protected Integer highlightedRowId;
protected boolean clickRows = true;
public DefaultTreeTablePanel(final String id)
{
super(id);
}
public DefaultTreeTablePanel(final String id, final ISelectCallerPage caller, final String selectProperty)
{
super(id);
this.caller = caller;
this.selectProperty = selectProperty;
}
public void init()
{
treeTableHead = new WebMarkupContainer("thead");
add(treeTableHead);
initializeColumnHeads();
treeTableBody = new WebMarkupContainer("tbody");
treeTableBody.setOutputMarkupId(true);
add(treeTableBody);
}
public void refresh()
{
treeList = null;
}
public void refreshTreeTable()
{
treeTable = null;
}
@Override
protected void onBeforeRender()
{
if (treeList == null) {
buildTree();
}
super.onBeforeRender();
}
private void buildTree()
{
createTreeRows();
}
protected abstract void initializeColumnHeads();
protected void createTreeRows()
{
if (rowRepeater != null) {
treeTableBody.remove(rowRepeater);
}
rowRepeater = new RepeatingView("rows");
treeTableBody.add(rowRepeater);
refresh(); // Force rebuilding.
if (getTreeList() == null) {
return;
}
int counter = 0;
for (final T node : treeList) {
final WebMarkupContainer row = createTreeRow(node);
if (counter++ % 2 == 0) {
row.add(AttributeModifier.replace("class", "even"));
} else {
row.add(AttributeModifier.replace("class", "odd"));
}
}
}
protected void addColumns(final RepeatingView colBodyRepeater, final String cssStyle, final T node)
{
}
protected abstract TreeIconsActionPanel< ? extends TreeTableNode> createTreeIconsActionPanel(final T node);
protected Label createColHead(final String i18nKey)
{
final Label colHead = new Label(colHeadRepeater.newChildId(), getString(i18nKey));
return colHead;
}
private WebMarkupContainer createTreeRow(final T node)
{
final WebMarkupContainer row = new WebMarkupContainer(rowRepeater.newChildId(), new Model<TreeTableNode>(node));
row.setOutputMarkupId(true);
row.add(AttributeModifier.replace("class", "even"));
if (clickRows == true) {
WicketUtils.addRowClick(row);
}
rowRepeater.add(row);
final RepeatingView colBodyRepeater = new RepeatingView("cols");
row.add(colBodyRepeater);
final String cssStyle = getCssStyle(node);
// Column: browse icons
final TreeIconsActionPanel< ? extends TreeTableNode> treeIconsActionPanel = createTreeIconsActionPanel(node);
addColumn(row, treeIconsActionPanel, cssStyle);
treeIconsActionPanel.init(this, node);
treeIconsActionPanel.add(AttributeModifier.append("style", new Model<String>("white-space: nowrap;")));
addColumns(colBodyRepeater, cssStyle, node);
return row;
}
protected String getCssStyle(final T node)
{
return null;
}
protected void addColumn(final WebMarkupContainer parent, final Component component, final String cssStyle)
{
if (cssStyle != null) {
component.add(AttributeModifier.append("style", new Model<String>(cssStyle)));
}
parent.add(component);
}
protected abstract List<T> buildTreeList();
/**
* Should be used in tree table view. The current tree will be returned for tree navigation.
* @return
*/
protected List<T> getTreeList()
{
if (treeList == null) {
treeList = buildTreeList();
}
return treeList;
}
protected abstract TreeTable<T> buildTreeTable();
public TreeTable<T> getTreeTable()
{
if (treeTable == null) {
treeTable = buildTreeTable();
}
return treeTable;
}
public void setOpenNodes(final Set<Serializable> openNodes)
{
if (getTreeTable() != null && openNodes != null) {
getTreeTable().setOpenNodes(openNodes);
}
}
public Set<Serializable> getOpenNodes()
{
return getTreeTable().getOpenNodes();
}
protected Component getTreeRow(final Serializable hashId)
{
@SuppressWarnings("unchecked")
final Iterator<Component> it = rowRepeater.iterator();
while (it.hasNext() == true) {
final Component child = it.next();
final TreeTableNode node = (TreeTableNode) child.getDefaultModelObject();
if (node.getHashId().equals(hashId) == true) {
return child;
}
}
return null;
}
/**
* Return the row after the row with the given id. If the row is the last row then null is returned.
* @param hashId
* @return
*/
protected Component getTreeRowAfter(final Serializable hashId)
{
final TreeTableNode node = getTreeTable().getElementAfter(getTreeList(), hashId);
if (node == null) {
return null;
} else {
return getTreeRow(node.getHashId());
}
}
protected Component removeTreeRow(final Serializable hashId)
{
final Component row = getTreeRow(hashId);
if (row != null) {
treeTableBody.remove(row);
}
return row;
}
/**
* Overload method should be return false if the call setEvent(AjaxRequestTarget, TreeTableEvent, TreeTableNode) should stop further
* processing.
* @param target
* @param event
* @param node
* @return Always true.
*/
protected boolean onSetEvent(final AjaxRequestTarget target, final TreeTableEvent event, final TreeTableNode node)
{
return true;
}
@SuppressWarnings("unchecked")
public void setEvent(final AjaxRequestTarget target, final TreeTableEvent event, final TreeTableNode node)
{
if (onSetEvent(target, event, node) == false) {
return;
}
if (treeTableBody == null) {
log.error("Oups, treeTableContainer is null. Ignoring Ajax event.");
return;
}
if (log.isDebugEnabled() == true) {
log.debug("setEvent: node=" + node.getHashId() + ", event=" + event + ", nodeStatus=" + node.getNodeStatus());
}
final Component currentRow = getTreeRow(node.getHashId());
final AbstractLink link = (AbstractLink) currentRow.get("c1:icons:folder");
if (event == TreeTableEvent.OPEN || event == TreeTableEvent.EXPLORE) {
final StringBuffer prependJavascriptBuf = new StringBuffer();
{
// Add all childs
final Component row = getTreeRowAfter(node.getHashId());
refresh(); // Force to rebuild tree list.
for (final T child : getTreeTable().getDescendants(getTreeList(), (T) node)) {
final WebMarkupContainer newRow = createTreeRow(child);
if (row != null) {
prependJavascriptBuf.append(WicketAjaxUtils.insertBefore(treeTableBody.getMarkupId(), row.getMarkupId(), "tr", newRow
.getMarkupId()));
} else {
prependJavascriptBuf.append(WicketAjaxUtils.appendChild(treeTableBody.getMarkupId(), "tr", newRow.getMarkupId()));
}
target.add(newRow);
}
}
{
// Replace opened-folder-icon by closed-folder-icon.
replaceFolderImage(target, link, node, prependJavascriptBuf);
}
final String javaScript = prependJavascriptBuf.toString();
if (javaScript.length() > 0) {
target.prependJavaScript(javaScript);
}
target.appendJavaScript("updateEvenOdd();");
} else {
// Remove all childs
final StringBuffer prependJavascriptBuf = new StringBuffer();
final Iterator<Component> it = rowRepeater.iterator();
final List<Component> toRemove = new ArrayList<Component>();
while (it.hasNext() == true) {
final Component row = it.next();
final TreeTableNode model = (TreeTableNode) row.getDefaultModelObject();
if (node.isParentOf(model) == true) {
prependJavascriptBuf.append(WicketAjaxUtils.removeChild(treeTableBody.getMarkupId(), row.getMarkupId()));
toRemove.add(row);
}
}
for (final Component row : toRemove) {
rowRepeater.remove(row);
}
{
// Replace closed-folder-icon by opened-folder-icon.
replaceFolderImage(target, link, node, prependJavascriptBuf);
}
final String javaScript = prependJavascriptBuf.toString();
if (javaScript.length() > 0) {
target.prependJavaScript(javaScript);
}
target.appendJavaScript("updateEvenOdd();");
}
}
private void replaceFolderImage(final AjaxRequestTarget target, final AbstractLink link, final TreeTableNode node,
final StringBuffer prependJavascriptBuf)
{
ContextImage oldImage = (ContextImage) link.get("folderImage");
if (oldImage == null || oldImage.isVisible() == false) {
oldImage = (ContextImage) link.get("folderOpenImage");
}
final ContextImage currentImage = TreeIconsActionPanel.getCurrentFolderImage(getResponse(), link, node);
if (oldImage != currentImage) {
prependJavascriptBuf.append(WicketAjaxUtils.replaceChild(link.getMarkupId(), oldImage.getMarkupId(), "img", currentImage
.getMarkupId()));
target.add(currentImage);
}
}
protected void onSetEventNode(final Serializable hashId)
{
}
public void setEventNode(final Serializable hashId)
{
onSetEventNode(hashId);
setHighlightedRowId((Integer) hashId);
}
public Serializable getEventNode()
{
return this.highlightedRowId;
}
public void setHighlightedRowId(final Integer highlightedRowId)
{
this.highlightedRowId = highlightedRowId;
getTreeTable().openNode(highlightedRowId); // Open path to highlighted (preselected) node.
treeList = null; // Force rebuilding of the tree.
}
/**
* @return true, if this page is called for selection by a caller otherwise false.
*/
public boolean isSelectMode()
{
return this.caller != null;
}
}