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
015package org.apache.tapestry5.corelib.components;
016
017import org.apache.tapestry5.*;
018import org.apache.tapestry5.annotations.Environmental;
019import org.apache.tapestry5.annotations.Events;
020import org.apache.tapestry5.annotations.Parameter;
021import org.apache.tapestry5.annotations.SupportsInformalParameters;
022import org.apache.tapestry5.corelib.SubmitMode;
023import org.apache.tapestry5.ioc.annotations.Inject;
024import org.apache.tapestry5.ioc.internal.util.InternalUtils;
025import org.apache.tapestry5.json.JSONArray;
026import org.apache.tapestry5.json.JSONObject;
027import org.apache.tapestry5.services.FormSupport;
028import org.apache.tapestry5.services.Heartbeat;
029import org.apache.tapestry5.services.Request;
030import org.apache.tapestry5.services.javascript.InitializationPriority;
031import 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")
042public 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}