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.picklist;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import javax.faces.application.ResourceDependency;
30  import javax.faces.component.EditableValueHolder;
31  import javax.faces.component.UIComponent;
32  import javax.faces.component.UISelectMany;
33  import javax.faces.component.behavior.ClientBehavior;
34  import javax.faces.component.behavior.ClientBehaviorHolder;
35  import javax.faces.context.FacesContext;
36  import javax.faces.context.ResponseWriter;
37  import javax.faces.convert.Converter;
38  import javax.faces.convert.ConverterException;
39  import javax.faces.model.SelectItem;
40  
41  import org.apache.commons.collections.CollectionUtils;
42  import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
43  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
44  import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
45  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlListboxRendererBase;
46  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils;
47  
48  /**
49   * 
50   * @JSFRenderer
51   *   renderKitId = "HTML_BASIC" 
52   *   family = "javax.faces.SelectMany"
53   *   type = "org.apache.myfaces.PicklistRenderer"
54   * @since 1.1.7
55   * @author Bruno Aranda (latest modification by $Author$)
56   * @version $Revision$ $Date$
57   */
58  @ResourceDependency(library="oam.custom.picklist",name="picklist.js")
59  public class HtmlPicklistRenderer extends HtmlListboxRendererBase
60  {
61  
62      private static final String FUNCTION_ADD_TO_SELECTED = "myfaces_picklist_addToSelected";
63      private static final String FUNCTION_ADD_ALL_TO_SELECTED = "myfaces_picklist_addAllToSelected";    
64      private static final String FUNCTION_REMOVE_FROM_SELECTED = "myfaces_picklist_removeFromSelected";
65      private static final String FUNCTION_REMOVE_ALL_FROM_SELECTED = "myfaces_picklist_removeAllFromSelected";
66  
67      private static final String AVAILABLE_SUFFIX = "_AVAILABLE";
68      private static final String SELECTED_SUFFIX = "_SELECTED";
69      private static final String HIDDEN_SUFFIX = "_HIDDEN";
70  
71      public void decode(FacesContext facesContext, UIComponent uiComponent)
72      {
73          RendererUtils.checkParamValidity(facesContext, uiComponent, null);
74  
75          if (!(uiComponent instanceof EditableValueHolder))
76          {
77              throw new IllegalArgumentException("Component "
78                                                 + uiComponent.getClientId(facesContext)
79                                                 + " is not an EditableValueHolder");
80          }
81  
82          String hiddenClientId = uiComponent.getClientId(facesContext)
83                                  + HIDDEN_SUFFIX;
84  
85          Map paramValuesMap = facesContext.getExternalContext()
86                  .getRequestParameterValuesMap();
87  
88          if (HtmlRendererUtils.isDisabledOrReadOnly(uiComponent))
89              return;
90  
91          if (paramValuesMap.containsKey(hiddenClientId))
92          {
93              String[] valuesInline = (String[]) paramValuesMap
94                      .get(hiddenClientId);
95  
96              if (valuesInline[0].trim().equals(""))
97              {
98                  ((EditableValueHolder) uiComponent)
99                  .setSubmittedValue(new String[] {});
100             }
101             else
102             {
103                 String[] reqValues = valuesInline[0].split(",");
104                 ((EditableValueHolder) uiComponent)
105                 .setSubmittedValue(reqValues);
106             }
107         }
108         else
109         {
110             /* request parameter not found, nothing to decode - set submitted value to an empty array
111              as we should get here only if the component is on a submitted form, is rendered
112              and if the component is not readonly or has not been disabled.
113 
114              So in fact, there must be component value at this location, but for listboxes, comboboxes etc.
115              the submitted value is not posted if no item is selected. */
116             ((EditableValueHolder) uiComponent)
117                     .setSubmittedValue(new String[] {});
118         }
119     }
120 
121     public void encodeEnd(FacesContext facesContext, UIComponent uiComponent)
122             throws IOException
123     {
124         RendererUtils.checkParamValidity(facesContext, uiComponent,
125                                          HtmlSelectManyPicklist.class);
126         
127         // check for displayValueOnly
128         if(HtmlRendererUtils.isDisplayValueOnly(uiComponent))
129         {
130             HtmlRendererUtils.renderDisplayValueOnlyForSelects(facesContext, uiComponent, true);
131             return;
132         }
133 
134         HtmlSelectManyPicklist picklist = (HtmlSelectManyPicklist) uiComponent;
135 
136         String addButtonText = picklist.getAddButtonText();
137         String addAllButtonText = picklist.getAddAllButtonText();        
138         String removeButtonText = picklist.getRemoveButtonText();
139         String removeAllButtonText = picklist.getRemoveAllButtonText();        
140         String addButtonStyle = picklist.getAddButtonStyle();
141         String addAllButtonStyle = picklist.getAddAllButtonStyle();  
142         String removeButtonStyle = picklist.getRemoveButtonStyle();
143         String removeAllButtonStyle = picklist.getRemoveAllButtonStyle();        
144         String addButtonStyleClass = picklist.getAddButtonStyleClass();
145         String addAllButtonStyleClass = picklist.getAddAllButtonStyleClass();        
146         String removeButtonStyleClass = picklist.getRemoveButtonStyleClass();
147         String removeAllButtonStyleClass = picklist.getRemoveAllButtonStyleClass();        
148         
149         //Set the default values for addButtonText and removeButtonText
150         if(addButtonText == null || addButtonText.length() == 0)
151             addButtonText = ">";
152         
153         if(addAllButtonText == null || addAllButtonText.length() == 0)
154             addAllButtonText = ">>";        
155         
156         if(removeButtonText == null || removeButtonText.length() == 0)
157             removeButtonText = "<";
158         
159         if(removeAllButtonText == null || removeAllButtonText.length() == 0)
160             removeAllButtonText = "<<";        
161 
162         //encodeJavascript(facesContext, uiComponent);
163 
164         String availableListClientId = uiComponent.getClientId(facesContext)
165                                        + AVAILABLE_SUFFIX;
166         String selectedListClientId = uiComponent.getClientId(facesContext)
167                                       + SELECTED_SUFFIX;
168         String hiddenFieldCliendId = uiComponent.getClientId(facesContext)
169                                      + HIDDEN_SUFFIX;
170 
171         List selectItemList = RendererUtils
172                 .getSelectItemList((UISelectMany) uiComponent);
173         Converter converter = getConverter(facesContext, uiComponent); 
174 
175         Set lookupSet = HtmlRendererUtils.getSubmittedOrSelectedValuesAsSet(
176                 true, uiComponent, facesContext, converter);
177 
178         List selectItemsForSelectedValues = selectItemsForSelectedList(
179                 facesContext, uiComponent, selectItemList, converter, lookupSet);
180         List selectItemsForAvailableList = selectItemsForAvailableList(
181                 facesContext, uiComponent, selectItemList,
182                 selectItemsForSelectedValues, converter);
183 
184         ResponseWriter writer = facesContext.getResponseWriter();
185 
186         writer.startElement(HTML.TABLE_ELEM, uiComponent);
187         
188         Map<String, List<ClientBehavior>> behaviors = null;
189         if (uiComponent instanceof ClientBehaviorHolder)
190         {
191             behaviors = ((ClientBehaviorHolder) uiComponent).getClientBehaviors();
192         }
193         
194         if (behaviors != null && !behaviors.isEmpty())
195         {
196             writer.writeAttribute(HTML.ID_ATTR, uiComponent.getClientId(facesContext),null);
197         }
198         else
199         {
200             HtmlRendererUtils.writeIdIfNecessary(writer, uiComponent, facesContext);
201         }
202         
203         writer.startElement(HTML.TR_ELEM, uiComponent);
204         writer.startElement(HTML.TD_ELEM, uiComponent);
205 
206         encodeSelect(facesContext, picklist, availableListClientId, isDisabled(
207                 facesContext, uiComponent), picklist.getSize(),
208                                             selectItemsForAvailableList, converter);
209 
210         writer.endElement(HTML.TD_ELEM);
211 
212         // encode buttons
213         writer.startElement(HTML.TD_ELEM, uiComponent);
214 
215         String javascriptAddToSelected = FUNCTION_ADD_TO_SELECTED + "('"
216                                          + availableListClientId + "','" + selectedListClientId + "','"
217                                          + hiddenFieldCliendId + "')";
218         
219         String javascriptAddAllToSelected = FUNCTION_ADD_ALL_TO_SELECTED + "('"
220                                          + availableListClientId + "','" + selectedListClientId + "','"
221                                          + hiddenFieldCliendId + "')";        
222         
223         String javascriptRemoveFromSelected = FUNCTION_REMOVE_FROM_SELECTED
224                                               + "('" + availableListClientId + "','" + selectedListClientId
225                                               + "','" + hiddenFieldCliendId + "')";
226         
227         String javascriptRemoveAllFromSelected = FUNCTION_REMOVE_ALL_FROM_SELECTED
228                                               + "('" + availableListClientId + "','" + selectedListClientId
229                                               + "','" + hiddenFieldCliendId + "')";        
230 
231         // encode (add selected) button.
232         encodeSwapButton(facesContext, uiComponent, javascriptAddToSelected,
233                 addButtonText, addButtonStyle, addButtonStyleClass);
234 
235         writer.startElement(HTML.BR_ELEM, uiComponent);
236         writer.endElement(HTML.BR_ELEM);
237         
238         // encode (add all) button.
239         encodeSwapButton(facesContext, uiComponent, javascriptAddAllToSelected, 
240                 addAllButtonText, addAllButtonStyle, addAllButtonStyleClass);      
241         
242         writer.startElement(HTML.BR_ELEM, uiComponent);
243         writer.endElement(HTML.BR_ELEM);
244 
245         // encode (remove selected) button.
246         encodeSwapButton(facesContext, uiComponent, javascriptRemoveFromSelected, 
247                 removeButtonText, removeButtonStyle, removeButtonStyleClass);  
248         
249         writer.startElement(HTML.BR_ELEM, uiComponent);
250         writer.endElement(HTML.BR_ELEM);        
251         
252         // encode (remove all) button.        
253         encodeSwapButton(facesContext, uiComponent, javascriptRemoveAllFromSelected, 
254                 removeAllButtonText, removeAllButtonStyle, removeAllButtonStyleClass);        
255 
256         writer.endElement(HTML.TD_ELEM);
257 
258         // encode selected list
259         writer.startElement(HTML.TD_ELEM, uiComponent);
260 
261         encodeSelect(facesContext, picklist, selectedListClientId, isDisabled(
262                 facesContext, uiComponent), picklist.getSize(),
263                                             selectItemsForSelectedValues, converter);
264 
265         // hidden field with the selected values
266         encodeHiddenField(facesContext, uiComponent, hiddenFieldCliendId,
267                           lookupSet);
268 
269         writer.endElement(HTML.TD_ELEM);
270         writer.endElement(HTML.TR_ELEM);
271         writer.endElement(HTML.TABLE_ELEM);
272     }
273 
274     /*private void encodeJavascript(FacesContext facesContext,
275                                   UIComponent uiComponent)
276     {
277         // AddResource takes care to add only one reference to the same script
278         AddResource addResource = AddResourceFactory.getInstance(facesContext);
279         addResource.addJavaScriptAtPosition(facesContext, AddResource.HEADER_BEGIN,
280                                             HtmlPicklistRenderer.class, "picklist.js");
281     }*/
282 
283     private void encodeSwapButton(FacesContext facesContext,
284                                   UIComponent uiComponent, String javaScriptFunction,
285                                   String text, String style, String styleClass)
286             throws IOException
287     {
288         ResponseWriter writer = facesContext.getResponseWriter();
289 
290         writer.startElement(HTML.INPUT_ELEM, uiComponent);
291         writer.writeAttribute(HTML.STYLE_ATTR, style, null);
292         writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
293         writer.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_BUTTON,
294                               JSFAttr.TYPE_ATTR);
295         writer.writeAttribute(HTML.ONCLICK_ATTR, javaScriptFunction, null);
296         writer.writeAttribute(HTML.VALUE_ATTR, text, null);
297         writer.endElement(HTML.INPUT_ELEM);
298     }
299     
300     /**
301      * Overrides HtmlListboxRendererBase to handle valueType attribute on UISelectMany.
302      */
303     @Override
304     public Object getConvertedValue(FacesContext facesContext,
305             UIComponent component, Object submittedValue)
306             throws ConverterException
307     {
308         RendererUtils.checkParamValidity(facesContext, component, null);
309         
310         if (component instanceof UISelectMany) 
311         {
312             // invoke getConvertedUISelectManyValue() with considerValueType = true
313             return RendererUtils.getConvertedUISelectManyValue(facesContext,
314                     (UISelectMany) component, submittedValue, true); 
315         } 
316         else 
317         {
318             // component is not a UISelectMany --> no change needed
319             return super.getConvertedValue(facesContext, component, submittedValue);
320         }
321     }
322     
323     /**
324      * Overrides HtmlListboxRendererBase to handle valueType attribute on UISelectMany.
325      */
326     @Override
327     protected Converter getConverter(FacesContext facesContext,
328             UIComponent component)
329     {
330         if (component instanceof UISelectMany)
331         {
332             // invoke findUISelectManyConverterFailsafe() with considerValueType = true
333             return HtmlRendererUtils.findUISelectManyConverterFailsafe(facesContext, 
334                     (UISelectMany) component, true);
335         }
336         else
337         {
338             // component is not a UISelectMany --> no change needed
339             return super.getConverter(facesContext, component);
340         }
341     }
342 
343     private void encodeSelect(FacesContext facesContext,
344                               UIComponent uiComponent, String clientId, boolean disabled,
345                               int size, List selectItemsToDisplay, Converter converter)
346             throws IOException
347     {
348         ResponseWriter writer = facesContext.getResponseWriter();
349 
350         writer.startElement(HTML.SELECT_ELEM, uiComponent);
351         writer.writeAttribute(HTML.ID_ATTR, clientId, JSFAttr.ID_ATTR);
352         writer.writeAttribute(HTML.NAME_ATTR, clientId, null);
353 
354         writer.writeAttribute(HTML.MULTIPLE_ATTR, "true", null);
355 
356         if (size == 0)
357         {
358             //No size given (Listbox) --> size is number of select items
359             writer.writeAttribute(HTML.SIZE_ATTR, Integer
360                     .toString(selectItemsToDisplay.size()), null);
361         }
362         else
363         {
364             writer.writeAttribute(HTML.SIZE_ATTR, Integer.toString(size), null);
365         }
366         
367         Map<String, List<ClientBehavior>> behaviors = null;
368         if (uiComponent instanceof ClientBehaviorHolder)
369         {
370             behaviors = ((ClientBehaviorHolder) uiComponent).getClientBehaviors();
371         }
372         
373         if (behaviors != null && !behaviors.isEmpty())
374         {
375             HtmlRendererUtils.renderBehaviorizedOnchangeEventHandler(facesContext, writer, uiComponent, behaviors);
376             HtmlRendererUtils.renderBehaviorizedEventHandlers(facesContext, writer, uiComponent, behaviors);
377             HtmlRendererUtils.renderBehaviorizedFieldEventHandlersWithoutOnchange(facesContext, writer, uiComponent, behaviors);
378             HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent, HTML.SELECT_PASSTHROUGH_ATTRIBUTES_WITHOUT_DISABLED_AND_EVENTS);
379         }
380         else
381         {
382             HtmlRendererUtils.renderHTMLAttributes(writer, uiComponent,
383                                                    HTML.SELECT_PASSTHROUGH_ATTRIBUTES_WITHOUT_DISABLED);
384         }
385         if (disabled)
386         {
387             writer.writeAttribute(HTML.DISABLED_ATTR, Boolean.TRUE, null);
388         }
389 
390         HtmlRendererUtils.renderSelectOptions(facesContext, uiComponent,
391                                               converter, Collections.EMPTY_SET, selectItemsToDisplay);
392 
393         // bug #970747: force separate end tag
394         writer.writeText("", null);
395         writer.endElement(HTML.SELECT_ELEM);
396     }
397 
398     private void encodeHiddenField(FacesContext facesContext,
399                                    UIComponent uiComponent, String hiddenFieldCliendId, Set lookupSet)
400             throws IOException
401     {
402         ResponseWriter writer = facesContext.getResponseWriter();
403 
404         writer.startElement(HTML.INPUT_ELEM, uiComponent);
405         writer.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_HIDDEN,
406                               JSFAttr.TYPE_ATTR);
407         writer.writeAttribute(HTML.ID_ATTR, hiddenFieldCliendId,
408                               JSFAttr.ID_ATTR);
409         writer.writeAttribute(HTML.NAME_ATTR, hiddenFieldCliendId, null);
410 
411         StringBuffer sb = new StringBuffer();
412         int n = 0;
413         for (Iterator i = lookupSet.iterator(); i.hasNext();)
414         {
415             if (n > 0)
416             {
417                 sb.append(",");
418             }
419             String value = (String) i.next();
420             sb.append(value);
421             n++;
422         }
423 
424         writer.writeAttribute(HTML.VALUE_ATTR, sb.toString(), null);
425         writer.endElement(HTML.INPUT_ELEM);
426 
427     }
428 
429     private List selectItemsForSelectedList(FacesContext facesContext,
430                                             UIComponent uiComponent, List selectItemList, Converter converter,
431                                             Set lookupSet)
432     {
433         List selectItemForSelectedValues = new ArrayList(lookupSet.size());
434 
435         for (Iterator i = selectItemList.iterator(); i.hasNext();)
436         {
437             SelectItem selectItem = (SelectItem) i.next();
438             String itemStrValue = RendererUtils.getConvertedStringValue(facesContext, uiComponent,
439                     converter, selectItem);
440 
441 
442             for (Iterator i2 = lookupSet.iterator(); i2.hasNext();)
443             {
444                 Object value = i2.next();
445                 if (value.equals(itemStrValue))
446                 {
447                     selectItemForSelectedValues.add(selectItem);
448                 }
449             }
450         }
451 
452         return selectItemForSelectedValues;
453     }
454 
455     private List selectItemsForAvailableList(FacesContext facesContext,
456                                              UIComponent uiComponent, List selectItemList,
457                                              List selectItemsForSelectedList, Converter converter)
458     {
459 
460         return new ArrayList(CollectionUtils.subtract(selectItemList,
461                                                       selectItemsForSelectedList));
462     }
463     
464 }