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.Iterator;
23  import java.util.Map;
24  import java.util.Set;
25  import java.util.logging.Logger;
26  
27  import javax.faces.context.ExternalContext;
28  import javax.faces.context.FacesContext;
29  import javax.faces.context.ResponseWriter;
30  
31  import org.apache.myfaces.shared.config.MyfacesConfig;
32  import org.apache.myfaces.shared.renderkit.html.HtmlRendererUtils.ScriptContext;
33  import org.apache.myfaces.shared.renderkit.html.util.JavascriptUtils;
34  import org.apache.myfaces.shared.renderkit.html.util.ResourceUtils;
35  
36  public final class HtmlJavaScriptUtils
37  {
38      private static final Logger log = Logger.getLogger(HtmlJavaScriptUtils.class
39              .getName());
40  
41      private static final String AUTO_SCROLL_PARAM = "autoScroll";
42      private static final String AUTO_SCROLL_FUNCTION = "getScrolling";
43  
44      private static final String SET_HIDDEN_INPUT_FN_NAME = "oamSetHiddenInput";
45      private static final String SET_HIDDEN_INPUT_FN_NAME_JSF2 = "myfaces.oam.setHiddenInput";
46  
47      private static final String FIRST_SUBMIT_SCRIPT_ON_PAGE = "org.apache.MyFaces.FIRST_SUBMIT_SCRIPT_ON_PAGE";
48      private static final String CLEAR_HIDDEN_INPUT_FN_NAME = "oamClearHiddenInput";
49      
50  
51      @SuppressWarnings("unchecked")
52      public static void renderFormSubmitScript(FacesContext facesContext)
53              throws IOException
54      {
55          if (facesContext.getPartialViewContext() != null && 
56                  (facesContext.getPartialViewContext().isPartialRequest() ||
57                   facesContext.getPartialViewContext().isAjaxRequest() )
58              )
59          {
60              return;
61          }
62  
63          Map map = facesContext.getExternalContext().getRequestMap();
64          Boolean firstScript = (Boolean) map.get(FIRST_SUBMIT_SCRIPT_ON_PAGE);
65  
66          if (firstScript == null || firstScript.equals(Boolean.TRUE))
67          {
68              map.put(FIRST_SUBMIT_SCRIPT_ON_PAGE, Boolean.FALSE);
69              renderFormSubmitScriptIfNecessary(facesContext);
70  
71              //we have to render the config just in case
72              renderConfigOptionsIfNecessary(facesContext);
73          }
74      }
75      
76      /**
77       * @param facesContext
78       * @throws IOException
79       */
80      private static void renderFormSubmitScriptIfNecessary(
81              FacesContext facesContext) throws IOException
82      {
83          ResponseWriter writer = facesContext.getResponseWriter();
84          ResourceUtils
85                  .renderMyfacesJSInlineIfNecessary(facesContext, writer);
86      }
87      
88      private static void renderConfigOptionsIfNecessary(FacesContext facesContext)
89              throws IOException
90      {
91          ResponseWriter writer = facesContext.getResponseWriter();
92          MyfacesConfig config = MyfacesConfig.getCurrentInstance(facesContext
93                  .getExternalContext());
94          ScriptContext script = new ScriptContext();
95          boolean autoScroll = config.isAutoScroll();
96          boolean autoSave = JavascriptUtils.isSaveFormSubmitLinkIE(facesContext
97                  .getExternalContext());
98  
99          if (autoScroll || autoSave)
100         {
101             script.prettyLine();
102             script.increaseIndent();
103             script.append("(!window.myfaces) ? window.myfaces = {} : null;");
104             script.append("(!myfaces.core) ? myfaces.core = {} : null;");
105             script.append("(!myfaces.core.config) ? myfaces.core.config = {} : null;");
106         }
107 
108         if (autoScroll)
109         {
110             script.append("myfaces.core.config.autoScroll = true;");
111         }
112         if (autoSave)
113         {
114             script.append("myfaces.core.config.ieAutoSave = true;");
115         }
116         if (autoScroll || autoSave)
117         {
118             writer.startElement(HTML.SCRIPT_ELEM, null);
119             writer.writeAttribute(HTML.TYPE_ATTR, "text/javascript", null);
120             writer.writeText(script.toString(), null);
121             writer.endElement(HTML.SCRIPT_ELEM);
122         }
123     }
124     
125     public static void appendAutoScrollAssignment(StringBuilder onClickValue,
126             String formName)
127     {
128         appendAutoScrollAssignment(FacesContext.getCurrentInstance(),
129                 new ScriptContext(onClickValue, false), formName);
130     }
131 
132     /**
133      * Adds the hidden form input value assignment that is necessary for the autoscroll
134      * feature to an html link or button onclick attribute.
135      */
136     public static void appendAutoScrollAssignment(FacesContext context,
137             StringBuilder onClickValue, String formName)
138     {
139         appendAutoScrollAssignment(context, new ScriptContext(onClickValue,
140                 false), formName);
141     }
142     
143     private static void appendAutoScrollAssignment(FacesContext context,
144             ScriptContext scriptContext, String formName)
145     {
146         String formNameStr = formName == null ? "formName" : (new StringBuilder(
147                 "'").append(formName).append("'").toString());
148         String paramName = new StringBuilder().append("'")
149                 .append(AUTO_SCROLL_PARAM).append("'").toString();
150         String value = new StringBuilder().append(AUTO_SCROLL_FUNCTION)
151                 .append("()").toString();
152 
153         scriptContext.prettyLine();
154         scriptContext.append("if(typeof window." + AUTO_SCROLL_FUNCTION
155                 + "!='undefined')");
156         scriptContext.append("{");
157         scriptContext.append(SET_HIDDEN_INPUT_FN_NAME_JSF2);
158         scriptContext.append("(").append(formNameStr).append(",")
159                 .append(paramName).append(",").append(value).append(");");
160         scriptContext.append("}");
161 
162     }
163     
164     public static String getAutoScrollFunction(FacesContext facesContext)
165     {
166         ScriptContext script = new ScriptContext();
167 
168         script.prettyLineIncreaseIndent();
169 
170         script.append("function ");
171         script.append(AUTO_SCROLL_FUNCTION);
172         script.append("()");
173         script.append("{");
174         script.append("var x = 0; var y = 0;");
175         script.append("if (self.pageXOffset || self.pageYOffset)");
176         script.append("{");
177         script.append("x = self.pageXOffset;");
178         script.prettyLine();
179         script.append("y = self.pageYOffset;");
180         script.append("}");
181         script.append(" else if ((document.documentElement && document.documentElement.scrollLeft)||"+
182                 "(document.documentElement && document.documentElement.scrollTop))");
183         script.append("{");
184         script.append("x = document.documentElement.scrollLeft;");
185         script.prettyLine();
186         script.append("y = document.documentElement.scrollTop;");
187         script.append("}");
188         script.append(" else if (document.body) ");
189         script.append("{");
190         script.append("x = document.body.scrollLeft;");
191         script.prettyLine();
192         script.append("y = document.body.scrollTop;");
193         script.append("}");
194         script.append("return x + \",\" + y;");
195         script.append("}");
196 
197         ExternalContext externalContext = facesContext.getExternalContext();
198         String oldViewId = JavascriptUtils.getOldViewId(externalContext);
199         if (oldViewId != null
200                 && oldViewId.equals(facesContext.getViewRoot().getViewId()))
201         {
202             //ok, we stayed on the same page, so let's scroll it to the former place
203             String scrolling = (String) externalContext
204                     .getRequestParameterMap().get(AUTO_SCROLL_PARAM);
205             if (scrolling != null && scrolling.length() > 0)
206             {
207                 double x = 0;
208                 double y = 0;
209                 int comma = scrolling.indexOf(',');
210                 if (comma == -1)
211                 {
212                     log.warning("Illegal autoscroll request parameter: "
213                             + scrolling);
214                 }
215                 else
216                 {
217                     try
218                     {
219                         //we convert to double against XSS vulnerability
220                         x = Double.parseDouble(scrolling.substring(0, comma));
221                     }
222                     catch (NumberFormatException e)
223                     {
224                         log.warning("Error getting x offset for autoscroll feature. Bad param value: "
225                                 + scrolling);
226                         x = 0; //ignore false numbers
227                     }
228 
229                     try
230                     {
231                         //we convert to int against XSS vulnerability
232                         y = Integer.parseInt(scrolling.substring(comma + 1));
233                     }
234                     catch (NumberFormatException e)
235                     {
236                         log.warning("Error getting y offset for autoscroll feature. Bad param value: "
237                                 + scrolling);
238                         y = 0; //ignore false numbers
239                     }
240                 }
241                 script.append("window.scrollTo(").append(String.valueOf(x)).append(",")
242                         .append(String.valueOf(y)).append(");\n");
243             }
244         }
245 
246         return script.toString();
247     }
248     
249     /**
250      * Renders the hidden form input that is necessary for the autoscroll feature.
251      */
252     public static void renderAutoScrollHiddenInput(FacesContext facesContext,
253             ResponseWriter writer) throws IOException
254     {
255         writer.startElement(HTML.INPUT_ELEM, null);
256         writer.writeAttribute(HTML.TYPE_ATTR, "hidden", null);
257         writer.writeAttribute(HTML.NAME_ATTR, AUTO_SCROLL_PARAM, null);
258         writer.endElement(HTML.INPUT_ELEM);
259     }
260 
261     /**
262      * Renders the autoscroll javascript function.
263      */
264     public static void renderAutoScrollFunction(FacesContext facesContext,
265             ResponseWriter writer) throws IOException
266     {
267         writer.startElement(HTML.SCRIPT_ELEM, null);
268         writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR,
269                 HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
270         writer.writeText(getAutoScrollFunction(facesContext), null);
271         writer.endElement(HTML.SCRIPT_ELEM);
272     }
273     
274     public static void appendClearHiddenCommandFormParamsFunctionCall(
275             StringBuilder buf, String formName)
276     {
277         appendClearHiddenCommandFormParamsFunctionCall(new ScriptContext(buf,
278                 false), formName);
279     }
280     
281     private static void appendClearHiddenCommandFormParamsFunctionCall(
282             ScriptContext context, String formName)
283     {
284         String functionName = HtmlRendererUtils
285                 .getClearHiddenCommandFormParamsFunctionName(formName);
286         if (formName == null)
287         {
288             context.prettyLine();
289             context.append("var clearFn = ");
290             context.append(functionName);
291             context.append(";");
292             context.prettyLine();
293             context.append("if(typeof window[clearFn] =='function')");
294             context.append("{");
295             context.append("window[clearFn](formName);");
296             context.append("}");
297         }
298         else
299         {
300             context.prettyLine();
301             context.append("if(typeof window.");
302             context.append(functionName);
303             context.append("=='function')");
304             context.append("{");
305             context.append(functionName).append("('").append(formName)
306                     .append("');");
307             context.append("}");
308         }
309     }
310     
311     /**
312      * Prefixes the given String with "clear_" and removes special characters
313      *
314      * @param formName
315      * @return String
316      */
317     public static String getClearHiddenCommandFormParamsFunctionName(
318             String formName)
319     {
320         final char separatorChar = FacesContext.getCurrentInstance().getNamingContainerSeparatorChar();
321         if (formName == null)
322         {
323             return "'" + HtmlRendererUtils.CLEAR_HIDDEN_FIELD_FN_NAME
324                     + "_'+formName.replace(/-/g, '\\$" + separatorChar
325                     + "').replace(/" + separatorChar + "/g,'_')";
326         }
327 
328         return JavascriptUtils
329                 .getValidJavascriptNameAsInRI(HtmlRendererUtils.CLEAR_HIDDEN_FIELD_FN_NAME + "_"
330                         + formName.replace(separatorChar, '_'));
331     }
332 
333     public static String getClearHiddenCommandFormParamsFunctionNameMyfacesLegacy(
334             String formName)
335     {
336         return "clear_"
337                 + JavascriptUtils.getValidJavascriptName(formName, false);
338     }
339     
340     /**
341      * Render the javascript function that is called on a click on a commandLink
342      * to clear the hidden inputs. This is necessary because on a browser back,
343      * each hidden input still has it's old value (browser cache!) and therefore
344      * a new submit would cause the according action once more!
345      *
346      * @param writer
347      * @param formName
348      * @param dummyFormParams
349      * @param formTarget
350      * @throws IOException
351      */
352     public static void renderClearHiddenCommandFormParamsFunction(
353             ResponseWriter writer, String formName, Set dummyFormParams,
354             String formTarget) throws IOException
355     {
356         //render the clear hidden inputs javascript function
357         String functionName = getClearHiddenCommandFormParamsFunctionName(formName);
358         writer.startElement(HTML.SCRIPT_ELEM, null);
359         writer.writeAttribute(HTML.TYPE_ATTR, "text/javascript", null);
360 
361         // Using writeComment instead of write with <!-- tag
362         StringBuilder script = new StringBuilder();
363         script.append("function ");
364         script.append(functionName);
365         script.append("() {");
366         if (dummyFormParams != null)
367         {
368             script.append("\n  var f = document.forms['");
369             script.append(formName);
370             script.append("'];");
371             int i = 0;
372             for (Iterator it = dummyFormParams.iterator(); it.hasNext();)
373             {
374                 String elemVarName = "elem" + i;
375                 script.append("\n  var ").append(elemVarName).append(" = ");
376                 script.append("f.elements['").append((String) it.next())
377                         .append("'];");
378                 script.append("\n  if(typeof ").append(elemVarName)
379                         .append(" !='undefined' && ");
380                 script.append(elemVarName).append(".nodeName=='INPUT'){");
381                 script.append("\n   if (").append(elemVarName)
382                         .append(".value != '') {");
383                 script.append("\n    " + elemVarName + ".value='';");
384                 script.append("\n   }");
385                 script.append("\n  }");
386                 i++;
387             }
388         }
389         // clear form target
390         script.append("\n  f.target=");
391         if (formTarget == null || formTarget.length() == 0)
392         {
393             //Normally one would think that setting target to null has the
394             //desired effect, but once again IE is different...
395             //Setting target to null causes IE to open a new window!
396             script.append("'';");
397         }
398         else
399         {
400             script.append("'");
401             script.append(formTarget);
402             script.append("';");
403         }
404         script.append("\n}");
405 
406         //Just to be sure we call this clear method on each load.
407         //Otherwise in the case, that someone submits a form by pressing Enter
408         //within a text input, the hidden inputs won't be cleared!
409         script.append("\n");
410         script.append(functionName);
411         script.append("();");
412 
413         writer.writeText(script.toString(), null);
414         writer.endElement(HTML.SCRIPT_ELEM);
415     }
416     
417     /**
418      * This function correctly escapes the given JavaScript code
419      * for the use in the jsf.util.chain() JavaScript function.
420      * It also handles double-escaping correclty.
421      *
422      * @param javaScript
423      * @return
424      */
425     public static String escapeJavaScriptForChain(String javaScript)
426     {
427         // first replace \' with \\'
428         //String escaped = StringUtils.replace(javaScript, "\\'", "\\\\'");
429 
430         // then replace ' with \'
431         // (this will replace every \' in the original to \\\')
432         //escaped = StringUtils.replace(escaped, '\'', "\\'");
433 
434         //return escaped;
435 
436         StringBuilder out = null;
437         for (int pos = 0; pos < javaScript.length(); pos++)
438         {
439             char c = javaScript.charAt(pos);
440 
441             if (c == '\\' || c == '\'')
442             {
443                 if (out == null)
444                 {
445                     out = new StringBuilder(javaScript.length() + 8);
446                     if (pos > 0)
447                     {
448                         out.append(javaScript, 0, pos);
449                     }
450                 }
451                 out.append('\\');
452             }
453             if (out != null)
454             {
455                 out.append(c);
456             }
457         }
458 
459         if (out == null)
460         {
461             return javaScript;
462         }
463         else
464         {
465             return out.toString();
466         }
467     }
468 }