-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added throttling function #1996
Changes from 4 commits
14bfa1c
a359ff1
affd52e
73aa69d
9f83ca5
b465c9d
c291d41
0307707
051fd74
2d2bea6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -579,6 +579,90 @@ | |
}, milliseconds || 0 ); | ||
}, | ||
|
||
/** | ||
* Throttles `input` events (or any `input` calls) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It has nothing to do with events, we're talking about |
||
* and triggers `output` not more often than once per `minInterval`. | ||
* | ||
* After each `input` scheduled `output` will be destroyed and replaced with | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if this is clear, but it could be only me.
Aaaand looking at above ☝️ it doesn't look any better 😄 I think we'll need to ask other devs to help us out here. But it's crucial this bit is understandable, as it's a very important information. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TBO I'm not sure if we need information about discarding arguments in preceding calls since preceding call (there is only one previously scheduled call) is discarded. However, I think that the sentence:
best corresponds to how this function works. Although I'm not sure if it's enough understandable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it works well 👍 |
||
* new one. It gives you the opportunity to modify passed parameters into `input` function and get | ||
* different results. | ||
* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it make sense to put a note that the first call is always guaranteed to be executed synchronously. |
||
* var buffer = CKEDITOR.tools.throttle( 200, function( message ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Listings should be wrapped with triple backticks + language hit 🙀 |
||
* console.log( message ); | ||
* } ); | ||
* | ||
* buffer.input( 'foo!' ); | ||
* // 'foo!' logged immediately. | ||
* buffer.input( 'buz!' ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The third word in foo bar used most commonly is |
||
* // Nothing logged. | ||
* buffer.input( 'bar!' ); | ||
* // Nothing logged. | ||
* // ... after 200ms a single 'bar!' will be logged. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change ... (thre dots) into a legit … (ellipsis) character 🙂 |
||
* | ||
* Can be easily used with events: | ||
* | ||
* var buffer = CKEDITOR.tools.throttle( 200, function( evt ) { | ||
* console.log( evt.data.text ); | ||
* } ); | ||
* | ||
* editor.on( 'key', buffer.input ); | ||
* // Note: There is no need to bind buffer as a context. | ||
* | ||
* @since 4.10.0 | ||
* @param {Number} minInterval Minimum interval between `output` calls in milliseconds. | ||
* @param {Function} output Function that will be executed as `output`. | ||
* @param {Object} [scopeObj] The object used to scope the listener call (the `this` object). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically it's not a scope… it's a context. There is a difference between the two (please have a read on it and don't repeat the mistakes that the Ancient Ones did 🙂). So |
||
* @returns {Object} | ||
* @returns {Function} return.input Buffer's input method. | ||
* Accepts parameters which will be directly passed into `output` function. | ||
* @returns {Function} return.reset Resets buffered events — `output` will not be executed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Buffered |
||
* until next `input` is triggered. | ||
*/ | ||
throttle: function( minInterval, output, scopeObj ) { | ||
var scheduled, | ||
lastOutput = 0; | ||
|
||
scopeObj = scopeObj || {}; | ||
|
||
return { | ||
input: input, | ||
reset: reset | ||
}; | ||
|
||
function input() { | ||
var args = Array.prototype.slice.call( arguments ); | ||
|
||
if ( scheduled ) { | ||
clearTimeout( scheduled ); | ||
scheduled = 0; | ||
} | ||
|
||
var diff = ( new Date() ).getTime() - lastOutput; | ||
|
||
// If less than minInterval passed after last check, | ||
// schedule next for minInterval after previous one. | ||
if ( diff < minInterval ) { | ||
scheduled = setTimeout( triggerOutput, minInterval - diff ); | ||
} else { | ||
triggerOutput(); | ||
} | ||
|
||
function triggerOutput() { | ||
lastOutput = ( new Date() ).getTime(); | ||
scheduled = false; | ||
|
||
output.apply( scopeObj, args ); | ||
} | ||
} | ||
|
||
function reset() { | ||
if ( scheduled ) { | ||
clearTimeout( scheduled ); | ||
scheduled = lastOutput = 0; | ||
} | ||
} | ||
}, | ||
|
||
/** | ||
* Removes spaces from the start and the end of a string. The following | ||
* characters are removed: space, tab, line break, line feed. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -628,6 +628,99 @@ | |
assert.areSame( 'ABcDeF', c( 'aBcDeF', true ) ); | ||
}, | ||
|
||
'test throttle': function() { | ||
var output = 0, | ||
foo = 'foo', | ||
buz = 'buz', | ||
message, | ||
buffer = CKEDITOR.tools.throttle( 200, function( arg ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once again, use sinon.stub / spy for function mocking, it will make the API much, much clearer. 🙂 If you'd use sinon, you had assertion like that: sinon.assert.calledOnce( inputSpy );
sinon.assert.calledWithExactly( inputSpy, 'foo' ); And then: assert.areSame( 2, inputSpy.callCount );
sinon.assert.calledWithExactly( foo.getCall( 1 ), 'buz' ) Just to make few examples. It's a really convenient API. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem with |
||
output++; | ||
message = arg; | ||
} ); | ||
|
||
assert.areSame( 0, output ); | ||
|
||
buffer.input( foo ); | ||
|
||
assert.areSame( 1, output ); | ||
assert.areSame( foo, message ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good luck with figuring out as any of the assertion fails. In other words, please, put meaningful assert messages when possible and applicable. For sure this test case is one of them. |
||
|
||
buffer.input( buz ); | ||
buffer.input( buz ); | ||
buffer.input( buz ); | ||
|
||
assert.areSame( 1, output ); | ||
assert.areSame( foo, message ); | ||
|
||
wait( function() { | ||
assert.areSame( 1, output ); | ||
assert.areSame( foo, message ); | ||
|
||
wait( function() { | ||
assert.areSame( 2, output ); | ||
assert.areSame( buz, message ); | ||
|
||
buffer.input( foo ); | ||
|
||
assert.areSame( 2, output ); | ||
assert.areSame( buz, message ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No reason to check the message here, as it couldn't be changed if the call count is the same. |
||
|
||
wait( function() { | ||
assert.areSame( 3, output ); | ||
assert.areSame( foo, message ); | ||
|
||
// Check that input triggered after 70ms from previous | ||
// buffer.input will trigger output after next 140ms (200-70). | ||
wait( function() { | ||
buffer.input( buz ); | ||
|
||
assert.areSame( 3, output ); | ||
assert.areSame( foo, message ); | ||
|
||
wait( function() { | ||
assert.areSame( 4, output ); | ||
assert.areSame( buz, message ); | ||
}, 140 ); | ||
}, 70 ); | ||
}, 210 ); | ||
}, 110 ); | ||
}, 100 ); | ||
}, | ||
|
||
'test throttle.reset': function() { | ||
var output = 0, | ||
buffer = CKEDITOR.tools.throttle( 100, function() { | ||
output++; | ||
} ); | ||
|
||
assert.areSame( 0, output ); | ||
|
||
buffer.input(); | ||
|
||
assert.areSame( 1, output ); | ||
|
||
buffer.input(); | ||
buffer.reset(); | ||
|
||
wait( function() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe you could even call it synchronously after reset and check the counter. |
||
assert.areSame( 1, output ); | ||
|
||
buffer.input(); | ||
|
||
assert.areSame( 2, output ); | ||
}, 110 ); | ||
}, | ||
|
||
'test throttle contex': function() { | ||
var spy = sinon.spy(), | ||
ctxObj = {}, | ||
buffer = CKEDITOR.tools.throttle( 100, spy, ctxObj ); | ||
|
||
buffer.input(); | ||
|
||
assert.areSame( ctxObj, spy.getCall( 0 ).thisValue, 'callback was executed with the right context' ); | ||
}, | ||
|
||
'test checkIfAnyObjectPropertyMatches': function() { | ||
var c = CKEDITOR.tools.checkIfAnyObjectPropertyMatches, | ||
r1 = /foo/, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<style> | ||
.red { | ||
background-color: red; | ||
} | ||
.blue { | ||
background-color: blue; | ||
} | ||
</style> | ||
|
||
<button id="btn" class="red" >Click me!</button> | ||
|
||
<script> | ||
var button = document.getElementById( 'btn' ), | ||
buffer = CKEDITOR.tools.throttle( 2000, function ( css ) { | ||
button.className = css === 'red' ? 'blue' : 'red'; | ||
} ); | ||
|
||
button.addEventListener( 'click', function() { | ||
buffer.input( button.className ); | ||
} ); | ||
</script> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
@bender-tags: feature, 4.10.0, 1993 | ||
@bender-ui: collapsed | ||
@bender-ckeditor-plugins: toolbar, wysiwygarea | ||
|
||
1. Click button `Click me` five times as fast as possible. | ||
|
||
## Expected | ||
|
||
Button changes its color: | ||
1. After first click to blue. | ||
1. After last click to red. | ||
|
||
## Unexpected | ||
|
||
Button changes its color in invalid order. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should land in the API section.