jenkins-bot has submitted this change and it was merged.

Change subject: mw.hook: Implement callback framework
......................................................................


mw.hook: Implement callback framework

We have various interface components that are extended, enhanced
or manipulated in some other way by extensions, gadgets and even
in core itself.

This framework helps streamlining the timing of when these other
code paths fire their plugins (instead of using document-ready,
which can and should be limited to firing only once).

Features like navigating to other wiki pages, previewing an edit
and editing itself – without a refresh – can then retrigger these
hooks accordingly to ensure everything still works as expected.

For the server side this has been possible already, but for the
client side we've been limited to running only once on the
original page load.

Further documentation and examples are in the code documentation.

Bug: 23580
Bug: 30713
Change-Id: Ic73a3efe53d6fb731e7f1e531d5f51530cd7e4fe
---
M maintenance/jsduck/categories.json
M resources/mediawiki/mediawiki.js
M resources/mediawiki/mediawiki.util.js
M tests/qunit/suites/resources/mediawiki/mediawiki.test.js
4 files changed, 166 insertions(+), 2 deletions(-)

Approvals:
  Anomie: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/maintenance/jsduck/categories.json 
b/maintenance/jsduck/categories.json
index c29c91c..f20289e 100644
--- a/maintenance/jsduck/categories.json
+++ b/maintenance/jsduck/categories.json
@@ -11,7 +11,8 @@
                                        "mw.loader",
                                        "mw.html",
                                        "mw.html.Cdata",
-                                       "mw.html.Raw"
+                                       "mw.html.Raw",
+                                       "mw.hook"
                                ]
                        },
                        {
diff --git a/resources/mediawiki/mediawiki.js b/resources/mediawiki/mediawiki.js
index 6c7e697..3225198 100644
--- a/resources/mediawiki/mediawiki.js
+++ b/resources/mediawiki/mediawiki.js
@@ -1734,7 +1734,82 @@
                user: {
                        options: new Map(),
                        tokens: new Map()
-               }
+               },
+
+               /**
+                * Registry and firing of events.
+                *
+                * MediaWiki has various interface components that are 
extended, enhanced
+                * or manipulated in some other way by extensions, gadgets and 
even
+                * in core itself.
+                *
+                * This framework helps streamlining the timing of when these 
other
+                * code paths fire their plugins (instead of using 
document-ready,
+                * which can and should be limited to firing only once).
+                *
+                * Features like navigating to other wiki pages, previewing an 
edit
+                * and editing itself – without a refresh – can then retrigger 
these
+                * hooks accordingly to ensure everything still works as 
expected.
+                *
+                * Example usage:
+                *
+                *     mw.hook( 'wikipage.content' ).add( fn ).remove( fn );
+                *     mw.hook( 'wikipage.content' ).fire( $content );
+                *
+                * Handlers can be added and fired for arbitrary event names at 
any time. The same
+                * event can be fired multiple times. The last run of an event 
is memorized
+                * (similar to `$(document).ready` and `$.Deferred().done`).
+                * This means if an event is fired, and a handler added 
afterwards, the added
+                * function will be fired right away with the last given event 
data.
+                *
+                * Like Deferreds and Promises, the mw.hook object is both 
detachable and chainable.
+                * Thus allowing flexible use and optimal maintainability and 
authority control.
+                * You can pass around the `add` and/or `fire` method to 
another piece of code
+                * without it having to know the event name (or `mw.hook` for 
that matter).
+                *
+                *     var h = mw.hook( 'bar.ready' );
+                *     new mw.Foo( .. ).fetch( { callback: h.fire } );
+                *
+                * @class mw.hook
+                */
+               hook: ( function () {
+                       var lists = {};
+
+                       /**
+                        * @method hook
+                        * @member mw
+                        * @param {string} name Name of hook.
+                        * @return {mw.hook}
+                        */
+                       return function ( name ) {
+                               var list = lists[name] || ( lists[name] = 
$.Callbacks( 'memory' ) );
+
+                               return {
+                                       /**
+                                        * Register a hook handler
+                                        * @param {Function...} handler 
Function to bind.
+                                        * @chainable
+                                        */
+                                       add: list.add,
+
+                                       /**
+                                        * Unregister a hook handler
+                                        * @param {Function...} handler 
Function to unbind.
+                                        * @chainable
+                                        */
+                                       remove: list.remove,
+
+                                       /**
+                                        * Run a hook.
+                                        * @param {Mixed...} data
+                                        * @chainable
+                                        */
+                                       fire: function () {
+                                               return list.fireWith( null, 
slice.call( arguments ) );
+                                       }
+                               };
+                       };
+               }() )
        };
 
 }( jQuery ) );
diff --git a/resources/mediawiki/mediawiki.util.js 
b/resources/mediawiki/mediawiki.util.js
index 60ef758..bd8e596 100644
--- a/resources/mediawiki/mediawiki.util.js
+++ b/resources/mediawiki/mediawiki.util.js
@@ -104,6 +104,8 @@
                                return util.$content;
                        } )();
 
+                       mw.hook( 'wikipage.content' ).fire( util.$content );
+
                        // Table of contents toggle
                        $tocTitle = $( '#toctitle' );
                        $tocToggleLink = $( '#togglelink' );
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js 
b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js
index e338675..73dcf34 100644
--- a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js
@@ -817,4 +817,90 @@
 
        } );
 
+       QUnit.test( 'mw.hook', 10, function ( assert ) {
+               var hook, add, fire, chars, callback;
+
+               mw.hook( 'test.hook.unfired' ).add( function () {
+                       assert.ok( false, 'Unfired hook' );
+               } );
+
+               mw.hook( 'test.hook.basic' ).add( function () {
+                       assert.ok( true, 'Basic callback' );
+               } );
+               mw.hook( 'test.hook.basic' ).fire();
+
+               mw.hook( 'test.hook.data' ).add( function ( data1, data2 ) {
+                       assert.equal( data1, 'example', 'Fire with data (string 
param)' );
+                       assert.deepEqual( data2, ['two'], 'Fire with data 
(array param)' );
+               } );
+               mw.hook( 'test.hook.data' ).fire( 'example', ['two'] );
+
+               mw.hook( 'test.hook.chainable' ).add( function () {
+                       assert.ok( true, 'Chainable' );
+               } ).fire();
+
+               hook = mw.hook( 'test.hook.detach' );
+               add = hook.add;
+               fire = hook.fire;
+               add( function ( x, y ) {
+                       assert.deepEqual( [x, y], ['x', 'y'], 'Detached 
(contextless) with data' );
+               } );
+               fire( 'x', 'y' );
+
+               mw.hook( 'test.hook.fireBefore' ).fire().add( function () {
+                       assert.ok( true, 'Invoke handler right away if it was 
fired before' );
+               } );
+
+               mw.hook( 'test.hook.fireTwiceBefore' ).fire().fire().add( 
function () {
+                       assert.ok( true, 'Invoke handler right away if it was 
fired before (only last one)' );
+               } );
+
+               chars = [];
+
+               mw.hook( 'test.hook.many' )
+                       .add( function ( chr ) {
+                               chars.push( chr );
+                       } )
+                       .fire( 'x' ).fire( 'y' ).fire( 'z' )
+                       .add( function ( chr ) {
+                               assert.equal( chr, 'z', 'Adding callback later 
invokes right away with last data' );
+                       } );
+
+               assert.deepEqual( chars, ['x', 'y', 'z'], 'Multiple callbacks 
with multiple fires' );
+
+               chars = [];
+               callback = function ( chr ) {
+                       chars.push( chr );
+               };
+
+               mw.hook( 'test.hook.variadic' )
+                       .add(
+                               callback,
+                               callback,
+                               function ( chr ) {
+                                       chars.push( chr );
+                               },
+                               callback
+                       )
+                       .fire( 'x' )
+                       .remove(
+                               function () {
+                                       'not-added';
+                               },
+                               callback
+                       )
+                       .fire( 'y' )
+                       .remove( callback )
+                       .fire( 'z' );
+
+               assert.deepEqual(
+                       chars,
+                       ['x', 'x', 'x', 'x', 'y', 'z'],
+                       '"add" and "remove" support variadic arguments. ' +
+                               '"add" does not filter unique. ' +
+                               '"remove" removes all equal by reference. ' +
+                               '"remove" is silent if the function is not 
found'
+               );
+       } );
+
 }( mediaWiki, jQuery ) );

-- 
To view, visit https://gerrit.wikimedia.org/r/56762
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ic73a3efe53d6fb731e7f1e531d5f51530cd7e4fe
Gerrit-PatchSet: 8
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Krinkle <[email protected]>
Gerrit-Reviewer: Anomie <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Daniel Friesen <[email protected]>
Gerrit-Reviewer: Hoo man <[email protected]>
Gerrit-Reviewer: Kaldari <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: MarkTraceur <[email protected]>
Gerrit-Reviewer: Matmarex <[email protected]>
Gerrit-Reviewer: Mattflaschen <[email protected]>
Gerrit-Reviewer: Nikerabbit <[email protected]>
Gerrit-Reviewer: Ori.livneh <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: Trevor Parscal <[email protected]>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to