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