001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * 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, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.user.client.ui;
017:
018: import com.google.gwt.core.client.GWT;
019: import com.google.gwt.user.client.Command;
020: import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
021: import com.google.gwt.user.client.DOM;
022: import com.google.gwt.user.client.DeferredCommand;
023: import com.google.gwt.user.client.Element;
024: import com.google.gwt.user.client.Event;
025: import com.google.gwt.user.client.ui.impl.FormPanelImpl;
026: import com.google.gwt.user.client.ui.impl.FormPanelImplHost;
027:
028: /**
029: * A panel that wraps its contents in an HTML <FORM> element.
030: *
031: * <p>
032: * This panel can be used to achieve interoperability with servers that accept
033: * traditional HTML form encoding. The following widgets (those that implement
034: * {@link com.google.gwt.user.client.ui.HasName}) will be submitted to the
035: * server if they are contained within this panel:
036: * <ul>
037: * <li>{@link com.google.gwt.user.client.ui.TextBox}</li>
038: * <li>{@link com.google.gwt.user.client.ui.PasswordTextBox}</li>
039: * <li>{@link com.google.gwt.user.client.ui.RadioButton}</li>
040: * <li>{@link com.google.gwt.user.client.ui.CheckBox}</li>
041: * <li>{@link com.google.gwt.user.client.ui.TextArea}</li>
042: * <li>{@link com.google.gwt.user.client.ui.ListBox}</li>
043: * <li>{@link com.google.gwt.user.client.ui.FileUpload}</li>
044: * <li>{@link com.google.gwt.user.client.ui.Hidden}</li>
045: * </ul>
046: * In particular, {@link com.google.gwt.user.client.ui.FileUpload} is <i>only</i>
047: * useful when used within a FormPanel, because the browser will only upload
048: * files using form submission.
049: * </p>
050: *
051: * <p>
052: * <h3>Example</h3>
053: * {@example com.google.gwt.examples.FormPanelExample}
054: * </p>
055: */
056: public class FormPanel extends SimplePanel implements FiresFormEvents,
057: FormPanelImplHost {
058:
059: /**
060: * Used with {@link #setEncoding(String)} to specify that the form will be
061: * submitted using MIME encoding (necessary for {@link FileUpload} to work
062: * properly).
063: */
064: public static final String ENCODING_MULTIPART = "multipart/form-data";
065:
066: /**
067: * Used with {@link #setEncoding(String)} to specify that the form will be
068: * submitted using traditional URL encoding.
069: */
070: public static final String ENCODING_URLENCODED = "application/x-www-form-urlencoded";
071:
072: /**
073: * Used with {@link #setMethod(String)} to specify that the form will be
074: * submitted using an HTTP GET request.
075: */
076: public static final String METHOD_GET = "get";
077:
078: /**
079: * Used with {@link #setMethod(String)} to specify that the form will be
080: * submitted using an HTTP POST request (necessary for {@link FileUpload} to
081: * work properly).
082: */
083: public static final String METHOD_POST = "post";
084:
085: private static int formId = 0;
086: private static FormPanelImpl impl = GWT.create(FormPanelImpl.class);
087:
088: private FormHandlerCollection formHandlers;
089: private String frameName;
090: private Element iframe;
091:
092: /**
093: * Creates a new FormPanel. When created using this constructor, it will be
094: * submitted to a hidden <iframe> element, and the results of the
095: * submission made available via {@link FormHandler}.
096: *
097: * <p>
098: * The back-end server is expected to respond with a content-type of
099: * 'text/html', meaning that the text returned will be treated as HTML. If any
100: * other content-type is specified by the server, then the result html sent in
101: * the onFormSubmit event will be unpredictable across browsers, and the
102: * {@link FormHandler#onSubmitComplete(FormSubmitCompleteEvent)} event may not
103: * fire at all.
104: * </p>
105: *
106: * @tip The initial implementation of FormPanel specified that the server
107: * respond with a content-type of 'text/plain'. This has been
108: * intentionally changed to specify 'text/html' because 'text/plain'
109: * cannot be made to work properly on all browsers.
110: */
111: public FormPanel() {
112: super (DOM.createForm());
113:
114: frameName = "FormPanel_" + (++formId);
115: setTarget(frameName);
116:
117: sinkEvents(Event.ONLOAD);
118: }
119:
120: /**
121: * Creates a FormPanel that targets a {@link NamedFrame}. The target frame is
122: * not physically attached to the form, and must therefore still be added to a
123: * panel elsewhere.
124: *
125: * <p>
126: * When the FormPanel targets an external frame in this way, it will not fire
127: * the onFormSubmit event.
128: * </p>
129: *
130: * @param frameTarget the {@link NamedFrame} to be targetted
131: */
132: public FormPanel(NamedFrame frameTarget) {
133: this (frameTarget.getName());
134: }
135:
136: /**
137: * Creates a new FormPanel. When created using this constructor, it will be
138: * submitted either by replacing the current page, or to the named
139: * <iframe>.
140: *
141: * <p>
142: * When the FormPanel targets an external frame in this way, it will not fire
143: * the onFormSubmit event.
144: * </p>
145: *
146: * @param target the name of the <iframe> to receive the results of the
147: * submission, or <code>null</code> to specify that the current
148: * page be replaced
149: */
150: public FormPanel(String target) {
151: super (DOM.createForm());
152: setTarget(target);
153: }
154:
155: public void addFormHandler(FormHandler handler) {
156: if (formHandlers == null) {
157: formHandlers = new FormHandlerCollection();
158: }
159: formHandlers.add(handler);
160: }
161:
162: /**
163: * Gets the 'action' associated with this form. This is the URL to which it
164: * will be submitted.
165: *
166: * @return the form's action
167: */
168: public String getAction() {
169: return DOM.getElementProperty(getElement(), "action");
170: }
171:
172: /**
173: * Gets the encoding used for submitting this form. This should be either
174: * {@link #ENCODING_MULTIPART} or {@link #ENCODING_URLENCODED}.
175: *
176: * @return the form's encoding
177: */
178: public String getEncoding() {
179: return impl.getEncoding(getElement());
180: }
181:
182: /**
183: * Gets the HTTP method used for submitting this form. This should be either
184: * {@link #METHOD_GET} or {@link #METHOD_POST}.
185: *
186: * @return the form's method
187: */
188: public String getMethod() {
189: return DOM.getElementProperty(getElement(), "method");
190: }
191:
192: /**
193: * Gets the form's 'target'. This is the name of the {@link NamedFrame} that
194: * will receive the results of submission, or <code>null</code> if none has
195: * been specified.
196: *
197: * @return the form's target.
198: */
199: public String getTarget() {
200: return DOM.getElementProperty(getElement(), "target");
201: }
202:
203: public boolean onFormSubmit() {
204: UncaughtExceptionHandler handler = GWT
205: .getUncaughtExceptionHandler();
206: if (handler != null) {
207: return onFormSubmitAndCatch(handler);
208: } else {
209: return onFormSubmitImpl();
210: }
211: }
212:
213: public void onFrameLoad() {
214: UncaughtExceptionHandler handler = GWT
215: .getUncaughtExceptionHandler();
216: if (handler != null) {
217: onFrameLoadAndCatch(handler);
218: } else {
219: onFrameLoadImpl();
220: }
221: }
222:
223: public void removeFormHandler(FormHandler handler) {
224: if (formHandlers != null) {
225: formHandlers.remove(handler);
226: }
227: }
228:
229: /**
230: * Sets the 'action' associated with this form. This is the URL to which it
231: * will be submitted.
232: *
233: * @param url the form's action
234: */
235: public void setAction(String url) {
236: DOM.setElementProperty(getElement(), "action", url);
237: }
238:
239: /**
240: * Sets the encoding used for submitting this form. This should be either
241: * {@link #ENCODING_MULTIPART} or {@link #ENCODING_URLENCODED}.
242: *
243: * @param encodingType the form's encoding
244: */
245: public void setEncoding(String encodingType) {
246: impl.setEncoding(getElement(), encodingType);
247: }
248:
249: /**
250: * Sets the HTTP method used for submitting this form. This should be either
251: * {@link #METHOD_GET} or {@link #METHOD_POST}.
252: *
253: * @param method the form's method
254: */
255: public void setMethod(String method) {
256: DOM.setElementProperty(getElement(), "method", method);
257: }
258:
259: /**
260: * Submits the form.
261: *
262: * <p>
263: * The FormPanel must <em>not</em> be detached (i.e. removed from its parent
264: * or otherwise disconnected from a {@link RootPanel}) until the submission
265: * is complete. Otherwise, notification of submission will fail.
266: * </p>
267: */
268: public void submit() {
269: // Fire the onSubmit event, because javascript's form.submit() does not
270: // fire the built-in onsubmit event.
271: if (formHandlers != null) {
272: if (formHandlers.fireOnSubmit(this )) {
273: return;
274: }
275: }
276:
277: impl.submit(getElement(), iframe);
278: }
279:
280: @Override
281: protected void onAttach() {
282: super .onAttach();
283:
284: // Create and attach a hidden iframe to the body element.
285: createFrame();
286: DOM.appendChild(RootPanel.getBodyElement(), iframe);
287:
288: // Hook up the underlying iframe's onLoad event when attached to the DOM.
289: // Making this connection only when attached avoids memory-leak issues.
290: // The FormPanel cannot use the built-in GWT event-handling mechanism
291: // because there is no standard onLoad event on iframes that works across
292: // browsers.
293: impl.hookEvents(iframe, getElement(), this );
294: }
295:
296: @Override
297: protected void onDetach() {
298: super .onDetach();
299:
300: // Unhook the iframe's onLoad when detached.
301: impl.unhookEvents(iframe, getElement());
302:
303: DOM.removeChild(RootPanel.getBodyElement(), iframe);
304: iframe = null;
305: }
306:
307: private void createFrame() {
308: // Attach a hidden IFrame to the form. This is the target iframe to which
309: // the form will be submitted. We have to create the iframe using innerHTML,
310: // because setting an iframe's 'name' property dynamically doesn't work on
311: // most browsers.
312: Element dummy = DOM.createDiv();
313: DOM
314: .setInnerHTML(
315: dummy,
316: "<iframe src=\"javascript:''\" name='"
317: + frameName
318: + "' style='position:absolute;width:0;height:0;border:0'>");
319:
320: iframe = DOM.getFirstChild(dummy);
321: }
322:
323: private boolean onFormSubmitAndCatch(
324: UncaughtExceptionHandler handler) {
325: try {
326: return onFormSubmitImpl();
327: } catch (Throwable e) {
328: handler.onUncaughtException(e);
329: return false;
330: }
331: }
332:
333: private boolean onFormSubmitImpl() {
334: if (formHandlers != null) {
335: // fireOnSubmit() returns true if the submit should be cancelled
336: return !formHandlers.fireOnSubmit(this );
337: }
338:
339: return true;
340: }
341:
342: private void onFrameLoadAndCatch(UncaughtExceptionHandler handler) {
343: try {
344: onFrameLoadImpl();
345: } catch (Throwable e) {
346: handler.onUncaughtException(e);
347: }
348: }
349:
350: private void onFrameLoadImpl() {
351: if (formHandlers != null) {
352: // Fire onComplete events in a deferred command. This is necessary
353: // because clients that detach the form panel when submission is
354: // complete can cause some browsers (i.e. Mozilla) to go into an
355: // 'infinite loading' state. See issue 916.
356: DeferredCommand.addCommand(new Command() {
357: public void execute() {
358: formHandlers.fireOnComplete(this , impl
359: .getContents(iframe));
360: }
361: });
362: }
363: }
364:
365: private void setTarget(String target) {
366: DOM.setElementProperty(getElement(), "target", target);
367: }
368: }
|