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  
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   * @JSFRenderer
55   *   renderKitId = "HTML_BASIC" 
56   *   family = "org.apache.myfaces.HtmlTree2"
57   *   type = "org.apache.myfaces.HtmlTree2"
58   *   
59   * @author Sean Schofield
60   * @author Chris Barlow
61   * @author Hans Bergsten (Some code taken from an example in his O'Reilly JavaServer Faces book. Copied with permission)
62   * @version $Revision: 782532 $ $Date: 2009-06-08 00:38:29 -0500 (lun, 08 jun 2009) $
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      // see superclass for documentation
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         // see if one of the nav nodes was clicked, if so, then toggle appropriate node
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         // Fixes TOMAHAWK-437
164         //HtmlTree tree = (HtmlTree)component;
165         //if (!tree.getDataModel().getTreeState().isTransient()
166         //        && tree.isClientSideToggle()
167         //        && tree.isPreserveToggle())
168         //{
169         //    restoreStateFromCookies(context, component);
170         //}
171 
172         // write javascript functions
173         encodeJavascript(context, component);
174     }
175 
176     /**
177      * Renders the whole tree.  It generates a <code>&lt;span></code> element with an <code>id</code>
178      * attribute if the component has been given an explicit ID.  The model nodes are rendered
179      * recursively by the private <code>encodeNodes</code> method.
180      *
181      * @param context FacesContext
182      * @param component The component whose children are to be rendered
183      * @throws IOException
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); // walk all nodes in client mode
218 
219         if (showRootNode)
220         {
221             // encode the tree (starting with the root node)
222             if (walker.next())
223             {
224                 encodeTree(context, out, tree, walker);
225             }
226         }
227         else
228         {
229             // skip the root node
230             walker.next();
231             TreeNode rootNode = tree.getNode();
232 
233             // now mark the root as expanded (so we don't stop there)
234             String rootNodeId = tree.getNodeId();
235             if(!state.isNodeExpanded(rootNodeId))
236             {
237                 state.toggleExpanded(rootNodeId);
238             }
239 
240             // now encode each of the nodes in the level immediately below the root
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         // reset the current node id once we're done encoding everything
251         tree.setNodeId(null);
252 
253         if (isOuterSpanUsed)
254         {
255             out.endElement("span");
256         }
257     }
258 
259     /**
260      * Encodes the tree and its children.
261      *
262      * @param context FacesContext
263      * @param out ResponseWriter
264      * @param tree HtmlTree
265      * @param walker TreeWalker
266      * @throws IOException
267      */
268     protected void encodeTree(FacesContext context, ResponseWriter out, HtmlTree tree, TreeWalker walker)
269         throws IOException
270     {
271         boolean clientSideToggle = tree.isClientSideToggle();
272 
273         // encode the current node
274         HtmlRendererUtils.writePrettyLineSeparator(context);
275         beforeNodeEncode(context, out, tree);
276         encodeCurrentNode(context, out, tree);
277         afterNodeEncode(context, out);
278 
279         // if client side toggling is on, add a span to be used for displaying/hiding children
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      * Encodes the current node.  It is protected so that custom {@link Renderer}s can extend it.  That might be useful
315      * if you would like to render additional per node information besides the tree node.
316      *
317      * @param context FacesContext
318      * @param out ResponseWriter
319      * @param tree HtmlTree
320      * @throws IOException
321      */
322     protected void encodeCurrentNode(FacesContext context, ResponseWriter out, HtmlTree tree)
323         throws IOException
324     {
325         TreeNode node = tree.getNode();
326 
327         // set configurable values
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             // we must show the nav icons if client side toggle is enabled (regardless of what user says)
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         // render node padding
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); //we use "style" because "background" is no valid xhtml attribute for td
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); // placing a suitable description for the alt.
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         // render node
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      * Handles the encoding related to the navigation functionality.
410      *
411      * @param context FacesContext
412      * @param out ResponseWriter
413      * @param tree HtmlTree
414      * @return The additional navigation image to display inside the node (if any).  Only used with client-side toggle.
415      * @throws IOException
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;//TOGGLE_SPAN + 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) // if there are no children, ignore expand state -> more flexible with dynamic tree-structures
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             // unacceptable bitmask combinations
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                 // catch all for any other combinations
501                 throw new IllegalArgumentException("Invalid bit mask of " + bitMask);
502         }
503 
504         // adjust navSrc and altSrc so that the images can be retrieved using the extensions filter
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         // render nav cell
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             //out.writeURIAttribute("background", getImageSrc(context, tree, "line-trunk.gif", true), null);
518             out.writeURIAttribute(HTML.STYLE_ATTR, "background-image:url('" + getImageSrc(context, tree, "line-trunk.gif", true) + "');", null); 
519         }
520 
521 //      add the appropriate image for the nav control
522         UIGraphic image = new HtmlGraphicImage();
523         image.setTransient(true);
524         String imageId = IMAGE_PREFIX+tree.createUniqueId(context, null).substring(UIViewRoot.UNIQUE_ID_PREFIX.length());//IMAGE_PREFIX+(counter++);
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"); // placing a suitable description for the alt.
533 
534         if (clientSideToggle)
535         {
536             /**
537              * With client side toggle, user has the option to specify open/closed images for the node (in addition to
538              * the navigtion ones provided by the component.)
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                 // set up the expand control and remove whatever children (if any) this control had previously
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      * Encodes any stand-alone javascript functions that are needed.  Uses either the extension filter, or a
628      * user-supplied location for the javascript files.
629      *
630      * @param context FacesContext
631      * @param component UIComponent
632      * @throws IOException
633      */
634     private void encodeJavascriptWithJSF2(FacesContext context, UIComponent component)
635     {
636         // render javascript function for client-side toggle (it won't be used if user has opted for server-side toggle)
637         String javascriptLocation = ((HtmlTree) component).getJavascriptLocation();
638         if (javascriptLocation == null)
639         {
640             String javascriptLibrary = ((HtmlTree) component).getJavascriptLibrary();
641             
642             if (javascriptLibrary == null)
643             {
644                 //addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, HtmlTreeRenderer.class, "javascript/tree.js");
645                 //addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, HtmlTreeRenderer.class, "javascript/cookielib.js");
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         // render javascript function for client-side toggle (it won't be used if user has opted for server-side toggle)
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      * Get the appropriate image src location.  Uses the extension filter mechanism for images if no image
683      * location has been specified.
684      *
685      * @param context The {@link FacesContext}.  NOTE: If <code>null</code> then context path information
686      *    will not be used with extensions filter (assuming no imageLocation specified.)
687      * @param component UIComponent
688      * @param imageName The name of the image file to use.
689      * @return The image src information.
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                 //return addResource.getResourceUri(context, HtmlTreeRenderer.class,
700                 //        "images/" + imageName, withContextPath);
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      * Helper method for getting the boolean value of an attribute.  If the attribute is not specified,
719      * then return the default value.
720      *
721      * @param component The component for which the attributes are to be checked.
722      * @param attributeName The name of the boolean attribute.
723      * @param defaultValue The default value of the attribute (to be returned if no value found).
724      * @return boolean
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 }