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