001 /* 002 * Copyright 2012 GWT-Bootstrap 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package com.github.gwtbootstrap.client.ui; 017 018 import com.github.gwtbootstrap.client.ui.base.ComplexWidget; 019 import com.github.gwtbootstrap.client.ui.base.StyleHelper; 020 import com.github.gwtbootstrap.client.ui.constants.FormType; 021 import com.google.gwt.core.client.GWT; 022 import com.google.gwt.core.client.Scheduler; 023 import com.google.gwt.core.client.Scheduler.ScheduledCommand; 024 import com.google.gwt.dom.client.Document; 025 import com.google.gwt.dom.client.Element; 026 import com.google.gwt.dom.client.FormElement; 027 import com.google.gwt.event.shared.EventHandler; 028 import com.google.gwt.event.shared.GwtEvent; 029 import com.google.gwt.event.shared.HandlerRegistration; 030 import com.google.gwt.safehtml.shared.SafeUri; 031 import com.google.gwt.user.client.Event; 032 import com.google.gwt.user.client.ui.RootPanel; 033 import com.google.gwt.user.client.ui.impl.FormPanelImpl; 034 import com.google.gwt.user.client.ui.impl.FormPanelImplHost; 035 036 //@formatter:off 037 /** 038 * Styled HTML form. 039 * 040 * <p> 041 * <h3>UiBinder Usage:</h3> 042 * 043 * <pre> 044 * {@code 045 * <b:Form>...</b:Form>} 046 * </pre> 047 * 048 * </p> 049 * 050 * @since 2.0.4.0 051 * 052 * @author Dominik Mayer 053 * @author ohashi keisuke 054 */ 055 // @formatter:on 056 public class Form extends ComplexWidget implements FormPanelImplHost { 057 058 private static FormPanelImpl impl = GWT.create(FormPanelImpl.class); 059 060 /** 061 * Fired when a form has been submitted successfully. 062 */ 063 public static class SubmitCompleteEvent extends GwtEvent<SubmitCompleteHandler> { 064 065 /** 066 * The event type. 067 */ 068 private static Type<SubmitCompleteHandler> TYPE; 069 070 /** 071 * Handler hook. 072 * 073 * @return the handler hook 074 */ 075 static Type<SubmitCompleteHandler> getType() { 076 if (TYPE == null) { 077 TYPE = new Type<SubmitCompleteHandler>(); 078 } 079 return TYPE; 080 } 081 082 private String resultHtml; 083 084 /** 085 * Create a submit complete event. 086 * 087 * @param resultsHtml 088 * the results from submitting the form 089 */ 090 protected SubmitCompleteEvent(String resultsHtml) { 091 this.resultHtml = resultsHtml; 092 } 093 094 @Override 095 public final Type<SubmitCompleteHandler> getAssociatedType() { 096 return TYPE; 097 } 098 099 /** 100 * Gets the result text of the form submission. 101 * 102 * @return the result html, or <code>null</code> if there was an error 103 * reading it 104 * @tip The result html can be <code>null</code> as a result of 105 * submitting a form to a different domain. 106 */ 107 public String getResults() { 108 return resultHtml; 109 } 110 111 @Override 112 protected void dispatch(SubmitCompleteHandler handler) { 113 handler.onSubmitComplete(this); 114 } 115 } 116 117 /** 118 * Handler for {@link SubmitCompleteEvent} events. 119 */ 120 public interface SubmitCompleteHandler extends EventHandler { 121 122 /** 123 * Fired when a form has been submitted successfully. 124 * 125 * @param event 126 * the event 127 */ 128 void onSubmitComplete(SubmitCompleteEvent event); 129 } 130 131 /** 132 * Fired when the form is submitted. 133 */ 134 public static class SubmitEvent extends GwtEvent<SubmitHandler> { 135 136 /** 137 * The event type. 138 */ 139 private static Type<SubmitHandler> TYPE = new Type<SubmitHandler>(); 140 141 /** 142 * Handler hook. 143 * 144 * @return the handler hook 145 */ 146 static Type<SubmitHandler> getType() { 147 if (TYPE == null) { 148 TYPE = new Type<SubmitHandler>(); 149 } 150 return TYPE; 151 } 152 153 private boolean canceled = false; 154 155 /** 156 * Cancel the form submit. Firing this will prevent a subsequent 157 * {@link SubmitCompleteEvent} from being fired. 158 */ 159 public void cancel() { 160 this.canceled = true; 161 } 162 163 @Override 164 public final Type<SubmitHandler> getAssociatedType() { 165 return TYPE; 166 } 167 168 /** 169 * Gets whether this form submit will be canceled. 170 * 171 * @return <code>true</code> if the form submit will be canceled 172 */ 173 public boolean isCanceled() { 174 return canceled; 175 } 176 177 @Override 178 protected void dispatch(SubmitHandler handler) { 179 handler.onSubmit(this); 180 } 181 } 182 183 /** 184 * Handler for {@link SubmitEvent} events. 185 */ 186 public interface SubmitHandler extends EventHandler { 187 188 /** 189 * Fired when the form is submitted. 190 * 191 * <p> 192 * The FormPanel must <em>not</em> be detached (i.e. removed from its 193 * parent or otherwise disconnected from a {@link RootPanel}) until the 194 * submission is complete. Otherwise, notification of submission will 195 * fail. 196 * </p> 197 * 198 * @param event 199 * the event 200 */ 201 void onSubmit(SubmitEvent event); 202 } 203 204 private static int formId = 0; 205 206 private String frameName; 207 208 private Element synthesizedFrame; 209 210 /** 211 * Creates an empty form. 212 */ 213 public Form() { 214 this(true); 215 } 216 217 public Form(boolean createIFrame) { 218 this(Document.get().createFormElement(), createIFrame); 219 } 220 221 /** 222 * Adds a {@link SubmitCompleteEvent} handler. 223 * 224 * @param handler 225 * the handler 226 * @return the handler registration used to remove the handler 227 */ 228 public HandlerRegistration addSubmitCompleteHandler(SubmitCompleteHandler handler) { 229 return addHandler(handler, SubmitCompleteEvent.getType()); 230 } 231 232 /** 233 * Adds a {@link SubmitEvent} handler. 234 * 235 * @param handler 236 * the handler 237 * @return the handler registration used to remove the handler 238 */ 239 public HandlerRegistration addSubmitHandler(SubmitHandler handler) { 240 return addHandler(handler, SubmitEvent.getType()); 241 } 242 243 /** 244 * Gets the 'action' associated with this form. This is the URL to which it 245 * will be submitted. 246 * 247 * @return the form's action 248 */ 249 public String getAction() { 250 return getFormElement().getAction(); 251 } 252 253 /** 254 * Gets the encoding used for submitting this form. This should be either 255 * {@link #ENCODING_MULTIPART} or {@link #ENCODING_URLENCODED}. 256 * 257 * @return the form's encoding 258 */ 259 public String getEncoding() { 260 return impl.getEncoding(getElement()); 261 } 262 263 /** 264 * Gets the HTTP method used for submitting this form. This should be either 265 * {@link #METHOD_GET} or {@link #METHOD_POST}. 266 * 267 * @return the form's method 268 */ 269 public String getMethod() { 270 return getFormElement().getMethod(); 271 } 272 273 /** 274 * This constructor may be used by subclasses to explicitly use an existing 275 * element. This element must be a <form> element. 276 * 277 * <p> 278 * If the createIFrame parameter is set to <code>true</code>, then the 279 * wrapped form's target attribute will be set to a hidden iframe. If not, 280 * the form's target will be left alone, and the FormSubmitComplete event 281 * will not be fired. 282 * </p> 283 * 284 * @param element 285 * the element to be used 286 * @param createIFrame 287 * <code>true</code> to create an <iframe> element that 288 * will be targeted by this form 289 */ 290 protected Form(Element element, 291 boolean createIFrame) { 292 super(element.getTagName()); 293 FormElement.as(element); 294 295 if (createIFrame) { 296 assert ((getTarget() == null) || (getTarget().trim().length() == 0)) : "Cannot create target iframe if the form's target is already set."; 297 298 // We use the module name as part of the unique ID to ensure that 299 // ids are 300 // unique across modules. 301 frameName = "FormPanel_" + GWT.getModuleName() + "_" + (++formId); 302 setTarget(frameName); 303 304 sinkEvents(Event.ONLOAD); 305 } 306 } 307 308 public String getTarget() { 309 return getFormElement().getTarget(); 310 } 311 312 private FormElement getFormElement() { 313 return getElement().cast(); 314 } 315 316 /** 317 * Sets the type of the form. 318 * 319 * @param type 320 * the form's type 321 */ 322 public void setType(FormType type) { 323 StyleHelper.changeStyle(this, type, FormType.class); 324 } 325 326 /** 327 * Resets the form, clearing all fields. 328 */ 329 public void reset() { 330 impl.reset(getElement()); 331 } 332 333 /** 334 * Sets the 'action' associated with this form. This is the URL to which it 335 * will be submitted. 336 * 337 * @param url 338 * the form's action 339 */ 340 public void setAction(String url) { 341 getFormElement().setAction(url); 342 } 343 344 /** 345 * Sets the 'action' associated with this form. This is the URL to which it 346 * will be submitted. 347 * 348 * @param url 349 * the form's action 350 */ 351 public void setAction(SafeUri url) { 352 setAction(url.asString()); 353 } 354 355 /** 356 * Sets the encoding used for submitting this form. This should be either 357 * {@link #ENCODING_MULTIPART} or {@link #ENCODING_URLENCODED}. 358 * 359 * @param encodingType 360 * the form's encoding 361 */ 362 public void setEncoding(String encodingType) { 363 impl.setEncoding(getElement(), encodingType); 364 } 365 366 /** 367 * Sets the HTTP method used for submitting this form. This should be either 368 * {@link #METHOD_GET} or {@link #METHOD_POST}. 369 * 370 * @param method 371 * the form's method 372 */ 373 public void setMethod(String method) { 374 getFormElement().setMethod(method); 375 } 376 377 /** 378 * Submits the form. 379 * 380 * <p> 381 * The FormPanel must <em>not</em> be detached (i.e. removed from its parent 382 * or otherwise disconnected from a {@link RootPanel}) until the submission 383 * is complete. Otherwise, notification of submission will fail. 384 * </p> 385 */ 386 public void submit() { 387 // Fire the onSubmit event, because javascript's form.submit() does not 388 // fire the built-in onsubmit event. 389 if (!fireSubmitEvent()) { 390 return; 391 } 392 393 impl.submit(getElement(), synthesizedFrame); 394 } 395 396 @Override 397 protected void onAttach() { 398 super.onAttach(); 399 400 if (frameName != null) { 401 // Create and attach a hidden iframe to the body element. 402 createFrame(); 403 Document.get().getBody().appendChild(synthesizedFrame); 404 } 405 // Hook up the underlying iframe's onLoad event when attached to the 406 // DOM. 407 // Making this connection only when attached avoids memory-leak issues. 408 // The FormPanel cannot use the built-in GWT event-handling mechanism 409 // because there is no standard onLoad event on iframes that works 410 // across 411 // browsers. 412 impl.hookEvents(synthesizedFrame, getElement(), this); 413 } 414 415 @Override 416 protected void onDetach() { 417 super.onDetach(); 418 419 // Unhook the iframe's onLoad when detached. 420 impl.unhookEvents(synthesizedFrame, getElement()); 421 422 if (synthesizedFrame != null) { 423 // And remove it from the document. 424 Document.get().getBody().removeChild(synthesizedFrame); 425 synthesizedFrame = null; 426 } 427 } 428 429 private void createFrame() { 430 // Attach a hidden IFrame to the form. This is the target iframe to 431 // which 432 // the form will be submitted. We have to create the iframe using 433 // innerHTML, 434 // because setting an iframe's 'name' property dynamically doesn't work 435 // on 436 // most browsers. 437 Element dummy = Document.get().createDivElement(); 438 dummy.setInnerHTML("<iframe src=\"javascript:''\" name='" + frameName + "' style='position:absolute;width:0;height:0;border:0'>"); 439 440 synthesizedFrame = dummy.getFirstChildElement(); 441 } 442 443 /** 444 * Fire a {@link SubmitEvent}. 445 * 446 * @return true to continue, false if canceled 447 */ 448 private boolean fireSubmitEvent() { 449 Form.SubmitEvent event = new Form.SubmitEvent(); 450 fireEvent(event); 451 return !event.isCanceled(); 452 } 453 454 /** 455 * Returns true if the form is submitted, false if canceled. 456 */ 457 private boolean onFormSubmitImpl() { 458 return fireSubmitEvent(); 459 } 460 461 private void onFrameLoadImpl() { 462 463 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 464 465 @Override 466 public void execute() { 467 fireEvent(new SubmitCompleteEvent(impl.getContents(synthesizedFrame))); 468 } 469 }); 470 } 471 472 public void setTarget(String target) { 473 getFormElement().setTarget(target); 474 } 475 476 @Override 477 public boolean onFormSubmit() { 478 return onFormSubmitImpl(); 479 } 480 481 @Override 482 public void onFrameLoad() { 483 onFrameLoadImpl(); 484 } 485 486 }