2015-09-07 18:44:10 +03:00
|
|
|
/**********************************************************************
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
**********************************************************************/
|
2016-08-21 02:19:24 +03:00
|
|
|
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
|
|
|
|
(function(require){ var module={} // make module AMD/node compatible...
|
|
|
|
|
/*********************************************************************/
|
2015-09-07 18:44:10 +03:00
|
|
|
|
2015-09-20 21:54:28 +03:00
|
|
|
var keyboard = require('../keyboard')
|
2015-12-08 07:42:07 +03:00
|
|
|
var object = require('../object')
|
2015-09-20 21:54:28 +03:00
|
|
|
|
|
|
|
|
|
2015-09-07 18:44:10 +03:00
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
|
// helpers...
|
|
|
|
|
|
2020-02-02 05:32:31 +03:00
|
|
|
// NOTE: this may produce a leak in cases where lots of events are bound
|
|
|
|
|
// and on a long running widget... (XXX)
|
2015-09-07 18:44:10 +03:00
|
|
|
var proxyToDom =
|
|
|
|
|
module.proxyToDom =
|
|
|
|
|
function(name){
|
2018-03-09 17:51:27 +03:00
|
|
|
return function(...args){
|
2020-02-02 05:32:31 +03:00
|
|
|
// XXX this feels hacky, investigate a better solution...
|
|
|
|
|
// ...one way to go is to either handle events internally
|
|
|
|
|
// or remove .bind(..)...
|
|
|
|
|
// XXX this is a potential leak (see note above)...
|
|
|
|
|
var d = this.__proxy_to_dom_dict =
|
|
|
|
|
this.__proxy_to_dom_dict || new Map()
|
2018-03-09 17:51:27 +03:00
|
|
|
// bind functions to this...
|
|
|
|
|
args = args
|
|
|
|
|
.map(function(a){
|
2020-02-02 05:32:31 +03:00
|
|
|
name != 'off'
|
|
|
|
|
&& a instanceof Function
|
|
|
|
|
&& ( d.has(a)
|
|
|
|
|
|| d.set(a, a.bind(this)) )
|
|
|
|
|
var res = a instanceof Function ?
|
|
|
|
|
d.get(a) || a.bind(this)
|
2018-03-09 17:51:27 +03:00
|
|
|
: a
|
2020-02-02 05:32:31 +03:00
|
|
|
// NOTE: this will delete cached handlers but it can't
|
|
|
|
|
// get all of them in a generic way...
|
|
|
|
|
name == 'off'
|
|
|
|
|
&& a instanceof Function
|
|
|
|
|
&& d.delete(a)
|
|
|
|
|
return res
|
2018-03-09 17:51:27 +03:00
|
|
|
}.bind(this))
|
2020-02-02 05:32:31 +03:00
|
|
|
// call method or trigger event...
|
2018-03-09 17:51:27 +03:00
|
|
|
name in this.dom ?
|
2020-01-24 18:10:53 +03:00
|
|
|
this.dom[name](...args)
|
2018-03-09 17:51:27 +03:00
|
|
|
: this.dom.trigger(name, args)
|
2020-02-02 05:32:31 +03:00
|
|
|
return this } }
|
2015-09-07 18:44:10 +03:00
|
|
|
|
2017-11-13 23:38:16 +03:00
|
|
|
var eventToDom =
|
|
|
|
|
module.eventToDom =
|
|
|
|
|
function(name, defaults){
|
|
|
|
|
return function(){
|
|
|
|
|
// register...
|
|
|
|
|
if(arguments[0] instanceof Function){
|
2018-11-12 23:04:00 +03:00
|
|
|
this.dom.trigger(name, [...arguments])
|
2017-11-13 23:38:16 +03:00
|
|
|
|
|
|
|
|
// trigger...
|
|
|
|
|
} else {
|
|
|
|
|
var args = (arguments.length == 0 && defaults) ?
|
|
|
|
|
defaults.call(this)
|
2018-11-12 23:04:00 +03:00
|
|
|
: [...arguments]
|
2017-11-13 23:38:16 +03:00
|
|
|
args = args instanceof Array ? args : [args]
|
|
|
|
|
|
|
|
|
|
this.dom.trigger(name, args)
|
|
|
|
|
}
|
2020-02-02 05:32:31 +03:00
|
|
|
return this } }
|
2017-11-13 23:38:16 +03:00
|
|
|
|
2015-09-07 18:44:10 +03:00
|
|
|
|
|
|
|
|
// XXX triggering events from here and from jQuery/dom has a
|
|
|
|
|
// different effect...
|
|
|
|
|
var triggerEventWithSource =
|
|
|
|
|
module.triggerEventWithSource =
|
|
|
|
|
function(){
|
2018-11-14 14:46:50 +03:00
|
|
|
var args = [...arguments]
|
2015-09-07 18:44:10 +03:00
|
|
|
var evt = args.shift()
|
|
|
|
|
|
|
|
|
|
if(typeof(evt) == typeof('str')){
|
|
|
|
|
evt = $.Event(evt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
evt.source = this
|
|
|
|
|
|
|
|
|
|
args.splice(0, 0, evt)
|
|
|
|
|
|
|
|
|
|
this.dom.trigger.apply(this.dom, args)
|
|
|
|
|
return this
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
|
|
2015-09-20 21:54:28 +03:00
|
|
|
var WidgetClassPrototype = {
|
2020-05-16 17:55:41 +03:00
|
|
|
//make: function(obj, client, options){
|
|
|
|
|
// console.error('Widget must define a .make method.')
|
|
|
|
|
//},
|
2015-09-20 21:54:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var WidgetPrototype = {
|
2016-05-08 16:13:19 +03:00
|
|
|
// NOTE: this must have .data('widget-controller', this) set...
|
2015-09-20 21:54:28 +03:00
|
|
|
dom: null,
|
|
|
|
|
client: null,
|
|
|
|
|
|
|
|
|
|
options: {
|
2017-03-08 21:26:37 +03:00
|
|
|
keyboardRepeatPause: 100,
|
|
|
|
|
|
2015-09-20 21:54:28 +03:00
|
|
|
nonPropagatedEvents: [
|
2017-01-25 22:22:33 +03:00
|
|
|
'start',
|
|
|
|
|
|
2015-09-20 21:54:28 +03:00
|
|
|
'click',
|
|
|
|
|
'keydown',
|
2016-04-30 20:14:39 +03:00
|
|
|
|
|
|
|
|
'close',
|
2015-09-20 21:54:28 +03:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
|
2017-01-12 02:09:59 +03:00
|
|
|
keybindings: null,
|
2015-09-20 21:54:28 +03:00
|
|
|
keyboard: null,
|
|
|
|
|
|
|
|
|
|
// XXX triggering events from here and from jQuery/dom has a
|
|
|
|
|
// different effect...
|
|
|
|
|
trigger: triggerEventWithSource,
|
|
|
|
|
|
|
|
|
|
// proxy event api...
|
|
|
|
|
on: proxyToDom('on'),
|
|
|
|
|
one: proxyToDom('one'),
|
|
|
|
|
off: proxyToDom('off'),
|
|
|
|
|
bind: proxyToDom('bind'),
|
|
|
|
|
unbind: proxyToDom('unbind'),
|
|
|
|
|
deligate: proxyToDom('deligate'),
|
|
|
|
|
undeligate: proxyToDom('undeligate'),
|
|
|
|
|
|
2017-01-06 05:17:14 +03:00
|
|
|
// custom events...
|
2017-01-24 00:14:31 +03:00
|
|
|
//
|
2017-01-25 22:22:33 +03:00
|
|
|
start: function(handler){
|
|
|
|
|
handler ?
|
|
|
|
|
this.on('start', handler)
|
|
|
|
|
:this.trigger('start')
|
|
|
|
|
return this
|
|
|
|
|
},
|
2017-01-24 00:14:31 +03:00
|
|
|
// NOTE: this can be passed a string that can be used as a reason
|
|
|
|
|
// for closing...
|
|
|
|
|
close: function(a){
|
2017-01-06 05:17:14 +03:00
|
|
|
// trigger...
|
2017-01-24 00:14:31 +03:00
|
|
|
if(a == null || typeof(a) == typeof('str')){
|
|
|
|
|
a = a || 'accept'
|
2017-01-06 05:17:14 +03:00
|
|
|
this.parent.close
|
2017-01-24 00:14:31 +03:00
|
|
|
&& this.parent.close(a)
|
|
|
|
|
this.trigger('close', a)
|
2017-01-06 05:17:14 +03:00
|
|
|
|
|
|
|
|
// register new handler...
|
|
|
|
|
} else {
|
2017-01-24 00:14:31 +03:00
|
|
|
this.on('close', a)
|
2017-01-06 05:17:14 +03:00
|
|
|
}
|
|
|
|
|
return this
|
|
|
|
|
},
|
2015-09-20 21:54:28 +03:00
|
|
|
|
|
|
|
|
// XXX this will not:
|
2015-10-02 17:32:13 +03:00
|
|
|
// - attach dom to parent... (???)
|
|
|
|
|
// - handle focus... (???)
|
2015-09-20 21:54:28 +03:00
|
|
|
// XXX same as ContainerPrototype.__init__ but skips client...
|
|
|
|
|
__init__: function(parent, options){
|
|
|
|
|
var that = this
|
|
|
|
|
|
|
|
|
|
parent = this.parent = $(parent || 'body')
|
|
|
|
|
|
2017-01-27 17:33:21 +03:00
|
|
|
this.keybindings = JSON.parse(JSON.stringify(this.keybindings))
|
|
|
|
|
|
2015-09-20 21:54:28 +03:00
|
|
|
// merge options...
|
2020-05-16 17:55:41 +03:00
|
|
|
options = this.options = Object.assign(
|
|
|
|
|
Object.create(this.options),
|
|
|
|
|
options || {})
|
2015-09-20 21:54:28 +03:00
|
|
|
|
|
|
|
|
// build the dom...
|
|
|
|
|
if(this.constructor.make){
|
2015-11-09 22:48:13 +03:00
|
|
|
this.dom = this.constructor.make(this, options)
|
2016-05-08 16:13:19 +03:00
|
|
|
|
|
|
|
|
this.dom.data('widget-controller', this)
|
2015-09-20 21:54:28 +03:00
|
|
|
}
|
|
|
|
|
|
2015-10-02 17:32:13 +03:00
|
|
|
// XXX do we do this here???
|
|
|
|
|
/*
|
|
|
|
|
if(parent && this.dom){
|
|
|
|
|
parent.append(this.dom)
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
2015-09-20 21:54:28 +03:00
|
|
|
// add keyboard handler...
|
2017-01-12 02:09:59 +03:00
|
|
|
if(this.keybindings && this.dom){
|
2017-01-12 02:41:03 +03:00
|
|
|
this.keyboard =
|
|
|
|
|
this.keyboard || keyboard.KeyboardWithCSSModes(
|
|
|
|
|
function(){ return that.keybindings },
|
|
|
|
|
function(){ return that.dom })
|
2017-01-06 05:17:14 +03:00
|
|
|
this.dom
|
|
|
|
|
.keydown(
|
2017-03-08 21:26:37 +03:00
|
|
|
keyboard.makePausableKeyboardHandler(
|
2017-01-06 05:17:14 +03:00
|
|
|
this.keyboard,
|
|
|
|
|
options.logKeys,
|
2017-03-08 21:26:37 +03:00
|
|
|
this,
|
2020-05-16 17:55:41 +03:00
|
|
|
function(){ return this.options.keyboardRepeatPause })) }
|
2015-09-20 21:54:28 +03:00
|
|
|
|
2020-05-16 17:55:41 +03:00
|
|
|
this.options.nonPropagatedEvents != null
|
|
|
|
|
&& this.on(this.options.nonPropagatedEvents.join(' '),
|
2016-04-30 20:14:39 +03:00
|
|
|
function(evt){ evt.stopPropagation() })
|
|
|
|
|
|
2015-09-20 21:54:28 +03:00
|
|
|
return this
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var Widget =
|
|
|
|
|
module.Widget =
|
2019-07-17 00:07:24 +03:00
|
|
|
object.Constructor('Widget',
|
2015-09-20 21:54:28 +03:00
|
|
|
WidgetClassPrototype,
|
|
|
|
|
WidgetPrototype)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
|
|
|
|
|
|
var ContainerClassPrototype = {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var ContainerPrototype = {
|
2016-05-08 16:13:19 +03:00
|
|
|
// NOTE: this must have .data('widget-controller', this) set...
|
|
|
|
|
dom: null,
|
2015-09-22 23:55:49 +03:00
|
|
|
|
|
|
|
|
focus: function(handler){
|
|
|
|
|
if(handler != null){
|
|
|
|
|
this.on('focus', handler)
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
this.dom.focus()
|
|
|
|
|
this.client
|
|
|
|
|
&& this.client.focus
|
|
|
|
|
&& this.client.focus()
|
|
|
|
|
}
|
|
|
|
|
return this
|
|
|
|
|
},
|
|
|
|
|
|
2015-09-20 21:54:28 +03:00
|
|
|
// XXX this is the same as WidgetPrototype.__init__ but also handles
|
|
|
|
|
// the client...
|
|
|
|
|
__init__: function(parent, client, options){
|
|
|
|
|
var that = this
|
2017-01-27 17:33:21 +03:00
|
|
|
|
2015-09-20 21:54:28 +03:00
|
|
|
parent = this.parent = $(parent || 'body')
|
|
|
|
|
|
2017-01-27 17:33:21 +03:00
|
|
|
this.keybindings = JSON.parse(JSON.stringify(this.keybindings))
|
|
|
|
|
|
2015-09-20 21:54:28 +03:00
|
|
|
this.client = client
|
2016-01-05 04:48:24 +03:00
|
|
|
client.parent = this
|
2015-09-20 21:54:28 +03:00
|
|
|
|
|
|
|
|
// merge options...
|
2020-05-16 17:55:41 +03:00
|
|
|
options = this.options = Object.assign(
|
|
|
|
|
Object.create(this.options),
|
|
|
|
|
options || {})
|
2015-09-20 21:54:28 +03:00
|
|
|
|
|
|
|
|
// build the dom...
|
|
|
|
|
if(this.constructor.make){
|
2015-09-22 23:55:49 +03:00
|
|
|
this.dom = this.constructor
|
2015-11-09 22:48:13 +03:00
|
|
|
.make(this, client.dom || client, options)
|
2016-05-08 16:13:19 +03:00
|
|
|
|
|
|
|
|
this.dom.data('widget-controller', this)
|
2015-09-20 21:54:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// add keyboard handler...
|
2017-01-12 02:09:59 +03:00
|
|
|
if(this.keybindings && this.dom){
|
2017-01-12 02:41:03 +03:00
|
|
|
this.keyboard =
|
|
|
|
|
this.keyboard || keyboard.KeyboardWithCSSModes(
|
|
|
|
|
function(){ return that.keybindings },
|
|
|
|
|
function(){ return that.dom })
|
2017-01-12 02:09:59 +03:00
|
|
|
this.dom
|
|
|
|
|
.keydown(
|
2017-03-08 21:26:37 +03:00
|
|
|
keyboard.makePausableKeyboardHandler(
|
2017-01-12 02:09:59 +03:00
|
|
|
this.keyboard,
|
|
|
|
|
options.logKeys,
|
2017-03-08 21:26:37 +03:00
|
|
|
this,
|
|
|
|
|
function(){ return this.options.keyboardRepeatPause }))
|
2015-09-20 21:54:28 +03:00
|
|
|
}
|
|
|
|
|
|
2016-04-30 20:14:39 +03:00
|
|
|
if(this.options.nonPropagatedEvents != null){
|
|
|
|
|
this.on(this.options.nonPropagatedEvents.join(' '),
|
|
|
|
|
function(evt){ evt.stopPropagation() })
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-20 21:54:28 +03:00
|
|
|
return this
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var Container =
|
|
|
|
|
module.Container =
|
2019-07-17 00:07:24 +03:00
|
|
|
object.Constructor('Container',
|
2020-05-16 17:55:41 +03:00
|
|
|
Widget,
|
|
|
|
|
ContainerClassPrototype,
|
|
|
|
|
ContainerPrototype)
|
2015-09-20 21:54:28 +03:00
|
|
|
|
2015-09-07 18:44:10 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**********************************************************************
|
2016-08-21 02:19:24 +03:00
|
|
|
* vim:set ts=4 sw=4 : */ return module })
|