001    // Copyright 2008, 2009, 2010, 2011, 2012 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.Events;
020    import org.apache.tapestry5.annotations.Parameter;
021    import org.apache.tapestry5.annotations.SupportsInformalParameters;
022    import org.apache.tapestry5.corelib.SubmitMode;
023    import org.apache.tapestry5.ioc.annotations.Inject;
024    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
025    import org.apache.tapestry5.json.JSONArray;
026    import org.apache.tapestry5.json.JSONObject;
027    import org.apache.tapestry5.services.FormSupport;
028    import org.apache.tapestry5.services.Heartbeat;
029    import org.apache.tapestry5.services.Request;
030    import org.apache.tapestry5.services.javascript.InitializationPriority;
031    import org.apache.tapestry5.services.javascript.JavaScriptSupport;
032    
033    /**
034     * Generates a client-side hyperlink that submits the enclosing form. If the link is clicked in the browser, the
035     * component will trigger an event ({@linkplain EventConstants#SELECTED selected} by default) , just like {@link Submit}
036     * .
037     *
038     * @tapestrydoc
039     */
040    @SupportsInformalParameters
041    @Events(EventConstants.SELECTED + " by default, may be overridden")
042    public class LinkSubmit implements ClientElement
043    {
044        /**
045         * If true, then no link (or accompanying JavaScript) is written (though the body still is).
046         */
047        @Parameter
048        private boolean disabled;
049    
050        /**
051         * The name of the event that will be triggered if this component is the cause of the form submission. The default
052         * is "selected".
053         */
054        @Parameter(allowNull = false, defaultPrefix = BindingConstants.LITERAL)
055        private String event = EventConstants.SELECTED;
056    
057        /**
058         * Defines the mode, or client-side behavior, for the submit. The default is {@link SubmitMode#NORMAL}; clicking the
059         * button submits the form with validation. {@link SubmitMode#CANCEL} indicates the form should be submitted as a cancel,
060         * with no client-side validation. {@link SubmitMode#UNCONDITIONAL} bypasses client-side validation, but does not indicate
061         * that the form was cancelled.
062         *
063         * @see EventConstants#CANCELED
064         * @since 5.2.0
065         */
066        @Parameter(allowNull = false, defaultPrefix = BindingConstants.LITERAL)
067        private SubmitMode mode = SubmitMode.NORMAL;
068    
069        /**
070         * If true (the default), then any notification sent by the component will be deferred until the end of the form
071         * submission (this is usually desirable). In general, this can be left as the default except when the LinkSubmit
072         * component is rendering inside a {@link Loop}, in which case defer should be bound to false (otherwise, the
073         * event context will always be the final value of the Loop).
074         */
075        @Parameter
076        private boolean defer = true;
077    
078        /**
079         * The list of values that will be made available to event handler method of this component when the form is
080         * submitted.
081         *
082         * @since 5.2.0
083         */
084        @Parameter
085        private Object[] context;
086    
087        @Inject
088        private ComponentResources resources;
089    
090        @Inject
091        private JavaScriptSupport javascriptSupport;
092    
093        @Environmental
094        private FormSupport formSupport;
095    
096        @Environmental
097        private Heartbeat heartbeat;
098    
099        @Inject
100        private Request request;
101    
102        @SuppressWarnings("unchecked")
103        @Environmental
104        private TrackableComponentEventCallback eventCallback;
105    
106        private String clientId;
107    
108        private static class ProcessSubmission implements ComponentAction<LinkSubmit>
109        {
110            private final String clientId;
111    
112            public ProcessSubmission(String clientId)
113            {
114                this.clientId = clientId;
115            }
116    
117            public void execute(LinkSubmit component)
118            {
119                component.processSubmission(clientId);
120            }
121        }
122    
123        private void processSubmission(String clientId)
124        {
125            this.clientId = clientId;
126    
127            String raw = request.getParameter(Form.SUBMITTING_ELEMENT_ID);
128    
129            if (InternalUtils.isNonBlank(raw) && new JSONArray(raw).getString(0).equals(clientId))
130            {
131                Runnable notification = new Runnable()
132                {
133                    public void run()
134                    {
135                        resources.triggerEvent(event, context, eventCallback);
136                    }
137                };
138    
139                if (defer)
140                    formSupport.defer(notification);
141                else
142                    heartbeat.defer(notification);
143            }
144        }
145    
146        void beginRender(MarkupWriter writer)
147        {
148            if (!disabled)
149            {
150                clientId = javascriptSupport.allocateClientId(resources);
151    
152                formSupport.store(this, new ProcessSubmission(clientId));
153    
154                writer.element("span",
155    
156                        "id", clientId);
157    
158                resources.renderInformalParameters(writer);
159            }
160        }
161    
162        void afterRender(MarkupWriter writer)
163        {
164            if (!disabled)
165            {
166                writer.end();
167    
168                JSONObject spec = new JSONObject("form", formSupport.getClientId(), "clientId", clientId);
169    
170                spec.put("mode", mode.name().toLowerCase());
171    
172                javascriptSupport.addInitializerCall(InitializationPriority.EARLY, "linkSubmit", spec);
173            }
174        }
175    
176        public String getClientId()
177        {
178            return clientId;
179        }
180    }