cleanup and some refactoring...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2019-05-30 19:44:22 +03:00
parent 62245eea34
commit 56cb5fa405

View File

@ -228,6 +228,26 @@ object.makeConstructor('BrowserEvent',
})
// Make a method comply with the event spec...
//
// This is mainly for use in overloading event methods.
//
// Example:
// someEvent: eventMethod('someEvent', function(..){
// // call the original handler...
// ...
//
// ...
// })
//
var eventMethod =
module.eventMethod =
function(event, func){
func.event = event
return func
}
// Generate an event method...
//
// Make and event method...
@ -248,28 +268,26 @@ object.makeConstructor('BrowserEvent',
// .event(func)
// -> this
//
var makeEventMethod = function(event, handler, retrigger){
var makeEventMethod =
module.makeEventMethod =
function(event, handler, retrigger){
retrigger = retrigger !== false
return Object.assign(
function(item){
// register handler...
if(item instanceof Function){
return this.on(event, item)
}
return eventMethod(event, function(item){
// register handler...
if(item instanceof Function){
return this.on(event, item)
}
var evt = new BrowserEvent(event)
var evt = new BrowserEvent(event)
handler
&& handler.call(this, evt, ...arguments)
handler
&& handler.call(this, evt, ...arguments)
return retrigger ?
this.trigger(evt, ...arguments)
: this
},
{
event: event,
}) }
return retrigger ?
this.trigger(evt, ...arguments)
: this
}) }
// Call item event handlers...
@ -277,7 +295,8 @@ var makeEventMethod = function(event, handler, retrigger){
// callItemEventHandlers(item, event_name, event_object, ...)
// -> null
//
var callItemEventHandlers = function(item, event, evt, ...args){
var callItemEventHandlers =
function(item, event, evt, ...args){
evt = evt || new BrowserEvent(event)
// get the relevant handlers...
;(item[event] ?
@ -338,7 +357,9 @@ var callItemEventHandlers = function(item, event, evt, ...args){
// NOTE: item events do not directly trigger the original caller's handlers
// those will get celled recursively when the events are propagated
// up the tree.
var makeItemEventMethod = function(event, handler, default_item, filter, options){
var makeItemEventMethod =
module.makeItemEventMethod =
function(event, handler, default_item, filter, options){
// parse args...
var args = [...arguments].slice(2)
default_item = args[0] instanceof Function
@ -413,7 +434,9 @@ var makeItemEventMethod = function(event, handler, default_item, filter, options
// - .toggleSelect([1, 2, 10, 20], 'next') -- toggles items on only, returns []
// - .toggleSelect([1, 2, 10, 20], 'on') -- works but returns []
// - .toggleSelect([1, 2, 10, 20], 'off') -- works but returns []
var makeItemEventToggler = function(get_state, set_state, unset_state, default_item, multi, options){
var makeItemEventToggler =
module.makeItemEventToggler =
function(get_state, set_state, unset_state, default_item, multi, options){
var _get_state = get_state instanceof Function ?
get_state
: function(e){ return !!e[get_state] }
@ -548,12 +571,19 @@ var makeItemEventToggler2 = function(get_state, set_state, unset_state, default_
var BaseBrowserClassPrototype = {
}
// XXX need a way to identify items...
// ...try order + text if all else fails...
// XXX Q: should we be able to add/remove/change items outside of .__list__(..)???
// ...only some item updates (how .collapsed is handled) make
// sense at this time -- need to think about this more
// carefully + strictly document the result...
// XXX can/should we make traversal simpler???
// ...currently to get to a nested item we'd need to:
// dialog.flatIndex.B.children.index.C. ...
// on the other hand we can .get('B/C/...')
var BaseBrowserPrototype = {
// XXX should we mix item/list options or separate them into sub-objects???
options: {
noDuplicateValues: false,
// If true item keys must be unique...
uniqueKeys: false,
},
// parent widget object...
@ -586,6 +616,22 @@ var BaseBrowserPrototype = {
set items(value){
this.__items = value },
// Clear cached data...
//
// This will delete all attributes of the format:
// .__<title>_cache
//
clearCache: function(){
Object.keys(this)
.forEach(function(key){
if(key.startsWith('__') && key.endsWith('_cache')){
delete this[key]
}
}.bind(this))
return this },
// Item index...
//
// Format:
@ -596,24 +642,11 @@ var BaseBrowserPrototype = {
// ...
// }
//
// NOTE: this will get overwritten each tume .make(..) is called.
//
// XXX need to maintain this over item add/remove/change...
// XXX Q: should we be able to add/remove/change items outside of .__list__(..)???
// ...only some item updates (how .collapsed is handled) make
// sense at this time -- need to think about this more
// carefully + strictly document the result...
// XXX can we make the format here simpler with less level
// of indirection??
// ...currently to go down a path we need to:
// this.index.A.children.index.B.children...
// would be nice to be closer to:
// this.A.B...
// XXX should this be constructed here??? (now this is done in .make(..))
__item_index: null,
// NOTE: this will get overwritten each time .make(..) is called.
__item_index_cache: null,
get index(){
return (this.__item_index =
this.__item_index
return (this.__item_index_cache =
this.__item_index_cache
|| this
.reduce(function(index, e, i, p){
var id = p = p.join('/')
@ -640,24 +673,24 @@ var BaseBrowserPrototype = {
//
// XXX should this be cached???
get flatIndex(){
return this.reduce(function(index, e, i, p){
var id = p = this.__key__(e)
var c = 0
while(id in index){
id = this.__id__(p, ++c)
}
index[id] = e
return index
}.bind(this), {}, {iterateAll: true}) },
return this
.reduce(function(index, e, i, p){
var id = p = this.__key__(e)
var c = 0
while(id in index){
id = this.__id__(p, ++c)
}
index[id] = e
return index
}.bind(this), {}, {iterateAll: true}) },
// XXX should we cache the value here????
// Shorthands for common item queries...
//
// XXX should these be cached???
get focused(){
return this.get('focused') },
set focused(value){
this.focus(value) },
// XXX should we cache the value here????
get selected(){
return this.search('selected') },
set selected(value){
@ -747,9 +780,9 @@ var BaseBrowserPrototype = {
throw new Error('.__list__(..): Not implemented.') },
// XXX need a better key/path API...
//
// Normalize value...
__value2key__: function(key){
//return JSON.stringify(key)
return key instanceof Array ?
@ -757,9 +790,6 @@ var BaseBrowserPrototype = {
: key },
// Key getter/generator...
//
// XXX should these include the path???
// XXX is JSON the best key format???
__key__: function(item){
return item.id
// value is a browser -> generate an unique id...
@ -1169,6 +1199,7 @@ var BaseBrowserPrototype = {
return [options, context] },
options, context)
.join('\n') },
// XXX need to check for output key uniqueness per level...
_test_tree: function(options, context){
var toObject = function(res, e){
if(e == null || e[0] == null){
@ -1201,16 +1232,15 @@ var BaseBrowserPrototype = {
options, context)
// construct the object...
.reduce(toObject, {}) },
paths: function(options, context){
// XXX we do not need this any more, as we got paths in the index...
_test_paths: function(options, context){
return this.walk(
function(n, i, p){
return n
&& [(options || {}).joinPaths !== false ?
p.join('/')
: p] },
'paths',
'_test_paths',
function(_, i, path, options, context){
// NOTE: for paths and indexes to be consistent between
// levels we need to thread the context on, here and
@ -1291,6 +1321,7 @@ var BaseBrowserPrototype = {
options, context) },
// Search items...
//
// Get list of matching elements...
// NOTE: this is similar to .filter(..)
@ -1553,7 +1584,7 @@ var BaseBrowserPrototype = {
options, context) },
// XXX EXPERIMENTAL...
// Get item...
//
// Get focused item...
// .get()
@ -1641,17 +1672,14 @@ var BaseBrowserPrototype = {
options) ].flat()[0] },
// XXX BROKEN...
// Sublist map functions...
//
// XXX should these return a sparse array... ???
// XXX this does not include inlined sections, should it???
sublists: function(func, options){
return this.search({children: true}, func, options) },
// XXX should there return an array or a .constructor(..) instance??
// XXX should these call respective methods (.forEach(..), .filter(..),
// .reduce(..)) on the nested browsers???
// XXX should these return an array or a .constructor(..) instance??
// XXX should this call .forEach(..) on nested stuff or just fall
// back to .map(..)???
forEach: function(func, options){
this.map(...arguments)
return this },
@ -1681,6 +1709,7 @@ var BaseBrowserPrototype = {
return context.result
},
// XXX should this return a path or a <path>:<count> ad in .index ???
positionOf: function(item, options){
return this.search(item,
function(_, i, p){
@ -1747,6 +1776,7 @@ var BaseBrowserPrototype = {
that.expand([...nodes]) }) },
// XXX do we need edit ability here?
// i.e. .set(..), .remove(..), .sort(..), ...
// ...if we are going to implement editing then we'll need to
@ -1772,7 +1802,12 @@ var BaseBrowserPrototype = {
// calls for such items...
//
// XXX revise options handling for .__list__(..)
// XXX see .flatIndex (preffered) / .pathIndex for alternative id generation...
// XXX might be a good idea to enable the used to merge the state
// manually...
// one way to do:
// - get the previous item via an index,
// - update it
// - pass it to make(..)
make: function(options){
options = Object.assign(Object.create(this.options || {}), options || {})
@ -1800,7 +1835,10 @@ var BaseBrowserPrototype = {
// while the latter stores a list of items.
// ...would be more logical to store the object (i.e. browser/list)
// directly as the element...
var keys = new Set()
var keys = options.uniqueKeys ?
new Set()
: null
var ids = new Set()
var make_called = false
var make = function(value, opts){
make_called = true
@ -1837,18 +1875,25 @@ var BaseBrowserPrototype = {
// item id...
var key = this.__key__(opts)
// handle duplicate ids -> err if found...
if(opts.id && keys.has(opts.id)){
throw new Error(`make(..): duplicate id "${key}": `
+`can't create multiple items with the same key.`) }
// duplicate keys (if .options.uniqueKeys is set)...
if(keys){
if(keys.has(key)){
throw new Error(`make(..): duplicate key "${key}": `
+`can't create multiple items with the same key `
+`when .options.uniqueKeys is set.`)
}
keys.add(key)
}
// duplicate ids...
if(opts.id && ids.has(opts.id)){
throw new Error(`make(..): duplicate id "${opts.id}": `
+`can't create multiple items with the same id.`) }
// build the item...
var item = Object.assign(
Object.create(options || {}),
opts,
{
parent: this,
})
{ parent: this })
// XXX do we need both this and the above ref???
item.children instanceof Browser
@ -1857,7 +1902,7 @@ var BaseBrowserPrototype = {
// store the item...
items.push(item)
keys.add(key)
ids.add(key)
return make
}.bind(this)
@ -1874,14 +1919,15 @@ var BaseBrowserPrototype = {
options || {})
: null)
// reset the index...
var old_index = this.__item_index || {}
delete this.__item_index
// 2'nd pass -> make id's unique...
// reset the index/cache...
var old_index = this.__item_index_cache || {}
this.clearCache()
// 2'nd pass -> make item index (unique id's)...
// NOTE: we are doing this in a separate pass as items can get
// rearranged during the make phase (Items.nest(..) ...),
// thus avoiding odd duplicate index numbering...
var index = this.__item_index = this.index
var index = this.__item_index_cache = this.index
// merge old item state...
Object.entries(index)
@ -1934,7 +1980,6 @@ var BaseBrowserPrototype = {
renderGroup: function(items, context){
return items },
// Render state...
//
// .render()
@ -2027,12 +2072,16 @@ var BaseBrowserPrototype = {
__event_handlers: null,
// List events...
// XXX avoid going through expensive props...
get events(){
var that = this
// props to skip...
// XXX should we just skip any prop???
var skip = new Set([
'events'
])
return Object.deepKeys(this)
.map(function(key){
return (key != 'events'
return (!skip.has(key)
&& that[key] instanceof Function
&& that[key].event) ?
that[key].event
@ -2221,7 +2270,6 @@ var BaseBrowserPrototype = {
// NOTE: this will ignore disabled items.
// NOTE: .focus('next') / .focus('prev') will not wrap around the
// first last elements...
// XXX should it???
focus: makeItemEventMethod('focus',
function(evt, items){
// blur .focused...
@ -2240,19 +2288,18 @@ var BaseBrowserPrototype = {
blur: makeItemEventMethod('blur', function(evt, items){
items.forEach(function(item){
delete item.focused }) }),
toggleFocus: makeItemEventToggler(
'focused',
'focus', 'blur',
function(){ return this.focused || 0 },
false),
// NOTE: .next() / .prev() will wrap around the first/last elements...
// XXX should wrapping be done here or in .focus(..)???
next: function(){
this.focus('next').focused || this.focus('first')
return this },
prev: function(){
this.focus('prev').focused || this.focus('last')
return this },
toggleFocus: makeItemEventToggler(
'focused',
'focus', 'blur',
function(){ return this.focused || 0 },
false),
select: makeItemEventMethod('select',
function(evt, items){
@ -2265,10 +2312,10 @@ var BaseBrowserPrototype = {
items.forEach(function(item){
delete item.selected }) },
function(){ return this.focused }),
toggleSelect: makeItemEventToggler('selected', 'select', 'deselect', 'focused'),
// XXX use a real toggler or just emulate toggler API???
// ...meke these two the same and select the simpler version...
toggleSelect: makeItemEventToggler('selected', 'select', 'deselect', 'focused'),
toggleSelect2: makeItemEventToggler2('selected', 'select', 'deselect', 'focused'),
//toggleSelect2: makeItemEventToggler2('selected', 'select', 'deselect', 'focused'),
// NOTE: .expand(..) / .collapse(..) / .toggleCollapse(..) ignore
// item.collapsed state....
@ -2341,12 +2388,8 @@ var BaseBrowserPrototype = {
__init__: function(func, options){
this.__list__ = func
this.options = Object.assign(
{},
this.options || {},
Object.create(this.options || {}),
options || {})
// XXX should this be here or should this be optional???
//this.update()
},
}
@ -2378,6 +2421,8 @@ var BrowserPrototype = {
__proto__: BaseBrowser.prototype,
options: {
__proto__: BaseBrowser.prototype.options,
hideListHeader: false,
renderHidden: false,