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.fisheye;
20  
21  import org.apache.commons.collections.map.HashedMap;
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.myfaces.custom.dojo.DojoUtils;
25  import org.apache.myfaces.custom.navmenu.UINavigationMenuItem;
26  import org.apache.myfaces.renderkit.html.ext.HtmlLinkRenderer;
27  import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
28  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
29  import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
30  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils;
31  import org.apache.myfaces.shared_tomahawk.renderkit.html.util.FormInfo;
32  
33  import javax.faces.component.UIComponent;
34  import javax.faces.context.FacesContext;
35  import javax.faces.context.ResponseWriter;
36  import javax.faces.event.ActionEvent;
37  import javax.servlet.http.HttpServletRequest;
38  import java.io.IOException;
39  import java.util.*;
40  
41  /**
42   * Renderer for the FishEyeList component
43   * 
44   * @JSFRenderer
45   *   renderKitId = "HTML_BASIC" 
46   *   family = "javax.faces.Data"
47   *   type = "org.apache.myfaces.FishEyeList"
48   * @JSFRenderer
49   *   renderKitId = "HTML_BASIC" 
50   *   family = "javax.faces.Command"
51   *   type = "org.apache.myfaces.FishEyeCommandLink"
52   *
53   * @author Jurgen Lust (latest modification by $Author$)
54   * @version $Revision$ $Date$
55   */
56  public class HtmlFishEyeNavigationMenuRenderer extends HtmlLinkRenderer {
57  
58      private static final String ON_CLICK_ATTR             = "onClick";
59      private static final String DOJO_COMPONENT_TYPE       = "ScrollableFisheyeList";
60      private static final String DOJO_ITEM_TYPE            = "ScrollableFisheyeListItem";
61      public static final String  ATTACH_EDGE_ATTR          = "attachEdge";
62      public static final String  CAPTION_ATTR              = "caption";
63      public static final String  EFFECT_UNITS_ATTR         = "effectUnits";
64      public static final String  ICON_SRC_ATTR             = "iconSrc";
65      public static final String  ITEM_HEIGHT_ATTR          = "itemHeight";
66      public static final String  ITEM_MAX_HEIGHT_ATTR      = "itemMaxHeight";
67      public static final String  ITEM_MAX_WIDTH_ATTR       = "itemMaxWidth";
68      public static final String  ITEM_PADDING_ATTR         = "itemPadding";
69      public static final String  ITEM_WIDTH_ATTR           = "itemWidth";
70      public static final String  LABEL_EDGE_ATTR           = "labelEdge";
71      public static final String  ORIENTATION_ATTR          = "orientation";
72      public static final String  CONSERVATIVE_TRIGGER_ATTR = "conservativeTrigger";
73      public static final String  RENDERER_TYPE             = "org.apache.myfaces.FishEyeList";
74  
75      private Log log = LogFactory.getLog(HtmlFishEyeNavigationMenuRenderer.class);
76      /**
77       * @see javax.faces.render.Renderer#decode(javax.faces.context.FacesContext,
78       *      javax.faces.component.UIComponent)
79       */
80      public void decode(FacesContext context, UIComponent component) {
81          FormInfo nestingForm = findNestingForm(component, context);
82          if (nestingForm != null) {
83              String fieldName = HtmlRendererUtils.getHiddenCommandLinkFieldName(nestingForm);
84              String reqValue = (String) context.getExternalContext().getRequestParameterMap().get(fieldName);
85              if (reqValue != null && reqValue.length() > 0) {
86                  if (component instanceof FishEyeCommandLink && reqValue.equals(component.getClientId(context))) {
87                      component.queueEvent(new ActionEvent(component));
88                  } else {
89                      // deprecated : the old UINavigationMenuItem way
90                      UIComponent source = context.getViewRoot().findComponent(reqValue);
91                      if (source instanceof UINavigationMenuItem) {
92                          source.queueEvent(new ActionEvent(source));
93                      }
94  
95                  }
96              }
97          }
98      }
99  
100     /**
101      * @see javax.faces.render.Renderer#encodeBegin(javax.faces.context.FacesContext,
102      *      javax.faces.component.UIComponent)
103      */
104     public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
105         if (component.isRendered()) {
106             HtmlFishEyeNavigationMenu fisheye = (HtmlFishEyeNavigationMenu) component;
107             ResponseWriter writer = context.getResponseWriter();
108             // initialize DOJO
109             String javascriptLocation = (String) component.getAttributes().get(JSFAttr.JAVASCRIPT_LOCATION);
110             DojoUtils.addMainInclude(context, component, javascriptLocation, DojoUtils.getDjConfigInstance(context));
111             DojoUtils.addRequire(context, component, "dojo.widget.myfaces.ScrollableFisheyeList");
112             DojoUtils.addRequire(context, component, "dojo.widget.html.myfaces.ScrollableFisheyeListItem");
113             DojoUtils.addRequire(context, component, "dojo.widget.Button");
114             String leftName = "";
115             if (fisheye.getVisibleWindow() != null) {
116                 writer.startElement(HTML.TABLE_ELEM, component);
117                 writer.startElement(HTML.TR_ELEM, component);
118                 writer.startElement(HTML.TD_ELEM, component);
119                 writer.writeAttribute(HTML.WIDTH_ATTR, "30", null);
120                 writer.startElement(HTML.DIV_ELEM, component);
121                 writer.writeAttribute(HTML.ID_ATTR, component.getClientId(context) + "_left", null);
122                 writer.write("<<");
123                 writer.endElement(HTML.DIV_ELEM);
124 
125                 Map paramMap = new TreeMap();
126                 paramMap.put("id", DojoUtils.calculateWidgetVarName(component.getClientId(context) + "_left"));
127                 leftName = DojoUtils.renderWidgetInitializationCode(writer, component, "Button", paramMap, component.getClientId(context) + "_left", true);
128                 // render the onclick
129                 writer.endElement(HTML.TD_ELEM);
130                 writer.startElement(HTML.TD_ELEM, component);
131             }
132 
133             writer.startElement(HTML.DIV_ELEM, fisheye);
134             writer.writeAttribute(HTML.ID_ATTR, component.getClientId(context), null);
135 
136             writer.endElement(HTML.DIV_ELEM);
137             String rightName = "";
138             if (fisheye.getVisibleWindow() != null) {
139                 writer.endElement(HTML.TD_ELEM);
140                 writer.startElement(HTML.TD_ELEM, component);
141                 writer.writeAttribute(HTML.WIDTH_ATTR, "30", null);
142                 writer.startElement(HTML.DIV_ELEM, component);
143                 writer.writeAttribute(HTML.ID_ATTR, component.getClientId(context) + "_right", null);
144                 writer.write(">>");
145                 writer.endElement(HTML.DIV_ELEM);
146                 Map paramMap = new TreeMap();
147                 paramMap.put("id", DojoUtils.calculateWidgetVarName(component.getClientId(context) + "_right"));
148                 rightName = DojoUtils.renderWidgetInitializationCode(writer, component, "Button", paramMap, component.getClientId(context) + "_right", true);
149 
150                 writer.endElement(HTML.TD_ELEM);
151                 writer.endElement(HTML.TR_ELEM);
152                 writer.endElement(HTML.TABLE_ELEM);
153             }
154 
155             Map paramMap = new HashedMap();
156 
157             if (fisheye.getVisibleWindow() != null) {
158                 paramMap.put("visibleWindow", fisheye.getVisibleWindow());
159             } else {
160                  HtmlFishEyeNavigationMenu menu = (HtmlFishEyeNavigationMenu) component;
161                  int visibleWindow = calculateVisbleWindow(component, menu);
162                  paramMap.put("visibleWindow",new Integer( visibleWindow)); //lets expand, the fisheye will shrink it as needed
163             }
164                 
165 
166             paramMap.put(ITEM_WIDTH_ATTR, fisheye.getItemWidth());
167             paramMap.put(ITEM_HEIGHT_ATTR, fisheye.getItemHeight());
168             paramMap.put(ITEM_MAX_WIDTH_ATTR, fisheye.getItemMaxWidth());
169             paramMap.put(ITEM_MAX_HEIGHT_ATTR, fisheye.getItemMaxHeight());
170             paramMap.put(ORIENTATION_ATTR, fisheye.getOrientation());
171             paramMap.put(EFFECT_UNITS_ATTR, fisheye.getEffectUnits());
172             paramMap.put(ITEM_PADDING_ATTR, fisheye.getItemPadding());
173             paramMap.put(ATTACH_EDGE_ATTR, fisheye.getAttachEdge());
174             paramMap.put(LABEL_EDGE_ATTR, fisheye.getLabelEdge());
175             paramMap.put(CONSERVATIVE_TRIGGER_ATTR, fisheye.getConservativeTrigger());
176 
177             DojoUtils.renderWidgetInitializationCode(context, component, DOJO_COMPONENT_TYPE, paramMap);
178 
179             if (fisheye.getVisibleWindow() != null) {
180                 writer.startElement(HTML.SCRIPT_ELEM, component);
181                 writer.writeAttribute(HTML.TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
182                 writer
183                         .write(leftName + ".onClick = function(e) {" + DojoUtils.calculateWidgetVarName(component.getClientId(context))
184                                 + ".onLeftScroll();};");
185                 writer.write(rightName + ".onClick = function(e) {" + DojoUtils.calculateWidgetVarName(component.getClientId(context))
186                         + ".onRightScroll();};");
187                 writer.endElement(HTML.SCRIPT_ELEM);
188             }
189 
190         }
191 
192     }
193 
194     private int calculateVisbleWindow(UIComponent component, HtmlFishEyeNavigationMenu menu) {
195         int visibleWindow = 0;
196          if (menu.getChildCount() == 1 && menu.getChildren().get(0) instanceof FishEyeCommandLink) {
197              visibleWindow = menu.getRowCount();
198          } else {
199              List children = component.getChildren();
200              
201              for (Iterator cit = children.iterator(); cit.hasNext();) {
202                  UIComponent child = (UIComponent) cit.next();
203                  if (!child.isRendered())
204                      continue;
205                  if (child instanceof UINavigationMenuItem) {
206                      visibleWindow += 1;
207                  }
208              }     
209          }
210         return visibleWindow;
211     }
212 
213     private Stack getChildsMenuStack(FacesContext context, UIComponent component) {
214         //Stack menuStack = (Stack) ((HttpServletRequest) context.getExternalContext().getRequest()).getAttribute(component.getClientId(context)
215         //        + "_FishEyeMenuAttr");
216         Stack menuStack = (Stack) context.getExternalContext().getRequestMap().get(component.getClientId(context)
217                 + "_FishEyeMenuAttr");
218         if (menuStack != null)
219             return menuStack;
220 
221         menuStack = new Stack();
222         //((HttpServletRequest) context.getExternalContext().getRequest()).setAttribute(component.getClientId(context) + "_FishEyeMenuAttr", menuStack);
223         context.getExternalContext().getRequestMap().put(
224                 component.getClientId(context) + "_FishEyeMenuAttr", menuStack);
225         
226         return menuStack;
227     }
228 
229     /**
230      * @see javax.faces.render.Renderer#encodeChildren(javax.faces.context.FacesContext,
231      *      javax.faces.component.UIComponent)
232      */
233     public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
234         ResponseWriter writer = context.getResponseWriter();
235         List children = component.getChildren();
236         Stack menuStack = getChildsMenuStack(context, component);
237         HtmlFishEyeNavigationMenu menu = (HtmlFishEyeNavigationMenu) component;
238         if (menu.getChildCount() == 1 && menu.getChildren().get(0) instanceof FishEyeCommandLink) {
239             FishEyeCommandLink link = (FishEyeCommandLink) menu.getChildren().get(0);
240             for (int i = 0; i < menu.getRowCount(); i++) {
241                 menu.setRowIndex(i);
242                 if(!menu.isRowAvailable()) {
243                     log.error("Model is not available. Rowindex = " + i);
244                     break;
245                 }
246                 renderMenuItem(context, writer, component, link, menuStack);
247             }
248         } else {
249             for (Iterator cit = children.iterator(); cit.hasNext();) {
250                 UIComponent child = (UIComponent) cit.next();
251                 if (!child.isRendered())
252                     continue;
253                 if (child instanceof UINavigationMenuItem) {
254                     renderMenuItem(context, writer, component, (UINavigationMenuItem) child, menuStack);
255                 }
256             }
257         }
258     }
259 
260     /**
261      * @see javax.faces.render.Renderer#encodeEnd(javax.faces.context.FacesContext,
262      *      javax.faces.component.UIComponent)
263      */
264     public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
265         if (component.isRendered()) {
266             ResponseWriter writer = context.getResponseWriter();
267             Stack menuStack = getChildsMenuStack(context, component);
268             String jsMenuVar = DojoUtils.calculateWidgetVarName(component.getClientId(context));
269 
270             writer.startElement(HTML.SCRIPT_ELEM, component);
271             writer.writeAttribute(HTML.TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
272             while (!menuStack.isEmpty()) {
273                 String item = (String) menuStack.pop();
274                 writer.write(jsMenuVar);
275                 writer.write(".addChild(");
276                 writer.write(item);
277                 writer.write(");\n");
278             }
279             writer.write(jsMenuVar + ".postCreate(['programmaticdone'],null);\n");
280             writer.endElement(HTML.SCRIPT_ELEM);
281         }
282     }
283 
284     /**
285      * @see javax.faces.render.Renderer#getRendersChildren()
286      */
287     public boolean getRendersChildren() {
288         // always render the menu items
289         return true;
290     }
291 
292     protected void renderMenuItem(FacesContext context, ResponseWriter writer, UIComponent menu, UIComponent item, Stack childsMenuStack)
293             throws IOException {
294         // find the enclosing form
295         FormInfo formInfo = findNestingForm(item, context);
296         String clientId = item.getClientId(context);
297         if (formInfo == null) {
298             throw new IllegalArgumentException("Component " + clientId + " must be embedded in an form");
299         }
300         UIComponent nestingForm = formInfo.getForm();
301         String formName = formInfo.getFormName();
302 
303         StringBuffer onClick = new StringBuffer();
304 
305         String jsForm = "document.forms['" + formName + "']";
306         if (RendererUtils.isAdfOrTrinidadForm(formInfo.getForm())) {
307             onClick.append("submitForm('");
308             onClick.append(formInfo.getForm().getClientId(context));
309             onClick.append("',1,{source:'");
310             onClick.append(clientId);
311             onClick.append("'});return false;");
312         } else {
313             // call the clear_<formName> method
314             onClick.append(HtmlRendererUtils.getClearHiddenCommandFormParamsFunctionName(formName)).append("();");
315 
316             //if (MyfacesConfig.getCurrentInstance(context.getExternalContext()).isAutoScroll()) {
317                 //HtmlRendererUtils.appendAutoScrollAssignment(onClick, formName);
318             //}
319 
320             // add id parameter for decode
321             String hiddenFieldName = HtmlRendererUtils.getHiddenCommandLinkFieldName(formInfo);
322             onClick.append(jsForm);
323             onClick.append(".elements['").append(hiddenFieldName).append("']");
324             onClick.append(".value='").append(clientId).append("';");
325             addHiddenCommandParameter(context, nestingForm, hiddenFieldName);
326         }
327         String target;
328         String caption;
329         String iconSrc;
330         if (item instanceof UINavigationMenuItem) {
331             target = ((UINavigationMenuItem)item).getTarget();
332             caption = ((UINavigationMenuItem)item).getItemLabel();
333             iconSrc = ((UINavigationMenuItem)item).getIcon();
334         } else if (item instanceof FishEyeCommandLink) {
335             target = ((FishEyeCommandLink)item).getTarget();
336             caption = ((FishEyeCommandLink)item).getCaption();
337             iconSrc = ((FishEyeCommandLink)item).getIconSrc();
338         } else {
339             throw new IllegalArgumentException("expected UINavigationMenuItem or FisheyCommandLink");
340         }
341 
342         // add the target window
343         if (target != null && target.trim().length() > 0) {
344             onClick.append(jsForm);
345             onClick.append(".target='");
346             onClick.append(target);
347             onClick.append("';");
348         }
349 
350         // onSubmit
351         onClick.append("if(").append(jsForm).append(".onsubmit){var result=").append(jsForm).append(
352                 ".onsubmit();  if( (typeof result == 'undefined') || result ) {" + jsForm + ".submit();}}else{");
353 
354         // submit
355         onClick.append(jsForm);
356         onClick.append(".submit();}return false;"); // return false, so that
357                                                     // browser does not handle
358                                                     // the click
359 
360         Map paramMap = new HashMap();
361         paramMap.put(CAPTION_ATTR, caption);
362         paramMap.put(ICON_SRC_ATTR, iconSrc);
363         paramMap.put(ON_CLICK_ATTR, new StringBuffer("function () {").append(onClick).append("}"));
364         // push the onclick as lambda and use a stringbuffer so that we do not
365         // get enclosing quotes
366         String menuItemId = DojoUtils.renderWidgetInitializationCode(writer, item, DOJO_ITEM_TYPE, paramMap, item.getClientId(context), false);
367         childsMenuStack.push(menuItemId);
368         // we have to revert the elements,
369         // hence a stack
370     }
371 
372     protected void writeAttribute(ResponseWriter writer, HtmlFishEyeNavigationMenu fisheye, String name, Object value) throws IOException {
373         if (name != null && value != null) {
374             writer.writeAttribute(name, value, null);
375         }
376     }
377 
378 }