    001// Copyright 2009, 2010, 2011 The Apache Software Foundation
    002//
    003// Licensed under the Apache License, Version 2.0 (the "License");
    004// you may not use this file except in compliance with the License.
    005// You may obtain a copy of the License at
    006//
    007// http://www.apache.org/licenses/LICENSE-2.0
    008//
    009// Unless required by applicable law or agreed to in writing, software
    010// distributed under the License is distributed on an "AS IS" BASIS,
    011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012// See the License for the specific language governing permissions and
    013// limitations under the License.
    014
    015package org.apache.tapestry5.corelib.components;
    016
    017import org.apache.tapestry5.*;
    018import org.apache.tapestry5.annotations.Environmental;
    019import org.apache.tapestry5.annotations.Parameter;
    020import org.apache.tapestry5.annotations.SupportsInformalParameters;
    021import org.apache.tapestry5.dom.Element;
    022import org.apache.tapestry5.ioc.annotations.Inject;
    023import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    024import org.apache.tapestry5.services.ComponentDefaultProvider;
    025import org.apache.tapestry5.services.FormSupport;
    026import org.apache.tapestry5.services.Request;
    027import org.apache.tapestry5.services.javascript.JavaScriptSupport;
    028
    029/**
    030 * Used to record a page property as a value into the form. The value is encoded
    031 * when the form is rendered, then decoded after the form is submitted back to
    032 * the server, and the "value" parameter updated.
    033 * <p/>
    034 * The encoding and decoding is done via a {@link org.apache.tapestry5.ValueEncoder},
    035 * therefore you must either bind the "encoder" parameter to a ValueEncoder or
    036 * use an entity type for the "value" parameter for which Tapestry can provide a
    037 * ValueEncoder automatically.
    038 *
    039 * @tapestrydoc
    040 * @since 5.1.0.2
    041 */
    042@SupportsInformalParameters
    043public class Hidden implements ClientElement
    044{
    045    /**
    046     * The value to read (when rendering) or update (when the form is submitted).
    047     */
    048    @Parameter(required = true, autoconnect = true, principal = true)
    049    private Object value;
    050
    051    /**
    052     * Defines how nulls on the server side, or sent from the client side, are treated. The selected strategy may
    053     * replace the nulls with some other value. The default strategy leaves nulls alone.  Another built-in strategy,
    054     * zero, replaces nulls with the value 0.
    055     */
    056    @Parameter(defaultPrefix = BindingConstants.NULLFIELDSTRATEGY, value = "default")
    057    private NullFieldStrategy nulls;
    058
    059    /**
    060     * A ValueEncoder used to convert the server-side object provided by the
    061     * "value" parameter into a unique client-side string (typically an ID) and
    062     * back. Note: this parameter may be OMITTED if Tapestry is configured to
    063     * provide a ValueEncoder automatically for the type of property bound to
    064     * the "value" parameter.
    065     */
    066    @Parameter(required = true)
    067    private ValueEncoder encoder;
    068
    069    private String clientId;
    070
    071    private String controlName;
    072
    073    private Element hiddenInputElement;
    074
    075    @Environmental(false)
    076    private FormSupport formSupport;
    077
    078    @Environmental
    079    private JavaScriptSupport jsSupport;
    080
    081    @Inject
    082    private ComponentResources resources;
    083
    084    @Inject
    085    private ComponentDefaultProvider defaultProvider;
    086
    087    @Inject
    088    private Request request;
    089
    090    ValueEncoder defaultEncoder()
    091    {
    092        return defaultProvider.defaultValueEncoder("value", resources);
    093    }
    094
    095    static class ProcessSubmission implements ComponentAction<Hidden>
    096    {
    097        private final String controlName;
    098
    099        public ProcessSubmission(String controlName)
    100        {
    101            this.controlName = controlName;
    102        }
    103
    104        public void execute(Hidden component)
    105        {
    106            component.processSubmission(controlName);
    107        }
    108    }
    109
    110    boolean beginRender(MarkupWriter writer)
    111    {
    112        if (formSupport == null)
    113        {
    114            throw new RuntimeException("The Hidden component must be enclosed by a Form component.");
    115        }
    116
    117        controlName = formSupport.allocateControlName(resources.getId());
    118
    119        clientId = null;
    120
    121        formSupport.store(this, new ProcessSubmission(controlName));
    122
    123        Object toEncode = value == null ? nulls.replaceToClient() : value;
    124
    125        String encoded = toEncode == null ? "" : encoder.toClient(toEncode);
    126
    127        hiddenInputElement = writer.element("input", "type", "hidden", "name", controlName, "value", encoded);
    128
    129        resources.renderInformalParameters(writer);
    130
    131        writer.end();
    132
    133        return false;
    134    }
    135
    136    private void processSubmission(String controlName)
    137    {
    138        String encoded = request.getParameter(controlName);
    139
    140        String toDecode = InternalUtils.isBlank(encoded) ? nulls.replaceFromClient() : encoded;
    141
    142        Object decoded = toDecode == null ? null : encoder.toValue(toDecode);
    143
    144        value = decoded;
    145    }
    146
    147    public String getClientId()
    148    {
    149        if (clientId == null)
    150        {
    151            clientId = jsSupport.allocateClientId(resources);
    152            hiddenInputElement.forceAttributes("id", clientId);
    153        }
    154
    155        return clientId;
    156    }
    157
    158    public String getControlName()
    159    {
    160        return controlName;
    161    }
    162}