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.shared.renderkit.html;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.EnumSet;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.logging.Logger;
30  import javax.faces.FacesException;
31  
32  import javax.faces.component.UIComponent;
33  import javax.faces.component.UIInput;
34  import javax.faces.component.UISelectOne;
35  import javax.faces.component.behavior.ClientBehavior;
36  import javax.faces.component.behavior.ClientBehaviorHolder;
37  import javax.faces.component.html.HtmlSelectOneRadio;
38  import javax.faces.component.visit.VisitCallback;
39  import javax.faces.component.visit.VisitContext;
40  import javax.faces.component.visit.VisitHint;
41  import javax.faces.component.visit.VisitResult;
42  import javax.faces.context.FacesContext;
43  import javax.faces.context.ResponseWriter;
44  import javax.faces.convert.Converter;
45  import javax.faces.convert.ConverterException;
46  import javax.faces.model.SelectItem;
47  import javax.faces.model.SelectItemGroup;
48  
49  import org.apache.myfaces.shared.renderkit.JSFAttr;
50  import org.apache.myfaces.shared.renderkit.RendererUtils;
51  import org.apache.myfaces.shared.renderkit.html.util.FormInfo;
52  import org.apache.myfaces.shared.renderkit.html.util.ResourceUtils;
53  
54  public class HtmlRadioRendererBase
55          extends HtmlRenderer
56  {
57      private static final Logger log = Logger.getLogger(HtmlRadioRendererBase.class.getName());
58  
59      private static final String PAGE_DIRECTION = "pageDirection";
60      private static final String LINE_DIRECTION = "lineDirection";
61      
62      private static final Set<VisitHint> FIND_SELECT_LIST_HINTS = 
63              Collections.unmodifiableSet(EnumSet.of(VisitHint.SKIP_UNRENDERED, VisitHint.SKIP_ITERATION));
64  
65      private Map<String, UISelectOne> groupFirst = new HashMap<String, UISelectOne>();
66      
67      @Override
68      public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException
69      {
70          RendererUtils.checkParamValidity(facesContext, uiComponent, UISelectOne.class);
71  
72          UISelectOne selectOne = (UISelectOne)uiComponent;
73  
74          String layout = getLayout(selectOne);
75  
76          boolean pageDirectionLayout = false; // Defaults to LINE_DIRECTION
77          if (layout != null)
78          {
79              if (layout.equals(PAGE_DIRECTION))
80              {
81                  pageDirectionLayout = true;
82              }
83              else if (layout.equals(LINE_DIRECTION))
84              {
85                  pageDirectionLayout = false;
86              }
87              else
88              {
89                  log.severe("Wrong layout attribute for component " + 
90                          selectOne.getClientId(facesContext) + ": " + layout);
91              }
92          }
93  
94          ResponseWriter writer = facesContext.getResponseWriter();
95  
96          Map<String, List<ClientBehavior>> behaviors = null;
97          if (uiComponent instanceof ClientBehaviorHolder)
98          {
99              behaviors = ((ClientBehaviorHolder) uiComponent).getClientBehaviors();
100             if (!behaviors.isEmpty())
101             {
102                 ResourceUtils.renderDefaultJsfJsInlineIfNecessary(facesContext, writer);
103             }
104         }
105         
106         String group = selectOne instanceof HtmlSelectOneRadio ? ((HtmlSelectOneRadio) selectOne).getGroup() : null;
107         if (group != null && !group.isEmpty())
108         {
109             if (!groupFirst.containsKey(group)) 
110             {
111                 groupFirst.put(group, selectOne);
112             }
113 
114             List selectItemList = RendererUtils.getSelectItemList(selectOne, facesContext);
115             if (selectItemList != null && !selectItemList.isEmpty())
116             {
117                 Converter converter = HtmlRendererUtils.findUIOutputConverterFailSafe(facesContext, selectOne);
118                 Object currentValue = null;
119 
120                 // If the current selectOne radio contains a value expression, 
121                 // then get the current value using the current selectOne radio.
122                 // Otherwise get the current value using the first selectOne radio which
123                 // must contain a value expression.
124                 if (selectOne.getValueExpression("value") != null)
125                 {
126                     currentValue = RendererUtils.getStringFromSubmittedValueOrLocalValueReturnNull(
127                                 facesContext, selectOne);
128                 }
129                 else
130                 {
131                     currentValue = RendererUtils.getStringFromSubmittedValueOrLocalValueReturnNull(
132                                 facesContext, groupFirst.get(group));
133                 }
134                 SelectItem selectItem = (SelectItem) selectItemList.get(0);
135                 renderGroupOrItemRadio(facesContext, selectOne,
136                                                      selectItem, currentValue,
137                                                      converter, pageDirectionLayout, group, 0);
138             }
139             else
140             {
141                 // Deferred case: find real component with attached selectItems
142                 FormInfo formInfo = RendererUtils.findNestingForm(uiComponent, facesContext);
143                 GetSelectItemListCallback callback = new GetSelectItemListCallback(selectOne, group);
144                 formInfo.getForm().visitTree(
145                         VisitContext.createVisitContext(facesContext, null, FIND_SELECT_LIST_HINTS),
146                         callback);                
147                 renderGroupOrItemRadio(facesContext, selectOne, callback.getSelectItem(),
148                         callback.getCurrentValue(), callback.getConverter(), 
149                         pageDirectionLayout, group, callback.getIndex());
150             }
151         }
152         else
153         {
154             // Render as single component
155             writer.startElement(HTML.TABLE_ELEM, selectOne);
156             HtmlRendererUtils.renderHTMLAttributes(writer, selectOne,
157                                                    HTML.SELECT_TABLE_PASSTHROUGH_ATTRIBUTES);
158 
159             if (behaviors != null && !behaviors.isEmpty())
160             {
161                 writer.writeAttribute(HTML.ID_ATTR, selectOne.getClientId(facesContext), null);
162             }
163             else
164             {
165                 HtmlRendererUtils.writeIdIfNecessary(writer, selectOne, facesContext); 
166             }        
167 
168             if (!pageDirectionLayout)
169             {
170                 writer.startElement(HTML.TR_ELEM, null); // selectOne);
171             }
172 
173             List selectItemList = RendererUtils.getSelectItemList(selectOne, facesContext);
174             Converter converter = HtmlRendererUtils.findUIOutputConverterFailSafe(facesContext, selectOne);
175             Object currentValue = RendererUtils.getStringFromSubmittedValueOrLocalValueReturnNull(
176                         facesContext, selectOne);
177 
178             int itemNum = 0;
179 
180             for (int i = 0; i < selectItemList.size(); i++)
181             {
182                 SelectItem selectItem = (SelectItem) selectItemList.get(i);
183 
184                 itemNum = renderGroupOrItemRadio(facesContext, selectOne,
185                                                  selectItem, currentValue,
186                                                  converter, pageDirectionLayout, itemNum);
187             }
188 
189             if (!pageDirectionLayout)
190             {
191                 writer.endElement(HTML.TR_ELEM);
192             }
193             writer.endElement(HTML.TABLE_ELEM);            
194         }
195     }
196 
197 
198     protected String getLayout(UIComponent selectOne)
199     {
200         if (selectOne instanceof HtmlSelectOneRadio)
201         {
202             return ((HtmlSelectOneRadio)selectOne).getLayout();
203         }
204 
205         return (String)selectOne.getAttributes().get(JSFAttr.LAYOUT_ATTR);
206     }
207 
208 
209     protected String getStyleClass(UISelectOne selectOne)
210      {
211          if (selectOne instanceof HtmlSelectOneRadio)
212          {
213              return ((HtmlSelectOneRadio)selectOne).getStyleClass();
214          }
215 
216          return (String)selectOne.getAttributes().get(JSFAttr.STYLE_CLASS_ATTR);
217      }
218 
219     protected int renderGroupOrItemRadio(FacesContext facesContext,
220                                          UIComponent uiComponent, SelectItem selectItem,
221                                          Object currentValue,
222                                          Converter converter, boolean pageDirectionLayout,
223                                          Integer itemNum) throws IOException
224     {
225         return renderGroupOrItemRadio(facesContext, uiComponent, selectItem, 
226                 currentValue,converter, pageDirectionLayout, null, itemNum);
227     }
228 
229     /**
230      * Renders the given SelectItem(Group)
231      * @return the itemNum for the next item
232      */
233     protected int renderGroupOrItemRadio(FacesContext facesContext,
234                                          UIComponent uiComponent, SelectItem selectItem,
235                                          Object currentValue,
236                                          Converter converter, boolean pageDirectionLayout, String group,
237                                          Integer itemNum) throws IOException
238     {
239 
240         ResponseWriter writer = facesContext.getResponseWriter();
241 
242         boolean isSelectItemGroup = (selectItem instanceof SelectItemGroup);
243 
244         // TODO : Check here for getSubmittedValue. Look at RendererUtils.getValue
245         // this is useless object creation
246 //        Object itemValue = selectItem.getValue();
247 
248         UISelectOne selectOne = (UISelectOne)uiComponent;
249 
250         if (isSelectItemGroup) 
251         {
252             if (pageDirectionLayout)
253             {
254                 writer.startElement(HTML.TR_ELEM, null); // selectOne);
255             }
256 
257             writer.startElement(HTML.TD_ELEM, null); // selectOne);
258             if (selectItem.isEscape())
259             {
260                 writer.writeText(selectItem.getLabel(),HTML.LABEL_ATTR);
261             }
262             else
263             {
264                 writer.write(selectItem.getLabel());
265             }
266             writer.endElement(HTML.TD_ELEM);
267 
268             if (pageDirectionLayout)
269             {
270                 writer.endElement(HTML.TR_ELEM);
271                 writer.startElement(HTML.TR_ELEM, null); // selectOne);
272             }
273             writer.startElement(HTML.TD_ELEM, null); // selectOne);
274 
275             writer.startElement(HTML.TABLE_ELEM, null); // selectOne);
276             writer.writeAttribute(HTML.BORDER_ATTR, "0", null);
277             
278             if(!pageDirectionLayout)
279             {
280                 writer.startElement(HTML.TR_ELEM, null); // selectOne);
281             }
282 
283             SelectItemGroup selectItemGroup = (SelectItemGroup) selectItem;
284             SelectItem[] selectItems = selectItemGroup.getSelectItems();
285 
286             for (SelectItem groupSelectItem : selectItems)
287             { 
288                 itemNum = renderGroupOrItemRadio(facesContext, selectOne, groupSelectItem, currentValue, 
289                                                  converter, pageDirectionLayout, itemNum);
290             }
291 
292             if(!pageDirectionLayout)
293             {
294                 writer.endElement(HTML.TR_ELEM);
295             }
296             writer.endElement(HTML.TABLE_ELEM);
297             writer.endElement(HTML.TD_ELEM);
298 
299             if (pageDirectionLayout)
300             {
301                 writer.endElement(HTML.TR_ELEM);
302             }
303 
304         } 
305         else 
306         {
307             String itemStrValue = RendererUtils.getConvertedStringValue(
308                     facesContext, selectOne, converter, selectItem.getValue());
309             boolean itemChecked = (itemStrValue == null) ? 
310                     itemStrValue == currentValue : 
311                     "".equals(itemStrValue) ? 
312                             (currentValue == null || itemStrValue.equals(currentValue)) : 
313                             itemStrValue.equals(currentValue);
314             
315             // IF the hideNoSelectionOption attribute of the component is true
316             // AND this selectItem is the "no selection option"
317             // AND there are currently selected items
318             // AND this item (the "no selection option") is not selected
319             if (HtmlRendererUtils.isHideNoSelectionOption(uiComponent) && selectItem.isNoSelectionOption() 
320                     && currentValue != null && !"".equals(currentValue) && !itemChecked)
321             {
322                 // do not render this selectItem
323                 return itemNum;
324             }
325             
326             //writer.write("\t\t");
327             boolean renderGroupId = false;
328             if (group != null && !group.isEmpty())
329             {
330                 //no op
331                 renderGroupId = true;
332             }
333             else
334             {
335                 if (pageDirectionLayout)
336                 {
337                     writer.startElement(HTML.TR_ELEM, null); // selectOne);
338                 }
339                 writer.startElement(HTML.TD_ELEM, null); // selectOne);
340             }
341     
342             boolean itemDisabled = selectItem.isDisabled();
343     
344             String itemId = renderRadio(facesContext, selectOne, itemStrValue, itemDisabled, 
345                     itemChecked, renderGroupId, renderGroupId ? null : itemNum);
346     
347             // label element after the input
348             boolean componentDisabled = isDisabled(facesContext, selectOne);
349             boolean disabled = (componentDisabled || itemDisabled);
350     
351             HtmlRendererUtils.renderLabel(writer, selectOne,
352                     renderGroupId ? selectOne.getClientId(facesContext) : itemId,
353                     selectItem, disabled);
354 
355             if (group != null && group.length() > 0)
356             {
357                 //no op
358             }
359             else
360             {
361                 writer.endElement(HTML.TD_ELEM);
362                 if (pageDirectionLayout)
363                 {
364                     writer.endElement(HTML.TR_ELEM);
365                 }
366             }
367             
368             // we rendered one radio --> increment itemNum
369             itemNum++;
370         }
371         return itemNum;
372     }
373 
374     @Deprecated
375     protected void renderRadio(FacesContext facesContext,
376                                UIComponent uiComponent,
377                                String value,
378                                String label,
379                                boolean disabled,
380                                boolean checked, boolean renderId)
381             throws IOException
382     {
383         renderRadio(facesContext, (UIInput) uiComponent, value, disabled, checked, renderId, 0);
384     }
385 
386     /**
387      * Renders the input item
388      * @return the 'id' value of the rendered element
389      */
390     protected String renderRadio(FacesContext facesContext,
391                                UIInput uiComponent,
392                                String value,
393                                boolean disabled,
394                                boolean checked,
395                                boolean renderId,
396                                Integer itemNum)
397             throws IOException
398     {
399         String clientId = uiComponent.getClientId(facesContext);
400 
401         String itemId = (itemNum == null) ? null : clientId + 
402                 facesContext.getNamingContainerSeparatorChar() + itemNum;
403 
404         ResponseWriter writer = facesContext.getResponseWriter();
405 
406         writer.startElement(HTML.INPUT_ELEM, uiComponent);
407 
408         if (itemId != null)
409         {
410             writer.writeAttribute(HTML.ID_ATTR, itemId, null);
411         }
412         else if (renderId)
413         {
414             writer.writeAttribute(HTML.ID_ATTR, clientId, null);
415         }
416         writer.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_RADIO, null);
417         
418         String group = uiComponent instanceof HtmlSelectOneRadio ? ((HtmlSelectOneRadio) uiComponent).getGroup() : null;
419         if (group != null && !group.isEmpty())
420         {
421             FormInfo formInfo = RendererUtils.findNestingForm(uiComponent, facesContext);
422             writer.writeAttribute(HTML.NAME_ATTR, formInfo.getFormName()+
423                     facesContext.getNamingContainerSeparatorChar() + group, null);
424         }
425         else
426         {
427             writer.writeAttribute(HTML.NAME_ATTR, clientId, null);
428         }
429 
430         if (disabled)
431         {
432             writer.writeAttribute(HTML.DISABLED_ATTR, HTML.DISABLED_ATTR, null);
433         }
434 
435         if (checked)
436         {
437             writer.writeAttribute(HTML.CHECKED_ATTR, HTML.CHECKED_ATTR, null);
438         }
439 
440         if (group != null && group.length() > 0)
441         {
442             if (value != null)
443             {
444                 writer.writeAttribute(HTML.VALUE_ATTR, 
445                         clientId + facesContext.getNamingContainerSeparatorChar() +value, null);
446             }
447             else
448             {
449                 writer.writeAttribute(HTML.VALUE_ATTR, 
450                         clientId + facesContext.getNamingContainerSeparatorChar() + "", null);
451             }
452         }
453         else
454         {
455             if (value != null)
456             {
457                 writer.writeAttribute(HTML.VALUE_ATTR, value, null);
458             }
459             else
460             {
461                 writer.writeAttribute(HTML.VALUE_ATTR, "", null);
462             }
463         }
464         
465         Map<String, List<ClientBehavior>> behaviors = null;
466         if (uiComponent instanceof ClientBehaviorHolder)
467         {
468             behaviors = ((ClientBehaviorHolder) uiComponent).getClientBehaviors();
469             
470             long commonPropertiesMarked = 0L;
471             if (isCommonPropertiesOptimizationEnabled(facesContext))
472             {
473                 commonPropertiesMarked = CommonPropertyUtils.getCommonPropertiesMarked(uiComponent);
474             }
475             if (behaviors.isEmpty() && isCommonPropertiesOptimizationEnabled(facesContext))
476             {
477                 CommonPropertyUtils.renderChangeEventProperty(writer, 
478                         commonPropertiesMarked, uiComponent);
479                 CommonPropertyUtils.renderEventProperties(writer, 
480                         commonPropertiesMarked, uiComponent);
481                 CommonPropertyUtils.renderFieldEventPropertiesWithoutOnchange(writer, 
482                         commonPropertiesMarked, uiComponent);
483             }
484             else
485             {
486                 HtmlRendererUtils.renderBehaviorizedOnchangeEventHandler(facesContext, writer, uiComponent, 
487                         itemId != null ? itemId : clientId, behaviors);
488                 if (isCommonEventsOptimizationEnabled(facesContext))
489                 {
490                     Long commonEventsMarked = CommonEventUtils.getCommonEventsMarked(uiComponent);
491                     CommonEventUtils.renderBehaviorizedEventHandlers(facesContext, writer, 
492                             commonPropertiesMarked, commonEventsMarked, uiComponent,
493                             itemId != null ? itemId : clientId, behaviors);
494                     CommonEventUtils.renderBehaviorizedFieldEventHandlersWithoutOnchange(
495                         facesContext, writer, commonPropertiesMarked, commonEventsMarked, uiComponent,
496                             itemId != null ? itemId : clientId, behaviors);
497                 }
498                 else
499                 {
500                     HtmlRendererUtils.renderBehaviorizedEventHandlers(facesContext, writer, uiComponent,
501                             itemId != null ? itemId : clientId, behaviors);
502                     HtmlRendererUtils.renderBehaviorizedFieldEventHandlersWithoutOnchange(
503                             facesContext, writer, uiComponent,
504                             itemId != null ? itemId : clientId, behaviors);
505                 }
506             }
507             HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, 
508                     HTML.INPUT_PASSTHROUGH_ATTRIBUTES_WITHOUT_DISABLED_AND_STYLE_AND_EVENTS);
509         }
510         else
511         {
512             HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, 
513                     HTML.INPUT_PASSTHROUGH_ATTRIBUTES_WITHOUT_DISABLED_AND_STYLE);
514         }
515 
516         if (isDisabled(facesContext, uiComponent))
517         {
518             writer.writeAttribute(HTML.DISABLED_ATTR, Boolean.TRUE, null);
519         }
520 
521         writer.endElement(HTML.INPUT_ELEM);
522 
523         return itemId;
524     }
525 
526 
527     protected boolean isDisabled(FacesContext facesContext, UIComponent uiComponent)
528     {
529         //TODO: overwrite in extended HtmlRadioRenderer and check for enabledOnUserRole
530         if (uiComponent instanceof HtmlSelectOneRadio)
531         {
532             return ((HtmlSelectOneRadio)uiComponent).isDisabled();
533         }
534 
535         return RendererUtils.getBooleanAttribute(uiComponent, HTML.DISABLED_ATTR, false);
536     }
537 
538     @Override
539     public void decode(FacesContext facesContext, UIComponent uiComponent)
540     {
541         RendererUtils.checkParamValidity(facesContext, uiComponent, null);
542         if (uiComponent instanceof UIInput)
543         {
544             HtmlRendererUtils.decodeUISelectOne(facesContext, uiComponent);
545         }
546         if (uiComponent instanceof ClientBehaviorHolder &&
547                 !HtmlRendererUtils.isDisabled(uiComponent))
548         {
549             HtmlRendererUtils.decodeClientBehaviors(facesContext, uiComponent);
550         }
551     }
552 
553     @Override
554     public Object getConvertedValue(FacesContext facesContext, UIComponent uiComponent, Object submittedValue)
555         throws ConverterException
556     {
557         RendererUtils.checkParamValidity(facesContext, uiComponent, UISelectOne.class);
558         return RendererUtils.getConvertedUISelectOneValue(facesContext,
559                 (UISelectOne)uiComponent, submittedValue);
560     }
561     
562     private static class GetSelectItemListCallback implements VisitCallback
563     {
564         private final UISelectOne selectOneRadio;
565         private final String group;
566         
567         private final List<UISelectOne> selectOneRadios;
568         
569         private int index;
570         private SelectItem selectItem;
571         private Converter converter;
572         private Object currentValue;
573 
574         public GetSelectItemListCallback(UISelectOne selectOneRadio, String group)
575         {
576             this.selectOneRadio = selectOneRadio;
577             this.group = group;
578             
579             this.selectOneRadios = new ArrayList<>();
580         }
581 
582         @Override
583         public VisitResult visit(VisitContext context, UIComponent target)
584         {
585             if (target instanceof UISelectOne)
586             {
587                 UISelectOne targetSelectOneRadio = ((UISelectOne) target);
588                 String targetGroup = targetSelectOneRadio.getGroup();
589                 if (group.equals(targetGroup))
590                 {
591                     selectOneRadios.add(targetSelectOneRadio);
592                     
593                     // check if the current selectOneRadio was already visited
594                     index = selectOneRadios.indexOf(selectOneRadio);
595                     if (index != -1)
596                     {                        
597                         UISelectOne first = selectOneRadios.get(0);
598 
599                         // if we were found,
600                         // lets take the selectItems from the first selectOneRadio of our group
601                         List<SelectItem> selectItemList = RendererUtils.getSelectItemList(first,
602                                 context.getFacesContext());
603                         if (selectItemList == null || selectItemList.isEmpty())
604                         {
605                             throw new FacesException("UISelectOne with id=\"" + first.getId()
606                                             + "\" and group=\"" + group + "\" does not have any UISelectItems!");
607                         }
608                         
609                         // evaluate required infos from the first selectOneRadio of our group
610                         selectItem = selectItemList.get(index);
611                         converter = HtmlRendererUtils.findUIOutputConverterFailSafe(context.getFacesContext(), first);
612                         currentValue = RendererUtils.getStringFromSubmittedValueOrLocalValueReturnNull(
613                                 context.getFacesContext(), first);
614                         
615                         return VisitResult.COMPLETE;
616                     }
617                     
618                     return VisitResult.REJECT;
619                 }
620                 else
621                 {
622                     return VisitResult.REJECT;
623                 }
624             }
625 
626             return VisitResult.ACCEPT;
627         }
628 
629         public int getIndex()
630         {
631             return index;
632         }
633         
634         public SelectItem getSelectItem()
635         {
636             return selectItem;
637         }
638         
639         public Converter getConverter()
640         {
641             return converter;
642         }
643 
644         public Object getCurrentValue()
645         {
646             return currentValue;
647         }
648     }
649 
650 }