001 // Copyright 2007, 2008, 2009, 2010, 2011 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.internal.InternalConstants; 024 import org.apache.tapestry5.ioc.annotations.Inject; 025 import org.apache.tapestry5.json.JSONArray; 026 import org.apache.tapestry5.services.FormSupport; 027 import org.apache.tapestry5.services.Heartbeat; 028 import org.apache.tapestry5.services.Request; 029 import org.apache.tapestry5.services.javascript.JavaScriptSupport; 030 031 /** 032 * Corresponds to <input type="submit"> or <input type="image">, a client-side element that can force the 033 * enclosing form to submit. The submit responsible for the form submission will post a notification that allows the 034 * application to know that it was the responsible entity. The notification is named "selected" and has no context. 035 * 036 * @tapestrydoc 037 */ 038 @SupportsInformalParameters 039 @Events(EventConstants.SELECTED + " by default, may be overridden") 040 public class Submit implements ClientElement 041 { 042 /** 043 * If true (the default), then any notification sent by the component will be deferred until the end of the form 044 * submission (this is usually desirable). In general, this can be left as the default except when the Submit 045 * component is rendering inside a {@link Loop}, in which case defer should be bound to false (otherwise, the 046 * event context will always be the final value of the Loop). 047 */ 048 @Parameter 049 private boolean defer = true; 050 051 /** 052 * The name of the event that will be triggered if this component is the cause of the form submission. The default 053 * is {@link EventConstants#SELECTED}. 054 */ 055 @Parameter(allowNull = false, defaultPrefix = BindingConstants.LITERAL) 056 private String event = EventConstants.SELECTED; 057 058 /** 059 * If true, then the field will render out with a disabled attribute 060 * (to turn off client-side behavior). When the form is submitted, the 061 * bound value is evaluated again and, if true, the field's value is 062 * ignored (not even validated) and the component's events are not fired. 063 */ 064 @Parameter("false") 065 private boolean disabled; 066 067 /** 068 * The list of values that will be made available to event handler method of this component when the form is 069 * submitted. 070 * 071 * @since 5.1.0.0 072 */ 073 @Parameter 074 private Object[] context; 075 076 /** 077 * If provided, the component renders an input tag with type "image". Otherwise "submit". 078 * 079 * @since 5.1.0.0 080 */ 081 @Parameter(defaultPrefix = BindingConstants.ASSET) 082 private Asset image; 083 084 /** 085 * Defines the mode, or client-side behavior, for the submit. The default is {@link SubmitMode#NORMAL}; clicking the 086 * button submits the form with validation. {@link SubmitMode#CANCEL} indicates the client-side validation 087 * should be omitted (though server-side validation still occurs). 088 * 089 * @since 5.2.0 090 */ 091 @Parameter(allowNull = false, defaultPrefix = BindingConstants.LITERAL) 092 private SubmitMode mode = SubmitMode.NORMAL; 093 094 @Environmental 095 private FormSupport formSupport; 096 097 @Environmental 098 private Heartbeat heartbeat; 099 100 @Inject 101 private ComponentResources resources; 102 103 @Inject 104 private Request request; 105 106 @Inject 107 private JavaScriptSupport javascriptSupport; 108 109 @SuppressWarnings("unchecked") 110 @Environmental 111 private TrackableComponentEventCallback eventCallback; 112 113 private String clientId; 114 115 private static class ProcessSubmission implements ComponentAction<Submit> 116 { 117 private final String clientId, elementName; 118 119 public ProcessSubmission(String clientId, String elementName) 120 { 121 this.clientId = clientId; 122 this.elementName = elementName; 123 } 124 125 public void execute(Submit component) 126 { 127 component.processSubmission(clientId, elementName); 128 } 129 } 130 131 public Submit() 132 { 133 } 134 135 Submit(Request request) 136 { 137 this.request = request; 138 } 139 140 void beginRender(MarkupWriter writer) 141 { 142 clientId = javascriptSupport.allocateClientId(resources); 143 144 boolean isCancel = mode == SubmitMode.CANCEL; 145 146 String name = 147 isCancel ? InternalConstants.CANCEL_NAME : 148 formSupport.allocateControlName(resources.getId()); 149 150 // Save the element, to see if an id is later requested. 151 152 String type = image == null ? "submit" : "image"; 153 154 writer.element("input", 155 156 "type", type, 157 158 "name", name, 159 160 "id", clientId); 161 162 if (disabled) 163 { 164 writer.attributes("disabled", "disabled"); 165 } 166 167 if (image != null) 168 { 169 writer.attributes("src", image.toClientURL()); 170 } 171 172 formSupport.store(this, new ProcessSubmission(clientId, name)); 173 174 resources.renderInformalParameters(writer); 175 176 if (isCancel) 177 { 178 javascriptSupport.addInitializerCall("cancelButton", getClientId()); 179 } 180 181 } 182 183 void afterRender(MarkupWriter writer) 184 { 185 writer.end(); 186 } 187 188 void processSubmission(String clientId, String elementName) 189 { 190 if (disabled || !selected(clientId, elementName)) 191 return; 192 193 Runnable sendNotification = new Runnable() 194 { 195 public void run() 196 { 197 // TAP5-1024: allow for navigation result from the event callback 198 resources.triggerEvent(event, context, eventCallback); 199 } 200 }; 201 202 // When not deferred, don't wait, fire the event now (actually, at the end of the current 203 // heartbeat). This is most likely because the Submit is inside a Loop and some contextual 204 // information will change if we defer. 205 206 if (defer) 207 formSupport.defer(sendNotification); 208 else 209 heartbeat.defer(sendNotification); 210 } 211 212 private boolean selected(String clientId, String elementName) 213 { 214 // Case #1: via JavaScript, the client id is passed up. 215 216 String raw = request.getParameter(Form.SUBMITTING_ELEMENT_ID); 217 218 if (raw != null && 219 new JSONArray(raw).getString(0).equals(clientId)) 220 { 221 return true; 222 } 223 224 // Case #2: No JavaScript, look for normal semantic (non-null value for the element's name). 225 // If configured as an image submit, look for a value for the x position. Ah, the ugliness 226 // of HTML. 227 228 String name = image == null ? elementName : elementName + ".x"; 229 230 String value = request.getParameter(name); 231 232 return value != null; 233 } 234 235 /** 236 * Returns the component's client id. This must be called after the component has rendered. 237 * 238 * @return client id for the component 239 */ 240 public String getClientId() 241 { 242 return clientId; 243 } 244 }