1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
32
33
34
35
36
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
160 layout[layout.length - 1] -= OFFSET;
161 }
162
163 if (!expanded)
164 {
165
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
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
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
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
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
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
270
271
272
273
274
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
291 path = null;
292 userObject = null;
293
294 if (isExpanded()) {
295
296 translateChildrenPath(pathUpdateIndex, 0, offset);
297 }
298 }
299
300
301 public void setExpanded(boolean expanded)
302 {
303 if (this.expanded == expanded)
304 {
305
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 }