Java tutorial
/* * Copyright 2011 Moxie Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.ait.toolkit.clientio.uploader.client; import java.util.ArrayList; import java.util.HashMap; import com.ait.toolkit.clientio.uploader.client.events.FileDialogCompleteEvent; import com.ait.toolkit.clientio.uploader.client.events.FileDialogCompleteHandler; import com.ait.toolkit.clientio.uploader.client.events.FileDialogStartEvent; import com.ait.toolkit.clientio.uploader.client.events.FileDialogStartHandler; import com.ait.toolkit.clientio.uploader.client.events.FileQueueErrorEvent; import com.ait.toolkit.clientio.uploader.client.events.FileQueueErrorHandler; import com.ait.toolkit.clientio.uploader.client.events.FileQueuedEvent; import com.ait.toolkit.clientio.uploader.client.events.FileQueuedHandler; import com.ait.toolkit.clientio.uploader.client.events.SWFUploadLoadedEvent; import com.ait.toolkit.clientio.uploader.client.events.SWFUploadLoadedHandler; import com.ait.toolkit.clientio.uploader.client.events.UploadCompleteEvent; import com.ait.toolkit.clientio.uploader.client.events.UploadCompleteHandler; import com.ait.toolkit.clientio.uploader.client.events.UploadErrorEvent; import com.ait.toolkit.clientio.uploader.client.events.UploadErrorHandler; import com.ait.toolkit.clientio.uploader.client.events.UploadProgressEvent; import com.ait.toolkit.clientio.uploader.client.events.UploadProgressHandler; import com.ait.toolkit.clientio.uploader.client.events.UploadStartEvent; import com.ait.toolkit.clientio.uploader.client.events.UploadStartHandler; import com.ait.toolkit.clientio.uploader.client.events.UploadSuccessEvent; import com.ait.toolkit.clientio.uploader.client.events.UploadSuccessHandler; import com.ait.toolkit.clientio.uploader.client.resources.UploaderResources; import com.ait.toolkit.core.client.Util; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; import com.google.gwt.core.client.JsArrayNumber; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.InputElement; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.StyleElement; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.MouseOutEvent; import com.google.gwt.event.dom.client.MouseOutHandler; import com.google.gwt.event.dom.client.MouseOverEvent; import com.google.gwt.event.dom.client.MouseOverHandler; import com.google.gwt.event.dom.client.MouseUpEvent; import com.google.gwt.event.dom.client.MouseUpHandler; import com.google.gwt.json.client.JSONBoolean; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONString; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.FileUpload; import com.google.gwt.user.client.ui.Label; /** * The main GWT widget that can be constructed and then configured in order to add an uploader * component into a GWT application. Basic usage is as follows: * <pre><code> * Uploader uploader = new Uploader() * .setUploadURL("http://www.widgetcorp.com/uploads.php") * .setButtonText("Click Here <span class='redText'>Yeah</span>") * .setButtonTextStyle(".redText { color: #FF0000; }") * .setButtonWidth(100) * .setButtonHeight(22) * .setButtonCursor(Uploader.Cursor.HAND); * RootPanel.get().add(uploader); * </code></pre> * Normally you'll also want to respond to various events that the GWT uploader component * can generate in order to notify the user about the progress and status of the uploader. E.g. * <pre><code> * uploader.setUploadProgressHandler(new UploadProgressHandler() { * public boolean onUploadProgress(UploadProgressEvent uploadProgressEvent) { * Window.alert("File progress: " + uploadProgressEvent.getFile().getName() + "\n" + * NumberFormat.getPercentFormat().format( * uploadProgressEvent.getBytesComplete() / uploadProgressEvent.getBytesTotal() * ) + " Percent Complete" * ); * return true; * } * }); * </code></pre> * Similar events are available for when the file dialog window is opened, when a new file * is added to the queue, when an uploader is started/completed, when errors occur, etc. * <p/> * Internally the GWT Uploader component defaults to attempting to use the browser's DOM * and XMLHttpRequest Level 2 mechanisms to manage the file selection, tracking of the various * events, and handling the uploader of the file to the server. However, some browsers don't * support the XMLHttpRequest Level 2 API (most notably of which is Internet Explorer). In * that case the uploader component automatically detects that the browser support is not * available and then transparently switches over to using a SWFUpload/Flash based * mechanism for handling the file selection and uploader process instead. More * details on the browsers which support the XMLHttpRequest Level 2 APIs can be found * <a href="http://caniuse.com/xhr2">here</a>. And additional details on the SWFUpload project can be * found <a href="http://code.google.com/p/swfupload/"></a>here. Note that if you only * want to use the SWFUpload mechanism, independent of whether or not the browser supports the * XMLHttpRequest Level 2 API, you can use the {@link #setAjaxUploadEnabled(boolean)} method * to disable the XMLHttpRequest Level 2 approach - in which case the GWT Uploader API serves * essentially as a GWT wrapper for the SWFUpload JS API. * * @author squinn@moxiegroup.com (Shawn Quinn) */ public class Uploader extends AbsolutePanel { private static final int BYTES_PER_KILOBYTE = 1024; private static final int BYTES_PER_MEGABYTE = BYTES_PER_KILOBYTE * 1024; private static final int BYTES_PER_GIGABYTE = BYTES_PER_MEGABYTE * 1024; static { Util.injectJs(UploaderResources.INSTANCE.js()); Util.injectJs(UploaderResources.INSTANCE.jsSpeed()); } /** * An enumeration of supported button action types, which can be passed to the * {@link Uploader#setButtonAction(ButtonAction)} method. The button action * defines the action taken when the uploader button (text, image, or flash) is clicked. */ public enum ButtonAction { /** * Select a single file when the button is clicked */ SELECT_FILE(-100), /** * Select multiple files when the button is clicked */ SELECT_FILES(-110), /** * Begin the uploader process when the button is clicked */ START_UPLOAD(-120); private ButtonAction(int optionValue) { this.optionValue = optionValue; } private final int optionValue; public int toInt() { return optionValue; } } /** * An enumeration of supported button cursor types, which can be passed to the * {@link Uploader#setButtonCursor(Cursor)} method. The button cursor * is used to define what type of mouse cursor is displayed when hovering over the uploader button.. */ public enum Cursor { /** * Show the cursor as an arrow when the user hovers over the button */ ARROW(-1), /** * Show the cursor as a hand when the user hovers over the button */ HAND(-2); private Cursor(int optionValue) { this.optionValue = optionValue; } private final int optionValue; public int toInt() { return optionValue; } } /** * An enumeration of supported button window types, which can be passed to the * {@link Uploader#setButtonWindowMode(WindowMode)} method. The window mode is used * to set the WMODE property of the Flash Movie. */ public enum WindowMode { /** * Set the flash movie in the "window" mode */ WINDOW("window"), /** * Set the flash movie in the "transparent" mode */ TRANSPARENT("transparent"), /** * Set the flash movie in the "opaque" mode */ OPAQUE("opaque"); private WindowMode(String optionValue) { this.optionValue = optionValue; } private final String optionValue; public String toString() { return optionValue; } } /** * Create a new Uploader component that can be added to a GWT application, setting up * an element in the DOM that the uploader button elements will be rendered inside of. */ public Uploader() { super(); } private String uploadURL; /** * Convenience method for setting the 'upload_url' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("upload_url", "http://www.widgetcorp.com/uploads.php"); * </code></pre> * The upload_url setting accepts a full, absolute, or relative target URL for the uploaded file. * Relative URLs should be relative to the document. The upload_url should be in the same domain * as the SWFUploader Flash Control for best compatibility in browsers that don't support the * XMLHttpRequest Level 2 API. * <p/> * In the case that the SWFUpload/Flash control is used if the {@link #setPreserveRelativeURLs(boolean)} * setting is false (the default) Uploader will convert the relative URL to an absolute URL to avoid the * URL being interpreted differently by the Flash Player on different platforms. If you disable * SWFUploads conversion of the URL relative URLs should be relative to the uploader.swf file. * <p/> * By default both the XMLHttpRequest Level 2/Ajax uploads and the SWFUpload/Flash uploads will * be posted to whatever URL is specified via this method. * However, if you'd like the Ajax uploads to be posted to a different URL than the SWF * uploads, you can call the {@link #setAjaxUploadURL(String)} method to set a unique URL * for the Ajax uploads (and then only the SWF uploads will use the general "uploadURL"). * * @param uploadURL The value to set as the 'upload_url' option on the Uploader component. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setUploadURL(String uploadURL) { this.uploadURL = uploadURL; if (swfUpload != null) { nativeSetUploadURL(swfUpload, uploadURL); } return this.setOption("/upload_url", uploadURL); } private static native void nativeSetUploadURL(JavaScriptObject swfUpload, String url) /*-{ swfUpload.setUploadURL(url); }-*/; private String ajaxUploadURL; /** * By default both the XMLHttpRequest Level 2/Ajax uploads and the SWFUpload/Flash uploads will * be posted to whatever URL is specified via the {@link #setUploadURL(String)} method. * However, if you'd like the Ajax uploads to be posted to a different URL than the SWF * uploads, you can call this method to set a unique URL for the Ajax uploads (and then * only the SWF uploads will use the general "uploadURL"). * * @param ajaxUploadURL The URL that only the Ajax uploads should be posted to (or null * to uploader both the Ajax and SWF uploads to whatever * the {@link #setUploadURL(String)} option has been set to.) * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setAjaxUploadURL(String ajaxUploadURL) { this.ajaxUploadURL = ajaxUploadURL; return this.setOption("/ajax_upload_url", uploadURL); } private boolean ajaxUploadEnabled = true; /** * By default the component will attempt to use a DOM based Ajax uploader process if it detects * that the browser can support the necessary requirements of the "XMLHttpRequest Level 2". And then, * only if the browser can not support the "XMLHttpRequest Level 2" mechanism will the component * failover to using the Flash based SWFUpload mechanism instead. However, if you wish to * only use the SWFUpload mechanism, then this option can be disabled. * * @param ajaxUploadEnabled By default ajax uploader is enabled and will be used as long as the * browser supports the "XMLHttpRequest Level 2" object. Setting this option * to "false" will instead force the component to avoid using the Ajax * approach and instead utilize the SWFUpload/Flash technique only. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setAjaxUploadEnabled(boolean ajaxUploadEnabled) { this.ajaxUploadEnabled = ajaxUploadEnabled; return this; } private String filePostName = null; /** * Convenience method for setting the 'file_post_name' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("file_post_name", "Filedata"); * </code></pre> * The file_post_name allows you to set the value name used to post the file. This is not related * to the file name. The default value is 'Filedata'. For maximum compatibility it is recommended * that the default value is used. * * @param filePostName The value to set as the 'file_post_name' option on the Uploader component. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setFilePostName(String filePostName) { if (swfUpload != null) { nativeSetFilePostName(swfUpload, filePostName); } else { this.filePostName = filePostName; } return this.setOption("/file_post_name", filePostName); } private static native void nativeSetFilePostName(JavaScriptObject swfUpload, String filePostName) /*-{ swfUpload.setFilePostName(filePostName); }-*/; private JSONObject postParams = null; /** * Convenience method for setting the 'post_params' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * JSONObject params = new JSONObject(); * params.put("post_param_name_1", new JSONString("post_param_value_1")); * params.put("post_param_name_2", new JSONString("post_param_value_2")); * params.put("post_param_name_n", new JSONString("post_param_value_n")); * uploader.setOption("post_params", params); * </code></pre> * The post_params setting defines the name/value pairs that will be posted with each uploaded file. * This setting accepts a simple JavaScript object. Multiple post name/value pairs should be defined * as demonstrated in the sample settings object. Values must be either strings or numbers * (as interpreted by the JavaScript typeof function). * <p/> * Note: Flash Player 8 does not support sending additional post parameters. SWFUpload will * automatically send the post_params as part of the query string. * <p/> * * @param postParams The value to set as the 'post_params' option on the Uploader component. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setPostParams(JSONObject postParams) { if (swfUpload != null) { nativeSetPostParams(swfUpload, postParams != null ? postParams.getJavaScriptObject() : null); } else { this.postParams = postParams; } return this.setOption("/post_params", postParams); } private static native void nativeSetPostParams(JavaScriptObject swfUpload, JavaScriptObject postParams) /*-{ swfUpload.setPostParams(postParams); }-*/; /** * Convenience method for setting the 'use_query_string' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("use_query_string", false); * </code></pre> * The use_query_string setting may be true or false. This value indicates whether Uploader * should send the post_params and file params on the query string or the post. * <p/> * Note that this option only applies when the SWFUpload/Flash component is rendered. When * the DHTML/Ajax mode is being used (the default) the params are always passed as * part of the post. * * @param useQueryString The value to set as the 'use_query_string' option on the Uploader component. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setUseQueryString(boolean useQueryString) { if (swfUpload != null) { nativeSetUseQueryString(swfUpload, useQueryString); } return this.setOption("/use_query_string", useQueryString); } private static native void nativeSetUseQueryString(JavaScriptObject swfUpload, boolean useQueryString) /*-{ swfUpload.setUseQueryString(useQueryString); }-*/; /** * Convenience method for setting the 'preserve_relative_urls' option of the component. Equivalent to: * <pre><code> * uploader.setOption("preserve_relative_urls", false); * </code></pre> * A boolean value that indicates whether Uploader should attempt to convert relative URLs used * by the Flash Player to absolute URLs. If set to true Uploader will not modify any URLs. * The default value is false. * <p/> * Note that this option only applies when the SWFUpload/Flash component is rendered. * * @param preserveRelativeURLs The value to set as the 'preserve_relative_urls' option on the Uploader component. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setPreserveRelativeURLs(boolean preserveRelativeURLs) { return this.setOption("/preserve_relative_urls", preserveRelativeURLs); } private boolean requeueOnError = false; /** * Convenience method for setting the 'requeue_on_error' option of the component. Equivalent to: * <pre><code> * uploader.setOption("requeue_on_error", true); * </code></pre> * The requeue_on_error setting may be true or false. When this setting is true any files that * has an uploadError (excluding fileQueue errors and the FILE_CANCELLED uploadError) is * returned to the front of the queue rather than being discarded. The file can be uploaded * again if needed. To remove the file from the queue the cancelUpload method must be called. * <p/> * All the events associated with a failed uploader are still called and so the requeuing the * failed uploader can conflict with the Queue Plugin (or custom code that uploads the entire * queue). Code that automatically uploads the next file in the queue will uploader the failed * file over and over again if care is not taken to allow the failing uploader to be cancelled. * * @param requeueOnError The value to set as the 'requeue_on_error' option on the Uploader component (default: false). * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setRequeueOnError(boolean requeueOnError) { this.requeueOnError = requeueOnError; return this.setOption("/requeue_on_error", requeueOnError); } private long[] httpSuccess; /** * Convenience method for setting the 'http_success' option of the component. Equivalent to: * <pre><code> * uploader.setOption("http_success", new Long[] { 200, 203 }); * </code></pre> * An array that defines the HTTP Status Codes that will trigger success. 200 is always a success. * Also, only the 200 status code provides the serverData. * <p/> * When returning and accepting an HTTP Status Code other than 200 it is not necessary for the * server to return content. * * @param httpSuccess The values to set as the HTTP response codes to treat as a successful response (default: 200). * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setHttpSuccess(long... httpSuccess) { this.httpSuccess = httpSuccess; return this.setOption("/http_success", httpSuccess); } /** * Convenience method for setting the 'assume_success_timeout' option of the component. Equivalent to: * <pre><code> * uploader.setOption("assume_success_timeout", 1000); * </code></pre> * The number of seconds SWFUpload should wait for Flash to detect the server's response after * the file has finished uploading. This setting allows you to work around the Flash Player * bugs where long running server side scripts causes Flash to ignore the server response * or the Mac Flash Player bug that ignores server responses with no content. * <p/> * Testing has shown that Flash will ignore server responses that take longer than 30 seconds * after the last uploadProgress event. * <p/> * A timeout of zero (0) seconds disables this feature and is the default value. SWFUpload will * wait indefinitely for the Flash Player to trigger the uploadSuccess event. * <p/> * Note that this option only applies when the SWFUpload/Flash component is rendered. * * @param assumeSuccessTimeout The value to set as the 'assume_success_timeout' option on the Uploader component. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setAssumeSuccessTimeout(long assumeSuccessTimeout) { return this.setOption("/assume_success_timeout", assumeSuccessTimeout); } private String fileTypes; /** * Convenience method for setting the 'file_types' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("file_types", "*.jpg;*.gif"); * </code></pre> * The file_types setting accepts a semi-colon separated list of file extensions that are * allowed to be selected by the user. Use '*.*' to allow all file types. * * @param fileTypes A semi-colon delimited list of file types that the file dialog should allow * the user to select. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setFileTypes(String fileTypes) { this.fileTypes = fileTypes; if (swfUpload != null && this.fileTypes != null && this.fileTypesDescription != null) { nativeSetFileTypes(swfUpload, this.fileTypes, this.fileTypesDescription); } return this.setOption("/file_types", fileTypes); } private String fileTypesDescription; /** * Convenience method for setting the 'file_types_description' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("file_types_description", "Web Photos"); * </code></pre> * A text description that is displayed to the user in the File Browser dialog. * * @param fileTypesDescription A text description that is displayed to the user in the File Browser dialog. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setFileTypesDescription(String fileTypesDescription) { // TODO: Add Ajax mode support for this option this.fileTypesDescription = fileTypesDescription; if (swfUpload != null && this.fileTypes != null && this.fileTypesDescription != null) { nativeSetFileTypes(swfUpload, this.fileTypes, this.fileTypesDescription); } return this.setOption("/file_types_description", fileTypesDescription); } private static native void nativeSetFileTypes(JavaScriptObject swfUpload, String fileTypes, String fileTypesDescription) /*-{ swfUpload.setFileTypes(fileTypes, fileTypesDescription); }-*/; private long fileSizeLimit = 0; /** * Convenience method for setting the 'file_size_limit' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("file_size_limit", "5 MB"); * </code></pre> * The file_size_limit setting defines the maximum allowed size of a file to be uploaded. * This setting accepts a value and unit. Valid units are B, KB, MB and GB. * <p/> * Examples: 2147483648 B, 2097152, 2097152KB, 2048 MB, 2 GB * <p/> * If the unit is omitted default is KB. A value of 0 (zero) is interpreted as unlimited. * <p/> * Note: This setting only applies to the user's browser. It does not affect any settings or * limits on the web server. * * @param fileSizeLimit The maximum allowed size of a file to be uploaded. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setFileSizeLimit(String fileSizeLimit) { if (fileSizeLimit != null && fileSizeLimit.length() > 0 && Character.isDigit(fileSizeLimit.charAt(0))) { String units = ""; int i = 0; while (i < fileSizeLimit.length() && Character.isDigit(fileSizeLimit.charAt(i))) { i++; } if (i < fileSizeLimit.length() - 1) { // Get the units units = fileSizeLimit.substring(i).trim(); } long size = Long.parseLong(fileSizeLimit.substring(0, i)); if ("".equalsIgnoreCase(units)) { // Default to KB this.fileSizeLimit = size * BYTES_PER_KILOBYTE; } else if ("B".equalsIgnoreCase(units)) { this.fileSizeLimit = size; } else if ("KB".equalsIgnoreCase(units)) { this.fileSizeLimit = size * BYTES_PER_KILOBYTE; } else if ("MB".equalsIgnoreCase(units)) { this.fileSizeLimit = size * BYTES_PER_MEGABYTE; } else if ("GB".equalsIgnoreCase(units)) { this.fileSizeLimit = size * BYTES_PER_GIGABYTE; } else { // Default to unlimited this.fileSizeLimit = 0; } } if (swfUpload != null) { nativeSetFileSizeLimit(swfUpload, fileSizeLimit); } return this.setOption("/file_size_limit", fileSizeLimit); } private static native void nativeSetFileSizeLimit(JavaScriptObject swfUpload, String fileSizeLimit) /*-{ swfUpload.setFileSizeLimit(fileSizeLimit); }-*/; private long fileUploadLimit = 0; private long totalFilesUploaded = 0; /** * Convenience method for setting the 'file_upload_limit' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("file_upload_limit", 10); * </code></pre> * Defines the number of files allowed to be uploaded by the uploader component. This setting also sets the * upper bound of the file_queue_limit setting. Once the user has uploaded or queued the maximum * number of files she will no longer be able to queue additional files. The value of 0 (zero) * is interpreted as unlimited. Only successful uploads (uploads the trigger the uploadSuccess * event) are counted toward the uploader limit. * <p/> * Note: This value is not tracked across pages and is reset when a page is refreshed. File quotas * should be managed by the web server. * * @param fileUploadLimit The number of files allowed to be uploaded by the uploader component. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setFileUploadLimit(long fileUploadLimit) { this.fileUploadLimit = fileUploadLimit; if (swfUpload != null) { nativeSetFileUploadLimit(swfUpload, fileUploadLimit); } return this.setOption("/file_upload_limit", fileUploadLimit); } private static native void nativeSetFileUploadLimit(JavaScriptObject swfUpload, double fileUploadLimit) /*-{ swfUpload.setFileUploadLimit(fileUploadLimit); }-*/; private long fileQueueLimit = Long.MAX_VALUE; /** * Convenience method for setting the 'file_queue_limit' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("file_queue_limit", 10); * </code></pre> * Defines the number of unprocessed files allowed to be simultaneously queued. Once a file is * uploaded, errored, or cancelled new files can be queued in its place until the queue limit * has been reached. If the uploader limit (or remaining uploads allowed) is less than the queue * limit then the lower number is used. * * @param fileQueueLimit The number of unprocessed files allowed to be simultaneously queued. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setFileQueueLimit(long fileQueueLimit) { this.fileQueueLimit = fileQueueLimit; if (swfUpload != null) { nativeSetFileQueueLimit(swfUpload, fileQueueLimit); } return this.setOption("/file_queue_limit", fileQueueLimit); } private static native void nativeSetFileQueueLimit(JavaScriptObject swfUpload, double fileQueueLimit) /*-{ swfUpload.setFileQueueLimit(fileQueueLimit); }-*/; private String flashURL; /** * Overrides the default "flash_url" setting. Normally you'll want to leave this setting alone * as the "uploader.swf" path will be automatically determined for you based on the GWT module * path. However, if you've deployed the "uploader.swf" somewhere else (or on a different domain) * then you can set the URL to the file here in order to override the default internal logic. * <p/> * The full, absolute, or relative URL to the Flash Control swf file. This setting cannot be * changed once the Uploader has been instantiated. Relative URLs are relative to the page URL. * <p/> * Note that this option only applies when the SWFUpload/Flash component is rendered. * * @param flashURL The value to set as the 'flash_url' option on the Uploader component. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setFlashURL(String flashURL) { // Purposefully treating this option separately, instead of storing it in the "Configuration" // instance, see the logic in the createNativeOptions() method for details... this.flashURL = flashURL; return this; } /** * Convenience method for setting the 'prevent_swf_caching' option of the component. Equivalent to: * <pre><code> * uploader.setOption("prevent_swf_caching", false); * </code></pre> * This boolean setting indicates whether a random value should be added to the Flash URL in an * attempt to prevent the browser from caching the SWF movie. This works around a bug in * some IE-engine based browsers. * <p/> * Note: The algorithm for adding the random number to the URL is dumb and cannot handle * URLs that already have some parameters. * <p/> * Note that this option only applies when the SWFUpload/Flash component is rendered. * * @param preventSWFCaching The value to set as the 'prevent_swf_caching' option on the Uploader component. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setPreventSWFCaching(boolean preventSWFCaching) { return this.setOption("/prevent_swf_caching", preventSWFCaching); } /** * Convenience method for setting the 'debug' option of the component. Equivalent to: * <pre><code> * uploader.setOption("debug", false); * </code></pre> * A boolean value that defines whether the debug event handler should be fired. * <p/> * Note that this option only applies when the SWFUpload/Flash component is rendered. * * @param debug The value to set as the 'debug' option on the Uploader component. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setDebug(boolean debug) { return this.setOption("/debug", debug); } private String buttonImageURL; /** * Convenience method for setting the 'button_image_url' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("button_image_url", "http://widgetcorp.com/upload_button.png"); * </code></pre> * Fully qualified, absolute or relative URL to the image file to be used as the button's image. * <p/> * This URL is affected by the preserve_relative_urls setting and should follow the same * rules as the upload_url setting. * <p/> * The button image is treated as a sprite. There are 4 button states that must be represented * by the button image. Each button state image should be stacked above the other in this * order: normal, hover, down/click, disabled. * * @param buttonImageURL The URL to the image file to be used as the button's image. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setButtonImageURL(String buttonImageURL) { this.buttonImageURL = buttonImageURL; if (swfUpload != null) { nativeSetButtonImageURL(swfUpload, buttonImageURL); } return this.setOption("/button_image_url", buttonImageURL); } private static native void nativeSetButtonImageURL(JavaScriptObject swfUpload, String buttonImageURL) /*-{ swfUpload.setButtonImageURL(buttonImageURL); }-*/; private int buttonWidth = -1; /** * Convenience method for setting the 'button_width' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("button_width", 62); * </code></pre> * A number defining the width of the uploader button. * * @param buttonWidth The value to set as width of the uploader button. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setButtonWidth(int buttonWidth) { this.buttonWidth = buttonWidth; if (this.buttonWidth >= 0 && this.buttonHeight >= 0) { this.setWidth(this.buttonWidth + "px"); this.setHeight(this.buttonHeight + "px"); if (swfUpload != null) { nativeSetButtonDimensions(swfUpload, this.buttonWidth, this.buttonHeight); } } return this.setOption("/button_width", buttonWidth); } private int buttonHeight = -1; /** * Convenience method for setting the 'button_height' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("button_height", 21); * </code></pre> * A number defining the height of the uploader button. * * @param buttonHeight The value to set as height of the uploader button. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setButtonHeight(int buttonHeight) { this.buttonHeight = buttonHeight; if (this.buttonWidth >= 0 && this.buttonHeight >= 0) { this.setWidth(this.buttonWidth + "px"); this.setHeight(this.buttonHeight + "px"); if (swfUpload != null) { nativeSetButtonDimensions(swfUpload, this.buttonWidth, this.buttonHeight); } } return this.setOption("/button_height", buttonHeight); } private static native void nativeSetButtonDimensions(JavaScriptObject swfUpload, int buttonWidth, int buttonHeight) /*-{ swfUpload.setButtonDimensions(buttonWidth, buttonHeight); }-*/; private String buttonText; private DivElement buttonTextElement; private DivElement buttonImageElement; /** * Convenience method for setting the 'button_text' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("button_text", "<b>Click</b> <span class="redText">here</span>"); * </code></pre> * Plain or HTML text that is displayed over the uploader button. HTML text can be further styled * using CSS classes and the button_text_style setting, but should be limited to what Flash * can support for compatibility with browsers that don't support the XMLHttpRequest Level 2 API. See * <a href="http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html">Adobe's Flash documentation</a> * for details. * * @param buttonText Plain or HTML text that is displayed over the uploader button. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setButtonText(String buttonText) { this.buttonText = buttonText; if (swfUpload != null) { nativeSetButtonText(swfUpload, buttonText); } if (buttonTextElement != null) { buttonTextElement.setInnerHTML(buttonText); } return this.setOption("/button_text", buttonText); } private static native void nativeSetButtonText(JavaScriptObject swfUpload, String buttonText) /*-{ swfUpload.setButtonText(buttonText); }-*/; private String buttonTextStyle; private StyleElement buttonTextStyleElement; /** * Convenience method for setting the 'button_text_style' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("button_text_style", ".redText { color: #FF0000; }"); * </code></pre> * CSS style string that defines how the button_text is displayed. Should be limited to what Flash * can support for compatibility with browsers that don't support the XMLHttpRequest Level 2 API. See * <a href="http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html">Adobe's Flash documentation</a> * for details. * * @param buttonTextStyle The CSS style string that defines how the button text is displayed. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setButtonTextStyle(String buttonTextStyle) { this.buttonTextStyle = buttonTextStyle; if (swfUpload != null) { nativeSetButtonTextStyle(swfUpload, buttonTextStyle); } if (buttonTextStyleElement != null) { buttonTextStyleElement.setInnerText(buttonTextStyle); } return this.setOption("/button_text_style", buttonTextStyle); } private static native void nativeSetButtonTextStyle(JavaScriptObject swfUpload, String buttonTextStyle) /*-{ swfUpload.setButtonTextStyle(buttonTextStyle); }-*/; private int buttonTextTopPadding = Integer.MIN_VALUE; /** * Convenience method for setting the 'button_text_top_padding' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("button_text_top_padding", 2); * </code></pre> * Used to vertically position the button text within the uploader component. Negative values may be used. * * @param buttonTextTopPadding The amount of padding to include between the outer bounds of the uploader * component and the top of the text. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setButtonTextTopPadding(int buttonTextTopPadding) { this.buttonTextTopPadding = buttonTextTopPadding; if (swfUpload != null && this.buttonTextLeftPadding > Integer.MIN_VALUE && this.buttonTextTopPadding > Integer.MIN_VALUE) { nativeSetButtonTextPadding(swfUpload, this.buttonTextLeftPadding, this.buttonTextTopPadding); } return this.setOption("/button_text_top_padding", buttonTextTopPadding); } private int buttonTextLeftPadding = Integer.MIN_VALUE; /** * Convenience method for setting the 'button_text_left_padding' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("button_text_left_padding", 2); * </code></pre> * Used to horizontally position the button text within the uploader component. Negative values may be used. * * @param buttonTextLeftPadding The amount of padding to include between the outer bounds of the uploader * component and the left of the text. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setButtonTextLeftPadding(int buttonTextLeftPadding) { this.buttonTextLeftPadding = buttonTextLeftPadding; if (swfUpload != null && this.buttonTextLeftPadding > Integer.MIN_VALUE && this.buttonTextTopPadding > Integer.MIN_VALUE) { nativeSetButtonTextPadding(swfUpload, this.buttonTextLeftPadding, this.buttonTextTopPadding); } return this.setOption("/button_text_left_padding", buttonTextLeftPadding); } private static native void nativeSetButtonTextPadding(JavaScriptObject swfUpload, int buttonTextLeftPadding, int buttonTextTopPadding) /*-{ swfUpload.setButtonTextPadding(buttonTextLeftPadding, buttonTextTopPadding); }-*/; private ButtonAction buttonAction = ButtonAction.SELECT_FILES; /** * Convenience method for setting the 'button_action' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("button_action", ButtonAction.SELECT_FILES); * </code></pre> * Defines the action to take when the uploader component is clicked. * * @param buttonAction The action to take when the uploader component is clicked. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setButtonAction(ButtonAction buttonAction) { this.buttonAction = buttonAction; if (swfUpload != null && buttonAction != null) { nativeSetButtonAction(swfUpload, buttonAction.toInt()); } return this.setOption("/button_action", buttonAction != null ? buttonAction.toInt() : null); } private static native void nativeSetButtonAction(JavaScriptObject swfUpload, int buttonAction) /*-{ swfUpload.setButtonAction(buttonAction); }-*/; private boolean buttonDisabled = false; /** * Convenience method for setting the 'button_disabled' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("button_disabled", false); * </code></pre> * A boolean value that sets whether the uploader button is in the disabled state. When in the * disabled state the button will not execute any actions. * * @param buttonDisabled Whether the uploader button is in the disabled state. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setButtonDisabled(boolean buttonDisabled) { this.buttonDisabled = buttonDisabled; if (swfUpload != null) { nativeSetButtonDisabled(swfUpload, buttonDisabled); } if (buttonImageElement != null && buttonHeight >= 0) { if (buttonDisabled) { buttonImageElement.getStyle().setProperty("backgroundPosition", "0px -" + (buttonHeight * 3) + "px"); } else { buttonImageElement.getStyle().setProperty("backgroundPosition", "0px 0px"); } } return this.setOption("/button_disabled", buttonDisabled); } /** * Returns whether or not the button has been set in a disabled state via the * {@link #setButtonDisabled(boolean)} method. * * @return 'true' if the button has been disabled (e.g. grayed out), or 'false' if the * button is still enabled. */ public boolean getButtonDisabled() { return this.buttonDisabled; } private static native void nativeSetButtonDisabled(JavaScriptObject swfUpload, boolean buttonDisabled) /*-{ swfUpload.setButtonDisabled(buttonDisabled); }-*/; private Cursor buttonCursor; /** * Convenience method for setting the 'button_cursor' option of the component, either before or * after the widget has been added to the DOM. Equivalent to: * <pre><code> * uploader.setOption("button_cursor", Cursor.HAND); * </code></pre> * Used to define what type of mouse cursor is displayed when hovering over the uploader component. * * @param cursor The type of cursor to display when the mouse is hovering over the uploader component. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setButtonCursor(Cursor cursor) { buttonCursor = cursor; if (swfUpload != null && cursor != null) { nativeSetButtonCursor(swfUpload, cursor.toInt()); } return this.setOption("/button_cursor", cursor != null ? cursor.toInt() : null); } private static native void nativeSetButtonCursor(JavaScriptObject swfUpload, int buttonCursor) /*-{ swfUpload.setButtonCursor(buttonCursor); }-*/; /** * Convenience method for setting the 'button_window_mode' option of the component. Equivalent to: * <pre><code> * uploader.setOption("button_window_mode", WindowMode.TRANSPARENT); * </code></pre> * Sets the WMODE property of the Flash Movie. * <p/> * Note that this option only applies when the SWFUpload/Flash component is rendered. * * @param buttonWindowMode The value to set as the 'button_window_mode' option on the Uploader component. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setButtonWindowMode(WindowMode buttonWindowMode) { return this.setOption("/button_window_mode", buttonWindowMode != null ? buttonWindowMode.toString() : null); } private JSONObject customSettings; /** * Convenience method for setting the 'custom_settings' option of the component. Equivalent to: * <pre><code> * JSONObject settings = new JSONObject(); * settings.put("My Setting", new JSONString("This is my setting")); * settings.put("myothersetting", new JSONString("This is my other setting")); * settings.put("integer_setting", new JSONNumber(100)); * uploader.setOption("custom_settings", settings); * </code></pre> * The custom_settings setting allows developers to safely attach additional information to a * uploader instance without worrying about affecting internal values or changes * in new GWT Uploader (or SWFUpload) versions. This setting accepts a JSON object. * </p> * Once instantiated the custom settings are accessed via the {@link #getCustomSettings()} * method. * * @param customSettings The value to set as the 'custom_settings' option on the uploader component. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setCustomSettings(JSONObject customSettings) { this.customSettings = customSettings; return this.setOption("/custom_settings", customSettings); } /** * Returns the custom settings that were stored on this uploader component instance via * the {@link #setCustomSettings(JSONObject)} method. The custom_settings setting allows * developers to safely attach additional information to a uploader instance without worrying * about affecting internal values or changes in new GWT Uploader (or SWFUpload) versions. * * @return JSONObject */ public JSONObject getCustomSettings() { return customSettings; } private Stats stats; /** * Returns the overall general statistics being tracked by the Uploader component, or null * if the widget has not yet been added to the DOM. * * @return Stats */ public Stats getStats() { if (swfUpload != null) { return nativeGetStats(swfUpload); } else if (stats == null) { stats = (Stats) JavaScriptObject.createObject(); } return stats; } private static native Stats nativeGetStats(JavaScriptObject swfUpload) /*-{ return swfUpload.getStats(); }-*/; private SWFUploadLoadedHandler swfUploadLoadedHandler; /** * Set a callback handler that will be invoked whenever a Uploader loaded event is fired. * <p/> * Note that this option only applies when the SWFUpload/Flash component is rendered. * * @param swfUploadLoadedHandler The handler that should be invoked whenever a Uploader loaded event occurs. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setSWFUploadLoadedHandler(SWFUploadLoadedHandler swfUploadLoadedHandler) { this.swfUploadLoadedHandler = swfUploadLoadedHandler; return this; } private FileDialogStartHandler fileDialogStartHandler; /** * Set a callback handler that will be invoked whenever a file dialog start event is fired. * * @param fileDialogStartHandler The handler that should be invoked whenever a file dialog start event occurs. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setFileDialogStartHandler(FileDialogStartHandler fileDialogStartHandler) { this.fileDialogStartHandler = fileDialogStartHandler; return this; } private FileQueuedHandler fileQueuedHandler; /** * Set a callback handler that will be invoked whenever a file queued event is fired. * * @param fileQueuedHandler The handler that should be invoked whenever a file queued event occurs. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setFileQueuedHandler(FileQueuedHandler fileQueuedHandler) { this.fileQueuedHandler = fileQueuedHandler; return this; } private FileQueueErrorHandler fileQueueErrorHandler; /** * Set a callback handler that will be invoked whenever a file queue error event is fired. * * @param fileQueueErrorHandler The handler that should be invoked whenever a file queue error event occurs. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setFileQueueErrorHandler(FileQueueErrorHandler fileQueueErrorHandler) { this.fileQueueErrorHandler = fileQueueErrorHandler; return this; } private FileDialogCompleteHandler fileDialogCompleteHandler; /** * Set a callback handler that will be invoked whenever a file dialog complete event is fired. * * @param fileDialogCompleteHandler The handler that should be invoked whenever a file dialog complete event occurs. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setFileDialogCompleteHandler(FileDialogCompleteHandler fileDialogCompleteHandler) { this.fileDialogCompleteHandler = fileDialogCompleteHandler; return this; } private UploadCompleteHandler uploadCompleteHandler; /** * Set a callback handler that will be invoked whenever an uploader complete event is fired. * * @param uploadCompleteHandler The handler that should be invoked whenever an uploader complete event occurs. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setUploadCompleteHandler(UploadCompleteHandler uploadCompleteHandler) { this.uploadCompleteHandler = uploadCompleteHandler; return this; } private UploadErrorHandler uploadErrorHandler; /** * Set a callback handler that will be invoked whenever an uploader error event is fired. * * @param uploadErrorHandler The handler that should be invoked whenever an uploader error event occurs. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setUploadErrorHandler(UploadErrorHandler uploadErrorHandler) { this.uploadErrorHandler = uploadErrorHandler; return this; } private UploadProgressHandler uploadProgressHandler; /** * Set a callback handler that will be invoked whenever an uploader progress event is fired. * * @param uploadProgressHandler The handler that should be invoked whenever an uploader progress event occurs. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setUploadProgressHandler(UploadProgressHandler uploadProgressHandler) { this.uploadProgressHandler = uploadProgressHandler; return this; } private UploadStartHandler uploadStartHandler; /** * Set a callback handler that will be invoked whenever an uploader start event is fired. * * @param uploadStartHandler The handler that should be invoked whenever an uploader start event occurs. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setUploadStartHandler(UploadStartHandler uploadStartHandler) { this.uploadStartHandler = uploadStartHandler; return this; } private UploadSuccessHandler uploadSuccessHandler; /** * Set a callback handler that will be invoked whenever an uploader success event is fired. * * @param uploadSuccessHandler The handler that should be invoked whenever an uploader success event occurs. * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setUploadSuccessHandler(UploadSuccessHandler uploadSuccessHandler) { this.uploadSuccessHandler = uploadSuccessHandler; return this; } // Delegate responsibility for managing configuration options to an anonymous helper class private Configurable configurable = new Configurable() { }; /** * General purpose method to set an option on the SWFUpload object at any level, using "/" characters * to designate which level of option you'd like to set. E.g., the following code: * <pre><code> * Uploader uploader = new Uploader(); * uploader.setOption("/upload_url", "http://widgetcorp.com/uploads.php"); * uploader.setOption("/post_params/upload_type", "Photo Upload"); * uploader.setOption("/post_params/user_id", "User 24"); * </code></pre> * Would result in initializing the SWFUpload component like the following: * <pre><code> * new SWFUpload({ * upload_url: "http://widgetcorp.com/uploads.php", * post_params: { * upload_type: "Photo Upload", * user_id: "User 24" * } * }); * </code></pre> * Note that the beginning "/" is optional, so <code>uploader.setOption("/thing", "piglet")</code> is * equivalent to <code>uploader.setOption("thing", "piglet")</code>. * <p/> * For details on available options see the <a href="http://demo.uploader.org/Documentation/#settingsobject">SWFUpload reference</a>. * <p/> * Important note: this method is only intended to support additional options of the SWFUpload API * that may not have explicit wrapper methods available in the GWT Uploader API. For all of the standard * configuration options it is important to use the appropriate setter methods instead in order * for the Ajax/DOM implementation to function properly as well. E.g. instead of doing this: * <pre><code> * uploader.setOption("upload_url", "http://widgetcorp.com/uploads.php"); * </code></pre> * Do this instead: * <pre><code> * uploader.setUploadURL("http://widgetcorp.com/uploads.php"); * </code></pre> * <p/> * * @param path The path to the option to set (e.g. "/title/text"); * @param value The value to set for the option (can be a String, Number, Boolean, or JSONObject) * @return A reference to this {@link Uploader} instance for convenient method chaining. */ public Uploader setOption(String path, Object value) { configurable.setOption(path, value); return this; } // The current XMLHttpRequest in progress (if any), which we'll need a reference to in case the // user attempts to cancel an in progress upload. private JavaScriptObject lastXMLHttpRequest = null; /** * Cause the first file in the queue to start the uploader process */ public void startUpload() { if (uploadURL == null) { throw new IllegalStateException( "The 'startUpload()' method was invoked before the uploader URL was provided. Please call the 'setUploadURL' first."); } if (swfUpload != null) { nativeStartSWFUpload(swfUpload); } if (nativeFilesQueued.size() > 0) { JavaScriptObject nativeFile = nativeFilesQueued.get(0); // Initialize properties on Start nativeSetProperty(nativeFile, "startTime", System.currentTimeMillis()); nativeSetProperty(nativeFile, "timeSinceLastEvent", System.currentTimeMillis()); // SWFUpload isn't in play, so we need to keep our global stats up to date manually nativeSetProperty(getStats(), "in_progress", 1); // The SWFUploader component will automatically invoke the UploadStartHandler, but we need to fire it // manually for the Ajax/XMLHttpRequest Level 2 case nativeUpdateFileProperties(nativeFile, File.Status.IN_PROGRESS.toInt()); if (uploadStartHandler != null) { uploadStartHandler.onUploadStart(new UploadStartEvent(nativeFile.<File>cast())); } // Let any registered progress handlers know that we're starting at the beginning uploadProgressEventCallback(nativeFile.<File>cast(), 0.0, (double) nativeFile.<File>cast().getSize()); lastXMLHttpRequest = nativeStartAjaxUpload(nativeFile, ajaxUploadURL != null ? ajaxUploadURL : uploadURL, filePostName != null ? filePostName : "Filedata", postParams != null ? postParams.getJavaScriptObject() : null); } } private static native void nativeStartSWFUpload(JavaScriptObject swfUpload) /*-{ swfUpload.startUpload(); }-*/; // See: https://developer.mozilla.org/en/Using_files_from_web_applications private native JavaScriptObject nativeStartAjaxUpload(JavaScriptObject file, String url, String filePostName, JavaScriptObject postParams) /*-{ var self = this; var xhr = new XMLHttpRequest(); // Setup the event handlers we'll need to let the consuming application know what's going on xhr.upload.addEventListener('progress', function(e) { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::uploadProgressEventCallback(Lcom/ait/toolkit/clientio/uploader/client/File;DD)(file, e.loaded, e.total); }, false); xhr.addEventListener('load', function() { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::uploadSuccessEventCallback(Lcom/ait/toolkit/clientio/uploader/client/File;Ljava/lang/String;Ljava/lang/String;)( @com.ait.toolkit.clientio.uploader.client.Uploader::nativeUpdateFileProperties(Lcom/google/gwt/core/client/JavaScriptObject;I)( file, -4 // File.Status.COMPLETE ), xhr.status + "", xhr.responseText ) }, false); xhr.addEventListener('error', function() { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::uploadErrorEventCallback(Lcom/ait/toolkit/clientio/uploader/client/File;ILjava/lang/String;)( @com.ait.toolkit.clientio.uploader.client.Uploader::nativeUpdateFileProperties(Lcom/google/gwt/core/client/JavaScriptObject;I)( file, -3 // File.Status.ERROR ), -250, // UploadErrorEvent.ErrorCode.UPLOAD_FAILED xhr.responseText ) }, false); xhr.addEventListener('abort', function() { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::uploadErrorEventCallback(Lcom/ait/toolkit/clientio/uploader/client/File;ILjava/lang/String;)( @com.ait.toolkit.clientio.uploader.client.Uploader::nativeUpdateFileProperties(Lcom/google/gwt/core/client/JavaScriptObject;I)( file, -5 // File.Status.CANCELLED ), -280, // UploadErrorEvent.ErrorCode.FILE_CANCELLED "Upload Aborted" ) }, false); xhr.open('POST', url, true); var formData = new FormData(); formData.append(filePostName, file); // Append on any post params if(postParams != null) { for (var key in postParams) { formData.append(key, postParams[key]); } } // Kick off the multipart/form-data upload xhr.send(formData); return xhr; }-*/; /** * Cause the file with the specified Id to start the uploader process. Note * that currently this method is only available when the SWFUpload/Flash * component is used to handle the upload. * * @param fileId The id of the file the begin uploading */ public void startUpload(String fileId) { // TODO: Implement AJAX equivalent if (swfUpload != null) { nativeStartUpload(swfUpload, fileId); } } private static native void nativeStartUpload(JavaScriptObject swfUpload, String fileId) /*-{ swfUpload.startUpload(fileId); }-*/; /** * Cancel the file upload for a specific file and remove from the queue. * * @param fileId The fileId to cancel * @param triggerErrorEvent if true, an uploadError event will be issued */ public void cancelUpload(String fileId, boolean triggerErrorEvent) { if (swfUpload != null) { nativeCancelUpload(swfUpload, fileId, triggerErrorEvent); } else { JavaScriptObject nativeFile = nativeFilesQueuedById.get(fileId); if (nativeFile != null) { boolean cancelledActiveUpload = false; // If the file being cancelled was currently in the process of being uploaded, then cancel the current // XMLHttpRequest as well if (nativeFilesQueued.size() > 0 && lastXMLHttpRequest != null) { final File currentFile = nativeFilesQueued.get(0).cast(); if (nativeFile.<File>cast().getId().equals(currentFile.getId()) && currentFile.getStatus() == File.Status.IN_PROGRESS) { try { cancelledActiveUpload = true; nativeAbortXMLHttpRequest(lastXMLHttpRequest); } catch (Throwable t) { // Purposefully ignoring any problems that may occur when aborting the XMLHttpRequest } } } nativeUpdateFileProperties(nativeFile, File.Status.CANCELLED.toInt()); // Keep the global stats up to date (since SWFUpload isn't taken care of it for us in this case) nativeSetProperty(getStats(), "upload_cancelled", getStats().getUploadsCancelled() + 1); if (triggerErrorEvent) { nativeSetProperty(getStats(), "upload_errors", getStats().getUploadErrors() + 1); if (uploadErrorHandler != null) { uploadErrorHandler.onUploadError(new UploadErrorEvent(nativeFile.<File>cast(), UploadErrorEvent.ErrorCode.FILE_CANCELLED.toInt(), "File Cancelled")); } } // If we just cancelled the upload that was in progress then we need to explicitly invoke the complete // handler after the file cancellation (in order to let the next potential upload continue) if (cancelledActiveUpload) { uploadCompleteEventCallback(nativeFile.<File>cast()); } else { // If we're not cancelling the file upload that was in progress, then we need to handle // pulling it out of the internal queue and statistics on our own nativeFilesQueued.remove(nativeFile); nativeFilesQueuedById.remove(nativeFile.<File>cast().getId()); nativeSetProperty(getStats(), "files_queued", nativeFilesQueued.size()); nativeSetProperty(getStats(), "in_progress", 0); } } } } private static native void nativeAbortXMLHttpRequest(JavaScriptObject xmlHttpRequest) /*-{ xmlHttpRequest.abort(); }-*/; private static native void nativeCancelUpload(JavaScriptObject swfUpload, String fileId, boolean triggerErrorEvent) /*-{ swfUpload.cancelUpload(fileId, triggerErrorEvent); }-*/; /** * Cancel the first file in the queue. */ public void cancelUpload() { if (swfUpload != null) { nativeCancelUpload(swfUpload); } else { if (nativeFilesQueued.size() > 0) { cancelUpload(nativeFilesQueued.get(0).<File>cast().getId()); } } } private static native void nativeCancelUpload(JavaScriptObject swfUpload) /*-{ swfUpload.cancelUpload(); }-*/; /** * Cancel the the file with the supplied id. An uploadError event will be * issued. * * @param fileId The fileId to cancel */ public void cancelUpload(String fileId) { if (swfUpload != null) { nativeCancelUpload(swfUpload, fileId); } else { cancelUpload(fileId, true); } } private static native void nativeCancelUpload(JavaScriptObject swfUpload, String fileId) /*-{ swfUpload.cancelUpload(fileId); }-*/; /** * Cancel the first file in the queue. * * @param triggerErrorEvent if true, an uploadError event will be issued */ public void cancelUpload(boolean triggerErrorEvent) { if (swfUpload != null) { nativeCancelUpload(swfUpload, triggerErrorEvent); } else { if (nativeFilesQueued.size() > 0) { cancelUpload(nativeFilesQueued.get(0).<File>cast().getId(), triggerErrorEvent); } } } private static native void nativeCancelUpload(JavaScriptObject swfUpload, boolean triggerErrorEvent) /*-{ swfUpload.cancelUpload(null, triggerErrorEvent); }-*/; /** * Stop and re-queues the file currently being uploaded. Note: this method * is currently only supported when in Flash/SWFUpload mode, so it's marked * as "private" for now until it can be more fully implemented if deemed needed. * * @param triggerErrorEvent if true, an uploadError event will be issued * @param fileId The fileId to stop */ @SuppressWarnings({ "UnusedDeclaration" }) private void stopUpload(String fileId, boolean triggerErrorEvent) { // TODO: Implement AJAX equivalent if (swfUpload != null) { nativeStopUpload(swfUpload, fileId, triggerErrorEvent); } } private static native void nativeStopUpload(JavaScriptObject swfUpload, String fileId, boolean triggerErrorEvent) /*-{ swfUpload.stopUpload(fileId, triggerErrorEvent); }-*/; private JavaScriptObject swfUpload; /** * Returns true if the browser appears to support an HTML5 style upload (via the XMLHttpRequest Level 2 * API). * <p/> * Logic adapted from: http://blog.new-bamboo.co.uk/2012/01/10/ridiculously-simple-ajax-uploads-with-formdata * * @return 'true' if an HTML5 style upload can be used, or 'false' if a Flash style upload * (utilizing the SWFUpload library) is the only available option. */ public static boolean isAjaxUploadWithProgressEventsSupported() { return isFileAPISupported() && isAjaxUploadProgressEventsSupported() && isFormDataSupported(); } private static native boolean isFileAPISupported() /*-{ var inputElement = document.createElement('INPUT'); inputElement.type = 'file'; return 'files' in inputElement; }-*/; private static native boolean isAjaxUploadProgressEventsSupported() /*-{ var xhr = new XMLHttpRequest(); return !!(xhr && ('upload' in xhr) && ('onprogress' in xhr.upload)); }-*/; private static native boolean isFormDataSupported() /*-{ return !!window.FormData; }-*/; // Purposefully using concrete types here instead of a generic list to increase GWT performance private ArrayList<JavaScriptObject> nativeFilesQueued = new ArrayList<JavaScriptObject>(); private HashMap<String, JavaScriptObject> nativeFilesQueuedById = new HashMap<String, JavaScriptObject>(); private FileUpload fileUpload; @Override protected void onLoad() { // Make sure our entire panel fits the size that they wanted for the button if (this.buttonWidth >= 0) { this.setWidth(this.buttonWidth + "px"); } if (this.buttonHeight >= 0) { this.setHeight(this.buttonHeight + "px"); } if (ajaxUploadEnabled && isAjaxUploadWithProgressEventsSupported()) { // If the browser supports the XMLHttpRequest Level 2 type then we can avoid rendering the flash component // and just stick with a DOM/Ajax approach. // Use a hidden file input component to handle allowing the user to popup the file system dialog // (but keep it outside of the button itself so it doesn't interfere with the mouse events) this.add(createFileUpload()); // Create the main element that will hold all of the inner workings of the uploader component Label button = new Label(); button.setWidth("100%"); button.setHeight("100%"); if (this.buttonCursor != null) { switch (this.buttonCursor) { case ARROW: button.getElement().getStyle().setCursor(Style.Cursor.DEFAULT); break; case HAND: button.getElement().getStyle().setCursor(Style.Cursor.POINTER); break; } } // Setup what we want to happen when someone clicks anywhere on the button button.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { if (buttonDisabled) { return; } switch (buttonAction) { case START_UPLOAD: startUpload(); break; case SELECT_FILES: openFileDialog(fileUpload, true); break; case SELECT_FILE: default: openFileDialog(fileUpload, false); break; } } }); button.addMouseOverHandler(new MouseOverHandler() { public void onMouseOver(MouseOverEvent event) { if (buttonImageURL != null && buttonHeight >= 0 && !buttonDisabled) { buttonImageElement.getStyle().setProperty("backgroundPosition", "0px -" + buttonHeight + "px"); } } }); button.addMouseOutHandler(new MouseOutHandler() { public void onMouseOut(MouseOutEvent event) { if (buttonImageURL != null && buttonHeight >= 0 && !buttonDisabled) { buttonImageElement.getStyle().setProperty("backgroundPosition", "0px 0px"); } } }); button.addMouseDownHandler(new MouseDownHandler() { public void onMouseDown(MouseDownEvent event) { if (buttonImageURL != null && buttonHeight >= 0 && !buttonDisabled) { buttonImageElement.getStyle().setProperty("backgroundPosition", "0px -" + (buttonHeight * 2) + "px"); } } }); button.addMouseUpHandler(new MouseUpHandler() { public void onMouseUp(MouseUpEvent event) { if (buttonImageURL != null && buttonHeight >= 0 && !buttonDisabled) { buttonImageElement.getStyle().setProperty("backgroundPosition", "0px 0px"); } } }); // Depending on the way they wanted the uploader button rendered, create the appropriate elements // in the DOM that the user will click on. if (this.buttonTextStyle != null) { buttonTextStyleElement = Document.get().createStyleElement(); buttonTextStyleElement.setInnerText(this.buttonTextStyle); button.getElement().appendChild(buttonTextStyleElement); } if (this.buttonText != null) { buttonTextElement = Document.get().createDivElement(); buttonTextElement.setInnerHTML(this.buttonText); buttonTextElement.getStyle().setWidth(100, Style.Unit.PCT); buttonTextElement.getStyle().setHeight(100, Style.Unit.PCT); if (this.buttonTextLeftPadding > Integer.MIN_VALUE) { buttonTextElement.getStyle().setPaddingLeft(this.buttonTextLeftPadding, Style.Unit.PX); } if (this.buttonTextTopPadding > Integer.MIN_VALUE) { buttonTextElement.getStyle().setPaddingTop(this.buttonTextTopPadding, Style.Unit.PX); } button.getElement().appendChild(buttonTextElement); } if (this.buttonImageURL != null) { buttonImageElement = Document.get().createDivElement(); buttonImageElement.getStyle().setBackgroundImage("url(\"" + this.buttonImageURL + "\")"); if (this.buttonDisabled) { buttonImageElement.getStyle().setProperty("backgroundPosition", "0px -" + (buttonHeight * 3) + "px"); } else { buttonImageElement.getStyle().setProperty("backgroundPosition", "0px 0px"); } buttonImageElement.getStyle().setWidth(100, Style.Unit.PCT); buttonImageElement.getStyle().setHeight(100, Style.Unit.PCT); button.getElement().appendChild(buttonImageElement); } // Add the entire button to the DOM this.add(button); } else { // If the browser doesn't support the XMLHttpRequest Level 2 type, then our only option is to use // the Flash/SWFUpload component. // The SWFUpload JS code completely replaces the DOM element that you give it as a target, // so we're creating an inner component that it can replace - leaving the outer component // for the caller to use as the GWT Widget that they can manage and style within the appropriate // container within their GWT application DivElement swfUploadElement = Document.get().createDivElement(); swfUploadElement.setId(Document.get().createUniqueId()); this.getElement().appendChild(swfUploadElement); JavaScriptObject nativeOptions = createNativeOptions(swfUploadElement.getId()); // Build a map that we'll use during the native creation process to setup // the necessary JSNI bridges to our Java event handlers... JSONObject eventHandlers = new JSONObject(); eventHandlers.put("swfupload_loaded_handler", JSONBoolean.getInstance(swfUploadLoadedHandler != null)); eventHandlers.put("file_dialog_start_handler", JSONBoolean.getInstance(fileDialogStartHandler != null)); eventHandlers.put("file_queued_handler", JSONBoolean.getInstance(fileQueuedHandler != null)); eventHandlers.put("file_queue_error_handler", JSONBoolean.getInstance(fileQueueErrorHandler != null)); eventHandlers.put("file_dialog_complete_handler", JSONBoolean.getInstance(fileDialogCompleteHandler != null)); eventHandlers.put("upload_start_handler", JSONBoolean.getInstance(uploadStartHandler != null)); eventHandlers.put("upload_progress_handler", JSONBoolean.getInstance(uploadProgressHandler != null)); eventHandlers.put("upload_error_handler", JSONBoolean.getInstance(uploadErrorHandler != null)); eventHandlers.put("upload_success_handler", JSONBoolean.getInstance(uploadSuccessHandler != null)); eventHandlers.put("upload_complete_handler", JSONBoolean.getInstance(uploadCompleteHandler != null)); swfUpload = nativeCreateSWFUpload(nativeOptions, eventHandlers.getJavaScriptObject()); } } private FileUpload createFileUpload() { fileUpload = new FileUpload(); fileUpload.getElement().getStyle().setDisplay(Style.Display.NONE); if (fileTypes != null) { // Convert the format that the SWFUpload/Flash parameter expects to the W3C DOM standard // See: http://dev.w3.org/html5/spec/states-of-the-type-attribute.html#attr-input-accept fileUpload.getElement().setAttribute("accept", this.fileTypes.replaceAll("\\;", ",").replaceAll("\\*\\.", ".")); // TODO: Need to consider validation of this in the file queued handler as well, as the browsers don't // appear to consistently support the "accept" attribute } final AbsolutePanel panel = this; fileUpload.addChangeHandler(new ChangeHandler() { public void onChange(ChangeEvent event) { JsArray selectedFiles = nativeGetSelectedFiles(fileUpload.getElement()); // Every time a file is selected replace the FileUpload component running in the DOM so that // the user can continue to attempt uploading the same file multiple times (otherwise the // "ChangeHandler" never fires) panel.remove(fileUpload); panel.add(createFileUpload()); addFilesToQueue(selectedFiles); } }); return fileUpload; } private long lastFileId = 0; private String getNextFileId() { lastFileId++; return "Uploader_" + lastFileId; } /** * When implementing drag/drop support for file uploads, this method can be used to * pass into the uploader the list of native file references that the user dropped * onto the application to be uploaded. Will normally be used in conjunction * with the {@link #getDroppedFiles(com.google.gwt.dom.client.NativeEvent)} method. E.g. * <pre><code> * final Uploader uploader = new Uploader(); * dropFilesArea.addDropHandler(new DropHandler() { * public void onDrop(DropEvent event) { * event.preventDefault(); * uploader.addFilesToQueue( * Uploader.getDroppedFiles(event.getNativeEvent()) * ); * } * }); * </code></pre> * * @param files An array of native file references to be added to the queue. */ public void addFilesToQueue(JsArray files) { int filesQueued = 0; if (files != null) { for (int i = 0; i < files.length(); i++) { JavaScriptObject nativeFile = files.get(i); // Make sure our maximum allowable queue size upload limit has not been exceeded if (nativeFilesQueued.size() >= fileQueueLimit) { fileQueueErrorEventCallback(nativeFile.<File>cast(), FileQueueErrorEvent.ErrorCode.QUEUE_LIMIT_EXCEEDED.toInt(), "Exceeded file queue size limit of " + fileQueueLimit); break; } if (fileUploadLimit > 0 && totalFilesUploaded >= fileUploadLimit) { // Keep the global stats up to date (since SWFUpload isn't taken care of it for us in this case) nativeSetProperty(getStats(), "upload_errors", getStats().getUploadErrors() + 1); if (uploadErrorHandler != null) { uploadErrorHandler.onUploadError(new UploadErrorEvent(nativeFile.<File>cast(), UploadErrorEvent.ErrorCode.UPLOAD_LIMIT_EXCEEDED.toInt(), "Exceeded upload limit of " + fileUploadLimit)); } break; } // Ensure the file size can be determined and that the file has some contents if (nativeFile.<File>cast().getSize() <= 0) { fileQueueErrorEventCallback(nativeFile.<File>cast(), FileQueueErrorEvent.ErrorCode.ZERO_BYTE_FILE.toInt(), "File is zero bytes and cannot be uploaded."); continue; } // Make sure the file doesn't exceed the configured size limit if (exceedsFileSizeLimit(nativeFile)) { fileQueueErrorEventCallback(nativeFile.<File>cast(), FileQueueErrorEvent.ErrorCode.FILE_EXCEEDS_SIZE_LIMIT.toInt(), "File size exceeds allowed limit."); continue; } nativeSetProperty(nativeFile, "id", getNextFileId()); filesQueued++; // Track each of the files that still need to be uploaded addFileToQueue(nativeFile); } } // If requested, notify the app each time the user has finished selecting a bunch of files if (fileDialogCompleteHandler != null) { fileDialogCompleteHandler.onFileDialogComplete(new FileDialogCompleteEvent( files != null ? files.length() : 0, filesQueued, nativeFilesQueued.size())); } } /** * A convenience method that can be used to extract the files that a user dropped * on the application during a GWT drop event. Useful when adding drag/drop support * for file uploads to your application. E.g. * <pre><code> * final Uploader uploader = new Uploader(); * dropFilesArea.addDropHandler(new DropHandler() { * public void onDrop(DropEvent event) { * event.preventDefault(); * uploader.addFilesToQueue( * Uploader.getDroppedFiles(event.getNativeEvent()) * ); * } * }); * </code></pre> * * @param event The native event provided by GWT that was fired when the drop occurred * @return An array of native file references that were dropped as part of the event, * which can subsequently be passed to the * {@link #addFileToQueue(com.google.gwt.core.client.JavaScriptObject)} method. */ public static native JsArray getDroppedFiles(NativeEvent event) /*-{ return event.target.files || event.dataTransfer.files; }-*/; private void addFileToQueue(JavaScriptObject nativeFile) { // Track each of the files that still need to be uploaded nativeFilesQueued.add(nativeFile); nativeFilesQueuedById.put(nativeFile.<File>cast().getId(), nativeFile); // SWFUpload isn't in play in this case, so we need to keep the global stats up to date manually nativeSetProperty(getStats(), "files_queued", nativeFilesQueued.size()); // If requested, notify the app each time a new file is added to the queue if (fileQueuedHandler != null) { fileQueuedHandler.onFileQueued(new FileQueuedEvent( nativeUpdateFileProperties(nativeFile, File.Status.QUEUED.toInt()).<File>cast())); } } private boolean exceedsFileSizeLimit(JavaScriptObject nativeFile) { long fileSize = nativeFile.<File>cast().getSize(); return this.fileSizeLimit != 0 && fileSize > this.fileSizeLimit; } private void openFileDialog(FileUpload fileUpload, boolean multipleFiles) { if (multipleFiles) { fileUpload.getElement().setAttribute("multiple", "true"); } else { fileUpload.getElement().removeAttribute("multiple"); } if (fileDialogStartHandler != null) { fileDialogStartHandler.onFileDialogStartEvent(new FileDialogStartEvent()); } InputElement.as(fileUpload.getElement()).click(); } @Override protected void onUnload() { if (swfUpload != null) { nativeSWFUploadDestroy(swfUpload); swfUpload = null; } buttonTextStyleElement = null; buttonTextElement = null; buttonImageElement = null; } private JavaScriptObject createNativeOptions(String placeHolderId) { JSONObject options = configurable.getOptions(); if (options == null) { options = new JSONObject(); } // Let the SWFUpload component know which element it can render itself inside of options.put("button_placeholder_id", new JSONString(placeHolderId)); // Unless they've explicitly overridden it, automatically serve the SWF out of our modules directly (which // should be included by GWT when it compiles in all of the resources in our "public" directory) if (flashURL == null) { options.put("flash_url", new JSONString(GWT.getModuleBaseURL() + "swfupload.swf")); } // For debugging the raw options that we're passing to the component on startup, uncomment the following line // com.google.gwt.user.client.Window.alert(options.toString()); return options.getJavaScriptObject(); } private native JavaScriptObject nativeCreateSWFUpload(JavaScriptObject options, JavaScriptObject eventHandlers) /*-{ var self = this; // Add in GWT interceptor callback functions for the various event handlers if (eventHandlers['swfupload_loaded_handler']) { options.swfupload_loaded_handler = function() { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::swfUploadLoadedEventCallback()(); }; } if (eventHandlers['file_dialog_start_handler']) { options.file_dialog_start_handler = function() { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::fileDialogStartEventCallback()(); }; } if (eventHandlers['file_queued_handler']) { options.file_queued_handler = function(file) { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::fileQueuedEventCallback(Lcom/ait/toolkit/clientio/uploader/client/File;)(file); }; } if (eventHandlers['file_queue_error_handler']) { options.file_queue_error_handler = function(file, errorCode, message) { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::fileQueueErrorEventCallback(Lcom/ait/toolkit/clientio/uploader/client/File;ILjava/lang/String;)(file, errorCode, message); }; } if (eventHandlers['file_dialog_complete_handler']) { options.file_dialog_complete_handler = function( numberOfFilesSelected, numberOfFilesQueued, totalFilesInQueue) { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::fileDialogCompleteEventCallback(III)(numberOfFilesSelected, numberOfFilesQueued, totalFilesInQueue); }; } if (eventHandlers['upload_start_handler']) { options.upload_start_handler = function(file) { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::uploadStartEventCallback(Lcom/ait/toolkit/clientio/uploader/client/File;)(file); }; } if (eventHandlers['upload_progress_handler']) { options.upload_progress_handler = function(file, bytesComplete, bytesTotal) { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::uploadProgressEventCallback(Lcom/ait/toolkit/clientio/uploader/client/File;DD)(file, bytesComplete, bytesTotal); }; } if (eventHandlers['upload_error_handler']) { options.upload_error_handler = function(file, errorCode, message) { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::uploadErrorEventCallback(Lcom/ait/toolkit/clientio/uploader/client/File;ILjava/lang/String;)(file, errorCode, message); }; } if (eventHandlers['upload_success_handler']) { options.upload_success_handler = function(file, serverData, responseReceived) { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::uploadSuccessEventCallback(Lcom/ait/toolkit/clientio/uploader/client/File;Ljava/lang/String;Ljava/lang/String;)(file, responseReceived, serverData); }; } if (eventHandlers['upload_complete_handler']) { options.upload_complete_handler = function(file) { return self.@com.ait.toolkit.clientio.uploader.client.Uploader::uploadCompleteEventCallback(Lcom/ait/toolkit/clientio/uploader/client/File;)(file); }; } return new $wnd.SWFUpload(options); }-*/; @SuppressWarnings({ "UnusedDeclaration" }) private boolean swfUploadLoadedEventCallback() { return swfUploadLoadedHandler == null || swfUploadLoadedHandler.onSWFUploadLoaded(new SWFUploadLoadedEvent()); } @SuppressWarnings({ "UnusedDeclaration" }) private boolean fileDialogStartEventCallback() { return fileDialogStartHandler == null || fileDialogStartHandler.onFileDialogStartEvent(new FileDialogStartEvent()); } @SuppressWarnings({ "UnusedDeclaration" }) private boolean fileQueuedEventCallback(File file) { return fileQueuedHandler == null || fileQueuedHandler.onFileQueued(new FileQueuedEvent(file)); } @SuppressWarnings({ "UnusedDeclaration" }) private boolean fileQueueErrorEventCallback(File file, int errorCode, String message) { // Keep the global stats up to date (at least if SWFUpload isn't keep track of them for us) if (swfUpload == null) { nativeSetProperty(getStats(), "queue_errors", getStats().getQueueErrors() + 1); } return fileQueueErrorHandler == null || fileQueueErrorHandler.onFileQueueError(new FileQueueErrorEvent(file, errorCode, message)); } @SuppressWarnings({ "UnusedDeclaration" }) private boolean fileDialogCompleteEventCallback(int numberOfFilesSelected, int numberOfFilesQueued, int totalFilesInQueue) { return fileDialogCompleteHandler == null || fileDialogCompleteHandler.onFileDialogComplete( new FileDialogCompleteEvent(numberOfFilesSelected, numberOfFilesQueued, totalFilesInQueue)); } @SuppressWarnings({ "UnusedDeclaration" }) private boolean uploadStartEventCallback(File file) { return uploadStartHandler == null || uploadStartHandler.onUploadStart(new UploadStartEvent(file)); } @SuppressWarnings({ "UnusedDeclaration" }) private boolean uploadProgressEventCallback(File file, double bytesComplete, double bytesTotal) { // If we're running in Flash mode the SWFUpload "speed" plugin takes care of this for us. But, if // not we need to do our own math to keep the file statistics up to date. if (swfUpload == null) { long now = System.currentTimeMillis(); long bytesSinceLastEvent = (long) (bytesComplete - (nativeGetPropertyAsDouble(file, "previousBytesComplete"))); double timeSinceLastEvent = (now - nativeGetPropertyAsDouble(file, "timeSinceLastEvent")) / 1000.0; double elapsedTime = (now - nativeGetPropertyAsDouble(file, "startTime")) / 1000.0; double currentSpeed = (bytesSinceLastEvent * 8) / timeSinceLastEvent; if (!Double.isInfinite(currentSpeed) && !Double.isNaN(currentSpeed)) { nativeAddValueToArray(file, "movingAverageHistory", currentSpeed); } nativeSetProperty(file, "bytesSinceLastEvent", bytesSinceLastEvent); nativeSetProperty(file, "previousBytesComplete", bytesComplete); nativeSetProperty(file, "timeSinceLastEvent", now); // consoleDebug(currentSpeed + ""); // Set properties nativeSetProperty(file, "averageSpeed", (bytesComplete * 8) / elapsedTime); // Bits per seconds nativeSetProperty(file, "currentSpeed", currentSpeed); // Bytes per seconds since last event nativeSetProperty(file, "movingAverageSpeed", calculateMovingAverage(nativeGetPropertyAsArray(file, "movingAverageHistory"))); // Bytes per // second nativeSetProperty(file, "percentUploaded", (bytesComplete / bytesTotal * 100)); // Bytes per seconds nativeSetProperty(file, "sizeUploaded", bytesComplete); // Bytes uploaded so far nativeSetProperty(file, "timeElapsed", elapsedTime); // Time since upload started nativeSetProperty(file, "timeRemaining", (bytesTotal - bytesComplete) / (bytesComplete / elapsedTime)); } return uploadProgressHandler == null || uploadProgressHandler .onUploadProgress(new UploadProgressEvent(file, (long) bytesComplete, (long) bytesTotal)); } @SuppressWarnings({ "UnusedDeclaration" }) private boolean uploadErrorEventCallback(File file, int errorCode, String message) { // If the user manually cancelled the file upload earlier (via the cancelUpload() method), then we've already // invoked // the error handler callback if appropriate. if (file.getStatus() == File.Status.CANCELLED) { return true; } // Keep the global stats up to date (at least if SWFUpload isn't keep track of them for us) if (swfUpload == null) { nativeSetProperty(getStats(), "upload_errors", getStats().getUploadErrors() + 1); } boolean response; try { response = uploadErrorHandler == null || uploadErrorHandler.onUploadError(new UploadErrorEvent(file, errorCode, message)); } finally { // In the case that we're running in Ajax/DOM mode (as opposed to in SWFUpload/Flash mode) we need to // explicitly // invoke the complete handler after each file upload error. (When in SWFUpload/Flash // mode this callback will be invoked automatically by the SWFUpload library.) if (swfUpload == null) { try { uploadCompleteEventCallback(file); } finally { // Similarly, we need to handle the requeue error logic manually if (requeueOnError && errorCode != File.Status.CANCELLED.toInt()) { addFileToQueue(file); } } } } return response; } @SuppressWarnings({ "UnusedDeclaration" }) private boolean uploadSuccessEventCallback(File file, String responseReceived, String serverData) { boolean response = false; if (swfUpload == null) { // Only call the success handler if the response code is one of the expected type, otherwise call the error // handler if (httpSuccess == null) { httpSuccess = new long[] { 200 }; } boolean success = false; for (long code : httpSuccess) { if ((code + "").equals(responseReceived)) { success = true; } } if (success) { try { // If the user manually cancelled the file upload earlier (via the cancelUpload() method), then // don't // allow the success handler to be invoked (but still allow the complete handler to run) if (file.getStatus() != File.Status.CANCELLED) { totalFilesUploaded++; // If the file upload was super quick, we may not have gotten any progress events. So, let // anyone who cares know that we've made it to a 100% successfully with this file uploadProgressEventCallback(file, file.getSize(), file.getSize()); // Keep the global stats up to date (since SWFUpload isn't in play in this case) nativeSetProperty(getStats(), "successful_uploads", getStats().getSuccessfulUploads() + 1); response = uploadSuccessHandler == null || uploadSuccessHandler .onUploadSuccess(new UploadSuccessEvent(file, serverData, responseReceived)); } } finally { // In the case that we're running in Ajax/DOM mode (as opposed to in SWFUpload/Flash mode) we need // to explicitly // invoke the complete handler after each file is uploaded successfully. (When in SWFUpload/Flash // mode this callback will be invoked automatically by the Uploader library.) uploadCompleteEventCallback(file); } } else { uploadErrorEventCallback(file, UploadErrorEvent.ErrorCode.HTTP_ERROR.toInt(), "Unsuccessful server response code of: " + responseReceived); } } else { response = uploadSuccessHandler == null || uploadSuccessHandler .onUploadSuccess(new UploadSuccessEvent(file, serverData, responseReceived)); } return response; } @SuppressWarnings({ "UnusedDeclaration" }) private boolean uploadCompleteEventCallback(File file) { if (nativeFilesQueued.size() > 0) { final JavaScriptObject nativeFile = nativeFilesQueued.get(0); nativeFilesQueued.remove(nativeFile); nativeFilesQueuedById.remove(nativeFile.<File>cast().getId()); } // Keep the global stats up to date (at least if SWFUpload isn't keep track of them for us) if (swfUpload == null) { nativeSetProperty(getStats(), "files_queued", nativeFilesQueued.size()); nativeSetProperty(getStats(), "in_progress", 0); } return uploadCompleteHandler == null || uploadCompleteHandler.onUploadComplete(new UploadCompleteEvent(file)); } private static native boolean nativeSWFUploadDestroy(JavaScriptObject swfUpload) /*-{ return swfUpload.destroy(); }-*/; private static native JsArray nativeGetSelectedFiles(Element fileInputElement) /*-{ return fileInputElement.files; }-*/; // We're using native objects instead of Java objects in order to keep the rest of the code compatible with // the native types returned by SWFUpload private static native JavaScriptObject nativeUpdateFileProperties(JavaScriptObject file, int fileStatus) /*-{ file.modificationdate = file.lastModifiedDate; file.filestatus = fileStatus; return file; }-*/; private static native void nativeSetProperty(JavaScriptObject obj, String key, double value) /*-{ obj[key] = value; }-*/; private static native void nativeSetProperty(JavaScriptObject obj, String key, String value) /*-{ obj[key] = value; }-*/; private static native void nativeAddValueToArray(JavaScriptObject obj, String key, double value) /*-{ if (!obj[key]) { obj[key] = new Array(); } obj[key].push(value); }-*/; private static native double nativeGetPropertyAsDouble(JavaScriptObject obj, String key) /*-{ return obj[key] ? obj[key] : 0.0; }-*/; private static native JsArrayNumber nativeGetPropertyAsArray(JavaScriptObject obj, String key) /*-{ return obj[key] ? obj[key] : new Array(); }-*/; /** * Implementation of SWFUpload Speed plug-in's moving average calculation based on the * set of data points available ported directly from the original 2.9 implementation * * @param history An array of values calculated so far * @return The moving average */ private static double calculateMovingAverage(JsArrayNumber history) { double[] vals = new double[history.length()]; long size = history.length(); double sum = 0.0; double mSum = 0.0; long mCount = 0; // Check for sufficient data if (size >= 8) { // Clone the array and Calculate sum of the values for (int i = 0; i < size; i++) { vals[i] = history.get(i); sum += vals[i]; } double mean = sum / size; // Calculate variance for the set double varianceTemp = 0.0; for (int i = 0; i < size; i++) { varianceTemp += Math.pow((vals[i] - mean), 2); } double variance = varianceTemp / size; double standardDev = Math.sqrt(variance); // Standardize the Data for (int i = 0; i < size; i++) { vals[i] = (vals[i] - mean) / standardDev; } // Calculate the average excluding outliers double deviationRange = 2.0; for (int i = 0; i < size; i++) { if (vals[i] <= deviationRange && vals[i] >= -deviationRange) { mCount++; mSum += history.get(i); } } } else { // Calculate the average (not enough data points to remove outliers) mCount = size; for (int i = 0; i < size; i++) { mSum += history.get(i); } } return mSum / mCount; } @SuppressWarnings({ "UnusedDeclaration" }) private native void consoleDebug(String msg) /*-{ $wnd.console.debug(msg); }-*/; }