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.renderkit.html.ext;
20  
21  import java.io.IOException;
22  import java.util.List;
23  import java.util.Set;
24  
25  import javax.faces.component.UIComponent;
26  import javax.faces.component.UISelectBoolean;
27  import javax.faces.component.UISelectMany;
28  import javax.faces.context.FacesContext;
29  import javax.faces.context.ResponseWriter;
30  import javax.faces.convert.Converter;
31  import javax.faces.convert.ConverterException;
32  import javax.faces.model.SelectItem;
33  import javax.faces.model.SelectItemGroup;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.myfaces.component.UserRoleUtils;
38  import org.apache.myfaces.component.html.ext.HtmlSelectManyCheckbox;
39  import org.apache.myfaces.custom.checkbox.HtmlCheckbox;
40  import org.apache.myfaces.shared_tomahawk.component.DisplayValueOnlyCapable;
41  import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
42  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
43  import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
44  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlCheckboxRendererBase;
45  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils;
46  
47  
48  /**
49   * @JSFRenderer
50   *   renderKitId = "HTML_BASIC"
51   *   family = "org.apache.myfaces.Checkbox"
52   *   type = "org.apache.myfaces.Checkbox"
53   * 
54   * @JSFRenderer
55   *   renderKitId = "HTML_BASIC"
56   *   family = "javax.faces.SelectBoolean"
57   *   type = "org.apache.myfaces.Checkbox"
58   *   
59   * @JSFRenderer
60   *   renderKitId = "HTML_BASIC"
61   *   family = "javax.faces.SelectMany"
62   *   type = "org.apache.myfaces.Checkbox"
63   * 
64   * @author Manfred Geiler (latest modification by $Author: jakobk $)
65   * @version $Revision: 955214 $ $Date: 2010-06-16 07:16:02 -0500 (Wed, 16 Jun 2010) $
66   */
67  public class HtmlCheckboxRenderer
68          extends HtmlCheckboxRendererBase
69  {
70      private static final Log log = LogFactory.getLog(HtmlCheckboxRenderer.class);
71  
72      private static final String PAGE_DIRECTION = "pageDirection";
73  
74      private static final String LINE_DIRECTION = "lineDirection";
75  
76      private static final String LAYOUT_SPREAD = "spread";
77  
78      public void encodeEnd(FacesContext context, UIComponent component) throws IOException
79      {
80          if (context == null) throw new NullPointerException("context");
81          if (component == null) throw new NullPointerException("component");
82  
83          if (component instanceof HtmlCheckbox)
84          {
85              renderSingleCheckbox(context, (HtmlCheckbox)component);
86          }
87          else if (component instanceof DisplayValueOnlyCapable && HtmlRendererUtils.isDisplayValueOnly(component))
88          {
89              HtmlRendererUtils.renderDisplayValueOnlyForSelects(context, component, true);
90          }
91          else if (component instanceof UISelectMany)
92          {
93              String layout = getLayout((UISelectMany)component);
94              if (layout != null && layout.equals(LAYOUT_SPREAD))
95              {
96                  return; //checkbox inputs are rendered by spread checkbox components
97              }
98              else
99              {
100                 super.encodeEnd(context, component);
101             }
102         }
103         else if(component instanceof UISelectBoolean)
104         {
105             super.encodeEnd(context,component);
106         }
107         else
108         {
109             throw new IllegalArgumentException("Unsupported component class " + component.getClass().getName());
110         }
111     }
112 
113     @Override
114     public void renderCheckboxList(FacesContext facesContext,
115             UISelectMany selectMany) throws IOException
116     {
117         final String layout = getLayout(selectMany);
118         if (layout != null)
119         {
120             Converter converter = getConverter(facesContext, selectMany);
121             if (layout.equals(PAGE_DIRECTION))
122             {
123                 renderCheckboxListVertically(facesContext, selectMany,
124                         converter);
125             }
126             else if (layout.equals(LINE_DIRECTION))
127             {
128                 renderCheckboxListHorizontally(facesContext, selectMany,
129                         converter);
130             }
131             else
132             {
133                 log.error("Wrong layout attribute for component "
134                         + selectMany.getClientId(facesContext) + ": " + layout);
135             }
136         }
137     }
138 
139     protected void renderCheckboxListHorizontally(FacesContext facesContext,
140             UISelectMany selectMany, Converter converter) throws IOException
141     {
142         Set lookupSet = RendererUtils.getSubmittedValuesAsSet(facesContext,
143                 selectMany, converter, selectMany);
144         boolean useSubmittedValues = lookupSet != null;
145         if (!useSubmittedValues)
146         {
147             lookupSet = RendererUtils.getSelectedValuesAsSet(facesContext,
148                     selectMany, converter, selectMany);
149         }
150 
151         ResponseWriter writer = facesContext.getResponseWriter();
152         writer.startElement(HTML.TABLE_ELEM, selectMany);
153         HtmlRendererUtils.renderHTMLAttributes(writer, selectMany,
154                 HTML.SELECT_TABLE_PASSTHROUGH_ATTRIBUTES);
155         HtmlRendererUtils.writeIdIfNecessary(writer, selectMany, facesContext);
156 
157         final int numRows = getLayoutWidth(selectMany);
158         for (int i = 0; i < numRows; i++)
159         {
160             renderRowForHorizontal(facesContext, selectMany, converter,
161                     lookupSet, writer, numRows, i);
162         }
163 
164         writer.endElement(HTML.TABLE_ELEM);
165     }
166 
167     protected void renderRowForHorizontal(FacesContext facesContext,
168             UISelectMany selectMany, Converter converter, Set lookupSet,
169             ResponseWriter writer, int totalRows, int rowNum)
170             throws IOException
171     {
172 
173         writer.startElement(HTML.TR_ELEM, selectMany);
174         int colNum = 0;
175         List items = RendererUtils.getSelectItemList(selectMany);
176         for (int count = rowNum; count < items.size(); count++)
177         {
178             int mod = count % totalRows;
179             if (mod == rowNum)
180             {
181                 colNum++;
182                 SelectItem selectItem = (SelectItem) items.get(count);
183                 writer.startElement(HTML.TD_ELEM, selectMany);
184                 renderGroupOrItemCheckbox(facesContext, selectMany, selectItem,
185                         lookupSet != null, lookupSet, converter, false);
186                 writer.endElement(HTML.TD_ELEM);
187             }
188         }
189         int totalItems = items.size();
190         int totalCols = (totalItems / totalRows);
191         if (totalItems % totalRows != 0)
192         {
193             totalCols++;
194         }
195         if (colNum < totalCols)
196         {
197             writer.startElement(HTML.TD_ELEM, selectMany);
198             writer.endElement(HTML.TD_ELEM);
199         }
200         writer.endElement(HTML.TR_ELEM);
201     }
202 
203     protected void renderCheckboxListVertically(FacesContext facesContext,
204             UISelectMany selectMany, Converter converter) throws IOException
205     {
206 
207         Set lookupSet = RendererUtils.getSubmittedValuesAsSet(facesContext,
208                 selectMany, converter, selectMany);
209         boolean useSubmittedValues = lookupSet != null;
210         if (!useSubmittedValues)
211         {
212             lookupSet = RendererUtils.getSelectedValuesAsSet(facesContext,
213                     selectMany, converter, selectMany);
214         }
215 
216         ResponseWriter writer = facesContext.getResponseWriter();
217         writer.startElement(HTML.TABLE_ELEM, selectMany);
218         HtmlRendererUtils.renderHTMLAttributes(writer, selectMany,
219                 HTML.SELECT_TABLE_PASSTHROUGH_ATTRIBUTES);
220         HtmlRendererUtils.writeIdIfNecessary(writer, selectMany, facesContext);
221 
222         List items = RendererUtils.getSelectItemList(selectMany);
223         int totalItems = items.size();
224         for (int count = 0; count < totalItems; count++)
225         {
226             writer.startElement(HTML.TR_ELEM, selectMany);
227             final int numCols = getLayoutWidth(selectMany);
228             for (int i = 0; i < numCols; i++)
229             {
230                 writer.startElement(HTML.TD_ELEM, selectMany);
231                 if (count < totalItems)
232                 {
233                     SelectItem selectItem = (SelectItem) items.get(count);
234                     renderGroupOrItemCheckbox(facesContext, selectMany,
235                             selectItem, lookupSet != null, lookupSet,
236                             converter, true);
237                 }
238                 writer.endElement(HTML.TD_ELEM);
239                 if (i < numCols - 1)
240                 {
241                     count += 1;
242                 }
243             }
244             writer.endElement(HTML.TR_ELEM);
245         }
246         writer.endElement(HTML.TABLE_ELEM);
247     }
248 
249     protected void renderGroupOrItemCheckbox(FacesContext facesContext,
250             UIComponent uiComponent, SelectItem selectItem,
251             boolean useSubmittedValues, Set lookupSet,
252             Converter converter, boolean pageDirectionLayout) throws IOException {
253         ResponseWriter writer = facesContext.getResponseWriter();
254         
255         boolean isSelectItemGroup = (selectItem instanceof SelectItemGroup);
256 
257         if (isSelectItemGroup)
258         {
259             SelectItemGroup selectItemGroup = (SelectItemGroup) selectItem;
260             renderCheckboxGroup(facesContext, uiComponent, selectItemGroup,
261                     useSubmittedValues, lookupSet, converter,
262                     pageDirectionLayout);
263         }
264         else
265         {
266             UISelectMany selectMany = (UISelectMany) uiComponent;
267             Object itemValue = selectItem.getValue(); // TODO : Check here for getSubmittedValue. Look at RendererUtils.getValue
268             String itemStrValue = getItemStringValue(facesContext, selectMany,
269                     converter, itemValue);
270 
271             boolean checked = (useSubmittedValues && lookupSet
272                     .contains(itemStrValue))
273                     || (!useSubmittedValues && lookupSet.contains(itemValue));
274 
275             boolean disabled = selectItem.isDisabled();
276 
277             writer.startElement(HTML.LABEL_ELEM, selectMany);
278             renderLabelClassIfNecessary(facesContext, selectMany, disabled);
279             renderCheckbox(facesContext, selectMany, itemStrValue, disabled, checked, false,0);
280             writer.write(HTML.NBSP_ENTITY);
281             if(selectItem.isEscape())
282             {
283                 writer.writeText(selectItem.getLabel(), null);
284             }
285             else
286             {
287                 writer.write(selectItem.getLabel());
288             }
289             writer.endElement(HTML.LABEL_ELEM);
290         }
291     }
292 
293     protected void renderLabelClassIfNecessary(FacesContext facesContext,
294             UISelectMany selectMany, boolean disabled) throws IOException
295     {
296         String labelClass = null;
297         boolean componentDisabled = isDisabled(facesContext, selectMany);
298         if (componentDisabled || disabled)
299         {
300             labelClass = (String) selectMany.getAttributes().get(
301                     JSFAttr.DISABLED_CLASS_ATTR);
302         }
303         else
304         {
305             labelClass = (String) selectMany.getAttributes().get(
306                     JSFAttr.ENABLED_CLASS_ATTR);
307         }
308         if (labelClass != null)
309         {
310             ResponseWriter writer = facesContext.getResponseWriter();
311             writer.writeAttribute("class", labelClass, "labelClass");
312         }
313     }
314 
315     protected void renderCheckboxGroup(FacesContext facesContext,
316             UIComponent uiComponent, SelectItemGroup selectItemGroup,
317             boolean useSubmittedValues, Set lookupSet,
318             Converter converter, boolean pageDirectionLayout) throws IOException {
319         ResponseWriter writer = facesContext.getResponseWriter();
320         UISelectMany selectMany = (UISelectMany)uiComponent;
321         writer.startElement(HTML.TABLE_ELEM, selectMany);
322         if (pageDirectionLayout)
323             writer.startElement(HTML.TR_ELEM, selectMany);
324         writer.startElement(HTML.TD_ELEM, selectMany);
325         writer.write(selectItemGroup.getLabel());
326         writer.endElement(HTML.TD_ELEM);
327         
328         if (pageDirectionLayout) {
329             writer.endElement(HTML.TR_ELEM);
330             writer.startElement(HTML.TR_ELEM, selectMany);
331         }
332         writer.startElement(HTML.TD_ELEM, selectMany);
333         writer.startElement(HTML.TABLE_ELEM, selectMany);
334         writer.writeAttribute(HTML.BORDER_ATTR, "0", null);
335 
336         SelectItem[] selectItems = selectItemGroup.getSelectItems();
337         for (int i=0; i<selectItems.length; i++) {
338             renderGroupOrItemCheckbox(facesContext, selectMany, selectItems[i], useSubmittedValues, lookupSet, converter, pageDirectionLayout);
339         }
340         
341         writer.endElement(HTML.TABLE_ELEM);
342         writer.endElement(HTML.TD_ELEM);
343         if (pageDirectionLayout)
344             writer.endElement(HTML.TR_ELEM);
345         writer.endElement(HTML.TABLE_ELEM);
346     }
347 
348     /**
349      * Determines the layout setting.  Defaults to
350      * <code>lineDirection</code> if not specified.
351      * @param selectMany the component
352      * @return the layout
353      */
354     protected String getLayout(UISelectMany selectMany) {
355         String layout = super.getLayout(selectMany);
356         if (layout == null) {
357             layout = LINE_DIRECTION;
358         }
359         return layout;
360     }
361     /**
362      * Gets the layout width.
363      * Returns the default layout width of 1 if the layout width
364      * is not set or is less than 1.  
365      * @param selectMany the component
366      * @return the layout width
367      */
368     protected int getLayoutWidth(UISelectMany selectMany) {
369         String layoutWidthString = null;
370         if (selectMany instanceof HtmlSelectManyCheckbox) {
371             layoutWidthString = ((HtmlSelectManyCheckbox) selectMany).getLayoutWidth();
372         } else {
373             layoutWidthString = (String) selectMany.getAttributes().get(JSFAttr.LAYOUT_WIDTH_ATTR);
374         }
375         final int defaultLayoutWidth = 1;
376         int layoutWidth = defaultLayoutWidth;
377         try {
378             if (layoutWidthString != null && layoutWidthString.trim().length() > 0) {
379                 layoutWidth = Integer.parseInt(layoutWidthString);
380             }
381             if (layoutWidth < 1) {
382                 layoutWidth = defaultLayoutWidth;
383             }
384         } catch (Exception e) {
385             layoutWidth = defaultLayoutWidth;
386         }
387         return layoutWidth;
388     }
389 
390     protected void renderSingleCheckbox(FacesContext facesContext, HtmlCheckbox checkbox) throws IOException
391     {
392         String forAttr = checkbox.getFor();
393         if (forAttr == null)
394         {
395             throw new IllegalStateException("mandatory attribute 'for'");
396         }
397         int index = checkbox.getIndex();
398         if (index < 0)
399         {
400             throw new IllegalStateException("positive index must be given");
401         }
402 
403         UIComponent uiComponent = checkbox.findComponent(forAttr);
404         if (uiComponent == null)
405         {
406             throw new IllegalStateException("Could not find component '" + forAttr + "' (calling findComponent on component '" + checkbox.getClientId(facesContext) + "')");
407         }
408         if (!(uiComponent instanceof UISelectMany))
409         {
410             throw new IllegalStateException("UISelectMany expected");
411         }
412 
413         UISelectMany uiSelectMany = (UISelectMany)uiComponent;
414         Converter converter = getConverter(facesContext, uiSelectMany);
415         List selectItemList = RendererUtils.getSelectItemList(uiSelectMany);
416         if (index >= selectItemList.size())
417         {
418             throw new IndexOutOfBoundsException("index " + index + " >= " + selectItemList.size());
419         }
420 
421         SelectItem selectItem = (SelectItem)selectItemList.get(index);
422         Object itemValue = selectItem.getValue();
423         String itemStrValue = getItemStringValue(facesContext, uiSelectMany, converter, itemValue);
424 
425         //TODO: we must cache this Set!
426         Set lookupSet = RendererUtils.getSubmittedValuesAsSet(facesContext, uiComponent, converter, uiSelectMany);
427         
428         boolean useSubmittedValues = (lookupSet != null);
429         if (!useSubmittedValues)
430         {
431             lookupSet = RendererUtils.getSelectedValuesAsSet(facesContext, uiComponent, converter, uiSelectMany);
432         }
433 
434         ResponseWriter writer = facesContext.getResponseWriter();
435         
436         //renderCheckbox(facesContext,
437         //               uiSelectMany,
438         //               itemStrValue,
439         //               selectItem.getLabel(),
440         //               isDisabled(facesContext,uiSelectMany),
441         //               lookupSet.contains(itemStrValue), true);
442         
443         String itemId = renderCheckbox(facesContext,
444                 uiSelectMany,
445                 itemStrValue,
446                 isDisabled(facesContext,uiSelectMany),
447                 lookupSet.contains(itemStrValue), false, index);
448 
449         //Render the
450         // label element after the input
451         boolean componentDisabled = isDisabled(facesContext, uiSelectMany);
452         boolean itemDisabled = selectItem.isDisabled();
453         boolean disabled = (componentDisabled || itemDisabled);
454 
455         HtmlRendererUtils.renderLabel(writer, uiSelectMany, itemId, selectItem.getLabel(), disabled);
456         
457     }
458 
459 
460     protected boolean isDisabled(FacesContext facesContext, UIComponent uiComponent)
461     {
462         if (!UserRoleUtils.isEnabledOnUserRole(uiComponent))
463         {
464             return true;
465         }
466         else
467         {
468             return super.isDisabled(facesContext, uiComponent);
469         }
470     }
471 
472     public void decode(FacesContext facesContext, UIComponent uiComponent)
473     {
474         if (uiComponent instanceof HtmlCheckbox)
475         {
476             //nothing to decode
477         }
478         else
479         {
480             super.decode(facesContext, uiComponent);
481         }
482     }
483 
484     protected String getItemStringValue(FacesContext facesContext, UISelectMany selectMany, 
485             Converter converter, Object itemValue) {
486         String itemStrValue;
487         if (converter == null)
488         {
489             itemStrValue = itemValue.toString();
490         }
491         else
492         {
493             itemStrValue = converter.getAsString(facesContext, selectMany, itemValue);
494         }
495         return itemStrValue;
496     }
497 
498     /**
499      * Overrides HtmlCheckboxRendererBase to handle valueType attribute on UISelectMany.
500      */
501     @Override
502     public Object getConvertedValue(FacesContext facesContext,
503             UIComponent component, Object submittedValue)
504             throws ConverterException
505     {
506         RendererUtils.checkParamValidity(facesContext, component, null);
507         
508         if (component instanceof UISelectMany) 
509         {
510             // invoke getConvertedUISelectManyValue() with considerValueType = true
511             return RendererUtils.getConvertedUISelectManyValue(facesContext,
512                     (UISelectMany) component, submittedValue, true); 
513         } 
514         else 
515         {
516             // component is not a UISelectMany --> no change needed
517             return super.getConvertedValue(facesContext, component, submittedValue);
518         }
519     }
520     
521     /**
522      * Overrides HtmlCheckboxRendererBase to handle valueType attribute on UISelectMany.
523      */
524     @Override
525     protected Converter getConverter(FacesContext facesContext,
526             UIComponent component)
527     {
528         if (component instanceof UISelectMany)
529         {
530             // invoke findUISelectManyConverterFailsafe() with considerValueType = true
531             return HtmlRendererUtils.findUISelectManyConverterFailsafe(facesContext, 
532                     (UISelectMany) component, true);
533         }
534         else
535         {
536             // component is not a UISelectMany --> no change needed
537             return super.getConverter(facesContext, component);
538         }
539     }
540 
541 }