    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    }