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 }