001: /**
002: * Copyright 2006 Webmedia Group Ltd.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: **/package org.araneaframework.uilib.tree;
016:
017: import java.io.Serializable;
018: import java.io.Writer;
019: import java.util.ArrayList;
020: import java.util.Collections;
021: import java.util.Iterator;
022: import java.util.LinkedList;
023: import java.util.List;
024: import org.araneaframework.Environment;
025: import org.araneaframework.InputData;
026: import org.araneaframework.OutputData;
027: import org.araneaframework.Widget;
028: import org.araneaframework.core.Assert;
029: import org.araneaframework.core.BaseApplicationWidget;
030: import org.araneaframework.core.StandardActionListener;
031: import org.araneaframework.core.StandardEnvironment;
032: import org.araneaframework.core.StandardEventListener;
033: import org.araneaframework.http.HttpOutputData;
034:
035: /**
036: * @author Alar Kvell (alar@araneaframework.org)
037: * @since 1.0.7
038: */
039: public class TreeNodeWidget extends BaseApplicationWidget implements
040: TreeNodeContext {
041:
042: private static final long serialVersionUID = 1L;
043:
044: /** Display widget id. */
045: public static final String DISPLAY_KEY = "display";
046: /** Toggle event or action id. */
047: public static final String TOGGLE_KEY = "toggle";
048:
049: private boolean collapsed = true;
050: private boolean collapsedDecide = false;
051: private Widget initDisplay;
052: private List initNodes;
053: private TreeNodeWidget parentNode;
054: private int index = -1;
055: private int nextChildIndex = 0;
056: private List childNodeWrappers;
057:
058: private static class ChildNodeWrapper implements Serializable {
059:
060: private TreeNodeWidget node;
061: private String widgetId;
062:
063: public ChildNodeWrapper(TreeNodeWidget node, String widgetId) {
064: this .node = node;
065: this .widgetId = widgetId;
066: }
067:
068: public TreeNodeWidget getNode() {
069: return node;
070: }
071:
072: public String getWidgetId() {
073: return widgetId;
074: }
075:
076: }
077:
078: /* Used by TreeWidget */
079: TreeNodeWidget() {
080: super ();
081: collapsed = false;
082: }
083:
084: /**
085: * Creates a new {@link TreeNodeWidget} instance. This node has no child nodes
086: * and will be collapsed (children hidden) by default.
087: *
088: * @param display
089: * widget that will be used to display this node.
090: */
091: public TreeNodeWidget(Widget display) {
092: super ();
093: Assert.notNullParam(display, "display");
094: initDisplay = display;
095: }
096:
097: /**
098: * Creates a new {@link TreeNodeWidget} instance and adds the given list of
099: * nodes as its children. Node will be expanded (children shown) by default.
100: *
101: * @param display
102: * widget that will be used to display this node.
103: * @param nodes
104: * list of {@link TreeNodeWidget}s added as children.
105: */
106: public TreeNodeWidget(Widget display, List nodes) {
107: this (display, nodes, true);
108: collapsedDecide = true;
109: }
110:
111: /**
112: * Creates a new {@link TreeNodeWidget} instance and adds the given list of
113: * nodes as its children.
114: *
115: * @param display
116: * widget that will be used to display this node.
117: * @param nodes
118: * list of {@link TreeNodeWidget}s added as children.
119: * @param collapsed
120: * if tree node will be collapsed (children hidden) by default.
121: */
122: public TreeNodeWidget(Widget display, List nodes, boolean collapsed) {
123: this (display);
124: Assert.notNullParam(nodes, "nodes");
125: initNodes = nodes;
126: this .collapsed = collapsed;
127: }
128:
129: protected void init() throws Exception {
130: Assert.notNull(parentNode, "parentNode must be set");
131: Assert.isTrue(index > -1, "index must be set");
132:
133: addWidget(DISPLAY_KEY, initDisplay,
134: getDisplayWidgetEnvironment());
135: initDisplay = null;
136:
137: if (initNodes != null) {
138: addAllNodes(initNodes);
139: initNodes = null;
140: }
141:
142: if (collapsedDecide) {
143: collapsed = getTreeCtx().isRemoveChildrenOnCollapse();
144: }
145:
146: if (getTreeCtx().useActions()) {
147: addActionListener(TOGGLE_KEY, new ToggleActionListener());
148: } else {
149: addEventListener(TOGGLE_KEY, new ToggleEventListener());
150: }
151: }
152:
153: private class ToggleEventListener extends StandardEventListener {
154: private static final long serialVersionUID = 1L;
155:
156: public void processEvent(Object eventId, String eventParam,
157: InputData input) throws Exception {
158: toggleCollapsed();
159: }
160: }
161:
162: private class ToggleActionListener extends StandardActionListener {
163: private static final long serialVersionUID = 1L;
164:
165: public void processAction(Object actionId, String actionParam,
166: InputData input, OutputData output) throws Exception {
167: toggleCollapsed();
168: render(output);
169: }
170: }
171:
172: protected Environment getDisplayWidgetEnvironment()
173: throws Exception {
174: return new StandardEnvironment(getEnvironment(),
175: TreeNodeContext.class, this );
176: }
177:
178: protected TreeContext getTreeCtx() {
179: return (TreeContext) getEnvironment().getEntry(
180: TreeContext.class);
181: }
182:
183: // returns List<TreeNodeWidget>
184: protected List loadChildren() {
185: if (getTreeCtx().getDataProvider() != null) {
186: return getTreeCtx().getDataProvider().getChildren(this );
187: }
188: return null;
189: }
190:
191: protected boolean shouldRenderToggleLink() {
192: if (getTreeCtx().getDataProvider() != null) {
193: if (isCollapsed()) {
194: return getTreeCtx().getDataProvider().hasChildren(this );
195: }
196: return true;
197: }
198: return false;
199: }
200:
201: public boolean isCollapsed() {
202: return collapsed;
203: }
204:
205: public void setCollapsed(boolean collapsed) {
206: this .collapsed = collapsed;
207: if (collapsed) {
208: if (getTreeCtx().isRemoveChildrenOnCollapse()) {
209: removeAllNodes();
210: }
211: } else {
212: if (!hasNodes()) {
213: List children = loadChildren();
214: if (children != null)
215: addAllNodes(children);
216: }
217: }
218: }
219:
220: public void toggleCollapsed() {
221: setCollapsed(!isCollapsed());
222: }
223:
224: public int getNodeCount() {
225: if (childNodeWrappers == null)
226: return 0;
227: return childNodeWrappers.size();
228: }
229:
230: public int addNode(TreeNodeWidget node) {
231: Assert.notNullParam(node, "node");
232: if (childNodeWrappers == null)
233: childNodeWrappers = new ArrayList();
234:
235: int nodeIndex = childNodeWrappers.size();
236: String widgetId = Integer.toString(nextChildIndex++);
237:
238: childNodeWrappers.add(new ChildNodeWrapper(node, widgetId));
239: node.setIndex(nodeIndex);
240: node.setParentNode(this );
241:
242: addWidget(widgetId, node);
243: return nodeIndex;
244: }
245:
246: public void addNode(int nodeIndex, TreeNodeWidget node) {
247: Assert.notNullParam(node, "node");
248: Assert.isTrue(nodeIndex >= 0 && nodeIndex < getNodeCount(),
249: "nodeIndex must be >= 0 and less than nodeCount");
250: if (childNodeWrappers == null)
251: childNodeWrappers = new ArrayList();
252:
253: String widgetId = Integer.toString(nextChildIndex++);
254:
255: childNodeWrappers.add(nodeIndex, new ChildNodeWrapper(node,
256: widgetId));
257: node.setIndex(nodeIndex);
258: node.setParentNode(this );
259:
260: for (int i = nodeIndex + 1; i < childNodeWrappers.size(); i++) {
261: getNodeWrapper(i).getNode().setIndex(i);
262: }
263:
264: addWidget(widgetId, node);
265: }
266:
267: public TreeNodeWidget removeNode(int nodeIndex) {
268: Assert.isTrue(nodeIndex >= 0 && nodeIndex < getNodeCount(),
269: "index must be >= 0 and less than nodeCount");
270:
271: ChildNodeWrapper nodeWrapper = getNodeWrapper(nodeIndex);
272: removeWidget(nodeWrapper.getWidgetId());
273: childNodeWrappers.remove(nodeIndex);
274:
275: for (int i = nodeIndex; i < childNodeWrappers.size(); i++) {
276: getNodeWrapper(i).getNode().setIndex(i);
277: }
278:
279: return nodeWrapper.getNode();
280: }
281:
282: public void addAllNodes(List nodes) {
283: Assert.notNullParam(nodes, "nodes");
284: for (Iterator i = nodes.iterator(); i.hasNext();) {
285: addNode((TreeNodeWidget) i.next());
286: }
287: }
288:
289: public void removeAllNodes() {
290: if (childNodeWrappers == null)
291: return;
292:
293: for (Iterator i = childNodeWrappers.iterator(); i.hasNext();) {
294: ChildNodeWrapper nodeWrapper = (ChildNodeWrapper) i.next();
295: removeWidget(nodeWrapper.getWidgetId());
296: }
297: childNodeWrappers.clear();
298: nextChildIndex = 0;
299: }
300:
301: public Widget getDisplayWidget() {
302: return getWidget(DISPLAY_KEY);
303: }
304:
305: public TreeNodeContext getNode(int nodeIndex) {
306: Assert.isTrue(nodeIndex >= 0 && nodeIndex < getNodeCount(),
307: "nodeIndex out of bounds");
308: return getNodeWrapper(nodeIndex).getNode();
309: }
310:
311: protected ChildNodeWrapper getNodeWrapper(int nodeIndex) {
312: return (ChildNodeWrapper) childNodeWrappers.get(nodeIndex);
313: }
314:
315: // returns List<TreeNodeWidget>
316: public List getNodes() {
317: List nodes = new ArrayList(getNodeCount());
318: if (childNodeWrappers != null) {
319: for (Iterator i = childNodeWrappers.iterator(); i.hasNext();) {
320: ChildNodeWrapper nodeWrapper = (ChildNodeWrapper) i
321: .next();
322: nodes.add(nodeWrapper.getNode());
323: }
324: }
325: return Collections.unmodifiableList(nodes);
326: }
327:
328: public boolean hasNodes() {
329: return getNodeCount() > 0;
330: }
331:
332: public int getParentCount() {
333: TreeNodeContext parent = getParentNode();
334: if (parent != null) {
335: return parent.getParentCount() + 1;
336: }
337: return 0;
338: }
339:
340: public void setParentNode(TreeNodeWidget parentNode) {
341: Assert.notNullParam(parentNode, "parentNode");
342: this .parentNode = parentNode;
343: }
344:
345: public TreeNodeContext getParentNode() {
346: return parentNode;
347: }
348:
349: public int getIndex() {
350: return index;
351: }
352:
353: public void setIndex(int index) {
354: Assert.isTrue(index >= 0, "index must be >= 0");
355: this .index = index;
356: }
357:
358: public String getFullId() {
359: return getScope().toString();
360: }
361:
362: public void renderNode(OutputData output) throws Exception { // Called only from display widget's action
363: Assert.notNullParam(output, "output");
364: render(output);
365: }
366:
367: //*******************************************************************
368: // RENDERING METHODS
369: //*******************************************************************
370:
371: protected void render(OutputData output) throws Exception {
372: TreeRenderer renderer = getTreeCtx().getRenderer();
373: Assert.notNull(renderer, "renderer must be set");
374: Writer out = ((HttpOutputData) output).getWriter();
375:
376: // Render display widget
377: Widget display = getDisplayWidget();
378: if (display != null) { // display is null if this is root node (TreeWidget)
379: renderDisplayPrefixRecursive(out, renderer);
380: if (shouldRenderToggleLink()) {
381: renderer.renderToggleLink(out, this );
382: }
383: out.flush();
384: display._getWidget().render(output);
385: }
386:
387: // Render child nodes
388: if (display == null || (!isCollapsed() && hasNodes())) {
389: if (display == null) {
390: renderer.renderTreeStart(out, this );
391: } else {
392: renderer.renderChildrenStart(out, this );
393: }
394: if (!isCollapsed() && hasNodes()) {
395: for (Iterator i = childNodeWrappers.iterator(); i
396: .hasNext();) {
397: ChildNodeWrapper nodeWrapper = (ChildNodeWrapper) i
398: .next();
399: TreeNodeWidget node = nodeWrapper.getNode();
400: renderer.renderChildStart(out, this , node);
401: out.flush();
402: node.render(output);
403: renderer.renderChildEnd(out, this , node);
404: }
405: }
406: if (display == null) {
407: renderer.renderTreeEnd(out, this );
408: } else {
409: renderer.renderChildrenEnd(out, this );
410: }
411: }
412: }
413:
414: protected void renderDisplayPrefixRecursive(Writer out,
415: TreeRenderer renderer) throws Exception {
416: LinkedList parents = new LinkedList();
417: TreeNodeContext parent = getParentNode();
418: while (parent != null) {
419: parents.addFirst(parent);
420: parent = parent.getParentNode();
421: }
422: for (Iterator i = parents.iterator(); i.hasNext();) {
423: renderer.renderDisplayPrefix(out, (TreeNodeContext) i
424: .next(), false);
425: }
426: renderer.renderDisplayPrefix(out, this , true);
427: }
428:
429: }
|