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 }