mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-29 18:30:09 +00:00
reworked debounce functionality...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
afae3656f8
commit
4b65f4ee25
@ -847,30 +847,71 @@ module.Cache = ImageGridFeatures.Feature({
|
|||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
// Timers...
|
// Timers...
|
||||||
|
|
||||||
|
// Create a debounced action...
|
||||||
|
//
|
||||||
|
// options format:
|
||||||
|
// {
|
||||||
|
// timeout: number,
|
||||||
|
// returns: 'cached' | 'dropped',
|
||||||
|
// callback: function(retriggered, args),
|
||||||
|
// }
|
||||||
|
//
|
||||||
var debounce =
|
var debounce =
|
||||||
module.debounce =
|
module.debounce =
|
||||||
function(timeout, func){
|
function(options, func){
|
||||||
func = timeout instanceof Function ? timeout : func
|
// parse args...
|
||||||
var f = function(...args){
|
func = options instanceof Function ? options : func
|
||||||
return this.debounceActionCall({
|
options = options instanceof Function ? {} : options
|
||||||
action: func,
|
|
||||||
args: args,
|
if(typeof(options) == typeof(123)){
|
||||||
tag: func instanceof Function ?
|
options.timeout = options
|
||||||
(func.name || f.name)
|
|
||||||
: func,
|
|
||||||
timeout: timeout instanceof Function ? null : timeout,
|
|
||||||
returns: 'cached',
|
|
||||||
retrigger: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// closure state...
|
||||||
|
var res = undefined
|
||||||
|
var debounced = false
|
||||||
|
var retriggered = 0
|
||||||
|
|
||||||
|
var f = function(...args){
|
||||||
|
if(!debounced){
|
||||||
|
res = func instanceof Function ?
|
||||||
|
func.call(this, ...args)
|
||||||
|
// alias...
|
||||||
|
: this.parseStringAction.callAction(this, func, ...args)
|
||||||
|
res = options.returns != 'cahced' ? res : undefined
|
||||||
|
|
||||||
|
// start the timer...
|
||||||
|
debounced = setTimeout(
|
||||||
|
function(){
|
||||||
|
// callback...
|
||||||
|
options.callback instanceof Function
|
||||||
|
&& options.callback.call(this, retriggered, args)
|
||||||
|
|
||||||
|
// cleanup...
|
||||||
|
retriggered = 0
|
||||||
|
res = undefined
|
||||||
|
debounced = false
|
||||||
|
}.bind(this),
|
||||||
|
options.timeout
|
||||||
|
|| this.config['debounce-action-timeout']
|
||||||
|
|| 200)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
retriggered++
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
f.toString = function(){
|
f.toString = function(){
|
||||||
return `// debounced...\n${doc([func.toString()])}`
|
return `// debounced...\n${doc([func.toString()])}`
|
||||||
}
|
}
|
||||||
// NOTE: this will force Action(..) to set the .name to the action name...
|
|
||||||
Object.defineProperty(f, 'name', { value: '<action-name>' })
|
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
var TimersActions = actions.Actions({
|
var TimersActions = actions.Actions({
|
||||||
config: {
|
config: {
|
||||||
//
|
//
|
||||||
@ -1100,120 +1141,6 @@ var TimersActions = actions.Actions({
|
|||||||
|
|
||||||
// Action debounce...
|
// Action debounce...
|
||||||
//
|
//
|
||||||
debounceActionCall: ['- System/',
|
|
||||||
doc`Debounce the action call...
|
|
||||||
|
|
||||||
.debounceActionCall(call)
|
|
||||||
-> result
|
|
||||||
|
|
||||||
Call format:
|
|
||||||
{
|
|
||||||
// action name of function to be called...
|
|
||||||
action: <name> | <function>,
|
|
||||||
|
|
||||||
// arguments to be passed to action/function...
|
|
||||||
args: <array>,
|
|
||||||
|
|
||||||
// tag to identify the call...
|
|
||||||
//
|
|
||||||
// Defaults to action name, optional for actions and
|
|
||||||
// required for functions...
|
|
||||||
tag: <tag> | null,
|
|
||||||
|
|
||||||
// timeout to drop calls within (optional).
|
|
||||||
//
|
|
||||||
// defaults to .config['debounce-action-timeout'] then
|
|
||||||
// to 200.
|
|
||||||
timeout: <number> | null,
|
|
||||||
|
|
||||||
// controls how action return value is handled:
|
|
||||||
//
|
|
||||||
// Values:
|
|
||||||
// 'cached' - cache the value and return it for
|
|
||||||
// every call within the timeout.
|
|
||||||
// 'dropped' - ignore return values.
|
|
||||||
//
|
|
||||||
// NOTE: this is designed to produce uniform results
|
|
||||||
// without and exceptions.
|
|
||||||
returns: 'cached' | 'dropped',
|
|
||||||
|
|
||||||
// if true re trigger the action after timeout if it was
|
|
||||||
// called after the initial call but before the timeout
|
|
||||||
// ended...
|
|
||||||
retrigger: <bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
NOTE: this does not affect actions called directly in any way.
|
|
||||||
`,
|
|
||||||
function(call){
|
|
||||||
var action = call.action
|
|
||||||
var args = call.args || []
|
|
||||||
var tag = call.tag || call.action
|
|
||||||
var timeout = call.timeout
|
|
||||||
|| this.config['debounce-action-timeout']
|
|
||||||
|| 200
|
|
||||||
var returns = call.returns || 'cached'
|
|
||||||
var retrigger = call.retrigger || false
|
|
||||||
|
|
||||||
// when debouncing a function a tag is required...
|
|
||||||
if(tag instanceof Function){
|
|
||||||
throw new TypeError('debounce: when passing a function a tag is required.')
|
|
||||||
}
|
|
||||||
|
|
||||||
var attr = '__debounce_'+ tag
|
|
||||||
|
|
||||||
// repeated call...
|
|
||||||
if(this[attr]){
|
|
||||||
if(retrigger){
|
|
||||||
this[attr +'_retriggered'] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var res = returns == 'cached' ?
|
|
||||||
this[attr +'_return']
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
// setup and first call...
|
|
||||||
} else {
|
|
||||||
// NOTE: we are ignoring the return value here so as to
|
|
||||||
// make the first and repeated call uniform...
|
|
||||||
var context = this
|
|
||||||
var res = (action instanceof Function ?
|
|
||||||
action
|
|
||||||
: action.split('.')
|
|
||||||
.reduce(function(res, e){
|
|
||||||
context = res
|
|
||||||
return res[e]
|
|
||||||
}, this))
|
|
||||||
.call(context, ...args)
|
|
||||||
|
|
||||||
// cache the return value...
|
|
||||||
if(returns == 'cached'){
|
|
||||||
this[attr +'_return'] = res
|
|
||||||
|
|
||||||
// drop the return value...
|
|
||||||
} else {
|
|
||||||
res = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
this[attr] = setTimeout(function(){
|
|
||||||
delete this[attr]
|
|
||||||
delete this[attr +'_return']
|
|
||||||
|
|
||||||
// retrigger...
|
|
||||||
if(this[attr +'_retriggered']){
|
|
||||||
delete this[attr +'_retriggered']
|
|
||||||
|
|
||||||
tag == action ?
|
|
||||||
this.debounce(timeout, action, ...args)
|
|
||||||
: this.debounce(timeout, tag, action, ...args)
|
|
||||||
}
|
|
||||||
}.bind(this), timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}],
|
|
||||||
|
|
||||||
// shorthand...
|
|
||||||
debounce: ['- System/',
|
debounce: ['- System/',
|
||||||
doc`Debounce action call...
|
doc`Debounce action call...
|
||||||
|
|
||||||
@ -1227,6 +1154,19 @@ var TimersActions = actions.Actions({
|
|||||||
.debounce(tag, func, ...)
|
.debounce(tag, func, ...)
|
||||||
.debounce(timeout, tag, func, ...)
|
.debounce(timeout, tag, func, ...)
|
||||||
|
|
||||||
|
Generic debounce:
|
||||||
|
.debounce(options, action, ...)
|
||||||
|
.debounce(options, func, ...)
|
||||||
|
|
||||||
|
options format:
|
||||||
|
{
|
||||||
|
tag: <string>,
|
||||||
|
timeout: <milliseconds>,
|
||||||
|
returns: 'cached' | 'dropped',
|
||||||
|
callback: <function>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Protocol:
|
Protocol:
|
||||||
- call
|
- call
|
||||||
- start timeout timer
|
- start timeout timer
|
||||||
@ -1244,26 +1184,58 @@ var TimersActions = actions.Actions({
|
|||||||
`,
|
`,
|
||||||
function(...args){
|
function(...args){
|
||||||
// parse the args...
|
// parse the args...
|
||||||
var timeout = typeof(args[0]) == typeof(123) ?
|
if(!(args[0] instanceof Function
|
||||||
|
|| typeof(args[0]) == typeof(123)
|
||||||
|
|| typeof(args[0]) == typeof('str'))){
|
||||||
|
var options = args.shift()
|
||||||
|
var tag = options.tag || args[0].name || args[0]
|
||||||
|
|
||||||
|
} else {
|
||||||
|
var options = {
|
||||||
|
timeout: typeof(args[0]) == typeof(123) ?
|
||||||
args.shift()
|
args.shift()
|
||||||
: (this.config['debounce-action-timeout'] || 200)
|
: (this.config['debounce-action-timeout'] || 200),
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: this[tag] must not be callable, otherwise we treat it
|
// NOTE: this[tag] must not be callable, otherwise we treat it
|
||||||
// as an action...
|
// as an action...
|
||||||
var tag = (args[0] instanceof Function
|
var tag = (args[0] instanceof Function
|
||||||
|| this[args[0]] instanceof Function) ?
|
|| this[args[0]] instanceof Function) ?
|
||||||
args[0]
|
args[0]
|
||||||
: args.shift()
|
: args.shift()
|
||||||
var action = args.shift()
|
}
|
||||||
|
|
||||||
return this.debounceActionCall({
|
// sanity check: when debouncing a function a tag is required...
|
||||||
action: action,
|
if(tag instanceof Function){
|
||||||
args: args,
|
throw new TypeError('debounce: when passing a function a tag is required.')
|
||||||
tag: tag,
|
}
|
||||||
timeout: timeout,
|
|
||||||
// XXX
|
var action = args.shift()
|
||||||
returns: 'dropped',
|
var attr = '__debounce_'+ tag
|
||||||
retrigger: true,
|
|
||||||
|
options = Object.assign(Object.create(options), {
|
||||||
|
callback: function(retriggered, args){
|
||||||
|
// cleanup...
|
||||||
|
delete this[attr]
|
||||||
|
|
||||||
|
// call the original callback...
|
||||||
|
options.__proto__.callback
|
||||||
|
&& options.__proto__.callback.call(that, ...args)
|
||||||
|
|
||||||
|
if(options.retrigger
|
||||||
|
&& retriggered > 0
|
||||||
|
// this prevents an extra action after "sitting"
|
||||||
|
// on the keyboard and triggering key repeat...
|
||||||
|
&& retriggered < (options.timeout || 200) / 200){
|
||||||
|
var func = this[attr] = this[attr] || debounce(options, action)
|
||||||
|
func.call(this, ...args)
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var func = this[attr] = this[attr] || debounce(options, action)
|
||||||
|
|
||||||
|
return func.call(this, ...args)
|
||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -60,6 +60,9 @@ var ExampleActions = actions.Actions({
|
|||||||
return args
|
return args
|
||||||
})],
|
})],
|
||||||
|
|
||||||
|
testDebounce: ['Test/',
|
||||||
|
core.debounce(1000, 'exampleAction: ... -- docs...')],
|
||||||
|
|
||||||
// a normal method...
|
// a normal method...
|
||||||
exampleMethod: function(){
|
exampleMethod: function(){
|
||||||
console.log('example method:', [].slice.call(arguments))
|
console.log('example method:', [].slice.call(arguments))
|
||||||
|
|||||||
@ -555,9 +555,12 @@ var KeyboardActions = actions.Actions({
|
|||||||
}, this)
|
}, this)
|
||||||
return data.debounce ?
|
return data.debounce ?
|
||||||
// debounce...
|
// debounce...
|
||||||
this.debounce(
|
this.debounce({
|
||||||
data.debounce,
|
timeout: data.debounce,
|
||||||
'tag:'+data.action,
|
tag: 'tag:'+data.action,
|
||||||
|
retrigger: true,
|
||||||
|
returns: 'dropped',
|
||||||
|
},
|
||||||
meth.bind(context), ...data.arguments)
|
meth.bind(context), ...data.arguments)
|
||||||
// direct call...
|
// direct call...
|
||||||
: meth.call(context, ...data.arguments) },
|
: meth.call(context, ...data.arguments) },
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user