View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.custom.tree;
20  
21  import org.apache.myfaces.custom.tree.model.TreeModel;
22  import org.apache.myfaces.custom.tree.model.TreePath;
23  
24  import javax.faces.component.html.HtmlCommandLink;
25  import javax.faces.context.FacesContext;
26  import java.util.Iterator;
27  import java.util.List;
28  
29  
30  /**
31   * Represents a single node of a three. A custom html link component representing the expand/collapse icon
32   * is held as a facet named <code>expandCollapse</code>.
33   *
34   * @JSFComponent
35   * @author <a href="mailto:oliver@rossmueller.com">Oliver Rossmueller</a>
36   * @version $Revision: 659874 $ $Date: 2008-05-24 15:59:15 -0500 (Sat, 24 May 2008) $
37   */
38  public class HtmlTreeNode
39      extends HtmlCommandLink
40  {
41  
42      private static final String DEFAULT_RENDERER_TYPE = "org.apache.myfaces.HtmlTreeNode";
43  
44      public static final String COMPONENT_TYPE = "org.apache.myfaces.HtmlTreeNode";
45      public static final String EXPAND_COLLAPSE_FACET = "expandCollapse";
46  
47      public static final int OPEN = 0;
48      public static final int OPEN_FIRST = 1;
49      public static final int OPEN_LAST = 2;
50      public static final int OPEN_SINGLE = 3;
51      public static final int CLOSED = 10;
52      public static final int CLOSED_FIRST = 11;
53      public static final int CLOSED_LAST = 12;
54      public static final int CLOSED_SINGLE = 13;
55      public static final int CHILD = 20;
56      public static final int CHILD_FIRST = 21;
57      public static final int CHILD_LAST = 22;
58      public static final int LINE = 30;
59      public static final int EMPTY = 40;
60      private static final int OFFSET = 10;
61      private static final int MOD_FIRST = 1;
62      private static final int MOD_LAST = 2;
63  
64      private transient TreePath path;
65      private int[] translatedPath;
66      private transient Object userObject;
67      private boolean expanded = false;
68      private boolean selected = false;
69      private int[] layout;
70  
71  
72      public HtmlTreeNode()
73      {
74          setRendererType(DEFAULT_RENDERER_TYPE);
75      }
76  
77  
78      public int getLevel()
79      {
80          return layout.length - 1;
81      }
82  
83  
84      public int getMaxChildLevel()
85      {
86          if (getChildCount() == 0)
87          {
88              return getLevel();
89          }
90  
91          int max = getLevel();
92          for (Iterator iterator = getChildren().iterator(); iterator.hasNext();)
93          {
94              HtmlTreeNode child = (HtmlTreeNode) iterator.next();
95              max = Math.max(max, child.getMaxChildLevel());
96          }
97          return max;
98      }
99  
100 
101     public TreePath getPath()
102     {
103         if (path == null)
104         {
105             if (translatedPath == null)
106             {
107                 throw new IllegalStateException("No path and no translated path available");
108             }
109             path = translatePath(translatedPath, getTreeModel(FacesContext.getCurrentInstance()));
110         }
111         return path;
112     }
113 
114 
115     public void setPath(TreePath path)
116     {
117         this.path = path;
118     }
119 
120 
121     int[] getTranslatedPath()
122     {
123         if (translatedPath != null)
124         {
125             return translatedPath;
126         }
127         
128         return translatePath(getPath(), getTreeModel(FacesContext.getCurrentInstance()));
129     }
130 
131 
132     public Object getUserObject()
133     {
134         if (userObject == null)
135         {
136             userObject = getPath().getLastPathComponent();
137         }
138         return userObject;
139     }
140 
141 
142     public void setUserObject(Object userObject)
143     {
144         this.userObject = userObject;
145         setValue(userObject.toString());
146     }
147 
148 
149     public boolean isExpanded()
150     {
151         return expanded;
152     }
153 
154 
155     void childrenAdded(int[] children, FacesContext context)
156     {
157         if (getChildCount() == 0 && children.length > 0)
158         {
159             // change to CLOSED_*
160             layout[layout.length - 1] -= OFFSET;
161         }
162 
163         if (!expanded)
164         {
165             // nothing to do
166             return;
167         }
168 
169         TreeModel model = getTreeModel(context);
170         int childCount = model.getChildCount(getUserObject());
171         int pathUpdateIndex = getTranslatedPath().length;
172 
173         for (int i = 0; i < children.length; i++)
174         {
175             int index = children[i];
176             translateChildrenPath(pathUpdateIndex, index, 1);
177             Object userObject = model.getChild(getUserObject(), index);
178             addNode(model, userObject, index, childCount, context);
179         }
180     }
181 
182 
183     void childrenChanged(int[] children, FacesContext context)
184     {
185         if (!expanded)
186         {
187             // nothing to do
188             return;
189         }
190 
191         TreeModel model = getTreeModel(context);
192 
193         for (int i = 0; i < children.length; i++)
194         {
195             int index = children[i];
196             Object userObject = model.getChild(getUserObject(), index);
197             HtmlTreeNode node = (HtmlTreeNode) getChildren().get(index);
198             node.setUserObject(userObject);
199             // todo: modify path????
200         }
201     }
202 
203 
204     private void addNode(TreeModel model, Object userObject, int index, int childCount, FacesContext context)
205     {
206         HtmlTreeNode node = createNode(model, userObject, childCount, index, context);
207         List children = getChildren();
208 
209         if (!children.isEmpty())
210         {
211             if (index == 0)
212             {
213                 HtmlTreeNode first = (HtmlTreeNode) getChildren().get(0);
214                 int[] layout = first.getLayout();
215                 if (layout[layout.length - 1] % OFFSET == MOD_FIRST)
216                 {
217                     // change from *_FIRST to *
218                     layout[layout.length - 1]--;
219                 }
220             } else if (index == childCount - 1)
221             {
222                 HtmlTreeNode last = (HtmlTreeNode) getChildren().get(childCount - 2);
223                 int[] layout = last.getLayout();
224                 if (layout[layout.length - 1] % OFFSET == MOD_LAST)
225                 {
226                     // change from *_LAST to *
227                     layout[layout.length - 1] -= 2;
228                 }
229             }
230         }
231 
232         children.add(index, node);
233     }
234 
235 
236     void childrenRemoved(int[] children)
237     {
238         if (!expanded)
239         {
240             // nothing to do
241             return;
242         }
243         List childNodes = getChildren();
244         int pathUpdateIndex = getTranslatedPath().length;
245 
246         for (int i = children.length - 1; i >= 0; i--)
247         {
248             translateChildrenPath(pathUpdateIndex, children[i], -1);
249             HtmlTreeNode child = (HtmlTreeNode) childNodes.get(children[i]);
250 
251             if (child.isSelected()) {
252                 child.setSelected(false);
253                 if (children[i] < childNodes.size() - 1) {
254                     ((HtmlTreeNode) childNodes.get(children[i] + 1)).setSelected(true);
255                 } else {
256                     if (children[i] > 0) {
257                         ((HtmlTreeNode) childNodes.get(children[i] - 1)).setSelected(true);
258                     } else {
259                         setSelected(true);
260                     }
261                 }
262             }
263             childNodes.remove(children[i]);
264         }
265     }
266 
267 
268     /**
269      * Update the translatedPath of all child nodes so the path points to the same object in the model
270      * after adding/removing a node
271      *
272      * @param pathUpdateIndex
273      * @param startIndex
274      * @param offset
275      */
276     private void translateChildrenPath(int pathUpdateIndex, int startIndex, int offset) {
277         List children = getChildren();
278         int childrenSize = children.size();
279 
280         for (int i = startIndex; i < childrenSize; i++) {
281             HtmlTreeNode node = (HtmlTreeNode) children.get(i);
282             node.updateTranslatedPath(pathUpdateIndex, offset);
283         }
284     }
285 
286 
287     private void updateTranslatedPath(int pathUpdateIndex, int offset)
288     {
289         translatedPath[pathUpdateIndex] += offset;
290         // reset path and userObject so the values are acquired from the model
291         path = null;
292         userObject = null;
293 
294         if (isExpanded()) {
295             // continue with the children of this node
296             translateChildrenPath(pathUpdateIndex, 0, offset);
297         }
298     }
299 
300 
301     public void setExpanded(boolean expanded)
302     {
303         if (this.expanded == expanded)
304         {
305             // no change
306             return;
307         }
308         this.expanded = expanded;
309 
310         if (expanded)
311         {
312             FacesContext context = FacesContext.getCurrentInstance();
313             TreeModel model = getTreeModel(context);
314             int childCount = model.getChildCount(getUserObject());
315 
316             for (int i = 0; i < childCount; i++)
317             {
318                 Object child = model.getChild(getUserObject(), i);
319                 HtmlTreeNode node = createNode(model, child, childCount, i, context);
320                 getChildren().add(node);
321             }
322             layout[layout.length - 1] -= OFFSET;
323         } else
324         {
325             if (clearSelection())
326             {
327                 setSelected(true);
328             }
329             getChildren().clear();
330             layout[layout.length - 1] += OFFSET;
331         }
332 
333     }
334 
335 
336     private HtmlTreeNode createNode(TreeModel model, Object child, int childCount, int index, FacesContext context)
337     {
338         HtmlTreeNode node = (HtmlTreeNode) context.getApplication().createComponent(HtmlTreeNode.COMPONENT_TYPE);
339         String id = getTree().createUniqueId(context);
340         node.setId(id);
341 
342         node.setPath(getPath().pathByAddingChild(child));
343         node.setUserObject(child);
344         int state = CHILD;
345 
346         if (model.isLeaf(child))
347         {
348 
349             if (childCount > 1)
350             {
351                 if (index == 0)
352                 {
353                     state = CHILD;
354                 } else if (index == childCount - 1)
355                 {
356                     state = CHILD_LAST;
357                 }
358             } else
359             {
360                 state = CHILD_LAST;
361             }
362         } else
363         {
364             if (childCount > 1)
365             {
366                 if (index == 0)
367                 {
368                     state = CLOSED;
369                 } else if (index == childCount - 1)
370                 {
371                     state = CLOSED_LAST;
372                 } else
373                 {
374                     state = CLOSED;
375                 }
376             } else
377             {
378                 state = CLOSED_LAST;
379             }
380 
381         }
382         node.setLayout(layout, state);
383 
384         return node;
385     }
386 
387 
388     public void toggleExpanded()
389     {
390         setExpanded(!expanded);
391     }
392 
393 
394     public boolean isSelected()
395     {
396         return selected;
397     }
398 
399 
400     public void setSelected(boolean selected)
401     {
402         if (selected)
403         {
404             getTree().getRootNode().clearSelection();
405         }
406         this.selected = selected;
407         getTree().selectionChanged(this);
408     }
409 
410 
411     public void toggleSelected()
412     {
413         setSelected(!selected);
414     }
415 
416 
417     private boolean clearSelection()
418     {
419         if (isSelected())
420         {
421             selected = false;
422             return true;
423         }
424         for (Iterator iterator = getChildren().iterator(); iterator.hasNext();)
425         {
426             HtmlTreeNode node = (HtmlTreeNode) iterator.next();
427             if (node.clearSelection())
428             {
429                 return true;
430             }
431         }
432         return false;
433     }
434 
435 
436     public int[] getLayout()
437     {
438         return layout;
439     }
440 
441 
442     public void setLayout(int[] layout)
443     {
444         this.layout = layout;
445     }
446 
447 
448     public void setLayout(int[] parentLayout, int layout)
449     {
450         this.layout = new int[parentLayout.length + 1];
451         this.layout[parentLayout.length] = layout;
452 
453         for (int i = 0; i < parentLayout.length; i++)
454         {
455             int state = parentLayout[i];
456             if (state == OPEN || state == OPEN_FIRST || state == CLOSED || state == CLOSED_FIRST || state == CHILD || state == CHILD_FIRST || state == LINE)
457             {
458                 this.layout[i] = LINE;
459             } else
460             {
461                 this.layout[i] = EMPTY;
462             }
463         }
464     }
465 
466 
467     public HtmlTreeImageCommandLink getExpandCollapseCommand(FacesContext context)
468     {
469         HtmlTreeImageCommandLink command = (HtmlTreeImageCommandLink) getFacet(EXPAND_COLLAPSE_FACET);
470 
471         if (command == null)
472         {
473             command = (HtmlTreeImageCommandLink) context.getApplication().createComponent(HtmlTreeImageCommandLink.COMPONENT_TYPE);
474             String id = getTree().createUniqueId(context);
475             command.setId(id);
476             getFacets().put(EXPAND_COLLAPSE_FACET, command);
477         }
478         return command;
479     }
480 
481 
482     public Object saveState(FacesContext context)
483     {
484         Object values[] = new Object[5];
485         values[0] = super.saveState(context);
486         values[1] = Boolean.valueOf(expanded);
487         values[2] = layout;
488         values[3] = translatePath(getPath(), getTreeModel(context));
489         values[4] = Boolean.valueOf(selected);
490         return values;
491     }
492 
493 
494     public void restoreState(FacesContext context, Object state)
495     {
496         Object values[] = (Object[]) state;
497         super.restoreState(context, values[0]);
498         expanded = ((Boolean) values[1]).booleanValue();
499         layout = (int[]) values[2];
500         translatedPath = (int[]) values[3];
501         selected = ((Boolean) values[4]).booleanValue();
502     }
503 
504 
505     protected static int[] translatePath(TreePath treePath, TreeModel model)
506     {
507         Object[] path = treePath.getPath();
508         int[] translated = new int[path.length - 1];
509 
510         Object parent = path[0];
511 
512         for (int i = 1; i < path.length; i++)
513         {
514             Object element = path[i];
515             translated[i - 1] = model.getIndexOfChild(parent, element);
516             parent = element;
517         }
518         return translated;
519     }
520 
521 
522     protected static TreePath translatePath(int[] path, TreeModel model)
523     {
524         Object[] translated = new Object[path.length + 1];
525         Object parent = model.getRoot();
526 
527         translated[0] = parent;
528 
529         for (int i = 0; i < path.length; i++)
530         {
531             int index = path[i];
532             translated[i + 1] = model.getChild(parent, index);
533             parent = translated[i + 1];
534         }
535         return new TreePath(translated);
536     }
537 
538 
539     protected TreeModel getTreeModel(FacesContext context)
540     {
541         return getTree().getModel(context);
542     }
543 
544 
545     protected HtmlTree getTree()
546     {
547         if (getParent() instanceof HtmlTree)
548         {
549             return (HtmlTree) getParent();
550         }
551         return ((HtmlTreeNode) getParent()).getTree();
552     }
553 
554 
555     public boolean isLeaf(FacesContext context)
556     {
557         return getTreeModel(context).isLeaf(getUserObject());
558     }
559 
560 
561     public void expandPath(int[] translatedPath, int current)
562     {
563         if (current >= translatedPath.length)
564         {
565             return;
566         }
567 
568         HtmlTreeNode child = (HtmlTreeNode) getChildren().get(translatedPath[current]);
569 
570         if (!child.isExpanded())
571         {
572             child.setExpanded(true);
573         }
574 
575         child.expandPath(translatedPath, current + 1);
576     }
577 
578 
579     public void restoreItemState(HtmlTreeNode node)
580     {
581         setExpanded(node.isExpanded());
582         selected = node.isSelected();
583         List children = getChildren();
584         List otherChildren = node.getChildren();
585         for (int i = 0; i < children.size(); i++)
586         {
587             HtmlTreeNode child = (HtmlTreeNode) children.get(i);
588             child.restoreItemState((HtmlTreeNode) otherChildren.get(i));
589         }
590     }
591 }