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