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