    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}