/*==========================================================================*\
| $Id: WCTree.java,v 1.3 2011/11/08 14:05:23 aallowat Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2009 Virginia Tech
|
| This file is part of Web-CAT.
|
| Web-CAT is free software; you can redistribute it and/or modify
| it under the terms of the GNU Affero General Public License as published
| by the Free Software Foundation; either version 3 of the License, or
| (at your option) any later version.
|
| Web-CAT 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 Affero General Public License
| along with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package org.webcat.ui;
import java.util.HashMap;
import java.util.Map;
import org.webcat.core.WCComponent;
import org.webcat.ui.generators.JavascriptFunction;
import org.webcat.ui.generators.JavascriptGenerator;
import org.webcat.ui.util.ComponentIDGenerator;
import com.webobjects.appserver.WOActionResults;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WODisplayGroup;
import com.webobjects.appserver.WOMessage;
import com.webobjects.appserver.WORequest;
import com.webobjects.appserver.WOResponse;
import com.webobjects.eocontrol.EOOrQualifier;
import com.webobjects.eocontrol.EOQualifier;
import com.webobjects.eocontrol.EOSortOrdering;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSMutableSet;
import com.webobjects.foundation.NSSelector;
import com.webobjects.foundation.NSSet;
import er.extensions.appserver.ERXDisplayGroup;
import er.extensions.appserver.ERXWOContext;
import er.extensions.eof.ERXQ;
import er.extensions.eof.ERXS;
import er.extensions.eof.ERXSortOrdering.ERXSortOrderings;
//-------------------------------------------------------------------------
/**
* <p>
* A reusable table component that is bound to a display group, and provides
* options to select rows, sort on table headings, and paging.
* </p>
* <dl>
* <dt>id (<code>String</code>)</dt>
* <dd>The widget id for the content pane that will surround the table. This
* can be used to refresh the table contents in response to events that occur
* outside the table.</dd>
* <dt>treeModel (<code>WCTreeModel<?></code>)</dt>
* <dd>The tree model that contains the objects that this tree will display.
* </dd>
* <dt>settingsKey (<code>String</code>)</dt>
* <dd>A key prefix that will be used to persist the table's settings in the
* current user's preferences.</dd>
* <dt>canSelectItems (<code>boolean</code>)</dt>
* <dd>Indicates whether checkbox or radio button controls will be inserted in
* front of each tree item's expansion controls to allow the user to select
* the item.</dd>
* <dt>multipleSelection (<code>boolean</code>)</dt>
* <dd>Indicates whether multiple items can be selected at once (using
* checkboxes) or if only one item can be selected at a time (using radio
* buttons).</dd>
* <dt>initialExpandDepth (<code>int</code>)</dt>
* <dd>Specifies the number of levels that should be initially expanded in the
* tree. 0 means expand nothing (just show the root nodes collapsed), 1 means
* show the root nodes expanded, and so forth. -1 means expand everything.
* </dd>
* <dt>onSelectionChanged (<code>String</code>)</dt>
* <dd>Javascript code that will be executed when the selection state in the
* tree changes.</dd>
* <dt>fixedSize ({@code String})</dt>
* <dd>If specified, the tree will be displayed with the specified size, and
* scrollbars will be added if the items require more space. This string should
* be in the format {@code "width,height"}, where the width and height are
* specified using CSS units. If this binding is omitted (or null), the tree
* will auto-size to fit its contents.</dd>
* </dl>
*
* @author Tony Allevato
* @author Last changed by $Author: aallowat $
* @version $Revision: 1.3 $, $Date: 2011/11/08 14:05:23 $
*/
public class WCTree extends WCComponent
{
//~ Constructors ..........................................................
// ----------------------------------------------------------
public WCTree(WOContext context)
{
super(context);
idFor = new ComponentIDGenerator(this);
}
//~ KVC attributes (must be public) .......................................
public String id;
public WCTreeModel treeModel;
public String settingsKey;
public boolean canSelectItems = false;
public boolean multipleSelection = false;
public int initialExpandDepth = 0;
public String onSelectionChanged;
public String selectionAction;
public String itemDoubleClickedAction;
public String fixedSize;
public String shadowSelection;
public ComponentIDGenerator idFor;
//~ Methods ...............................................................
// ----------------------------------------------------------
@Override
public void appendToResponse(WOResponse response, WOContext context)
{
WCTree oldTree = setCurrentTree(this);
super.appendToResponse(response, context);
setCurrentTree(oldTree);
}
// ----------------------------------------------------------
@Override
public void takeValuesFromRequest(WORequest request, WOContext context)
{
WCTree oldTree = setCurrentTree(this);
super.takeValuesFromRequest(request, context);
setCurrentTree(oldTree);
}
// ----------------------------------------------------------
@Override
public WOActionResults invokeAction(WORequest request, WOContext context)
{
WCTree oldTree = setCurrentTree(this);
WOActionResults result = super.invokeAction(request, context);
setCurrentTree(oldTree);
return result;
}
// ----------------------------------------------------------
public String id()
{
if (id == null)
{
id = idFor.get();
}
return id;
}
// ----------------------------------------------------------
public String cssStyleForContentPane()
{
StringBuffer buffer = new StringBuffer();
if (fixedSize != null)
{
String[] parts = fixedSize.split(",");
buffer.append("overflow: auto; ");
buffer.append("width: ");
buffer.append(parts[0]);
buffer.append("; height: ");
buffer.append(parts[1]);
buffer.append("; white-space: nowrap;");
}
if (buffer.length() > 0)
{
return buffer.toString();
}
else
{
return null;
}
}
// ----------------------------------------------------------
public String cssClassForContentPane()
{
if (fixedSize != null)
{
return "tableborder";
}
else
{
return null;
}
}
// ----------------------------------------------------------
public String cssStyleForTable()
{
StringBuffer buffer = new StringBuffer();
if (fixedSize != null)
{
buffer.append("width: 100%;");
}
if (buffer.length() > 0)
{
return buffer.toString();
}
else
{
return null;
}
}
// ----------------------------------------------------------
public String cssClassForTable()
{
if (fixedSize != null)
{
return "layout nomargin";
}
else
{
return null;
}
}
// ----------------------------------------------------------
public Map<Object, Boolean> expandedItems()
{
if (expandedItems == null)
{
retrieveExpansionState();
}
// If there's a selection, expand to make that visible too.
for (Object object : treeModel.selectedObjects())
{
String pathToSelectedObject = treeModel.pathForObject(object);
if (pathToSelectedObject != null)
{
String[] components = pathToSelectedObject.split("/");
Object current = null;
for (String component : components)
{
current = treeModel.childWithPathComponent(current, component);
if (current == null)
{
break;
}
if (!expandedItems.containsKey(current))
{
expandedItems.put(current, true);
}
}
}
}
return expandedItems;
}
// ----------------------------------------------------------
public boolean isItemExpanded(Object item)
{
if (!expandedItems().containsKey(item))
{
return false;
//expandedItems().put(item, false);
}
return expandedItems().get(item);
}
// ----------------------------------------------------------
public void toggleItemExpanded(Object item)
{
if (expandedItems().containsKey(item))
{
expandedItems().put(item, !expandedItems().get(item));
}
else
{
expandedItems().put(item, true);
}
storeExpansionState();
}
// ----------------------------------------------------------
private void retrieveExpansionState()
{
expandedItems = new HashMap<Object, Boolean>();
if (settingsKey != null)
{
String ids = (String) user().preferences().valueForKey(
settingsKey + "_expandedItemIds");
if (ids != null)
{
String[] splitIds = ids.split("\0");
Map<String, Boolean> expandedItemIds =
new HashMap<String, Boolean>();
for (int i = 0; i < splitIds.length; i += 2)
{
String id = splitIds[i];
boolean expanded = Boolean.parseBoolean(splitIds[i + 1]);
if (id.length() > 0)
{
expandedItemIds.put(id, expanded);
}
}
//recursivelyExpandChildren(null, 0, expandedItemIds);
}
}
}
// ----------------------------------------------------------
private void recursivelyExpandChildren(Object item, int depth,
Map<String, Boolean> expandedItemIds)
{
if (treeModel.objectHasArrangedChildren(item))
{
NSArray<?> children = treeModel.arrangedChildrenOfObject(item);
if (children != null)
{
for (Object child : children)
{
String childId = treeModel.persistentIdOfObject(child);
if ((childId != null && expandedItemIds.containsKey(childId))
|| (!expandedItemIds.containsKey(childId)
&& (initialExpandDepth == -1
|| depth < initialExpandDepth)))
{
expandedItems.put(child,
expandedItemIds.get(childId));
recursivelyExpandChildren(child, depth + 1, expandedItemIds);
}
}
}
}
}
// ----------------------------------------------------------
private void storeExpansionState()
{
if (settingsKey != null)
{
StringBuffer buffer = new StringBuffer();
for (Object item : expandedItems.keySet())
{
boolean expanded = expandedItems.get(item);
String id = treeModel.persistentIdOfObject(item);
if (id != null)
{
buffer.append(id);
buffer.append('\0');
buffer.append(Boolean.toString(expanded));
buffer.append('\0');
}
}
String expandedItemIdsString = buffer.toString();
user().preferences().takeValueForKey(
expandedItemIdsString, settingsKey + "_expandedItemIds");
user().savePreferences();
}
}
// ----------------------------------------------------------
public void updateSelectionFromShadowField()
{
String[] parts = shadowSelection.split(",");
treeModel.clearSelection();
for (String item : parts)
{
item = item.substring(
idFor.get("path_").length()).replace('_', '.');
WCIndexPath path = new WCIndexPath(item);
treeModel.selectObject(treeModel.objectAtIndexPath(path));
}
}
// ----------------------------------------------------------
public WOActionResults singleSelectionChanged()
{
updateSelectionFromShadowField();
if (selectionAction != null)
{
return performParentAction(selectionAction);
}
else
{
return null;
}
}
// ----------------------------------------------------------
public WOActionResults itemDoubleClicked()
{
updateSelectionFromShadowField();
if (itemDoubleClickedAction != null)
{
return performParentAction(itemDoubleClickedAction);
}
else
{
return null;
}
}
// ----------------------------------------------------------
public static void refresh(JavascriptGenerator js, String id)
{
refresh(js, id, null);
}
// ----------------------------------------------------------
public static void refresh(JavascriptGenerator js, String id,
JavascriptFunction onAfterRefresh)
{
js.append(renderTableBusyScript(id));
js.refresh(onAfterRefresh, id);
}
// ----------------------------------------------------------
public JavascriptGenerator refreshTable()
{
return refreshTable(null);
}
// ----------------------------------------------------------
public JavascriptGenerator refreshTable(JavascriptFunction onAfterRefresh)
{
if (onAfterRefresh == null)
{
return new JavascriptGenerator().refresh(id());
}
else
{
return new JavascriptGenerator().refresh(onAfterRefresh, id());
}
}
// ----------------------------------------------------------
public String tableBodyId()
{
return id() + "__tbody";
}
// ----------------------------------------------------------
public static String renderTableBusyScript(String gridId)
{
NSMutableDictionary<String, Object> props =
new NSMutableDictionary<String, Object>();
props.setObjectForKey(0.25, "opacity");
JavascriptGenerator js = new JavascriptGenerator();
// TODO change this id to be more general
js.animateProperty(gridId + "__tbody", props, 250, null).play();
return js.toString(true);
}
// ----------------------------------------------------------
// TODO rewrite this function to use the one above
public String renderTableBusyScript()
{
NSMutableDictionary<String, Object> props =
new NSMutableDictionary<String, Object>();
props.setObjectForKey(0.25, "opacity");
JavascriptGenerator js = new JavascriptGenerator();
js.animateProperty(tableBodyId(), props, 250, null).play();
return js.toString(true);
}
// ----------------------------------------------------------
public static WCTree currentTree()
{
return (WCTree) ERXWOContext.contextDictionary().objectForKey(
CURRENT_TREE_KEY);
}
// ----------------------------------------------------------
public static WCTree setCurrentTree(WCTree tree)
{
WCTree oldTree =
(WCTree) ERXWOContext.contextDictionary().objectForKey(
CURRENT_TREE_KEY);
if (tree == null)
{
ERXWOContext.contextDictionary().removeObjectForKey(
CURRENT_TREE_KEY);
}
else
{
ERXWOContext.contextDictionary().setObjectForKey(tree,
CURRENT_TREE_KEY);
}
return oldTree;
}
// ----------------------------------------------------------
public int numberOfColumns()
{
return numberOfColumns;
}
// ----------------------------------------------------------
public String passthroughAttributes()
{
return passthroughAttributes;
}
// ----------------------------------------------------------
public void handleTakeValueForUnboundKey(Object value, String key)
{
if (passthroughAttributes == null)
{
passthroughAttributes = "";
}
if (value != null)
{
passthroughAttributes += " " + key + "=\""
+ WOMessage.stringByEscapingHTMLAttributeValue(
value.toString()) + "\"";
}
}
//~ Static/instance variables .............................................
private static final String CURRENT_TREE_KEY =
"org.webcat.ui.WCTree.currentTree";
private String passthroughAttributes;
private Map<Object, Boolean> expandedItems;
private boolean expandedItemsCalled;
protected int numberOfColumns = 0;
}