r109721 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r109720‎ | r109721 | r109722 >
Date:04:58, 22 January 2012
Author:krinkle
Status:ok
Tags:
Comment:
[ApiSandbox] front-end code clean up
CSS:
* #api-sandbox-output: removing all styling
-- width: 100%; Parent element has padding. Children with width 100% will exceed the box and cause scroll bars
-- overflow: scroll; Don't force scroll bars in both directions all the time. auto will show them for other one or both directions, when needed.
-- height: use the page scrollbar, instead of a limited height so that users can see more of the result on their screen (making optimal use of the window height)
* .api-sandbox-label: removing font-family, using <code> instead
-- "font-family: monospace;" shows a very tiny font, should use "font-family: Courier" as preference first. Wikis might want to style code information in a custom way as well. Applying all that by using <code>

JS:
* Aliasing mw and undefined locally
* Using strict comparison to undefined instead of operating typeof and comparing strings
* Moving definitions out of jQuery( domready fn ). No need to postpone all those declarations to after document-ready, speed up by putting only dom manipulation and invocations inside the domready hook
* Order of definitions
* Coding style and other minor stuff
* Changing interactive states through properties instead of attributes (changing attribute disabled, checked, value etc. only change the html document element, not the live presentation, need to change properties for that). jQuery falls back from .attr() to .prop() for some attributes, but that should not be relied on, and is deprecated:
-- - $foo.attr( 'disabled', 'disabled' ); $foo.removeAttr( 'disabled' );
-- + $foo.prop( 'disabled', true ); $foo.prop( 'disabled', false )
* Removing no-oop line that causes exception to be thrown:
-- $examplesContainer.hide();
-- > ReferenceError: $examplesContainer not declared anywhere
* Moving var statements to the top of each scope, so that it becomes clear when a variable is re-used (JavaScript has no block scope or loop scope, a second var statement for the same variable name in the same scope is simply ignored, there were a few for-loops using each others 'i' variable). Also makes it easy to re-arrange functions since one doesn't have to pay attention if 'var' is already there. Always keep it at the top. JavaScript does this internally at runtime if not already.
* Fixing broken line:
-- typeof selected !== 'array'
-- There is no typeof 'array' in JavaScript. Pretty much everything is an object (e.g. new Array() etc. literal [] doesn't change that). Using $.isArray instead

* Using mw.config.get( 'wgFormattedNamespaces' ) instead of AJAX request to ApiQuerySiteinfo (namespace info is available from config on all pages)
Modified paths:
  • /trunk/extensions/ApiSandbox/SpecialApiSandbox.php (modified) (history)
  • /trunk/extensions/ApiSandbox/ext.apiSandbox.css (modified) (history)
  • /trunk/extensions/ApiSandbox/ext.apiSandbox.js (modified) (history)

Diff [purge]

Index: trunk/extensions/ApiSandbox/SpecialApiSandbox.php
@@ -81,11 +81,11 @@
8282 $s = '<table class="api-sandbox-options">
8383 <tbody>
8484 ';
85 - $s .= '<tr><td class="api-sandbox-label"><label for="api-sandbox-format">format=</label></td><td class="api-sandbox-value">'
 85+ $s .= '<tr><td class="api-sandbox-label"><label for="api-sandbox-format"><code>format=</code></label></td><td class="api-sandbox-value">'
8686 . self::getSelect( 'format', $formats, 'json' )
8787 . '</td><td>' . $this->getButtonsBox() . '</td></tr>
8888 ';
89 - $s .= '<tr><td class="api-sandbox-label"><label for="api-sandbox-action">action=</label></td><td class="api-sandbox-value">'
 89+ $s .= '<tr><td class="api-sandbox-label"><label for="api-sandbox-action"><code>action=</code></label></td><td class="api-sandbox-value">'
9090 . self::getSelect( 'action', $modules )
9191 . '</td><td id="api-sandbox-help" rowspan="2"></td></tr>
9292 ';
Index: trunk/extensions/ApiSandbox/ext.apiSandbox.js
@@ -1,8 +1,293 @@
2 -jQuery( function( $ ) {
 2+/*global jQuery, mediaWiki*/
 3+/*jslint regexp: true, browser: true, continue: true, sloppy: true, white: true, forin: true, plusplus: true */
 4+( function ( $, mw, undefined ) {
 5+
 6+ var mainRequest, genericRequest, generatorRequest, queryRequest, // UiBuilder objects
 7+ // Caches
 8+ paramInfo, namespaceOptions,
 9+ // page elements
 10+ $format, $action, $query, $queryRow, $help, $mainContainer, $genericContainer,
 11+ $generatorContainer, $queryContainer, $generatorBox, $submit, $requestUrl, $requestPost,
 12+ $output, $postRow, $buttonsContainer, $examplesButton, $examplesContent;
 13+
 14+
 15+ /** Local utility functions **/
 16+
 17+ function showLoading( $element ) {
 18+ $element.html(
 19+ mw.html.element( 'img',
 20+ { src: mw.config.get( 'stylepath' ) + '/common/images/spinner.gif', alt: '' } )
 21+ + mw.html.escape( mw.msg( 'apisb-loading' )
 22+ )
 23+ );
 24+ }
 25+
 26+ function showLoadError( $element, message ) {
 27+ $element.html(
 28+ mw.html.element( 'span', { 'class': 'error' }, mw.msg( message ) )
 29+ );
 30+ }
 31+
 32+ function getParamInfo( what, loadCallback, completeCallback, errorCallback ) {
 33+ var needed, param, subParam;
 34+
 35+ needed = {};
 36+ for ( param in what ) {
 37+ if ( paramInfo[param] === undefined ) {
 38+ needed[param] = what[param];
 39+ } else if ( typeof needed[param] === 'object' ) {
 40+ for ( subParam in param ) {
 41+ if ( paramInfo[param][subParam] === undefined ) {
 42+ needed[param][subParam] = what[param][subParam];
 43+ }
 44+ }
 45+ } else {
 46+ needed[param] = what[param];
 47+ }
 48+ }
 49+ if ( $.isEmptyObject( needed ) ) {
 50+ completeCallback();
 51+ } else {
 52+ loadCallback();
 53+ needed.format = 'json';
 54+ needed.action = 'paraminfo';
 55+ $.getJSON(
 56+ mw.util.wikiScript( 'api' ),
 57+ needed,
 58+ function ( data ) {
 59+ var prop, i, info;
 60+
 61+ if ( data.error || !data.paraminfo ) {
 62+ errorCallback();
 63+ return;
 64+ }
 65+ for ( prop in data.paraminfo ) {
 66+ if ( paramInfo[prop] === undefined ) {
 67+ paramInfo[prop] = data.paraminfo[prop];
 68+ } else {
 69+ for ( i = 0; i < data.paraminfo[prop].length; i++ ) {
 70+ info = data.paraminfo[prop][i];
 71+ if ( !paramInfo[prop][info.name] ) {
 72+ paramInfo[prop][info.name] = info;
 73+ }
 74+ }
 75+ }
 76+ }
 77+ completeCallback();
 78+ }
 79+ ).error( errorCallback );
 80+ }
 81+ }
 82+
 83+ function resetUI() {
 84+ $( '.api-sandbox-builder' ).each( function () {
 85+ $( this ).data( 'builder' ).createInputs();
 86+ } );
 87+ }
 88+
389 /**
4 - * Class that creates inputs for a query and builds request data
 90+ * @context {Element}
 91+ * @param e {jQuery.Event}
 92+ */
 93+ function exampleClick( e ) {
 94+ var link, params, i, pieces, key, value, $el, splitted, j;
 95+ e.preventDefault();
 96+
 97+ resetUI();
 98+ link = $( this ).attr( 'href' ).replace( /^.*?\?/, '' );
 99+ params = link.split( '&' );
 100+ for ( i = 0; i < params.length; i++ ) {
 101+ pieces = params[i].split( '=' );
 102+ if ( pieces.length === 1 ) { // checkbox
 103+ $( '#param-' + pieces[0] ).prop( 'checked', true );
 104+ } else {
 105+ key = pieces[0];
 106+ value = decodeURIComponent( pieces.slice( 1 ).join( '=' ) );
 107+ if ( [ 'action', 'format', 'list', 'prop', 'meta' ].indexOf( key ) !== -1 ) {
 108+ continue;
 109+ }
 110+ $el = $( '#param-' + key );
 111+ if ( !$el.length ) {
 112+ continue;
 113+ }
 114+ switch ( $el[0].nodeName ) {
 115+ case 'SELECT':
 116+ if ( $el.attr( 'multiple' ) ) {
 117+ splitted = value.split( '|' );
 118+ for ( j = 0; j < splitted.length; j++ ) {
 119+ $el.children( 'option[value=' + mw.html.escape( splitted[j] ) + ']' )
 120+ .prop( 'selected', true );
 121+ }
 122+ } else {
 123+ $el.children( 'option[value=' + mw.html.escape( value ) + ']' )
 124+ .prop( 'selected', true );
 125+ }
 126+ break;
 127+ case 'INPUT':
 128+ if ( $el.attr( 'type' ) === 'checkbox' ) {
 129+ $( '#param-' + key ).prop( 'checked', true );
 130+ } else {
 131+ $el.val( value );
 132+ }
 133+ break;
 134+ default:
 135+ continue;
 136+ }
 137+ }
 138+ }
 139+ }
 140+
 141+ function updateExamples( info ) {
 142+ var i, $list, urlRegex, count, href, text, match, prefix, $prefix, linkText;
 143+
 144+ if ( info.allexamples === undefined ) {
 145+ return;
 146+ }
 147+ // on 1.18, convert everything into 1.19 format
 148+ if ( info.allexamples.length > 0 && typeof info.allexamples[0] === 'string' ) {
 149+ for ( i = 0; i < info.allexamples.length; i++ ) {
 150+ info.allexamples[i] = { '*': info.allexamples[i] };
 151+ }
 152+ }
 153+ $examplesContent.hide().html( '' );
 154+ $list = $( '<ul>' );
 155+ urlRegex = /api.php\?\S+/m;
 156+ count = 0;
 157+ for ( i = 0; i < info.allexamples.length; i++ ) {
 158+ href = '';
 159+ text = '';
 160+ while ( i < info.allexamples.length && info.allexamples[i].description === undefined ) {
 161+ match = urlRegex.exec( info.allexamples[i]['*'] );
 162+ if ( match ) {
 163+ href = match[0];
 164+ break;
 165+ } else {
 166+ text += '\n' + info.allexamples[i]['*'];
 167+ }
 168+ i++;
 169+ }
 170+ if ( !href ) {
 171+ href = info.allexamples[i]['*'];
 172+ }
 173+ if ( !text ) {
 174+ text = info.allexamples[i].description !== undefined ? info.allexamples[i].description : href;
 175+ }
 176+ prefix = text.replace( /[^\n]*$/, '' );
 177+ $prefix = prefix.length ? $( '<b>' ).text( prefix ) : [];
 178+ linkText = text.replace( /^.*\n/, '' );
 179+ $( '<li>' )
 180+ .append( $prefix )
 181+ .append(
 182+ $( '<a>' )
 183+ .attr( 'href', href )
 184+ .text( linkText )
 185+ .click( exampleClick )
 186+ ).appendTo( $list );
 187+ count++;
 188+ }
 189+ $examplesButton.text( mw.msg( count === 1 ? 'apisb-example' : 'apisb-examples' ) );
 190+ $list.appendTo( $examplesContent );
 191+ if ( count ) {
 192+ $examplesButton.show();
 193+ } else {
 194+ $examplesButton.hide();
 195+ }
 196+ }
 197+
 198+ function updateQueryInfo( action, query ) {
 199+ var data,
 200+ isQuery = action === 'query';
 201+
 202+ if ( action === '-' || ( isQuery && query === '-' ) ) {
 203+ $submit.prop( 'disabled', true );
 204+ return;
 205+ }
 206+ query = query.replace( /^.*=/, '' );
 207+ data = {};
 208+ if ( isQuery ) {
 209+ data.querymodules = query;
 210+ } else {
 211+ data.modules = action;
 212+ }
 213+ getParamInfo( data,
 214+ function () {
 215+ showLoading( $mainContainer );
 216+ $submit.prop( 'disabled', true );
 217+ $examplesContent.hide();
 218+ },
 219+ function () {
 220+ var info;
 221+ if ( isQuery ) {
 222+ info = paramInfo.querymodules[query];
 223+ } else {
 224+ info = paramInfo.modules[action];
 225+ }
 226+ mainRequest = new UiBuilder( $mainContainer, info, '' );
 227+ mainRequest.setHelp( $help );
 228+ $submit.prop( 'disabled', false );
 229+ updateExamples( info );
 230+ },
 231+ function () {
 232+ $submit.removeAttr( 'disabled' );
 233+ showLoadError( $mainContainer, 'apisb-load-error' );
 234+ $examplesContent.hide();
 235+ }
 236+ );
 237+ }
 238+
 239+ /**
 240+ * HTML-escapes and pretty-formats an API description string
5241 *
6 - * Constructor
 242+ * @param s {String} String to escape
 243+ * @return {String}
 244+ */
 245+ function smartEscape( s ) {
 246+ if ( !s ) {
 247+ return ''; // @todo: fully verify paraminfo output
 248+ }
 249+ s = mw.html.escape( s );
 250+ if ( s.indexOf( '\n ' ) >= 0 ) {
 251+ // turns *-bulleted list into a HTML list
 252+ s = s.replace( /^(.*?)((?:\n\s+\*?[^\n]*)+)(.*?)$/m, '$1<ul>$2</ul>$3' ); // outer tags
 253+ s = s.replace( /\n\s+\*?([^\n]*)/g, '\n<li>$1</li>' ); // <li> around bulleted lines
 254+ }
 255+ s = s.replace( /\n(?!<)/, '\n<br/>' );
 256+ s = s.replace( /(?:https?:)?\/\/[^\s<>]+/g, function ( s ) {
 257+ // linkify URLs, input is already HTML-escaped above
 258+ return '<a href="' + s + '">' + s + '</a>';
 259+ } );
 260+ return s;
 261+ }
 262+
 263+ /**
 264+ * Updates UI after basic query parameters have been changed
 265+ */
 266+ function updateUI() {
 267+ var a = $action.val(),
 268+ q = $query.val(),
 269+ isQuery = a === 'query';
 270+ if ( isQuery ) {
 271+ $queryRow.show();
 272+ if ( q !== '-' ) {
 273+ $queryContainer.show();
 274+ } else {
 275+ $queryContainer.hide();
 276+ }
 277+ } else {
 278+ $queryRow.hide();
 279+ $queryContainer.hide();
 280+ }
 281+ $mainContainer.text( '' );
 282+ $help.text( '' );
 283+ updateQueryInfo( a, q );
 284+ $generatorBox.hide();
 285+ }
 286+
 287+
 288+ /**
 289+ * Constructor that creates inputs for a query and builds request data
 290+ *
 291+ * @constructor
7292 * @param $container {jQuery} Container to put UI into
8293 * @param info {Object} Query information
9294 * @param prefix {String} Additional prefix for parameter names
@@ -12,10 +297,9 @@
13298 this.$container = $container;
14299 this.info = info;
15300 this.prefix = prefix + info.prefix;
16 - this.params = isset( params ) ? params : info.parameters;
 301+ this.params = params !== undefined ? params : info.parameters;
17302
18 - $container.addClass( 'api-sandbox-builder' );
19 - $container.data( 'builder', this );
 303+ $container.addClass( 'api-sandbox-builder' ).data( 'builder', this );
20304
21305 this.createInputs();
22306 }
@@ -24,22 +308,22 @@
25309 /**
26310 * Creates inputs and places them into container
27311 */
28 - createInputs: function() {
 312+ createInputs: function () {
29313 var $table, i, length, param, name;
30314
31 - $table = $( '<table class="api-sandbox-options">' );
 315+ $table = $( '<table class="api-sandbox-options"></table>' );
32316 for ( i = 0, length = this.params.length; i < length; i += 1 ) {
33317 param = this.params[i];
34318 name = this.prefix + param.name;
35319
36320 $( '<tr>' )
37321 .append(
38 - $( '<td class="api-sandbox-label">' )
 322+ $( '<td class="api-sandbox-label"></td>' )
39323 .html( mw.html.element( 'label',
40324 { 'for': 'param-' + name }, name + '=' )
41325 )
42326 )
43 - .append( $( '<td class="api-sandbox-value">' ).html( this.input( param, name ) ) )
 327+ .append( $( '<td class="api-sandbox-value"></td>' ).html( this.input( param, name ) ) )
44328 .append( $( '<td>' ).html( smartEscape( param.description ) ) )
45329 .appendTo( $table );
46330 }
@@ -48,43 +332,59 @@
49333
50334 /**
51335 * Adds module help to a container
52 - * @param $container {jQuery} Container to use
 336+ * @param $container {jQuery} Container to use
53337 */
54 - setHelp: function( $container ) {
 338+ setHelp: function ( $container ) {
55339 var desc = smartEscape( this.info.description );
56 - if ( isset( this.info.helpurls ) && isset( this.info.helpurls[0] ) && this.info.helpurls[0] ) {
 340+ if ( this.info.helpurls && this.info.helpurls[0] ) {
57341 desc = desc.replace( /^([^\r\n\.]*)/,
58 - mw.html.element( 'a', { 'target': '_blank', 'href': this.info.helpurls[0] }, '$1' )
 342+ mw.html.element( 'a', {
 343+ 'target': '_blank',
 344+ 'href': this.info.helpurls[0]
 345+ }, '$1' )
59346 );
60347 }
61348 $container.html( desc );
62349 },
63350
64 - input: function( param, name ) {
65 - var s,
 351+ input: function ( param, name ) {
 352+ var s, id, attributes,
66353 value = '';
67354 switch ( param.type ) {
68355 case 'limit':
69356 value = '10';
 357+ // fall through:
70358 case 'user':
71359 case 'timestamp':
72360 case 'integer':
73361 case 'string':
74 - s = mw.html.element( 'input', { 'class': 'api-sandbox-input', 'id': 'param-' + name, 'value': value } );
 362+ s = mw.html.element( 'input', {
 363+ 'class': 'api-sandbox-input',
 364+ 'id': 'param-' + name,
 365+ 'value': value
 366+ } );
75367 break;
 368+
76369 case 'bool':
77 - param.type = 'boolean'; // normalisation for later use
 370+ // normalisation for later use
 371+ param.type = 'boolean';
 372+ // fall through:
78373 case 'boolean':
79 - s = mw.html.element( 'input', { 'id': 'param-' + name, 'type': 'checkbox' } );
 374+ s = mw.html.element( 'input', {
 375+ 'id': 'param-' + name,
 376+ 'type': 'checkbox'
 377+ } );
80378 break;
 379+
81380 case 'namespace':
82 - param.type = namespaces;
 381+ param.type = namespaceOptions;
 382+ // fall through:
83383 default:
84 - if ( typeof param.type == 'object' ) {
85 - var id = 'param-' + name,
86 - attributes = { 'id': id };
87 - if ( isset( param.multi ) ) {
88 - attributes.multiple = 'multiple';
 384+ if ( typeof param.type === 'object' ) {
 385+ id = 'param-' + name;
 386+ attributes = { 'id': id };
 387+ if ( param.multi !== undefined ) {
 388+ attributes.multiple = true;
89389 s = this.select( param.type, attributes, false );
90390 } else {
91391 s = this.select( param.type, attributes, true );
@@ -96,48 +396,53 @@
97397 return s;
98398 },
99399
100 - select: function( values, attributes, selected ) {
101 - var s = '', i, length, value, face, attrs;
 400+ select: function ( values, attributes, selected ) {
 401+ var i, length, value, face, attrs,
 402+ s = '';
102403
103404 attributes['class'] = 'api-sandbox-input';
104 - if ( isset( attributes.multiple ) ) {
105 - attributes['size'] = Math.min( values.length, 10 );
 405+ if ( attributes.multiple === true ) {
 406+ attributes.size = Math.min( values.length, 10 );
106407 }
107 - if ( typeof selected != 'array' ) {
 408+ if ( !$.isArray( selected ) ) {
108409 if ( selected ) {
109 - s += mw.html.element( 'option', { value: '', selected: 'selected' }, mw.msg( 'apisb-select-value' ) );
 410+ s += mw.html.element( 'option', {
 411+ value: '',
 412+ selected: true
 413+ }, mw.msg( 'apisb-select-value' ) );
110414 }
111415 selected = [];
112416 }
113417
114418 for ( i = 0, length = values.length; i < length; i += 1 ) {
115 - value = typeof values[i] == 'object' ? values[i].key : values[i];
116 - face = typeof values[i] == 'object' ? values[i].value : values[i];
 419+ value = typeof values[i] === 'object' ? values[i].key : values[i];
 420+ face = typeof values[i] === 'object' ? values[i].value : values[i];
117421 attrs = { 'value': value };
118422
119423 if ( $.inArray( value, selected ) >= 0 ) {
120 - attrs.selected = 'selected';
 424+ attrs.selected = true;
121425 }
122426
123 - s += '\n' + mw.html.element( 'option', attrs, face );
 427+ s += mw.html.element( 'option', attrs, face );
124428 }
125429 s = mw.html.element( 'select', attributes, new mw.html.Raw( s ) );
126430 return s;
127431 },
128432
129 - getRequestData: function() {
130 - var params = '', i, length, param, name, $node;
 433+ getRequestData: function () {
 434+ var params = '', i, length, param, name, $node, value;
 435+
131436 for ( i = 0, length = this.params.length; i < length; i += 1 ) {
132437 param = this.params[i];
133438 name = this.prefix + param.name;
134439 $node = $( '#param-' + name );
135 - if ( param.type == 'boolean' ) {
136 - if ( $node.is( ':checked' ) ) {
 440+ if ( param.type === 'boolean' ) {
 441+ if ( $node.prop( 'checked' ) === true ) {
137442 params += '&' + name;
138443 }
139444 } else {
140 - var value = $node.val();
141 - if ( !isset( value ) || value == '' || value == null ) {
 445+ value = $node.val();
 446+ if ( value === undefined || value === null || value === '' ) {
142447 continue;
143448 }
144449 if ( $.isArray( value ) ) {
@@ -148,428 +453,166 @@
149454 }
150455 return params;
151456 }
152 - } // end of UiBuilder.prototype
 457+ }; // end of UiBuilder.prototype
153458
154 - var $content = $( '#api-sandbox-content' );
155 - if ( !$content.length ) {
156 - return;
157 - }
158 - $content.show();
 459+ /** When the dom is ready.. **/
159460
160 - // page elements
161 - var $format = $( '#api-sandbox-format' ),
162 - $action = $( '#api-sandbox-action' ),
163 - $query = $( '#api-sandbox-query' ),
164 - $queryRow = $( '#api-sandbox-query-row' ),
165 - $help = $( '#api-sandbox-help' ),
166 - $mainContainer = $( '#api-sandbox-main-inputs' ),
167 - $genericContainer, // will be set later
168 - $generatorContainer = $( '#api-sandbox-generator-inputs' ),
169 - $queryContainer = $( '#api-sandbox-query-inputs' ),
170 - $generatorBox = $( '#api-sandbox-generator-parameters' ),
171 - $submit = $( '#api-sandbox-submit' ),
172 - $requestUrl = $( '#api-sandbox-url' ),
173 - $requestPost = $( '#api-sandbox-post' ),
174 - $output = $( '#api-sandbox-output' ),
175 - $postRow = $( '#api-sandbox-post-row' ),
176 - $buttonsContainer = $( '#api-sandbox-buttons' ),
177 - $examplesButton = $( '<button></button>' )
178 - .click( function( e ) {
 461+ $( function () {
 462+
 463+ $( '#api-sandbox-content' ).show();
 464+
 465+ // init page elements
 466+ $format = $( '#api-sandbox-format' );
 467+ $action = $( '#api-sandbox-action' );
 468+ $query = $( '#api-sandbox-query' );
 469+ $queryRow = $( '#api-sandbox-query-row' );
 470+ $help = $( '#api-sandbox-help' );
 471+ $mainContainer = $( '#api-sandbox-main-inputs' );
 472+ $generatorContainer = $( '#api-sandbox-generator-inputs' );
 473+ $queryContainer = $( '#api-sandbox-query-inputs' );
 474+ $generatorBox = $( '#api-sandbox-generator-parameters' );
 475+ $submit = $( '#api-sandbox-submit' );
 476+ $requestUrl = $( '#api-sandbox-url' );
 477+ $requestPost = $( '#api-sandbox-post' );
 478+ $output = $( '#api-sandbox-output' );
 479+ $postRow = $( '#api-sandbox-post-row' );
 480+ $buttonsContainer = $( '#api-sandbox-buttons' );
 481+ $examplesButton = $( '<button>' )
 482+ .click( function ( e ) {
179483 e.preventDefault();
180484 $examplesContent.slideToggle();
181485 } )
182486 .hide()
183 - .appendTo( $buttonsContainer ),
 487+ .appendTo( $buttonsContainer );
184488 $examplesContent = $( '#api-sandbox-examples' );
185489
186 - // UiBuilder objects
187 - var mainRequest,
188 - genericRequest,
189 - generatorRequest,
190 - queryRequest;
 490+ // init caches
 491+ paramInfo = { modules: {}, querymodules: {} };
 492+ namespaceOptions = [];
191493
192 - // cached stuff
193 - var paramInfo = { modules: {}, querymodules: {} },
194 - namespaces = [];
195 -
196 - $( '<button></button>' )
197 - .text(mw.msg( 'apisb-clear' ) )
198 - .click( function( e ) {
199 - e.preventDefault();
200 - resetUI();
201 - } )
202 - .insertAfter( $examplesButton );
203 -
204 - // load namespaces
205 - $.getJSON( mw.util.wikiScript( 'api' ),
206 - { format: 'json', action: 'query', meta: 'siteinfo', siprop: 'namespaces' },
207 - function( data ) {
208 - if ( isset( data.query ) && isset( data.query.namespaces ) ) {
209 - for ( var id in data.query.namespaces ) {
210 - if ( id < 0 ) {
211 - continue;
212 - }
213 - var ns = data.query.namespaces[id]['*'];
214 - if ( ns == '' ) {
215 - ns = mw.msg( 'apisb-ns-main' );
216 - }
217 - namespaces.push( { key: id, value: ns } );
 494+ // build namespace cache
 495+ $.each( mw.config.get( 'wgFormattedNamespaces' ), function( nsId, nsName ) {
 496+ if ( Number( nsId ) >= 0 ) {
 497+ if ( nsId === '0' ) {
 498+ nsName = mw.msg( 'apisb-ns-main' );
218499 }
219 - } else {
220 - showLoadError( $mainContainer, 'apisb-namespaces-error' );
221 - }
222 - }
223 - );
 500+ namespaceOptions.push( {
 501+ key: nsId,
 502+ value: nsName
 503+ } );
 504+ }
 505+ } );
224506
225 - // load stuff we need from the beginning
226 - getParamInfo( { mainmodule: 1, pagesetmodule: 1, modules: 'query' },
227 - function() {},
228 - function() {
229 - paramInfo.mainmodule.parameters = paramInfo.mainmodule.parameters.slice( 2 ); // remove format and action
230 - paramInfo.modules.query.parameters = paramInfo.modules.query.parameters.slice( 3 );
231 - $genericContainer = $( '#api-sandbox-generic-inputs > div' );
232 - genericRequest = new UiBuilder( $genericContainer, paramInfo.mainmodule, '' );
233 - queryRequest = new UiBuilder( $queryContainer, paramInfo.modules.query, '',
234 - [].concat( paramInfo.modules.query.parameters, paramInfo.pagesetmodule.parameters )
235 - );
236 - },
237 - function() {}
238 - );
 507+ $( '<button>' )
 508+ .text( mw.msg( 'apisb-clear' ) )
 509+ .click( function ( e ) {
 510+ e.preventDefault();
 511+ resetUI();
 512+ } )
 513+ .insertAfter( $examplesButton );
239514
240 - $action.change( updateUI );
241 - $query.change( updateUI );
 515+ // load stuff we need from the beginning
 516+ getParamInfo(
 517+ {
 518+ mainmodule: 1,
 519+ pagesetmodule: 1,
 520+ modules: 'query'
 521+ },
 522+ function () {},
 523+ function () {
 524+ paramInfo.mainmodule.parameters = paramInfo.mainmodule.parameters.slice( 2 ); // remove format and action
 525+ paramInfo.modules.query.parameters = paramInfo.modules.query.parameters.slice( 3 );
 526+ $genericContainer = $( '#api-sandbox-generic-inputs > div' );
 527+ genericRequest = new UiBuilder( $genericContainer, paramInfo.mainmodule, '' );
 528+ queryRequest = new UiBuilder( $queryContainer, paramInfo.modules.query, '',
 529+ [].concat( paramInfo.modules.query.parameters, paramInfo.pagesetmodule.parameters )
 530+ );
 531+ },
 532+ function () {}
 533+ );
242534
243 - $( '#param-generator' ).live( 'change', function() {
244 - var generator = $( '#param-generator' ).val();
245 - if ( generator == '' ) {
246 - $generatorBox.hide();
247 - } else {
248 - $generatorBox.show();
249 - getParamInfo( { querymodules: generator },
250 - function() { showLoading( $generatorContainer ); },
251 - function() {
252 - generatorRequest = new UiBuilder( $generatorContainer, paramInfo.querymodules[generator], 'g' );
253 - },
254 - function() {
255 - showLoadError( $generatorContainer, 'apisb-request-error' );
256 - }
257 - );
258 - }
259 - } );
 535+ $action.change( updateUI );
 536+ $query.change( updateUI );
260537
261 -
262 - $( '#api-sandbox-form' ).submit( function( e ) {
263 - e.preventDefault();
264 - if ( !$submit.is( ':enabled' ) ) {
265 - return;
266 - }
267 - var url = mw.util.wikiScript( 'api' ) + '?action=' + $action.val(),
268 - params = mainRequest.getRequestData(),
269 - mustBePosted = isset( mainRequest.info.mustbeposted );
270 - if ( $action.val() == 'query' ) {
271 - url += '&' + $query.val();
272 - params += queryRequest.getRequestData();
273 - }
274 - url += '&format=' + $format.val();
275 -
276 - params += genericRequest.getRequestData();
277 - if ( $( '#param-generator' ).length && $( '#param-generator' ).val() != '' ) {
278 - params += generatorRequest.getRequestData();
279 - }
280 -
281 - showLoading( $output );
282 - if ( mustBePosted ) {
283 - $requestUrl.val( url );
284 - if ( params.length > 0 ) {
285 - params = params.substr( 1 ); // remove leading &
 538+ $( '#param-generator' ).live( 'change', function () {
 539+ var generator = $( '#param-generator' ).val();
 540+ if ( generator === '' ) {
 541+ $generatorBox.hide();
 542+ } else {
 543+ $generatorBox.show();
 544+ getParamInfo(
 545+ { querymodules: generator },
 546+ function () { showLoading( $generatorContainer ); },
 547+ function () {
 548+ generatorRequest = new UiBuilder( $generatorContainer, paramInfo.querymodules[generator], 'g' );
 549+ },
 550+ function () {
 551+ showLoadError( $generatorContainer, 'apisb-request-error' );
 552+ }
 553+ );
286554 }
287 - $requestPost.val( params );
288 - $postRow.show();
289 - } else {
290 - $requestUrl.val( url + params );
291 - $postRow.hide();
292 - }
293 - url = url.replace( /(&format=[^&]+)/, '$1fm' );
294 - var data = {
295 - url: url,
296 - data: params,
297 - dataType: 'text',
298 - type: mustBePosted ? 'POST' : 'GET',
299 - success: function( data ) {
300 - var match = data.match( /<pre>[\s\S]*<\/pre>/ );
301 - if ( $.isArray( match ) ) {
302 - data = match[0];
303 - } else {
304 - // some actions don't honor user-specified format
305 - data = '<pre>' + mw.html.escape( data ) + '</pre>';
306 - }
307 - $output.html( data );
308 - },
309 - error: function() {
310 - showLoadError( $output, 'apisb-request-error' );
311 - }
312 - };
313 - $.ajax( data );
314 - });
 555+ } );
315556
316 - /**
317 - * Shamelessly borrowed from PHP
318 - */
319 - function isset( x ) {
320 - return typeof x != 'undefined';
321 - }
322557
323 - function showLoading( $element ) {
324 - $element.html(
325 - mw.html.element( 'img',
326 - { src: mw.config.get( 'stylepath' ) + '/common/images/spinner.gif', alt: '' } )
327 - + mw.html.escape( mw.msg( 'apisb-loading' ) ) );
328 - }
 558+ $( '#api-sandbox-form' ).submit( function ( e ) {
 559+ var url, params, mustBePosted, data;
329560
330 - function showLoadError( $element, message ) {
331 - $element.html( mw.html.element( 'span', { 'class': 'error' }, mw.msg( message ) ) );
332 - }
 561+ e.preventDefault();
333562
334 - function getParamInfo( what, loadCallback, completeCallback, errorCallback ) {
335 - var needed = {};
336 - for ( var param in what ) {
337 - if ( !isset( paramInfo[param] ) ) {
338 - needed[param] = what[param];
339 - } else if (typeof needed[param] == 'object' ) {
340 - for ( var subParam in param ) {
341 - if ( !isset( paramInfo[param][subParam] ) ) {
342 - needed[param][subParam] = what[param][subParam];
343 - }
344 - }
345 - } else {
346 - needed[param] = what[param];
 563+ if ( $submit.prop( 'disabled' ) === true ) {
 564+ return;
347565 }
348 - }
349 - if ( $.isEmptyObject( needed ) ) {
350 - completeCallback();
351 - } else {
352 - loadCallback();
353 - needed.format = 'json';
354 - needed.action = 'paraminfo';
355 - $.getJSON(
356 - mw.util.wikiScript( 'api' ),
357 - needed,
358 - function( data ) {
359 - if ( data.error || !data.paraminfo ) {
360 - errorCallback();
361 - return;
362 - }
363 - for ( var prop in data.paraminfo ) {
364 - if ( !isset( paramInfo[prop] ) ) {
365 - paramInfo[prop] = data.paraminfo[prop];
366 - } else {
367 - for ( var i = 0; i < data.paraminfo[prop].length; i++ ) {
368 - var info = data.paraminfo[prop][i];
369 - if ( !paramInfo[prop][info.name] ) {
370 - paramInfo[prop][info.name] = info;
371 - }
372 - }
373 - }
374 - }
375 - completeCallback();
376 - }
377 - ).error( errorCallback );
378 - }
379 - }
380566
381 - function updateQueryInfo( action, query ) {
382 - var isQuery = action == 'query';
383 - if ( action == '-' || ( isQuery && query == '-' ) ) {
384 - $submit.attr( 'disabled', 'disabled' );
385 - return;
386 - }
387 - query = query.replace( /^.*=/, '' );
388 - var data = {};
389 - if (isQuery ) {
390 - data.querymodules = query;
391 - } else {
392 - data.modules = action;
393 - }
394 - getParamInfo( data,
395 - function() {
396 - showLoading( $mainContainer );
397 - $submit.attr( 'disabled', 'disabled' );
398 - $examplesContent.hide();
399 - },
400 - function() {
401 - var info;
402 - if ( isQuery ) {
403 - info = paramInfo.querymodules[query];
404 - } else {
405 - info = paramInfo.modules[action];
406 - }
407 - mainRequest = new UiBuilder( $mainContainer, info, '' );
408 - mainRequest.setHelp( $help );
409 - $submit.removeAttr( 'disabled' );
410 - updateExamples( info );
411 - },
412 - function() {
413 - $submit.removeAttr( 'disabled' );
414 - showLoadError( $mainContainer, 'apisb-load-error' );
415 - $examplesContent.hide();
 567+ url = mw.util.wikiScript( 'api' ) + '?' + $.param({ action: $action.val() });
 568+ params = mainRequest.getRequestData();
 569+ mustBePosted = mainRequest.info.mustbeposted === '';
 570+ debugger;
 571+ if ( $action.val() === 'query' ) {
 572+ url += '&' + $query.val();
 573+ params += queryRequest.getRequestData();
416574 }
417 - );
418 - }
 575+ url += '&format=' + $format.val();
419576
420 - function updateExamples( info ) {
421 - if ( !isset( info.allexamples ) ) {
422 - $examplesContainer.hide(); // just in case
423 - return;
424 - }
425 - // on 1.18, convert everything into 1.19 format
426 - if ( info.allexamples.length > 0 && typeof info.allexamples[0] == 'string' ) {
427 - for ( var i = 0; i < info.allexamples.length; i++ ) {
428 - info.allexamples[i] = { '*': info.allexamples[i] };
 577+ params += genericRequest.getRequestData();
 578+ if ( $( '#param-generator' ).length && $( '#param-generator' ).val() ) {
 579+ params += generatorRequest.getRequestData();
429580 }
430 - }
431 - $examplesContent.hide()
432 - .html( '' );
433 - var $list = $( '<ul></ul>' );
434 - var urlRegex = /api.php\?\S+/m;
435 - var count = 0;
436 - for ( var i = 0; i < info.allexamples.length; i++ ) {
437 - var href = '';
438 - var text = '';
439 - while ( i < info.allexamples.length && !isset( info.allexamples[i].description ) ) {
440 - var match = urlRegex.exec( info.allexamples[i]['*'] );
441 - if ( match ) {
442 - href = match[0];
443 - break;
444 - } else {
445 - text += '\n' + info.allexamples[i]['*'];
 581+
 582+ showLoading( $output );
 583+ if ( mustBePosted ) {
 584+ $requestUrl.val( url );
 585+ if ( params.length > 0 ) {
 586+ params = params.substr( 1 ); // remove leading &
446587 }
447 - i++;
 588+ $requestPost.val( params );
 589+ $postRow.show();
 590+ } else {
 591+ $requestUrl.val( url + params );
 592+ $postRow.hide();
448593 }
449 - if ( !href ) {
450 - href = info.allexamples[i]['*'];
451 - }
452 - if ( !text ) {
453 - text = isset( info.allexamples[i].description ) ? info.allexamples[i].description : href;
454 - }
455 - var prefix = text.replace( /[^\n]*$/, '' );
456 - var $prefix = prefix.length ? $( '<b>' ).text( prefix ) : [];
457 - var linkText = text.replace( /^.*\n/, '' );
458 - $( '<li>' ).append( $prefix )
459 - .append( $( '<a/>' )
460 - .attr( 'href', href )
461 - .text( linkText )
462 - .click( exampleClick )
463 - ).appendTo( $list );
464 - count++;
465 - }
466 - $examplesButton.text( mw.msg( count == 1 ? 'apisb-example' : 'apisb-examples' ) );
467 - $list.appendTo( $examplesContent );
468 - if ( count ) {
469 - $examplesButton.show();
470 - } else {
471 - $examplesButton.hide();
472 - }
473 - }
474 -
475 - function exampleClick( e ) {
476 - e.preventDefault();
477 -
478 - resetUI();
479 - var link = $( this ).attr( 'href' ).replace( /^.*?\?/, '' );
480 - var params = link.split( '&' );
481 - for ( var i = 0; i < params.length; i++ ) {
482 - var pieces = params[i].split( '=' );
483 - if ( pieces.length == 1 ) { // checkbox
484 - $( '#param-' + pieces[0] ).attr( 'checked', 'checked' );
485 - } else {
486 - var key = pieces[0],
487 - value = decodeURIComponent( pieces.slice( 1 ).join( '=' ) );
488 - if ( [ 'action', 'format', 'list', 'prop', 'meta' ].indexOf( key ) != -1 ) {
489 - continue;
 594+ url = url.replace( /(&format=[^&]+)/, '$1fm' );
 595+ data = {
 596+ url: url,
 597+ data: params,
 598+ dataType: 'text',
 599+ type: mustBePosted ? 'POST' : 'GET',
 600+ success: function ( data ) {
 601+ var match = data.match( /<pre>[\s\S]*<\/pre>/ );
 602+ if ( $.isArray( match ) ) {
 603+ data = match[0];
 604+ } else {
 605+ // some actions don't honor user-specified format
 606+ data = '<pre>' + mw.html.escape( data ) + '</pre>';
 607+ }
 608+ $output.html( data );
 609+ },
 610+ error: function () {
 611+ showLoadError( $output, 'apisb-request-error' );
490612 }
491 - var $el = $( '#param-' + key );
492 - if ( !$el.length ) {
493 - continue;
494 - }
495 - switch ( $el[0].nodeName ) {
496 - case 'SELECT':
497 - if ( $el.attr( 'multiple' ) ) {
498 - var splitted = value.split( '|' );
499 - for ( var j = 0; j < splitted.length; j++ ) {
500 - $el.children( 'option[value=' + mw.html.escape( splitted[j] ) + ']' )
501 - .attr( 'selected', 'selected' );
502 - }
503 - } else {
504 - $el.children( 'option[value=' + mw.html.escape( value ) + ']' )
505 - .attr( 'selected', 'selected' );
506 - }
507 - break;
508 - case 'INPUT':
509 - if ( $el.attr( 'type' ) == 'checkbox' ) {
510 - $( '#param-' + key ).attr( 'checked', 'checked' );
511 - } else {
512 - $el.val( value );
513 - }
514 - break;
515 - default:
516 - continue;
517 - }
518 - }
519 - }
520 - }
 613+ };
 614+ $.ajax( data );
 615+ });
521616
522 - function resetUI() {
523 - $( '.api-sandbox-builder' ).each( function() {
524 - $( this ).data( 'builder' ).createInputs();
525 - } );
526 - }
 617+ });
527618
528 - /**
529 - * HTML-escapes and pretty-formats an API description string
530 - *
531 - * @param s {String} String to escape
532 - * @return {String}
533 - */
534 - function smartEscape( s ) {
535 - if ( !s ) {
536 - return ''; // @todo: fully verify paraminfo output
537 - }
538 - s = mw.html.escape( s );
539 - if ( s.indexOf( '\n ' ) >= 0 ) {
540 - // turns *-bulleted list into a HTML list
541 - s = s.replace( /^(.*?)((?:\n\s+\*?[^\n]*)+)(.*?)$/m, '$1<ul>$2</ul>$3' ); // outer tags
542 - s = s.replace( /\n\s+\*?([^\n]*)/g, '\n<li>$1</li>' ); // <li> around bulleted lines
543 - }
544 - s = s.replace( /\n(?!<)/, '\n<br/>' );
545 - s = s.replace( /(?:https?:)?\/\/[^\s<>]+/g, function( s ) {
546 - // linkify URLs, input is already HTML-escaped above
547 - return '<a href="' + s + '">' + s + '</a>';
548 - } );
549 - return s;
550 - }
551 -
552 - /**
553 - * Updates UI after basic query parameters have been changed
554 - */
555 - function updateUI() {
556 - var a = $action.val(),
557 - q = $query.val(),
558 - isQuery = a == 'query';
559 - if ( isQuery ) {
560 - $queryRow.show();
561 - if ( q != '-' ) {
562 - $queryContainer.show();
563 - } else {
564 - $queryContainer.hide();
565 - }
566 - } else {
567 - $queryRow.hide();
568 - $queryContainer.hide();
569 - }
570 - $mainContainer.text( '' );
571 - $help.text( '' );
572 - updateQueryInfo( a, q );
573 - $generatorBox.hide();
574 - }
575 -
576 -});
 619+}( jQuery, mediaWiki ) );
Index: trunk/extensions/ApiSandbox/ext.apiSandbox.css
@@ -1,25 +1,24 @@
2 -table.api-sandbox-options {
 2+.api-sandbox-options {
33 width: 100%;
44 }
55
6 -td.api-sandbox-label {
 6+.api-sandbox-label {
77 text-align: right;
88 width: 12em;
99 vertical-align: top;
10 - font-family: monospace;
1110 }
1211
13 -td.api-sandbox-result-label {
 12+.api-sandbox-result-label {
1413 width: 12em;
1514 }
1615
17 -td.api-sandbox-value {
 16+.api-sandbox-value {
1817 width: 18em;
1918 vertical-align: top;
2019 overflow: auto;
2120 }
2221
23 -table.api-sandbox-result-container {
 22+.api-sandbox-result-container {
2423 width: 100%;
2524 }
2625
@@ -27,16 +26,11 @@
2827 width: 17em;
2928 }
3029
31 -#api-sandbox-url, #api-sandbox-post {
 30+#api-sandbox-url,
 31+#api-sandbox-post {
3232 width: 100%;
3333 }
3434
35 -#api-sandbox-output {
36 - width: 100%;
37 - height: 15em;
38 - overflow: scroll;
39 -}
40 -
4135 /* override enwiki's insane styles */
4236 div.mw-collapsible {
4337 border: none !important;

Follow-up revisions

RevisionCommit summaryAuthorDate
r109726[ApiSandbox] front-end makeover...krinkle07:10, 22 January 2012

Status & tagging log