1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.custom.tree2;
21
22
23 import org.apache.myfaces.component.html.ext.HtmlGraphicImage;
24 import org.apache.myfaces.renderkit.html.util.AddResource;
25 import org.apache.myfaces.renderkit.html.util.AddResourceFactory;
26 import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
27 import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils;
28 import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
29
30 import javax.faces.component.NamingContainer;
31 import javax.faces.component.UIComponent;
32 import javax.faces.component.UICommand;
33 import javax.faces.component.UIGraphic;
34 import javax.faces.component.UIViewRoot;
35 import javax.faces.component.UIParameter;
36 import javax.faces.context.FacesContext;
37 import javax.faces.context.ResponseWriter;
38 import javax.faces.render.Renderer;
39
40 import java.io.IOException;
41 import java.io.UnsupportedEncodingException;
42 import java.util.Map;
43 import java.util.Iterator;
44 import java.net.URLDecoder;
45 import javax.servlet.http.Cookie;
46 import java.util.HashMap;
47
48
49
50
51
52
53
54
55
56
57
58
59 public class HtmlTreeRenderer extends Renderer
60 {
61 protected static final String TOGGLE_SPAN = "org.apache.myfaces.tree.TOGGLE_SPAN";
62 protected static final String ROOT_NODE_ID = "0";
63
64 private static final String NAV_COMMAND = "org.apache.myfaces.tree.NAV_COMMAND";
65 private static final String ENCODING = "UTF-8";
66 private static final String ATTRIB_DELIM = ";";
67 private static final String ATTRIB_KEYVAL = "=";
68 private static final String NODE_STATE_EXPANDED = "x";
69 private static final String NODE_STATE_CLOSED = "c";
70 private static final String SEPARATOR = String.valueOf(NamingContainer.SEPARATOR_CHAR);
71 private static final String IMAGE_PREFIX = "t2";
72 private static final String TOGGLE_ID = "t2g";
73
74 private static final int NOTHING = 0;
75 private static final int CHILDREN = 1;
76 private static final int EXPANDED = 2;
77 private static final int LINES = 4;
78 private static final int LAST = 8;
79 private int counter = 0;
80
81
82 public boolean getRendersChildren()
83 {
84 return true;
85 }
86
87 private void restoreStateFromCookies(FacesContext context, UIComponent component) {
88 String nodeId = null;
89 HtmlTree tree = (HtmlTree)component;
90 TreeState state = tree.getDataModel().getTreeState();
91
92 Map cookieMap = context.getExternalContext().getRequestCookieMap();
93 Cookie treeCookie = (Cookie)cookieMap.get(component.getId());
94 if (treeCookie == null || treeCookie.getValue() == null)
95 {
96 return;
97 }
98
99 String nodeState = null;
100 Map attrMap = getCookieAttr(treeCookie);
101 Iterator i = attrMap.keySet().iterator();
102 while (i.hasNext())
103 {
104 nodeId = (String)i.next();
105 nodeState = (String)attrMap.get(nodeId);
106
107 if (NODE_STATE_EXPANDED.equals(nodeState))
108 {
109 if (!state.isNodeExpanded(nodeId))
110 {
111 state.toggleExpanded(nodeId);
112 }
113 }
114 else if (NODE_STATE_CLOSED.equals(nodeState))
115 {
116 if (state.isNodeExpanded(nodeId))
117 {
118 state.toggleExpanded(nodeId);
119 }
120 }
121 }
122 }
123
124
125 public void decode(FacesContext context, UIComponent component)
126 {
127 super.decode(context, component);
128
129
130 String nodeId = null;
131 HtmlTree tree = (HtmlTree)component;
132
133 if (tree.isClientSideToggle() && tree.isPreserveToggle())
134 {
135 restoreStateFromCookies(context, component);
136 }
137 else
138 {
139 nodeId = (String)context.getExternalContext().getRequestParameterMap().get(tree.getId() + SEPARATOR + NAV_COMMAND);
140
141 if (nodeId == null || nodeId.equals(""))
142 {
143 return;
144 }
145
146 component.queueEvent(new ToggleExpandedEvent(component, nodeId));
147 }
148 }
149
150 public void encodeBegin(FacesContext context, UIComponent component) throws IOException
151 {
152
153
154
155
156
157
158
159
160
161
162 encodeJavascript(context, component);
163 }
164
165
166
167
168
169
170
171
172
173
174 public void encodeChildren(FacesContext context, UIComponent component) throws IOException
175 {
176 HtmlTree tree = (HtmlTree)component;
177
178 if (!component.isRendered()) return;
179 if (tree.getValue() == null) return;
180
181 ResponseWriter out = context.getResponseWriter();
182 String clientId = null;
183
184 if (component.getId() != null && !component.getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX))
185 {
186 clientId = component.getClientId(context);
187 }
188
189 boolean isOuterSpanUsed = false;
190
191 if (clientId != null)
192 {
193 isOuterSpanUsed = true;
194 out.startElement("span", component);
195 out.writeAttribute("id", clientId, "id");
196 }
197
198 boolean clientSideToggle = tree.isClientSideToggle();
199 boolean showRootNode = tree.isShowRootNode();
200
201 TreeState state = tree.getDataModel().getTreeState();
202 TreeWalker walker = tree.getDataModel().getTreeWalker();
203 walker.reset();
204 walker.setTree(tree);
205
206 walker.setCheckState(!clientSideToggle);
207
208 if (showRootNode)
209 {
210
211 if (walker.next())
212 {
213 encodeTree(context, out, tree, walker);
214 }
215 }
216 else
217 {
218
219 walker.next();
220 TreeNode rootNode = tree.getNode();
221
222
223 String rootNodeId = tree.getNodeId();
224 if(!state.isNodeExpanded(rootNodeId))
225 {
226 state.toggleExpanded(rootNodeId);
227 }
228
229
230 for (int i=0; i < rootNode.getChildCount(); i++)
231 {
232 if (walker.next())
233 {
234 encodeTree(context, out, tree, walker);
235 }
236 }
237 }
238
239
240 tree.setNodeId(null);
241
242 if (isOuterSpanUsed)
243 {
244 out.endElement("span");
245 }
246 }
247
248
249
250
251
252
253
254
255
256
257 protected void encodeTree(FacesContext context, ResponseWriter out, HtmlTree tree, TreeWalker walker)
258 throws IOException
259 {
260 boolean clientSideToggle = tree.isClientSideToggle();
261
262
263 HtmlRendererUtils.writePrettyLineSeparator(context);
264 beforeNodeEncode(context, out, tree);
265 encodeCurrentNode(context, out, tree);
266 afterNodeEncode(context, out);
267
268
269 if (clientSideToggle)
270 {
271 String spanId = TOGGLE_SPAN + ":" + tree.getClientId(context) + ":" + tree.getNodeId();
272
273 out.startElement(HTML.SPAN_ELEM, tree);
274 out.writeAttribute(HTML.ID_ATTR, spanId, null);
275
276 if (tree.isNodeExpanded())
277 {
278 out.writeAttribute(HTML.STYLE_ATTR, "display:block", null);
279 }
280 else
281 {
282 out.writeAttribute(HTML.STYLE_ATTR, "display:none", null);
283 }
284 }
285
286 TreeNode node = tree.getNode();
287
288 for (int i=0; i < node.getChildCount(); i++)
289 {
290 if (walker.next())
291 {
292 encodeTree(context, out, tree, walker);
293 }
294 }
295
296 if (clientSideToggle)
297 {
298 out.endElement(HTML.SPAN_ELEM);
299 }
300 }
301
302
303
304
305
306
307
308
309
310
311 protected void encodeCurrentNode(FacesContext context, ResponseWriter out, HtmlTree tree)
312 throws IOException
313 {
314 TreeNode node = tree.getNode();
315
316
317 boolean showRootNode = tree.isShowRootNode();
318 boolean showNav = tree.isShowNav();
319 boolean showLines = tree.isShowLines();
320 boolean clientSideToggle = tree.isClientSideToggle();
321
322 if (clientSideToggle)
323 {
324
325 showNav = true;
326 }
327
328 UIComponent nodeTypeFacet = tree.getFacet(node.getType());
329 UIComponent nodeImgFacet = null;
330
331 if (nodeTypeFacet == null)
332 {
333 throw new IllegalArgumentException("Unable to locate facet with the name: " + node.getType());
334 }
335
336
337 String[] pathInfo = tree.getPathInformation(tree.getNodeId());
338 int paddingLevel = pathInfo.length - 1;
339
340 for (int i = (showRootNode ? 0 : 1); i < paddingLevel; i++)
341 {
342 boolean lastChild = tree.isLastChild((String)pathInfo[i]);
343 String lineSrc = (!lastChild && showLines)
344 ? getImageSrc(context, tree, "line-trunk.gif", true)
345 : getImageSrc(context, tree, "spacer.gif", true);
346 String altString = (!lastChild && showLines)
347 ? "line trunk"
348 : "spacer";
349
350
351 out.startElement(HTML.TD_ELEM, tree);
352 out.writeAttribute(HTML.WIDTH_ATTR, "19", null);
353 out.writeAttribute(HTML.HEIGHT_ATTR, "100%", null);
354 out.writeURIAttribute(HTML.STYLE_ATTR, "background-image:url('" + lineSrc + "');", null);
355 out.startElement(HTML.IMG_ELEM, tree);
356 out.writeURIAttribute(HTML.SRC_ATTR, lineSrc, null);
357 out.writeAttribute(HTML.WIDTH_ATTR, "19", null);
358 out.writeAttribute(HTML.HEIGHT_ATTR, "18", null);
359 out.writeAttribute(HTML.BORDER_ATTR, "0", null);
360 out.writeAttribute(HTML.ALT_ATTR, altString, null);
361 out.endElement(HTML.IMG_ELEM);
362 out.endElement(HTML.TD_ELEM);
363 }
364
365 if (showNav)
366 {
367 nodeImgFacet = encodeNavigation(context, out, tree);
368 }
369
370
371 out.startElement(HTML.TD_ELEM, tree);
372 if (nodeImgFacet != null)
373 {
374 RendererUtils.renderChild(context, nodeImgFacet);
375 }
376 RendererUtils.renderChild(context, nodeTypeFacet);
377 out.endElement(HTML.TD_ELEM);
378 }
379
380 protected void beforeNodeEncode(FacesContext context, ResponseWriter out, HtmlTree tree)
381 throws IOException
382 {
383 out.startElement(HTML.TABLE_ELEM, tree);
384 out.writeAttribute(HTML.CELLPADDING_ATTR, "0", null);
385 out.writeAttribute(HTML.CELLSPACING_ATTR, "0", null);
386 out.writeAttribute(HTML.BORDER_ATTR, "0", null);
387 out.startElement(HTML.TR_ELEM, tree);
388 }
389
390 protected void afterNodeEncode(FacesContext context, ResponseWriter out)
391 throws IOException
392 {
393 out.endElement(HTML.TR_ELEM);
394 out.endElement(HTML.TABLE_ELEM);
395 }
396
397
398
399
400
401
402
403
404
405
406 private UIComponent encodeNavigation(FacesContext context, ResponseWriter out, HtmlTree tree)
407 throws IOException
408 {
409 TreeNode node = tree.getNode();
410 String nodeId = tree.getNodeId();
411 String spanId = TOGGLE_SPAN + ":" + tree.getClientId(context) + ":" + nodeId;
412 boolean showLines = tree.isShowLines();
413 boolean clientSideToggle = tree.isClientSideToggle();
414 UIComponent nodeTypeFacet = tree.getFacet(node.getType());
415 String navSrc = null;
416 String altSrc = null;
417 UIComponent nodeImgFacet = null;
418
419 int bitMask = NOTHING;
420 bitMask += (node.isLeaf()) ? NOTHING : CHILDREN;
421 if (bitMask == CHILDREN)
422 bitMask += (tree.isNodeExpanded()) ? EXPANDED : NOTHING;
423 bitMask += (tree.isLastChild(tree.getNodeId())) ? LAST : NOTHING;
424 bitMask += (showLines) ? LINES : NOTHING;
425
426 switch (bitMask)
427 {
428 case (NOTHING):
429
430 case (LAST):
431 navSrc = "spacer.gif";
432 break;
433
434 case (LINES):
435 navSrc = "line-middle.gif";
436 break;
437
438 case (LINES + LAST):
439 navSrc = "line-last.gif";
440 break;
441
442 case (CHILDREN):
443
444 case (CHILDREN + LAST):
445 navSrc = "nav-plus.gif";
446 altSrc = "nav-minus.gif";
447 break;
448
449 case (CHILDREN + LINES):
450
451 navSrc = "nav-plus-line-middle.gif";
452 altSrc = "nav-minus-line-middle.gif";
453 break;
454
455 case (CHILDREN + LINES + LAST):
456
457 navSrc = "nav-plus-line-last.gif";
458 altSrc = "nav-minus-line-last.gif";
459 break;
460
461 case (CHILDREN + EXPANDED):
462
463 case (CHILDREN + EXPANDED + LAST):
464 navSrc = "nav-minus.gif";
465 altSrc = "nav-plus.gif";
466 break;
467
468 case (CHILDREN + EXPANDED + LINES):
469 navSrc = "nav-minus-line-middle.gif";
470 altSrc = "nav-plus-line-middle.gif";
471 break;
472
473 case (CHILDREN + EXPANDED + LINES + LAST):
474 navSrc = "nav-minus-line-last.gif";
475 altSrc = "nav-plus-line-last.gif";
476 break;
477
478
479
480 case (EXPANDED + LINES):
481 case (EXPANDED + LINES + LAST):
482 case (EXPANDED):
483 case (EXPANDED + LAST):
484
485 throw new IllegalStateException("Encountered a node ["+ nodeId + "] + with an illogical state. " +
486 "Node is expanded but it is also considered a leaf (a leaf cannot be considered expanded.");
487
488 default:
489
490 throw new IllegalArgumentException("Invalid bit mask of " + bitMask);
491 }
492
493
494 String navSrcUrl = getImageSrc(context, tree, navSrc, false);
495 navSrc = getImageSrc(context, tree, navSrc, true);
496 altSrc = getImageSrc(context, tree, altSrc, true);
497
498
499 out.startElement(HTML.TD_ELEM, tree);
500 out.writeAttribute(HTML.WIDTH_ATTR, "19", null);
501 out.writeAttribute(HTML.HEIGHT_ATTR, "100%", null);
502 out.writeAttribute("valign", "top", null);
503
504 if ((bitMask & LINES)!=0 && (bitMask & LAST)==0)
505 {
506
507 out.writeURIAttribute(HTML.STYLE_ATTR, "background-image:url('" + getImageSrc(context, tree, "line-trunk.gif", true) + "');", null);
508 }
509
510
511 UIGraphic image = new HtmlGraphicImage();
512 String imageId = IMAGE_PREFIX+(counter++);
513 image.setId(imageId);
514 image.setUrl(navSrcUrl);
515
516 Map imageAttrs = image.getAttributes();
517 imageAttrs.put(HTML.WIDTH_ATTR, "19");
518 imageAttrs.put(HTML.HEIGHT_ATTR, "18");
519 imageAttrs.put(HTML.BORDER_ATTR, "0");
520 imageAttrs.put(HTML.ALT_ATTR, "Nav control image");
521
522 if (clientSideToggle)
523 {
524
525
526
527
528 String expandImgSrc = "";
529 String collapseImgSrc = "";
530 String nodeImageId = "";
531
532 UIComponent expandFacet = nodeTypeFacet.getFacet("expand");
533 if (expandFacet != null)
534 {
535 UIGraphic expandImg = (UIGraphic)expandFacet;
536 expandImgSrc = context.getApplication().getViewHandler().getResourceURL(context, expandImg.getUrl());
537 if (expandImg.isRendered())
538 {
539 expandImg.setId(imageId + NODE_STATE_EXPANDED);
540 expandImg.setParent(tree);
541 nodeImageId = expandImg.getClientId(context);
542 nodeImgFacet = expandFacet;
543 }
544 }
545
546 UIComponent collapseFacet = nodeTypeFacet.getFacet("collapse");
547 if (collapseFacet != null)
548 {
549 UIGraphic collapseImg = (UIGraphic)collapseFacet;
550 collapseImgSrc = context.getApplication().getViewHandler().getResourceURL(context, collapseImg.getUrl());
551 if (collapseImg.isRendered())
552 {
553 collapseImg.setId(imageId + NODE_STATE_CLOSED);
554 collapseImg.setParent(tree);
555 nodeImageId = collapseImg.getClientId(context);
556 nodeImgFacet = collapseFacet;
557 }
558 }
559
560 image.setParent(tree);
561 if (node.getChildCount() > 0)
562 {
563 String onClick = new StringBuffer()
564 .append("treeNavClick('")
565 .append(spanId)
566 .append("', '")
567 .append(image.getClientId(context))
568 .append("', '")
569 .append(navSrc)
570 .append("', '")
571 .append(altSrc)
572 .append("', '")
573 .append(nodeImageId)
574 .append("', '")
575 .append(expandImgSrc)
576 .append("', '")
577 .append(collapseImgSrc)
578 .append("', '")
579 .append(tree.getId())
580 .append("', '")
581 .append(nodeId)
582 .append("');")
583 .toString();
584
585 imageAttrs.put(HTML.ONCLICK_ATTR, onClick);
586 imageAttrs.put(HTML.STYLE_ATTR, "cursor:pointer;cursor:hand");
587 }
588 RendererUtils.renderChild(context, image);
589
590 }
591 else
592 {
593 if (node.getChildCount() > 0)
594 {
595
596 UICommand expandControl = tree.getExpandControl();
597 expandControl.getChildren().clear();
598 expandControl.setId(TOGGLE_ID);
599
600 UIParameter param = new UIParameter();
601 param.setName(tree.getId() + NamingContainer.SEPARATOR_CHAR + NAV_COMMAND);
602 param.setValue(tree.getNodeId());
603 expandControl.getChildren().add(param);
604 expandControl.getChildren().add(image);
605
606 RendererUtils.renderChild(context, expandControl);
607 }
608 else
609 {
610 RendererUtils.renderChild(context, image);
611 }
612 }
613 out.endElement(HTML.TD_ELEM);
614
615 return nodeImgFacet;
616 }
617
618
619
620
621
622
623
624
625
626 private void encodeJavascript(FacesContext context, UIComponent component) throws IOException
627 {
628
629 String javascriptLocation = ((HtmlTree)component).getJavascriptLocation();
630 AddResource addResource = AddResourceFactory.getInstance(context);
631 if (javascriptLocation == null)
632 {
633 addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, HtmlTreeRenderer.class, "javascript/tree.js");
634 addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, HtmlTreeRenderer.class, "javascript/cookielib.js");
635 }
636 else
637 {
638 addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, javascriptLocation + "/tree.js");
639 addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, javascriptLocation + "/cookielib.js");
640 }
641 }
642
643
644
645
646
647
648
649
650
651
652
653 private String getImageSrc(FacesContext context, UIComponent component, String imageName, boolean withContextPath)
654 {
655 String imageLocation = ((HtmlTree)component).getImageLocation();
656 AddResource addResource = AddResourceFactory.getInstance(context);
657 if (imageLocation == null)
658 {
659 return addResource.getResourceUri(context, HtmlTreeRenderer.class,
660 "images/" + imageName, withContextPath);
661 }
662 else
663 {
664 return addResource.getResourceUri(context, imageLocation + "/" + imageName, withContextPath);
665 }
666 }
667
668
669
670
671
672
673
674
675
676
677 protected boolean getBoolean(UIComponent component, String attributeName, boolean defaultValue)
678 {
679 Boolean booleanAttr = (Boolean)component.getAttributes().get(attributeName);
680
681 if (booleanAttr == null)
682 {
683 return defaultValue;
684 }
685 else
686 {
687 return booleanAttr.booleanValue();
688 }
689 }
690
691 private Map getCookieAttr(Cookie cookie)
692 {
693 Map attribMap = new HashMap();
694 try
695 {
696 String cookieValue = URLDecoder.decode(cookie.getValue(),ENCODING);
697 String[] attribArray = cookieValue.split(ATTRIB_DELIM);
698 for (int j = 0; j < attribArray.length; j++)
699 {
700 int index = attribArray[j].indexOf(ATTRIB_KEYVAL);
701 String name = attribArray[j].substring(0, index);
702 String value = attribArray[j].substring(index + 1);
703 attribMap.put(name, value);
704 }
705 }
706 catch (UnsupportedEncodingException e)
707 {
708 throw new RuntimeException("Error parsing tree cookies", e);
709 }
710 return attribMap;
711 }
712 }