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