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    }