/** * The FileHTML5 class provides a wrapper for a file pointer in an HTML5 The File wrapper * also implements the mechanics for uploading a file and tracking its progress. * @module file-html5 */ /** * The class provides a wrapper for a file pointer. * @class FileHTML5 * @extends Base * @constructor * @param {Object} config Configuration object. */ var Lang = Y.Lang, Bind = Y.bind, Win = Y.config.win; var FileHTML5 = function(o) { var file = null; if (FileHTML5.isValidFile(o)) { file = o; } else if (FileHTML5.isValidFile(o.file)) { file = o.file; } else { file = false; } FileHTML5.superclass.constructor.apply(this, arguments); if (file && FileHTML5.canUpload()) { if (!this.get("file")) { this._set("file", file); } if (!this.get("name")) { this._set("name", file.name || file.fileName); } if (this.get("size") != (file.size || file.fileSize)) { this._set("size", file.size || file.fileSize); } if (!this.get("type")) { this._set("type", file.type); } if (file.hasOwnProperty("lastModifiedDate") && !this.get("dateModified")) { this._set("dateModified", file.lastModifiedDate); } } }; Y.extend(FileHTML5, Y.Base, { /** * Construction logic executed during FileHTML5 instantiation. * * @method initializer * @protected */ initializer : function (cfg) { if (!this.get("id")) { this._set("id", Y.guid("file")); } }, /** * Handler of events dispatched by the XMLHTTPRequest. * * @method _uploadEventHandler * @param {Event} event The event object received from the XMLHTTPRequest. * @protected */ _uploadEventHandler: function (event) { var xhr = this.get("xhr"); switch (event.type) { case "progress": /** * Signals that progress has been made on the upload of this file. * * @event uploadprogress * @param event {Event} The event object for the `uploadprogress` with the * following payload: * <dl> * <dt>originEvent</dt> * <dd>The original event fired by the XMLHttpRequest instance.</dd> * <dt>bytesLoaded</dt> * <dd>The number of bytes of the file that has been uploaded.</dd> * <dt>bytesTotal</dt> * <dd>The total number of bytes in the file (the file size)</dd> * <dt>percentLoaded</dt> * <dd>The fraction of the file that has been uploaded, out of 100.</dd> * </dl> */ this.fire("uploadprogress", {originEvent: event, bytesLoaded: event.loaded, bytesTotal: this.get("size"), percentLoaded: Math.min(100, Math.round(10000*event.loaded/this.get("size"))/100) }); this._set("bytesUploaded", event.loaded); break; case "load": /** * Signals that this file's upload has completed and data has been received from the server. * * @event uploadcomplete * @param event {Event} The event object for the `uploadcomplete` with the * following payload: * <dl> * <dt>originEvent</dt> * <dd>The original event fired by the XMLHttpRequest instance.</dd> * <dt>data</dt> * <dd>The data returned by the server.</dd> * </dl> */ if (xhr.status >= 200 && xhr.status <= 299) { this.fire("uploadcomplete", {originEvent: event, data: event.target.responseText}); var xhrupload = xhr.upload, boundEventHandler = this.get("boundEventHandler"); xhrupload.removeEventListener ("progress", boundEventHandler); xhrupload.removeEventListener ("error", boundEventHandler); xhrupload.removeEventListener ("abort", boundEventHandler); xhr.removeEventListener ("load", boundEventHandler); xhr.removeEventListener ("error", boundEventHandler); xhr.removeEventListener ("readystatechange", boundEventHandler); this._set("xhr", null); } else { this.fire("uploaderror", {originEvent: event, data: xhr.responseText, status: xhr.status, statusText: xhr.statusText, source: "http"}); } break; case "error": /** * Signals that this file's upload has encountered an error. * * @event uploaderror * @param event {Event} The event object for the `uploaderror` with the * following payload: * <dl> * <dt>originEvent</dt> * <dd>The original event fired by the XMLHttpRequest instance.</dd> * <dt>data</dt> * <dd>The data returned by the server.</dd> * <dt>status</dt> * <dd>The status code reported by the XMLHttpRequest. If it's an HTTP error, then this corresponds to the HTTP status code received by the uploader.</dd> * <dt>statusText</dt> * <dd>The text of the error event reported by the XMLHttpRequest instance</dd> * <dt>source</dt> * <dd>Either "http" (if it's an HTTP error), or "io" (if it's a network transmission * error.)</dd> * * </dl> */ this.fire("uploaderror", {originEvent: event, data: xhr.responseText, status: xhr.status, statusText: xhr.statusText, source: "io"}); break; case "abort": /** * Signals that this file's upload has been cancelled. * * @event uploadcancel * @param event {Event} The event object for the `uploadcancel` with the * following payload: * <dl> * <dt>originEvent</dt> * <dd>The original event fired by the XMLHttpRequest instance.</dd> * </dl> */ this.fire("uploadcancel", {originEvent: event}); break; case "readystatechange": /** * Signals that XMLHttpRequest has fired a readystatechange event. * * @event readystatechange * @param event {Event} The event object for the `readystatechange` with the * following payload: * <dl> * <dt>readyState</dt> * <dd>The readyState code reported by the XMLHttpRequest instance.</dd> * <dt>originEvent</dt> * <dd>The original event fired by the XMLHttpRequest instance.</dd> * </dl> */ this.fire("readystatechange", {readyState: event.target.readyState, originEvent: event}); break; } }, /** * Starts the upload of a specific file. * * @method startUpload * @param url {String} The URL to upload the file to. * @param parameters {Object} (optional) A set of key-value pairs to send as variables along with the file upload HTTP request. * @param fileFieldName {String} (optional) The name of the POST variable that should contain the uploaded file ('Filedata' by default) */ startUpload: function(url, parameters, fileFieldName) { this._set("bytesUploaded", 0); this._set("xhr", new XMLHttpRequest()); this._set("boundEventHandler", Bind(this._uploadEventHandler, this)); var uploadData = new FormData(), fileField = fileFieldName || "Filedata", xhr = this.get("xhr"), xhrupload = this.get("xhr").upload, boundEventHandler = this.get("boundEventHandler"); Y.each(parameters, function (value, key) {uploadData.append(key, value);}); uploadData.append(fileField, this.get("file")); xhr.addEventListener ("loadstart", boundEventHandler, false); xhrupload.addEventListener ("progress", boundEventHandler, false); xhr.addEventListener ("load", boundEventHandler, false); xhr.addEventListener ("error", boundEventHandler, false); xhrupload.addEventListener ("error", boundEventHandler, false); xhrupload.addEventListener ("abort", boundEventHandler, false); xhr.addEventListener ("abort", boundEventHandler, false); xhr.addEventListener ("loadend", boundEventHandler, false); xhr.addEventListener ("readystatechange", boundEventHandler, false); xhr.open("POST", url, true); xhr.withCredentials = this.get("xhrWithCredentials"); Y.each(this.get("xhrHeaders"), function (value, key) { xhr.setRequestHeader(key, value); }); xhr.send(uploadData); /** * Signals that this file's upload has started. * * @event uploadstart * @param event {Event} The event object for the `uploadstart` with the * following payload: * <dl> * <dt>xhr</dt> * <dd>The XMLHttpRequest instance handling the file upload.</dd> * </dl> */ this.fire("uploadstart", {xhr: xhr}); }, /** * Cancels the upload of a specific file, if currently in progress. * * @method cancelUpload */ cancelUpload: function () { var xhr = this.get('xhr'); if (xhr) { xhr.abort(); } } }, { /** * The identity of the class. * * @property NAME * @type String * @default 'file' * @readOnly * @protected * @static */ NAME: 'file', /** * The type of transport. * * @property TYPE * @type String * @default 'html5' * @readOnly * @protected * @static */ TYPE: 'html5', /** * Static property used to define the default attribute configuration of * the File. * * @property ATTRS * @type {Object} * @protected * @static */ ATTRS: { /** * A String containing the unique id of the file wrapped by the FileFlash instance. * The id is supplied by the Flash player uploader. * * @attribute id * @type {String} * @initOnly */ id: { writeOnce: "initOnly", value: null }, /** * The size of the file wrapped by FileHTML5. This value is supplied by the instance of File(). * * @attribute size * @type {Number} * @initOnly */ size: { writeOnce: "initOnly", value: 0 }, /** * The name of the file wrapped by FileHTML5. This value is supplied by the instance of File(). * * @attribute name * @type {String} * @initOnly */ name: { writeOnce: "initOnly", value: null }, /** * The date that the file wrapped by FileHTML5 was created on. This value is supplied by the instance of File(). * * @attribute dateCreated * @type {Date} * @initOnly * @default null */ dateCreated: { writeOnce: "initOnly", value: null }, /** * The date that the file wrapped by FileHTML5 was last modified on. This value is supplied by the instance of File(). * * @attribute dateModified * @type {Date} * @initOnly */ dateModified: { writeOnce: "initOnly", value: null }, /** * The number of bytes of the file that has been uploaded to the server. This value is * non-zero only while a file is being uploaded. * * @attribute bytesUploaded * @type {Date} * @readOnly */ bytesUploaded: { readOnly: true, value: 0 }, /** * The type of the file wrapped by FileHTML. This value is provided by the instance of File() * * @attribute type * @type {String} * @initOnly */ type: { writeOnce: "initOnly", value: null }, /** * The pointer to the instance of File() wrapped by FileHTML5. * * @attribute file * @type {File} * @initOnly */ file: { writeOnce: "initOnly", value: null }, /** * The pointer to the instance of XMLHttpRequest used by FileHTML5 to upload the file. * * @attribute xhr * @type {XMLHttpRequest} * @initOnly */ xhr: { readOnly: true, value: null }, /** * The dictionary of headers that should be set on the XMLHttpRequest object before * sending it. * * @attribute xhrHeaders * @type {Object} * @initOnly */ xhrHeaders: { value: {} }, /** * A Boolean indicating whether the XMLHttpRequest should be sent with user credentials. * This does not affect same-site requests. * * @attribute xhrWithCredentials * @type {Boolean} * @initOnly */ xhrWithCredentials: { value: true }, /** * The bound event handler used to handle events from XMLHttpRequest. * * @attribute boundEventHandler * @type {Function} * @initOnly */ boundEventHandler: { readOnly: true, value: null } }, /** * Checks whether a specific native file instance is valid * * @method isValidFile * @param file {File} A native File() instance. * @static */ isValidFile: function (file) { return (Win && Win.File && file instanceof File); }, /** * Checks whether the browser has a native upload capability * via XMLHttpRequest Level 2. * * @method canUpload * @static */ canUpload: function () { return (Win && Win.FormData && Win.XMLHttpRequest); } }); Y.FileHTML5 = FileHTML5;