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