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