mw.toolbar: Clean up the API of the classic toolbar.
authorTimo Tijhof <ttijhof@wikimedia.org>
Sat, 21 Jul 2012 23:49:46 +0000 (16:49 -0700)
committerTimo Tijhof <ttijhof@wikimedia.org>
Sun, 22 Jul 2012 00:04:46 +0000 (17:04 -0700)
* Several methods were added here recently during 1.20 development
  that should not have been public methods.

* Also in the creation of this new module (replacing the old
  mwCustomEditButtons) a design flaw was made. Instead of using
  a key-value pair object, the signature was changed to a tentacle
  function with 7 (for callers, unnamed) arguments.

* Changed it back with the compatibility fix the other way around.
  So everything is backwards compatible.

* Moved to local scope:
 - buttons queue
 - $toolbar
 - insertButton
 These were recently introduced during 1.20 development but not
 meant to be public. When used too early or too late from outside
 the module it will break or be ignored. For example $toolbar is
 false before dom ready, buttons queue is ignored after domready,
 insertButton will break if called before dom ready because the
 target element doesn't exist yet. These are not bugs, but result
 of calling internal methods before they are initialized.
 The public API takes care of these state differences by using
 the queue and the dom ready handler.

 Scripts should (and do) only use the addButton API.

* Kept:
 - addButton
 - insertTags
 - init (empty b/c function, was already there)

* Improved:
 - addButton: Now takes an object as well, just like
   mwCustomEditButtons used to do.
 - Cache Array.prototype.slice instead of re-grabbing from
   a new dummy array.
 - Store buttons[i] in a local variable in both cases, not just
   for legacy. Saves 2 property lookups. Minor gain, but
   in this case it was already going to be stored in a local
   variable, so might as well do it in the other case.

* Fixes:
 - Clear queue array after it has been used. Though in practice
   it should never happen that it is iterated over twice, just in
   case.
 - Added comment to init() function explaining where it is used.
 - Updated closure arguments per code conventions.
 - Made it a position-top module so that it actually can be used
   before the document is ready.

* Example usages tested:
<code>
// Legacy way from wikibits.js:
// Has to be done before document ready
window.mwCustomEditButtons[window.mwCustomEditButtons.length] = {
  imageFile: 'http://placehold.it/23x22',
  speedTip: 'tool tip',
  tagOpen: 'x-',
  tagClose: '-y'
};

// mw.toolbar: List of arguments
mw.toolbar.addButton( 'http://placehold.it/23x22', 'tooltip', 'x-', '-y' );

// mw.toolbar: Object
mw.toolbar.addButton({
  imageFile: 'http://placehold.it/23x22',
  speedTip: 'tool tip',
  tagOpen: 'x-',
  tagClose: '-y'
});
</code>

Change-Id: Id19819707c937c2c3144ad8177b75baa46f5073c

resources/Resources.php
resources/mediawiki.action/mediawiki.action.edit.js

index 0e85050..9900ab1 100644 (file)
@@ -632,6 +632,7 @@ return array(
                        'jquery.textSelection',
                        'jquery.byteLimit',
                ),
+               'position' => 'top',
        ),
        'mediawiki.action.history' => array(
                'scripts' => 'resources/mediawiki.action/mediawiki.action.history.js',
index 14b845d..bd07cd0 100644 (file)
@@ -1,42 +1,74 @@
-( function ( $, mw ) {
-       var isReady, toolbar, currentFocused;
+( function ( mw, $ ) {
+       var isReady, toolbar, currentFocused, queue, $toolbar, slice;
 
        isReady = false;
+       queue = [];
+       $toolbar = false;
+       slice = Array.prototype.slice;
+
+       /**
+        * Internal helper that does the actual insertion
+        * of the button into the toolbar.
+        * See mw.toolbar.addButton for parameter documentation.
+        */
+       function insertButton( b /* imageFile */, speedTip, tagOpen, tagClose, sampleText, imageId, selectText ) {
+               // Backwards compatibility
+               if ( typeof b !== 'object' ) {
+                       b = {
+                               imageFile: b,
+                               speedTip: speedTip,
+                               tagOpen: tagOpen,
+                               tagClose: tagClose,
+                               sampleText: sampleText,
+                               imageId: imageId,
+                               selectText: selectText
+                       };
+               }
+               var $image = $('<img>', {
+                       width : 23,
+                       height: 22,
+                       src   : b.imageFile,
+                       alt   : b.speedTip,
+                       title : b.speedTip,
+                       id    : b.imageId || undefined,
+                       'class': 'mw-toolbar-editbutton'
+               } ).click( function () {
+                       toolbar.insertTags( b.tagOpen, b.tagClose, b.sampleText, b.selectText );
+                       return false;
+               } );
+
+               $toolbar.append( $image );
+               return true;
+       }
 
        toolbar = {
-               $toolbar: false,
-               buttons: [],
                /**
-                * If you want to add buttons, use
-                * mw.toolbar.addButton( imageFile, speedTip, tagOpen, tagClose, sampleText, imageId, selectText );
+                * Add buttons to the toolbar.
+                * Takes care of race conditions and time-based dependencies
+                * by placing buttons in a queue if this method is called before
+                * the toolbar is created.
+                * @param {Object} button: Object with the following properties:
+                * - imageFile
+                * - speedTip
+                * - tagOpen
+                * - tagClose
+                * - sampleText
+                * - imageId
+                * - selectText
+                * For compatiblity, passing the above as separate arguments
+                * (in the listed order) is also supported.
                 */
                addButton: function () {
                        if ( isReady ) {
-                               toolbar.insertButton.apply( toolbar, arguments );
+                               insertButton.apply( toolbar, arguments );
                        } else {
-                               toolbar.buttons.push( [].slice.call( arguments ) );
-                       }       
-               },
-               insertButton: function ( imageFile, speedTip, tagOpen, tagClose, sampleText, imageId, selectText ) {
-                       var image = $('<img>', {
-                               width : 23,
-                               height: 22,
-                               src   : imageFile,
-                               alt   : speedTip,
-                               title : speedTip,
-                               id    : imageId || '',
-                               'class': 'mw-toolbar-editbutton'
-                       } ).click( function () {
-                               mw.toolbar.insertTags( tagOpen, tagClose, sampleText, selectText );
-                               return false;
-                       } );
-
-                       toolbar.$toolbar.append( image );
-                       return true;
+                               // Convert arguments list to array
+                               queue.push( slice.call( arguments ) );
+                       }
                },
 
                /**
-                * apply tagOpen/tagClose to selection in textarea,
+                * Apply tagOpen/tagClose to selection in textarea,
                 * use sampleText instead of selection if there is none.
                 */
                insertTags: function ( tagOpen, tagClose, sampleText, selectText ) {
@@ -51,7 +83,8 @@
                        }
                },
 
-               // For backwards compatibility
+               // For backwards compatibility,
+               // Called from EditPage.php, maybe in other places as well.
                init: function () {}
        };
 
        window.addButton = toolbar.addButton;
        window.insertTags = toolbar.insertTags;
 
-       // Explose publicly
+       // Explose API publicly
        mw.toolbar = toolbar;
 
        $( document ).ready( function () {
-               var buttons, i, c, iframe;
+               var buttons, i, b, iframe;
 
                // currentFocus is used to determine where to insert tags
                currentFocused = $( '#wpTextbox1' );
 
-               // Populate the selector cache for $toolbar 
-               toolbar.$toolbar = $( '#toolbar' );
+               // Populate the selector cache for $toolbar
+               $toolbar = $( '#toolbar' );
 
                // Legacy: Merge buttons from mwCustomEditButtons
-               buttons = [].concat( toolbar.buttons, window.mwCustomEditButtons );
+               buttons = [].concat( queue, window.mwCustomEditButtons );
+               // Clear queue
+               queue.length = 0;
                for ( i = 0; i < buttons.length; i++ ) {
-                       if ( $.isArray( buttons[i] ) ) {
-                               // Passes our button array as arguments
-                               toolbar.insertButton.apply( toolbar, buttons[i] );
+                       b = buttons[i];
+                       if ( $.isArray( b ) ) {
+                               // Forwarded arguments array from mw.toolbar.addButton
+                               insertButton.apply( toolbar, b );
                        } else {
-                               // Legacy mwCustomEditButtons is an object
-                               c = buttons[i];
-                               toolbar.insertButton( c.imageFile, c.speedTip, c.tagOpen, 
-                                       c.tagClose, c.sampleText, c.imageId, c.selectText );
+                               // Raw object from legacy mwCustomEditButtons
+                               insertButton( b );
                        }
                }
 
                }
        });
 
-}( jQuery, mediaWiki ) );
+}( mediaWiki, jQuery ) );