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 }