-/* mw.Api objects represent the API of a particular MediaWiki server. */
-
+/**
+ * mw.Api objects represent the API of a particular MediaWiki server.
+ */
( function( $, mw, undefined ) {
/**
ajax: {
url: mw.util.wikiScript( 'api' ),
- ok: function() {},
-
- // caller can supply handlers for http transport error or api errors
- err: function( code, result ) {
- mw.log( 'mw.Api error: ' + code, 'debug' );
- },
-
- timeout: 30000, // 30 seconds
+ timeout: 30 * 1000, // 30 seconds
dataType: 'json'
}
mw.Api.prototype = {
/**
- * For api queries, in simple cases the caller just passes a success callback.
- * In complex cases they pass an object with a success property as callback and
- * probably other options.
- * Normalize the argument so that it's always the latter case.
+ * Normalize the ajax options for compatibility and/or convenience methods.
*
- * @param {Object|Function} An object contaning one or more of options.ajax,
+ * @param {undefined|Object|Function} An object contaning one or more of options.ajax,
* or just a success function (options.ajax.ok).
* @return {Object} Normalized ajax options.
*/
- normalizeAjaxOptions: function( arg ) {
- var opt = arg;
+ normalizeAjaxOptions: function ( arg ) {
+ // Arg argument is usually empty
+ // (before MW 1.20 it was often used to pass ok/err callbacks)
+ var opts = arg || {};
+ // Options can also be a success callback handler
if ( typeof arg === 'function' ) {
- opt = { 'ok': arg };
+ opts = { ok: arg };
}
- if ( !opt.ok ) {
- throw new Error( 'ajax options must include ok callback' );
- }
- return opt;
+ return opts;
},
/**
* Perform API get request
*
* @param {Object} request parameters
- * @param {Object|Function} ajax options, or just a success function
- * @return {jqXHR}
+ * @param {Object|Function} [optional] ajax options
+ * @return {jQuery.Promise}
*/
get: function( parameters, ajaxOptions ) {
ajaxOptions = this.normalizeAjaxOptions( ajaxOptions );
* @todo Post actions for nonlocal will need proxy
*
* @param {Object} request parameters
- * @param {Object|Function} ajax options, or just a success function
- * @return {jqXHR}
+ * @param {Object|Function} [optional] ajax options
+ * @return {jQuery.Promise}
*/
post: function( parameters, ajaxOptions ) {
ajaxOptions = this.normalizeAjaxOptions( ajaxOptions );
*
* @param {Object} request parameters
* @param {Object} ajax options
- * @return {jqXHR}
+ * @return {jQuery.Promise}
+ * - done: API response data as first argument
+ * - fail: errorcode as first arg, details (string or object) as second arg.
*/
ajax: function( parameters, ajaxOptions ) {
- var token;
+ var token,
+ apiDeferred = $.Deferred();
+
parameters = $.extend( {}, this.defaults.parameters, parameters );
ajaxOptions = $.extend( {}, this.defaults.ajax, ajaxOptions );
// So let's escape them here. See bug #28235
// This works because jQuery accepts data as a query string or as an Object
ajaxOptions.data = $.param( parameters ).replace( /\./g, '%2E' );
+
// If we extracted a token parameter, add it back in.
if ( token ) {
ajaxOptions.data += '&token=' + encodeURIComponent( token );
}
- ajaxOptions.error = function( xhr, textStatus, exception ) {
- ajaxOptions.err( 'http', {
- xhr: xhr,
- textStatus: textStatus,
- exception: exception
+
+ // Backwards compatibility: Before MediaWiki 1.20,
+ // callbacks were done with the 'ok' and 'err' property in ajaxOptions.
+ if ( ajaxOptions.ok ) {
+ apiDeferred.done( ajaxOptions.ok );
+ delete ajaxOptions.ok;
+ }
+ if ( ajaxOptions.err ) {
+ apiDeferred.fail( ajaxOptions.err );
+ delete ajaxOptions.err;
+ }
+
+ // Make the AJAX request
+ $.ajax( ajaxOptions )
+ // If AJAX fails, reject API call with error code 'http'
+ // and details in second argument.
+ .fail( function ( xhr, textStatus, exception ) {
+ apiDeferred.reject( 'http', {
+ xhr: xhr,
+ textStatus: textStatus,
+ exception: exception
+ } );
+ } )
+ // AJAX success just means "200 OK" response, also check API error codes
+ .done( function ( result ) {
+ if ( result === undefined || result === null || result === '' ) {
+ apiDeferred.reject( 'ok-but-empty',
+ 'OK response but empty result (check HTTP headers?)'
+ );
+ } else if ( result.error ) {
+ var code = result.error.code === undefined ? 'unknown' : result.error.code;
+ apiDeferred.reject( code, result );
+ } else {
+ apiDeferred.resolve( result );
+ }
} );
- };
-
- // Success just means 200 OK; also check for output and API errors
- ajaxOptions.success = function( result ) {
- if ( result === undefined || result === null || result === '' ) {
- ajaxOptions.err( 'ok-but-empty',
- 'OK response but empty result (check HTTP headers?)' );
- } else if ( result.error ) {
- var code = result.error.code === undefined ? 'unknown' : result.error.code;
- ajaxOptions.err( code, result );
- } else {
- ajaxOptions.ok( result );
- }
- };
-
- return $.ajax( ajaxOptions );
+
+ // Return the Promise
+ return apiDeferred.promise().fail( function ( code, details ) {
+ mw.log( 'mw.Api error: ', code, details );
+ });
}
};
/**
- * Additional mw.Api methods to assist with API calls related to parsing wikitext.
+ * mw.Api methods for parsing wikitext.
*/
-
-( function( $, mw ) {
+( function ( mw, $ ) {
$.extend( mw.Api.prototype, {
/**
* Convinience method for 'action=parse'. Parses wikitext into HTML.
*
* @param wikiText {String}
- * @param success {Function} callback to which to pass success HTML
- * @param err {Function} callback if error (optional)
- * @return {jqXHR}
+ * @param ok {Function} [optional] deprecated (success callback)
+ * @param err {Function} [optional] deprecated (error callback)
+ * @return {jQuery.Promise}
*/
- parse: function( wikiText, success, err ) {
- var params = {
- text: wikiText,
- action: 'parse'
- },
- ok = function( data ) {
+ parse: function( wikiText, ok, err ) {
+ var apiDeferred = $.Deferred();
+
+ // Backwards compatibility (< MW 1.20)
+ if ( ok ) {
+ apiDeferred.done( ok );
+ }
+ if ( err ) {
+ apiDeferred.fail( err );
+ }
+
+ this.get( {
+ action: 'parse',
+ text: wikiText
+ } )
+ .done( function ( data ) {
if ( data.parse && data.parse.text && data.parse.text['*'] ) {
- success( data.parse.text['*'] );
+ apiDeferred.resolve( data.parse.text['*'] );
}
- };
- return this.get( params, { ok: ok, err: err } );
- }
+ } )
+ .fail( apiDeferred.reject );
+ // Return the promise
+ return apiDeferred.promise();
+ }
} );
-} )( jQuery, mediaWiki );
+} )( mediaWiki, jQuery );
'tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js',
'tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js',
'tests/qunit/suites/resources/jquery/jquery.textSelection.test.js',
+ 'tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js',
+ 'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js',
+ 'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.parse.test.js',
'tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js',
- 'tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js',
),
'dependencies' => array(
'jquery.tablesorter',
'jquery.textSelection',
'mediawiki',
+ 'mediawiki.api',
+ 'mediawiki.api.parse',
+ 'mediawiki.jqueryMsg',
'mediawiki.Title',
'mediawiki.Uri',
'mediawiki.user',
'mediawiki.util',
'mediawiki.special.recentchanges',
- 'mediawiki.jqueryMsg',
- 'mediawiki.language'
+ 'mediawiki.language',
),
'position' => 'top',
)
--- /dev/null
+QUnit.module( 'mediawiki.api', QUnit.newMwEnvironment() );
+
+QUnit.asyncTest( 'Basic functionality', function ( assert ) {
+ var api, d1, d2, d3;
+ QUnit.expect( 3 );
+
+ api = new mw.Api();
+
+ d1 = api.get( {} )
+ .done( function ( data ) {
+ assert.deepEqual( data, [], 'If request succeeds without errors, resolve deferred' );
+ });
+
+ d2 = api.get({
+ action: 'doesntexist'
+ })
+ .fail( function ( errorCode, details ) {
+ assert.equal( errorCode, 'unknown_action', 'API error (e.g. "unknown_action") should reject the deferred' );
+ });
+
+ d3 = api.post( {} )
+ .done( function ( data ) {
+ assert.deepEqual( data, [], 'Simple POST request' );
+ });
+
+ // After all are completed, continue the test suite.
+ QUnit.whenPromisesComplete( d1, d2, d3 ).always( function () {
+ QUnit.start();
+ });
+});
+
+QUnit.asyncTest( 'Deprecated callback methods', function ( assert ) {
+ var api, d1, d2, d3;
+ QUnit.expect( 3 );
+
+ api = new mw.Api();
+
+ d1 = api.get( {}, function () {
+ assert.ok( true, 'Function argument treated as success callback.' );
+ });
+
+ d2 = api.get( {}, {
+ ok: function ( data ) {
+ assert.ok( true, '"ok" property treated as success callback.' );
+ }
+ });
+
+ d3 = api.get({
+ action: 'doesntexist'
+ }, {
+ err: function ( data ) {
+ assert.ok( true, '"err" property treated as error callback.' );
+ }
+ });
+
+ QUnit.whenPromisesComplete( d1, d2, d3 ).always( function () {
+ QUnit.start();
+ });
+});