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_orchestra.renderkit.html;
20  
21  import org.apache.myfaces.shared_orchestra.config.MyfacesConfig;
22  import org.apache.myfaces.shared_orchestra.renderkit.RendererUtils;
23  import org.apache.myfaces.shared_orchestra.renderkit.JSFAttr;
24  import org.apache.myfaces.shared_orchestra.renderkit.html.util.FormInfo;
25  
26  import javax.faces.FacesException;
27  import javax.faces.application.ViewHandler;
28  import javax.faces.component.UIComponent;
29  import javax.faces.component.UIForm;
30  import javax.faces.component.html.HtmlForm;
31  import javax.faces.context.FacesContext;
32  import javax.faces.context.ResponseWriter;
33  import java.io.IOException;
34  import java.util.HashSet;
35  import java.util.Map;
36  import java.util.Set;
37  
38  /**
39   * @author Manfred Geiler (latest modification by $Author: skitching $)
40   * @author Thomas Spiegl
41   * @author Anton Koinov
42   * @version $Revision: 673826 $ $Date: 2008-07-03 16:43:52 -0500 (Thu, 03 Jul 2008) $
43   */
44  public class HtmlFormRendererBase
45      extends HtmlRenderer {
46      //private static final Log log = LogFactory.getLog(HtmlFormRenderer.class);
47  
48      private static final String HIDDEN_SUBMIT_INPUT_SUFFIX = "_SUBMIT";
49      private static final String HIDDEN_SUBMIT_INPUT_VALUE = "1";
50  
51      private static final String HIDDEN_COMMAND_INPUTS_SET_ATTR
52          = UIForm.class.getName() + ".org.apache.myfaces.HIDDEN_COMMAND_INPUTS_SET";
53  
54      private static final String VALIDATE_CONTEXT_PARAM = "org.apache.myfaces.VALIDATE";
55      private static final String SCROLL_HIDDEN_INPUT = "org.apache.myfaces.SCROLL_HIDDEN_INPUT";
56  
57      /**
58       * Okay, we're going far for compatibility here, but well, we ought to.
59       * this is the parameter that is set with the form id in the RI
60       * if you don't set this parameter, buttons in the RI 1.1. won't work with your form
61       * failing with a javascript error.
62       * <p/>
63       * nice, eh?
64       * <p/>
65       * fixed in the RI in 1.2
66       * <p/>
67       * if you want to know more, search for this comment in the 1.1. RI sources:
68       * <p/>
69       * // look up the clientId of the form in request scope to arrive the name of
70       * // the javascript function to invoke from the onclick event handler.
71       * // PENDING (visvan) we need to fix this dependency between the renderers.
72       * // This solution is only temporary.
73       */
74      private static final String FORM_CLIENT_ID_ATTR = "com.sun.faces.FORM_CLIENT_ID_ATTR";
75  
76  
77      public void encodeBegin(FacesContext facesContext, UIComponent component)
78          throws IOException {
79          org.apache.myfaces.shared_orchestra.renderkit.RendererUtils.checkParamValidity(facesContext, component, UIForm.class);
80  
81          // see comment for FORM_CLIENT_ID_ATTR above - this is entirely for compatibility with RI in version 1.1
82          //todo: move out for 1.2!
83          facesContext.getExternalContext().getRequestMap().put(FORM_CLIENT_ID_ATTR,
84                                                                component.getClientId(facesContext));
85  
86          if ("true".equals(facesContext.getExternalContext().getInitParameter(VALIDATE_CONTEXT_PARAM))) { //todo: should go to MyfacesConfig class!
87              FormInfo info = RendererUtils.findNestingForm(component, facesContext);
88  
89              if (info != null && info.getForm() != null) {
90                  throw new FacesException("You should never nest HTML-forms. " +
91                      "This leads to unpredictable behaviour in all major browsers. " +
92                      "You can use multiple forms on a page, but they may not be nested!" +
93                      "If you need to disable this check (on your own risk!) set the param " +
94                      "org.apache.myfaces.VALIDATE in your web.xml context parameters to false");
95              }
96          }
97  
98          UIForm htmlForm = (UIForm) component;
99  
100         ResponseWriter writer = facesContext.getResponseWriter();
101         String clientId = htmlForm.getClientId(facesContext);
102         String actionURL = getActionUrl(facesContext, htmlForm);
103         String method = getMethod(facesContext, htmlForm);
104         String acceptCharset = getAcceptCharset(facesContext, htmlForm);
105 
106         writer.startElement(HTML.FORM_ELEM, htmlForm);
107         writer.writeAttribute(HTML.ID_ATTR, clientId, null);
108         writer.writeAttribute(HTML.NAME_ATTR, clientId, null);
109         writer.writeAttribute(HTML.METHOD_ATTR, method, null);
110         if((acceptCharset != null) && (acceptCharset.compareTo("")!=0)) writer.writeAttribute(HTML.ACCEPT_CHARSET_ATTR, acceptCharset, null);
111         writer.writeURIAttribute(HTML.ACTION_ATTR,
112                                  facesContext.getExternalContext().encodeActionURL(actionURL),
113                                  null);
114 
115         HtmlRendererUtils.renderHTMLAttributes(writer, htmlForm, HTML.FORM_PASSTHROUGH_ATTRIBUTES);
116 
117         writer.write(""); // force start element tag to be closed
118 
119         // not needed in this version as nothing is written to the form tag, but
120         // included for backward compatibility to the 1.1.1 patch (JIRA MYFACES-1276)
121         // However, might be needed in the future
122         beforeFormElementsStart(facesContext, component);
123         afterFormElementsStart(facesContext, component);
124     }
125 
126     protected String getActionUrl(FacesContext facesContext, UIForm form) {
127         return getActionUrl(facesContext);
128     }
129 
130     protected String getMethod(FacesContext facesContext, UIForm form) {
131         return "post";
132     }
133 
134     protected String getAcceptCharset(FacesContext facesContext, UIForm form ) {
135         return (String)form.getAttributes().get( JSFAttr.ACCEPTCHARSET_ATTR );
136     }
137 
138     public void encodeEnd(FacesContext facesContext, UIComponent component)
139         throws IOException {
140         ResponseWriter writer = facesContext.getResponseWriter();
141 
142         beforeFormElementsEnd(facesContext, component);
143 
144         //write hidden input to determine "submitted" value on decode
145         writer.startElement(HTML.INPUT_ELEM, component);
146         writer.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_HIDDEN, null);
147         writer.writeAttribute(HTML.NAME_ATTR, component.getClientId(facesContext) +
148             HIDDEN_SUBMIT_INPUT_SUFFIX, null);
149         writer.writeAttribute(HTML.VALUE_ATTR, HIDDEN_SUBMIT_INPUT_VALUE, null);
150         writer.endElement(HTML.INPUT_ELEM);
151 
152         renderScrollHiddenInputIfNecessary(component, facesContext, writer);
153 
154         /*
155         see JspViewHandlerImpl.writeState(...)
156         if(!facesContext.getApplication().getStateManager().isSavingStateInClient(facesContext))
157         {
158             writer.startElement(HTML.INPUT_ELEM, component);
159             writer.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_HIDDEN, null);
160             writer.writeAttribute(HTML.NAME_ATTR, org.apache.myfaces.shared_orchestra.renderkit.RendererUtils.SEQUENCE_PARAM, null);
161             writer.writeAttribute(org.apache.myfaces.shared_orchestra.renderkit.html.HTML.VALUE_ATTR, org.apache.myfaces.shared_orchestra.renderkit.RendererUtils.getViewSequence(facesContext), null);
162             writer.endElement(HTML.INPUT_ELEM);
163         }
164         */
165 
166         //render hidden command inputs
167         Set set = (Set) facesContext.getExternalContext().getRequestMap().get(
168             getHiddenCommandInputsSetName(facesContext, component));
169         if (set != null && !set.isEmpty()) {
170             HtmlRendererUtils.renderHiddenCommandFormParams(writer, set);
171 
172             String target;
173             if (component instanceof HtmlForm) {
174                 target = ((HtmlForm) component).getTarget();
175             }
176             else {
177                 target = (String) component.getAttributes().get(HTML.TARGET_ATTR);
178             }
179             HtmlRendererUtils.renderClearHiddenCommandFormParamsFunction(writer,
180                                                                          component.getClientId(facesContext),
181                                                                          set,
182                                                                          target);
183         }
184 
185         //write state marker at the end of the form
186         //Todo: this breaks client-side enabled AJAX components again which are searching for the state
187         //we'll need to fix this
188         ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
189         viewHandler.writeState(facesContext);
190 
191         afterFormElementsEnd(facesContext, component);
192         writer.endElement(HTML.FORM_ELEM);
193     }
194 
195     private static String getHiddenCommandInputsSetName(FacesContext facesContext, UIComponent form) {
196         StringBuffer buf = new StringBuffer();
197         buf.append(HIDDEN_COMMAND_INPUTS_SET_ATTR);
198         buf.append("_");
199         buf.append(form.getClientId(facesContext));
200         return buf.toString();
201     }
202 
203     private static String getScrollHiddenInputName(FacesContext facesContext, UIComponent form) {
204         StringBuffer buf = new StringBuffer();
205         buf.append(SCROLL_HIDDEN_INPUT);
206         buf.append("_");
207         buf.append(form.getClientId(facesContext));
208         return buf.toString();
209     }
210 
211 
212     public void decode(FacesContext facesContext, UIComponent component) {
213         org.apache.myfaces.shared_orchestra.renderkit.RendererUtils.checkParamValidity(facesContext, component, UIForm.class);
214 
215         /*
216         if (HTMLUtil.isDisabled(component))
217         {
218             return;
219         }
220         */
221 
222         UIForm htmlForm = (UIForm) component;
223 
224         Map paramMap = facesContext.getExternalContext().getRequestParameterMap();
225         String submittedValue = (String) paramMap.get(component.getClientId(facesContext) +
226             HIDDEN_SUBMIT_INPUT_SUFFIX);
227         if (submittedValue != null && submittedValue.equals(HIDDEN_SUBMIT_INPUT_VALUE)) {
228             htmlForm.setSubmitted(true);
229         }
230         else {
231             htmlForm.setSubmitted(false);
232         }
233     }
234 
235 
236     public static void addHiddenCommandParameter(FacesContext facesContext, UIComponent form, String paramName) {
237         Set set = (Set) facesContext.getExternalContext().getRequestMap().get(getHiddenCommandInputsSetName(facesContext, form));
238         if (set == null) {
239             set = new HashSet();
240             facesContext.getExternalContext().getRequestMap().put(getHiddenCommandInputsSetName(facesContext, form), set);
241         }
242         set.add(paramName);
243     }
244 
245     public static void renderScrollHiddenInputIfNecessary(UIComponent form, FacesContext facesContext, ResponseWriter writer)
246         throws IOException {
247         if (form == null) {
248             return;
249         }
250 
251         if (facesContext.getExternalContext().getRequestMap().get(getScrollHiddenInputName(facesContext, form)) == null)
252         {
253             if (MyfacesConfig.getCurrentInstance(facesContext.getExternalContext()).isAutoScroll()) {
254                 HtmlRendererUtils.renderAutoScrollHiddenInput(facesContext, writer);
255             }
256             facesContext.getExternalContext().getRequestMap().put(getScrollHiddenInputName(facesContext, form), Boolean.TRUE);
257         }
258     }
259 
260     /**
261      * Called before the state and any elements are added to the form tag in the
262      * encodeBegin method
263      */
264     protected void beforeFormElementsStart(FacesContext facesContext, UIComponent component)
265         throws IOException {
266     }
267 
268     /**
269      * Called after the state and any elements are added to the form tag in the
270      * encodeBegin method
271      */
272     protected void afterFormElementsStart(FacesContext facesContext, UIComponent component)
273         throws IOException {
274     }
275 
276     /**
277      * Called before the state and any elements are added to the form tag in the
278      * encodeEnd method
279      */
280     protected void beforeFormElementsEnd(FacesContext facesContext, UIComponent component)
281         throws IOException {
282     }
283 
284     /**
285      * Called after the state and any elements are added to the form tag in the
286      * encodeEnd method
287      */
288     protected void afterFormElementsEnd(FacesContext facesContext, UIComponent component)
289         throws IOException {
290     }
291 }