Divec has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/84997


Change subject: WIP:EventSequencer listeners at event loop start/end
......................................................................

WIP:EventSequencer listeners at event loop start/end

Implement two new types of event listener, both triggered only once per
event loop iteration.

ve.EventSequencer.js
* onLoop( f ) to trigger a listener at the start of the event loop
* afterLoop( f ) to trigger a listener at the end of the event loop

Change-Id: Iee9d4e5599836f52840ac34deca3408b1e72c4c3
---
M modules/ve/ve.EventSequencer.js
1 file changed, 103 insertions(+), 4 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor 
refs/changes/97/84997/1

diff --git a/modules/ve/ve.EventSequencer.js b/modules/ve/ve.EventSequencer.js
index e34d8d1..e78c385 100644
--- a/modules/ve/ve.EventSequencer.js
+++ b/modules/ve/ve.EventSequencer.js
@@ -19,9 +19,13 @@
  * *after* the keypress listener (i.e. in the 'wrong' order). EventSequencer
  * ensures that this does not happen.
  *
- * All listeners receive the jQuery event as an argument. If an on-event
+ * All these listeners receive the jQuery event as an argument. If an on-event
  * listener needs to pass information to a corresponding after-event listener,
  * it can do so by adding properties into the jQuery event itself.
+ *
+ * There are also 'onLoop' and 'afterLoop' listeners, which only fire once per
+ * Javascript event loop iteration, respectively before and after all the
+ * other listeners fire.
  *
  * @class ve.EventSequencer
  */
@@ -81,6 +85,26 @@
                this.afterListenersForEvent[eventName] = [];
                this.eventHandlers[eventName] = makeEventHandler( eventName );
        }
+
+       /**
+        * @property {Function[]}
+        */
+       this.onLoopListeners = [];
+
+       /**
+        * @property {Function[]}
+        */
+       this.afterLoopListeners = [];
+
+       /**
+        * @property {boolean}
+        */
+       this.doneOnLoop = false;
+
+       /**
+        * @property {number}
+        */
+       this.afterLoopTimeoutId = null;
 };
 
 /**
@@ -110,6 +134,15 @@
 
 
 /**
+ * Add listeners to be fired at the start of the Javascript event loop 
iteration
+ * @method
+ * @param {Function[]} listeners Listeners that take no arguments
+ */
+ve.EventSequencer.prototype.onLoop = function ( listeners ) {
+       Array.prototype.push.apply( this.onLoopListeners, listeners );
+};
+
+/**
  * Add listeners to be fired just before the browser native action
  * @method
  * @param {Object.<string,Function>} listeners Function for each event
@@ -120,7 +153,6 @@
                this.onListenersForEvent[eventName].push( listeners[eventName] 
);
        }
 };
-
 
 /**
  * Add listeners to be fired as soon as possible after the native action
@@ -135,6 +167,15 @@
 };
 
 /**
+ * Add listeners to be fired at the end of the Javascript event loop iteration
+ * @method
+ * @param {Function[]} listeners Listeners that take no arguments
+ */
+ve.EventSequencer.prototype.afterLoop = function ( listeners ) {
+       Array.prototype.push.apply( this.afterLoopListeners, listeners );
+};
+
+/**
  * Generic listener method which does the sequencing
  * @private
  * @method
@@ -144,6 +185,10 @@
 ve.EventSequencer.prototype.onEvent = function ( eventName, ev ) {
        var i, len, onListener, afterListener, pendingCall;
        this.runPendingCalls();
+       if ( ! this.doneOnLoop ) {
+               this.doneOnLoop = true;
+               this.doOnLoop();
+       }
        // Length cache 'len' is required, as an onListener could add another 
onListener
        for ( i = 0, len = this.onListenersForEvent[eventName].length; i < len; 
i++ ) {
                onListener = this.onListenersForEvent[eventName][i];
@@ -160,23 +205,77 @@
                // Must wrap everything in a function call, to create the 
required closure.
                pendingCall = { 'func': afterListener, 'id': null, 'ev': ev, 
'eventName': eventName };
                /*jshint loopfunc:true */
-               ( function ( pendingCall, ev ) {
+               // Should put the loop into the setTimeout. But len < 2 usually 
so it's not so bad
+               ( function ( pendingCall, ev, me ) {
                        var id = setTimeout( function () {
                                if ( pendingCall.id === null ) {
                                        // clearTimeout seems not always to 
work immediately
                                        return;
                                }
+                               ve.EventSequencer.resetAfterLoopTimeout( me );
                                pendingCall.id = null;
                                pendingCall.func( ev );
                        } );
                        pendingCall.id = id;
-               } )( pendingCall, ev );
+               } )( pendingCall, ev, this );
                /*jshint loopfunc:false */
                this.pendingCalls.push( pendingCall );
        }
 };
 
 /**
+ * Call each onLoopListener once
+ * @private
+ * @method
+ */
+ve.EventSequencer.prototype.doOnLoop = function () {
+       var i, len, onLoopListener;
+       // Length cache 'len' is required, as the functions called may add 
another listener
+       for ( i = 0, len = this.onLoopListeners.length; i < len; i++ ) {
+               onLoopListener = this.onLoopListeners[i];
+               onLoopListener();
+       }
+};
+
+/**
+ * Call each afterLoopListener once, unless the setTimeout is already cancelled
+ * @private
+ * @method
+ * @param {number} myTimeoutId The calling setTimeout id
+ */
+ve.EventSequencer.prototype.doAfterLoop = function ( myTimeoutId ) {
+       var i, len, afterLoopListener;
+
+       if ( this.afterLoopTimeoutId !== myTimeoutId ) {
+               // cancelled; do nothing
+               return;
+       }
+       this.afterLoopTimeoutId = null;
+
+       // Length cache 'len' is required, as the functions called may add 
another listener
+       for ( i = 0, len = this.afterLoopListeners.length; i < len; i++ ) {
+               afterLoopListener = this.afterLoopListeners[i];
+               afterLoopListener();
+       }
+};
+
+/**
+ * Cancel any pending setTimeout for the doAfterLoop, then set a new one
+ * @private
+ * @function
+ * @param {me} {ve.EventSequencer} The calling EventSequencer
+ */
+ve.EventSequencer.resetAfterLoopTimeout = function ( me ) {
+       var timeoutId;
+       if ( me.afterLoopTimeoutId !== null ) {
+               clearTimeout( me.afterLoopTimeoutId );
+       }
+       timeoutId = setTimeout( function () {
+               me.doAfterLoop( timeoutId );
+       } );
+};
+
+/**
  * Run any pending listeners, and clear the pending queue
  * @private
  * @method

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: Iee9d4e5599836f52840ac34deca3408b1e72c4c3
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Divec <[email protected]>

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

Reply via email to