jenkins-bot has submitted this change and it was merged.
Change subject: Implemented EventSingletonManager
......................................................................
Implemented EventSingletonManager
Implemented $.util.EventSingletonManager and applied it to $.sticknode plugin.
The utility constructor will be used for solving 73300 as well.
Change-Id: I46fed73ae4ab25284d392ce19b912ee8272b5495
---
M lib/resources/Resources.php
M lib/resources/jquery/jquery.sticknode.js
A lib/resources/jquery/jquery.util.EventSingletonManager.js
A lib/tests/qunit/jquery/jquery.util.EventSingletonManager.tests.js
M lib/tests/qunit/resources.php
5 files changed, 393 insertions(+), 58 deletions(-)
Approvals:
Adrian Lang: Looks good to me, approved
jenkins-bot: Verified
diff --git a/lib/resources/Resources.php b/lib/resources/Resources.php
index d3fb44f..534676b 100644
--- a/lib/resources/Resources.php
+++ b/lib/resources/Resources.php
@@ -132,6 +132,15 @@
'jquery/jquery.sticknode.js',
),
'dependencies' => array(
+ 'jquery.util.EventSingletonManager',
+ ),
+ ),
+
+ 'jquery.util.EventSingletonManager' => $moduleTemplate + array(
+ 'scripts' => array(
+ 'jquery/jquery.util.EventSingletonManager.js',
+ ),
+ 'dependencies' => array(
'jquery.throttle-debounce',
),
),
diff --git a/lib/resources/jquery/jquery.sticknode.js
b/lib/resources/jquery/jquery.sticknode.js
index 92aa88a..4650ac4 100644
--- a/lib/resources/jquery/jquery.sticknode.js
+++ b/lib/resources/jquery/jquery.sticknode.js
@@ -6,7 +6,7 @@
'use strict';
var $window = $( window ),
- stickyInstances = [],
+ eventSingleton = new $.util.EventSingletonManager(),
PLUGIN_NAME = 'sticknode';
/**
@@ -34,65 +34,39 @@
return;
}
- register( new StickyNode( $( this ), options ) );
+ var stickyNode = new StickyNode( $( this ), options );
+
+ eventSingleton.register(
+ stickyNode,
+ window,
+ 'scroll.' + PLUGIN_NAME + ' ' + 'touchmove.' +
PLUGIN_NAME,
+ function( event, stickyNode ) {
+ if( stickyNode.update( $window.scrollTop() ) ) {
+ stickyNode.$node.triggerHandler(
PLUGIN_NAME + 'update' );
+ }
+ },
+ {
+ throttle: 150
+ }
+ );
+
+ eventSingleton.register(
+ stickyNode,
+ window,
+ 'resize.' + PLUGIN_NAME,
+ function( event, stickyNode ) {
+ if( stickyNode.update( $window.scrollTop(),
true ) ) {
+ stickyNode.$node.triggerHandler(
PLUGIN_NAME + 'update' );
+ }
+ },
+ {
+ throttle: 150
+ }
+ );
} );
return this;
};
-
-/**
- * @param {boolean} [force]
- */
-function update( force ) {
- force = force === true;
-
- for( var i = 0; i < stickyInstances.length; i++ ) {
- if( stickyInstances[i].update( $window.scrollTop(), force ) ) {
- stickyInstances[i].$node.triggerHandler( PLUGIN_NAME +
'update' );
- }
- }
-}
-
-function updateAndForceTriggerEvent() {
- update( true );
-}
-
-/**
- * @param {Function} fn
- * @return {Function}
- */
-function throttle( fn ) {
- return $.throttle ? $.throttle( 150, fn ) : fn;
-}
-
-/**
- * @param {StickyNode} sticky
- */
-function register( sticky ) {
- if( !stickyInstances.length ) {
- $window
- .on( 'scroll.' + PLUGIN_NAME + ' ' + 'touchmove.' +
PLUGIN_NAME, ( function() {
- return throttle( update );
- }() ) )
- .on( 'resize.' + PLUGIN_NAME, ( function() {
- return throttle( updateAndForceTriggerEvent );
- }() ) );
- }
- stickyInstances.push( sticky );
-}
-
-/**
- * @param {StickyNode} sticky
- */
-function deregister( sticky ) {
- var index = $.inArray( sticky );
- if( index ) {
- stickyInstances.splice( index, 1 );
- }
- if( !stickyInstances.length ) {
- $window.off( '.' + PLUGIN_NAME );
- }
-}
/**
* @constructor
@@ -133,10 +107,14 @@
_changesDocumentHeight: false,
/**
- * Destroys and deregisters the plugin.
+ * Destroys and unregisters the plugin.
*/
destroy: function() {
- deregister( this );
+ eventSingleton.unregister(
+ this.$node.data( PLUGIN_NAME ),
+ window,
+ '.' + PLUGIN_NAME
+ );
this.$node.removeData( PLUGIN_NAME );
},
diff --git a/lib/resources/jquery/jquery.util.EventSingletonManager.js
b/lib/resources/jquery/jquery.util.EventSingletonManager.js
new file mode 100644
index 0000000..8005093
--- /dev/null
+++ b/lib/resources/jquery/jquery.util.EventSingletonManager.js
@@ -0,0 +1,201 @@
+/**
+ * @licence GNU GPL v2+
+ * @author H. Snater < [email protected] >
+ */
+( function( $ ) {
+ 'use strict';
+
+$.util = $.util || {};
+
+/**
+ * Manages attaching an event handler to a target only once for a set of
source objects.
+ * Since an event is attached only once, the initial event handler (for the
combination of target/
+ * event name/event namespace) may not be overwritten later on.
+ * @constructor
+ */
+var SELF = $.util.EventSingletonManager = function() {
+ this._registry = [];
+};
+
+$.extend( SELF.prototype, {
+ /**
+ * @type {Object[]}
+ */
+ _registry: [],
+
+ /**
+ * Attaches an event handler to a target element unless it is not
attached already. The event
+ * binding is registered for a source element.
+ *
+ * @param {*} source
+ * @param {HTMLElement|Object} target
+ * @param {string} event
+ * @param {Function} handler
+ * Will receive the following arguments when the event is
triggered:
+ * - {jQuery.Event} Object representing the actual event
triggered on the target.
+ * - {*} source
+ * @param {Object} [options]
+ * - {number} [throttle] Throttle delay
+ * Default: undefined (no throttling)
+ * - {number} [debounce] Debounce delay
+ * Default: undefined (no debouncing)
+ */
+ register: function( source, target, event, handler, options ) {
+ var namespacedEvents = event.split( ' ' );
+
+ options = options || {};
+
+ for( var i = 0; i < namespacedEvents.length; i++ ) {
+ var registration = this._getRegistration( target,
namespacedEvents[i] );
+
+ if( registration ) {
+ registration.sources.push( source );
+ } else {
+ this._attach( source, target,
namespacedEvents[i], handler, options );
+ }
+ }
+ },
+
+ /**
+ * Unregisters one or multiple events attached to a target element and
registered for a specific
+ * source.
+ *
+ * @param {*} source
+ * @param {HTMLElement|Object} target
+ * @param {string} event
+ * Instead of white-space separated list of event names, a
single namespace may be passed
+ * to remove all events attached to target and registered on
source.
+ */
+ unregister: function( source, target, event ) {
+ var registrations = [],
+ i;
+
+ if( event.indexOf( '.' ) === 0 ) {
+ registrations = this._getRegistrations( target,
event.split( '.' )[1] );
+ } else {
+ var events = event.split( ' ' );
+ for( i = 0; i < events.length; i++ ) {
+ var registration = this._getRegistration(
target, events[i] );
+ if( registration ) {
+ registrations.push( registration );
+ }
+ }
+ }
+
+ for( i = 0; i < registrations.length; i++ ) {
+ var index = $.inArray( source, registrations[i].sources
);
+ if( index !== -1 ) {
+ registrations[i].sources.splice( index, 1 );
+ }
+ if( !registrations[i].sources.length ) {
+ this._detach( registrations[i] );
+ }
+ }
+ },
+
+ /**
+ * @param {HTMLElement|Object} target
+ * @param {string} event
+ * @return {Object}
+ */
+ _getRegistration: function( target, event ) {
+ var eventSegments = event.split( '.' );
+
+ for( var i = 0; i < this._registry.length; i++ ) {
+ if(
+ this._registry[i].target === target
+ && this._registry[i].event === eventSegments[0]
+ && this._registry[i].namespace ===
eventSegments[1]
+ ) {
+ return this._registry[i];
+ }
+ }
+ },
+
+ /**
+ * @param {HTMLElement|Object} target
+ * @param {string} namespace
+ * @return {Object[]}
+ */
+ _getRegistrations: function( target, namespace ) {
+ var registered = [];
+
+ for( var i = 0; i < this._registry.length; i++ ) {
+ if( this._registry[i].target === target &&
this._registry[i].namespace === namespace ) {
+ registered.push( this._registry[i] );
+ }
+ }
+
+ return registered;
+ },
+
+ /**
+ * @param {*} source
+ * @param {HTMLElement|Object} target
+ * @param {string} event
+ * @param {Function} handler
+ * @param {Object} options
+ */
+ _attach: function( source, target, event, handler, options ) {
+ var self = this,
+ eventSegments = event.split( '.' ),
+ actualHandler = function( actualEvent ) {
+ self._triggerHandler( target, event,
actualEvent );
+ },
+ alteredHandler;
+
+ if( options.throttle ) {
+ alteredHandler = $.throttle( options.throttle,
actualHandler );
+ } else if( options.debounce ) {
+ alteredHandler = $.debounce( options.debounce,
actualHandler );
+ }
+
+ $( target ).on( event, alteredHandler || actualHandler );
+
+ this._registry.push( {
+ sources: [source],
+ target: target,
+ event: eventSegments[0],
+ namespace: eventSegments[1],
+ handler: handler
+ } );
+ },
+
+ /**
+ * @param {Object} registration
+ */
+ _detach: function( registration ) {
+ var namespaced = registration.event;
+ if( registration.namespace ) {
+ namespaced += '.' + registration.namespace;
+ }
+ $( registration.target ).off( namespaced );
+
+ for( var i = 0; i < this._registry.length; i++ ) {
+ if(
+ this._registry[i].target === registration.target
+ && this._registry[i].event ===
registration.event
+ && this._registry[i].namespace ===
registration.namespace
+ ) {
+ this._registry.splice( i, 1 );
+ }
+ }
+ },
+
+ /**
+ * @param {HTMLElement|Object} target
+ * @param {string} event
+ * @param {jQuery.Event} actualEvent
+ */
+ _triggerHandler: function( target, event, actualEvent ) {
+ var registration = this._getRegistration( target, event );
+
+ if( registration ) {
+ for( var i = 0; i < registration.sources.length; i++ ) {
+ registration.handler( actualEvent,
registration.sources[i] );
+ }
+ }
+ }
+} );
+
+} )( jQuery );
diff --git a/lib/tests/qunit/jquery/jquery.util.EventSingletonManager.tests.js
b/lib/tests/qunit/jquery/jquery.util.EventSingletonManager.tests.js
new file mode 100644
index 0000000..1c23ec9
--- /dev/null
+++ b/lib/tests/qunit/jquery/jquery.util.EventSingletonManager.tests.js
@@ -0,0 +1,138 @@
+/**
+ * @licence GNU GPL v2+
+ * @author H. Snater < [email protected] >
+ */
+( function( $, QUnit ) {
+ 'use strict';
+
+QUnit.module( 'jquery.util.EventSingletonManager' );
+
+QUnit.test( 'register() & unregister() (single source)', 2, function( assert )
{
+ var manager = new $.util.EventSingletonManager(),
+ $source = $( '<div/>' ),
+ $target = $( '<div/>' ),
+ event = $.Event( 'custom' );
+
+ manager.register(
+ $source.get( 0 ),
+ $target.get( 0 ),
+ 'custom.namespace',
+ function( event, source ) {
+ assert.ok(
+ true,
+ 'Triggered event "' + event.type + '.'
+ );
+
+ assert.equal(
+ $source.get( 0 ),
+ source,
+ 'Verified source element being passed to the
handler.'
+ );
+ }
+ );
+
+ $target.trigger( event );
+
+ manager.unregister( $source.get( 0 ), $target.get( 0 ),
'custom.namespace' );
+
+ $target.trigger( event );
+} );
+
+QUnit.test( 'register() & unregister() (multiple sources)', 4, function(
assert ) {
+ var manager = new $.util.EventSingletonManager(),
+ $sources = $( '<div/><div/>' ),
+ sources = $sources.map( function() { return this; } ),
+ $target = $( '<div/>' ),
+ triggeredForSources = [],
+ event = $.Event( 'custom' ),
+ handler = function( event, source ) {
+ triggeredForSources.push( source );
+ };
+
+ manager.register( $sources.get( 0 ), $target.get( 0 ),
'custom.namespace', handler );
+ manager.register( $sources.get( 1 ), $target.get( 0 ),
'custom.namespace', handler );
+
+ $target.trigger( event );
+
+ assert.equal(
+ triggeredForSources.length,
+ $sources.length,
+ 'Handler has been called for every source.'
+ );
+
+ assert.ok(
+ $.inArray( sources[0], triggeredForSources ) !== -1,
+ 'Handler was called for first source.'
+ );
+
+ assert.ok(
+ $.inArray( sources[1], triggeredForSources ) !== -1,
+ 'Handler was called for second source.'
+ );
+
+ manager.unregister( $sources.get( 1 ), $target.get( 0 ),
'custom.namespace' );
+
+ $target.trigger( event );
+
+ assert.equal(
+ triggeredForSources[2],
+ $sources.get( 0 ),
+ 'Handler was called once again after unregistering a source.'
+ );
+
+ manager.unregister( $sources.get( 0 ), $target.get( 0 ), '.namespace' );
+} );
+
+QUnit.test( 'unregister() & unregister() (multiple events)', 8, function(
assert ) {
+ var manager = new $.util.EventSingletonManager(),
+ $source = $( '<div/>' ),
+ $target = $( '<div/>' ),
+ event1 = $.Event( 'custom1' ),
+ event2 = $.Event( 'custom2' ),
+ event3 = $.Event( 'custom3' ),
+ event4 = $.Event( 'custom4' );
+
+ manager.register(
+ $source.get( 0 ),
+ $target.get( 0 ),
+ 'custom1.namespace custom2.namespace custom3.namespace
custom4.othernamespace',
+ function( event, source ) {
+ assert.ok(
+ true,
+ 'Triggered event "' + event.type + '".'
+ );
+ }
+ );
+
+ $target.trigger( event1 );
+ $target.trigger( event2 );
+ $target.trigger( event3 );
+ $target.trigger( event4 );
+
+ manager.unregister(
+ $source.get( 0 ),
+ $target.get( 0 ),
+ 'custom1.namespace'
+ );
+
+ $target.trigger( event1 ); // no action
+ $target.trigger( event2 );
+ $target.trigger( event3 );
+ $target.trigger( event4 );
+
+ manager.unregister( $source.get( 0 ), $target.get( 0 ), '.namespace' );
+
+ $target.trigger( event1 ); // no action
+ $target.trigger( event2 ); // no action
+ $target.trigger( event3 ); // no action
+ $target.trigger( event4 );
+
+ manager.unregister( $source.get( 0 ), $target.get( 0 ),
'.othernamespace' );
+
+ $target.trigger( event1 ); // no action
+ $target.trigger( event2 ); // no action
+ $target.trigger( event3 ); // no action
+ $target.trigger( event4 ); // no action
+} );
+
+}( jQuery, QUnit ) );
diff --git a/lib/tests/qunit/resources.php b/lib/tests/qunit/resources.php
index 1fda429..b6e0c15 100644
--- a/lib/tests/qunit/resources.php
+++ b/lib/tests/qunit/resources.php
@@ -53,6 +53,15 @@
),
),
+ 'jquery.util.EventSingletonManager.tests' => $moduleBase +
array(
+ 'scripts' => array(
+
'jquery/jquery.util.EventSingletonManager.tests.js',
+ ),
+ 'dependencies' => array(
+ 'jquery.util.EventSingletonManager',
+ ),
+ ),
+
'jquery.ui.tagadata.tests' => $moduleBase + array(
'scripts' => array(
'jquery.ui/jquery.ui.tagadata.tests.js',
--
To view, visit https://gerrit.wikimedia.org/r/172987
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I46fed73ae4ab25284d392ce19b912ee8272b5495
Gerrit-PatchSet: 2
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Henning Snater <[email protected]>
Gerrit-Reviewer: Adrian Lang <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits