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.navmenu.jscookmenu;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.StringTokenizer;
26  
27  import javax.faces.FacesException;
28  import javax.faces.application.Resource;
29  import javax.faces.application.ResourceHandler;
30  import javax.faces.component.UIComponent;
31  import javax.faces.context.ExternalContext;
32  import javax.faces.context.FacesContext;
33  import javax.faces.context.ResponseWriter;
34  import javax.faces.el.MethodBinding;
35  import javax.faces.el.ValueBinding;
36  import javax.faces.event.ActionEvent;
37  import javax.faces.event.ComponentSystemEvent;
38  import javax.faces.event.ComponentSystemEventListener;
39  import javax.faces.event.ListenerFor;
40  
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  import org.apache.myfaces.component.LibraryLocationAware;
44  import org.apache.myfaces.custom.navmenu.NavigationMenuItem;
45  import org.apache.myfaces.custom.navmenu.NavigationMenuUtils;
46  import org.apache.myfaces.custom.navmenu.UINavigationMenuItem;
47  import org.apache.myfaces.renderkit.html.util.AddResource;
48  import org.apache.myfaces.renderkit.html.util.AddResourceFactory;
49  import org.apache.myfaces.shared_tomahawk.el.SimpleActionMethodBinding;
50  import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
51  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
52  import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
53  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlFormRendererBase;
54  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRenderer;
55  import org.apache.myfaces.shared_tomahawk.renderkit.html.util.FormInfo;
56  import org.apache.myfaces.shared_tomahawk.renderkit.html.util.JavascriptUtils;
57  import org.apache.myfaces.tomahawk.application.PreRenderViewAddResourceEvent;
58  import org.apache.myfaces.tomahawk.util.TomahawkResourceUtils;
59  
60  /**
61   * @JSFRenderer
62   *   renderKitId = "HTML_BASIC" 
63   *   family = "javax.faces.Command"
64   *   type = "org.apache.myfaces.JSCookMenu"
65   * 
66   * @author Thomas Spiegl
67   * @version $Revision: 698742 $ $Date: 2008-09-24 16:25:46 -0500 (miƩ, 24 sep 2008) $
68   */
69  @ListenerFor(systemEventClass=PreRenderViewAddResourceEvent.class)
70  public class HtmlJSCookMenuRenderer
71      extends HtmlRenderer implements ComponentSystemEventListener
72  {
73      private static final String MYFACES_HACK_SCRIPT = "MyFacesHack.js";
74  
75      private static final String JSCOOK_MENU_SCRIPT = "JSCookMenu.js";
76      
77      private static final String JSCOOK_EFFECT_SCRIPT = "effect.js";    
78  
79      private static final Log log = LogFactory.getLog(HtmlJSCookMenuRenderer.class);
80  
81      private static final String JSCOOK_ACTION_PARAM = "jscook_action";
82      private static final Class[] ACTION_LISTENER_ARGS = {ActionEvent.class};
83  
84      private static final Map builtInThemes = new java.util.HashMap();
85  
86      static {
87          builtInThemes.put("ThemeOffice", "ThemeOffice/");
88          builtInThemes.put("ThemeMiniBlack", "ThemeMiniBlack/");
89          builtInThemes.put("ThemeIE", "ThemeIE/");
90          builtInThemes.put("ThemePanel", "ThemePanel/");
91          builtInThemes.put("ThemeGray", "ThemeGray/");        
92      }
93  
94      public void processEvent(ComponentSystemEvent event)
95      {
96          HtmlCommandJSCookMenu menu = (HtmlCommandJSCookMenu) event.getComponent();
97          String theme = menu.getTheme();
98          if (theme == null) {
99              // should never happen; theme is a required attribute in the jsp tag definition
100             throw new IllegalArgumentException("theme name is mandatory for a jscookmenu.");
101         }
102 
103         addResourcesToHeaderWithJSF2ResourceAPI(theme, menu, FacesContext.getCurrentInstance());
104     }
105 
106     public void decode(FacesContext context, UIComponent component) {
107         RendererUtils.checkParamValidity(context, component, HtmlCommandJSCookMenu.class);
108 
109         Map parameter = context.getExternalContext().getRequestParameterMap();
110         String actionParam = (String) parameter.get(JSCOOK_ACTION_PARAM);
111         if (actionParam != null && !actionParam.trim().equals("") &&
112             !actionParam.trim().equals("null")) {
113             String compId = getMenuId(context, component);
114             StringTokenizer tokenizer = new StringTokenizer(actionParam, ":");
115             if (tokenizer.countTokens() > 1) {
116                 String actionId = tokenizer.nextToken();
117                 if (! compId.equals(actionId)) {
118                     return;
119                 }
120                 while (tokenizer.hasMoreTokens()) {
121                     String action = tokenizer.nextToken();
122                     if (action.startsWith("A]")) {
123                         action = action.substring(2, action.length());
124                         action = decodeValueBinding(action, context);
125                         MethodBinding mb;
126                         if (NavigationMenuUtils.isValueReference(action)) {
127                             mb = context.getApplication().createMethodBinding(action, null);
128                         }
129                         else {
130                             mb = new SimpleActionMethodBinding(action);
131                         }
132                         ((HtmlCommandJSCookMenu) component).setAction(mb);
133                     }
134                     else if (action.startsWith("L]")) {
135                         action = action.substring(2, action.length());
136                         String value = null;
137                         int idx = action.indexOf(";");
138                         if (idx > 0 && idx < action.length() - 1) {
139                             value = action.substring(idx + 1, action.length());
140                             action = action.substring(0, idx);
141                             ((HtmlCommandJSCookMenu) component).setValue(value);
142                         }
143                         else if (idx == action.length() - 1)
144                         {
145                             value = null; //No Value found, so set it to null as expected
146                             action = action.substring(0, idx);
147                         }
148                         MethodBinding mb;
149                         if (NavigationMenuUtils.isValueReference(action)) {
150                             mb = context.getApplication().createMethodBinding(action, ACTION_LISTENER_ARGS);
151                             ((HtmlCommandJSCookMenu) component).setActionListener(mb);
152                             if (value != null)
153                                 ((HtmlCommandJSCookMenu) component).setValue(value);
154                         }
155                     }
156                 }
157             }
158             component.queueEvent(new ActionEvent(component));
159         }
160     }
161 
162     private String decodeValueBinding(String actionParam, FacesContext context) {
163         int idx = actionParam.indexOf(";#{");
164         if (idx == -1) {
165             return actionParam;
166         }
167 
168         String newActionParam = actionParam.substring(0, idx);
169         String vbParam = actionParam.substring(idx + 1);
170 
171         idx = vbParam.indexOf('=');
172         if (idx == -1) {
173             return newActionParam;
174         }
175         String vbExpressionString = vbParam.substring(0, idx);
176         String vbValue = vbParam.substring(idx + 1);
177 
178         ValueBinding vb =
179             context.getApplication().createValueBinding(vbExpressionString);
180         vb.setValue(context, vbValue);
181 
182         return newActionParam;
183     }
184 
185     public boolean getRendersChildren() {
186         return true;
187     }
188 
189     public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
190         RendererUtils.checkParamValidity(context, component, HtmlCommandJSCookMenu.class);
191 
192         List list = NavigationMenuUtils.getNavigationMenuItemList(component);
193         if (list.size() > 0) {
194             FormInfo parentFormInfo = RendererUtils.findNestingForm(component, context);
195             ResponseWriter writer = context.getResponseWriter();
196 
197             if (parentFormInfo == null)
198                 throw new FacesException("jscook menu is not embedded in a form.");
199             String formName = parentFormInfo.getFormName();
200             List uiNavMenuItemList = component.getChildren();
201             /* todo: disabled for now. Check if dummy form stuff is still needed/desired
202                 if( formName == null ) {
203                 DummyFormUtils.setWriteDummyForm(context,true);
204                 DummyFormUtils.addDummyFormParameter(context,JSCOOK_ACTION_PARAM);
205 
206                 formName = DummyFormUtils.getDummyFormName();
207             }
208             else {*/
209             if (RendererUtils.isAdfOrTrinidadForm(parentFormInfo.getForm())) {
210                 // need to add hidden input, cause MyFaces form is missing hence will not render hidden inputs
211                 writer.write("<input type=\"hidden\" name=\"");
212                 writer.write(JSCOOK_ACTION_PARAM);
213                 writer.write("\" />");
214             }
215             else {
216                 HtmlFormRendererBase.addHiddenCommandParameter(context, parentFormInfo.getForm(), JSCOOK_ACTION_PARAM);
217             }
218 
219             //}
220 
221             String myId = getMenuId(context, component);
222 
223             writer.startElement(HTML.SCRIPT_ELEM, component);
224             writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
225             StringBuffer script = new StringBuffer();
226             script.append("var ").append(getMenuId(context, component)).append(" =\n[");
227             encodeNavigationMenuItems(context, script,
228                                       (NavigationMenuItem[]) list.toArray(new NavigationMenuItem[list.size()]),
229                                       uiNavMenuItemList,
230                                       myId, formName);
231 
232             script.append("];");
233             writer.writeText(script.toString(), null);
234             writer.endElement(HTML.SCRIPT_ELEM);
235         }
236     }
237 
238     private void encodeNavigationMenuItems(FacesContext context,
239                                            StringBuffer writer,
240                                            NavigationMenuItem[] items,
241                                            List uiNavMenuItemList,
242                                            String menuId, String formName)
243         throws IOException {
244         for (int i = 0; i < items.length; i++) {
245             NavigationMenuItem item = items[i];
246             Object tempObj = null;
247             UINavigationMenuItem uiNavMenuItem = null;
248             if (i < uiNavMenuItemList.size()) {
249                 tempObj = uiNavMenuItemList.get(i);
250             }
251             if (tempObj != null) {
252                 if (tempObj instanceof UINavigationMenuItem) {
253                     uiNavMenuItem = (UINavigationMenuItem) tempObj;
254                 }
255             }
256 
257             if (! item.isRendered()) {
258                 continue;
259             }
260 
261             if (i > 0) {
262                 writer.append(",\n");
263             }
264 
265             if (item.isSplit()) {
266                 writer.append("_cmSplit,");
267 
268                 if (item.getLabel().equals("0")) {
269                     continue;
270                 }
271             }
272 
273             writer.append("[");
274             if (item.getIcon() != null) {
275                 String iconSrc = context.getApplication().getViewHandler().getResourceURL(context, item.getIcon());
276                 writer.append("'<img src=\"");
277                 writer.append(context.getExternalContext().encodeResourceURL(iconSrc));
278                 writer.append("\"/>'");
279             }
280             else {
281                 writer.append("null");
282             }
283             writer.append(", '");
284             if (item.getLabel() != null) {
285                 writer.append(getString(context, item.getLabel()));
286             }
287             writer.append("', ");
288             StringBuffer actionStr = new StringBuffer();
289             if ((item.getAction() != null || item.getActionListener() != null) && ! item.isDisabled()) {
290                 actionStr.append("'");
291                 actionStr.append(menuId);
292                 if (item.getActionListener() != null) {
293                     actionStr.append(":L]");
294                     actionStr.append(item.getActionListener());
295                     if (uiNavMenuItem != null && uiNavMenuItem.getItemValue() != null) {
296                         actionStr.append(';');
297                         actionStr.append(getString(context, uiNavMenuItem.getItemValue()));
298                     }
299                     else if (item.getValue() != null) {
300                         actionStr.append(';');
301                         actionStr.append(getString(context, item.getValue()));
302                     }
303                 }
304                 if (item.getAction() != null) {
305                     actionStr.append(":A]");
306                     actionStr.append(item.getAction());
307                     if (uiNavMenuItem != null) {
308                         encodeValueBinding(actionStr, uiNavMenuItem, item);
309                     }
310                 }
311                 actionStr.append("'");
312                 writer.append(actionStr.toString());
313             }
314             else {
315                 writer.append("null");
316             }
317             writer.append(", '");
318             // Change here to allow the use of non dummy form.
319             writer.append(formName);
320             writer.append("', null");
321 
322             if (item.isRendered() && ! item.isDisabled()) {
323                 // render children only if parent is visible/enabled
324                 NavigationMenuItem[] menuItems = item.getNavigationMenuItems();
325                 if (menuItems != null && menuItems.length > 0) {
326                     writer.append(",");
327                     if (uiNavMenuItem != null) {
328                         encodeNavigationMenuItems(context, writer, menuItems,
329                                                   uiNavMenuItem.getChildren(), menuId, formName);
330                     }
331                     else {
332                         encodeNavigationMenuItems(context, writer, menuItems,
333                                                   new ArrayList(1), menuId, formName);
334                     }
335                 }
336             }
337             writer.append("]");
338         }
339     }
340 
341     private String getString(FacesContext facesContext, Object value) {
342         String str = "";
343 
344         if (value != null) {
345             str = value.toString();
346         }
347 
348         if (NavigationMenuUtils.isValueReference(str)) {
349             value = facesContext.getApplication().createValueBinding(str).getValue(facesContext);
350 
351             if (value != null) {
352                 str = value.toString();
353             }
354             else {
355                 str = "";
356             }
357         }
358 
359         return JavascriptUtils.encodeString(str);
360     }
361 
362     private void encodeValueBinding(StringBuffer writer, UINavigationMenuItem uiNavMenuItem,
363                                     NavigationMenuItem item) {
364         ValueBinding vb = uiNavMenuItem.getValueBinding("NavMenuItemValue");
365         if (vb == null) {
366             return;
367         }
368         String vbExpression = vb.getExpressionString();
369         if (vbExpression == null) {
370             return;
371         }
372         Object tempObj = item.getValue();
373         if (tempObj == null) {
374             return;
375         }
376 
377         writer.append(";");
378         writer.append(vbExpression);
379         writer.append("=");
380         writer.append(tempObj.toString());
381     }
382 
383     public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
384 
385         HtmlCommandJSCookMenu menu = (HtmlCommandJSCookMenu) component;
386         String theme = menu.getTheme();
387         if (theme == null) {
388             // should never happen; theme is a required attribute in the jsp tag definition
389             throw new IllegalArgumentException("theme name is mandatory for a jscookmenu.");
390         }
391 
392         addResourcesToHeader(theme, menu, context);
393 
394     }
395 
396     public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
397         RendererUtils.checkParamValidity(context, component, HtmlCommandJSCookMenu.class);
398         HtmlCommandJSCookMenu menu = (HtmlCommandJSCookMenu) component;
399         String theme = menu.getTheme();
400 
401 
402         ResponseWriter writer = context.getResponseWriter();
403 
404         String menuId = getMenuId(context, component);
405 
406         writer.write("<div id=\"");
407         writer.write(menuId);
408         writer.write("\"></div>\n");
409         writer.startElement(HTML.SCRIPT_ELEM, menu);
410         writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
411 
412         StringBuffer buf = new StringBuffer();
413         buf.append("\tif(window.cmDraw!=undefined) { cmDraw ('").
414             append(menuId).
415             append("', ").
416             append(menuId).
417             append(", '").
418             append(menu.getLayout()).
419             append("', cm").
420             append(theme).
421             append(", '").
422             append(theme).
423             append("');}");
424 
425         writer.writeText(buf.toString(), null);
426         writer.endElement(HTML.SCRIPT_ELEM);
427     }
428 
429     private void addResourcesToHeaderWithJSF2ResourceAPI(String themeName, HtmlCommandJSCookMenu menu, FacesContext context) {
430         String javascriptLocation = (String) menu.getAttributes().get(JSFAttr.JAVASCRIPT_LOCATION);
431         String imageLocation = (String) menu.getAttributes().get(JSFAttr.IMAGE_LOCATION);
432         String styleLocation = (String) menu.getAttributes().get(JSFAttr.STYLE_LOCATION);
433         String javascriptLibrary = (String) menu.getAttributes().get(LibraryLocationAware.JAVASCRIPT_LIBRARY_ATTR);
434         String imageLibrary = (String) menu.getAttributes().get(LibraryLocationAware.IMAGE_LIBRARY_ATTR);
435         String styleLibrary = (String) menu.getAttributes().get(LibraryLocationAware.STYLE_LIBRARY_ATTR);
436 
437         //AddResource addResource = AddResourceFactory.getInstance(context);
438 
439         //if (javascriptLocation != null) {
440         //    addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, javascriptLocation + "/" + JSCOOK_MENU_SCRIPT);
441         //    addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, javascriptLocation + "/" + JSCOOK_EFFECT_SCRIPT);            
442         //    addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, javascriptLocation + "/" + MYFACES_HACK_SCRIPT);
443         //}
444         //else 
445         if (javascriptLocation == null)
446         {
447             if (javascriptLibrary == null)
448             {
449                 //addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, HtmlJSCookMenuRenderer.class, JSCOOK_MENU_SCRIPT);
450                 TomahawkResourceUtils.addOutputScriptResource(context, "oam.custom.navmenu.jscookmenu", JSCOOK_MENU_SCRIPT);
451                 //addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, HtmlJSCookMenuRenderer.class, JSCOOK_EFFECT_SCRIPT);
452                 TomahawkResourceUtils.addOutputScriptResource(context, "oam.custom.navmenu.jscookmenu", JSCOOK_EFFECT_SCRIPT);
453                 //addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, HtmlJSCookMenuRenderer.class, MYFACES_HACK_SCRIPT);
454                 TomahawkResourceUtils.addOutputScriptResource(context, "oam.custom.navmenu.jscookmenu", MYFACES_HACK_SCRIPT);
455             }
456             else
457             {
458                 TomahawkResourceUtils.addOutputScriptResource(context, javascriptLibrary, JSCOOK_MENU_SCRIPT);
459                 TomahawkResourceUtils.addOutputScriptResource(context, javascriptLibrary, JSCOOK_EFFECT_SCRIPT);
460                 TomahawkResourceUtils.addOutputScriptResource(context, javascriptLibrary, MYFACES_HACK_SCRIPT);
461             }
462         }
463 
464         addThemeSpecificResourcesWithJSF2ResourceAPI(themeName, styleLocation, javascriptLocation, imageLocation, 
465                 styleLibrary, javascriptLibrary, imageLibrary, context);
466     }
467     
468     private void addResourcesToHeader(String themeName, HtmlCommandJSCookMenu menu, FacesContext context) {
469         String javascriptLocation = (String) menu.getAttributes().get(JSFAttr.JAVASCRIPT_LOCATION);
470         String imageLocation = (String) menu.getAttributes().get(JSFAttr.IMAGE_LOCATION);
471         String styleLocation = (String) menu.getAttributes().get(JSFAttr.STYLE_LOCATION);
472         String javascriptLibrary = (String) menu.getAttributes().get(LibraryLocationAware.JAVASCRIPT_LIBRARY_ATTR);
473         String imageLibrary = (String) menu.getAttributes().get(LibraryLocationAware.IMAGE_LIBRARY_ATTR);
474         String styleLibrary = (String) menu.getAttributes().get(LibraryLocationAware.STYLE_LIBRARY_ATTR);
475 
476         AddResource addResource = AddResourceFactory.getInstance(context);
477 
478         if (javascriptLocation != null) {
479             addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, javascriptLocation + "/" + JSCOOK_MENU_SCRIPT);
480             addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, javascriptLocation + "/" + JSCOOK_EFFECT_SCRIPT);            
481             addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, javascriptLocation + "/" + MYFACES_HACK_SCRIPT);
482         }
483 
484         addThemeSpecificResources(themeName, styleLocation, javascriptLocation, imageLocation, 
485                 styleLibrary, javascriptLibrary, imageLibrary, context);
486     }
487 
488     /**
489      * A theme for a menu requires a number of external files; this method
490      * outputs those into the page head section.
491      *
492      * @param themeName          is the name of the theme for this menu. It is never
493      *                           null. It may match one of the built-in theme names or may be a custom
494      *                           theme defined by the application.
495      * @param styleLocation      is the URL of a directory containing a
496      *                           "theme.css" file. A stylesheet link tag will be inserted into
497      *                           the page header referencing that file. If null then if the
498      *                           themeName is a built-in one then a reference to the appropriate
499      *                           built-in stylesheet is generated (requires the ExtensionsFilter).
500      *                           If null and a custom theme is used then no stylesheet link will be
501      *                           generated here.
502      * @param javascriptLocation is the URL of a directory containing a
503      *                           "theme.js" file. A script tag will be inserted into the page header
504      *                           referencing that file. If null then if the themeName is a built-in
505      *                           one then a reference to the built-in stylesheet is generated (requires
506      *                           the ExtensionsFilter). If null and a custom theme is used then no
507      *                           stylesheet link will be generated here.
508      * @param imageLocation      is the URL of a directory containing files
509      *                           (esp. image files) used by the theme.js file to define the menu
510      *                           theme. A javascript variable of name "my{themeName}Base" is
511      *                           generated in the page header containing this URL, so that the
512      *                           theme.js script can locate the files. If null then if the themeName
513      *                           is a built-in one then the URL to the appropriate resource directory
514      *                           is generated (requires the ExtensionsFilter). If null and a custom
515      *                           theme is used then no javascript variable will be generated here.
516      * @param context            is the current faces context.
517      */
518     private void addThemeSpecificResourcesWithJSF2ResourceAPI(String themeName, String styleLocation,
519                                            String javascriptLocation, String imageLocation,
520                                            String styleLibrary, String javascriptLibrary,
521                                            String imageLibrary, FacesContext context) {
522         String themeLocation = (String) builtInThemes.get(themeName);
523         if (themeLocation == null) {
524             log.debug("Unknown theme name '" + themeName + "' specified.");
525         }
526 
527         //AddResource addResource = AddResourceFactory.getInstance(context);
528 
529         if ((imageLocation != null) || (themeLocation != null)) {
530             // Generate a javascript variable containing a reference to the
531             // directory containing theme image files, for use by the theme
532             // javascript file. If neither of these is defined (ie a custom
533             // theme was specified but no imageLocation) then presumably the
534             // theme.js file uses some other mechanism to determine where
535             // its image files are.
536             StringBuffer buf = new StringBuffer();
537             buf.append("var my");
538             buf.append(themeName);
539             buf.append("Base='");
540             ExternalContext externalContext = context.getExternalContext();
541             //if (imageLocation != null) {
542             //    buf.append(externalContext.encodeResourceURL(addResource.getResourceUri(context,
543             //                                                                            imageLocation + "/" + themeName)));
544             //    buf.append("';");
545             //    addResource.addInlineScriptAtPosition(context, AddResource.HEADER_BEGIN, buf.toString());
546             //}
547             //else 
548             if (imageLocation == null)
549             {
550                 //buf.append(externalContext.encodeResourceURL(addResource.getResourceUri(context,
551                 //        HtmlJSCookMenuRenderer.class, themeLocation)));
552                 Resource resource = context.getApplication().getResourceHandler().
553                     createResource(";j","oam.custom.navmenu.jscookmenu."+themeName);
554                 buf.append(resource.getRequestPath());
555                 buf.append("';");
556                 TomahawkResourceUtils.addInlineOutputScriptResource(context, TomahawkResourceUtils.HEAD_LOCATION, buf.toString());
557             }
558         }
559         else
560         {
561             if ((imageLibrary != null))
562             {
563                 StringBuffer buf = new StringBuffer();
564                 buf.append("var my");
565                 buf.append(themeName);
566                 buf.append("Base='");
567                 Resource resource = context.getApplication().getResourceHandler().
568                     createResource(";j",imageLibrary+'.'+themeName);
569                 buf.append(resource.getRequestPath());
570                 buf.append("';");
571                 //addResource.addInlineScriptAtPosition(context, AddResource.HEADER_BEGIN, buf.toString());
572                 TomahawkResourceUtils.addInlineOutputScriptResource(context, TomahawkResourceUtils.HEAD_LOCATION, buf.toString());
573             }
574         }
575 
576         if ((javascriptLocation != null) || (themeLocation != null)) {
577             // Generate a <script> tag in the page header pointing to the
578             // theme.js file for this theme. If neither of these is defined
579             // then presumably the theme.js file is referenced by a <script>
580             // tag hard-wired into the page or inserted via some other means.
581             //if (javascriptLocation != null) {
582                 // For now, assume that if the user specified a location for a custom
583                 // version of the jscookMenu.js file then the theme.js file can be found
584                 // in the same location.
585                 //addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, javascriptLocation + "/" + themeName
586                 //    + "/theme.js");
587             //}
588             //else
589             if (javascriptLocation == null)
590             {
591                 // Using a built-in theme, so we know where the theme.js file is.
592                 //addResource.addJavaScriptAtPosition(context, AddResource.HEADER_BEGIN, HtmlJSCookMenuRenderer.class, themeName
593                 //    + "/theme.js");
594                 TomahawkResourceUtils.addOutputScriptResource(context, "oam.custom.navmenu.jscookmenu."+themeName, "theme.js");
595             }
596         }
597         else
598         {
599             if ((javascriptLibrary != null))
600             {
601                 TomahawkResourceUtils.addOutputScriptResource(context, javascriptLibrary+'.'+themeName, "theme.js");
602             }
603         }
604 
605         if ((styleLocation != null) || (themeLocation != null)) {
606             // Generate a <link type="text/css"> tag in the page header pointing to
607             // the theme stylesheet. If neither of these is defined then presumably
608             // the stylesheet is referenced by a <link> tag hard-wired into the page
609             // or inserted via some other means.
610             //if (styleLocation != null) {
611             //    addResource.addStyleSheet(context, AddResource.HEADER_BEGIN, styleLocation + "/" + themeName + "/theme.css");
612             //}
613             //else
614             if (styleLocation == null)
615             {
616                 //addResource.addStyleSheet(context, AddResource.HEADER_BEGIN, HtmlJSCookMenuRenderer.class, themeName
617                 //    + "/theme.css");
618                 TomahawkResourceUtils.addOutputStylesheetResource(context, "oam.custom.navmenu.jscookmenu."+themeName, "theme.css");
619             }
620         }
621         else
622         {
623             if ((styleLibrary != null))
624             {
625                 TomahawkResourceUtils.addOutputStylesheetResource(context, styleLibrary+'.'+themeName, "theme.css");
626             }
627         }
628     }
629     
630     private void addThemeSpecificResources(String themeName,
631             String styleLocation, String javascriptLocation,
632             String imageLocation, String styleLibrary,
633             String javascriptLibrary, String imageLibrary, FacesContext context)
634     {
635         String themeLocation = (String) builtInThemes.get(themeName);
636         if (themeLocation == null)
637         {
638             log.debug("Unknown theme name '" + themeName + "' specified.");
639         }
640 
641         AddResource addResource = AddResourceFactory.getInstance(context);
642 
643         if ((imageLocation != null) || (themeLocation != null))
644         {
645             // Generate a javascript variable containing a reference to the
646             // directory containing theme image files, for use by the theme
647             // javascript file. If neither of these is defined (ie a custom
648             // theme was specified but no imageLocation) then presumably the
649             // theme.js file uses some other mechanism to determine where
650             // its image files are.
651             StringBuffer buf = new StringBuffer();
652             buf.append("var my");
653             buf.append(themeName);
654             buf.append("Base='");
655             ExternalContext externalContext = context.getExternalContext();
656             if (imageLocation != null)
657             {
658                 buf.append(externalContext.encodeResourceURL(addResource
659                         .getResourceUri(context, imageLocation + "/"
660                                 + themeName)));
661                 buf.append("';");
662                 addResource.addInlineScriptAtPosition(context,
663                         AddResource.HEADER_BEGIN, buf.toString());
664             }
665         }
666 
667         if ((javascriptLocation != null) || (themeLocation != null))
668         {
669             // Generate a <script> tag in the page header pointing to the
670             // theme.js file for this theme. If neither of these is defined
671             // then presumably the theme.js file is referenced by a <script>
672             // tag hard-wired into the page or inserted via some other means.
673             if (javascriptLocation != null)
674             {
675                 // For now, assume that if the user specified a location for a custom
676                 // version of the jscookMenu.js file then the theme.js file can be found
677                 // in the same location.
678                 addResource.addJavaScriptAtPosition(context,
679                         AddResource.HEADER_BEGIN, javascriptLocation + "/"
680                                 + themeName + "/theme.js");
681             }
682         }
683 
684         if ((styleLocation != null) || (themeLocation != null))
685         {
686             // Generate a <link type="text/css"> tag in the page header pointing to
687             // the theme stylesheet. If neither of these is defined then presumably
688             // the stylesheet is referenced by a <link> tag hard-wired into the page
689             // or inserted via some other means.
690             if (styleLocation != null)
691             {
692                 addResource.addStyleSheet(context, AddResource.HEADER_BEGIN,
693                         styleLocation + "/" + themeName + "/theme.css");
694             }
695         }
696     }
697 
698     /**
699      * Fetch the very last part of the menu id.
700      *
701      * @param context
702      * @param component
703      * @return String id of the menu
704      */
705     private String getMenuId(FacesContext context, UIComponent component) {
706         String menuId = component.getClientId(context).replaceAll(":", "_") + "_menu";
707         while (menuId.startsWith("_")) {
708             menuId = menuId.substring(1);
709         }
710         return menuId;
711     }
712 }