From ee7132a55208b1319a28bb85ea8aa752992e7740 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bartosz=20Dziewo=C5=84ski?= Date: Wed, 19 Aug 2015 00:50:30 +0200 Subject: [PATCH] mediawiki.api.uploadWithFormData: Implement in terms of existing mw.Api functionality * mw.Api#ajax can already handle FormData, if instructed to, since d19432a332c21935d42087db706e50c5259063ea (which seems to have been a part of mobile uploads experiments). * MobileFrontend's api.js already had code to provide upload progress events while using mw.Api, lifted it from there. With this change, we should be able to just use mw.ForeignApi (being added in Ic20b9682d28633baa87d22e6e9fb71ce507da58d) to upload to a different wiki. (Assuming that the browser supports FormData.) Additionally: * Improve detection of whether we can use FormData: if we are given a HTMLInputElement, try to get a File from it before we fall back to iframe form upload. * mediawiki.api.edit: In #postWithEditToken, pass through the ajaxOptions parameter to #postWithToken. Change-Id: Ib9abe32ee3320c67ac0a4544c942b844a5550562 --- .../src/mediawiki.api/mediawiki.api.edit.js | 5 +- .../src/mediawiki.api/mediawiki.api.upload.js | 115 ++++++------------ 2 files changed, 43 insertions(+), 77 deletions(-) diff --git a/resources/src/mediawiki.api/mediawiki.api.edit.js b/resources/src/mediawiki.api/mediawiki.api.edit.js index dbe45bf618..e6161e422e 100644 --- a/resources/src/mediawiki.api/mediawiki.api.edit.js +++ b/resources/src/mediawiki.api/mediawiki.api.edit.js @@ -11,10 +11,11 @@ * cached token and start over. * * @param {Object} params API parameters + * @param {Object} [ajaxOptions] * @return {jQuery.Promise} See #post */ - postWithEditToken: function ( params ) { - return this.postWithToken( 'edit', params ); + postWithEditToken: function ( params, ajaxOptions ) { + return this.postWithToken( 'edit', params, ajaxOptions ); }, /** diff --git a/resources/src/mediawiki.api/mediawiki.api.upload.js b/resources/src/mediawiki.api/mediawiki.api.upload.js index cec352ae36..19d25423ff 100644 --- a/resources/src/mediawiki.api/mediawiki.api.upload.js +++ b/resources/src/mediawiki.api/mediawiki.api.upload.js @@ -47,29 +47,6 @@ .val( val ); } - /** - * Parse response from an XHR to the server. - * @private - * @param {Event} e - * @return {Object} - */ - function parseXHRResponse( e ) { - var response; - - try { - response = $.parseJSON( e.target.responseText ); - } catch ( error ) { - response = { - error: { - code: e.target.code, - info: e.target.responseText - } - }; - } - - return response; - } - /** * Process the result of the form submission, returned to an iframe. * This is the iframe's onload event. @@ -116,20 +93,25 @@ * @return {jQuery.Promise} */ upload: function ( file, data ) { - var iframe, formData; + var isFileInput, canUseFormData; + + isFileInput = file && file.nodeType === Node.ELEMENT_NODE; + + if ( formDataAvailable() && isFileInput && file.files ) { + file = file.files[0]; + } if ( !file ) { return $.Deferred().reject( 'No file' ); } - iframe = file.nodeType && file.nodeType === Node.ELEMENT_NODE; - formData = formDataAvailable() && file instanceof window.File; + canUseFormData = formDataAvailable() && file instanceof window.File; - if ( !iframe && !formData ) { + if ( !isFileInput && !canUseFormData ) { return $.Deferred().reject( 'Unsupported argument type passed to mw.Api.upload' ); } - if ( formData ) { + if ( canUseFormData ) { return this.uploadWithFormData( file, data ); } @@ -242,11 +224,10 @@ * Uploads a file using the FormData API. * @param {File} file * @param {Object} data + * @return {jQuery.Promise} */ uploadWithFormData: function ( file, data ) { - var key, xhr, - api = this, - formData = new FormData(), + var key, deferred = $.Deferred(); for ( key in data ) { @@ -256,58 +237,42 @@ } data = $.extend( {}, this.defaults.parameters, { action: 'upload' }, data ); - - $.each( data, function ( key, val ) { - formData.append( key, val ); - } ); + data.file = file; if ( !data.filename && !data.stash ) { return $.Deferred().reject( 'Filename not included in file data.' ); } - formData.append( 'file', file ); - - xhr = new XMLHttpRequest(); - - xhr.upload.addEventListener( 'progress', function ( e ) { - if ( e.lengthComputable ) { - deferred.notify( e.loaded / e.total ); - } - }, false ); - - xhr.addEventListener( 'abort', function ( e ) { - deferred.reject( parseXHRResponse( e ) ); - }, false ); - - xhr.addEventListener( 'load', function ( e ) { - var result = parseXHRResponse( e ); - - if ( result.error || result.warnings ) { - if ( result.error && result.error.code === 'badtoken' ) { - api.badToken( 'edit' ); + // Use this.postWithEditToken() or this.post() + this[ this.needToken() ? 'postWithEditToken' : 'post' ]( data, { + // Use FormData (if we got here, we know that it's available) + contentType: 'multipart/form-data', + // Provide upload progress notifications + xhr: function () { + var xhr = $.ajaxSettings.xhr(); + if ( xhr.upload ) { + // need to bind this event before we open the connection (see note at + // https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest#Monitoring_progress) + xhr.upload.addEventListener( 'progress', function ( ev ) { + if ( ev.lengthComputable ) { + deferred.notify( ev.loaded / ev.total ); + } + } ); } - - deferred.reject( result.error || result.warnings ); - } else { - deferred.notify( 1 ); - deferred.resolve( result ); + return xhr; } - }, false ); - - xhr.addEventListener( 'error', function ( e ) { - deferred.reject( parseXHRResponse( e ) ); - }, false ); - - xhr.open( 'POST', this.defaults.ajax.url, true ); - - if ( this.needToken() ) { - this.getEditToken().then( function ( token ) { - formData.append( 'token', token ); - xhr.send( formData ); + } ) + .done( function ( result ) { + if ( result.error || result.warnings ) { + deferred.reject( result.error || result.warnings ); + } else { + deferred.notify( 1 ); + deferred.resolve( result ); + } + } ) + .fail( function ( result ) { + deferred.reject( result ); } ); - } else { - xhr.send( formData ); - } return deferred.promise(); }, -- 2.20.1