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