    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
    015    package org.apache.tapestry5.corelib.components;
    016
    017    import org.apache.tapestry5.*;
    018    import org.apache.tapestry5.annotations.Environmental;
    019    import org.apache.tapestry5.annotations.Parameter;
    020    import org.apache.tapestry5.annotations.SupportsInformalParameters;
    021    import org.apache.tapestry5.dom.Element;
    022    import org.apache.tapestry5.ioc.annotations.Inject;
    023    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    024    import org.apache.tapestry5.services.ComponentDefaultProvider;
    025    import org.apache.tapestry5.services.FormSupport;
    026    import org.apache.tapestry5.services.Request;
    027    import 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
    043    public 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    }