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  
20  package org.apache.myfaces.custom.suggestajax.inputsuggestajax;
21  
22  import org.apache.commons.collections.map.HashedMap;
23  import org.apache.myfaces.custom.ajax.api.AjaxRenderer;
24  import org.apache.myfaces.custom.dojo.DojoConfig;
25  import org.apache.myfaces.custom.dojo.DojoUtils;
26  import org.apache.myfaces.custom.suggestajax.SuggestAjaxRenderer;
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  
31  import javax.faces.component.UIComponent;
32  import javax.faces.context.FacesContext;
33  import javax.faces.context.ResponseWriter;
34  import javax.faces.convert.Converter;
35  import javax.el.MethodExpression;
36  import javax.el.MethodNotFoundException;
37  import javax.servlet.ServletResponse;
38  import java.io.IOException;
39  import java.util.Collection;
40  import java.util.Iterator;
41  import java.util.Map;
42  
43  /**
44   *
45   * @JSFRenderer
46   *   renderKitId = "HTML_BASIC"
47   *   family = "javax.faces.Input"
48   *   type = "org.apache.myfaces.InputSuggestAjax"
49   *
50   * @author Gerald Müllan
51   * @author Martin Marinschek
52   * @version $Revision: 674280 $ $Date: 2008-07-06 05:09:28 -0500 (Sun, 06 Jul 2008) $
53   */
54  public class InputSuggestAjaxRenderer extends SuggestAjaxRenderer implements AjaxRenderer
55  {
56     /**
57       * Encodes any stand-alone javascript functions that are needed.
58       * Uses either the extension filter, or a
59       * user-supplied location for the javascript files.
60       *
61       * @param context FacesContext
62       * @param component UIComponent
63       * @throws java.io.IOException if base-transport layer was not available
64       */
65      private void encodeJavascript(FacesContext context, UIComponent component)
66                                                                          throws IOException
67      {
68          String javascriptLocation = (String)component.getAttributes().get(JSFAttr.JAVASCRIPT_LOCATION);
69  
70          DojoUtils.addMainInclude(context, component, javascriptLocation, new DojoConfig());
71          DojoUtils.addRequire(context, component, "extensions.FacesIO");
72          DojoUtils.addRequire(context, component, "extensions.widget.InputSuggestAjax");
73          DojoUtils.addRequire(context, component, "dojo.event.*");
74      }
75  
76      public void encodeEnd(FacesContext context, UIComponent component) throws IOException
77      {
78          RendererUtils.checkParamValidity(context, component, InputSuggestAjax.class);
79  
80          InputSuggestAjax inputSuggestAjax = (InputSuggestAjax) component;
81  
82          encodeJavascript(context,component);
83  
84          String clientId = component.getClientId(context);
85          String actionURL = getActionUrl(context);
86  
87          String charset = (inputSuggestAjax.getCharset() != null ? inputSuggestAjax.getCharset() : "");
88  
89          String ajaxUrl = context.getExternalContext().encodeActionURL(addQueryString(actionURL, "affectedAjaxComponent=" + clientId +
90                  "&charset=" + charset + "&" + clientId + "=%{searchString}"));
91  
92          ResponseWriter out = context.getResponseWriter();
93  
94          String label = null;
95          String hiddenInputValue = null;
96          boolean hasLabelMethod = false;
97  
98          String mainComponentRenderedValue = null;
99  
100         /* check if the user supplied a label method */
101         if (inputSuggestAjax.getItemLabelMethod() == null)
102         {
103             mainComponentRenderedValue = RendererUtils.getStringValue(context, inputSuggestAjax);
104         }
105         else
106         {
107             MethodExpression labelMethod = inputSuggestAjax.getItemLabelMethod();
108 
109             if (labelMethod != null)
110             {
111                 hasLabelMethod = true;
112 
113                 Object valueObject = inputSuggestAjax.getValue();
114 
115                 Converter converter = getRequiredConverter(context, inputSuggestAjax);
116 
117                 label = (String) labelMethod.invoke(context.getELContext(), new Object[]{valueObject});
118 
119                 hiddenInputValue = converter.getAsString(context, inputSuggestAjax, valueObject);
120                 mainComponentRenderedValue = hiddenInputValue;
121             }
122         }
123 
124         String placeHolderId = context.getViewRoot().createUniqueId();
125         out.startElement(HTML.DIV_ELEM, component);
126         out.writeAttribute(HTML.ID_ATTR, placeHolderId , null);
127         if(inputSuggestAjax.getStyle() != null)
128         {
129             out.writeAttribute(HTML.STYLE_ATTR, inputSuggestAjax.getStyle(), null);
130         }
131         if(inputSuggestAjax.getStyleClass() != null)
132         {
133             out.writeAttribute(HTML.CLASS_ATTR, inputSuggestAjax.getStyleClass(), null);
134         }
135         out.endElement(HTML.DIV_ELEM);
136 
137         String textInputId = inputSuggestAjax.getClientId(context);
138         if (label != null)
139         {
140             // whe have a label method and thus a hidden input field holding the real value
141             // now fake the component id to have the rendered input component use another id
142             // than the one we render later for the real value
143             String oriId = inputSuggestAjax.getId();
144             try
145             {
146                 // fake the label
147                 inputSuggestAjax.setId(oriId + "_fake");
148 
149                 textInputId = inputSuggestAjax.getClientId(context);
150 
151                 // fake a submitted value so we have it rendered
152                 inputSuggestAjax.setSubmittedValue(label);
153 
154                 super.encodeEnd(context, inputSuggestAjax);
155             }
156             finally
157             {
158                 inputSuggestAjax.setSubmittedValue(null);
159                 inputSuggestAjax.setId(oriId);
160             }
161         }
162         else
163         {
164             super.encodeEnd(context, inputSuggestAjax);
165         }
166 
167         String inputSuggestComponentVar = DojoUtils.calculateWidgetVarName(placeHolderId);
168 
169         Map attributes = new HashedMap();
170 
171         attributes.put("dataUrl", ajaxUrl);
172         attributes.put("mode", "remote");
173         attributes.put("textInputId", textInputId);
174 
175         String autoComplete = inputSuggestAjax.getAutoComplete().booleanValue()?"true":"false";
176         attributes.put("autoComplete", autoComplete);
177 
178         if (label != null)
179         {
180             mainComponentRenderedValue = label;
181         }
182         else if (mainComponentRenderedValue != null)
183         {
184             mainComponentRenderedValue = escapeQuotes(mainComponentRenderedValue);
185         }
186 
187         DojoUtils.renderWidgetInitializationCode(context.getResponseWriter(), component, "extensions:InputSuggestAjax", attributes, placeHolderId.toString(), true);
188 
189         out.startElement(HTML.SCRIPT_ELEM, null);
190         out.writeAttribute(HTML.TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
191 
192         StringBuffer buffer = new StringBuffer();
193 
194         buffer.append("dojo.addOnLoad(function() {\n")
195               .append(inputSuggestComponentVar).append(".comboBoxValue.value = \"").append(mainComponentRenderedValue).append("\";\n")
196               .append(inputSuggestComponentVar).append(".onResize();\n")
197               .append("});\n");
198 
199         out.write(buffer.toString());
200 
201         out.endElement(HTML.SCRIPT_ELEM);
202 
203         if (hasLabelMethod)
204         {
205             out.startElement(HTML.INPUT_ELEM, inputSuggestAjax);
206             out.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_HIDDEN, null);
207             out.writeAttribute(HTML.ID_ATTR, clientId, null);
208             out.writeAttribute(HTML.NAME_ATTR, clientId, null);
209             out.writeAttribute(HTML.VALUE_ATTR, hiddenInputValue!=null?hiddenInputValue:"", null);
210             out.endElement(HTML.INPUT_ELEM);
211 
212             out.startElement(HTML.SCRIPT_ELEM, null);
213             out.writeAttribute(HTML.TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
214 
215             StringBuffer script = new StringBuffer();
216 
217              script.append("dojo.event.connect("+inputSuggestComponentVar+", \"_selectOption\", function(evt) { \n"
218                    + "dojo.byId('"+ clientId +"').value = ").append(inputSuggestComponentVar).append(".comboBoxSelectionValue.value; });\n");
219 
220             out.write(script.toString());
221 
222             out.endElement(HTML.SCRIPT_ELEM);
223         }
224     }
225 
226     protected Converter getRequiredConverter(FacesContext context, InputSuggestAjax inputSuggestAjax)
227     {
228         Converter converter = inputSuggestAjax.getConverter();
229         if (converter != null)
230         {
231             return converter;
232         }
233 
234         Class type = inputSuggestAjax.getValueBinding("value").getType(context);
235         if (type != null)
236         {
237             converter = context.getApplication().createConverter(type);
238             if (converter != null)
239             {
240                 return converter;
241             }
242         }
243 
244         throw new IllegalStateException("There must be a converter if " +
245                                                           "attribute \"labelMethod\" is used");
246     }
247 
248     public void encodeAjax(FacesContext context, UIComponent uiComponent)
249                                                                     throws IOException
250     {
251         // we are not sending html, xml or json, so notify the system about that else any filter
252         // trying to parse the result as html (e.g. richfaces) will fail
253         ServletResponse response = (ServletResponse) context.getExternalContext().getResponse();
254         response.setContentType("text/plain");
255 
256         InputSuggestAjax inputSuggestAjax = (InputSuggestAjax) uiComponent;
257 
258         Collection suggesteds = getSuggestedItems(context, uiComponent);
259 
260         MethodExpression labelMethod = inputSuggestAjax.getItemLabelMethod();
261 
262         StringBuffer buf = new StringBuffer();
263 
264         buf.append("[");
265 
266         if (labelMethod != null)
267         {
268             Converter converter = getRequiredConverter(context, inputSuggestAjax);
269 
270             for (Iterator iterator = suggesteds.iterator(); iterator.hasNext();)
271             {
272                 Object suggestedItemObject = iterator.next();
273 
274                 String label = (String) labelMethod.invoke(context.getELContext(), new Object[]{suggestedItemObject});
275                 String value = converter.getAsString(context, inputSuggestAjax, suggestedItemObject);
276 
277                 buf.append("[\"").append(label).append("\",\"").append(value).append("\"],");
278             }
279         }
280         else
281         {
282             //writing the suggested list
283             for (Iterator suggestedItem = suggesteds.iterator(); suggestedItem.hasNext() ;)
284             {
285                 Object item = suggestedItem.next();
286 
287                 String prefix = escapeQuotes(encodeSuggestString(item.toString()).substring(0, 1)).toUpperCase();
288 
289                 buf.append("[\"").append(encodeSuggestString(escapeQuotes(item.toString()))).append("\",\"")
290                    .append(prefix).append("\"],");
291             }
292         }
293 
294         buf.append("]");
295 
296         context.getResponseWriter().write(buf.toString());
297     }
298 
299     protected String encodeSuggestString(String str)
300     {
301         //If you want UTF-8 and we don't do it, you can enable it here with UnicodeEncoder.encode()
302         return str;
303     }
304 
305     public void decode(FacesContext facesContext, UIComponent component)
306     {
307         super.decode(facesContext, component);
308     }
309 
310     private String escapeQuotes(String input)
311     {
312            return input != null ? input.replaceAll("\"", "\\\\\"") : "";
313     }
314 
315 }