| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | /********************************************************************** | 
					
						
							|  |  |  | *  | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | **********************************************************************/ | 
					
						
							|  |  |  | ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | 
					
						
							|  |  |  | (function(require){ var module={} // make module AMD/node compatible...
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var toggler = require('../toggler') | 
					
						
							|  |  |  | var keyboard = require('../keyboard') | 
					
						
							|  |  |  | var object = require('../object') | 
					
						
							|  |  |  | var widget = require('./widget') | 
					
						
							| 
									
										
										
										
											2019-06-12 04:44:17 +03:00
										 |  |  | var walk = require('lib/walk').walk | 
					
						
							| 
									
										
										
										
											2019-05-03 19:43:22 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							| 
									
										
										
										
											2019-02-05 18:25:23 +03:00
										 |  |  | // Helpers...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | // Collect a list of literal values and "make(..) calls" into an array...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //	collectItems(context, items)
 | 
					
						
							|  |  |  | //		-> values
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // items format:
 | 
					
						
							|  |  |  | // 	[
 | 
					
						
							|  |  |  | // 		// explicit value...
 | 
					
						
							|  |  |  | // 		value,
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 		// literal make call...
 | 
					
						
							|  |  |  | // 		make(..),
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 		...
 | 
					
						
							|  |  |  | // 	]
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-02-05 18:25:23 +03:00
										 |  |  | // NOTE: this will remove the made via make(..) items from .items thus the
 | 
					
						
							|  |  |  | // 		caller is responsible for adding them back...
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | // NOTE: this uses the make(..) return value to implicitly infer the items
 | 
					
						
							|  |  |  | // 		to collect, thus the items must already be constructed and in 
 | 
					
						
							|  |  |  | // 		the same order as they are present in .items
 | 
					
						
							|  |  |  | // 		...also, considering that this implicitly identifies the items 
 | 
					
						
							|  |  |  | // 		passing the make function without calling it can trick the system
 | 
					
						
							|  |  |  | // 		and lead to unexpected results.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // XXX would be nice to have a better check/test...
 | 
					
						
							|  |  |  | // 		...this could be done by chaining instances of make instead of 
 | 
					
						
							|  |  |  | // 		returning an actual function, i.e. each make call would return 
 | 
					
						
							|  |  |  | // 		a "new" function that would reference the actual item (.item())
 | 
					
						
							|  |  |  | // 		and the previous item created (.prevItem()), ... etc.
 | 
					
						
							|  |  |  | // 		...this would enable us to uniquely identify the actual items 
 | 
					
						
							|  |  |  | // 		and prevent allot of specific errors...
 | 
					
						
							| 
									
										
										
										
											2019-03-11 19:40:41 +03:00
										 |  |  | var collectItems = function(make, items){ | 
					
						
							| 
									
										
										
										
											2019-05-29 16:27:14 +03:00
										 |  |  | 	items = items instanceof Array ?  | 
					
						
							|  |  |  | 		items  | 
					
						
							|  |  |  | 		: [items] | 
					
						
							| 
									
										
										
										
											2019-02-05 18:25:23 +03:00
										 |  |  | 	var made = items | 
					
						
							|  |  |  | 		.filter(function(e){ | 
					
						
							| 
									
										
										
										
											2019-03-11 19:40:41 +03:00
										 |  |  | 			return e === make }) | 
					
						
							| 
									
										
										
										
											2019-02-05 18:25:23 +03:00
										 |  |  | 	// constructed item list...
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 	// ...remove each instance from .items
 | 
					
						
							| 
									
										
										
										
											2019-03-11 19:40:41 +03:00
										 |  |  | 	made = make.items.splice( | 
					
						
							|  |  |  | 		make.items.length - made.length,  | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 		made.length) | 
					
						
							| 
									
										
										
										
											2019-02-05 18:25:23 +03:00
										 |  |  | 	// get the actual item values...
 | 
					
						
							|  |  |  | 	return items | 
					
						
							|  |  |  | 		.map(function(e){ | 
					
						
							| 
									
										
										
										
											2019-03-11 19:40:41 +03:00
										 |  |  | 			return e === make ? | 
					
						
							| 
									
										
										
										
											2019-02-05 18:25:23 +03:00
										 |  |  | 				made.shift() | 
					
						
							| 
									
										
										
										
											2019-03-11 19:40:41 +03:00
										 |  |  | 				// raw item -> make(..)
 | 
					
						
							|  |  |  | 				: (make(e)  | 
					
						
							|  |  |  | 					&& make.items.pop()) }) } | 
					
						
							| 
									
										
										
										
											2019-02-05 18:25:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | // Items...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | // XXX general design:
 | 
					
						
							|  |  |  | // 		- each of these can take either a value or a function (constructor)
 | 
					
						
							|  |  |  | //		- the function has access to Items.* and context
 | 
					
						
							|  |  |  | //		- the constructor can be called from two contexts:
 | 
					
						
							|  |  |  | //			- external
 | 
					
						
							|  |  |  | //				called from the module or as a function...
 | 
					
						
							|  |  |  | //				calls the passed constructor (passing context)
 | 
					
						
							|  |  |  | //				builds the container
 | 
					
						
							|  |  |  | //			- nested
 | 
					
						
							|  |  |  | //				called from constructor function...
 | 
					
						
							|  |  |  | //				calls constructor (if applicable)
 | 
					
						
							|  |  |  | //				builds item(s)
 | 
					
						
							|  |  |  | // XXX need a way to pass container constructors (a-la ui-widgets dialog containers)
 | 
					
						
							|  |  |  | // 		- passing through the context (this) makes this more flexible...
 | 
					
						
							|  |  |  | // 		- passing via args fixes the signature which is a good thing...
 | 
					
						
							|  |  |  | //		
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX
 | 
					
						
							| 
									
										
										
										
											2019-02-10 18:09:08 +03:00
										 |  |  | // XXX can't use Object.assign(..) here as it will not copy props...
 | 
					
						
							| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | var Items = module.items = function(){} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | // placeholders...
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | Items.dialog = null | 
					
						
							|  |  |  | Items.items = null | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-04 14:18:00 +03:00
										 |  |  | // Last item created...
 | 
					
						
							|  |  |  | // XXX not sure about this...
 | 
					
						
							|  |  |  | // XXX should this be a prop???
 | 
					
						
							|  |  |  | Items.last = function(){ | 
					
						
							|  |  |  | 	return (this.items || [])[this.items.length - 1] } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-22 07:10:49 +03:00
										 |  |  | // Focus last created item...
 | 
					
						
							|  |  |  | Items.focus = function(){ | 
					
						
							| 
									
										
										
										
											2019-06-30 20:12:00 +03:00
										 |  |  | 	this.last().focused = true } | 
					
						
							| 
									
										
										
										
											2019-01-23 05:45:50 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-04 14:18:00 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | // Group a set of items...
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | //	.group(make(..), ..)
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | //	.group([make(..), ..])
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | //		-> make
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | // Example:
 | 
					
						
							|  |  |  | // 	make.group(
 | 
					
						
							| 
									
										
										
										
											2019-02-02 18:45:14 +03:00
										 |  |  | // 		make('made item'),
 | 
					
						
							| 
									
										
										
										
											2019-02-05 18:25:23 +03:00
										 |  |  | // 		'literal item',
 | 
					
						
							|  |  |  | // 		...)
 | 
					
						
							| 
									
										
										
										
											2019-01-23 05:45:50 +03:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // NOTE: see notes to collectItems(..) for more info...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | // XXX do we need to pass options to groups???
 | 
					
						
							|  |  |  | Items.group = function(...items){ | 
					
						
							| 
									
										
										
										
											2019-01-26 22:34:56 +03:00
										 |  |  | 	var that = this | 
					
						
							| 
									
										
										
										
											2019-02-04 14:18:00 +03:00
										 |  |  | 	items = items.length == 1 && items[0] instanceof Array ? | 
					
						
							|  |  |  | 		items[0] | 
					
						
							|  |  |  | 		: items | 
					
						
							| 
									
										
										
										
											2019-01-26 22:34:56 +03:00
										 |  |  | 	// replace the items with the group...
 | 
					
						
							| 
									
										
										
										
											2019-04-20 17:08:10 +03:00
										 |  |  | 	this.items.splice(this.items.length, 0, collectItems(this, items)) | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 	return this | 
					
						
							| 
									
										
										
										
											2019-01-23 05:45:50 +03:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-01-22 07:10:49 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Place list in a sub-list of item...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | // Examples:
 | 
					
						
							|  |  |  | // 	make.nest('literal header', [
 | 
					
						
							|  |  |  | // 		'literal item',
 | 
					
						
							|  |  |  | // 		make('item'),
 | 
					
						
							|  |  |  | // 		...
 | 
					
						
							|  |  |  | // 	])
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	make.nest(make('header'), [
 | 
					
						
							|  |  |  | // 		'literal item',
 | 
					
						
							|  |  |  | // 		make('item'),
 | 
					
						
							|  |  |  | // 		...
 | 
					
						
							|  |  |  | // 	])
 | 
					
						
							|  |  |  | // 	
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | Items.nest = function(item, list, options){ | 
					
						
							|  |  |  | 	options = options || {} | 
					
						
							| 
									
										
										
										
											2019-03-20 16:54:10 +03:00
										 |  |  | 	//options = Object.assign(Object.create(this.options || {}), options || {})
 | 
					
						
							| 
									
										
										
										
											2019-05-28 03:56:33 +03:00
										 |  |  | 	options = Object.assign({}, | 
					
						
							|  |  |  | 		{ children: list instanceof Array ? | 
					
						
							|  |  |  | 			collectItems(this, list) | 
					
						
							|  |  |  | 			: list }, | 
					
						
							|  |  |  | 		options) | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 	return item === this ? | 
					
						
							|  |  |  | 		((this.last().children = options.children), this) | 
					
						
							|  |  |  | 		: this(item, options) | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-01-24 05:32:21 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-22 07:10:49 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							| 
									
										
										
										
											2019-06-22 06:03:44 +03:00
										 |  |  | // Buttons...
 | 
					
						
							|  |  |  | var buttons = Items.buttons = {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-22 18:24:43 +03:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-06-23 00:14:45 +03:00
										 |  |  | // 	Draw checked checkboz is <attr> is true...
 | 
					
						
							| 
									
										
										
										
											2019-06-22 18:24:43 +03:00
										 |  |  | // 	Checkbox('attr')
 | 
					
						
							| 
									
										
										
										
											2019-06-23 00:14:45 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // 	Draw checked checkboz is <attr> is false...
 | 
					
						
							| 
									
										
										
										
											2019-06-22 18:24:43 +03:00
										 |  |  | // 	Checkbox('!attr')
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // XXX rename -- distinguish from actual button...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | buttons.Checkbox = function(item, attr){ | 
					
						
							|  |  |  | 	return (attr[0] == '!'  | 
					
						
							|  |  |  | 				&& !item[attr.slice(1)])  | 
					
						
							|  |  |  | 			|| item[attr] ?  | 
					
						
							|  |  |  | 		'☐'  | 
					
						
							|  |  |  | 		: '☑' }  | 
					
						
							| 
									
										
										
										
											2019-06-22 18:06:39 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 00:14:45 +03:00
										 |  |  | // XXX can we make these not use the same icon...
 | 
					
						
							| 
									
										
										
										
											2019-06-22 18:06:39 +03:00
										 |  |  | buttons.ToggleDisabled = [ | 
					
						
							| 
									
										
										
										
											2019-06-22 18:24:43 +03:00
										 |  |  | 	'Checkbox: "disabled"', | 
					
						
							| 
									
										
										
										
											2019-06-22 06:03:44 +03:00
										 |  |  | 	'toggleDisabled: item', | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 	true, | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		alt: 'Disable/enable item', | 
					
						
							|  |  |  | 		cls: 'toggle-disabled', | 
					
						
							|  |  |  | 	}] | 
					
						
							| 
									
										
										
										
											2019-06-22 06:03:44 +03:00
										 |  |  | buttons.ToggleHidden = [ | 
					
						
							| 
									
										
										
										
											2019-06-22 18:24:43 +03:00
										 |  |  | 	'Checkbox: "hidden"', | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 	'toggleHidden: item', | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		alt: 'Show/hide item', | 
					
						
							|  |  |  | 		cls: 'toggle-hidden', | 
					
						
							|  |  |  | 	}] | 
					
						
							| 
									
										
										
										
											2019-06-22 06:03:44 +03:00
										 |  |  | buttons.ToggleSelected = [ | 
					
						
							| 
									
										
										
										
											2019-06-22 18:24:43 +03:00
										 |  |  | 	'Checkbox: "selected"', | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 	'toggleSelect: item', | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		alt: 'Select/deselect item', | 
					
						
							|  |  |  | 		cls: 'toggle-select', | 
					
						
							|  |  |  | 	}] | 
					
						
							| 
									
										
										
										
											2019-06-23 00:14:45 +03:00
										 |  |  | // NOTE: this button is disabled for all items but the ones with .children...
 | 
					
						
							|  |  |  | buttons.ToggleCollapse = [ | 
					
						
							|  |  |  | 	function(item){ | 
					
						
							|  |  |  | 		return !item.children ? | 
					
						
							|  |  |  | 				// placeholder...
 | 
					
						
							|  |  |  | 				' ' | 
					
						
							|  |  |  | 			: item.collapsed ? | 
					
						
							|  |  |  | 				'+' | 
					
						
							|  |  |  | 			: '-' }, | 
					
						
							|  |  |  | 	'toggleCollapse: item', | 
					
						
							|  |  |  | 	// disable button for all items that do not have children...
 | 
					
						
							|  |  |  | 	function(item){  | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 		return 'children' in item }, | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		alt: 'Collapse/expand item', | 
					
						
							|  |  |  | 		cls: function(item){  | 
					
						
							|  |  |  | 			return 'children' in item ?  | 
					
						
							|  |  |  | 				'toggle-collapse'  | 
					
						
							|  |  |  | 				: ['toggle-collapse', 'blank'] }, | 
					
						
							|  |  |  | 	}] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX delete button -- requires .markDelete(..) action...
 | 
					
						
							|  |  |  | buttons.Delete = [ | 
					
						
							|  |  |  | 	'×', | 
					
						
							|  |  |  | 	'markDelete: item', | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		alt: 'Mark item for deletion', | 
					
						
							|  |  |  | 		cls: 'toggle-delete', | 
					
						
							|  |  |  | 		//keys: ['Delete', 'd'],
 | 
					
						
							|  |  |  | 	}] | 
					
						
							| 
									
										
										
										
											2019-06-22 06:03:44 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-22 18:06:39 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | // Wrappers...
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-29 02:41:17 +03:00
										 |  |  | // this is here for uniformity...
 | 
					
						
							| 
									
										
										
										
											2019-05-27 17:36:57 +03:00
										 |  |  | Items.Item = function(value, options){ return this(...arguments) } | 
					
						
							| 
									
										
										
										
											2019-05-29 02:41:17 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-27 03:09:55 +03:00
										 |  |  | Items.Empty = function(value){} | 
					
						
							| 
									
										
										
										
											2019-05-27 17:36:57 +03:00
										 |  |  | Items.Separator = function(){ return this('---') } | 
					
						
							|  |  |  | Items.Spinner = function(){ return this('...') } | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | Items.Heading = function(value, options){ | 
					
						
							|  |  |  | 	var cls = 'heading' | 
					
						
							|  |  |  | 	options = options || {} | 
					
						
							|  |  |  | 	options.cls = options.cls instanceof Array ?  | 
					
						
							|  |  |  | 			options.cls.concat([cls]) | 
					
						
							|  |  |  | 		: typeof(options.cls) == typeof('str') ? | 
					
						
							|  |  |  | 			options.cls +' '+ cls | 
					
						
							|  |  |  | 		: [cls] | 
					
						
							|  |  |  | 	return this(value, options) } | 
					
						
							| 
									
										
										
										
											2019-01-20 05:39:47 +03:00
										 |  |  | Items.Selected = function(value){} | 
					
						
							| 
									
										
										
										
											2019-06-22 18:06:39 +03:00
										 |  |  | Items.Action = function(value, options){} | 
					
						
							| 
									
										
										
										
											2019-01-20 05:39:47 +03:00
										 |  |  | Items.ConfirmAction = function(value){} | 
					
						
							| 
									
										
										
										
											2019-06-22 18:06:39 +03:00
										 |  |  | Items.Editable = function(value){} | 
					
						
							| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // lists...
 | 
					
						
							| 
									
										
										
										
											2019-02-05 18:11:36 +03:00
										 |  |  | Items.List = function(values){} | 
					
						
							| 
									
										
										
										
											2019-01-20 05:39:47 +03:00
										 |  |  | Items.EditableList = function(values){} | 
					
						
							|  |  |  | Items.EditablePinnedList = function(values){} | 
					
						
							| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-20 05:39:47 +03:00
										 |  |  | // Special list components...
 | 
					
						
							| 
									
										
										
										
											2019-05-27 01:40:04 +03:00
										 |  |  | //Items.ListPath = function(){}
 | 
					
						
							|  |  |  | //Items.ListTitle = function(){}
 | 
					
						
							| 
									
										
										
										
											2019-01-20 05:39:47 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | // XXX EXPERIMENTAL...
 | 
					
						
							|  |  |  | // XXX get keys from options...
 | 
					
						
							|  |  |  | Items.Confirm = function(message, accept, reject, options){ | 
					
						
							|  |  |  | 	return this(message,  | 
					
						
							|  |  |  | 		Object.assign({ | 
					
						
							|  |  |  | 			// XXX should the user be able to merge buttons from options???
 | 
					
						
							|  |  |  | 			buttons: [ | 
					
						
							|  |  |  | 				...[reject instanceof Function ? | 
					
						
							|  |  |  | 					['$Cancel', reject] | 
					
						
							|  |  |  | 					: []], | 
					
						
							|  |  |  | 				...[accept instanceof Function ? | 
					
						
							|  |  |  | 					['$OK', accept] | 
					
						
							|  |  |  | 					: []], ], }, | 
					
						
							|  |  |  | 			options || {})) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | // Generators...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // A generator is a function that creates 1 or more elements and sets up
 | 
					
						
							|  |  |  | // the appropriate interactions...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // NOTE: these can work both as item generators called from inside 
 | 
					
						
							|  |  |  | // 		.make(..), i.e. as methods of the make constructor, or as
 | 
					
						
							|  |  |  | // 		generators assigned to .__header__ / .__items__ / .__footer__
 | 
					
						
							|  |  |  | // 		attributes...
 | 
					
						
							|  |  |  | // NOTE: when re-using these options.id needs to be set so as not to 
 | 
					
						
							|  |  |  | // 		overwrite existing instances data and handlers...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-01 04:47:31 +03:00
										 |  |  | // Make item generator...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | Items.makeDisplayItem = function(text, options){ | 
					
						
							|  |  |  | 	var args = [...arguments] | 
					
						
							|  |  |  | 	return function(make, options){ | 
					
						
							|  |  |  | 		make(...args) } } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Make confirm item generator...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | // XXX move this to Item.Confirm(..) and reuse that...
 | 
					
						
							| 
									
										
										
										
											2019-07-01 04:47:31 +03:00
										 |  |  | Items.makeDisplayConfirm = function(message, accept, reject){ | 
					
						
							|  |  |  | 	return this.makeDisplayItem(message, { | 
					
						
							|  |  |  | 		buttons: [ | 
					
						
							|  |  |  | 			...[reject instanceof Function ? | 
					
						
							|  |  |  | 				['Cancel', reject] | 
					
						
							|  |  |  | 				: []], | 
					
						
							|  |  |  | 			...[accept instanceof Function ? | 
					
						
							|  |  |  | 				['OK', accept] | 
					
						
							|  |  |  | 				: []], ], }) } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | // Focused item path...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // XXX add search/filter field...
 | 
					
						
							|  |  |  | // XXX add path navigation...
 | 
					
						
							|  |  |  | Items.DisplayFocusedPath = function(make, options){ | 
					
						
							|  |  |  | 	options = make instanceof Function ? | 
					
						
							|  |  |  | 		options | 
					
						
							|  |  |  | 		: make | 
					
						
							|  |  |  | 	options = options || {} | 
					
						
							|  |  |  | 	make = make instanceof Function ? | 
					
						
							|  |  |  | 		make | 
					
						
							|  |  |  | 		: this | 
					
						
							|  |  |  | 	var dialog = this.dialog || this | 
					
						
							|  |  |  | 	var tag = options.id || 'item_path_display' | 
					
						
							|  |  |  | 	// indicator...
 | 
					
						
							|  |  |  | 	var e = make('CURRENT_PATH', { | 
					
						
							|  |  |  | 			id: tag, | 
					
						
							| 
									
										
										
										
											2019-07-01 04:47:31 +03:00
										 |  |  | 			cls: 'path',  | 
					
						
							|  |  |  | 		})  | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 		.last() | 
					
						
							|  |  |  | 	// event handlers...
 | 
					
						
							|  |  |  | 	dialog  | 
					
						
							|  |  |  | 		.off('*', tag) | 
					
						
							|  |  |  | 		.on('focus',  | 
					
						
							|  |  |  | 			function(){ | 
					
						
							|  |  |  | 				e.value = this.pathArray | 
					
						
							|  |  |  | 				this.renderItem(e) }, | 
					
						
							|  |  |  | 			tag) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Item info...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Show item .info or .alt text.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This will show info for items that are:
 | 
					
						
							|  |  |  | // 	- focused
 | 
					
						
							|  |  |  | // 	- hovered (not yet implemented)
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // XXX use focused elements and not just item...
 | 
					
						
							|  |  |  | // XXX add on mouse over...
 | 
					
						
							|  |  |  | Items.DisplayItemInfo = function(make, options){ | 
					
						
							|  |  |  | 	options = make instanceof Function ? | 
					
						
							|  |  |  | 		options | 
					
						
							|  |  |  | 		: make | 
					
						
							|  |  |  | 	options = options || {} | 
					
						
							|  |  |  | 	make = make instanceof Function ? | 
					
						
							|  |  |  | 		make | 
					
						
							|  |  |  | 		: this | 
					
						
							|  |  |  | 	var dialog = this.dialog || this | 
					
						
							|  |  |  | 	var tag = options.id || 'item_info_display' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// indicator...
 | 
					
						
							|  |  |  | 	var e = make('INFO', { | 
					
						
							|  |  |  | 			id: tag, | 
					
						
							|  |  |  | 			cls: 'info', | 
					
						
							| 
									
										
										
										
											2019-07-01 04:47:31 +03:00
										 |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 		.last() | 
					
						
							|  |  |  | 	// event handlers...
 | 
					
						
							|  |  |  | 	dialog | 
					
						
							|  |  |  | 		.off('*', tag) | 
					
						
							|  |  |  | 		.on('focus', | 
					
						
							|  |  |  | 			function(){ | 
					
						
							|  |  |  | 				var focused = this.focused | 
					
						
							|  |  |  | 				e.value = focused.info  | 
					
						
							|  |  |  | 					|| focused.alt | 
					
						
							|  |  |  | 					|| ' ' | 
					
						
							|  |  |  | 				this.renderItem(e) }, | 
					
						
							|  |  |  | 		tag) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Item...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | var BaseItemClassPrototype = { | 
					
						
							|  |  |  | 	text: function(elem){ | 
					
						
							|  |  |  | 		var txt = elem.value instanceof Array ? | 
					
						
							|  |  |  | 				elem.value.join(' ')	 | 
					
						
							|  |  |  | 			: elem.value == null || elem.value instanceof Object ? | 
					
						
							|  |  |  | 				elem.alt || elem.__id  | 
					
						
							|  |  |  | 			: elem.value | 
					
						
							|  |  |  | 		return txt }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var BaseItemPrototype = { | 
					
						
							| 
									
										
										
										
											2019-06-23 17:32:14 +03:00
										 |  |  | 	parent: null, | 
					
						
							|  |  |  | 	 | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 	// children: null,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// id: null,
 | 
					
						
							|  |  |  | 	// value: null,
 | 
					
						
							|  |  |  | 	// alt: null,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 	// dom: null,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 	// focused: null,
 | 
					
						
							|  |  |  | 	// disabled: null,
 | 
					
						
							|  |  |  | 	// selected: null,
 | 
					
						
							|  |  |  | 	// collapsed: null,
 | 
					
						
							| 
									
										
										
										
											2019-06-23 17:32:14 +03:00
										 |  |  | 	 | 
					
						
							|  |  |  | 	get id(){ | 
					
						
							|  |  |  | 		return this.__id || this.text }, | 
					
						
							|  |  |  | 	set id(value){ | 
					
						
							|  |  |  | 		this.__id = value }, | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	get text(){ | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 		return this.constructor.text(this) }, | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-24 05:38:05 +03:00
										 |  |  | 	// NOTE: we are intentionally not including .index here as there are 
 | 
					
						
							|  |  |  | 	// 		multiple ways to get and index...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 21:18:43 +03:00
										 |  |  | 	get pathArray(){ | 
					
						
							|  |  |  | 		var p = this.parent | 
					
						
							|  |  |  | 		while(p.parent instanceof BaseBrowser){ | 
					
						
							|  |  |  | 			p = p.parent } | 
					
						
							|  |  |  | 		return p ?  | 
					
						
							|  |  |  | 			p.pathOf(this) | 
					
						
							|  |  |  | 			: undefined }, | 
					
						
							|  |  |  | 	get path(){ | 
					
						
							|  |  |  | 		return this.pathArray.join('/') }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 	__init__(...state){ | 
					
						
							|  |  |  | 		Object.assign(this, ...state) }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | var BaseItem =  | 
					
						
							|  |  |  | module.BaseItem =  | 
					
						
							|  |  |  | object.makeConstructor('BaseItem',  | 
					
						
							|  |  |  | 	BaseItemClassPrototype, | 
					
						
							|  |  |  | 	BaseItemPrototype) | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-21 05:29:26 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2019-05-24 00:18:53 +03:00
										 |  |  | // Event system parts and helpers...
 | 
					
						
							| 
									
										
										
										
											2019-05-22 23:28:38 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // XXX might be a good idea to make this a generic module...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-24 00:18:53 +03:00
										 |  |  | // Base event object...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-05-23 16:07:57 +03:00
										 |  |  | var BrowserEvent = | 
					
						
							|  |  |  | module.BrowserEvent =  | 
					
						
							|  |  |  | object.makeConstructor('BrowserEvent',  | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	// event name...
 | 
					
						
							|  |  |  | 	name: undefined, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	data: undefined, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	propagationStopped: false, | 
					
						
							|  |  |  | 	stopPropagation: function(){ | 
					
						
							|  |  |  | 		this.propagationStopped = true }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-23 19:41:07 +03:00
										 |  |  | 	// XXX not used....
 | 
					
						
							|  |  |  | 	defaultPrevented: false, | 
					
						
							|  |  |  | 	preventDefault: function(){ | 
					
						
							|  |  |  | 		this.defaultPrevented = true }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-23 16:07:57 +03:00
										 |  |  | 	__init__: function(name, ...data){ | 
					
						
							|  |  |  | 		// sanity check...
 | 
					
						
							|  |  |  | 		if(arguments.length < 1){ | 
					
						
							|  |  |  | 			throw new Error('new BrowserEvent(..): ' | 
					
						
							|  |  |  | 				+'at least event name must be passed as argument.') } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.name = name | 
					
						
							|  |  |  | 		this.data = data.length > 0 ?  | 
					
						
							|  |  |  | 			data  | 
					
						
							|  |  |  | 			: undefined | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-21 05:29:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | // 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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-25 03:34:21 +03:00
										 |  |  | // Generate an event method...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | // 	Make and event method...
 | 
					
						
							| 
									
										
										
										
											2019-05-27 01:40:04 +03:00
										 |  |  | // 	makeEventMethod(event_name)
 | 
					
						
							| 
									
										
										
										
											2019-06-01 00:39:29 +03:00
										 |  |  | // 	makeEventMethod(event_name, handler[, retrigger])
 | 
					
						
							|  |  |  | // 	makeEventMethod(event_name, handler, action[, retrigger])
 | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | // 		-> event_method
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This will produce an event method that supports binding handlers to the
 | 
					
						
							|  |  |  | // event (shorthand to: .on(event, handler, ...)) and triggering the 
 | 
					
						
							|  |  |  | // said event (similar to: .trigger(event, ..) )...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-02-25 03:34:21 +03:00
										 |  |  | //	Trigger an event
 | 
					
						
							|  |  |  | //	.event()
 | 
					
						
							|  |  |  | //	.event(arg, ..)
 | 
					
						
							|  |  |  | //		-> this
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //	Bind an event handler...
 | 
					
						
							|  |  |  | //	.event(func)
 | 
					
						
							|  |  |  | //		-> this
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | var makeEventMethod =  | 
					
						
							|  |  |  | module.makeEventMethod = | 
					
						
							| 
									
										
										
										
											2019-06-01 00:39:29 +03:00
										 |  |  | function(event, handler, action, retrigger){ | 
					
						
							|  |  |  | 	var args = [...arguments].slice(2) | 
					
						
							|  |  |  | 	action = (args[0] !== true && args[0] !== false) ?  | 
					
						
							|  |  |  | 		args.shift()  | 
					
						
							|  |  |  | 		: null | 
					
						
							|  |  |  | 	retrigger = args.pop() !== false | 
					
						
							| 
									
										
										
										
											2019-05-23 16:07:57 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 	return eventMethod(event, function(item){ | 
					
						
							|  |  |  | 		// register handler...
 | 
					
						
							|  |  |  | 		if(item instanceof Function){ | 
					
						
							|  |  |  | 			return this.on(event, item)  | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-02-24 14:51:35 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 		var evt = new BrowserEvent(event) | 
					
						
							| 
									
										
										
										
											2019-02-26 04:20:05 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-01 00:39:29 +03:00
										 |  |  | 		// main handler...
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 		handler | 
					
						
							|  |  |  | 			&& handler.call(this, evt, ...arguments) | 
					
						
							| 
									
										
										
										
											2019-02-24 14:51:35 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-01 00:39:29 +03:00
										 |  |  | 		// trigger the bound handlers...
 | 
					
						
							|  |  |  | 		retrigger | 
					
						
							|  |  |  | 			&& this.trigger(evt, ...arguments) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// default action...
 | 
					
						
							|  |  |  | 		action | 
					
						
							|  |  |  | 			&& !evt.defaultPrevented | 
					
						
							|  |  |  | 			&& action.call(this, evt, ...arguments) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 	}) } | 
					
						
							| 
									
										
										
										
											2019-02-24 14:51:35 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-24 00:18:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 05:00:15 +03:00
										 |  |  | // Call item event handlers...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	callItemEventHandlers(item, event_name, event_object, ...)
 | 
					
						
							|  |  |  | // 		-> null
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | var callItemEventHandlers =  | 
					
						
							|  |  |  | function(item, event, evt, ...args){ | 
					
						
							| 
									
										
										
										
											2019-05-23 19:41:07 +03:00
										 |  |  | 	evt = evt || new BrowserEvent(event) | 
					
						
							|  |  |  | 	// get the relevant handlers...
 | 
					
						
							| 
									
										
										
										
											2019-03-02 17:29:26 +03:00
										 |  |  | 	;(item[event] ? | 
					
						
							|  |  |  | 			[item[event]] | 
					
						
							|  |  |  | 			: []) | 
					
						
							|  |  |  | 		.concat((item.events || {})[event] || []) | 
					
						
							| 
									
										
										
										
											2019-05-23 19:41:07 +03:00
										 |  |  | 		// call the handlers...
 | 
					
						
							| 
									
										
										
										
											2019-03-02 17:29:26 +03:00
										 |  |  | 		.forEach(function(handler){ | 
					
						
							| 
									
										
										
										
											2019-05-23 16:07:57 +03:00
										 |  |  | 			handler.call(item, evt, item, ...args) }) | 
					
						
							|  |  |  | 	// propagate the event...
 | 
					
						
							| 
									
										
										
										
											2019-05-23 19:41:07 +03:00
										 |  |  | 	// NOTE: .parent of items in an array container is the first actual
 | 
					
						
							|  |  |  | 	// 		browser container up the tree, so we do not need to skip
 | 
					
						
							|  |  |  | 	// 		non-browser parents...
 | 
					
						
							| 
									
										
										
										
											2019-05-23 16:07:57 +03:00
										 |  |  | 	item.parent | 
					
						
							| 
									
										
										
										
											2019-05-23 19:41:07 +03:00
										 |  |  | 		&& item.parent.trigger | 
					
						
							| 
									
										
										
										
											2019-05-23 16:21:08 +03:00
										 |  |  | 		&& item.parent.trigger(evt, item, ...args) } | 
					
						
							| 
									
										
										
										
											2019-03-02 17:29:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-24 00:18:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 05:00:15 +03:00
										 |  |  | // Generate item event method...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-05-27 01:40:04 +03:00
										 |  |  | // 	makeItemEventMethod(event_name)
 | 
					
						
							| 
									
										
										
										
											2019-06-15 18:49:41 +03:00
										 |  |  | // 	makeItemEventMethod(event_name, {handler, default_getter, filter, options, getter})
 | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | // 		-> event_method
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-05-16 05:00:15 +03:00
										 |  |  | // This extends makeEventMethod(..) by adding an option to pass an item
 | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | // when triggering the event and if no item is passed to produce a default,
 | 
					
						
							|  |  |  | // the rest of the signature is identical...
 | 
					
						
							| 
									
										
										
										
											2019-05-16 05:00:15 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // 	Trigger an event on item(s)...
 | 
					
						
							|  |  |  | // 	.event(item, ..)
 | 
					
						
							|  |  |  | // 	.event([item, ..], ..)
 | 
					
						
							|  |  |  | // 		-> this
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-06-10 17:18:55 +03:00
										 |  |  | // 	Trigger event on empty list of items...
 | 
					
						
							|  |  |  | // 	.event(null, ..)
 | 
					
						
							|  |  |  | // 	.event([], ..)
 | 
					
						
							|  |  |  | // 		-> this
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-05-24 00:18:53 +03:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-05-27 01:40:04 +03:00
										 |  |  | // 	Handle event action...
 | 
					
						
							|  |  |  | // 	handler(event_object, items, ...)
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	Get default item if none are given...
 | 
					
						
							|  |  |  | // 	default_getter()
 | 
					
						
							|  |  |  | // 		-> item
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	Check item applicability...
 | 
					
						
							|  |  |  | // 	filter(item)
 | 
					
						
							|  |  |  | // 		-> bool
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-07-01 20:13:20 +03:00
										 |  |  | // options format:
 | 
					
						
							|  |  |  | // 	{
 | 
					
						
							|  |  |  | // 		// if true unresolved items will not trigger the event unless the
 | 
					
						
							|  |  |  | // 		// input was null/undefined...
 | 
					
						
							| 
									
										
										
										
											2019-07-01 20:20:30 +03:00
										 |  |  | // 		// default: true
 | 
					
						
							| 
									
										
										
										
											2019-07-01 20:13:20 +03:00
										 |  |  | // 		skipUnresolved: <bool>,
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 		...
 | 
					
						
							|  |  |  | // 	}
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-05-16 05:00:15 +03:00
										 |  |  | // NOTE: item is compatible to .search(item, ..) spec, see that for more 
 | 
					
						
							|  |  |  | // 		details...
 | 
					
						
							| 
									
										
										
										
											2019-05-23 16:21:08 +03:00
										 |  |  | // NOTE: triggering an event that matches several items will handle each 
 | 
					
						
							| 
									
										
										
										
											2019-05-24 00:18:53 +03:00
										 |  |  | // 		item-parent chain individually, and independently when propagating
 | 
					
						
							|  |  |  | // 		the event up...
 | 
					
						
							| 
									
										
										
										
											2019-05-23 19:43:01 +03:00
										 |  |  | // NOTE: a parent that contains multiple items will get triggered multiple 
 | 
					
						
							|  |  |  | // 		times, once per each item...
 | 
					
						
							| 
									
										
										
										
											2019-05-23 19:41:07 +03:00
										 |  |  | // 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.
 | 
					
						
							| 
									
										
										
										
											2019-06-24 05:38:05 +03:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-06-15 18:49:41 +03:00
										 |  |  | // XXX destructuring: should default_item get .focused??? 
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | var makeItemEventMethod =  | 
					
						
							|  |  |  | module.makeItemEventMethod = | 
					
						
							| 
									
										
										
										
											2019-06-15 18:49:41 +03:00
										 |  |  | function(event, {handler, action, default_item, filter, options={}, getter='search'}={}){ | 
					
						
							| 
									
										
										
										
											2019-05-26 19:23:40 +03:00
										 |  |  | 	var filterItems = function(items){ | 
					
						
							| 
									
										
										
										
											2019-05-27 19:33:49 +03:00
										 |  |  | 		items = items instanceof Array ?  | 
					
						
							|  |  |  | 				items  | 
					
						
							|  |  |  | 			: items === undefined ? | 
					
						
							|  |  |  | 				[] | 
					
						
							|  |  |  | 			: [items] | 
					
						
							| 
									
										
										
										
											2019-05-26 19:23:40 +03:00
										 |  |  | 		return filter ?  | 
					
						
							| 
									
										
										
										
											2019-06-30 01:42:41 +03:00
										 |  |  | 			items.filter(filter.bind(this))  | 
					
						
							| 
									
										
										
										
											2019-05-26 19:23:40 +03:00
										 |  |  | 			: items } | 
					
						
							| 
									
										
										
										
											2019-06-25 17:41:12 +03:00
										 |  |  | 	// options constructor...
 | 
					
						
							|  |  |  | 	var makeOptions = function(){ | 
					
						
							|  |  |  | 		return Object.assign( | 
					
						
							|  |  |  | 			{  | 
					
						
							|  |  |  | 				// NOTE: we need to be able to pass item objects, so we can not
 | 
					
						
							|  |  |  | 				// 		use queries at the same time as there is not way to 
 | 
					
						
							|  |  |  | 				// 		distinguish one from the other...
 | 
					
						
							|  |  |  | 				noQueryCheck: true,  | 
					
						
							|  |  |  | 				skipDisabled: true, | 
					
						
							| 
									
										
										
										
											2019-07-01 20:20:30 +03:00
										 |  |  | 				// XXX should this be the default...
 | 
					
						
							|  |  |  | 				skipUnresolved: true, | 
					
						
							| 
									
										
										
										
											2019-06-25 17:41:12 +03:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 			options instanceof Function ?  | 
					
						
							|  |  |  | 				options.call(this)  | 
					
						
							|  |  |  | 				: options) } | 
					
						
							| 
									
										
										
										
											2019-05-22 23:28:38 +03:00
										 |  |  | 	// base event method...
 | 
					
						
							| 
									
										
										
										
											2019-05-14 16:36:28 +03:00
										 |  |  | 	// NOTE: this is not returned directly as we need to query the items
 | 
					
						
							|  |  |  | 	// 		and pass those on to the handlers rather than the arguments 
 | 
					
						
							|  |  |  | 	// 		as-is...
 | 
					
						
							| 
									
										
										
										
											2019-05-22 23:28:38 +03:00
										 |  |  | 	var base = makeEventMethod(event,  | 
					
						
							| 
									
										
										
										
											2019-05-14 16:36:28 +03:00
										 |  |  | 		function(evt, item, ...args){ | 
					
						
							|  |  |  | 			handler | 
					
						
							| 
									
										
										
										
											2019-05-15 21:45:20 +03:00
										 |  |  | 				&& handler.call(this, evt, item.slice(), ...args) | 
					
						
							| 
									
										
										
										
											2019-05-14 16:36:28 +03:00
										 |  |  | 			item.forEach(function(item){ | 
					
						
							| 
									
										
										
										
											2019-05-23 19:41:07 +03:00
										 |  |  | 				// NOTE: we ignore the root event here and force each 
 | 
					
						
							|  |  |  | 				// 		item chain to create it's own new event object...
 | 
					
						
							|  |  |  | 				// 		this will isolate each chain from the others in 
 | 
					
						
							|  |  |  | 				// 		state and handling propagation...
 | 
					
						
							| 
									
										
										
										
											2019-06-06 17:16:36 +03:00
										 |  |  | 				callItemEventHandlers(item, event, evt, ...args) }) }, | 
					
						
							| 
									
										
										
										
											2019-06-01 00:39:29 +03:00
										 |  |  | 		...(action ? [action] : []), | 
					
						
							| 
									
										
										
										
											2019-05-23 16:07:57 +03:00
										 |  |  | 		false)  | 
					
						
							| 
									
										
										
										
											2019-06-25 17:41:12 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// build the options statically if we can...
 | 
					
						
							|  |  |  | 	options = options instanceof Function ? | 
					
						
							|  |  |  | 		options | 
					
						
							|  |  |  | 		: makeOptions() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-18 02:58:38 +03:00
										 |  |  | 	return Object.assign( | 
					
						
							| 
									
										
										
										
											2019-05-22 23:28:38 +03:00
										 |  |  | 		// the actual method we return...
 | 
					
						
							| 
									
										
										
										
											2019-05-18 02:58:38 +03:00
										 |  |  | 		function(item, ...args){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							| 
									
										
										
										
											2019-06-25 17:41:12 +03:00
										 |  |  | 			// build the options dynamically if needed...
 | 
					
						
							|  |  |  | 			var opts = options instanceof Function ? | 
					
						
							|  |  |  | 				makeOptions.call(this) | 
					
						
							|  |  |  | 				: options | 
					
						
							| 
									
										
										
										
											2019-07-01 20:13:20 +03:00
										 |  |  | 			var skipUnresolved = opts.skipUnresolved | 
					
						
							|  |  |  | 			var resolved =  | 
					
						
							| 
									
										
										
										
											2019-05-18 02:58:38 +03:00
										 |  |  | 				// event handler...
 | 
					
						
							|  |  |  | 				item instanceof Function ? | 
					
						
							|  |  |  | 					item | 
					
						
							|  |  |  | 				// array of queries...
 | 
					
						
							|  |  |  | 				: item instanceof Array ? | 
					
						
							| 
									
										
										
										
											2019-06-30 01:42:41 +03:00
										 |  |  | 					filterItems.call(this, item | 
					
						
							| 
									
										
										
										
											2019-05-18 02:58:38 +03:00
										 |  |  | 						.map(function(e){ | 
					
						
							| 
									
										
										
										
											2019-06-25 17:41:12 +03:00
										 |  |  | 							return that.search(e, opts) }) | 
					
						
							| 
									
										
										
										
											2019-05-18 02:58:38 +03:00
										 |  |  | 						.flat() | 
					
						
							| 
									
										
										
										
											2019-05-26 19:23:40 +03:00
										 |  |  | 						.unique()) | 
					
						
							| 
									
										
										
										
											2019-05-18 02:58:38 +03:00
										 |  |  | 				// explicit item or query...
 | 
					
						
							|  |  |  | 				: item != null ?  | 
					
						
							| 
									
										
										
										
											2019-06-30 01:42:41 +03:00
										 |  |  | 					filterItems.call(this, this[getter](item, opts)) | 
					
						
							| 
									
										
										
										
											2019-06-10 17:18:55 +03:00
										 |  |  | 				// item is undefined -- get default...
 | 
					
						
							|  |  |  | 				: item !== null && default_item instanceof Function ? | 
					
						
							| 
									
										
										
										
											2019-05-26 03:38:38 +03:00
										 |  |  | 					[default_item.call(that) || []].flat() | 
					
						
							| 
									
										
										
										
											2019-06-10 17:18:55 +03:00
										 |  |  | 				// item is null (explicitly) or other...
 | 
					
						
							| 
									
										
										
										
											2019-07-01 20:13:20 +03:00
										 |  |  | 				: [] | 
					
						
							|  |  |  | 			return (skipUnresolved  | 
					
						
							|  |  |  | 					&& resolved.length == 0  | 
					
						
							|  |  |  | 					&& item != null) ? | 
					
						
							|  |  |  | 				// skip unresolved...
 | 
					
						
							|  |  |  | 				this | 
					
						
							|  |  |  | 				: base.call(this,  | 
					
						
							|  |  |  | 					resolved, | 
					
						
							|  |  |  | 					...args) }, | 
					
						
							| 
									
										
										
										
											2019-05-22 23:28:38 +03:00
										 |  |  | 			// get base method attributes -- keep the event method format...
 | 
					
						
							| 
									
										
										
										
											2019-05-26 03:38:38 +03:00
										 |  |  |    			base) } | 
					
						
							| 
									
										
										
										
											2019-03-02 17:29:26 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | // Make event method edit item...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // XXX should this .update()
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | var makeItemEditEventMethod = | 
					
						
							|  |  |  | module.makeItemEditEventMethod = | 
					
						
							|  |  |  | function(event, edit, {handler, default_item, filter, options}={}){ | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | 	return makeItemEventMethod(event, { | 
					
						
							|  |  |  | 		handler: function(evt, items){ | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 			var that = this | 
					
						
							|  |  |  | 			items.forEach(function(item){ | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 				edit(item) | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 				handler | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 					&& handler.call(that, item) }) }, | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | 		default_item:  | 
					
						
							|  |  |  | 			default_item  | 
					
						
							|  |  |  | 				|| function(){ return this.focused }, | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 		filter, | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | 		options, }) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Make event method to toggle item attr on/off...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | var makeItemOptionOnEventMethod = | 
					
						
							|  |  |  | module.makeItemOptionOnEventMethod = | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | function(event, attr, {handler, default_item, filter, options}={}){ | 
					
						
							|  |  |  | 	return makeItemEditEventMethod(event, | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | 		function(item){ | 
					
						
							|  |  |  | 			return item[attr] = true }, | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 		{ handler, default_item, filter, options }) } | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | var makeItemOptionOffEventMethod = | 
					
						
							|  |  |  | module.makeItemOptionOffEventMethod = | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | function(event, attr, {handler, default_item, filter, options}={}){ | 
					
						
							|  |  |  | 	return makeItemEditEventMethod(event, | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | 		function(item){ | 
					
						
							|  |  |  | 			change = !!item[attr] | 
					
						
							|  |  |  | 			delete item[attr] | 
					
						
							|  |  |  | 			return change }, | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 		{ handler, default_item, filter, options }) } | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-26 18:55:24 +03:00
										 |  |  | // Generate item event/state toggler...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-05-27 05:09:01 +03:00
										 |  |  | // XXX should this make a toggler.Toggler???
 | 
					
						
							|  |  |  | // XXX BUG: the generated toggler in multi mode handles query arrays inconsistently...
 | 
					
						
							| 
									
										
										
										
											2019-05-26 18:55:24 +03:00
										 |  |  | // 		- [] is always returned...
 | 
					
						
							|  |  |  | // 		- .toggleSelect([1, 2, 10, 20]) -- toggles items on only, returns []
 | 
					
						
							|  |  |  | // 		- .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 []
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | var makeItemEventToggler =  | 
					
						
							|  |  |  | module.makeItemEventToggler =  | 
					
						
							|  |  |  | function(get_state, set_state, unset_state, default_item, multi, options){ | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | 	var _get_state = get_state instanceof Function ? | 
					
						
							|  |  |  | 		get_state | 
					
						
							|  |  |  | 		: function(e){ return !!e[get_state] } | 
					
						
							|  |  |  | 	var _set_state = set_state instanceof Function ? | 
					
						
							|  |  |  | 		set_state | 
					
						
							|  |  |  | 		: function(e){ return !!this[set_state](e) } | 
					
						
							|  |  |  | 	var _unset_state = unset_state instanceof Function ? | 
					
						
							|  |  |  | 		unset_state | 
					
						
							|  |  |  | 		: function(e){ return !this[unset_state](e) } | 
					
						
							|  |  |  | 	var _default_item = default_item instanceof Function ? | 
					
						
							|  |  |  | 		default_item | 
					
						
							|  |  |  | 		: function(){ return this[default_item] } | 
					
						
							| 
									
										
										
										
											2019-05-26 20:20:18 +03:00
										 |  |  | 	// filter/multi...
 | 
					
						
							|  |  |  | 	var filter = multi instanceof Function | 
					
						
							|  |  |  | 		&& multi | 
					
						
							|  |  |  | 	var filterItems = function(items){ | 
					
						
							|  |  |  | 		return filter ?  | 
					
						
							| 
									
										
										
										
											2019-06-30 01:42:41 +03:00
										 |  |  | 			items.filter(filter.bind(this))  | 
					
						
							| 
									
										
										
										
											2019-05-26 20:20:18 +03:00
										 |  |  | 			: items } | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | 	multi = multi !== false | 
					
						
							|  |  |  | 	var getter = multi ? 'search' : 'get' | 
					
						
							| 
									
										
										
										
											2019-05-26 20:20:18 +03:00
										 |  |  | 	options = Object.assign( | 
					
						
							|  |  |  | 		// NOTE: we need to be able to pass item objects, so we can not
 | 
					
						
							|  |  |  | 		// 		use queries at the same time as there is not way to 
 | 
					
						
							|  |  |  | 		// 		distinguish one from the other...
 | 
					
						
							|  |  |  | 		{ noQueryCheck: true }, | 
					
						
							|  |  |  | 		options || {}) | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// state normalization lookup table...
 | 
					
						
							|  |  |  | 	var states = { | 
					
						
							|  |  |  | 		true: true,  | 
					
						
							|  |  |  | 		on: true, | 
					
						
							|  |  |  | 		false: false,  | 
					
						
							|  |  |  | 		off: false, | 
					
						
							|  |  |  | 		// only two states, so next/prev are the same...
 | 
					
						
							|  |  |  | 		prev: 'next',  | 
					
						
							|  |  |  | 		next: 'next', | 
					
						
							|  |  |  | 		'?': '?',  | 
					
						
							|  |  |  | 		'??': '??',  | 
					
						
							|  |  |  | 		'!': '!', | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-27 01:49:47 +03:00
										 |  |  | 	return (function eventToggler(item, state){ | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | 		var that = this | 
					
						
							|  |  |  | 		// normalize/parse args...
 | 
					
						
							|  |  |  | 		state = item in states ? | 
					
						
							|  |  |  | 			item  | 
					
						
							|  |  |  | 			: state | 
					
						
							| 
									
										
										
										
											2019-06-10 17:18:55 +03:00
										 |  |  | 		item = state === item ?  | 
					
						
							|  |  |  | 				undefined  | 
					
						
							|  |  |  | 				: item  | 
					
						
							|  |  |  | 		item = item === undefined ? | 
					
						
							|  |  |  | 			_default_item.call(this) | 
					
						
							|  |  |  | 			: item | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | 		state = state in states ?  | 
					
						
							|  |  |  | 			states[state]  | 
					
						
							|  |  |  | 			: 'next' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return [  | 
					
						
							|  |  |  | 				state == '??' ? | 
					
						
							|  |  |  | 					[true, false] | 
					
						
							|  |  |  | 				: item == null ? | 
					
						
							|  |  |  | 					false	 | 
					
						
							|  |  |  | 				: state == '?' ? | 
					
						
							| 
									
										
										
										
											2019-06-30 01:42:41 +03:00
										 |  |  | 					filterItems.call(this, | 
					
						
							| 
									
										
										
										
											2019-05-26 20:20:18 +03:00
										 |  |  | 						[this[getter](item, options)] | 
					
						
							|  |  |  | 							.flat()) | 
					
						
							|  |  |  | 							.map(_get_state) | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | 				: state === true ? | 
					
						
							|  |  |  | 					_set_state.call(this, item) | 
					
						
							|  |  |  | 				: state == false ?  | 
					
						
							|  |  |  | 					_unset_state.call(this, item) | 
					
						
							| 
									
										
										
										
											2019-05-26 20:20:18 +03:00
										 |  |  | 				// 'next' or '!'...
 | 
					
						
							|  |  |  | 				// NOTE: 'next' and '!' are opposites of each other...
 | 
					
						
							| 
									
										
										
										
											2019-06-30 01:42:41 +03:00
										 |  |  | 				: filterItems.call(this, | 
					
						
							| 
									
										
										
										
											2019-05-26 20:20:18 +03:00
										 |  |  | 					[this[getter](item, options)] | 
					
						
							|  |  |  | 						.flat()) | 
					
						
							|  |  |  | 						.map(function(e){ | 
					
						
							|  |  |  | 							return (state == 'next' ?  | 
					
						
							|  |  |  | 									_get_state(e) | 
					
						
							|  |  |  | 									: !_get_state(e)) ? | 
					
						
							|  |  |  | 								_unset_state.call(that, e) | 
					
						
							|  |  |  | 								: _set_state.call(that, e) })  | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | 			] | 
					
						
							|  |  |  | 			.flat() | 
					
						
							|  |  |  | 			// normalize for single item results -> return item and array...
 | 
					
						
							|  |  |  | 			.run(function(){ | 
					
						
							|  |  |  | 				return this.length == 1 ?  | 
					
						
							|  |  |  | 					this[0]  | 
					
						
							| 
									
										
										
										
											2019-05-27 01:49:47 +03:00
										 |  |  | 					: this }) }) | 
					
						
							|  |  |  | 		// support instanceof Toggler tests...
 | 
					
						
							|  |  |  | 		.run(function(){ | 
					
						
							|  |  |  | 			this.__proto__ = toggler.Toggler.prototype | 
					
						
							|  |  |  | 			this.constructor = toggler.Toggler })} | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | // XXX this is incomplete...
 | 
					
						
							| 
									
										
										
										
											2019-05-26 18:55:24 +03:00
										 |  |  | var makeItemEventToggler2 = function(get_state, set_state, unset_state, default_item, multi){ | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | 	var _get_state = get_state instanceof Function ? | 
					
						
							|  |  |  | 		get_state | 
					
						
							|  |  |  | 		: function(e){ return !!e[get_state] } | 
					
						
							|  |  |  | 	var _set_state = set_state instanceof Function ? | 
					
						
							|  |  |  | 		set_state | 
					
						
							|  |  |  | 		: function(e){ return !!this[set_state](e) } | 
					
						
							|  |  |  | 	var _unset_state = unset_state instanceof Function ? | 
					
						
							|  |  |  | 		unset_state | 
					
						
							|  |  |  | 		: function(e){ return !this[unset_state](e) } | 
					
						
							| 
									
										
										
										
											2019-05-26 18:55:24 +03:00
										 |  |  | 	multi = multi !== false | 
					
						
							|  |  |  | 	var getter = multi ? 'search' : 'get' | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return toggler.Toggler( | 
					
						
							|  |  |  | 		default_item, | 
					
						
							|  |  |  | 		function(item, state){ | 
					
						
							|  |  |  | 			if(item == null){ | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return state == null ? | 
					
						
							|  |  |  | 				_get_state(item) | 
					
						
							|  |  |  | 				: state | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		[true, false], | 
					
						
							|  |  |  | 		function(state, item){ | 
					
						
							|  |  |  | 			// if no item focused/given return false...
 | 
					
						
							|  |  |  | 			return item == null ?  | 
					
						
							|  |  |  | 					false  | 
					
						
							|  |  |  | 				// XXX add support for item lists...
 | 
					
						
							|  |  |  | 				: state ? | 
					
						
							|  |  |  | 	   				_set_state.call(this, item) | 
					
						
							|  |  |  | 				: _unset_state.call(this, item) }) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-25 03:34:21 +03:00
										 |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | var BaseBrowserClassPrototype = { | 
					
						
							| 
									
										
										
										
											2019-01-24 05:32:21 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | // XXX Q: should we be able to add/remove/change items outside of .__items__(..)???
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | // 		...only some item updates (how .collapsed is handled) make 
 | 
					
						
							|  |  |  | // 		sense at this time -- need to think about this more 
 | 
					
						
							|  |  |  | // 		carefully + strictly document the result...
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | var BaseBrowserPrototype = { | 
					
						
							| 
									
										
										
										
											2019-02-28 20:26:22 +03:00
										 |  |  | 	options: { | 
					
						
							| 
									
										
										
										
											2019-06-30 23:09:06 +03:00
										 |  |  | 		// List of sections to make...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// default: ['header', 'items', 'footer']
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 		sections: [ | 
					
						
							|  |  |  | 			'header', | 
					
						
							|  |  |  | 			'items', | 
					
						
							| 
									
										
										
										
											2019-06-29 16:09:32 +03:00
										 |  |  | 			'footer', | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 		], | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-30 23:09:06 +03:00
										 |  |  | 		// If true allows disabled items to be focused...
 | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 		focusDisabledItems: false, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-30 23:09:06 +03:00
										 |  |  | 		// If true allows focus to shift into header/footer...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// XXX needs more work and testing....
 | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 		allowSecondaySectionFocus: false, | 
					
						
							| 
									
										
										
										
											2019-06-25 17:41:12 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 		// If true item keys must be unique...
 | 
					
						
							|  |  |  | 		uniqueKeys: false, | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-30 23:09:06 +03:00
										 |  |  | 		// Controls how the disabled sub-tree root elements are skipped...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// Can be:
 | 
					
						
							|  |  |  | 		// 	'node'		- skip only the disabled node (default)
 | 
					
						
							|  |  |  | 		// 	'branch'	- skip whole branch, i.e. all nested elements.
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// XXX if this is 'branch' we should also either show all the 
 | 
					
						
							|  |  |  | 		// 		nested elements as disabled or outright disable them,
 | 
					
						
							|  |  |  | 		// 		otherwise they can still be focused via clicking and other
 | 
					
						
							|  |  |  | 		// 		means...
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 		//skipDisabledMode: 'node',
 | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-30 23:09:06 +03:00
										 |  |  | 		// Minimum number of milliseconds between updates...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// This works in the following manner:
 | 
					
						
							|  |  |  | 		// 	- for 10 consecutive calls:
 | 
					
						
							|  |  |  | 		// 		- call (first) 
 | 
					
						
							|  |  |  | 		// 			-> triggered right away
 | 
					
						
							|  |  |  | 		// 		- call (within timeout)
 | 
					
						
							|  |  |  | 		// 			-> schedule after timeout
 | 
					
						
							|  |  |  | 		// 		- call (within timeout)
 | 
					
						
							|  |  |  | 		// 			-> drop previous scheduled call
 | 
					
						
							|  |  |  | 		// 			-> schedule after timeout
 | 
					
						
							|  |  |  | 		// 		- ...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// Essentially this prevents more than one call to .update(..) 
 | 
					
						
							|  |  |  | 		// within the timeout and more than two calls within a fast call
 | 
					
						
							|  |  |  | 		// sequence...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2019-07-01 03:22:40 +03:00
										 |  |  | 		// NOTE: the full options to .update(..) is remembered even if the
 | 
					
						
							|  |  |  | 		// 		update was deferred the next update either after the timeout
 | 
					
						
							|  |  |  | 		// 		or max timeout will be full retaining the passed options...
 | 
					
						
							|  |  |  | 		// NOTE: the delayed update is called with the same set of arguments
 | 
					
						
							|  |  |  | 		// 		as the last update call of that type (full / non-full).
 | 
					
						
							|  |  |  | 		// NOTE: this does not care about other semantics of the .update(..)
 | 
					
						
							|  |  |  | 		// 		calls it drops (i.e. the options passed), only the first 
 | 
					
						
							| 
									
										
										
										
											2019-06-30 23:09:06 +03:00
										 |  |  | 		// 		and last call in sequence get actually called.
 | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 		updateTimeout: 30, | 
					
						
							| 
									
										
										
										
											2019-07-01 04:03:38 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Maximum time between .update(..) calls when calling updates 
 | 
					
						
							| 
									
										
										
										
											2019-06-30 23:09:06 +03:00
										 |  |  | 		// in sequence...
 | 
					
						
							|  |  |  | 		updateMaxDelay: 200, | 
					
						
							| 
									
										
										
										
											2019-06-24 05:13:49 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-24 15:57:40 +03:00
										 |  |  | 		// Item templates...
 | 
					
						
							| 
									
										
										
										
											2019-06-24 05:13:49 +03:00
										 |  |  | 		//
 | 
					
						
							|  |  |  | 		// Format:
 | 
					
						
							|  |  |  | 		// 	{
 | 
					
						
							| 
									
										
										
										
											2019-06-24 15:57:40 +03:00
										 |  |  | 		// 		// Default item template...
 | 
					
						
							|  |  |  | 		// 		//
 | 
					
						
							|  |  |  | 		// 		// This will be added to all items, including ones that
 | 
					
						
							| 
									
										
										
										
											2019-06-30 23:09:06 +03:00
										 |  |  | 		// 		// directly match another template template...
 | 
					
						
							| 
									
										
										
										
											2019-06-24 15:57:40 +03:00
										 |  |  | 		// 		'*': <item>,
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 		// Normal item template...
 | 
					
						
							| 
									
										
										
										
											2019-06-24 05:13:49 +03:00
										 |  |  | 		// 		<key>: <item>,
 | 
					
						
							|  |  |  | 		// 		...
 | 
					
						
							|  |  |  | 		// 	}
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// If make(..) gets passed <key> it will construct the base element
 | 
					
						
							|  |  |  | 		// via <item> and merge the item options into that.
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// <item> format is the same as the format passed to make(..)
 | 
					
						
							| 
									
										
										
										
											2019-06-24 15:57:40 +03:00
										 |  |  | 		//
 | 
					
						
							|  |  |  | 		// XXX should we have an ability to "blank-out" some items?
 | 
					
						
							|  |  |  | 		// 		...i.e. do not create an item matching a template in 
 | 
					
						
							|  |  |  | 		// 		certain context...
 | 
					
						
							|  |  |  | 		// 		No, currently this is not needed.
 | 
					
						
							|  |  |  | 		itemTemplate: {}, | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// If not null these indicate the name of the generator to use, 
 | 
					
						
							|  |  |  | 		// when  the client does not supply the corresponding function 
 | 
					
						
							|  |  |  | 		// (i.e. Items[name])
 | 
					
						
							|  |  |  | 		defaultHeader: null, | 
					
						
							|  |  |  | 		defaultFooter: null, | 
					
						
							| 
									
										
										
										
											2019-02-28 20:26:22 +03:00
										 |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-01-24 05:32:21 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Props and introspection...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-06 15:25:58 +03:00
										 |  |  | 	// parent widget object...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-03-06 15:38:37 +03:00
										 |  |  | 	// NOTE: this may or may not be a Browser object.
 | 
					
						
							| 
									
										
										
										
											2019-03-05 18:15:49 +03:00
										 |  |  | 	parent: null, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 	// Section containers...
 | 
					
						
							| 
									
										
										
										
											2019-02-16 04:25:16 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | 	// Format:
 | 
					
						
							|  |  |  | 	// 	[
 | 
					
						
							|  |  |  | 	// 		<item> | <browser>,
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	]
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// <item> format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							| 
									
										
										
										
											2019-01-25 20:19:33 +03:00
										 |  |  | 	// 		value: ...,
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-02-16 04:25:16 +03:00
										 |  |  | 	// NOTE: this can't be a map/dict as we need both order manipulation 
 | 
					
						
							|  |  |  | 	// 		and nested structures which would overcomplicate things, as 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 05:00:15 +03:00
										 |  |  | 	// 		a compromise we use .index below for item identification.
 | 
					
						
							| 
									
										
										
										
											2019-06-27 03:09:55 +03:00
										 |  |  | 	__header: null, | 
					
						
							|  |  |  | 	get header(){ | 
					
						
							|  |  |  | 		this.__header | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 			|| ((this.__header__  | 
					
						
							|  |  |  | 					|| Items[this.options.defaultHeader]) | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 				&& this.make({section: 'header'})) | 
					
						
							|  |  |  | 		return this.__header || [] }, | 
					
						
							| 
									
										
										
										
											2019-06-27 03:09:55 +03:00
										 |  |  | 	set header(value){ | 
					
						
							|  |  |  | 		this.__header = value }, | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 	__items: null, | 
					
						
							|  |  |  | 	get items(){ | 
					
						
							|  |  |  | 		this.__items | 
					
						
							|  |  |  | 			|| this.make() | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 		return this.__items }, | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 	set items(value){ | 
					
						
							|  |  |  | 		this.__items = value }, | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 	__footer: null, | 
					
						
							|  |  |  | 	get footer(){ | 
					
						
							|  |  |  | 		this.__footer | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 			|| ((this.__footer__  | 
					
						
							|  |  |  | 					|| Items[this.options.defaultFooter]) | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 				&& this.make({section: 'footer'})) | 
					
						
							|  |  |  | 		return this.__footer || [] }, | 
					
						
							|  |  |  | 	set footer(value){ | 
					
						
							|  |  |  | 		this.__footer = value }, | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Clear cached data...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-31 00:07:36 +03:00
										 |  |  | 	// 	Clear all cache data...
 | 
					
						
							|  |  |  | 	// 	.clearCache()
 | 
					
						
							|  |  |  | 	// 		-> this
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Clear specific cache data...
 | 
					
						
							|  |  |  | 	// 	.clearCache(title)
 | 
					
						
							|  |  |  | 	// 	.clearCache(title, ..)
 | 
					
						
							|  |  |  | 	// 	.clearCache([title, ..])
 | 
					
						
							|  |  |  | 	// 		-> this
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 	// This will delete all attributes of the format:
 | 
					
						
							|  |  |  | 	// 	.__<title>_cache
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-23 21:18:43 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Clear all cache data including generated items...
 | 
					
						
							|  |  |  | 	// 	.clearCache(true)
 | 
					
						
							|  |  |  | 	// 		-> this
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: .clearCache(true) will yield a state that would require at 
 | 
					
						
							|  |  |  | 	// 		least a .update() call to be usable...
 | 
					
						
							| 
									
										
										
										
											2019-05-31 00:07:36 +03:00
										 |  |  | 	clearCache: function(title){ | 
					
						
							| 
									
										
										
										
											2019-06-23 21:18:43 +03:00
										 |  |  | 		if(title == null || title === true){ | 
					
						
							| 
									
										
										
										
											2019-05-31 00:07:36 +03:00
										 |  |  | 			Object.keys(this) | 
					
						
							|  |  |  | 				.forEach(function(key){ | 
					
						
							|  |  |  | 					if(key.startsWith('__') && key.endsWith('_cache')){ | 
					
						
							|  |  |  | 						delete this[key] | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				}.bind(this))  | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			[...arguments].flat() | 
					
						
							|  |  |  | 				.forEach(function(title){ | 
					
						
							|  |  |  | 					delete this[`__${title}_cache`] | 
					
						
							|  |  |  | 				}.bind(this)) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-06-23 21:18:43 +03:00
										 |  |  | 		if(title === true){ | 
					
						
							|  |  |  | 			delete this.__items | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-30 16:51:10 +03:00
										 |  |  | 	// Item index...
 | 
					
						
							| 
									
										
										
										
											2019-02-16 04:25:16 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// Format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							| 
									
										
										
										
											2019-05-30 16:51:10 +03:00
										 |  |  | 	// 		"<path>": <item>,
 | 
					
						
							|  |  |  | 	// 		// repeating path...
 | 
					
						
							|  |  |  | 	// 		"<path>:<count>": <item>,
 | 
					
						
							| 
									
										
										
										
											2019-02-16 04:25:16 +03:00
										 |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 	// NOTE: this will get overwritten each time .make(..) is called.
 | 
					
						
							| 
									
										
										
										
											2019-05-31 00:07:36 +03:00
										 |  |  | 	// NOTE: .make(..) will also set item's .id where this will add a 
 | 
					
						
							|  |  |  | 	// 		count to the path...
 | 
					
						
							|  |  |  | 	// 		This will also make re-generating the indexes and searching
 | 
					
						
							|  |  |  | 	// 		stable...
 | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX for some odd reason this is sorted wrong...
 | 
					
						
							|  |  |  | 	// 		...keys that are numbers for some reason are first and sorted 
 | 
					
						
							|  |  |  | 	// 		by value and not by position...
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 	__item_index_cache: null, | 
					
						
							| 
									
										
										
										
											2019-05-16 05:00:15 +03:00
										 |  |  | 	get index(){ | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 		return (this.__item_index_cache =  | 
					
						
							|  |  |  | 			this.__item_index_cache  | 
					
						
							| 
									
										
										
										
											2019-05-30 16:51:10 +03:00
										 |  |  | 				|| this | 
					
						
							|  |  |  | 					.reduce(function(index, e, i, p){ | 
					
						
							|  |  |  | 						var id = p = p.join('/') | 
					
						
							|  |  |  | 						var c = 0 | 
					
						
							|  |  |  | 						// generate a unique id...
 | 
					
						
							|  |  |  | 						// NOTE: no need to check if e.id is unique as we already 
 | 
					
						
							|  |  |  | 						// 		did this in make(..)...
 | 
					
						
							|  |  |  | 						while(id in index){ | 
					
						
							|  |  |  | 							id = this.__id__(p, ++c) | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						index[id] = e | 
					
						
							|  |  |  | 						return index | 
					
						
							|  |  |  | 					}.bind(this), {}, {iterateAll: true})) }, | 
					
						
							| 
									
										
										
										
											2019-02-16 04:25:16 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-30 16:51:10 +03:00
										 |  |  | 	// Flat item index...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// Format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							|  |  |  | 	// 		"<key>": <item>,
 | 
					
						
							|  |  |  | 	// 		// repeating keys...
 | 
					
						
							|  |  |  | 	// 		"<key>:<count>": <item>,
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX should this be cached???
 | 
					
						
							| 
									
										
										
										
											2019-05-29 16:27:14 +03:00
										 |  |  | 	get flatIndex(){ | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 		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}) }, | 
					
						
							| 
									
										
										
										
											2019-05-29 16:27:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 	// Shorthands for common item queries...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX should these be cached???
 | 
					
						
							| 
									
										
										
										
											2019-02-28 20:13:01 +03:00
										 |  |  | 	get focused(){ | 
					
						
							| 
									
										
										
										
											2019-05-13 18:58:17 +03:00
										 |  |  | 		return this.get('focused') }, | 
					
						
							| 
									
										
										
										
											2019-02-28 20:13:01 +03:00
										 |  |  | 	set focused(value){ | 
					
						
							| 
									
										
										
										
											2019-05-13 18:58:17 +03:00
										 |  |  | 		this.focus(value) }, | 
					
						
							| 
									
										
										
										
											2019-02-28 20:13:01 +03:00
										 |  |  | 	get selected(){ | 
					
						
							| 
									
										
										
										
											2019-05-13 18:58:17 +03:00
										 |  |  | 		return this.search('selected') }, | 
					
						
							| 
									
										
										
										
											2019-02-28 20:13:01 +03:00
										 |  |  | 	set selected(value){ | 
					
						
							| 
									
										
										
										
											2019-05-30 20:00:09 +03:00
										 |  |  | 		this | 
					
						
							|  |  |  | 			.deselect('selected') | 
					
						
							|  |  |  | 			.select(value) }, | 
					
						
							| 
									
										
										
										
											2019-02-28 20:13:01 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:32:21 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	// XXX should this return a list or a string???
 | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 	// XXX should this be cached???
 | 
					
						
							|  |  |  | 	// XXX should this set .options???
 | 
					
						
							|  |  |  | 	// XXX need to normalizePath(..)
 | 
					
						
							|  |  |  | 	// 		...array .value is not compliant with POLS
 | 
					
						
							|  |  |  | 	get path(){ | 
					
						
							| 
									
										
										
										
											2019-06-26 19:41:42 +03:00
										 |  |  | 		return (this.pathArray || []).join('/') }, | 
					
						
							|  |  |  | 	set path(value){ | 
					
						
							|  |  |  | 		this.load(value) }, | 
					
						
							| 
									
										
										
										
											2019-06-27 02:39:33 +03:00
										 |  |  | 	// XXX do we make this writable???
 | 
					
						
							| 
									
										
										
										
											2019-06-26 19:41:42 +03:00
										 |  |  | 	get pathArray(){ | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 		return this.__items != null ? | 
					
						
							|  |  |  | 			this.get('focused',  | 
					
						
							| 
									
										
										
										
											2019-06-26 19:41:42 +03:00
										 |  |  | 				function(e, i, p){ return p })  | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 			// XXX do we use .options.path???
 | 
					
						
							| 
									
										
										
										
											2019-06-26 19:41:42 +03:00
										 |  |  | 			// XXX is this an array???
 | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 			: (this.options || {}).path }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-17 21:54:26 +03:00
										 |  |  | 	// Length...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// visible only...
 | 
					
						
							| 
									
										
										
										
											2019-03-14 00:45:33 +03:00
										 |  |  | 	get length(){ | 
					
						
							| 
									
										
										
										
											2019-05-09 02:28:06 +03:00
										 |  |  | 		return this.map().length }, | 
					
						
							| 
									
										
										
										
											2019-05-10 21:07:17 +03:00
										 |  |  | 	// include collapsed elements...
 | 
					
						
							| 
									
										
										
										
											2019-03-17 21:54:26 +03:00
										 |  |  | 	get lengthTree(){ | 
					
						
							| 
									
										
										
										
											2019-05-09 02:28:06 +03:00
										 |  |  | 		return this.map({iterateCollapsed: true}).length }, | 
					
						
							| 
									
										
										
										
											2019-05-10 21:07:17 +03:00
										 |  |  | 	// include non-iterable elements...
 | 
					
						
							| 
									
										
										
										
											2019-03-17 21:54:26 +03:00
										 |  |  | 	get lengthAll(){ | 
					
						
							| 
									
										
										
										
											2019-05-09 02:28:06 +03:00
										 |  |  | 		return this.map({iterateAll: true}).length }, | 
					
						
							| 
									
										
										
										
											2019-03-14 00:45:33 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	// Configuration / Extension...
 | 
					
						
							|  |  |  | 	 | 
					
						
							|  |  |  | 	// Key getter/generator...
 | 
					
						
							|  |  |  | 	__key__: function(item){ | 
					
						
							|  |  |  | 		return item.id  | 
					
						
							| 
									
										
										
										
											2019-06-23 17:32:14 +03:00
										 |  |  | 			|| item.text  | 
					
						
							|  |  |  | 			|| this.__id__() }, | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// ID generator...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.__id__()
 | 
					
						
							|  |  |  | 	// 	.__id__(prefix)
 | 
					
						
							|  |  |  | 	// 	.__id__(prefix, count)
 | 
					
						
							|  |  |  | 	// 		-> id
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// Format:
 | 
					
						
							|  |  |  | 	// 	"<date>"
 | 
					
						
							|  |  |  | 	// 	"<prefix>:<count>"
 | 
					
						
							|  |  |  | 	// 	"<prefix>:<date>"
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX not sure about the logic of this, should this take an item as 
 | 
					
						
							|  |  |  | 	// 		input and return an id???
 | 
					
						
							|  |  |  | 	// 		...should this check for uniqueness???
 | 
					
						
							|  |  |  | 	// 		think merging this with any of the actual ID generators would be best...
 | 
					
						
							|  |  |  | 	__id__: function(prefix, count){ | 
					
						
							|  |  |  | 		return prefix ? | 
					
						
							|  |  |  | 			// id prefix...
 | 
					
						
							|  |  |  | 			//`${prefix} (${count || Date.now()})`
 | 
					
						
							|  |  |  | 			`${prefix}:${count || Date.now()}` | 
					
						
							|  |  |  | 			// plain id...
 | 
					
						
							|  |  |  | 			: `item${Date.now()}` }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Data generation (make)...
 | 
					
						
							|  |  |  | 	 | 
					
						
							| 
									
										
										
										
											2019-06-24 05:38:05 +03:00
										 |  |  | 	__item__: BaseItem, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 	// Section item list constructor...
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:32:21 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 	// 	.__header__(make, options)
 | 
					
						
							|  |  |  | 	// 	.__items__(make, options)
 | 
					
						
							|  |  |  | 	// 	.__footer__(make, options)
 | 
					
						
							| 
									
										
										
										
											2019-01-26 20:09:36 +03:00
										 |  |  | 	// 		-> undefined
 | 
					
						
							|  |  |  | 	// 		-> list
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:32:21 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-02-16 04:25:16 +03:00
										 |  |  | 	// 	Item constructor:
 | 
					
						
							|  |  |  | 	// 		make(value)
 | 
					
						
							|  |  |  | 	// 		make(value, options)
 | 
					
						
							|  |  |  | 	// 			-> make
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// There are two modes of operation:
 | 
					
						
							|  |  |  | 	// 	1) call make(..) to create items
 | 
					
						
							|  |  |  | 	// 	2) return a list of items
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-02-16 04:25:16 +03:00
										 |  |  | 	// The if make(..) is called at least once the return value is 
 | 
					
						
							|  |  |  | 	// ignored (mode #1), otherwise, the returned list is used as the 
 | 
					
						
							|  |  |  | 	// .items structure.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// When calling make(..) (mode #1) the item is built by combining 
 | 
					
						
							|  |  |  | 	// the following in order:
 | 
					
						
							|  |  |  | 	// 	- original item (.items[key]) if present,
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 	// 	- options passed to .make(<options>) method calling .__items__(..),
 | 
					
						
							| 
									
										
										
										
											2019-02-16 04:25:16 +03:00
										 |  |  | 	// 	- options passed to make(.., <options>) constructing the item,
 | 
					
						
							|  |  |  | 	// 	- {value: <value>} where <value> passed to make(<value>, ..)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// Each of the above will override values of the previous sections.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// The resulting item is stored in:
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 	// 	.header, .items or .footer
 | 
					
						
							| 
									
										
										
										
											2019-05-16 05:00:15 +03:00
										 |  |  | 	// 	.index (keyed via .id or JSONified .value)
 | 
					
						
							| 
									
										
										
										
											2019-02-16 04:25:16 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// Each of the above structures is reset on each call to .make(..)
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-03-12 16:05:51 +03:00
										 |  |  | 	// options format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							|  |  |  | 	// 		id: <string>,
 | 
					
						
							|  |  |  | 	// 		value: <string> | <array>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 	// 		children: <browser> | <array>,
 | 
					
						
							| 
									
										
										
										
											2019-03-12 16:05:51 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		focused: <bool>,
 | 
					
						
							|  |  |  | 	// 		selected: <bool>,
 | 
					
						
							|  |  |  | 	// 		disabled: <bool>,
 | 
					
						
							|  |  |  | 	// 		noniterable: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		// Set automatically...
 | 
					
						
							|  |  |  | 	// 		parent: <browser>,
 | 
					
						
							|  |  |  | 	// 		// XXX move this to the appropriate object...
 | 
					
						
							|  |  |  | 	// 		dom: <dom>,
 | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//		...
 | 
					
						
							| 
									
										
										
										
											2019-03-12 16:05:51 +03:00
										 |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 	// Example:
 | 
					
						
							|  |  |  | 	// 	XXX
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-02-16 04:25:16 +03:00
										 |  |  | 	// In mode #2 XXX
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 	// NOTE: this is not designed to be called directly...
 | 
					
						
							| 
									
										
										
										
											2019-06-27 03:33:57 +03:00
										 |  |  | 	__header__: null, | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 	__items__: function(make, options){ | 
					
						
							|  |  |  | 		throw new Error('.__items__(..): Not implemented.') }, | 
					
						
							|  |  |  | 	__footer__: null, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-27 03:33:57 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	// Make extension...
 | 
					
						
							| 
									
										
										
										
											2019-03-02 20:38:08 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 	// This is called per item created by make(..) in .__items__(..)
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this can update/modify the item but it can not replace it.
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 	//__make__: function(section, item){
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	//},
 | 
					
						
							| 
									
										
										
										
											2019-03-02 20:38:08 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-02 01:50:04 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	// Make .items and .index...
 | 
					
						
							| 
									
										
										
										
											2019-03-02 01:50:04 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	// 	.make()
 | 
					
						
							|  |  |  | 	// 	.make(options)
 | 
					
						
							|  |  |  | 	// 		-> this
 | 
					
						
							| 
									
										
										
										
											2019-05-29 16:27:14 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 	// The items are constructed by passing a make function to .__items__(..)
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	// which in turn will call this make(..) per item created.
 | 
					
						
							| 
									
										
										
										
											2019-03-02 01:50:04 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	// For more doc on item construction see: .__init__(..)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: each call to this will reset both .items and .index
 | 
					
						
							|  |  |  | 	// NOTE: for items with repeating values there is no way to correctly 
 | 
					
						
							|  |  |  | 	// 		identify an item thus no state is maintained between .make(..)
 | 
					
						
							|  |  |  | 	// 		calls for such items...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 	// XXX revise options handling for .__items__(..)
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	// XXX might be a good idea to enable the user to merge the state 
 | 
					
						
							|  |  |  | 	// 		manually...
 | 
					
						
							|  |  |  | 	// 		one way to do:
 | 
					
						
							|  |  |  | 	// 			- get the previous item via an index, 
 | 
					
						
							|  |  |  | 	// 			- update it
 | 
					
						
							|  |  |  | 	// 			- pass it to make(..)
 | 
					
						
							|  |  |  | 	// 		Example:
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 	// 			// just a rough example in .__items__(..)...
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	// 			make(value, 
 | 
					
						
							|  |  |  | 	// 				value in this.index ? 
 | 
					
						
							|  |  |  | 	// 					Object.assign(
 | 
					
						
							|  |  |  | 	// 						this.index[value], 
 | 
					
						
							|  |  |  | 	// 						opts) 
 | 
					
						
							|  |  |  | 	// 					: opts)
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 	// XXX revise if stage 2 is applicable to sections other than .items
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	make: function(options){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		options = Object.assign( | 
					
						
							|  |  |  | 			Object.create(this.options || {}),  | 
					
						
							|  |  |  | 			options || {}) | 
					
						
							| 
									
										
										
										
											2019-03-02 01:50:04 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 		// sections to make...
 | 
					
						
							|  |  |  | 		var sections = options.section == '*' ? | 
					
						
							|  |  |  | 			(options.sections || ['header', 'items', 'footer']) | 
					
						
							|  |  |  | 			: (options.section || 'items') | 
					
						
							|  |  |  | 		sections = (sections instanceof Array ?  | 
					
						
							|  |  |  | 				sections  | 
					
						
							|  |  |  | 				: [sections]) | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 			.map(function(name){ | 
					
						
							|  |  |  | 				return [ | 
					
						
							|  |  |  | 					name, | 
					
						
							|  |  |  | 					that[`__${name}__`]  | 
					
						
							|  |  |  | 						|| Items[options[`default${name.capitalize()}`]], | 
					
						
							|  |  |  | 				] }) | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 			// keep only sections we know how to make...
 | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 			.filter(function([_, handler]){ | 
					
						
							|  |  |  | 				return !!handler }) | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// item constructor...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	Make an item...
 | 
					
						
							|  |  |  | 		// 	make(value[, options])
 | 
					
						
							|  |  |  | 		// 	make(value, func[, options])
 | 
					
						
							|  |  |  | 		// 		-> make
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	Inline a browser instance...
 | 
					
						
							|  |  |  | 		// 	make(browser)
 | 
					
						
							|  |  |  | 		// 		-> make
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: when inlining a browser, options are ignored.
 | 
					
						
							|  |  |  | 		// NOTE: when inlining a browser it's .parent will be set this 
 | 
					
						
							|  |  |  | 		// 		reusing the inlined object browser may mess up this 
 | 
					
						
							|  |  |  | 		// 		property...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// XXX problem: make(Browser(..), ..) and make.group(...) produce 
 | 
					
						
							|  |  |  | 		// 		different formats -- the first stores {value: browser, ...}
 | 
					
						
							|  |  |  | 		// 		while the latter stores a list of items.
 | 
					
						
							|  |  |  | 		// 		...would be more logical to store the object (i.e. browser/list)
 | 
					
						
							|  |  |  | 		// 		directly as the element...
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 		var section | 
					
						
							| 
									
										
										
										
											2019-06-28 17:48:02 +03:00
										 |  |  | 		var make_called = false | 
					
						
							|  |  |  | 		var ids = new Set() | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 		var list = [] | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 		var keys = options.uniqueKeys ?  | 
					
						
							|  |  |  | 			new Set()  | 
					
						
							|  |  |  | 			: null | 
					
						
							|  |  |  | 		var make = function(value, opts){ | 
					
						
							|  |  |  | 			make_called = true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// special-case: inlined browser...
 | 
					
						
							|  |  |  | 			//
 | 
					
						
							|  |  |  | 			// NOTE: we ignore opts here...
 | 
					
						
							|  |  |  | 			// XXX not sure if this is the right way to go...
 | 
					
						
							|  |  |  | 			// 		...for removal just remove the if statement and its
 | 
					
						
							|  |  |  | 			// 		first branch...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 			if(value instanceof BaseBrowser){ | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 				var item = value | 
					
						
							|  |  |  | 				item.parent = this | 
					
						
							| 
									
										
										
										
											2019-07-01 04:47:31 +03:00
										 |  |  | 				item.section = section | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// normal item...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				var args = [...arguments] | 
					
						
							|  |  |  | 				opts = opts || {} | 
					
						
							|  |  |  | 				// handle: make(.., func, ..)
 | 
					
						
							|  |  |  | 				opts = opts instanceof Function ? | 
					
						
							|  |  |  | 					{open: opts} | 
					
						
							|  |  |  | 					: opts | 
					
						
							|  |  |  | 				// handle trailing options...
 | 
					
						
							|  |  |  | 				opts = args.length > 2 ? | 
					
						
							|  |  |  | 					Object.assign({}, | 
					
						
							|  |  |  | 						args.pop(), | 
					
						
							|  |  |  | 						opts) | 
					
						
							|  |  |  | 					: opts | 
					
						
							|  |  |  | 				opts = Object.assign( | 
					
						
							|  |  |  | 					{}, | 
					
						
							|  |  |  | 					opts,  | 
					
						
							|  |  |  | 					{value: value}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// item id...
 | 
					
						
							|  |  |  | 				var key = this.__key__(opts) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// 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...
 | 
					
						
							| 
									
										
										
										
											2019-06-24 15:57:40 +03:00
										 |  |  | 				// NOTE: we intentionally isolate the item object from 
 | 
					
						
							|  |  |  | 				// 		the input opts here, yes, having a ref to a mutable
 | 
					
						
							|  |  |  | 				// 		object may be convenient in some cases but in this
 | 
					
						
							|  |  |  | 				// 		case it would promote going around the main API...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 				var item = new this.__item__( | 
					
						
							| 
									
										
										
										
											2019-06-24 15:57:40 +03:00
										 |  |  | 					// default item template...
 | 
					
						
							|  |  |  | 					(options.itemTemplate || {})['*'] || {}, | 
					
						
							|  |  |  | 					// item template...
 | 
					
						
							|  |  |  | 					(options.itemTemplate || {})[opts.value] || {}, | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 					opts, | 
					
						
							| 
									
										
										
										
											2019-07-01 04:47:31 +03:00
										 |  |  | 					{  | 
					
						
							|  |  |  | 						parent: this,  | 
					
						
							|  |  |  | 						section, | 
					
						
							|  |  |  | 					}) | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				// XXX do we need both this and the above ref???
 | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 				item.children instanceof BaseBrowser | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 					&& (item.children.parent = this) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// user extended make...
 | 
					
						
							| 
									
										
										
										
											2019-06-28 17:48:02 +03:00
										 |  |  | 			// XXX differentiate this for header and list...
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 			this.__make__ | 
					
						
							|  |  |  | 				&& this.__make__(section, item) | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// store the item...
 | 
					
						
							| 
									
										
										
										
											2019-06-28 17:48:02 +03:00
										 |  |  | 			list.push(item) | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 			ids.add(key) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return make | 
					
						
							|  |  |  | 		}.bind(this) | 
					
						
							|  |  |  | 		make.__proto__ = Items | 
					
						
							|  |  |  | 		make.dialog = this | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 		// build the sections...
 | 
					
						
							|  |  |  | 		sections | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 			.forEach(function([name, handler]){ | 
					
						
							| 
									
										
										
										
											2019-06-28 17:48:02 +03:00
										 |  |  | 				// setup closure for make(..)...
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 				section = name | 
					
						
							| 
									
										
										
										
											2019-06-28 17:48:02 +03:00
										 |  |  | 				make_called = false | 
					
						
							|  |  |  | 				ids = new Set() | 
					
						
							|  |  |  | 				list = make.items = that[name] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// build list...
 | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 				var res = handler.call(that,  | 
					
						
							|  |  |  | 					make, | 
					
						
							| 
									
										
										
										
											2019-06-28 17:48:02 +03:00
										 |  |  | 					// XXX not sure about this -- revise options handling...
 | 
					
						
							|  |  |  | 					options ?  | 
					
						
							|  |  |  | 						Object.assign( | 
					
						
							|  |  |  | 							Object.create(that.options || {}),  | 
					
						
							|  |  |  | 							options || {})  | 
					
						
							|  |  |  | 						: null) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 				// if make was not called use the .__items__(..) return value...
 | 
					
						
							| 
									
										
										
										
											2019-06-28 17:48:02 +03:00
										 |  |  | 				that[name] = make_called ?  | 
					
						
							|  |  |  | 					that[name] | 
					
						
							|  |  |  | 					: res }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 		// reset the index/cache...
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 		// XXX should this be only for .items???
 | 
					
						
							|  |  |  | 		// 		...should this be global (all items?)
 | 
					
						
							|  |  |  | 		if(sections.includes('items')){ | 
					
						
							|  |  |  | 			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_cache = this.index | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// post process the items...
 | 
					
						
							|  |  |  | 			Object.entries(index) | 
					
						
							|  |  |  | 				.forEach(function([id, e]){ | 
					
						
							|  |  |  | 					// update item.id of items with duplicate keys...
 | 
					
						
							|  |  |  | 					!id.endsWith(that.__key__(e)) | 
					
						
							|  |  |  | 						&& (e.id = id.split(/[\/]/g).pop()) | 
					
						
							|  |  |  | 					// merge old item state...
 | 
					
						
							|  |  |  | 					id in old_index | 
					
						
							|  |  |  | 						// XXX this is not very elegant(???), revise... 
 | 
					
						
							|  |  |  | 						&& Object.assign(e, | 
					
						
							|  |  |  | 							old_index[id], | 
					
						
							|  |  |  | 							e) }) } | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Data access and iteration...
 | 
					
						
							| 
									
										
										
										
											2019-01-24 06:12:04 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-25 17:40:28 +03:00
										 |  |  | 	// Walk the browser...
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:32:21 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:05:16 +03:00
										 |  |  | 	//	Get list of nodes in tree...
 | 
					
						
							|  |  |  | 	//	.walk()
 | 
					
						
							|  |  |  | 	//		-> list
 | 
					
						
							| 
									
										
										
										
											2019-02-16 04:25:16 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:05:16 +03:00
										 |  |  | 	//	Walk the tree passing each node to func(..)
 | 
					
						
							|  |  |  | 	//	.walk(func(..)[, options])
 | 
					
						
							|  |  |  | 	//		-> list
 | 
					
						
							| 
									
										
										
										
											2019-02-16 04:25:16 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:05:16 +03:00
										 |  |  | 	//	Walk tree passing each node to func(..) using method name to 
 | 
					
						
							|  |  |  | 	//	walk nested browsers...
 | 
					
						
							|  |  |  | 	//	NOTE: 'walk' is used as name if name is not present in the object...
 | 
					
						
							|  |  |  | 	//	.walk(func(..), name, args(..)[, options])
 | 
					
						
							|  |  |  | 	//	.walk(func(..), name, args(..), walkable(..)[, options])
 | 
					
						
							|  |  |  | 	//		-> list
 | 
					
						
							| 
									
										
										
										
											2019-04-25 17:40:28 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:05:16 +03:00
										 |  |  | 	//	Walk tree passign each node to func(..) and handle nested browser 
 | 
					
						
							|  |  |  | 	//	walking in recursion(..) optionally testing if walkable with walkable(..)
 | 
					
						
							|  |  |  | 	//	.walk(func(..), recursion(..)[, options])
 | 
					
						
							|  |  |  | 	//		-> list
 | 
					
						
							| 
									
										
										
										
											2019-02-16 04:25:16 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-01-26 04:48:36 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 05:24:36 +03:00
										 |  |  | 	//	Item handler...
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:05:16 +03:00
										 |  |  | 	//	func(node, index, path, next(..), stop(..), children)
 | 
					
						
							|  |  |  | 	//		-> list
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 05:24:36 +03:00
										 |  |  | 	//	Trigger next/nested item handling...
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:05:16 +03:00
										 |  |  | 	//	next(children)
 | 
					
						
							|  |  |  | 	//		-> list
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 05:24:36 +03:00
										 |  |  | 	//	Stop handling...
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:05:16 +03:00
										 |  |  | 	//	stop(result)
 | 
					
						
							| 
									
										
										
										
											2019-04-25 17:40:28 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:05:16 +03:00
										 |  |  | 	//	Handle walkable node children (recursively)...
 | 
					
						
							|  |  |  | 	//	recursion(children, index, path, options, context, func(..), stop(..), walk())
 | 
					
						
							|  |  |  | 	//		-> list
 | 
					
						
							| 
									
										
										
										
											2019-04-25 17:40:28 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:05:16 +03:00
										 |  |  | 	//	Prepare arguments for call of name function on nested browser...
 | 
					
						
							|  |  |  | 	//	args(list, index, path, options, context, func(..), stop(..))
 | 
					
						
							|  |  |  | 	//		-> list
 | 
					
						
							| 
									
										
										
										
											2019-04-25 17:40:28 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:05:16 +03:00
										 |  |  | 	//	Test if node is walkable...
 | 
					
						
							|  |  |  | 	//	walkable(node)
 | 
					
						
							| 
									
										
										
										
											2019-04-25 17:40:28 +03:00
										 |  |  | 	//		-> bool
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:32:21 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:05:16 +03:00
										 |  |  | 	// For examples see: .text(..), .paths(..) and .map(..)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-01-26 04:38:11 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// options format:
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 	// 	{
 | 
					
						
							| 
									
										
										
										
											2019-04-26 02:28:58 +03:00
										 |  |  | 	// 		// Partial walking...
 | 
					
						
							|  |  |  | 	// 		//
 | 
					
						
							|  |  |  | 	// 		// XXX not implemented yet...
 | 
					
						
							|  |  |  | 	// 		start: <index> | <path>,
 | 
					
						
							|  |  |  | 	// 		count: <number>,
 | 
					
						
							|  |  |  | 	// 		end: <index> | <path>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// 		// Iterate ALL items...
 | 
					
						
							|  |  |  | 	// 		//
 | 
					
						
							|  |  |  | 	// 		// NOTE: this if true overrides all other iteration coverage 
 | 
					
						
							|  |  |  | 	// 		//		options... 
 | 
					
						
							|  |  |  | 	// 		iterateAll: <bool>,
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// 		// If true do not skip items with .noniterable set to true...
 | 
					
						
							|  |  |  | 	// 		iterateNonIterable: <bool>,
 | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 	// 		// If true do not skip item.children of items with .collapsed 
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// 		// set to true...
 | 
					
						
							|  |  |  | 	// 		iterateCollapsed: <bool>,
 | 
					
						
							|  |  |  | 	// 		// If true skip iterating nested items...
 | 
					
						
							|  |  |  | 	// 		skipNested: <bool>,
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-03 03:25:57 +03:00
										 |  |  | 	// 		// XXX not yet supported...
 | 
					
						
							|  |  |  | 	// 		skipInlined: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	// 		skipDisabledMode: 'node' | 'branch',
 | 
					
						
							|  |  |  | 	// 		skipDisabled: <bool> | 'node' | 'branch',
 | 
					
						
							| 
									
										
										
										
											2019-05-27 20:09:56 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-25 17:40:28 +03:00
										 |  |  | 	// 		// Reverse iteration order...
 | 
					
						
							|  |  |  | 	//		//
 | 
					
						
							|  |  |  | 	//		// modes:
 | 
					
						
							|  |  |  | 	//		//	false | null		- normal order (default)
 | 
					
						
							| 
									
										
										
										
											2019-04-25 17:50:32 +03:00
										 |  |  | 	//		//	true | 'tree'		- reverse order of levels but keep 
 | 
					
						
							| 
									
										
										
										
											2019-04-25 17:40:28 +03:00
										 |  |  | 	//		//							topology order, i.e. containers
 | 
					
						
							|  |  |  | 	//		//							will precede contained elements.
 | 
					
						
							|  |  |  | 	//		//	'flat'				- full flat reverse
 | 
					
						
							|  |  |  | 	//		//
 | 
					
						
							|  |  |  | 	//		// NOTE: in 'flat' mode the client loses control over the 
 | 
					
						
							|  |  |  | 	//		//		order of processing via doNested(..) as it will be 
 | 
					
						
							|  |  |  | 	//		//		called before handleItem(..)
 | 
					
						
							| 
									
										
										
										
											2019-04-25 18:05:17 +03:00
										 |  |  | 	// 		reverse: <bool> | 'flat' | 'tree',
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		// The value to be used if .reverse is set to true...
 | 
					
						
							|  |  |  | 	// 		defaultReverse: 'tree' (default) | 'flat',
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-03-25 21:32:30 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// 		// If true include inlined parent id in path...
 | 
					
						
							|  |  |  | 	// 		// XXX not implemented yet -- can we implement this???...
 | 
					
						
							| 
									
										
										
										
											2019-04-25 03:25:03 +03:00
										 |  |  | 	// 		// XXX do we need this??
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// 		inlinedPaths: <bool>,
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 	// NOTE: if recursion(..) is not given then .walk(..) is used to 
 | 
					
						
							| 
									
										
										
										
											2019-05-11 05:24:36 +03:00
										 |  |  | 	// 		handle all the nested elements (children)...
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 	// NOTE: if walkable(..) is not given then we check for .walk(..)
 | 
					
						
							| 
									
										
										
										
											2019-05-04 17:56:02 +03:00
										 |  |  | 	// 		availability...
 | 
					
						
							|  |  |  | 	// NOTE: children arrays are handled internally...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-02 21:53:20 +03:00
										 |  |  | 	// XXX BUG?: next(false) will not count any of the skipped elements
 | 
					
						
							|  |  |  | 	// 		thus messing up the element index...
 | 
					
						
							| 
									
										
										
										
											2019-05-06 03:42:19 +03:00
										 |  |  | 	// XXX which of the forms should be documented in the signature???
 | 
					
						
							|  |  |  | 	// 		NOTE: it does not matter which is used as we manually
 | 
					
						
							|  |  |  | 	// 		parse arguments...
 | 
					
						
							| 
									
										
										
										
											2019-05-07 18:17:03 +03:00
										 |  |  | 	// XXX passing both index directly and context containing index 
 | 
					
						
							|  |  |  | 	// 		(context.index) feels excessive...
 | 
					
						
							| 
									
										
										
										
											2019-05-07 19:19:23 +03:00
										 |  |  | 	// 			+ this is done so as to provide the user a simpler 
 | 
					
						
							|  |  |  | 	// 				.map(..)-like form...
 | 
					
						
							|  |  |  | 	// 				Ex:
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 	// 					.walk((e, i, p, next, stop) => p.join('/'))
 | 
					
						
							| 
									
										
										
										
											2019-05-07 19:19:23 +03:00
										 |  |  | 	// 					// vs.
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 	// 					.walk((e, c, next, stop) => c.path.join('/'))
 | 
					
						
							| 
									
										
										
										
											2019-05-07 19:19:23 +03:00
										 |  |  | 	// 			- two ways to get index and one to update it...
 | 
					
						
							| 
									
										
										
										
											2019-05-07 18:17:03 +03:00
										 |  |  | 	// 		...if this can produce errors we need to simplify...
 | 
					
						
							| 
									
										
										
										
											2019-05-27 20:10:56 +03:00
										 |  |  | 	// XXX add options.skip(elem) function to test elements for skipping...
 | 
					
						
							| 
									
										
										
										
											2019-05-07 19:09:05 +03:00
										 |  |  | 	// XXX add docs:
 | 
					
						
							|  |  |  | 	// 		- maintaining context to implement/extend walkers...
 | 
					
						
							|  |  |  | 	// 		- correctly stopping recursive calls (call root stop(..))
 | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 	// XXX can this be simpler???
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 	walk: function(func, recursion, walkable, options){ | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 		var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// parse args...
 | 
					
						
							|  |  |  | 		var args = [...arguments] | 
					
						
							| 
									
										
										
										
											2019-05-04 15:46:04 +03:00
										 |  |  | 		func = (args[0] instanceof Function  | 
					
						
							|  |  |  | 				|| args[0] == null) ?  | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 			args.shift()  | 
					
						
							|  |  |  | 			: undefined | 
					
						
							|  |  |  | 		var recursion = (args[0] instanceof Function  | 
					
						
							|  |  |  | 				|| typeof(args[0]) == typeof('str') | 
					
						
							|  |  |  | 				|| args[0] == null) ?  | 
					
						
							|  |  |  | 			args.shift()  | 
					
						
							|  |  |  | 			: undefined | 
					
						
							| 
									
										
										
										
											2019-05-06 03:34:40 +03:00
										 |  |  | 		var formArgs = (typeof(recursion) == typeof('str') | 
					
						
							|  |  |  | 				&& args[0] instanceof Function) ? | 
					
						
							|  |  |  | 			args.shift() | 
					
						
							|  |  |  | 			: null | 
					
						
							|  |  |  | 		// sanity check...
 | 
					
						
							|  |  |  | 		if(formArgs == null && typeof(recursion) == typeof('str')){ | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 			throw new Error(`.walk(func, name, formArgs, ..): ` | 
					
						
							| 
									
										
										
										
											2019-05-06 03:34:40 +03:00
										 |  |  | 				+`expected function as third argument, got: ${formArgs}.`) } | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 		var walkable = (!formArgs  | 
					
						
							|  |  |  | 				&& (args[0] instanceof Function  | 
					
						
							|  |  |  | 					|| args[0] == null)) ? | 
					
						
							|  |  |  | 			args.shift()  | 
					
						
							|  |  |  | 			: null  | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 		options = Object.assign( | 
					
						
							|  |  |  | 			Object.create(this.options || {}), | 
					
						
							|  |  |  | 			args.shift() || {}) | 
					
						
							| 
									
										
										
										
											2019-05-06 03:34:40 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-07 19:09:05 +03:00
										 |  |  | 		// get/build context...
 | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 		var context = args.shift() | 
					
						
							|  |  |  | 		context = context instanceof Array ?  | 
					
						
							| 
									
										
										
										
											2019-05-07 19:09:05 +03:00
										 |  |  | 			{ path: context }  | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 			: (context || {}) | 
					
						
							|  |  |  | 		context.root = context.root || this | 
					
						
							| 
									
										
										
										
											2019-05-06 19:21:22 +03:00
										 |  |  | 		context.index = context.index || 0 | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// options specifics...
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 		var sections = options.section == '*' ? | 
					
						
							|  |  |  | 			(options.sections  | 
					
						
							|  |  |  | 				|| ['header', 'items', 'footer']) | 
					
						
							|  |  |  | 			: options.section  | 
					
						
							|  |  |  | 		// NOTE: we include sections other than 'items' only for the root context...
 | 
					
						
							|  |  |  | 		sections = (sections instanceof Array  | 
					
						
							|  |  |  | 				&& context.root !== this) | 
					
						
							|  |  |  | 				&& sections.includes('items') ? | 
					
						
							|  |  |  | 			'items' | 
					
						
							|  |  |  | 			: (sections || 'items') | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 		sections = sections instanceof Array ?  | 
					
						
							|  |  |  | 			sections  | 
					
						
							|  |  |  | 			: [sections] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 		var iterateNonIterable = options.iterateAll || options.iterateNonIterable | 
					
						
							|  |  |  | 		var iterateCollapsed = options.iterateAll || options.iterateCollapsed | 
					
						
							|  |  |  | 		var skipNested = !options.iterateAll && options.skipNested | 
					
						
							| 
									
										
										
										
											2019-05-09 00:20:11 +03:00
										 |  |  | 		var skipInlined = !options.iterateAll && options.skipInlined | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-27 20:09:56 +03:00
										 |  |  | 		var skipDisabled = !options.iterateAll && options.skipDisabled | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 		skipDisabled = skipDisabled === true ?  | 
					
						
							|  |  |  | 			(options.skipDisabledMode || 'node') | 
					
						
							|  |  |  | 			: skipDisabled | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 		var reverse = options.reverse === true ? | 
					
						
							|  |  |  | 			(options.defaultReverse || 'tree') | 
					
						
							|  |  |  | 			: options.reverse | 
					
						
							| 
									
										
										
										
											2019-05-03 19:43:22 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 		var isWalkable = walkable ? | 
					
						
							|  |  |  | 			function(node){ | 
					
						
							|  |  |  | 				return node instanceof Array || walkable(node) } | 
					
						
							|  |  |  | 			: function(node){ | 
					
						
							| 
									
										
										
										
											2019-05-06 03:34:40 +03:00
										 |  |  | 				return node  | 
					
						
							|  |  |  | 					&& (node instanceof Array  | 
					
						
							|  |  |  | 						// requested method name is available...
 | 
					
						
							|  |  |  | 						|| (typeof(recursion) == typeof('str')  | 
					
						
							|  |  |  | 							&& node[recursion]) | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 						|| node.walk ) } | 
					
						
							| 
									
										
										
										
											2019-05-03 19:43:22 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		return walk( | 
					
						
							| 
									
										
										
										
											2019-06-29 07:23:08 +03:00
										 |  |  | 				function(state, node, next, stop){ | 
					
						
							|  |  |  | 					// keep only the root stop(..) -> stop the entire call tree...
 | 
					
						
							|  |  |  | 					stop = context.stop = context.stop || stop | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// skip non-iterable items...
 | 
					
						
							|  |  |  | 					if(!iterateNonIterable && node.noniterable){ | 
					
						
							|  |  |  | 						return state } | 
					
						
							|  |  |  | 					// skip disabled branch...
 | 
					
						
							|  |  |  | 					if(skipDisabled == 'branch' && node.disabled){ | 
					
						
							|  |  |  | 						return state } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// XXX BUG?: doNested(false) will not count any of the 
 | 
					
						
							|  |  |  | 					// 		skipped elements thus messing up i...
 | 
					
						
							|  |  |  | 					// 		...we can't just use .length as this would 1)
 | 
					
						
							|  |  |  | 					// 		introduce a branch in the protocol + would not
 | 
					
						
							|  |  |  | 					// 		comply with the passed options in all cases but 
 | 
					
						
							|  |  |  | 					// 		the default...
 | 
					
						
							|  |  |  | 					// 		...one way to do this is to set func to a dud
 | 
					
						
							|  |  |  | 					// 		the only problem we have is the next(..) call
 | 
					
						
							|  |  |  | 					// 		below that will call the parent function and
 | 
					
						
							|  |  |  | 					// 		mess things up... we can go around this via 
 | 
					
						
							|  |  |  | 					// 		the context (context.skipping) but this feels 
 | 
					
						
							|  |  |  | 					// 		hack-ish...
 | 
					
						
							|  |  |  | 					var nested = false | 
					
						
							|  |  |  | 					var doNested = function(list){ | 
					
						
							|  |  |  | 						// this can be called only once -> return cached results...
 | 
					
						
							|  |  |  | 						if(nested !== false){ | 
					
						
							|  |  |  | 							return nested } | 
					
						
							|  |  |  | 						// calling this on a node without .children is a no-op...
 | 
					
						
							|  |  |  | 						if(children == null){ | 
					
						
							|  |  |  | 							return [] } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						// normalize...
 | 
					
						
							|  |  |  | 						list = list === true ? | 
					
						
							|  |  |  | 								children	 | 
					
						
							|  |  |  | 							: (!iterateCollapsed && node.collapsed) ? | 
					
						
							| 
									
										
										
										
											2019-06-02 21:53:20 +03:00
										 |  |  | 								[] | 
					
						
							| 
									
										
										
										
											2019-06-29 07:23:08 +03:00
										 |  |  | 							: list == null ? | 
					
						
							|  |  |  | 								children | 
					
						
							|  |  |  | 							: list | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						// call .walk(..) recursively...
 | 
					
						
							|  |  |  | 						var useWalk = function(){ | 
					
						
							|  |  |  | 							return list.walk( | 
					
						
							|  |  |  | 								func,  | 
					
						
							|  |  |  | 								recursion,  | 
					
						
							|  |  |  | 								...(formArgs instanceof Function ?  | 
					
						
							|  |  |  | 									[formArgs]  | 
					
						
							|  |  |  | 									: [walkable]),  | 
					
						
							|  |  |  | 								options, context) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						return ( | 
					
						
							|  |  |  | 								// XXX BUG?: in this case we lose item indexing...
 | 
					
						
							|  |  |  | 								list === false || list == 'skip' ? | 
					
						
							|  |  |  | 									[] | 
					
						
							|  |  |  | 								// handle arrays internally...
 | 
					
						
							|  |  |  | 								: list instanceof Array ? | 
					
						
							|  |  |  | 									// NOTE: this gets the path and i from context...
 | 
					
						
							|  |  |  | 									next('do', [],  | 
					
						
							|  |  |  | 										...(reverse ?  | 
					
						
							|  |  |  | 											list.slice().reverse()  | 
					
						
							|  |  |  | 											: list)) | 
					
						
							|  |  |  | 								// user-defined recursion...
 | 
					
						
							|  |  |  | 								: recursion instanceof Function ? | 
					
						
							|  |  |  | 									recursion.call(that,  | 
					
						
							| 
									
										
										
										
											2019-05-07 18:17:03 +03:00
										 |  |  | 										list, context.index, p,  | 
					
						
							|  |  |  | 										options, context,  | 
					
						
							| 
									
										
										
										
											2019-06-29 07:23:08 +03:00
										 |  |  | 										func, useWalk) | 
					
						
							|  |  |  | 								// method with arg forming...
 | 
					
						
							|  |  |  | 								: formArgs instanceof Function  | 
					
						
							|  |  |  | 										&& list[recursion] ? | 
					
						
							|  |  |  | 									list[recursion]( | 
					
						
							|  |  |  | 										...(formArgs( | 
					
						
							|  |  |  | 											list, context.index, p,  | 
					
						
							|  |  |  | 											options, context,  | 
					
						
							|  |  |  | 											func, useWalk) || [])) | 
					
						
							|  |  |  | 								// .walk(..)
 | 
					
						
							|  |  |  | 								: useWalk()) | 
					
						
							|  |  |  | 							// normalize and merge to state...
 | 
					
						
							|  |  |  | 							.run(function(){ | 
					
						
							|  |  |  | 								return (nested = this instanceof Array ? | 
					
						
							|  |  |  | 									this | 
					
						
							|  |  |  | 									: [this]) }) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// prepare context...
 | 
					
						
							|  |  |  | 					var id = node.id | 
					
						
							|  |  |  | 					var path = context.path = context.path || [] | 
					
						
							|  |  |  | 					var [inline, p, children] =  | 
					
						
							|  |  |  | 						// inline...
 | 
					
						
							|  |  |  | 						isWalkable(node) ? | 
					
						
							|  |  |  | 							[true, path.slice(), node] | 
					
						
							|  |  |  | 						// nested...
 | 
					
						
							|  |  |  | 						: (!skipNested && isWalkable(node.children)) ? | 
					
						
							|  |  |  | 							[false,  | 
					
						
							|  |  |  | 								// update context for nested items...
 | 
					
						
							|  |  |  | 								path.push(id)  | 
					
						
							|  |  |  | 									&& path.slice(),  | 
					
						
							|  |  |  | 								node.children] | 
					
						
							|  |  |  | 						// leaf...
 | 
					
						
							|  |  |  | 						: [false, path.concat([id]), undefined] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					if(inline && skipInlined){ | 
					
						
							|  |  |  | 						return state } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// go through the elements...
 | 
					
						
							|  |  |  | 					state.splice(state.length, 0, | 
					
						
							|  |  |  | 						...[ | 
					
						
							|  |  |  | 							// reverse -> do children...
 | 
					
						
							|  |  |  | 							reverse == 'flat'  | 
					
						
							|  |  |  | 								&& children | 
					
						
							|  |  |  | 								&& doNested()  | 
					
						
							|  |  |  | 								|| [], | 
					
						
							|  |  |  | 							// do element...
 | 
					
						
							|  |  |  | 							!(skipDisabled && node.disabled) ? | 
					
						
							|  |  |  | 								(func ?  | 
					
						
							|  |  |  | 									(func.call(that,  | 
					
						
							|  |  |  | 										...(inline ?  | 
					
						
							|  |  |  | 											[null, context.index]  | 
					
						
							|  |  |  | 											: [node, context.index++]), | 
					
						
							|  |  |  | 										p,  | 
					
						
							|  |  |  | 										// NOTE: when calling this it is the 
 | 
					
						
							|  |  |  | 										// 		responsibility of the caller to return
 | 
					
						
							|  |  |  | 										// 		the result to be added to state...
 | 
					
						
							|  |  |  | 										doNested,  | 
					
						
							|  |  |  | 										stop, | 
					
						
							|  |  |  | 										children) || [])  | 
					
						
							|  |  |  | 									: [node]) | 
					
						
							|  |  |  | 								// element is disabled -> handle children...
 | 
					
						
							|  |  |  | 								: [], | 
					
						
							|  |  |  | 							// normal order -> do children...
 | 
					
						
							|  |  |  | 							children | 
					
						
							|  |  |  | 								&& nested === false | 
					
						
							|  |  |  | 								&& doNested()  | 
					
						
							|  |  |  | 								|| [], | 
					
						
							|  |  |  | 						].flat()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// restore path context...
 | 
					
						
							|  |  |  | 					children | 
					
						
							|  |  |  | 						&& context.path.pop() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					return state | 
					
						
							|  |  |  | 				},  | 
					
						
							|  |  |  | 				[],  | 
					
						
							|  |  |  | 				// input items...
 | 
					
						
							|  |  |  | 				...(sections | 
					
						
							|  |  |  | 					.map(function(name){ | 
					
						
							|  |  |  | 						return that[name] || [] }) | 
					
						
							|  |  |  | 					.flat() | 
					
						
							|  |  |  | 					.run(function(){ | 
					
						
							|  |  |  | 						return reverse ? | 
					
						
							|  |  |  | 							this.reverse() | 
					
						
							|  |  |  | 							: this }))) | 
					
						
							|  |  |  | 			// NOTE: walk(..) if passed to items will return a function...
 | 
					
						
							|  |  |  |    			.run(function(){ | 
					
						
							|  |  |  | 				return this instanceof Function ?  | 
					
						
							|  |  |  | 					[]  | 
					
						
							|  |  |  | 					: this})}, | 
					
						
							| 
									
										
										
										
											2019-05-03 19:43:22 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-11 14:54:48 +03:00
										 |  |  | 	// Test/Example Text renders...
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 14:54:48 +03:00
										 |  |  | 	//	Recursively render the browser as text tree...
 | 
					
						
							|  |  |  | 	//	._test_texttree(..)
 | 
					
						
							|  |  |  | 	//		-> string
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-11 14:54:48 +03:00
										 |  |  | 	//	Recursively render the browser as text tree with manual nesting...
 | 
					
						
							|  |  |  | 	//	._test_texttree_manual(..)
 | 
					
						
							|  |  |  | 	//		-> string
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Build a nested object tree from the browser...
 | 
					
						
							|  |  |  | 	//	._test_tree(..)
 | 
					
						
							|  |  |  | 	//		-> object
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	_test_texttree: function(options, context){ | 
					
						
							|  |  |  | 		// NOTE: here we do not care about the topology (other than path 
 | 
					
						
							|  |  |  | 		// 		depth) and just handle items...
 | 
					
						
							| 
									
										
										
										
											2019-05-04 17:56:02 +03:00
										 |  |  | 		return this | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 			.walk( | 
					
						
							| 
									
										
										
										
											2019-05-04 17:56:02 +03:00
										 |  |  | 				function(node, i, path){ | 
					
						
							|  |  |  | 					return node ?  | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 						path.slice(1) | 
					
						
							|  |  |  | 							.map(e => '  ') | 
					
						
							| 
									
										
										
										
											2019-06-11 15:44:10 +03:00
										 |  |  | 							.join('')  | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 								+ node.text | 
					
						
							| 
									
										
										
										
											2019-04-25 17:40:28 +03:00
										 |  |  | 						: [] }, | 
					
						
							| 
									
										
										
										
											2019-05-11 14:54:48 +03:00
										 |  |  | 				'_test_texttree', | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 				function(func, i, path, options, context){ | 
					
						
							|  |  |  | 					return [options, context] }, | 
					
						
							| 
									
										
										
										
											2019-05-07 19:19:23 +03:00
										 |  |  | 				options, context) | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 			.join('\n') }, | 
					
						
							| 
									
										
										
										
											2019-05-11 14:54:48 +03:00
										 |  |  | 	_test_texttree_manual: function(options, context){ | 
					
						
							|  |  |  | 		// NOTE: here we do basic topology -- append children to their 
 | 
					
						
							|  |  |  | 		// 		respective node...
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 		return this | 
					
						
							|  |  |  | 			.walk( | 
					
						
							|  |  |  | 				function(node, i, path, next){ | 
					
						
							|  |  |  | 					return node == null ?  | 
					
						
							|  |  |  | 							[] | 
					
						
							| 
									
										
										
										
											2019-05-11 14:54:48 +03:00
										 |  |  | 						// make a node...
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 						: [path.slice(1) | 
					
						
							|  |  |  | 							.map(e => '  ') | 
					
						
							| 
									
										
										
										
											2019-06-11 15:44:10 +03:00
										 |  |  | 							.join('') | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 								+ node.text] | 
					
						
							| 
									
										
										
										
											2019-05-11 14:54:48 +03:00
										 |  |  | 							// append child nodes if present...
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 			   				.concat(node.children ? | 
					
						
							|  |  |  | 								next() | 
					
						
							|  |  |  | 								: []) }, | 
					
						
							| 
									
										
										
										
											2019-05-11 14:54:48 +03:00
										 |  |  | 				'_test_texttree_manual', | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 				function(func, i, path, options, context){ | 
					
						
							|  |  |  | 					return [options, context] }, | 
					
						
							|  |  |  | 				options, context) | 
					
						
							|  |  |  | 			.join('\n') }, | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 	// XXX need to check for output key uniqueness per level...
 | 
					
						
							| 
									
										
										
										
											2019-05-11 14:54:48 +03:00
										 |  |  | 	_test_tree: function(options, context){ | 
					
						
							|  |  |  | 		var toObject = function(res, e){ | 
					
						
							|  |  |  | 			if(e == null || e[0] == null){ | 
					
						
							|  |  |  | 				return res | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			res[e[0]] = e[1] instanceof Array ?  | 
					
						
							|  |  |  | 				// handle nested arrays...
 | 
					
						
							|  |  |  | 				// NOTE: these did not get through the .reduce(..) below
 | 
					
						
							|  |  |  | 				// 		as they are simple arrays that do not implement
 | 
					
						
							|  |  |  | 				// 		either .walk(..) or ._test_tree(..)
 | 
					
						
							|  |  |  | 				e.slice(1).reduce(toObject, {})  | 
					
						
							|  |  |  | 				: e[1] | 
					
						
							|  |  |  | 			return res | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 			// build [key, children] pairs...
 | 
					
						
							|  |  |  | 			.walk( | 
					
						
							|  |  |  | 				function(node, i, path, next){ | 
					
						
							|  |  |  | 					return node == null ?  | 
					
						
							|  |  |  | 							[] | 
					
						
							|  |  |  | 						// make a node...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 						: [[node.text] | 
					
						
							| 
									
										
										
										
											2019-05-11 14:54:48 +03:00
										 |  |  | 							// append child nodes if present...
 | 
					
						
							|  |  |  | 			   				.concat(node.children ? | 
					
						
							|  |  |  | 								next() | 
					
						
							|  |  |  | 								: null) ] }, | 
					
						
							|  |  |  | 				'_test_tree', | 
					
						
							|  |  |  | 				function(func, i, path, options, context){ | 
					
						
							|  |  |  | 					return [options, context] }, | 
					
						
							|  |  |  | 				options, context) | 
					
						
							|  |  |  | 			// construct the object...
 | 
					
						
							|  |  |  |    			.reduce(toObject, {}) }, | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 	// XXX we do not need this any more, as we got paths in the index...
 | 
					
						
							|  |  |  | 	_test_paths: function(options, context){ | 
					
						
							| 
									
										
										
										
											2019-05-11 05:24:36 +03:00
										 |  |  | 		return this.walk( | 
					
						
							|  |  |  | 			function(n, i, p){ | 
					
						
							|  |  |  | 				return n  | 
					
						
							|  |  |  | 					&& [(options || {}).joinPaths !== false ?  | 
					
						
							|  |  |  | 						p.join('/')  | 
					
						
							|  |  |  | 						: p] },  | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 			'_test_paths', | 
					
						
							| 
									
										
										
										
											2019-05-11 05:24:36 +03:00
										 |  |  | 			function(_, i, path, options, context){ | 
					
						
							|  |  |  | 				// NOTE: for paths and indexes to be consistent between
 | 
					
						
							|  |  |  | 				// 		levels we need to thread the context on, here and
 | 
					
						
							|  |  |  | 				// 		into the base .walk(..) call below...
 | 
					
						
							|  |  |  | 				return [options, context] }, | 
					
						
							|  |  |  | 			options, context) }, | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-11 14:54:48 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// Extended map...
 | 
					
						
							| 
									
										
										
										
											2019-03-17 23:30:27 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	//	Get all items...
 | 
					
						
							|  |  |  | 	//	.map([options])
 | 
					
						
							|  |  |  | 	//		-> items
 | 
					
						
							| 
									
										
										
										
											2019-03-17 23:30:27 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	//	Map func to items...
 | 
					
						
							|  |  |  | 	//	.map(func[, options])
 | 
					
						
							|  |  |  | 	//		-> items
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:54:10 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	//	func(item, index, path, browser)
 | 
					
						
							|  |  |  | 	//		-> result
 | 
					
						
							| 
									
										
										
										
											2019-03-17 23:30:27 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-25 17:50:32 +03:00
										 |  |  | 	// options format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							| 
									
										
										
										
											2019-04-25 18:05:17 +03:00
										 |  |  | 	// 		// The value used if .reverse is set to true...
 | 
					
						
							|  |  |  | 	// 		//
 | 
					
						
							|  |  |  | 	// 		// NOTE: the default is different from .walk(..)
 | 
					
						
							|  |  |  | 	// 		defaultReverse: 'flat' (default) | 'tree',
 | 
					
						
							| 
									
										
										
										
											2019-04-25 17:50:32 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		// For other supported options see docs for .walk(..)
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// By default this will not iterate items that are:
 | 
					
						
							|  |  |  | 	// 	- non-iterable (item.noniterable is true)
 | 
					
						
							|  |  |  | 	// 	- collapsed sub-items (item.collapsed is true)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// This extends the Array .map(..) by adding:
 | 
					
						
							|  |  |  | 	// 	- ability to run without arguments
 | 
					
						
							|  |  |  | 	// 	- support for options
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-27 04:15:03 +03:00
										 |  |  | 	// XXX should we move the defaults to .config???
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// XXX Q: should we have an option to treat groups as elements???
 | 
					
						
							|  |  |  | 	map: function(func, options){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// parse args...
 | 
					
						
							|  |  |  | 		var args = [...arguments] | 
					
						
							| 
									
										
										
										
											2019-04-28 02:38:54 +03:00
										 |  |  | 		func = (args[0] instanceof Function  | 
					
						
							|  |  |  | 				|| args[0] === undefined) ?  | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 			args.shift()  | 
					
						
							|  |  |  | 			: undefined | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 		// NOTE: we do not inherit options from this.options here is it 
 | 
					
						
							|  |  |  | 		// 		will be done in .walk(..)
 | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 		options = args.shift() || {} | 
					
						
							| 
									
										
										
										
											2019-04-25 18:05:17 +03:00
										 |  |  | 		options = !options.defaultReverse ? | 
					
						
							| 
									
										
										
										
											2019-04-25 17:50:32 +03:00
										 |  |  | 			Object.assign({}, | 
					
						
							|  |  |  | 				options,  | 
					
						
							| 
									
										
										
										
											2019-04-25 18:05:17 +03:00
										 |  |  | 				{ defaultReverse: 'flat' }) | 
					
						
							| 
									
										
										
										
											2019-04-25 17:50:32 +03:00
										 |  |  | 			: options | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 		var context = args.shift() | 
					
						
							| 
									
										
										
										
											2019-04-25 17:50:32 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 		return this.walk( | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 			function(elem, i, path){ | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 				return elem != null ? | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 					[func === undefined ? | 
					
						
							|  |  |  | 						elem | 
					
						
							|  |  |  | 						// XXX should this pass the current or the root 
 | 
					
						
							|  |  |  | 						// 		container to func???
 | 
					
						
							|  |  |  | 						: func.call(that, elem, i, path, that)] | 
					
						
							|  |  |  | 					: [] },  | 
					
						
							|  |  |  | 			'map', | 
					
						
							|  |  |  | 			function(_, i, p, options, context){ | 
					
						
							|  |  |  | 				return [func, options, context] }, | 
					
						
							| 
									
										
										
										
											2019-05-07 19:19:23 +03:00
										 |  |  | 			options, context) }, | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 16:54:10 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 	// Search items...
 | 
					
						
							| 
									
										
										
										
											2019-04-27 20:59:56 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-02 00:29:09 +03:00
										 |  |  | 	// 	Get list of matching elements...
 | 
					
						
							|  |  |  | 	// 	NOTE: this is similar to .filter(..)
 | 
					
						
							|  |  |  | 	// 	.search(test[, options])
 | 
					
						
							|  |  |  | 	// 		-> items
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Map func to list of matching elements and return results...
 | 
					
						
							|  |  |  | 	// 	NOTE: this is similar to .filter(..).map(func)
 | 
					
						
							|  |  |  | 	// 	.search(test, func[, options])
 | 
					
						
							|  |  |  | 	// 		-> items
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// test can be:
 | 
					
						
							|  |  |  | 	// 	predicate(..)	- function returning true or false
 | 
					
						
							|  |  |  | 	// 	index			- element index
 | 
					
						
							|  |  |  | 	// 						NOTE: index can be positive or negative to 
 | 
					
						
							|  |  |  | 	// 							access items from the end.
 | 
					
						
							|  |  |  | 	// 	path			- array of path elements or '*' (matches any element)
 | 
					
						
							|  |  |  | 	// 	regexp			- regexp object to test item path
 | 
					
						
							|  |  |  | 	// 	query			- object to test against the element 
 | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 	// 	keyword			- 
 | 
					
						
							| 
									
										
										
										
											2019-05-02 00:29:09 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	predicate(elem, i, path)
 | 
					
						
							|  |  |  | 	// 		-> bool
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// query format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 	// 		// match if <attr-name> exists and is true...
 | 
					
						
							|  |  |  | 	// 		// XXX revise...
 | 
					
						
							| 
									
										
										
										
											2019-05-02 00:29:09 +03:00
										 |  |  | 	// 		<attr-name>: true,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 	// 		// match if <attr-name> does not exist or is false...
 | 
					
						
							|  |  |  | 	// 		// XXX revise...
 | 
					
						
							| 
									
										
										
										
											2019-05-02 00:29:09 +03:00
										 |  |  | 	// 		<attr-name>: false,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		// match if <attr-name> equals value...
 | 
					
						
							|  |  |  | 	// 		<attr-name>: <value>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		// match if func(<attr-value>) return true...
 | 
					
						
							|  |  |  | 	// 		<attr-name>: <func>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							| 
									
										
										
										
											2019-04-27 20:59:56 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 	// supported keywords:
 | 
					
						
							|  |  |  | 	// 	'first'		- get first item (same as 0)
 | 
					
						
							|  |  |  | 	// 	'last'		- get last item (same as -1)
 | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 	// 	'selected'	- get selected items (shorthand to {selected: true})
 | 
					
						
							|  |  |  | 	// 	'focused'	- get focused items (shorthand to {focused: true})
 | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-09 00:20:11 +03:00
										 |  |  | 	// options format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							|  |  |  | 	// 		noIdentityCheck: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		noQueryCheck: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-07 19:56:46 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// __search_test_generators__ format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							|  |  |  | 	// 		// NOTE: generator order is significant as patterns are testen 
 | 
					
						
							|  |  |  | 	// 		//		in order the generators are defined...
 | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 	// 		// NOTE: testGenerator(..) is called in the context of 
 | 
					
						
							|  |  |  | 	// 		//		__search_test_generators__ (XXX ???)
 | 
					
						
							| 
									
										
										
										
											2019-05-07 19:56:46 +03:00
										 |  |  | 	// 		// NOTE: <key> is only used for documentation...
 | 
					
						
							|  |  |  | 	// 		<key>: testGenerator(..),
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	testGenerator(pattern)
 | 
					
						
							|  |  |  | 	//		-> test(elem, i, path)
 | 
					
						
							|  |  |  | 	//		-> false
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-01 00:39:29 +03:00
										 |  |  | 	// NOTE: search is self-applicable, e.g. 
 | 
					
						
							|  |  |  | 	// 			x.search(x.search(..), {noQueryCheck: true})
 | 
					
						
							|  |  |  | 	// 		should yield the same result as:
 | 
					
						
							|  |  |  | 	// 			x.search(..)
 | 
					
						
							|  |  |  | 	// 		this is very fast as we shortcut by simply checking of an 
 | 
					
						
							|  |  |  | 	// 		item exists...
 | 
					
						
							|  |  |  | 	// NOTE: if .search(..) is passed a list of items (e.g. a result of 
 | 
					
						
							|  |  |  | 	// 		another .search(..)) it will return the items that are in
 | 
					
						
							|  |  |  | 	// 		.index as-is regardless of what is set in options...
 | 
					
						
							|  |  |  | 	// 		given options in this case will be applied only to list items
 | 
					
						
							|  |  |  | 	// 		that are searched i.e. the non-items in the input list...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX can .search(..) of a non-path array as a pattern be done in 
 | 
					
						
							|  |  |  | 	// 		a single pass???
 | 
					
						
							| 
									
										
										
										
											2019-05-18 03:28:53 +03:00
										 |  |  | 	// XXX add support for fuzzy match search -- match substring by default 
 | 
					
						
							|  |  |  | 	// 		and exact title if using quotes...
 | 
					
						
							| 
									
										
										
										
											2019-05-07 19:56:46 +03:00
										 |  |  | 	// XXX add diff support...
 | 
					
						
							| 
									
										
										
										
											2019-05-26 20:20:18 +03:00
										 |  |  | 	// XXX should this check hidden items when doing an identity check???
 | 
					
						
							| 
									
										
										
										
											2019-05-07 19:56:46 +03:00
										 |  |  | 	__search_test_generators__: { | 
					
						
							|  |  |  | 		// regexp path test...
 | 
					
						
							|  |  |  | 		regexp: function(pattern){ | 
					
						
							|  |  |  | 			return pattern instanceof RegExp | 
					
						
							|  |  |  | 				&& function(elem, i, path){ | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 					return pattern.test(elem.text) | 
					
						
							| 
									
										
										
										
											2019-05-07 19:56:46 +03:00
										 |  |  | 						|| pattern.test('/'+ path.join('/')) } }, | 
					
						
							| 
									
										
										
										
											2019-05-18 03:23:43 +03:00
										 |  |  | 		// string path test...
 | 
					
						
							|  |  |  | 		// XXX should 'B' be equivalent to '/B' or should it be more like '**/B'?
 | 
					
						
							|  |  |  | 		strPath: function(pattern){ | 
					
						
							|  |  |  | 			if(typeof(pattern) == typeof('str')){ | 
					
						
							| 
									
										
										
										
											2019-05-18 03:15:03 +03:00
										 |  |  | 				pattern = pattern instanceof Array ? | 
					
						
							|  |  |  | 					pattern | 
					
						
							|  |  |  | 					: pattern | 
					
						
							|  |  |  | 						.split(/[\\\/]/g) | 
					
						
							|  |  |  | 						.filter(function(e){ return e.trim().length > 0 }) | 
					
						
							| 
									
										
										
										
											2019-05-18 03:23:43 +03:00
										 |  |  | 				return this.path(pattern) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		// path test...
 | 
					
						
							|  |  |  | 		// NOTE: this does not go down branches that do not match the path...
 | 
					
						
							| 
									
										
										
										
											2019-06-02 21:16:23 +03:00
										 |  |  | 		// XXX add support for '**' ???
 | 
					
						
							| 
									
										
										
										
											2019-05-18 03:23:43 +03:00
										 |  |  | 		path: function(pattern){ | 
					
						
							|  |  |  | 			if(pattern instanceof Array){ | 
					
						
							| 
									
										
										
										
											2019-05-11 00:14:10 +03:00
										 |  |  | 				var cmp = function(a, b){ | 
					
						
							|  |  |  | 					return a.length == b.length | 
					
						
							|  |  |  | 						&& !a | 
					
						
							|  |  |  | 							.reduce(function(res, e, i){ | 
					
						
							|  |  |  | 								return res || !( | 
					
						
							|  |  |  | 									e == '*'  | 
					
						
							|  |  |  | 										|| (e instanceof RegExp  | 
					
						
							|  |  |  | 											&& e.test(b[i])) | 
					
						
							|  |  |  | 										|| e == b[i]) }, false) } | 
					
						
							|  |  |  | 				var onPath = function(path){ | 
					
						
							|  |  |  | 					return pattern.length >= path.length  | 
					
						
							|  |  |  | 						&& cmp( | 
					
						
							|  |  |  | 							pattern.slice(0, path.length),  | 
					
						
							|  |  |  | 							path) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				return function(elem, i, path, next){ | 
					
						
							| 
									
										
										
										
											2019-05-10 21:07:17 +03:00
										 |  |  | 					// do not go down branches beyond pattern length or 
 | 
					
						
							|  |  |  | 					// ones that are not on path...
 | 
					
						
							| 
									
										
										
										
											2019-06-02 21:16:23 +03:00
										 |  |  | 					// XXX BUG: this messes up i...
 | 
					
						
							|  |  |  | 					// 		...can we do this while maintaining i correctly???
 | 
					
						
							|  |  |  | 					//;(pattern.length == path.length
 | 
					
						
							|  |  |  | 					//		|| !onPath(path))
 | 
					
						
							|  |  |  | 					//	&& next(false)
 | 
					
						
							| 
									
										
										
										
											2019-05-10 21:07:17 +03:00
										 |  |  | 					// do the test...
 | 
					
						
							| 
									
										
										
										
											2019-05-07 19:56:46 +03:00
										 |  |  | 					return path.length > 0 | 
					
						
							|  |  |  | 						&& pattern.length == path.length | 
					
						
							| 
									
										
										
										
											2019-05-11 00:14:10 +03:00
										 |  |  | 						&& cmp(pattern, path) }  | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2019-05-07 19:56:46 +03:00
										 |  |  | 		// item index test...
 | 
					
						
							|  |  |  | 		index: function(pattern){ | 
					
						
							|  |  |  | 			return typeof(pattern) == typeof(123) | 
					
						
							|  |  |  | 				&& function(elem, i, path){ | 
					
						
							|  |  |  | 					return i == pattern } }, | 
					
						
							|  |  |  | 		// XXX add diff support...
 | 
					
						
							|  |  |  | 		// object query..
 | 
					
						
							|  |  |  | 		// NOTE: this must be last as it will return a test unconditionally...
 | 
					
						
							|  |  |  | 		query: function(pattern){  | 
					
						
							| 
									
										
										
										
											2019-05-15 21:45:20 +03:00
										 |  |  | 			var that = this | 
					
						
							| 
									
										
										
										
											2019-05-07 19:56:46 +03:00
										 |  |  | 			return function(elem){ | 
					
						
							|  |  |  | 				return Object.entries(pattern) | 
					
						
							|  |  |  | 					.reduce(function(res, [key, pattern]){ | 
					
						
							|  |  |  | 						return res  | 
					
						
							|  |  |  | 							&& (elem[key] == pattern | 
					
						
							|  |  |  | 								// bool...
 | 
					
						
							|  |  |  | 								|| ((pattern === true || pattern === false) | 
					
						
							|  |  |  | 									&& pattern === !!elem[key]) | 
					
						
							|  |  |  | 								// predicate...
 | 
					
						
							|  |  |  | 								|| (pattern instanceof Function  | 
					
						
							|  |  |  | 									&& pattern.call(that, elem[key])) | 
					
						
							|  |  |  | 								// regexp...
 | 
					
						
							|  |  |  | 								|| (pattern instanceof RegExp | 
					
						
							|  |  |  | 									&& pattern.test(elem[key])) | 
					
						
							|  |  |  | 								// type...
 | 
					
						
							|  |  |  | 								// XXX problem, we can't distinguish this 
 | 
					
						
							|  |  |  | 								// 		and a predicate...
 | 
					
						
							|  |  |  | 								// 		...so for now use:
 | 
					
						
							|  |  |  | 								// 			.search(v => v instanceof Array)
 | 
					
						
							|  |  |  | 								//|| (typeof(pattern) == typeof({})
 | 
					
						
							|  |  |  | 								//	&& pattern instanceof Function
 | 
					
						
							|  |  |  | 								//	&& elem[key] instanceof pattern)
 | 
					
						
							|  |  |  | 							) }, true) } }, | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-05-02 00:29:09 +03:00
										 |  |  | 	search: function(pattern, func, options){ | 
					
						
							| 
									
										
										
										
											2019-04-27 04:15:03 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2019-06-01 00:39:29 +03:00
										 |  |  | 		var args = [...arguments] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-02 00:29:09 +03:00
										 |  |  | 		// parse args...
 | 
					
						
							| 
									
										
										
										
											2019-05-11 00:14:10 +03:00
										 |  |  | 		pattern = args.length == 0 ?  | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 			true  | 
					
						
							| 
									
										
										
										
											2019-05-11 00:14:10 +03:00
										 |  |  | 			: args.shift()  | 
					
						
							| 
									
										
										
										
											2019-05-02 00:29:09 +03:00
										 |  |  | 		func = (args[0] instanceof Function  | 
					
						
							|  |  |  | 				|| args[0] === undefined) ?  | 
					
						
							|  |  |  | 			args.shift()  | 
					
						
							|  |  |  | 			: undefined | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 		// NOTE: we do not inherit options from this.options here is it 
 | 
					
						
							|  |  |  | 		// 		will be done in .walk(..)
 | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 		options = args.shift() || {} | 
					
						
							| 
									
										
										
										
											2019-05-06 19:21:22 +03:00
										 |  |  | 		var context = args.shift() | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-13 02:54:41 +03:00
										 |  |  | 		// non-path array or item as-is...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// here we'll do one of the following for pattern / each element of pattern:
 | 
					
						
							|  |  |  | 		// 	- pattern is an explicitly given item
 | 
					
						
							|  |  |  | 		// 		-> pass to func(..) if given, else return as-is
 | 
					
						
							|  |  |  | 		// 	- call .search(pattern, ..)
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: a non-path array is one where at least one element is 
 | 
					
						
							|  |  |  | 		// 		an object...
 | 
					
						
							|  |  |  | 		// NOTE: this might get expensive as we call .search(..) per item...
 | 
					
						
							| 
									
										
										
										
											2019-06-13 03:05:44 +03:00
										 |  |  | 		// XXX needs refactoring -- feels overcomplicated...
 | 
					
						
							| 
									
										
										
										
											2019-06-13 02:54:41 +03:00
										 |  |  | 		var index = new Set(Object.values(this.index)) | 
					
						
							|  |  |  | 		if(index.has(pattern)  | 
					
						
							|  |  |  | 				|| (pattern instanceof Array | 
					
						
							|  |  |  | 					&& !pattern | 
					
						
							|  |  |  | 						.reduce(function(r, e){  | 
					
						
							|  |  |  | 							return r && typeof(e) != typeof({})  }, true))){ | 
					
						
							|  |  |  | 			// reverse index...
 | 
					
						
							|  |  |  | 			index = this | 
					
						
							|  |  |  | 				.reduce(function(res, e, i, p){ | 
					
						
							|  |  |  | 					res.set(e, [i, p]) | 
					
						
							|  |  |  | 					return res | 
					
						
							|  |  |  | 				}, new Map(), {iterateCollapsed: true}) | 
					
						
							|  |  |  | 			var res | 
					
						
							|  |  |  | 			var Stop = new Error('Stop iteration') | 
					
						
							|  |  |  | 			try { | 
					
						
							|  |  |  | 				return (pattern instanceof Array ?  | 
					
						
							|  |  |  | 						pattern  | 
					
						
							|  |  |  | 						: [pattern]) | 
					
						
							|  |  |  | 					.map(function(pattern){  | 
					
						
							|  |  |  | 						return index.has(pattern) ?  | 
					
						
							|  |  |  | 							// pattern is an explicit item...
 | 
					
						
							|  |  |  | 							[ func ? | 
					
						
							|  |  |  | 								func.call(this, pattern,  | 
					
						
							|  |  |  | 									...index.get(pattern),  | 
					
						
							| 
									
										
										
										
											2019-06-13 03:05:44 +03:00
										 |  |  | 									// stop(..)
 | 
					
						
							|  |  |  | 									function stop(v){ | 
					
						
							| 
									
										
										
										
											2019-06-13 02:54:41 +03:00
										 |  |  | 										res = v | 
					
						
							|  |  |  | 										throw Stop }) | 
					
						
							|  |  |  | 								: pattern ] | 
					
						
							|  |  |  | 							// search...
 | 
					
						
							|  |  |  | 							: that.search(pattern, ...args.slice(1)) }) | 
					
						
							|  |  |  | 					.flat() | 
					
						
							|  |  |  | 					.unique()  | 
					
						
							|  |  |  | 			} catch(e){ | 
					
						
							|  |  |  | 				if(e === Stop){ | 
					
						
							|  |  |  | 					return res | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				throw e } } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 		// pattern -- normalize and do pattern keywords...
 | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 		pattern = options.ignoreKeywords ? | 
					
						
							|  |  |  | 				pattern | 
					
						
							| 
									
										
										
										
											2019-05-11 00:14:10 +03:00
										 |  |  | 			: typeof(pattern) == typeof('str') ? | 
					
						
							|  |  |  | 				((pattern === 'all' || pattern == '*') ? | 
					
						
							|  |  |  | 					true | 
					
						
							|  |  |  | 				: pattern == 'first' ? | 
					
						
							|  |  |  | 					0 | 
					
						
							|  |  |  | 				: pattern == 'last' ? | 
					
						
							|  |  |  | 					-1 | 
					
						
							|  |  |  | 				: pattern == 'selected' ? | 
					
						
							| 
									
										
										
										
											2019-05-30 20:00:09 +03:00
										 |  |  | 					function(e){ return !!e.selected } | 
					
						
							| 
									
										
										
										
											2019-05-11 00:14:10 +03:00
										 |  |  | 				: pattern == 'focused' ? | 
					
						
							| 
									
										
										
										
											2019-05-30 20:00:09 +03:00
										 |  |  | 					function(e){ return !!e.focused } | 
					
						
							| 
									
										
										
										
											2019-05-11 00:14:10 +03:00
										 |  |  | 				: pattern) | 
					
						
							| 
									
										
										
										
											2019-04-30 17:24:04 +03:00
										 |  |  | 			: pattern | 
					
						
							|  |  |  | 		// normalize negative index...
 | 
					
						
							|  |  |  | 		if(typeof(pattern) == typeof(123) && pattern < 0){ | 
					
						
							|  |  |  | 			pattern = -pattern - 1 | 
					
						
							| 
									
										
										
										
											2019-05-27 19:33:49 +03:00
										 |  |  | 			options = Object.assign({}, | 
					
						
							|  |  |  | 				options, | 
					
						
							|  |  |  | 				{reverse: 'flat'}) | 
					
						
							| 
									
										
										
										
											2019-04-30 17:24:04 +03:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		// normalize/build the test predicate...
 | 
					
						
							| 
									
										
										
										
											2019-05-02 00:29:09 +03:00
										 |  |  | 		var test = ( | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 			// all...
 | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 			pattern === true ? | 
					
						
							|  |  |  | 				pattern | 
					
						
							| 
									
										
										
										
											2019-04-27 17:02:36 +03:00
										 |  |  | 			// predicate...
 | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 			: pattern instanceof Function ? | 
					
						
							| 
									
										
										
										
											2019-04-27 17:02:36 +03:00
										 |  |  | 				pattern | 
					
						
							| 
									
										
										
										
											2019-05-07 19:56:46 +03:00
										 |  |  | 			// other -> get a compatible test function...
 | 
					
						
							| 
									
										
										
										
											2019-05-09 00:20:11 +03:00
										 |  |  | 			: Object.entries(this.__search_test_generators__) | 
					
						
							| 
									
										
										
										
											2019-05-11 00:14:10 +03:00
										 |  |  | 				.filter(function([key, _]){ | 
					
						
							| 
									
										
										
										
											2019-05-09 00:20:11 +03:00
										 |  |  | 					return !(options.noQueryCheck  | 
					
						
							|  |  |  | 						&& key == 'query') }) | 
					
						
							|  |  |  | 				.reduce(function(res, [_, get]){ | 
					
						
							| 
									
										
										
										
											2019-05-07 19:56:46 +03:00
										 |  |  | 					return res  | 
					
						
							| 
									
										
										
										
											2019-05-18 03:23:43 +03:00
										 |  |  | 						|| get.call(that.__search_test_generators__, pattern) }, false) ) | 
					
						
							| 
									
										
										
										
											2019-04-27 17:02:36 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 		return this.walk( | 
					
						
							| 
									
										
										
										
											2019-05-10 21:07:17 +03:00
										 |  |  | 			function(elem, i, path, next, stop){ | 
					
						
							| 
									
										
										
										
											2019-04-30 17:24:04 +03:00
										 |  |  | 				// match...
 | 
					
						
							| 
									
										
										
										
											2019-05-06 19:21:22 +03:00
										 |  |  | 				var res = (elem | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 						&& (test === true  | 
					
						
							| 
									
										
										
										
											2019-05-09 00:20:11 +03:00
										 |  |  | 							// identity check...
 | 
					
						
							|  |  |  | 							|| (!options.noIdentityCheck  | 
					
						
							|  |  |  | 								&& pattern === elem) | 
					
						
							|  |  |  | 							// test...
 | 
					
						
							|  |  |  | 							|| (test  | 
					
						
							| 
									
										
										
										
											2019-05-10 21:07:17 +03:00
										 |  |  | 								// NOTE: we pass next here to provide the 
 | 
					
						
							|  |  |  | 								// 		test with the option to filter out
 | 
					
						
							|  |  |  | 								// 		branches that it knows will not 
 | 
					
						
							|  |  |  | 								// 		match...
 | 
					
						
							|  |  |  | 								&& test.call(this, elem, i, path, next)))) ? | 
					
						
							| 
									
										
										
										
											2019-05-09 00:20:11 +03:00
										 |  |  | 					// handle the passed items...
 | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 					[ func ? | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 						func.call(this, elem, i, path, stop) | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 						: elem ] | 
					
						
							|  |  |  | 					: []  | 
					
						
							|  |  |  | 				return ((options.firstMatch  | 
					
						
							|  |  |  | 							|| typeof(pattern) == typeof(123))  | 
					
						
							|  |  |  | 						&& res.length > 0) ?  | 
					
						
							| 
									
										
										
										
											2019-05-06 19:21:22 +03:00
										 |  |  | 					stop(res) | 
					
						
							| 
									
										
										
										
											2019-05-06 16:57:06 +03:00
										 |  |  | 					: res }, | 
					
						
							|  |  |  | 			'search', | 
					
						
							|  |  |  | 			function(_, i, p, options, context){ | 
					
						
							|  |  |  | 				return [pattern, func, options, context] }, | 
					
						
							| 
									
										
										
										
											2019-05-07 19:19:23 +03:00
										 |  |  | 			options, context) }, | 
					
						
							| 
									
										
										
										
											2019-04-27 04:15:03 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 	// Get item... 
 | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-03 03:25:57 +03:00
										 |  |  | 	// 	Get focused item...
 | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 	// 	.get()
 | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 	// 	.get('focused'[, func])
 | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 	// 		-> item
 | 
					
						
							|  |  |  | 	// 		-> undefined
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-03 03:25:57 +03:00
										 |  |  | 	// 	Get next/prev item relative to focused...
 | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 	// 	.get('prev'[, offset][, func][, options])
 | 
					
						
							|  |  |  | 	// 	.get('next'[, offset][, func][, options])
 | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 	// 		-> item
 | 
					
						
							|  |  |  | 	// 		-> undefined
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 	// 	Get parent element relative to focused...
 | 
					
						
							|  |  |  | 	// 	.get('parent'[, func][, options])
 | 
					
						
							|  |  |  | 	// 		-> item
 | 
					
						
							|  |  |  | 	// 		-> undefined
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-03 03:25:57 +03:00
										 |  |  | 	// 	Get first item matching pattern...
 | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 	// 	.get(pattern[, func][, options])
 | 
					
						
							| 
									
										
										
										
											2019-05-03 03:25:57 +03:00
										 |  |  | 	// 		-> item
 | 
					
						
							|  |  |  | 	// 		-> undefined
 | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-03 03:25:57 +03:00
										 |  |  | 	// pattern mostly follows the same scheme as in .select(..) so see 
 | 
					
						
							|  |  |  | 	// docs for that for more info.
 | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this is just like a lazy .search(..) that will return the 
 | 
					
						
							|  |  |  | 	// 		first result only.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 	// XXX should we be able to get offset values relative to any match?
 | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 	get: function(pattern, options){ | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 		var args = [...arguments] | 
					
						
							|  |  |  | 		pattern = args.shift() | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 		pattern = pattern === undefined ?  | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 			'focused'  | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 			: pattern | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 		var offset = (pattern == 'next' || pattern == 'prev') | 
					
						
							|  |  |  | 				&& typeof(args[0]) == typeof(123) ? | 
					
						
							| 
									
										
										
										
											2019-05-27 19:16:51 +03:00
										 |  |  | 			args.shift() + 1 | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 			: 1 | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 		var func = args[0] instanceof Function ? | 
					
						
							|  |  |  | 			args.shift()  | 
					
						
							|  |  |  | 			// XXX return format...
 | 
					
						
							|  |  |  | 			: function(e, i, p){ return e } | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 		// NOTE: we do not inherit options from this.options here is it 
 | 
					
						
							|  |  |  | 		// 		will be done in .walk(..)
 | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 		options = args.pop() || {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-13 02:54:41 +03:00
										 |  |  | 		// special case: path pattern -> include collapsed elements... 
 | 
					
						
							|  |  |  | 		// XXX use something like .isPath(..)
 | 
					
						
							|  |  |  | 		if(((typeof(pattern) == typeof('str')  | 
					
						
							|  |  |  | 						&& pattern.split(/[\\\/]/g).length > 1) | 
					
						
							|  |  |  | 					// array path...
 | 
					
						
							|  |  |  | 					|| (pattern instanceof Array  | 
					
						
							|  |  |  | 						&& !pattern | 
					
						
							|  |  |  | 							.reduce(function(r, e){  | 
					
						
							|  |  |  | 								return r || typeof(e) != typeof('str') }, false))) | 
					
						
							|  |  |  | 				&& !('iterateCollapsed' in options)){ | 
					
						
							|  |  |  | 			options = Object.assign( | 
					
						
							|  |  |  | 				Object.create(options),  | 
					
						
							|  |  |  | 				{iterateCollapsed: true}) } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 		// sanity checks...
 | 
					
						
							|  |  |  | 		if(offset <= 0){ | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 			throw new Error(`.get(..): offset must be a positive number, got: ${offset}.`) } | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 		// NOTE: we do not care about return values here as we'll return 
 | 
					
						
							|  |  |  | 		// 		via stop(..)...
 | 
					
						
							| 
									
										
										
										
											2019-05-27 19:16:51 +03:00
										 |  |  | 		var b = pattern == 'prev' ? [] : null | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 		return [ | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 			// next + offset...
 | 
					
						
							|  |  |  | 			pattern == 'next' ? | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 				this.search(true,  | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 					function(elem, i, path, stop){ | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 						if(elem.focused == true){ | 
					
						
							| 
									
										
										
										
											2019-05-27 19:16:51 +03:00
										 |  |  | 							b = offset | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 						// get the offset item...
 | 
					
						
							| 
									
										
										
										
											2019-05-27 19:16:51 +03:00
										 |  |  | 						} else if(b != null && b <= 0){ | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 							stop([func(elem, i, path)]) | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 						} | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 						// countdown to offset...
 | 
					
						
							| 
									
										
										
										
											2019-05-27 19:16:51 +03:00
										 |  |  | 						b = typeof(b) == typeof(123) ?  | 
					
						
							|  |  |  | 							b - 1  | 
					
						
							|  |  |  | 							: b }, | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 					options) | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 			// prev + offset...
 | 
					
						
							|  |  |  | 			: pattern == 'prev' ? | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 				this.search(true,  | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 					function(elem, i, path, stop){ | 
					
						
							|  |  |  | 						elem.focused == true | 
					
						
							| 
									
										
										
										
											2019-05-27 19:16:51 +03:00
										 |  |  | 							&& stop([func(...(b.length >= offset ?  | 
					
						
							|  |  |  | 								b[0] | 
					
						
							|  |  |  | 								: [undefined]))]) | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 						// buffer the previous offset items...
 | 
					
						
							| 
									
										
										
										
											2019-05-27 19:16:51 +03:00
										 |  |  | 						b.push([elem, i, path]) | 
					
						
							|  |  |  | 						b.length > offset | 
					
						
							|  |  |  | 							&& b.shift() }, | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 					options) | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 			// get parent element...
 | 
					
						
							|  |  |  | 			: pattern == 'parent' ? | 
					
						
							|  |  |  | 				this.parentOf() | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 			// base case -> get first match...
 | 
					
						
							|  |  |  | 			: this.search(pattern,  | 
					
						
							| 
									
										
										
										
											2019-05-08 04:14:29 +03:00
										 |  |  | 				function(elem, i, path, stop){ | 
					
						
							|  |  |  | 					stop([func(elem, i, path)]) },  | 
					
						
							|  |  |  | 				options) ].flat()[0] }, | 
					
						
							| 
									
										
										
										
											2019-05-02 04:25:58 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// Sublist map functions...
 | 
					
						
							| 
									
										
										
										
											2019-05-10 21:07:17 +03:00
										 |  |  | 	// XXX this does not include inlined sections, should it???
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	sublists: function(func, options){ | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 		return this.search({children: true}, func, options) }, | 
					
						
							| 
									
										
										
										
											2019-03-17 23:30:27 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 	// XXX should these return an array or a .constructor(..) instance??
 | 
					
						
							|  |  |  | 	// XXX should this call .forEach(..) on nested stuff or just fall 
 | 
					
						
							|  |  |  | 	// 		back to .map(..)???
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	forEach: function(func, options){ | 
					
						
							|  |  |  | 		this.map(...arguments) | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2019-05-09 00:20:11 +03:00
										 |  |  | 	filter: function(func, options, context){ | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 		return this.walk( | 
					
						
							| 
									
										
										
										
											2019-05-09 00:20:11 +03:00
										 |  |  | 			function(e, i, p){ | 
					
						
							|  |  |  | 				return e && func.call(this, e, i, p) ? [e] : [] }, | 
					
						
							|  |  |  | 			'filter', | 
					
						
							|  |  |  | 			function(_, i, p, options, context){ | 
					
						
							|  |  |  | 				return [func, options, context] }, | 
					
						
							|  |  |  | 			options, context) }, | 
					
						
							| 
									
										
										
										
											2019-05-10 21:07:17 +03:00
										 |  |  | 	reduce: function(func, start, options){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		var context = arguments[3] || {result: start} | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 		this.walk( | 
					
						
							| 
									
										
										
										
											2019-05-10 21:07:17 +03:00
										 |  |  | 			function(e, i, p){ | 
					
						
							|  |  |  | 				context.result = e ?  | 
					
						
							|  |  |  | 					func.call(that, context.result, e, i, p)  | 
					
						
							|  |  |  | 					: context.result | 
					
						
							|  |  |  | 				return context.result  | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			'reduce', | 
					
						
							|  |  |  | 			function(_, i, p, options, context){ | 
					
						
							|  |  |  | 				return [func, context.result, options, context] | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			options, context) | 
					
						
							|  |  |  | 		return context.result | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-03-17 21:54:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-17 03:12:24 +03:00
										 |  |  | 	// 	
 | 
					
						
							|  |  |  | 	// 	Get parent of .focused
 | 
					
						
							|  |  |  | 	// 	.parentOf()
 | 
					
						
							|  |  |  | 	// 	.parentOf('focused'[, ..])
 | 
					
						
							|  |  |  | 	// 		-> parent
 | 
					
						
							|  |  |  | 	// 		-> this
 | 
					
						
							|  |  |  | 	// 		-> undefined
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Get parent of elem
 | 
					
						
							|  |  |  | 	// 	.parentOf(elem[, ..])
 | 
					
						
							|  |  |  | 	// 		-> parent
 | 
					
						
							|  |  |  | 	// 		-> this
 | 
					
						
							|  |  |  | 	// 		-> undefined
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// Return values:
 | 
					
						
							|  |  |  | 	// 	- element		- actual parent element
 | 
					
						
							|  |  |  | 	// 	- this			- input element is at root of browser
 | 
					
						
							|  |  |  | 	// 	- undefined		- element not found
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this is signature compatible with .get(..) see that for more
 | 
					
						
							|  |  |  | 	// 		docs...
 | 
					
						
							|  |  |  | 	parentOf: function(item, options){ | 
					
						
							|  |  |  | 		item = item == null ? this.focused : item | 
					
						
							|  |  |  | 		if(item == null){ | 
					
						
							|  |  |  | 			return undefined } | 
					
						
							|  |  |  | 		var path = this.pathOf(item) | 
					
						
							|  |  |  | 		return path.length == 1 ? | 
					
						
							|  |  |  | 			this | 
					
						
							|  |  |  | 			: this.get(path.slice(0, -1), options) }, | 
					
						
							| 
									
										
										
										
											2019-05-09 00:20:11 +03:00
										 |  |  | 	positionOf: function(item, options){ | 
					
						
							| 
									
										
										
										
											2019-06-17 03:12:24 +03:00
										 |  |  | 		return this.search(item == null ? this.focused : item,  | 
					
						
							| 
									
										
										
										
											2019-05-09 00:20:11 +03:00
										 |  |  | 			function(_, i, p){  | 
					
						
							|  |  |  | 				return [i, p] },  | 
					
						
							|  |  |  | 			Object.assign( | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					firstMatch: true,  | 
					
						
							|  |  |  | 					noQueryCheck: true, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				options || {})).concat([[-1, undefined]]).shift() }, | 
					
						
							| 
									
										
										
										
											2019-04-24 16:15:41 +03:00
										 |  |  | 	indexOf: function(item, options){ | 
					
						
							| 
									
										
										
										
											2019-05-09 00:20:11 +03:00
										 |  |  | 		return this.positionOf(item, options)[0] }, | 
					
						
							| 
									
										
										
										
											2019-04-24 16:15:41 +03:00
										 |  |  | 	pathOf: function(item, options){ | 
					
						
							| 
									
										
										
										
											2019-05-09 00:20:11 +03:00
										 |  |  | 		return this.positionOf(item, options)[1] }, | 
					
						
							| 
									
										
										
										
											2019-04-24 16:15:41 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-17 03:12:24 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 	// Like .select(.., {iterateCollapsed: true}) but will expand all the 
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:15:41 +03:00
										 |  |  | 	// path items to reveal the target...
 | 
					
						
							| 
									
										
										
										
											2019-05-26 21:56:49 +03:00
										 |  |  | 	// XXX should this return the matched item(s), expanded item(s) or this???
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:15:41 +03:00
										 |  |  | 	reveal: function(key, options){ | 
					
						
							| 
									
										
										
										
											2019-05-10 21:07:17 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2019-05-26 21:56:49 +03:00
										 |  |  | 		var nodes = new Set() | 
					
						
							| 
									
										
										
										
											2019-05-11 00:14:10 +03:00
										 |  |  | 		return this.search(key,  | 
					
						
							|  |  |  | 				function(e, i, path){ | 
					
						
							|  |  |  | 					return [path, e] },  | 
					
						
							|  |  |  | 				Object.assign( | 
					
						
							| 
									
										
										
										
											2019-05-26 21:56:49 +03:00
										 |  |  | 					{ iterateCollapsed: true },  | 
					
						
							| 
									
										
										
										
											2019-05-11 00:14:10 +03:00
										 |  |  | 					options || {})) | 
					
						
							| 
									
										
										
										
											2019-05-26 21:56:49 +03:00
										 |  |  | 			// NOTE: we expand individual items so the order here is not relevant...
 | 
					
						
							| 
									
										
										
										
											2019-05-11 00:14:10 +03:00
										 |  |  | 			.map(function([path, e]){ | 
					
						
							| 
									
										
										
										
											2019-06-13 02:54:41 +03:00
										 |  |  | 				// get all collapsed items in path...
 | 
					
						
							|  |  |  | 				path | 
					
						
							|  |  |  | 					.slice(0, -1) | 
					
						
							|  |  |  | 					.forEach(function(_, i){ | 
					
						
							|  |  |  | 						var p = that.index[path.slice(0, i+1).join('/')] | 
					
						
							|  |  |  | 						p.collapsed | 
					
						
							|  |  |  | 							&& nodes.add(p) }) | 
					
						
							| 
									
										
										
										
											2019-05-26 21:56:49 +03:00
										 |  |  | 				return e }) | 
					
						
							|  |  |  | 			// do the actual expansion...
 | 
					
						
							| 
									
										
										
										
											2019-05-11 00:14:10 +03:00
										 |  |  | 			.run(function(){ | 
					
						
							| 
									
										
										
										
											2019-06-13 02:54:41 +03:00
										 |  |  | 				nodes.size > 0 | 
					
						
							|  |  |  | 					&& that.expand([...nodes]) }) }, | 
					
						
							| 
									
										
										
										
											2019-04-24 16:15:41 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-17 21:54:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// Renderers...
 | 
					
						
							| 
									
										
										
										
											2019-03-18 03:58:23 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 	// 	.renderFinalize(header, items, footer, context)
 | 
					
						
							|  |  |  | 	// 	.renderList(header, items, footer, context)
 | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 	// 	.renderNested(header, children, item, context)
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// 	.renderNestedHeader(item, i, context)
 | 
					
						
							|  |  |  | 	// 	.renderItem(item, i, context)
 | 
					
						
							|  |  |  | 	// 	.renderGroup(items, context)
 | 
					
						
							| 
									
										
										
										
											2019-03-18 03:58:23 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-31 00:07:36 +03:00
										 |  |  | 	// NOTE: there are not to be used directly...
 | 
					
						
							|  |  |  | 	// XXX might be a good idea to move these into a separate renderer 
 | 
					
						
							|  |  |  | 	// 		object (mixin or encapsulated)...
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 	renderFinalize: function(header, items, footer, context){ | 
					
						
							|  |  |  | 		return this.renderList(header, items, footer, context) }, | 
					
						
							|  |  |  | 	renderList: function(header, items, footer, context){ | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 		return items }, | 
					
						
							|  |  |  | 	// NOTE: to skip rendering an item/list return null...
 | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 	// XXX should this take an empty children???
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// 		...this would make it simpler to expand/collapse without 
 | 
					
						
							|  |  |  | 	// 		re-rendering the whole list...
 | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 	renderNested: function(header, children, item, context){ | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 		return header ?  | 
					
						
							|  |  |  | 			this.renderGroup([ | 
					
						
							|  |  |  | 				header,  | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 				children, | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 			]) | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  |    			: children }, | 
					
						
							| 
									
										
										
										
											2019-06-02 22:57:15 +03:00
										 |  |  | 	// XXX use a real blank item...
 | 
					
						
							|  |  |  | 	renderNestedBlank(children, i, context){ | 
					
						
							|  |  |  | 		var elem = {value: '   '} | 
					
						
							|  |  |  | 		return this.renderNested( | 
					
						
							|  |  |  | 			this.renderNestedHeader(elem, i, context), | 
					
						
							|  |  |  | 			children,  | 
					
						
							|  |  |  | 			elem,  | 
					
						
							|  |  |  | 			context) }, | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	renderNestedHeader: function(item, i, context){ | 
					
						
							|  |  |  | 		return this.renderItem(item, i, context) }, | 
					
						
							|  |  |  | 	// NOTE: to skip rendering an item/list return null...
 | 
					
						
							|  |  |  | 	renderItem: function(item, i, context){ | 
					
						
							|  |  |  | 		return item }, | 
					
						
							|  |  |  | 	renderGroup: function(items, context){ | 
					
						
							|  |  |  | 		return items }, | 
					
						
							| 
									
										
										
										
											2019-03-18 03:58:23 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// Render state...
 | 
					
						
							| 
									
										
										
										
											2019-04-22 02:16:30 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	//	.render()
 | 
					
						
							| 
									
										
										
										
											2019-05-12 12:59:48 +03:00
										 |  |  | 	//	.render(options[, renderer[, context]])
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	//		-> state
 | 
					
						
							| 
									
										
										
										
											2019-04-21 16:54:19 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-22 02:16:30 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// context format:
 | 
					
						
							| 
									
										
										
										
											2019-04-22 02:16:30 +03:00
										 |  |  | 	// 	{
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// 		root: <root-browser>,
 | 
					
						
							|  |  |  | 	// 		options: <options>,
 | 
					
						
							| 
									
										
										
										
											2019-06-02 17:36:04 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-02 17:59:30 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		// These are the same as in options...
 | 
					
						
							|  |  |  | 	// 		//
 | 
					
						
							|  |  |  | 	// 		// NOTE: these will get set to the item indexes...
 | 
					
						
							|  |  |  | 	// 		from: <index> | <query>,
 | 
					
						
							|  |  |  | 	// 		to: <index> | <query>,
 | 
					
						
							|  |  |  | 	// 		// optional...
 | 
					
						
							|  |  |  | 	// 		// NOTE: in general we set these in options...
 | 
					
						
							|  |  |  | 	// 		//around: <index> | <query>,
 | 
					
						
							|  |  |  | 	// 		//count: <number>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// options:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							| 
									
										
										
										
											2019-06-02 17:36:04 +03:00
										 |  |  | 	// 		// Partial render parameters...
 | 
					
						
							|  |  |  | 	//		//
 | 
					
						
							|  |  |  | 	// 		// supported combinations:
 | 
					
						
							|  |  |  | 	// 		//	- from, to
 | 
					
						
							|  |  |  | 	// 		//	- from, count
 | 
					
						
							|  |  |  | 	// 		//	- to, count
 | 
					
						
							|  |  |  | 	// 		//	- around, count
 | 
					
						
							|  |  |  | 	// 		//
 | 
					
						
							|  |  |  | 	// 		// NOTE: the only constrain on to/from is that from must be 
 | 
					
						
							|  |  |  | 	// 		//		less or equal to to, other than that it's fair game,
 | 
					
						
							|  |  |  | 	// 		//		i.e. overflowing values (<0 or >length) are allowed.
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	// 		// NOTE: these are not inherited from .options...
 | 
					
						
							| 
									
										
										
										
											2019-06-02 17:36:04 +03:00
										 |  |  | 	// 		from: <index> | <query>,
 | 
					
						
							|  |  |  | 	// 		to: <index> | <query>,
 | 
					
						
							|  |  |  | 	// 		around: <index> | <query>,
 | 
					
						
							|  |  |  | 	// 		count: <number>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-04-25 03:25:03 +03:00
										 |  |  | 	// 		nonFinalized: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//		// for more supported options see: .walk(..)
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-27 01:40:04 +03:00
										 |  |  | 	// NOTE: there is no need to explicitly .make(..) the state before
 | 
					
						
							|  |  |  | 	// 		calling this as first access to .items will do so automatically...
 | 
					
						
							| 
									
										
										
										
											2019-04-25 03:25:03 +03:00
										 |  |  | 	// NOTE: calling this will re-render the existing state. to re-make 
 | 
					
						
							|  |  |  | 	// 		the state anew that use .update(..)...
 | 
					
						
							| 
									
										
										
										
											2019-05-27 01:40:04 +03:00
										 |  |  | 	// NOTE: it is not recommended to extend this. all the responsibility
 | 
					
						
							|  |  |  | 	// 		of actual rendering should lay on the renderer methods...
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	// NOTE: currently options and context are distinguished only via 
 | 
					
						
							|  |  |  | 	// 		the .options attribute...
 | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX use partial render for things like search....
 | 
					
						
							| 
									
										
										
										
											2019-06-12 21:45:51 +03:00
										 |  |  | 	// XXX make partial render be lazy -- i.e. add/remove elements and 
 | 
					
						
							|  |  |  | 	// 		do not reconstruct the ones already present...
 | 
					
						
							| 
									
										
										
										
											2019-06-25 03:34:29 +03:00
										 |  |  | 	// XXX should from/to/around/count be a feature of this or of .walk(..)???
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 	// XXX render all the sections at root... (???)
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 	render: function(options, renderer, context){ | 
					
						
							|  |  |  | 		context = context || {} | 
					
						
							| 
									
										
										
										
											2019-04-22 02:16:30 +03:00
										 |  |  | 		renderer = renderer || this | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 		options = context.options = context.options  | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 			|| Object.assign( | 
					
						
							|  |  |  | 				Object.create(this.options || {}), | 
					
						
							|  |  |  | 				{ iterateNonIterable: true },  | 
					
						
							|  |  |  | 				options || {}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 		var section = options.section || '*' | 
					
						
							|  |  |  | 		section = section == '*' ? | 
					
						
							|  |  |  | 			options.sections | 
					
						
							|  |  |  | 			: section | 
					
						
							|  |  |  | 		section = section instanceof Array && section.length == 1 ? | 
					
						
							|  |  |  | 			section[0] | 
					
						
							|  |  |  | 			: section | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-02 17:36:04 +03:00
										 |  |  | 		// build range bounds...
 | 
					
						
							| 
									
										
										
										
											2019-06-02 17:59:30 +03:00
										 |  |  | 		// use .get(..) on full (non-partial) range...
 | 
					
						
							|  |  |  | 		var get_options = Object.assign( | 
					
						
							|  |  |  | 			Object.create(options), | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 			// XXX for some magical reason if we do not explicitly include 
 | 
					
						
							|  |  |  | 			// 		.iterateNonIterable here it is not seen down the line...
 | 
					
						
							|  |  |  | 			{from: null, to: null, around: null, | 
					
						
							|  |  |  | 				iterateNonIterable: options.iterateNonIterable}) | 
					
						
							|  |  |  | 			//{from: null, to: null, around: null, count: null})
 | 
					
						
							| 
									
										
										
										
											2019-06-02 17:59:30 +03:00
										 |  |  | 		// index getter...
 | 
					
						
							| 
									
										
										
										
											2019-06-02 17:36:04 +03:00
										 |  |  | 		var normIndex = function(i){ | 
					
						
							|  |  |  | 			return (i === undefined || typeof(i) == typeof(123)) ? | 
					
						
							|  |  |  | 				i | 
					
						
							| 
									
										
										
										
											2019-06-02 21:16:23 +03:00
										 |  |  | 				: this.get(i,  | 
					
						
							|  |  |  | 					function(_, i){ return i },  | 
					
						
							|  |  |  | 					get_options) }.bind(this) | 
					
						
							| 
									
										
										
										
											2019-06-02 17:59:30 +03:00
										 |  |  | 		// NOTE: we prefer context.from / context.to as they are more 
 | 
					
						
							|  |  |  | 		// 		likely to be normalized.
 | 
					
						
							|  |  |  | 		// 		as to the rest of the values of set we look first in the 
 | 
					
						
							|  |  |  | 		// 		options as we'll need them only if from/to are not 
 | 
					
						
							|  |  |  | 		// 		normalized...
 | 
					
						
							| 
									
										
										
										
											2019-06-02 21:16:23 +03:00
										 |  |  | 		var from = context.from = normIndex('from' in context ? context.from : options.from) | 
					
						
							|  |  |  | 		var to = context.to = normIndex('to' in context ? context.to : options.to) | 
					
						
							|  |  |  | 		var around = normIndex('around' in options ? options.around : context.around) | 
					
						
							|  |  |  | 		var count = 'count' in options ? options.count : context.count | 
					
						
							| 
									
										
										
										
											2019-06-02 17:59:30 +03:00
										 |  |  | 		// NOTE: count < 0 is the same as no count / all...
 | 
					
						
							|  |  |  | 		count = count < 0 ?  | 
					
						
							| 
									
										
										
										
											2019-06-02 17:36:04 +03:00
										 |  |  | 			null  | 
					
						
							| 
									
										
										
										
											2019-06-02 17:59:30 +03:00
										 |  |  | 			: count | 
					
						
							| 
									
										
										
										
											2019-06-02 17:36:04 +03:00
										 |  |  | 		// complete to/from based on count and/or around...
 | 
					
						
							|  |  |  | 		// NOTE: we do not care about overflow here...
 | 
					
						
							|  |  |  | 		;(from == null && count != null)  | 
					
						
							|  |  |  | 			&& (from = context.from =  | 
					
						
							|  |  |  | 				to != null ?  | 
					
						
							|  |  |  | 					to - count | 
					
						
							|  |  |  | 				: around != null ? | 
					
						
							|  |  |  | 					around - Math.floor(count/2) | 
					
						
							|  |  |  | 				: from) | 
					
						
							|  |  |  | 		;(to == null && count != null) | 
					
						
							|  |  |  | 			&& (to = context.to =  | 
					
						
							|  |  |  | 				from != null ?  | 
					
						
							|  |  |  | 					from + count | 
					
						
							|  |  |  | 				: around != null ? | 
					
						
							|  |  |  | 					around + Math.ceil(count/2) | 
					
						
							|  |  |  | 				: to) | 
					
						
							|  |  |  | 		// sanity check...
 | 
					
						
							|  |  |  | 		if(from != null && to != null && to < from){ | 
					
						
							|  |  |  | 			throw new Error(`.render(..): context.from must be less than ` | 
					
						
							|  |  |  | 				+`or equal to context.to. (got: from=${from} and to=${to})`) } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-02 21:16:23 +03:00
										 |  |  | 		// XXX use this to check if an item is on the path to <from> and
 | 
					
						
							|  |  |  | 		// 		pass it to the skipped topology constructor...
 | 
					
						
							| 
									
										
										
										
											2019-06-02 22:57:15 +03:00
										 |  |  | 		var from_path = context.from_path = | 
					
						
							|  |  |  | 			context.from_path	 | 
					
						
							|  |  |  | 				|| (from != null  | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 					&& this.get(from,  | 
					
						
							|  |  |  | 						function(e, i, p){ return p },  | 
					
						
							|  |  |  | 						get_options)) | 
					
						
							| 
									
										
										
										
											2019-06-02 21:16:23 +03:00
										 |  |  | 		from_path = from_path instanceof Array | 
					
						
							|  |  |  | 			&& from_path | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 		// root call -> build sections (calling .render(..) per section)...
 | 
					
						
							|  |  |  | 		if(context.root == null && section instanceof Array){ | 
					
						
							|  |  |  | 			context.root = this | 
					
						
							|  |  |  | 			var s= {} | 
					
						
							|  |  |  | 			section | 
					
						
							|  |  |  | 				.forEach(function(name){ | 
					
						
							|  |  |  | 					s[name] = this.render( | 
					
						
							|  |  |  | 						Object.assign( | 
					
						
							|  |  |  | 							Object.create(options), | 
					
						
							|  |  |  | 							{ | 
					
						
							|  |  |  | 								section: name, | 
					
						
							|  |  |  | 								nonFinalized: true, | 
					
						
							|  |  |  | 							}),  | 
					
						
							|  |  |  | 						renderer) }.bind(this)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return (!options.nonFinalized && context.root === this) ? | 
					
						
							|  |  |  | 				renderer.renderFinalize(s.header, s.items, s.footer, context) | 
					
						
							|  |  |  | 				: s | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// build specific sections...
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			// do the walk...
 | 
					
						
							|  |  |  | 			var items = this.walk( | 
					
						
							|  |  |  | 				function(elem, i, path, nested){ | 
					
						
							|  |  |  | 					return ( | 
					
						
							|  |  |  | 						// special case: nested <from> elem -> topology only...
 | 
					
						
							|  |  |  | 						(from_path  | 
					
						
							|  |  |  | 								&& i < from  | 
					
						
							|  |  |  | 								// only for nested...
 | 
					
						
							|  |  |  | 								&& elem && elem.children | 
					
						
							|  |  |  | 								// only sub-path...
 | 
					
						
							|  |  |  | 								&& path.cmp(from_path.slice(0, path.length))) ? | 
					
						
							|  |  |  | 							[ renderer.renderNestedBlank(nested(), i, context) ] | 
					
						
							|  |  |  | 						// out of range -> skip...
 | 
					
						
							|  |  |  | 						: ((from != null && i < from)  | 
					
						
							|  |  |  | 								|| (to != null && i >= to)) ? | 
					
						
							|  |  |  | 							[] | 
					
						
							|  |  |  | 						// inline...
 | 
					
						
							|  |  |  | 						: elem == null ? | 
					
						
							|  |  |  | 							// NOTE: here we are forcing rendering of the 
 | 
					
						
							|  |  |  | 							// 		inline browser/list, i.e. ignoring 
 | 
					
						
							|  |  |  | 							// 		options.skipNested for inline stuff...
 | 
					
						
							|  |  |  | 							[ renderer.renderGroup(nested(true), context) ] | 
					
						
							|  |  |  | 						// nested...
 | 
					
						
							|  |  |  | 						: elem.children ? | 
					
						
							|  |  |  | 							[ renderer.renderNested( | 
					
						
							|  |  |  | 								renderer.renderNestedHeader(elem, i, context), | 
					
						
							|  |  |  | 								nested(), | 
					
						
							|  |  |  | 								elem,  | 
					
						
							|  |  |  | 								context) ] | 
					
						
							|  |  |  | 						// normal elem...
 | 
					
						
							|  |  |  | 						: [ renderer.renderItem(elem, i, context) ] ) }, | 
					
						
							|  |  |  | 				'render', | 
					
						
							|  |  |  | 				function(_, i, p, options, context){ | 
					
						
							|  |  |  | 					return [options, renderer, context] }, | 
					
						
							|  |  |  | 				options, context)  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// finalize depending on render mode...
 | 
					
						
							|  |  |  | 			return (!options.nonFinalized && context.root === this) ? | 
					
						
							|  |  |  | 				// root context -> render list and return this...
 | 
					
						
							|  |  |  | 				renderer.renderFinalize(null, items, null, context) | 
					
						
							|  |  |  | 				// nested context -> return item list...
 | 
					
						
							|  |  |  | 				: items } }, | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	 | 
					
						
							| 
									
										
										
										
											2019-04-24 04:17:07 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-27 03:35:03 +03:00
										 |  |  | 	// Events...
 | 
					
						
							| 
									
										
										
										
											2019-02-26 04:40:38 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// Format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							|  |  |  | 	// 		<event-name>: [
 | 
					
						
							|  |  |  | 	// 			<handler>,
 | 
					
						
							|  |  |  | 	// 			...
 | 
					
						
							|  |  |  | 	// 		],
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-27 05:09:01 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: event handlers may have a .tag attribute that stores the tag
 | 
					
						
							|  |  |  | 	// 		it was created with, this is used by .off(..) to unbind handlers
 | 
					
						
							|  |  |  | 	// 		tagged with specific tags...
 | 
					
						
							| 
									
										
										
										
											2019-02-26 04:13:00 +03:00
										 |  |  | 	__event_handlers: null, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | 	// List events...
 | 
					
						
							|  |  |  | 	get events(){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 		// props to skip...
 | 
					
						
							|  |  |  | 		// XXX should we just skip any prop???
 | 
					
						
							|  |  |  | 		var skip = new Set([ | 
					
						
							|  |  |  | 			'events' | 
					
						
							|  |  |  | 		]) | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | 		return Object.deepKeys(this) | 
					
						
							|  |  |  | 			.map(function(key){ | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 				return (!skip.has(key)  | 
					
						
							| 
									
										
										
										
											2019-05-26 18:30:31 +03:00
										 |  |  | 						&& that[key] instanceof Function  | 
					
						
							|  |  |  | 						&& that[key].event) ?  | 
					
						
							|  |  |  | 					that[key].event  | 
					
						
							|  |  |  | 					: [] }) | 
					
						
							|  |  |  | 			.flat() }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-24 00:18:53 +03:00
										 |  |  | 	// Generic event infrastructure...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Bind a handler to an event...
 | 
					
						
							|  |  |  | 	// 	.on(event, func)
 | 
					
						
							|  |  |  | 	// 	.on(event, func, tag)
 | 
					
						
							|  |  |  | 	// 		-> this
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// tag can be used to unregister several handlers in a single operation,
 | 
					
						
							|  |  |  | 	// see .off(..) for more info...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: .one(..) has the same signature as .on(..) but will unregister 
 | 
					
						
							|  |  |  | 	// 		the handler as soon as it is done...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX should we be able to trigger events from the item directly???
 | 
					
						
							|  |  |  | 	// 		i.e. .get(42).on('open', ...) instead of .get(42).open = ...
 | 
					
						
							| 
									
										
										
										
											2019-05-27 01:40:04 +03:00
										 |  |  | 	// 		...might be a good idea to create an item wrapper object...
 | 
					
						
							| 
									
										
										
										
											2019-05-17 19:53:33 +03:00
										 |  |  | 	on: function(evt, handler, tag){ | 
					
						
							| 
									
										
										
										
											2019-02-26 04:13:00 +03:00
										 |  |  | 		var handlers = this.__event_handlers = this.__event_handlers || {} | 
					
						
							|  |  |  | 		handlers = handlers[evt] = handlers[evt] || [] | 
					
						
							|  |  |  | 		handlers.push(handler) | 
					
						
							| 
									
										
										
										
											2019-05-17 19:53:33 +03:00
										 |  |  | 		tag | 
					
						
							|  |  |  | 			&& (handler.tag = tag) | 
					
						
							| 
									
										
										
										
											2019-02-24 14:51:35 +03:00
										 |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-05-24 00:18:53 +03:00
										 |  |  | 	one: function(evt, handler, tag){ | 
					
						
							| 
									
										
										
										
											2019-02-26 04:13:00 +03:00
										 |  |  | 		var func = function(...args){ | 
					
						
							|  |  |  | 			handler.call(this, ...args) | 
					
						
							|  |  |  | 			this.off(evt, func) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-05-24 00:18:53 +03:00
										 |  |  | 		this.on(evt, func, tag) | 
					
						
							| 
									
										
										
										
											2019-02-24 14:51:35 +03:00
										 |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-05-17 19:53:33 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Clear all event handlers...
 | 
					
						
							|  |  |  | 	//	.off('*')
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Clear all event handlers from evt(s)...
 | 
					
						
							|  |  |  | 	//	.off(evt)
 | 
					
						
							|  |  |  | 	//	.off([evt, ..])
 | 
					
						
							|  |  |  | 	//	.off(evt, '*')
 | 
					
						
							|  |  |  | 	//	.off([evt, ..], '*')
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Clear handler of evt(s)...
 | 
					
						
							|  |  |  | 	//	.off(evt, handler)
 | 
					
						
							|  |  |  | 	//	.off([evt, ..], handler)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Clear all handlers tagged with tag of evt(s)...
 | 
					
						
							|  |  |  | 	//	.off(evt, tag)
 | 
					
						
							|  |  |  | 	//	.off([evt, ..], tag)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: evt can be '*' or 'all' to indicate all events.
 | 
					
						
							| 
									
										
										
										
											2019-02-24 14:51:35 +03:00
										 |  |  | 	off: function(evt, handler){ | 
					
						
							| 
									
										
										
										
											2019-05-17 19:53:33 +03:00
										 |  |  | 		if(arguments.length == 0){ | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-18 02:58:38 +03:00
										 |  |  | 		var handlers = this.__event_handlers || {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-17 19:53:33 +03:00
										 |  |  | 		// parse args...
 | 
					
						
							|  |  |  | 		handler = handler || '*' | 
					
						
							|  |  |  | 		evt =  | 
					
						
							|  |  |  | 			// all events / direct handler...
 | 
					
						
							|  |  |  | 			(!(evt in handlers)  | 
					
						
							|  |  |  | 					|| evt == '*'  | 
					
						
							|  |  |  | 					|| evt == 'all') ?  | 
					
						
							|  |  |  | 				Object.keys(handlers)  | 
					
						
							|  |  |  | 			// list of events...
 | 
					
						
							|  |  |  | 			: evt instanceof Array ? | 
					
						
							|  |  |  | 				evt | 
					
						
							|  |  |  | 			// explicit event...
 | 
					
						
							|  |  |  | 			: [evt] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-26 04:13:00 +03:00
										 |  |  | 		// remove all handlers
 | 
					
						
							| 
									
										
										
										
											2019-05-17 19:53:33 +03:00
										 |  |  | 		handler == '*' || handler == 'all' ? | 
					
						
							|  |  |  | 			evt | 
					
						
							|  |  |  | 				.forEach(function(evt){ | 
					
						
							|  |  |  | 					delete handlers[evt] }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// remove tagged handlers...
 | 
					
						
							|  |  |  | 		: typeof(handler) == typeof('str') ? | 
					
						
							|  |  |  | 			evt | 
					
						
							|  |  |  | 				.forEach(function(evt){ | 
					
						
							|  |  |  | 					var h = handlers[evt] || [] | 
					
						
							|  |  |  | 					var l = h.length | 
					
						
							|  |  |  | 					h | 
					
						
							|  |  |  | 						.slice() | 
					
						
							|  |  |  | 						.reverse() | 
					
						
							|  |  |  | 						.forEach(function(e, i){  | 
					
						
							|  |  |  | 							e.tag == handler | 
					
						
							|  |  |  | 								&& h.splice(l-i-1, 1) }) }) | 
					
						
							| 
									
										
										
										
											2019-02-26 04:13:00 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// remove only the specific handler...
 | 
					
						
							| 
									
										
										
										
											2019-05-17 19:53:33 +03:00
										 |  |  | 		: evt | 
					
						
							|  |  |  | 			.forEach(function(evt){ | 
					
						
							|  |  |  | 				var h = handlers[evt] || [] | 
					
						
							|  |  |  | 				do{ | 
					
						
							|  |  |  | 					var i = h.indexOf(handler) | 
					
						
							|  |  |  | 					i > -1 | 
					
						
							|  |  |  | 						&& h.splice(i, 1) | 
					
						
							|  |  |  | 				} while(i > -1) }) | 
					
						
							| 
									
										
										
										
											2019-02-24 14:51:35 +03:00
										 |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-05-18 02:58:38 +03:00
										 |  |  | 	// 
 | 
					
						
							|  |  |  | 	// 	Trigger an event by name...
 | 
					
						
							|  |  |  | 	// 	.trigger(<event-name>, ..)
 | 
					
						
							|  |  |  | 	// 		-> this
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Trigger an event...
 | 
					
						
							|  |  |  | 	// 	.trigger(<event-object>, ..)
 | 
					
						
							|  |  |  | 	// 		-> this
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-18 03:15:03 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-03 19:22:01 +03:00
										 |  |  | 	// Optional event extension methods:
 | 
					
						
							|  |  |  | 	// 	Event shorthand 
 | 
					
						
							|  |  |  | 	// 	.<event-name>(..)
 | 
					
						
							|  |  |  | 	// 		called by .trigger(<event-name>, ..)
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 		create <event-object>
 | 
					
						
							|  |  |  | 	// 		call .trigger(<event-object>, ..)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		Used for:
 | 
					
						
							|  |  |  | 	// 			- shorthand to .trigger(<event-name>, ..)
 | 
					
						
							|  |  |  | 	// 			- shorthand to .on(<event-name>, ..) 
 | 
					
						
							|  |  |  | 	// 			- base event functionality
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		See: makeEventMethod(..) and makeItemEventMethod(..) for docs.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Base event handler
 | 
					
						
							|  |  |  | 	// 	.__<event-name>__(event, ..)
 | 
					
						
							|  |  |  | 	// 		called by .trigger(<event-object>, ..) as the first handler
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		Used as system event handler that can not be removed via 
 | 
					
						
							|  |  |  | 	// 		.off(..)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-18 02:58:38 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-23 16:07:57 +03:00
										 |  |  | 	// for docs on <event-object> see BrowserEvent(..)
 | 
					
						
							| 
									
										
										
										
											2019-02-24 14:51:35 +03:00
										 |  |  | 	trigger: function(evt, ...args){ | 
					
						
							| 
									
										
										
										
											2019-02-26 04:13:00 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2019-05-18 02:58:38 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// trigger the appropriate event handler if available...
 | 
					
						
							|  |  |  | 		// NOTE: this makes .someEvent(..) and .trigger('someEvent', ..)
 | 
					
						
							|  |  |  | 		// 		do the same thing by always triggering .someEvent(..) 
 | 
					
						
							|  |  |  | 		// 		first and letting it decide how to call .trigger(..)...
 | 
					
						
							|  |  |  | 		// NOTE: the event method should pass a fully formed event object
 | 
					
						
							|  |  |  | 		// 		into trigger when it requires to call the handlers...
 | 
					
						
							|  |  |  | 		if(typeof(evt) == typeof('str')  | 
					
						
							|  |  |  | 				&& this[evt] instanceof Function | 
					
						
							|  |  |  | 				&& this[evt].event == evt){ | 
					
						
							|  |  |  | 			this[evt](...args) | 
					
						
							|  |  |  | 			return this | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-05-23 16:07:57 +03:00
										 |  |  | 		// propagation is stopped...
 | 
					
						
							|  |  |  | 		// XXX expand this check to support DOM events...
 | 
					
						
							|  |  |  | 		if(evt.propagationStopped || evt.cancelBubble){ | 
					
						
							|  |  |  | 			return this | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-05-18 02:58:38 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-26 04:20:05 +03:00
										 |  |  | 		var evt = typeof(evt) == typeof('str') ? | 
					
						
							| 
									
										
										
										
											2019-05-23 16:07:57 +03:00
										 |  |  | 			new BrowserEvent(evt) | 
					
						
							| 
									
										
										
										
											2019-02-26 04:20:05 +03:00
										 |  |  | 			: evt | 
					
						
							| 
									
										
										
										
											2019-03-06 16:06:48 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// call the main set of handlers...
 | 
					
						
							| 
									
										
										
										
											2019-02-26 04:20:05 +03:00
										 |  |  | 		;((this.__event_handlers || {})[evt.name] || []) | 
					
						
							| 
									
										
										
										
											2019-02-26 04:40:38 +03:00
										 |  |  | 			// prevent .off(..) from affecting the call loop...
 | 
					
						
							|  |  |  | 			.slice() | 
					
						
							| 
									
										
										
										
											2019-06-03 19:22:01 +03:00
										 |  |  | 			// add the static .__<event>__(..) handler if present...
 | 
					
						
							|  |  |  | 			.concat([this[`__${evt.name}__`] || []].flat()) | 
					
						
							|  |  |  | 			// call handlers...
 | 
					
						
							| 
									
										
										
										
											2019-02-26 04:13:00 +03:00
										 |  |  | 			.forEach(function(handler){ | 
					
						
							| 
									
										
										
										
											2019-02-26 04:20:05 +03:00
										 |  |  | 				handler.call(that, evt, ...args) }) | 
					
						
							| 
									
										
										
										
											2019-03-06 16:06:48 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// trigger the parent's event...
 | 
					
						
							| 
									
										
										
										
											2019-05-23 16:07:57 +03:00
										 |  |  | 		!(evt.propagationStopped || evt.cancelBubble) | 
					
						
							| 
									
										
										
										
											2019-03-06 16:06:48 +03:00
										 |  |  | 			&& this.parent | 
					
						
							| 
									
										
										
										
											2019-05-18 02:58:38 +03:00
										 |  |  | 			&& this.parent.trigger instanceof Function | 
					
						
							| 
									
										
										
										
											2019-05-23 16:07:57 +03:00
										 |  |  | 			// XXX should we pass trigger and event object or event name???
 | 
					
						
							|  |  |  | 			&& this.parent.trigger(evt, ...args) | 
					
						
							| 
									
										
										
										
											2019-03-06 16:06:48 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-24 14:51:35 +03:00
										 |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-24 00:18:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-26 04:40:38 +03:00
										 |  |  | 	// domain events/actions...
 | 
					
						
							| 
									
										
										
										
											2019-05-24 00:18:53 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Bind a handler to an event...
 | 
					
						
							|  |  |  | 	// 	.focus(func)
 | 
					
						
							|  |  |  | 	// 		-> this
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Trigger an event...
 | 
					
						
							|  |  |  | 	// 	.focus(query[, ...])
 | 
					
						
							|  |  |  | 	// 		-> this
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-27 20:09:56 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this will ignore disabled items.
 | 
					
						
							|  |  |  | 	// NOTE: .focus('next') / .focus('prev') will not wrap around the 
 | 
					
						
							|  |  |  | 	// 		first last elements...
 | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | 	// NOTE: if focus does not change this will trigger any handlers...
 | 
					
						
							| 
									
										
										
										
											2019-06-13 02:54:41 +03:00
										 |  |  | 	// NOTE: this will reveal the focused item...
 | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | 	focus: makeItemEventMethod('focus', { | 
					
						
							|  |  |  | 		handler: function(evt, items){ | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 			var item = items.shift() | 
					
						
							| 
									
										
										
										
											2019-05-26 03:38:38 +03:00
										 |  |  | 			// blur .focused...
 | 
					
						
							|  |  |  | 			this.focused | 
					
						
							|  |  |  | 				&& this.blur(this.focused) | 
					
						
							|  |  |  | 			// NOTE: if we got multiple matches we care only about the first one...
 | 
					
						
							|  |  |  | 			item != null | 
					
						
							| 
									
										
										
										
											2019-06-13 02:54:41 +03:00
										 |  |  | 				&& this.reveal(item) | 
					
						
							|  |  |  | 				&& (item.focused = true) }, | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | 		default_item: function(){ return this.get(0) }, | 
					
						
							| 
									
										
										
										
											2019-06-25 17:41:12 +03:00
										 |  |  | 		options: function(){ | 
					
						
							|  |  |  | 			return { | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 				skipDisabled: !(this.options || {}).focusDisabledItems, | 
					
						
							| 
									
										
										
										
											2019-06-25 17:41:12 +03:00
										 |  |  | 			} }, | 
					
						
							| 
									
										
										
										
											2019-06-16 02:55:27 +03:00
										 |  |  | 		getter: 'get' }), | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | 	blur: makeItemEventMethod('blur', { | 
					
						
							|  |  |  | 		handler: function(evt, items){ | 
					
						
							| 
									
										
										
										
											2019-06-10 04:07:55 +03:00
										 |  |  | 			items.forEach(function(item){ | 
					
						
							|  |  |  | 				delete item.focused }) }, | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | 		default_item: function(){ return this.focused } }), | 
					
						
							| 
									
										
										
										
											2019-06-30 01:42:41 +03:00
										 |  |  | 	toggleFocus: makeItemEventToggler( | 
					
						
							|  |  |  | 		'focused',  | 
					
						
							|  |  |  | 		'focus', 'blur',  | 
					
						
							|  |  |  | 		function(){ return this.focused || 0 },  | 
					
						
							|  |  |  | 		false), | 
					
						
							| 
									
										
										
										
											2019-06-16 21:20:34 +03:00
										 |  |  | 	// NOTE: .next() / .prev() will wrap around the first/last elements,
 | 
					
						
							|  |  |  | 	// 		this is different from .focus('next') / .focus('prev')...
 | 
					
						
							| 
									
										
										
										
											2019-06-30 01:42:41 +03:00
										 |  |  | 	// NOTE: these also differ from focus in that they will only go 
 | 
					
						
							|  |  |  | 	// 		through the main section...
 | 
					
						
							| 
									
										
										
										
											2019-06-30 02:15:35 +03:00
										 |  |  | 	next: function(options){  | 
					
						
							|  |  |  | 		options = Object.assign( | 
					
						
							|  |  |  | 			{ skipDisabled: !(this.options || {}).focusDisabledItems }, | 
					
						
							|  |  |  | 			options || {}) | 
					
						
							| 
									
										
										
										
											2019-06-30 02:21:05 +03:00
										 |  |  | 		return this.focus(this.get('next', options) || this.get('first', options)) }, | 
					
						
							| 
									
										
										
										
											2019-06-30 02:15:35 +03:00
										 |  |  | 	prev: function(options){  | 
					
						
							|  |  |  | 		options = Object.assign( | 
					
						
							|  |  |  | 			{ skipDisabled: !(this.options || {}).focusDisabledItems }, | 
					
						
							|  |  |  | 			options || {}) | 
					
						
							| 
									
										
										
										
											2019-06-30 02:21:05 +03:00
										 |  |  | 		return this.focus(this.get('prev', options) || this.get('last', options)) }, | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 	// selection...
 | 
					
						
							| 
									
										
										
										
											2019-06-26 12:53:55 +03:00
										 |  |  | 	select: makeItemOptionOnEventMethod('select', 'selected', { | 
					
						
							|  |  |  | 		options: function(){ | 
					
						
							|  |  |  | 			return { | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 				skipDisabled: !(this.options || {}).focusDisabledItems, | 
					
						
							| 
									
										
										
										
											2019-06-26 12:53:55 +03:00
										 |  |  | 			} }, }), | 
					
						
							|  |  |  | 	deselect: makeItemOptionOffEventMethod('deselect', 'selected', { | 
					
						
							|  |  |  | 		options: { skipDisabled: false }, }), | 
					
						
							| 
									
										
										
										
											2019-06-16 21:20:34 +03:00
										 |  |  | 	toggleSelect: makeItemEventToggler('selected', 'select', 'deselect', 'focused'), | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 	// topology...
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	collapse: makeItemOptionOnEventMethod('collapse', 'collapsed', { | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 		filter: function(elem){ return elem.value && elem.children }, | 
					
						
							|  |  |  | 		options: {iterateCollapsed: true}, }), | 
					
						
							|  |  |  | 	expand: makeItemOptionOffEventMethod('expand', 'collapsed', { | 
					
						
							|  |  |  | 		filter: function(elem){ return elem.value && elem.children }, | 
					
						
							|  |  |  | 		options: {iterateCollapsed: true}, }), | 
					
						
							| 
									
										
										
										
											2019-05-26 20:20:18 +03:00
										 |  |  | 	toggleCollapse: makeItemEventToggler( | 
					
						
							|  |  |  | 		'collapsed',  | 
					
						
							|  |  |  | 		'collapse', 'expand',  | 
					
						
							|  |  |  | 		'focused', | 
					
						
							|  |  |  | 		function(elem){ return elem.value && elem.children }, | 
					
						
							|  |  |  | 		{iterateCollapsed: true}), | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 	// item state events...
 | 
					
						
							|  |  |  | 	disable: makeItemOptionOnEventMethod('disable', 'disabled',  | 
					
						
							| 
									
										
										
										
											2019-06-25 17:41:12 +03:00
										 |  |  | 		{ handler: function(item){  | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 			(this.options || {}).focusDisabledItems  | 
					
						
							| 
									
										
										
										
											2019-06-25 17:41:12 +03:00
										 |  |  | 				|| this.blur(item) }, }), | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 	enable: makeItemOptionOffEventMethod('enable', 'disabled',  | 
					
						
							|  |  |  | 		{ options: {skipDisabled: false}, }), | 
					
						
							| 
									
										
										
										
											2019-06-10 17:18:55 +03:00
										 |  |  | 	toggleDisabled: makeItemEventToggler( | 
					
						
							|  |  |  | 		'disabled',  | 
					
						
							|  |  |  | 		'disable', 'enable',  | 
					
						
							|  |  |  | 		'focused', | 
					
						
							|  |  |  | 		{ skipDisabled: false }), | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 	// visibility...
 | 
					
						
							|  |  |  | 	hide: makeItemOptionOnEventMethod('hide', 'hidden'), | 
					
						
							|  |  |  | 	show: makeItemOptionOffEventMethod('show', 'hidden'), | 
					
						
							| 
									
										
										
										
											2019-06-16 21:20:34 +03:00
										 |  |  | 	toggleHidden: makeItemEventToggler('hidden', 'hide', 'show', 'focused'), | 
					
						
							| 
									
										
										
										
											2019-06-10 04:07:55 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-02 17:36:04 +03:00
										 |  |  | 	// primary/secondary/ternary? item actions...
 | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | 	open: makeItemEventMethod('open', { | 
					
						
							| 
									
										
										
										
											2019-06-01 00:39:29 +03:00
										 |  |  | 		// XXX not yet sure if this is correct...
 | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | 		action: function(evt, item){ | 
					
						
							| 
									
										
										
										
											2019-06-01 00:39:29 +03:00
										 |  |  | 			item.length > 0 | 
					
						
							|  |  |  | 				&& this.toggleCollapse(item) }, | 
					
						
							| 
									
										
										
										
											2019-06-15 18:43:26 +03:00
										 |  |  | 		default_item: function(){ return this.focused } }), | 
					
						
							|  |  |  | 	launch: makeItemEventMethod('launch', { | 
					
						
							|  |  |  | 		default_item: function(){ return this.focused } }), | 
					
						
							| 
									
										
										
										
											2019-03-02 17:29:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-27 01:40:04 +03:00
										 |  |  | 	// Update state (make then render)...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Update (re-render) the current state...
 | 
					
						
							|  |  |  | 	// 	.update()
 | 
					
						
							|  |  |  | 	// 	.update(options)
 | 
					
						
							|  |  |  | 	// 		-> state
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Force re-make the state and re-render...
 | 
					
						
							|  |  |  | 	// 	.update(true[, options])
 | 
					
						
							|  |  |  | 	// 		-> state
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:20:34 +03:00
										 |  |  | 	// NOTE: .update() without arguments is the same as .render()
 | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 	// NOTE: if called too often this will delay subsequent calls...
 | 
					
						
							| 
									
										
										
										
											2019-05-27 01:40:04 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX calling this on a nested browser should update the whole thing...
 | 
					
						
							| 
									
										
										
										
											2019-05-27 05:09:01 +03:00
										 |  |  | 	// 		...can we restore the context via .parent???
 | 
					
						
							| 
									
										
										
										
											2019-06-30 23:09:06 +03:00
										 |  |  | 	// XXX should we force calling update if options are given???
 | 
					
						
							|  |  |  | 	// 		...and should full get passed if at least one call in sequence
 | 
					
						
							|  |  |  | 	// 		got a full=true???
 | 
					
						
							| 
									
										
										
										
											2019-07-01 03:22:40 +03:00
										 |  |  | 	__update_full: undefined, | 
					
						
							|  |  |  | 	__update_args: undefined, | 
					
						
							|  |  |  | 	__update_timeout: undefined, | 
					
						
							|  |  |  | 	__update_max_timeout: undefined, | 
					
						
							| 
									
										
										
										
											2019-06-01 00:39:29 +03:00
										 |  |  | 	update: makeEventMethod('update',  | 
					
						
							|  |  |  | 		function(evt, full, options){ | 
					
						
							| 
									
										
										
										
											2019-07-01 03:22:40 +03:00
										 |  |  | 			options =  | 
					
						
							|  |  |  | 				(full && full !== true && full !== false) ?  | 
					
						
							|  |  |  | 					full  | 
					
						
							|  |  |  | 					: options | 
					
						
							| 
									
										
										
										
											2019-06-01 00:39:29 +03:00
										 |  |  | 			full = full === options ?  | 
					
						
							|  |  |  | 				false  | 
					
						
							|  |  |  | 				: full | 
					
						
							| 
									
										
										
										
											2019-07-01 04:03:38 +03:00
										 |  |  | 			// NOTE: we can't simply use _update(..) closure for this as
 | 
					
						
							|  |  |  | 			// 		it can be called out of two contexts (timeout and 
 | 
					
						
							|  |  |  | 			// 		max_timeout), one (timeout) is renewed on each call 
 | 
					
						
							|  |  |  | 			// 		thus storing the latest args, while the other (i.e.
 | 
					
						
							|  |  |  | 			// 		max_timeout) is not renewed until it is actually 
 | 
					
						
							|  |  |  | 			// 		called and thus would store the args at the time of 
 | 
					
						
							|  |  |  | 			// 		its setTimeout(..)...
 | 
					
						
							|  |  |  | 			// 		storing the arguments in .__update_args would remove
 | 
					
						
							|  |  |  | 			// 		this inconsistency...
 | 
					
						
							| 
									
										
										
										
											2019-07-01 03:22:40 +03:00
										 |  |  | 			var args = this.__update_args = [ | 
					
						
							|  |  |  | 				[evt, full,  | 
					
						
							|  |  |  | 					...(options ?  | 
					
						
							|  |  |  | 						[options]  | 
					
						
							|  |  |  | 						: [])],  | 
					
						
							|  |  |  | 				options] | 
					
						
							|  |  |  | 			this.__update_full = (full && args)  | 
					
						
							|  |  |  | 				|| this.__update_full | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 			var timeout = (options || {}).updateTimeout | 
					
						
							|  |  |  | 				|| this.options.updateTimeout | 
					
						
							| 
									
										
										
										
											2019-07-01 03:22:40 +03:00
										 |  |  | 			var max_timeout = (options || {}).updateMaxDelay | 
					
						
							|  |  |  | 				|| this.options.updateMaxDelay | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-01 03:22:40 +03:00
										 |  |  | 			var _clear_timers = function(){ | 
					
						
							|  |  |  | 				// house keeping...
 | 
					
						
							| 
									
										
										
										
											2019-06-30 23:09:06 +03:00
										 |  |  | 				clearTimeout(this.__update_max_timeout) | 
					
						
							|  |  |  | 				delete this.__update_max_timeout | 
					
						
							| 
									
										
										
										
											2019-07-01 03:22:40 +03:00
										 |  |  | 				clearTimeout(this.__update_timeout) | 
					
						
							|  |  |  | 				delete this.__update_timeout }.bind(this) | 
					
						
							|  |  |  | 			var _update = function(){ | 
					
						
							|  |  |  | 				_clear_timers() | 
					
						
							|  |  |  | 				var full = !!this.__update_full | 
					
						
							|  |  |  | 				var [args, opts] = this.__update_full  | 
					
						
							|  |  |  | 					|| this.__update_args  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				delete this.__update_full | 
					
						
							|  |  |  | 				delete this.__update_args | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				full  | 
					
						
							|  |  |  | 					&& this.make(opts)  | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 				this | 
					
						
							| 
									
										
										
										
											2019-07-01 03:22:40 +03:00
										 |  |  | 					.preRender() | 
					
						
							|  |  |  | 					.render(opts)  | 
					
						
							|  |  |  | 				this.trigger(...args) }.bind(this) | 
					
						
							|  |  |  | 			var _update_n_delay = function(){ | 
					
						
							|  |  |  | 				// call...
 | 
					
						
							|  |  |  | 				_update() | 
					
						
							|  |  |  | 				// schedule clear...
 | 
					
						
							|  |  |  | 				this.__update_timeout = setTimeout(_clear_timers, timeout) }.bind(this) | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// no timeout...
 | 
					
						
							|  |  |  | 			if(!timeout){ | 
					
						
							|  |  |  | 				_update() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// first call -> call sync then delay...
 | 
					
						
							|  |  |  | 			} else if(this.__update_timeout == null){ | 
					
						
							| 
									
										
										
										
											2019-07-01 03:22:40 +03:00
										 |  |  | 				_update_n_delay() | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// fast subsequent calls -> delay... 
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				clearTimeout(this.__update_timeout) | 
					
						
							|  |  |  | 				this.__update_timeout = setTimeout(_update, timeout)  | 
					
						
							| 
									
										
										
										
											2019-06-30 23:09:06 +03:00
										 |  |  | 				// force run at max_timeout...
 | 
					
						
							|  |  |  | 				max_timeout  | 
					
						
							|  |  |  | 					&& this.__update_max_timeout == null | 
					
						
							| 
									
										
										
										
											2019-07-01 03:22:40 +03:00
										 |  |  | 					&& (this.__update_max_timeout =  | 
					
						
							|  |  |  | 						setTimeout(_update_n_delay, max_timeout)) | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		},  | 
					
						
							|  |  |  | 		// we'll retrigger manually...
 | 
					
						
							|  |  |  | 		false), | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	// this is triggered by .update() just before render...
 | 
					
						
							|  |  |  | 	preRender: makeEventMethod('preRender'), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-13 03:05:44 +03:00
										 |  |  | 	// NOTE: if given a path that does not exist this will try and load 
 | 
					
						
							|  |  |  | 	// 		the longest existing sub-path...
 | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 	// XXX should level drawing be a feature of the browser or the 
 | 
					
						
							|  |  |  | 	// 		client (as-is in browser.js)???
 | 
					
						
							|  |  |  | 	// XXX would also need to pass the path to .make(..) and friends for 
 | 
					
						
							|  |  |  | 	// 		compatibility...
 | 
					
						
							|  |  |  | 	// 		...or set .options.path (and keep it up to date in the API)...
 | 
					
						
							| 
									
										
										
										
											2019-06-13 02:54:41 +03:00
										 |  |  | 	load: makeEventMethod('load',  | 
					
						
							|  |  |  | 		function(evt, target){}, | 
					
						
							|  |  |  | 		function(evt, target){ | 
					
						
							|  |  |  | 			// XXX use .normalizePath(..)
 | 
					
						
							|  |  |  | 			target = typeof(target) == typeof('str') ? | 
					
						
							|  |  |  | 				(target.trim().endsWith('/') ?  | 
					
						
							|  |  |  | 					target.trim() + '*' | 
					
						
							|  |  |  | 					: target.trim()).split(/[\\\/]/g) | 
					
						
							|  |  |  | 				: target | 
					
						
							| 
									
										
										
										
											2019-06-13 03:05:44 +03:00
										 |  |  | 			// search for longest existing path...
 | 
					
						
							| 
									
										
										
										
											2019-06-13 02:54:41 +03:00
										 |  |  | 			var elem | 
					
						
							|  |  |  | 			do{ | 
					
						
							|  |  |  | 				elem = this.get(target) | 
					
						
							|  |  |  | 			} while(elem === undefined && target.pop()) | 
					
						
							|  |  |  | 			elem | 
					
						
							|  |  |  | 				&& this.focus(elem) }), | 
					
						
							| 
									
										
										
										
											2019-03-02 17:29:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-26 04:40:38 +03:00
										 |  |  | 	close: makeEventMethod('close', function(evt, reason){}), | 
					
						
							| 
									
										
										
										
											2019-02-23 06:38:46 +03:00
										 |  |  | 	 | 
					
						
							| 
									
										
										
										
											2019-04-25 03:25:03 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 	// Constructor...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	BaseBrowser(items(make, options)[, options])
 | 
					
						
							|  |  |  | 	// 		-> browser
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Set header and items generators...
 | 
					
						
							|  |  |  | 	// 	BaseBrowser(
 | 
					
						
							|  |  |  | 	// 			header(make, options) | null, 
 | 
					
						
							|  |  |  | 	// 			items(make, options)[, options])
 | 
					
						
							|  |  |  | 	// 		-> browser
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Set both header and footer...
 | 
					
						
							|  |  |  | 	// 	BaseBrowser(
 | 
					
						
							|  |  |  | 	// 			header(make, options) | null, 
 | 
					
						
							|  |  |  | 	// 			items(make, options), 
 | 
					
						
							|  |  |  | 	// 			footer(make, options) | null[, options])
 | 
					
						
							|  |  |  | 	// 		-> browser
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: of either header or footer are set to null and 
 | 
					
						
							|  |  |  | 	// 		options.defaultHeader / options.defaultFooter are set then 
 | 
					
						
							|  |  |  | 	// 		they will be used. To disable header footer completely set 
 | 
					
						
							|  |  |  | 	// 		the corresponding default option to null too.
 | 
					
						
							|  |  |  | 	// NOTE: for options.defaultHeader / options.defaultFooter the docs
 | 
					
						
							|  |  |  | 	// 		are in the options section.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX should we .update(..) on init....
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | 	__init__: function(func, options){ | 
					
						
							| 
									
										
										
										
											2019-06-27 03:33:57 +03:00
										 |  |  | 		var args = [...arguments] | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 16:09:32 +03:00
										 |  |  | 		// header (optional)...
 | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 		args[1] instanceof Function ? | 
					
						
							|  |  |  | 			(this.__header__ = args.shift()) | 
					
						
							|  |  |  | 		: args[0] == null | 
					
						
							|  |  |  | 			&& args.shift() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 16:09:32 +03:00
										 |  |  | 		// items...
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 		this.__items__ = args.shift() | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 16:09:32 +03:00
										 |  |  | 		// footer (optional)..
 | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 		args[0] instanceof Function ? | 
					
						
							|  |  |  | 			(this.__footer__ = args.shift()) | 
					
						
							|  |  |  | 		: args[0] == null | 
					
						
							|  |  |  | 			&& args.shift() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 16:09:32 +03:00
										 |  |  | 		// options (optional)...
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | 		this.options = Object.assign( | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 			Object.create(this.options || {}),  | 
					
						
							| 
									
										
										
										
											2019-06-29 16:09:32 +03:00
										 |  |  | 			args[0] || {}) }, | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var BaseBrowser =  | 
					
						
							|  |  |  | module.BaseBrowser =  | 
					
						
							|  |  |  | object.makeConstructor('BaseBrowser',  | 
					
						
							|  |  |  | 		BaseBrowserClassPrototype,  | 
					
						
							|  |  |  | 		BaseBrowserPrototype) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-25 20:19:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-12 21:45:51 +03:00
										 |  |  | var KEYBOARD_CONFIG = | 
					
						
							|  |  |  | module.KEYBOARD_CONFIG = { | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	ItemEdit: { | 
					
						
							|  |  |  | 		pattern: '.list .text[contenteditable]', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// XXX
 | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	PathEdit: { | 
					
						
							|  |  |  | 		pattern: '.path[contenteditable]', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// XXX
 | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-12 21:45:51 +03:00
										 |  |  | 	Filter: { | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 		pattern: '.path div.cur[contenteditable]', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// XXX
 | 
					
						
							| 
									
										
										
										
											2019-06-12 21:45:51 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	General: { | 
					
						
							|  |  |  | 		pattern: '*', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// XXX use up/down
 | 
					
						
							|  |  |  | 		Up: 'prev!', | 
					
						
							|  |  |  | 		Down: 'next!', | 
					
						
							|  |  |  | 		Left: 'left', | 
					
						
							|  |  |  | 		Right: 'right', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		PgUp: 'pageUp!', | 
					
						
							|  |  |  | 		PgDown: 'pageDown!', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Home: 'focus: "first"', | 
					
						
							|  |  |  | 		End: 'focus: "last"', | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 		'#1': 'focus: 0', | 
					
						
							|  |  |  | 		'#2': 'focus: 1', | 
					
						
							|  |  |  | 		'#3': 'focus: 2', | 
					
						
							|  |  |  | 		'#4': 'focus: 3', | 
					
						
							|  |  |  | 		'#5': 'focus: 4', | 
					
						
							|  |  |  | 		'#6': 'focus: 5', | 
					
						
							|  |  |  | 		'#7': 'focus: 6', | 
					
						
							|  |  |  | 		'#8': 'focus: 7', | 
					
						
							|  |  |  | 		'#9': 'focus: 8', | 
					
						
							|  |  |  | 		'#0': 'focus: 9', | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-12 21:45:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		Enter: 'open', | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 		Space: 'toggleSelect!', | 
					
						
							| 
									
										
										
										
											2019-06-12 21:45:51 +03:00
										 |  |  | 		ctrl_A: 'select!: "*"', | 
					
						
							|  |  |  | 		ctrl_D: 'deselect!: "*"', | 
					
						
							|  |  |  | 		ctrl_I: 'toggleSelect!: "*"', | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 		// paste...
 | 
					
						
							|  |  |  | 		ctrl_V: '__paste', | 
					
						
							|  |  |  | 		meta_V: 'ctrl_V', | 
					
						
							|  |  |  | 		// copy...
 | 
					
						
							|  |  |  | 		ctrl_C: '__copy', | 
					
						
							|  |  |  | 		ctrl_X: 'ctrl_C', | 
					
						
							|  |  |  | 		meta_C: 'ctrl_C', | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-12 21:45:51 +03:00
										 |  |  | 		// NOTE: do not bind this key, it is used to jump to buttons
 | 
					
						
							|  |  |  | 		// 		via tabindex...
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 		Tab: 'NEXT!', | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX need to keep this local to each dialog instance...
 | 
					
						
							|  |  |  | 	ItemShortcuts: { | 
					
						
							|  |  |  | 		doc: 'Item shortcuts', | 
					
						
							|  |  |  | 		pattern: '*', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// this is where item-specific shortcuts will be set...
 | 
					
						
							| 
									
										
										
										
											2019-06-12 21:45:51 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | // Item...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var HTMLItemClassPrototype = { | 
					
						
							|  |  |  | 	__proto__: BaseItem, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	text: function(elem){ | 
					
						
							|  |  |  | 		var txt = object.parent(HTMLItem.text, this).call(this, elem) | 
					
						
							|  |  |  | 		return txt != null ? | 
					
						
							|  |  |  | 			(txt + '') | 
					
						
							|  |  |  | 				.replace(/\$(.)/g, '$1')  | 
					
						
							|  |  |  | 			: txt }, | 
					
						
							|  |  |  | 	elem: function(elem){ | 
					
						
							|  |  |  | 		elem = elem.dom || elem | 
					
						
							|  |  |  | 		return elem.classList.contains('list') ?  | 
					
						
							|  |  |  | 				elem.querySelector('.item') | 
					
						
							|  |  |  | 				: elem }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var HTMLItemPrototype = { | 
					
						
							|  |  |  | 	__proto__: BaseItem.prototype, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-26 12:53:55 +03:00
										 |  |  | 	__dom: undefined, | 
					
						
							|  |  |  | 	get dom(){ | 
					
						
							|  |  |  | 		return this.__dom }, | 
					
						
							|  |  |  | 	set dom(value){ | 
					
						
							|  |  |  | 		this.__dom | 
					
						
							|  |  |  | 			// NOTE: a node can't be attached to two places, so in this 
 | 
					
						
							|  |  |  | 			// 		case (i.e. when replacing item with list containing 
 | 
					
						
							|  |  |  | 			// 		item) we do not need to do anything as attaching to
 | 
					
						
							|  |  |  | 			// 		the tree is done by the code that created the parent
 | 
					
						
							|  |  |  | 			// 		and called us...
 | 
					
						
							|  |  |  | 			&& !value.contains(this.__dom) | 
					
						
							|  |  |  | 			&& this.__dom.replaceWith(value) | 
					
						
							|  |  |  | 		this.__dom = value }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 	get elem(){ | 
					
						
							|  |  |  | 		return this.constructor.elem(this) }, | 
					
						
							| 
									
										
										
										
											2019-06-26 12:53:55 +03:00
										 |  |  | 	// XXX for this to be practical we need to slightly update rendering...
 | 
					
						
							|  |  |  | 	// 		...currently the following are not equivalent:
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 			dialog.get(0).elem = dialog.renderItem(dialog.get(0), 0, {})
 | 
					
						
							|  |  |  | 	// 		
 | 
					
						
							|  |  |  | 	// 			dialog.get(0).elem.replaceWith(dialog.renderItem(dialog.get(0), 0, {}))
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		#2 works as expected while #1 seems not to change anything, this
 | 
					
						
							|  |  |  | 	// 		is because in #1 .renderItem(..) actually sets new .dom BEFORE
 | 
					
						
							|  |  |  | 	// 		calling .elem.replaceWith(..)... 
 | 
					
						
							|  |  |  | 	// 		the new .dom value is replaced correctly but it is detached, 
 | 
					
						
							|  |  |  | 	// 		thus we see no change...
 | 
					
						
							| 
									
										
										
										
											2019-06-27 02:39:33 +03:00
										 |  |  | 	// XXX THIS IS WRONG...
 | 
					
						
							|  |  |  | 	// 		...this can detach elements, see above for more info...
 | 
					
						
							| 
									
										
										
										
											2019-06-26 12:53:55 +03:00
										 |  |  | 	set elem(value){ | 
					
						
							| 
									
										
										
										
											2019-06-26 19:41:42 +03:00
										 |  |  | 		this.dom ? | 
					
						
							|  |  |  | 			this.elem.replaceWith(value) | 
					
						
							|  |  |  | 			: (this.dom = value)}, | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var HTMLItem =  | 
					
						
							|  |  |  | module.HTMLItem =  | 
					
						
							|  |  |  | object.makeConstructor('HTMLItem',  | 
					
						
							|  |  |  | 	HTMLItemClassPrototype, | 
					
						
							|  |  |  | 	HTMLItemPrototype) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-12 21:45:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-16 22:45:00 +03:00
										 |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 03:08:58 +03:00
										 |  |  | // Helpers...
 | 
					
						
							| 
									
										
										
										
											2019-06-16 22:45:00 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 03:08:58 +03:00
										 |  |  | // Scrolling / offset...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-06-17 02:50:49 +03:00
										 |  |  | var scrollOffset = function(browser, direction, elem){ | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 	var elem = (elem || browser.focused).elem | 
					
						
							| 
									
										
										
										
											2019-06-29 07:34:01 +03:00
										 |  |  | 	var lst = browser.dom.querySelector('.list.items') | 
					
						
							| 
									
										
										
										
											2019-06-17 02:50:49 +03:00
										 |  |  | 	return direction == 'top' ? | 
					
						
							|  |  |  | 		elem.offsetTop - lst.scrollTop | 
					
						
							|  |  |  | 		: lst.offsetHeight  | 
					
						
							|  |  |  | 			- (elem.offsetTop - lst.scrollTop  | 
					
						
							|  |  |  | 				+ elem.offsetHeight) } | 
					
						
							|  |  |  | var nudgeElement = function(browser, direction, elem){ | 
					
						
							|  |  |  | 	var threashold = browser.options.focusOffsetWhileScrolling || 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// keep scrolled item at threashold from list edge...
 | 
					
						
							|  |  |  | 	var offset = scrollOffset(browser,  | 
					
						
							|  |  |  | 		direction == 'up' ?  | 
					
						
							|  |  |  | 			'top'  | 
					
						
							|  |  |  | 			: 'bottom',  | 
					
						
							|  |  |  | 		elem) | 
					
						
							| 
									
										
										
										
											2019-06-29 07:34:01 +03:00
										 |  |  | 	var lst = browser.dom.querySelector('.list.items') | 
					
						
							| 
									
										
										
										
											2019-06-17 02:50:49 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	offset < threashold | 
					
						
							|  |  |  | 		&& lst.scrollBy(0,  | 
					
						
							|  |  |  | 			direction == 'up' ? | 
					
						
							|  |  |  | 				offset - threashold | 
					
						
							|  |  |  | 				: Math.floor(threashold - offset)) }  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Make item/page navigation methods...
 | 
					
						
							| 
									
										
										
										
											2019-06-16 22:45:00 +03:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-06-17 02:50:49 +03:00
										 |  |  | var focusItem = function(direction){ | 
					
						
							|  |  |  | 	// sanity check...
 | 
					
						
							|  |  |  | 	if(direction != 'up' && direction != 'down'){ | 
					
						
							|  |  |  | 		throw new Error('focusItem(..): unknown direction: '+ direction) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return function(){ | 
					
						
							|  |  |  | 		var name = direction == 'up' ? 'prev' : 'next' | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 		object.parent(HTMLBrowserPrototype[name], name, this).call(this, ...arguments) | 
					
						
							| 
									
										
										
										
											2019-06-17 02:50:49 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		var threashold = this.options.focusOffsetWhileScrolling || 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var focused = this.focused | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 		var first = this.get('first', {skipDisabled: !(this.options || {}).focusDisabledItems}) | 
					
						
							|  |  |  | 		var last = this.get('last', {skipDisabled: !(this.options || {}).focusDisabledItems}) | 
					
						
							| 
									
										
										
										
											2019-06-17 02:50:49 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// center the first/last elements to reveal hidden items before/after...
 | 
					
						
							|  |  |  | 		;(focused === last || focused === first) ? | 
					
						
							|  |  |  | 			this.scrollTo(this.focused, 'center') | 
					
						
							|  |  |  | 		// keep scrolled item at threashold from list edge...
 | 
					
						
							|  |  |  | 		: threashold > 0 | 
					
						
							|  |  |  | 			&& nudgeElement(this, direction, this.focused) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// hold repeat at last element...
 | 
					
						
							|  |  |  | 		focused === (direction == 'up' ? first : last) | 
					
						
							|  |  |  | 			&& this.keyboard.pauseRepeat | 
					
						
							| 
									
										
										
										
											2019-06-30 02:21:05 +03:00
										 |  |  | 			&& this.keyboard.pauseRepeat()  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this } } | 
					
						
							| 
									
										
										
										
											2019-06-16 22:45:00 +03:00
										 |  |  | // XXX this behaves in an odd way with .options.scrollBehavior = 'smooth'
 | 
					
						
							|  |  |  | var focusPage = function(direction){ | 
					
						
							|  |  |  | 	var d = direction == 'up' ? | 
					
						
							|  |  |  | 			'pagetop' | 
					
						
							|  |  |  | 		: direction == 'down' ? | 
					
						
							|  |  |  | 			'pagebottom' | 
					
						
							|  |  |  | 		: null | 
					
						
							|  |  |  | 	var t = direction == 'up' ? | 
					
						
							|  |  |  | 			'first' | 
					
						
							|  |  |  | 		: direction == 'down' ? | 
					
						
							|  |  |  | 			'last' | 
					
						
							|  |  |  | 		: null | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// sanity check...
 | 
					
						
							|  |  |  | 	if(d == null){ | 
					
						
							|  |  |  | 		throw new Error('focusPage(..): unknown direction: '+ direction) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return function(){ | 
					
						
							|  |  |  | 		var target = this.get(d) | 
					
						
							|  |  |  | 		var focused = this.focused | 
					
						
							| 
									
										
										
										
											2019-06-17 02:50:49 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// reveal diabled elements above the top focusable...
 | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 		;(target === this.get(t, {skipDisabled: !(this.options || {}).focusDisabledItems})  | 
					
						
							| 
									
										
										
										
											2019-06-25 17:41:12 +03:00
										 |  |  | 				&& target === focused) ? | 
					
						
							| 
									
										
										
										
											2019-06-17 02:50:49 +03:00
										 |  |  | 			this.scrollTo(target, 'center') | 
					
						
							|  |  |  | 		// scroll one page and focus...
 | 
					
						
							|  |  |  | 		: target === focused ? | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 			this.focus(this.get(d, 1, {skipDisabled: !(this.options || {}).focusDisabledItems})) | 
					
						
							| 
									
										
										
										
											2019-06-17 02:50:49 +03:00
										 |  |  | 		// focus top/bottom of current page...
 | 
					
						
							|  |  |  | 		: this.focus(target) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		;(this.options.focusOffsetWhileScrolling || 0) > 0 | 
					
						
							|  |  |  | 			&& nudgeElement(this, direction, this.focused) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	} } | 
					
						
							| 
									
										
										
										
											2019-06-16 22:45:00 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Update element class...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // XXX should we use .renderItem(...) for this???
 | 
					
						
							|  |  |  | var updateElemClass = function(action, cls, handler){ | 
					
						
							|  |  |  | 	return function(evt, elem, ...args){ | 
					
						
							|  |  |  | 		elem  | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 			&& elem.elem.classList[action](cls)  | 
					
						
							| 
									
										
										
										
											2019-06-16 22:45:00 +03:00
										 |  |  | 		return handler  | 
					
						
							|  |  |  | 			&& handler.call(this, evt, elem, ...args)} } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-04 19:50:52 +03:00
										 |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | var HTMLBrowserClassPrototype = { | 
					
						
							| 
									
										
										
										
											2019-07-01 04:47:31 +03:00
										 |  |  | 	__proto__: BaseBrowser, } | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | // XXX render of nested lists does not affect the parent list(s)...
 | 
					
						
							| 
									
										
										
										
											2019-02-20 16:30:40 +03:00
										 |  |  | // 		...need to render lists and items both as a whole or independently...
 | 
					
						
							| 
									
										
										
										
											2019-06-14 16:14:07 +03:00
										 |  |  | // XXX need a strategy to update the DOM -- i.e. add/remove nodes for 
 | 
					
						
							|  |  |  | // 		partial rendering instead of full DOM replacement...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | var HTMLBrowserPrototype = { | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | 	__proto__: BaseBrowser.prototype, | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 	__item__: HTMLItem, | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 01:58:08 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | 	options: { | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 		__proto__: BaseBrowser.prototype.options, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-30 19:19:26 +03:00
										 |  |  | 		// Default header/footer generators...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// These are the Item.<generator> to use when the user does not
 | 
					
						
							|  |  |  | 		// manually set a header/footer.
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// If set to null, no corresponding header/footer will be created 
 | 
					
						
							|  |  |  | 		// automatically.
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: changing these on the fly would require both clearing 
 | 
					
						
							|  |  |  | 		// 		the cache and an update, i.e.:
 | 
					
						
							|  |  |  | 		// 			dialog.options.defaultFooter = 'DisplayItemInfo'
 | 
					
						
							|  |  |  | 		// 			dialog
 | 
					
						
							|  |  |  | 		// 				.clearCache()
 | 
					
						
							|  |  |  | 		// 				.update(true)
 | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 		defaultHeader: 'DisplayFocusedPath', | 
					
						
							|  |  |  | 		//defaultFooter: 'DisplayItemInfo',
 | 
					
						
							| 
									
										
										
										
											2019-06-30 19:19:26 +03:00
										 |  |  | 	 | 
					
						
							|  |  |  | 		// If true hide header/footer...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: these will prevent rendering of the corresponding 
 | 
					
						
							|  |  |  | 		// 		header/footer but their data will still be made and 
 | 
					
						
							|  |  |  | 		// 		potentially updated...
 | 
					
						
							|  |  |  | 		hideListHeader: false, | 
					
						
							|  |  |  | 		hideListFooter: false, | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-30 19:19:26 +03:00
										 |  |  | 		// If true render hidden elements...
 | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2019-06-30 19:19:26 +03:00
										 |  |  | 		renderHidden: false, | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-17 02:50:49 +03:00
										 |  |  | 		// Sets the distance between the focused element and top/bottom
 | 
					
						
							|  |  |  | 		// border while moving through elements...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2019-06-17 00:33:44 +03:00
										 |  |  | 		// XXX can we make this relative???
 | 
					
						
							| 
									
										
										
										
											2019-06-17 02:50:49 +03:00
										 |  |  | 		// 		...i.e. about half of the average element height...
 | 
					
						
							| 
									
										
										
										
											2019-06-17 03:12:24 +03:00
										 |  |  | 		focusOffsetWhileScrolling: 18, | 
					
						
							| 
									
										
										
										
											2019-06-17 00:33:44 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-30 19:19:26 +03:00
										 |  |  | 		// for more docs see:
 | 
					
						
							|  |  |  | 		//	https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// XXX 'smooth' value yields odd results...
 | 
					
						
							|  |  |  | 		//scrollBehavior: 'auto',
 | 
					
						
							| 
									
										
										
										
											2019-02-07 01:30:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-30 19:19:26 +03:00
										 |  |  | 		itemTemplate: { | 
					
						
							|  |  |  | 			__proto__: (BaseBrowser.prototype.options || {}).itemTemplate || {}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			'   ': { | 
					
						
							|  |  |  | 				'class': 'separator', | 
					
						
							|  |  |  | 				'html': '<div/>', | 
					
						
							|  |  |  | 				noniterable: true, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			'---': { | 
					
						
							|  |  |  | 				'class': 'separator', | 
					
						
							|  |  |  | 				'html': '<hr>', | 
					
						
							|  |  |  | 				noniterable: true, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			'...': { | 
					
						
							|  |  |  | 				'class': 'separator', | 
					
						
							|  |  |  | 				'html': '<center><div class="loader"/></center>', | 
					
						
							|  |  |  | 				noniterable: true, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// events not to bubble up the tree...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 		localEvents: [ | 
					
						
							| 
									
										
										
										
											2019-02-10 19:28:10 +03:00
										 |  |  | 			// XXX STUB???
 | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 			'click', | 
					
						
							| 
									
										
										
										
											2019-02-10 19:28:10 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// XXX keyboard stuff...
 | 
					
						
							|  |  |  | 			// XXX
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// XXX custom events...
 | 
					
						
							|  |  |  | 			// XXX
 | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 		], | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 		//buttonLocalEvents: [
 | 
					
						
							|  |  |  | 		//],
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-30 19:19:26 +03:00
										 |  |  | 		// Default buttons for header/items/footer sections...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 		// Format:
 | 
					
						
							|  |  |  | 		// 	[
 | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 		// 		// basic button handler...
 | 
					
						
							|  |  |  | 		// 		['html', 
 | 
					
						
							|  |  |  | 		// 			<handler>],
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 		// 		// full button handler...
 | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 		// 		//
 | 
					
						
							|  |  |  | 		// 		//	<arg> can be:
 | 
					
						
							|  |  |  | 		// 		//		- item			- item containing the button
 | 
					
						
							|  |  |  | 		// 		//		- focused		- currently focused item
 | 
					
						
							|  |  |  | 		// 		//							NOTE: if we are not focusing 
 | 
					
						
							|  |  |  | 		// 		//								on button click this may 
 | 
					
						
							|  |  |  | 		// 		//								be different from item...
 | 
					
						
							|  |  |  | 		// 		//		- event			- event object
 | 
					
						
							|  |  |  | 		// 		//		- button		- text on button
 | 
					
						
							|  |  |  | 		// 		//		- number/string/list/object
 | 
					
						
							|  |  |  | 		// 		//						- any values...
 | 
					
						
							|  |  |  | 		// 		//
 | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 		// 		//	<force>	(optional, bool or function), of true the button will 
 | 
					
						
							| 
									
										
										
										
											2019-06-22 06:03:44 +03:00
										 |  |  | 		// 		//	be active while the item is disabled...
 | 
					
						
							|  |  |  | 		// 		//
 | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 		// 		// NOTE: for more doc see keyboard.Keyboard.parseStringHandler(..)
 | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 		// 		[
 | 
					
						
							|  |  |  | 		// 			// button view...
 | 
					
						
							|  |  |  | 		// 			'text or html' 
 | 
					
						
							|  |  |  | 		// 				| '<button-generator>: <arg> .. -- comment' 
 | 
					
						
							|  |  |  | 		// 				| <function>
 | 
					
						
							|  |  |  | 		// 				| <HTMLElement>, 
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 			// button action...
 | 
					
						
							|  |  |  | 		// 			'<action>: <arg> .. -- comment' 
 | 
					
						
							|  |  |  | 		// 				| <function>,
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2019-06-30 19:19:26 +03:00
										 |  |  | 		// 			// force active on disabled items (optional)...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 		// 			bool 
 | 
					
						
							|  |  |  | 		// 				| <function>,
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 			// button metadata (optional)...
 | 
					
						
							| 
									
										
										
										
											2019-06-30 19:19:26 +03:00
										 |  |  | 		// 			{
 | 
					
						
							|  |  |  | 		// 				cls: <css-class>,
 | 
					
						
							|  |  |  | 		// 				alt: <string>,
 | 
					
						
							|  |  |  | 		// 				keys: <key> | [ <key>, ... ],
 | 
					
						
							|  |  |  | 		// 				...
 | 
					
						
							|  |  |  | 		// 			},
 | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 		// 		],
 | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 		// 		...
 | 
					
						
							|  |  |  | 		// 	]
 | 
					
						
							| 
									
										
										
										
											2019-06-30 19:19:26 +03:00
										 |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2019-06-28 17:48:02 +03:00
										 |  |  | 		headerButtons: [ | 
					
						
							| 
									
										
										
										
											2019-02-12 02:34:11 +03:00
										 |  |  | 		], | 
					
						
							| 
									
										
										
										
											2019-06-30 19:19:26 +03:00
										 |  |  | 		itemButtons: [ | 
					
						
							|  |  |  | 		], | 
					
						
							| 
									
										
										
										
											2019-06-29 16:09:32 +03:00
										 |  |  | 		footerButtons: [ | 
					
						
							|  |  |  | 		], | 
					
						
							| 
									
										
										
										
											2019-02-27 03:35:03 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 		// If true will disable button shortcut key handling...
 | 
					
						
							|  |  |  | 		//disableButtonSortcuts: false,
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-18 20:58:40 +03:00
										 |  |  | 		// debug and testing options...
 | 
					
						
							|  |  |  | 		//keyboardReportUnhandled: false,
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-02-09 02:20:31 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	// Keyboard...
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-19 15:23:54 +03:00
										 |  |  | 	// XXX these should get the root handler if not defined explicitly...
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	__keyboard_config: Object.assign({}, KEYBOARD_CONFIG), | 
					
						
							| 
									
										
										
										
											2019-06-03 19:22:01 +03:00
										 |  |  | 	get keybindings(){ | 
					
						
							|  |  |  | 		return this.__keyboard_config }, | 
					
						
							|  |  |  | 	__keyboard_object: null, | 
					
						
							|  |  |  | 	get keyboard(){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		// XXX should this be here on in start event???
 | 
					
						
							|  |  |  | 		var kb = this.__keyboard_object =  | 
					
						
							|  |  |  | 			this.__keyboard_object  | 
					
						
							|  |  |  | 				|| keyboard.KeyboardWithCSSModes( | 
					
						
							|  |  |  | 					function(data){  | 
					
						
							|  |  |  | 						if(data){ | 
					
						
							|  |  |  | 							that.__keyboard_config = data | 
					
						
							|  |  |  | 						} else { | 
					
						
							|  |  |  | 							return that.__keyboard_config | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 					function(){ return that.dom }) | 
					
						
							|  |  |  | 		return kb }, | 
					
						
							| 
									
										
										
										
											2019-06-18 20:58:40 +03:00
										 |  |  | 	// NOTE: this is not designed for direct use...
 | 
					
						
							|  |  |  | 	get __keyboard_handler(){ | 
					
						
							|  |  |  | 		var options = this.options || {} | 
					
						
							|  |  |  | 		return (this.____keyboard_handler = this.____keyboard_handler  | 
					
						
							|  |  |  | 			|| keyboard.makePausableKeyboardHandler( | 
					
						
							|  |  |  | 				this.keyboard, | 
					
						
							|  |  |  | 				function(){  | 
					
						
							|  |  |  | 					options.keyboardReportUnhandled | 
					
						
							|  |  |  | 						&& console.log('KEY:', ...arguments) },  | 
					
						
							|  |  |  | 				this)) }, | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 	 | 
					
						
							|  |  |  | 	// Proxy to .keyboard.parseStringHandler(..)
 | 
					
						
							|  |  |  | 	parseStringHandler: function(code, context){ | 
					
						
							|  |  |  | 		return this.keyboard.parseStringHandler(code, context || this) }, | 
					
						
							| 
									
										
										
										
											2019-06-18 20:58:40 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-03 19:22:01 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	// DOM props..
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-19 15:23:54 +03:00
										 |  |  | 	// XXX the problem with nested browser elements .update(..) not 
 | 
					
						
							|  |  |  | 	// 		updating unless called with correct context is that .dom / .container
 | 
					
						
							|  |  |  | 	// 		are not maintained in children...
 | 
					
						
							|  |  |  | 	// 		...if done correctly this should fix the issue automatically...
 | 
					
						
							|  |  |  | 	// XXX might be a good idea to make dom support arrays of items...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-02-09 03:42:34 +03:00
										 |  |  | 	// parent element (optional)...
 | 
					
						
							| 
									
										
										
										
											2019-03-05 18:15:49 +03:00
										 |  |  | 	// XXX rename???
 | 
					
						
							|  |  |  | 	// 		... should this be .containerDom or .parentDom???
 | 
					
						
							|  |  |  | 	get container(){ | 
					
						
							|  |  |  | 		return this.__container  | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 			|| (this.__dom ?  | 
					
						
							|  |  |  | 				this.__dom.parentElement  | 
					
						
							|  |  |  | 				: undefined) }, | 
					
						
							| 
									
										
										
										
											2019-03-05 18:15:49 +03:00
										 |  |  | 	set container(value){ | 
					
						
							| 
									
										
										
										
											2019-02-09 02:20:31 +03:00
										 |  |  | 		var dom = this.dom | 
					
						
							| 
									
										
										
										
											2019-03-05 18:15:49 +03:00
										 |  |  | 		this.__container = value | 
					
						
							| 
									
										
										
										
											2019-02-09 02:20:31 +03:00
										 |  |  | 		// transfer the dom to the new parent...
 | 
					
						
							| 
									
										
										
										
											2019-06-19 15:23:54 +03:00
										 |  |  | 		dom  | 
					
						
							|  |  |  | 			&& (this.dom = dom) }, | 
					
						
							| 
									
										
										
										
											2019-02-09 02:20:31 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-09 03:42:34 +03:00
										 |  |  | 	// browser dom...
 | 
					
						
							| 
									
										
										
										
											2019-02-09 01:39:13 +03:00
										 |  |  | 	get dom(){ | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 		return this.__dom }, | 
					
						
							| 
									
										
										
										
											2019-02-09 01:39:13 +03:00
										 |  |  | 	set dom(value){ | 
					
						
							| 
									
										
										
										
											2019-03-05 18:15:49 +03:00
										 |  |  | 		this.container  | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 			&& (this.__dom ? | 
					
						
							| 
									
										
										
										
											2019-06-26 12:53:55 +03:00
										 |  |  | 				this.dom.replaceWith(value)  | 
					
						
							| 
									
										
										
										
											2019-03-05 18:15:49 +03:00
										 |  |  | 				: this.container.appendChild(value)) | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 		this.__dom = value }, | 
					
						
							| 
									
										
										
										
											2019-02-09 02:20:31 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Extending query...
 | 
					
						
							|  |  |  | 	//	
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	// Extended .search(..) to support:
 | 
					
						
							| 
									
										
										
										
											2019-06-04 04:28:08 +03:00
										 |  |  | 	// 	- 'pagetop'
 | 
					
						
							|  |  |  | 	// 	- 'pagebottom'
 | 
					
						
							|  |  |  | 	// 	- searching for items via DOM / jQuery objects
 | 
					
						
							|  |  |  | 	// 		XXX currently direct match only...
 | 
					
						
							|  |  |  | 	// 			...should we add containment search -- match closest item containing obj...
 | 
					
						
							|  |  |  | 	// 
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	//	.search('pagetop'[, offset] ..)
 | 
					
						
							|  |  |  | 	//	.search('pagebottom'[, offset] ..)
 | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-19 01:58:08 +03:00
										 |  |  | 	// XXX add support for pixel offset???
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:34:01 +03:00
										 |  |  | 	// XXX BROKEN -- 'pagetop' / 'pagebottom'
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	search: function(pattern){ | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 		var args = [...arguments].slice(1) | 
					
						
							| 
									
										
										
										
											2019-06-04 04:28:08 +03:00
										 |  |  | 		var p = pattern | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// XXX skip detached elements...
 | 
					
						
							|  |  |  | 		var getAtPagePosition = function(pos, offset){ | 
					
						
							|  |  |  | 			pos = pos || 'top' | 
					
						
							| 
									
										
										
										
											2019-06-29 07:34:01 +03:00
										 |  |  | 			var lst = this.dom.querySelector('.list.items') | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 			offset = lst.offsetHeight * (offset || 0) | 
					
						
							|  |  |  | 			var st = lst.scrollTop | 
					
						
							|  |  |  | 			var H = pos == 'bottom' ?  | 
					
						
							|  |  |  | 				lst.offsetHeight  | 
					
						
							|  |  |  | 				: 0 | 
					
						
							|  |  |  | 			return this.search(true, | 
					
						
							|  |  |  | 					function(e, i, p, stop){ | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 						var edom = e.elem | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 						// first below upper border...
 | 
					
						
							|  |  |  | 						pos == 'top'  | 
					
						
							|  |  |  | 							&& Math.round(edom.offsetTop  | 
					
						
							|  |  |  | 								- Math.max(0, st - offset)) >= 0 | 
					
						
							|  |  |  | 							&& stop(e) | 
					
						
							|  |  |  | 						// last above lower border...
 | 
					
						
							|  |  |  | 						pos == 'bottom' | 
					
						
							|  |  |  | 							&& Math.round(edom.offsetTop + edom.offsetHeight) | 
					
						
							|  |  |  | 								- Math.max(0, st + H + offset) <= 0 | 
					
						
							|  |  |  | 							&& stop(e) }, | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 					{  | 
					
						
							|  |  |  | 						reverse: pos == 'bottom' ?  | 
					
						
							|  |  |  | 							'flat'  | 
					
						
							|  |  |  | 							: false, | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 						skipDisabled: !(this.options || {}).focusDisabledItems,  | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 					}) | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 				.run(function(){ | 
					
						
							|  |  |  | 					return this instanceof Array ? | 
					
						
							|  |  |  | 						undefined | 
					
						
							|  |  |  | 						: this }) }.bind(this) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-04 04:28:08 +03:00
										 |  |  | 		pattern = arguments[0] =  | 
					
						
							|  |  |  | 			// DOM element...
 | 
					
						
							|  |  |  | 			pattern instanceof HTMLElement ? | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 				function(e){ return e.elem === p || e.elem === p } | 
					
						
							| 
									
										
										
										
											2019-06-04 04:28:08 +03:00
										 |  |  | 			// jQuery object...
 | 
					
						
							|  |  |  | 			: (typeof(jQuery) != 'undefined' && pattern instanceof jQuery) ? | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 				function(e){ return p.is(e.dom) || p.is(e.elem) } | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 			// pagetop + offset...
 | 
					
						
							|  |  |  | 			: pattern == 'pagetop' ? | 
					
						
							|  |  |  | 				getAtPagePosition('top',  | 
					
						
							|  |  |  | 					// page offset...
 | 
					
						
							|  |  |  | 					typeof(args[0]) == typeof(123) ? args.shift() : 0) | 
					
						
							|  |  |  | 			// pagebottom + offset...
 | 
					
						
							| 
									
										
										
										
											2019-06-04 04:28:08 +03:00
										 |  |  | 			: pattern == 'pagebottom' ? | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 				getAtPagePosition('bottom',  | 
					
						
							|  |  |  | 					// page offset...
 | 
					
						
							|  |  |  | 					typeof(args[0]) == typeof(123) ? args.shift() : 0) | 
					
						
							|  |  |  | 			// other...
 | 
					
						
							|  |  |  | 			: pattern | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// call parent...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 		return object.parent(HTMLBrowserPrototype.search, this).call(this, pattern, ...args) }, | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-16 22:45:00 +03:00
										 |  |  | 	// Extended .get(..) to support:
 | 
					
						
							|  |  |  | 	// 	- 'pagetop'/'pagebottom' + offset...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.get('pagetop'[, offset] ..)
 | 
					
						
							|  |  |  | 	//	.get('pagebottom'[, offset] ..)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this short-circuits .get(..) directly to .search(..) when 
 | 
					
						
							|  |  |  | 	// 		passed 'pagetop'/'pagebottom' + offset, this may become an 
 | 
					
						
							|  |  |  | 	// 		issue if .get(..) starts doing something extra, currently 
 | 
					
						
							|  |  |  | 	// 		this is a non-issue...
 | 
					
						
							|  |  |  | 	get: function(pattern){ | 
					
						
							|  |  |  | 		var args = [...arguments].slice(1) | 
					
						
							|  |  |  | 		var offset = typeof(args[0]) == typeof(123) ? | 
					
						
							|  |  |  | 			args.shift() | 
					
						
							|  |  |  | 			: false | 
					
						
							|  |  |  | 		var func = args[0] instanceof Function ? | 
					
						
							|  |  |  | 			args.shift() | 
					
						
							|  |  |  | 			: null | 
					
						
							|  |  |  | 		return (pattern == 'pagetop' || pattern == 'pagebottom') && offset ? | 
					
						
							|  |  |  | 			// special case: pagetop/pagebottom + offset -> do search...
 | 
					
						
							|  |  |  | 			this.search(pattern, offset,  | 
					
						
							|  |  |  | 				function(e, i, p, stop){ | 
					
						
							|  |  |  | 					stop(func ?  | 
					
						
							|  |  |  | 						func.call(this, e, i, p) | 
					
						
							|  |  |  | 						: e) }, ...args) | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 			: object.parent(HTMLBrowserPrototype.get, this).call(this, pattern, func, ...args) }, | 
					
						
							| 
									
										
										
										
											2019-06-16 22:45:00 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	// Copy/Paste support...
 | 
					
						
							| 
									
										
										
										
											2019-06-19 01:14:13 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: not for direct use...
 | 
					
						
							|  |  |  | 	// NOTE: both of these feel hackish...
 | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 	__paste: function(callback){ | 
					
						
							|  |  |  | 		var focus = this.dom.querySelector(':focus') || this.dom | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var text = document.createElement('textarea') | 
					
						
							|  |  |  | 		text.style.position = 'absolute' | 
					
						
							|  |  |  | 		text.style.opacity = '0' | 
					
						
							|  |  |  | 		text.style.width = '10px' | 
					
						
							|  |  |  | 		text.style.height = '10px' | 
					
						
							|  |  |  | 		text.style.left = '-1000px' | 
					
						
							|  |  |  | 		this.dom.appendChild(text) | 
					
						
							|  |  |  | 		text.focus() | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		setTimeout(function(){ | 
					
						
							|  |  |  | 			var str = text.value | 
					
						
							|  |  |  | 			text.remove() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// restore focus...
 | 
					
						
							|  |  |  | 			focus | 
					
						
							|  |  |  | 				&& focus.focus() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			callback ? | 
					
						
							|  |  |  | 				callback(str) | 
					
						
							|  |  |  | 				: this.load(str)  | 
					
						
							|  |  |  | 		}.bind(this), 5) | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-06-19 02:06:30 +03:00
										 |  |  | 	// NOTE: FF does not support permission querying so we are not asking,
 | 
					
						
							|  |  |  | 	// 		yes this may result in things breaking, but all the shards 
 | 
					
						
							|  |  |  | 	// 		should be contained within the handler...
 | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 	__copy: function(text){ | 
					
						
							| 
									
										
										
										
											2019-06-19 02:06:30 +03:00
										 |  |  | 		navigator.clipboard.writeText(text || this.path) }, | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 01:58:08 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 15:23:54 +03:00
										 |  |  | 	// Renderers (DOM)...
 | 
					
						
							| 
									
										
										
										
											2019-02-12 02:34:11 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 	// This also does:
 | 
					
						
							| 
									
										
										
										
											2019-04-24 17:01:57 +03:00
										 |  |  | 	// 	- save the rendered state to .dom 
 | 
					
						
							|  |  |  | 	// 	- wrap a list of nodes (nested list) in a div
 | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 	// 	- setup event handling
 | 
					
						
							|  |  |  | 	// 	- init state...
 | 
					
						
							| 
									
										
										
										
											2019-04-24 17:01:57 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// Format:
 | 
					
						
							| 
									
										
										
										
											2019-04-24 18:58:16 +03:00
										 |  |  | 	// 	if list of items passed:
 | 
					
						
							|  |  |  | 	// 		<div>
 | 
					
						
							|  |  |  | 	// 			<!-- items -->
 | 
					
						
							|  |  |  | 	// 			...
 | 
					
						
							|  |  |  | 	// 		</div>
 | 
					
						
							|  |  |  | 	// 	or same as .renderList(..)
 | 
					
						
							| 
									
										
										
										
											2019-04-24 17:01:57 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 	renderFinalize: function(header, items, footer, context){ | 
					
						
							| 
									
										
										
										
											2019-06-04 19:50:52 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 		var d = this.renderList(header, items, footer, context) | 
					
						
							| 
									
										
										
										
											2019-06-18 20:58:40 +03:00
										 |  |  | 		var options = context.options || this.options || {} | 
					
						
							| 
									
										
										
										
											2019-04-24 17:01:57 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// wrap the list (nested list) of nodes in a div...
 | 
					
						
							|  |  |  | 		if(d instanceof Array){ | 
					
						
							|  |  |  | 			var c = document.createElement('div') | 
					
						
							| 
									
										
										
										
											2019-06-04 19:50:52 +03:00
										 |  |  | 			d.classList.add('focusable') | 
					
						
							| 
									
										
										
										
											2019-04-24 17:01:57 +03:00
										 |  |  | 			d.forEach(function(e){ | 
					
						
							|  |  |  | 				c.appendChild(e) }) | 
					
						
							|  |  |  | 			d = c | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-06-03 19:22:01 +03:00
										 |  |  | 		d.setAttribute('tabindex', '0') | 
					
						
							| 
									
										
										
										
											2019-06-18 20:58:40 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-04 19:50:52 +03:00
										 |  |  | 		// Setup basic event handlers...
 | 
					
						
							|  |  |  | 		// keyboard...
 | 
					
						
							| 
									
										
										
										
											2019-06-18 20:58:40 +03:00
										 |  |  | 		// NOTE: we are not doing: 
 | 
					
						
							|  |  |  | 		// 			d.addEventListener('keydown', this.keyPress.bind(this))
 | 
					
						
							|  |  |  | 		// 		because we are abstracting the user from DOM events and 
 | 
					
						
							|  |  |  | 		// 		directly passing them parsed keys...
 | 
					
						
							|  |  |  | 		d.addEventListener('keydown', function(evt){ | 
					
						
							|  |  |  | 			that.keyPress(that.keyboard.event2key(evt)) }) | 
					
						
							| 
									
										
										
										
											2019-06-04 19:50:52 +03:00
										 |  |  | 		// focus...
 | 
					
						
							|  |  |  | 		d.addEventListener('click',  | 
					
						
							|  |  |  | 			function(e){  | 
					
						
							|  |  |  | 				e.stopPropagation() | 
					
						
							| 
									
										
										
										
											2019-06-18 20:58:40 +03:00
										 |  |  | 				d.focus() }) | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 		/* XXX this messes up scrollbar... | 
					
						
							| 
									
										
										
										
											2019-06-04 19:50:52 +03:00
										 |  |  | 		d.addEventListener('focus', | 
					
						
							|  |  |  | 		   function(){ | 
					
						
							|  |  |  | 			   that.focused | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 					&& that.focused.elem.focus() }) | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 		//*/
 | 
					
						
							| 
									
										
										
										
											2019-06-03 19:22:01 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 15:23:54 +03:00
										 |  |  | 		// XXX should this be done here or in .render(..)???
 | 
					
						
							| 
									
										
										
										
											2019-04-24 17:01:57 +03:00
										 |  |  | 		this.dom = d | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// keep focus where it is...
 | 
					
						
							|  |  |  | 		var focused = this.focused | 
					
						
							|  |  |  | 		focused | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 			&& focused.elem | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 				// XXX this will trigger the focus event...
 | 
					
						
							|  |  |  | 				// 		...can we do this without triggering new events???
 | 
					
						
							|  |  |  | 				.focus() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-24 17:01:57 +03:00
										 |  |  | 		return this.dom | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-02-12 02:34:11 +03:00
										 |  |  | 	// Foramt:
 | 
					
						
							|  |  |  | 	// 	<div class="browse-widget" tabindex="0">
 | 
					
						
							|  |  |  | 	// 		<!-- header -->
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		<!-- list -->
 | 
					
						
							|  |  |  | 	// 		<div class="list v-block">
 | 
					
						
							|  |  |  | 	// 			<!-- items -->
 | 
					
						
							|  |  |  | 	// 			...
 | 
					
						
							|  |  |  | 	// 		</div>
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		<!-- footer -->
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							| 
									
										
										
										
											2019-02-12 02:34:11 +03:00
										 |  |  | 	// 	</div>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 	renderList: function(header, items, footer, context){ | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2019-06-18 20:58:40 +03:00
										 |  |  | 		var options = context.options || this.options || {} | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-08 21:21:36 +03:00
										 |  |  | 		// dialog (container)...
 | 
					
						
							| 
									
										
										
										
											2019-02-07 01:30:30 +03:00
										 |  |  | 		var dialog = document.createElement('div') | 
					
						
							|  |  |  | 		dialog.classList.add('browse-widget') | 
					
						
							| 
									
										
										
										
											2019-02-09 01:39:13 +03:00
										 |  |  | 		dialog.setAttribute('tabindex', '0') | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 		// HACK?: prevent dialog from grabbing focus from item...
 | 
					
						
							|  |  |  | 		dialog.addEventListener('mousedown',  | 
					
						
							|  |  |  | 			function(evt){ evt.stopPropagation() }) | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-07 01:30:30 +03:00
										 |  |  | 		// header...
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 		header  | 
					
						
							|  |  |  | 			&& !options.hideListHeader | 
					
						
							|  |  |  | 			&& dialog.appendChild(this.renderListHeader(header, context)) | 
					
						
							| 
									
										
										
										
											2019-02-07 01:30:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// list...
 | 
					
						
							|  |  |  | 		var list = document.createElement('div') | 
					
						
							| 
									
										
										
										
											2019-06-29 07:34:01 +03:00
										 |  |  | 		list.classList.add('list', 'v-block', 'items') | 
					
						
							| 
									
										
										
										
											2019-06-19 01:58:08 +03:00
										 |  |  | 		// prevent scrollbar from grabbing focus...
 | 
					
						
							| 
									
										
										
										
											2019-06-12 04:44:17 +03:00
										 |  |  | 		list.addEventListener('mousedown',  | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 			function(evt){ evt.stopPropagation() }) | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 		items | 
					
						
							|  |  |  | 			.forEach(function(item){ | 
					
						
							| 
									
										
										
										
											2019-02-07 01:30:30 +03:00
										 |  |  | 				list.appendChild(item instanceof Array ?  | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 					that.renderGroup(item)  | 
					
						
							|  |  |  | 					: item) }) | 
					
						
							| 
									
										
										
										
											2019-02-07 01:30:30 +03:00
										 |  |  | 		dialog.appendChild(list) | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 		// footer...
 | 
					
						
							|  |  |  | 		footer  | 
					
						
							|  |  |  | 			&& !options.hideListFooter | 
					
						
							|  |  |  | 			&& dialog.appendChild(this.renderListFooter(footer, context)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-07 01:30:30 +03:00
										 |  |  | 		return dialog  | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-02-12 02:34:11 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// Foramt:
 | 
					
						
							|  |  |  | 	//	<div class="path v-block">
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 	// 		<!-- list -->
 | 
					
						
							|  |  |  | 	// 		<div class="list v-block header">
 | 
					
						
							|  |  |  | 	// 			<!-- items -->
 | 
					
						
							|  |  |  | 	// 			...
 | 
					
						
							|  |  |  | 	// 		</div>
 | 
					
						
							| 
									
										
										
										
											2019-02-12 02:34:11 +03:00
										 |  |  | 	//	</div>
 | 
					
						
							|  |  |  | 	// 	
 | 
					
						
							| 
									
										
										
										
											2019-02-07 01:30:30 +03:00
										 |  |  | 	// XXX populate this...
 | 
					
						
							| 
									
										
										
										
											2019-02-09 03:42:34 +03:00
										 |  |  | 	// XXX make this an item???
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 	renderListHeader: function(items, context){ | 
					
						
							|  |  |  | 		var elem = this.renderList(null, items, null, context).firstChild  | 
					
						
							|  |  |  | 		// XXX should we replace 'list' or add 'header'
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:34:01 +03:00
										 |  |  | 		elem.classList.replace('items', 'header') | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 		return elem }, | 
					
						
							|  |  |  | 	renderListFooter: function(items, context){ | 
					
						
							|  |  |  | 		var elem = this.renderList(null, items, null, context).firstChild  | 
					
						
							|  |  |  | 		// XXX should we replace 'list' or add 'footer'
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:34:01 +03:00
										 |  |  | 		elem.classList.replace('items', 'footer') | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 		return elem }, | 
					
						
							| 
									
										
										
										
											2019-02-12 02:34:11 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// Format:
 | 
					
						
							|  |  |  | 	// 	<div class="list">
 | 
					
						
							|  |  |  | 	// 		<!-- header (optional) -->
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 	// 		<!-- children (optional) -->
 | 
					
						
							| 
									
										
										
										
											2019-02-12 02:34:11 +03:00
										 |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	</div>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 	renderNested: function(header, children, item, context){ | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2019-06-18 20:58:40 +03:00
										 |  |  | 		var options = context.options || this.options || {} | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// container...
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 		var e = document.createElement('div') | 
					
						
							|  |  |  | 		e.classList.add('list') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 		// localize events...
 | 
					
						
							| 
									
										
										
										
											2019-02-10 17:44:42 +03:00
										 |  |  | 		var stopPropagation = function(evt){ evt.stopPropagation() } | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 		;(options.localEvents || []) | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 			.forEach(function(evt){ | 
					
						
							| 
									
										
										
										
											2019-02-10 17:44:42 +03:00
										 |  |  | 				e.addEventListener(evt, stopPropagation) }) | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 		// header...
 | 
					
						
							| 
									
										
										
										
											2019-02-13 16:11:14 +03:00
										 |  |  | 		header | 
					
						
							|  |  |  | 			&& e.appendChild(header) | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// items...
 | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 		children instanceof Node ? | 
					
						
							|  |  |  | 			e.appendChild(children) | 
					
						
							|  |  |  | 		: children instanceof Array ? | 
					
						
							|  |  |  | 			children | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 				.forEach(function(item){ | 
					
						
							|  |  |  | 					e.appendChild(item) }) | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 		: null | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		item.dom = e | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return e | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-02-13 16:11:14 +03:00
										 |  |  | 	// NOTE: this is the similar to .renderItem(..)
 | 
					
						
							|  |  |  | 	renderNestedHeader: function(item, i, context){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		return this.renderItem(item, i, context) | 
					
						
							|  |  |  | 			// update dom...
 | 
					
						
							|  |  |  | 			.run(function(){ | 
					
						
							| 
									
										
										
										
											2019-03-28 02:16:33 +03:00
										 |  |  | 				this.classList.add('sub-list-header', 'traversable') | 
					
						
							| 
									
										
										
										
											2019-02-13 16:11:14 +03:00
										 |  |  | 				item.collapsed | 
					
						
							|  |  |  | 					&& this.classList.add('collapsed') | 
					
						
							|  |  |  | 			}) }, | 
					
						
							| 
									
										
										
										
											2019-02-12 02:34:11 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// Format:
 | 
					
						
							|  |  |  | 	// 	<div class="group">
 | 
					
						
							| 
									
										
										
										
											2019-06-19 01:58:08 +03:00
										 |  |  | 	// 		<!-- items -->
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							| 
									
										
										
										
											2019-02-12 02:34:11 +03:00
										 |  |  | 	// 	</div>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 	renderGroup: function(items, context){ | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 		var e = document.createElement('div') | 
					
						
							|  |  |  | 		e.classList.add('group') | 
					
						
							|  |  |  | 		items | 
					
						
							|  |  |  | 			// XXX is this wrong???
 | 
					
						
							|  |  |  | 			.flat(Infinity) | 
					
						
							|  |  |  | 			.forEach(function(item){ | 
					
						
							|  |  |  | 				e.appendChild(item) }) | 
					
						
							|  |  |  | 		return e }, | 
					
						
							| 
									
										
										
										
											2019-02-12 02:34:11 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// Format:
 | 
					
						
							|  |  |  | 	// 	<div value="value_json" class="item .." tabindex="0" ..>
 | 
					
						
							|  |  |  | 	// 		<!-- value -->
 | 
					
						
							|  |  |  | 	// 		<div class="text">value_a</div>
 | 
					
						
							|  |  |  | 	// 		<div class="text">value_b</div>
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		<!-- buttons (optional) -->
 | 
					
						
							|  |  |  | 	// 		<div class="button">button_a_html</div>
 | 
					
						
							|  |  |  | 	// 		<div class="button">button_b_html</div>
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	</div>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 	// NOTE: DOM events trigger Browser events but not the other way 
 | 
					
						
							|  |  |  | 	// 		around. It is not recommended to use DOM events directly.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-24 05:38:05 +03:00
										 |  |  | 	// XXX need to figure out an intuitive behavior of focus + disable/enable...
 | 
					
						
							|  |  |  | 	// 		...do we skip disabled elements?
 | 
					
						
							|  |  |  | 	// 		...can a disabled item be focused?
 | 
					
						
							|  |  |  | 	// 		...how do we collapse/expand a disabled root?
 | 
					
						
							|  |  |  | 	// 		...what do we focus when toggleing disabled?
 | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 	// XXX handle .options.focusDisabledItems correctly...
 | 
					
						
							| 
									
										
										
										
											2019-06-25 17:46:53 +03:00
										 |  |  | 	// 		- tabindex -- DONE
 | 
					
						
							|  |  |  | 	// 		- ???
 | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 	// XXX show button global/local keys...
 | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 	renderItem: function(item, i, context){ | 
					
						
							| 
									
										
										
										
											2019-05-27 01:00:48 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2019-06-26 12:53:55 +03:00
										 |  |  | 		var options = (context || {}).options || this.options || {} | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 		if(options.hidden && !options.renderHidden){ | 
					
						
							|  |  |  | 			return null | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-07-01 04:47:31 +03:00
										 |  |  | 		var section = item.section || options.section | 
					
						
							| 
									
										
										
										
											2019-02-27 03:35:03 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 		// helpers...
 | 
					
						
							| 
									
										
										
										
											2019-06-26 12:53:55 +03:00
										 |  |  | 		// XXX we need to more carefully test the value to avoid name clashes...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 		var resolveValue = function(value, context, exec_context){ | 
					
						
							|  |  |  | 			var htmlhandler = typeof(value) == typeof('str') ? | 
					
						
							|  |  |  | 				that.parseStringHandler(value, exec_context) | 
					
						
							|  |  |  | 				: null | 
					
						
							|  |  |  | 			return value instanceof Function ? | 
					
						
							|  |  |  | 					value.call(that, item) | 
					
						
							| 
									
										
										
										
											2019-06-26 12:53:55 +03:00
										 |  |  | 				: htmlhandler  | 
					
						
							|  |  |  | 						&& htmlhandler.action in context  | 
					
						
							|  |  |  | 						&& context[htmlhandler.action] instanceof Function ? | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 					context[htmlhandler.action] | 
					
						
							|  |  |  | 						.call(that, item, ...htmlhandler.arguments) | 
					
						
							|  |  |  | 				: value } | 
					
						
							|  |  |  | 		var setDOMValue = function(target, value){ | 
					
						
							|  |  |  | 			value instanceof HTMLElement ? | 
					
						
							|  |  |  | 				target.appendChild(value) | 
					
						
							|  |  |  | 			: (typeof(jQuery) != 'undefined' && value instanceof jQuery) ? | 
					
						
							|  |  |  | 				value.appendTo(target) | 
					
						
							|  |  |  | 			: (target.innerHTML = value) | 
					
						
							|  |  |  | 			return target } | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 		var doTextKeys = function(text, doKey){ | 
					
						
							|  |  |  | 			return text.replace(/\$\w/g,  | 
					
						
							|  |  |  | 				function(k){ | 
					
						
							|  |  |  | 					// forget the '$'...
 | 
					
						
							|  |  |  | 					k = k[1]  | 
					
						
							|  |  |  | 					return (doKey && doKey(k)) ? | 
					
						
							|  |  |  | 						`<u class="key-hint">${k}</u>` | 
					
						
							|  |  |  | 						: k }) } | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-24 05:13:49 +03:00
										 |  |  | 		// special-case: item.html...
 | 
					
						
							|  |  |  | 		if(item.html){ | 
					
						
							| 
									
										
										
										
											2019-02-27 06:52:10 +03:00
										 |  |  | 			// NOTE: this is a bit of a cheat, but it saves us from either 
 | 
					
						
							|  |  |  | 			// 		parsing or restricting the format...
 | 
					
						
							| 
									
										
										
										
											2019-06-19 03:08:58 +03:00
										 |  |  | 			var tmp = document.createElement('div') | 
					
						
							|  |  |  | 			tmp.innerHTML = item.html | 
					
						
							|  |  |  | 			var elem = item.dom = tmp.firstElementChild  | 
					
						
							| 
									
										
										
										
											2019-02-27 06:52:10 +03:00
										 |  |  | 			elem.classList.add( | 
					
						
							| 
									
										
										
										
											2019-03-12 16:05:51 +03:00
										 |  |  | 				...(item['class'] instanceof Array ? | 
					
						
							|  |  |  | 					item['class'] | 
					
						
							|  |  |  | 					: item['class'].split(/\s+/g))) | 
					
						
							| 
									
										
										
										
											2019-02-27 06:47:41 +03:00
										 |  |  | 			return elem  | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-02-27 03:35:03 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-27 06:52:10 +03:00
										 |  |  | 		// Base DOM...
 | 
					
						
							| 
									
										
										
										
											2019-02-08 21:21:36 +03:00
										 |  |  | 		var elem = document.createElement('div') | 
					
						
							| 
									
										
										
										
											2019-06-23 17:32:14 +03:00
										 |  |  | 		var text = item.text | 
					
						
							| 
									
										
										
										
											2019-02-27 06:47:41 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 		// classes...
 | 
					
						
							| 
									
										
										
										
											2019-02-08 21:21:36 +03:00
										 |  |  | 		elem.classList.add(...['item'] | 
					
						
							|  |  |  | 			// user classes...
 | 
					
						
							| 
									
										
										
										
											2019-06-27 03:09:55 +03:00
										 |  |  | 			.concat((item['class'] || item.cls || []) | 
					
						
							| 
									
										
										
										
											2019-06-27 03:33:57 +03:00
										 |  |  | 				// parse space-separated class strings...
 | 
					
						
							| 
									
										
										
										
											2019-06-27 03:09:55 +03:00
										 |  |  | 				.run(function(){ | 
					
						
							|  |  |  | 					return this instanceof Array ? | 
					
						
							|  |  |  | 						this | 
					
						
							|  |  |  | 						: this.split(/\s+/g) })) | 
					
						
							| 
									
										
										
										
											2019-02-08 21:21:36 +03:00
										 |  |  | 			// special classes...
 | 
					
						
							|  |  |  | 			.concat([ | 
					
						
							| 
									
										
										
										
											2019-06-15 18:11:22 +03:00
										 |  |  | 				'focused', | 
					
						
							| 
									
										
										
										
											2019-02-08 21:21:36 +03:00
										 |  |  | 				'selected', | 
					
						
							|  |  |  | 				'disabled', | 
					
						
							|  |  |  | 				'hidden', | 
					
						
							|  |  |  | 			].filter(function(cls){  | 
					
						
							| 
									
										
										
										
											2019-02-09 01:39:13 +03:00
										 |  |  | 				return !!item[cls] }))) | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// attrs...
 | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 		;(item.disabled && !options.focusDisabledItems) | 
					
						
							| 
									
										
										
										
											2019-02-12 02:34:11 +03:00
										 |  |  | 			|| elem.setAttribute('tabindex', '0') | 
					
						
							| 
									
										
										
										
											2019-02-09 01:39:13 +03:00
										 |  |  | 		Object.entries(item.attrs || {}) | 
					
						
							| 
									
										
										
										
											2019-06-23 05:07:06 +03:00
										 |  |  | 			// shorthand attrs...
 | 
					
						
							| 
									
										
										
										
											2019-06-24 05:38:05 +03:00
										 |  |  | 			//.concat([
 | 
					
						
							|  |  |  | 			//].map(function(key){ 
 | 
					
						
							|  |  |  | 			//	return [key, item[key]] }))
 | 
					
						
							| 
									
										
										
										
											2019-02-09 01:39:13 +03:00
										 |  |  | 			.forEach(function([key, value]){ | 
					
						
							| 
									
										
										
										
											2019-06-23 05:07:06 +03:00
										 |  |  | 				value !== undefined | 
					
						
							|  |  |  | 					&& elem.setAttribute(key, value) }) | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 		;(item.value == null  | 
					
						
							|  |  |  | 				|| item.value instanceof Object) | 
					
						
							|  |  |  | 			|| elem.setAttribute('value', item.text) | 
					
						
							|  |  |  | 		;(item.value == null  | 
					
						
							|  |  |  | 				|| item.value instanceof Object  | 
					
						
							|  |  |  | 				|| item.alt != item.text) | 
					
						
							|  |  |  | 			&& elem.setAttribute('alt', item.alt) | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// values...
 | 
					
						
							| 
									
										
										
										
											2019-06-11 15:44:10 +03:00
										 |  |  | 		text != null | 
					
						
							| 
									
										
										
										
											2019-06-24 05:13:49 +03:00
										 |  |  | 			&& (item.value instanceof Array ?  | 
					
						
							|  |  |  | 					item.value  | 
					
						
							|  |  |  | 					: [item.value]) | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 				// handle $keys and other stuff...
 | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 				// NOTE: the actual key setup is done in .__preRender__(..)
 | 
					
						
							|  |  |  | 				// 		see that for more info...
 | 
					
						
							| 
									
										
										
										
											2019-02-27 06:47:41 +03:00
										 |  |  | 				.map(function(v){ | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 					// handle key-shortcuts $K...
 | 
					
						
							|  |  |  | 					v = typeof(v) == typeof('str') ? | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 						doTextKeys(v,  | 
					
						
							|  |  |  | 							function(k){ | 
					
						
							|  |  |  | 								return (item.keys || []) | 
					
						
							|  |  |  | 									.includes(that.keyboard.normalizeKey(k)) }) | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 						: v | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-27 06:47:41 +03:00
										 |  |  | 					var value = document.createElement('span') | 
					
						
							|  |  |  | 					value.classList.add('text') | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					// set the value...
 | 
					
						
							|  |  |  | 					setDOMValue(value,  | 
					
						
							|  |  |  | 						resolveValue(v, that)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-27 06:47:41 +03:00
										 |  |  | 					elem.appendChild(value) | 
					
						
							|  |  |  | 				}) | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-27 01:00:48 +03:00
										 |  |  | 		// system events...
 | 
					
						
							| 
									
										
										
										
											2019-02-12 05:19:57 +03:00
										 |  |  | 		elem.addEventListener('click',  | 
					
						
							| 
									
										
										
										
											2019-06-18 20:58:40 +03:00
										 |  |  | 			function(evt){ | 
					
						
							|  |  |  | 				evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 				// NOTE: if an item is disabled we retain its expand/collapse
 | 
					
						
							|  |  |  | 				// 		functionality...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 00:14:45 +03:00
										 |  |  | 				// XXX revise...
 | 
					
						
							|  |  |  | 				item.disabled ? | 
					
						
							|  |  |  | 					that.toggleCollapse(item) | 
					
						
							|  |  |  | 					: that.open(item, text, elem) }) | 
					
						
							| 
									
										
										
										
											2019-05-27 01:00:48 +03:00
										 |  |  | 		elem.addEventListener('focus',  | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 			function(){  | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 				// NOTE: we do not retrigger focus on an item if it's 
 | 
					
						
							|  |  |  | 				// 		already focused...
 | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 				that.focused !== item | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 					// only trigger focus on gettable items...
 | 
					
						
							|  |  |  | 					// ...i.e. items in the main section excluding headers 
 | 
					
						
							|  |  |  | 					// and footers...
 | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 					&& that.focus(item) }) | 
					
						
							| 
									
										
										
										
											2019-06-19 01:14:13 +03:00
										 |  |  | 		elem.addEventListener('contextmenu',  | 
					
						
							|  |  |  | 			function(evt){  | 
					
						
							|  |  |  | 				evt.preventDefault() | 
					
						
							|  |  |  | 				that.menu(item) }) | 
					
						
							| 
									
										
										
										
											2019-05-27 01:00:48 +03:00
										 |  |  | 		// user events...
 | 
					
						
							| 
									
										
										
										
											2019-02-09 01:39:13 +03:00
										 |  |  | 		Object.entries(item.events || {}) | 
					
						
							| 
									
										
										
										
											2019-06-19 01:14:13 +03:00
										 |  |  | 			// shorthand DOM events...
 | 
					
						
							| 
									
										
										
										
											2019-02-08 21:21:36 +03:00
										 |  |  | 			.concat([ | 
					
						
							| 
									
										
										
										
											2019-02-09 01:39:13 +03:00
										 |  |  | 				].map(function(evt){  | 
					
						
							|  |  |  | 					return [evt, item[evt]] })) | 
					
						
							| 
									
										
										
										
											2019-02-08 21:21:36 +03:00
										 |  |  | 			// setup the handlers...
 | 
					
						
							| 
									
										
										
										
											2019-02-09 01:39:13 +03:00
										 |  |  | 			.forEach(function([evt, handler]){ | 
					
						
							| 
									
										
										
										
											2019-02-08 21:21:36 +03:00
										 |  |  | 				handler | 
					
						
							| 
									
										
										
										
											2019-06-19 01:14:13 +03:00
										 |  |  | 					&& elem.addEventListener(evt, handler.bind(that)) }) | 
					
						
							| 
									
										
										
										
											2019-02-08 21:21:36 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 		// buttons...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 		var button_keys = {} | 
					
						
							| 
									
										
										
										
											2019-06-23 05:07:06 +03:00
										 |  |  | 		// XXX migrate button inheritance...
 | 
					
						
							| 
									
										
										
										
											2019-07-01 04:47:31 +03:00
										 |  |  | 		var buttons = (item.buttons  | 
					
						
							|  |  |  | 				|| (section == 'header'  | 
					
						
							|  |  |  | 					&& (options.headerButtons || [])) | 
					
						
							|  |  |  | 				|| (section == 'footer'  | 
					
						
							|  |  |  | 					&& (options.footerButtons || []))  | 
					
						
							|  |  |  | 				|| options.itemButtons  | 
					
						
							|  |  |  | 				|| []) | 
					
						
							| 
									
										
										
										
											2019-06-22 06:03:44 +03:00
										 |  |  | 			// resolve buttons from library...
 | 
					
						
							|  |  |  | 			.map(function(button){ | 
					
						
							|  |  |  | 				return button instanceof Array ? | 
					
						
							| 
									
										
										
										
											2019-06-23 00:14:45 +03:00
										 |  |  | 						button | 
					
						
							| 
									
										
										
										
											2019-06-22 18:24:43 +03:00
										 |  |  | 					// XXX reference the actual make(..) and not Items...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 00:14:45 +03:00
										 |  |  | 					: Items.buttons[button] instanceof Function ? | 
					
						
							|  |  |  | 						Items.buttons[button].call(that, item) | 
					
						
							| 
									
										
										
										
											2019-06-22 06:03:44 +03:00
										 |  |  | 					: Items.buttons[button] || button }) | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 			// NOTE: keep the order unsurprising -- first defined, first from left...
 | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 			.reverse() | 
					
						
							|  |  |  | 		var stopPropagation = function(evt){ evt.stopPropagation() } | 
					
						
							|  |  |  | 		buttons | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 			.forEach(function([html, handler, ...rest]){ | 
					
						
							|  |  |  | 				var force = (rest[0] === true  | 
					
						
							|  |  |  | 						|| rest[0] === false  | 
					
						
							|  |  |  | 						|| rest[0] instanceof Function) ?  | 
					
						
							|  |  |  | 					rest.shift()  | 
					
						
							|  |  |  | 					: undefined | 
					
						
							|  |  |  | 				var metadata = rest.shift() || {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 05:07:06 +03:00
										 |  |  | 				// metadata...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 				var cls = metadata.cls || [] | 
					
						
							|  |  |  | 				cls = cls instanceof Function ? | 
					
						
							|  |  |  | 					cls.call(that, item) | 
					
						
							|  |  |  | 					: cls | 
					
						
							|  |  |  | 				cls = cls instanceof Array ?  | 
					
						
							|  |  |  | 					cls  | 
					
						
							|  |  |  | 					: cls.split(/\s+/g) | 
					
						
							|  |  |  | 				var alt = metadata.alt | 
					
						
							|  |  |  | 				alt = alt instanceof Function ? | 
					
						
							|  |  |  | 						alt.call(that, item) | 
					
						
							|  |  |  | 					: alt | 
					
						
							|  |  |  | 				var keys = metadata.keys | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 				var button = document.createElement('div') | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 				button.classList.add('button', ...cls) | 
					
						
							|  |  |  | 				alt | 
					
						
							|  |  |  | 					&& button.setAttribute('alt', alt) | 
					
						
							| 
									
										
										
										
											2019-06-22 06:03:44 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 				// button content...
 | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 				var text_keys = [] | 
					
						
							|  |  |  | 				var v = resolveValue(html, Items.buttons, {item}) | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 				setDOMValue(button, | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 					typeof(v) == typeof('str') ? | 
					
						
							|  |  |  | 						doTextKeys(v, | 
					
						
							|  |  |  | 							function(k){ | 
					
						
							|  |  |  | 								k = that.keyboard.normalizeKey(k) | 
					
						
							|  |  |  | 								return !text_keys.includes(k) | 
					
						
							|  |  |  | 									&& text_keys.push(k) }) | 
					
						
							|  |  |  | 						: v) | 
					
						
							|  |  |  | 				keys = text_keys.length > 0 ? | 
					
						
							|  |  |  | 					(keys || []).concat(text_keys) | 
					
						
							|  |  |  | 					: keys | 
					
						
							| 
									
										
										
										
											2019-06-22 06:03:44 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 05:07:06 +03:00
										 |  |  | 				// non-disabled button...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 00:14:45 +03:00
										 |  |  | 				if(force instanceof Function ?  | 
					
						
							|  |  |  | 						force.call(that, item)  | 
					
						
							|  |  |  | 						: (force || !item.disabled) ){ | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 					button.setAttribute('tabindex', '0') | 
					
						
							| 
									
										
										
										
											2019-06-10 04:07:55 +03:00
										 |  |  | 					// events to keep in buttons...
 | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 					;(options.buttonLocalEvents || options.localEvents || []) | 
					
						
							|  |  |  | 						.forEach(function(evt){ | 
					
						
							|  |  |  | 							button.addEventListener(evt, stopPropagation) }) | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 					// button keys...
 | 
					
						
							|  |  |  | 					keys && !options.disableButtonSortcuts | 
					
						
							|  |  |  | 						&& (keys instanceof Array ? keys : [keys]) | 
					
						
							|  |  |  | 							.forEach(function(key){ | 
					
						
							|  |  |  | 								// XXX should we break or warn???
 | 
					
						
							|  |  |  | 								if(key in button_keys){ | 
					
						
							|  |  |  | 									throw new Error(`renderItem(..): button key already used: ${key}`) } | 
					
						
							|  |  |  | 								button_keys[keyboard.joinKey(keyboard.normalizeKey(key))] = button }) | 
					
						
							| 
									
										
										
										
											2019-06-10 04:07:55 +03:00
										 |  |  | 					// keep focus on the item containing the button -- i.e. if
 | 
					
						
							|  |  |  | 					// we tab out of the item focus the item we get to...
 | 
					
						
							|  |  |  | 					button.addEventListener('focus', function(){ | 
					
						
							|  |  |  | 						item.focused  | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 							// only focus items in the main section, 
 | 
					
						
							|  |  |  | 							// outside of headers and footers...
 | 
					
						
							| 
									
										
										
										
											2019-06-10 04:07:55 +03:00
										 |  |  | 							|| that.focus(item)  | 
					
						
							|  |  |  | 								&& button.focus() }) | 
					
						
							|  |  |  | 					// main button action (click/enter)...
 | 
					
						
							|  |  |  | 					// XXX should there be a secondary action (i.e. shift-enter)???
 | 
					
						
							|  |  |  | 					if(handler){ | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 						var func = handler instanceof Function ? | 
					
						
							|  |  |  | 							handler | 
					
						
							|  |  |  | 							// string handler -> that.<handler>(item)
 | 
					
						
							|  |  |  | 							: function(evt, ...args){ | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 								var a = that.parseStringHandler( | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 									handler,  | 
					
						
							|  |  |  | 									// button handler arg namespace...
 | 
					
						
							|  |  |  | 									{ | 
					
						
							|  |  |  | 										event: evt, | 
					
						
							|  |  |  | 										item: item, | 
					
						
							|  |  |  | 										// NOTE: if we are not focusing 
 | 
					
						
							|  |  |  | 										// 		on button click this may 
 | 
					
						
							|  |  |  | 										// 		be different from item...
 | 
					
						
							|  |  |  | 										focused: that.focused, | 
					
						
							|  |  |  | 										button: html, | 
					
						
							|  |  |  | 									}) | 
					
						
							|  |  |  | 								that[a.action](...a.arguments) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						// handle clicks and keyboard...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 05:07:06 +03:00
										 |  |  | 						button.addEventListener('click', func.bind(that)) | 
					
						
							| 
									
										
										
										
											2019-06-19 00:31:12 +03:00
										 |  |  | 						// NOTE: we only trigger buttons on Enter and do 
 | 
					
						
							|  |  |  | 						// 		not care about other keys...
 | 
					
						
							| 
									
										
										
										
											2019-06-10 04:07:55 +03:00
										 |  |  | 						button.addEventListener('keydown',  | 
					
						
							|  |  |  | 							function(evt){ | 
					
						
							|  |  |  | 								var k = keyboard.event2key(evt) | 
					
						
							|  |  |  | 								if(k.includes('Enter')){ | 
					
						
							|  |  |  | 									event.stopPropagation() | 
					
						
							| 
									
										
										
										
											2019-06-22 06:03:44 +03:00
										 |  |  | 									func.call(that, evt, item) } }) }  | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 				elem.appendChild(button) | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// button shortcut keys...
 | 
					
						
							|  |  |  | 		Object.keys(button_keys).length > 0 | 
					
						
							|  |  |  | 			&& elem.addEventListener('keydown',  | 
					
						
							|  |  |  | 				function(evt){  | 
					
						
							|  |  |  | 				var k = keyboard.joinKey(keyboard.event2key(evt)) | 
					
						
							|  |  |  | 				if(k in button_keys){ | 
					
						
							|  |  |  | 					evt.preventDefault() | 
					
						
							|  |  |  | 					evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 					button_keys[k].focus() | 
					
						
							|  |  |  | 					// XXX should this be optional???
 | 
					
						
							| 
									
										
										
										
											2019-06-23 04:45:31 +03:00
										 |  |  | 					button_keys[k].click() } }) | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 		 | 
					
						
							| 
									
										
										
										
											2019-02-08 21:21:36 +03:00
										 |  |  | 		item.dom = elem | 
					
						
							| 
									
										
										
										
											2019-06-26 19:41:42 +03:00
										 |  |  | 		//item.elem = elem
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-08 21:21:36 +03:00
										 |  |  | 		return elem  | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-25 03:34:29 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 	// Events extensions...
 | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	// NOTE: this will also kill any user-set keys for disabled/hidden items...
 | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX also handle global button keys...
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	__preRender__: function(){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		// reset item shortcuts...
 | 
					
						
							|  |  |  | 		var shortcuts =  | 
					
						
							|  |  |  | 			this.keybindings.ItemShortcuts =  | 
					
						
							|  |  |  | 				Object.assign({}, KEYBOARD_CONFIG.ItemShortcuts) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var i = 0 | 
					
						
							|  |  |  | 		this.map(function(e){ | 
					
						
							|  |  |  | 			// shortcut number hint...
 | 
					
						
							|  |  |  | 			// NOTE: these are just hints, the actual keys are handled 
 | 
					
						
							|  |  |  | 			// 		in .keybindings...
 | 
					
						
							|  |  |  | 			if(i < 10 && !e.disabled && !e.hidden){ | 
					
						
							|  |  |  | 				var attrs = e.attrs = e.attrs || {} | 
					
						
							|  |  |  | 				attrs['shortcut-number'] = (++i) % 10 | 
					
						
							|  |  |  | 			// cleanup...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				delete (e.attrs || {})['shortcut-number'] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			 | 
					
						
							|  |  |  | 			// handle item keys...
 | 
					
						
							|  |  |  | 			if(!e.disabled && !e.hidden){ | 
					
						
							|  |  |  | 				;((e.value instanceof Array ?  | 
					
						
							|  |  |  | 						e.value  | 
					
						
							|  |  |  | 						: [e.value]) | 
					
						
							|  |  |  | 					.join(' ') | 
					
						
							|  |  |  | 					// XXX this does not include non-English chars...
 | 
					
						
							|  |  |  | 					.match(/\$\w/g) || []) | 
					
						
							|  |  |  | 						.map(function(k){ | 
					
						
							|  |  |  | 							k = that.keyboard.normalizeKey(k[1]) | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 							if(!shortcuts[k]){ | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 								shortcuts[k] = function(){  | 
					
						
							|  |  |  | 									// XXX should this focus or open???
 | 
					
						
							|  |  |  | 									that.focus(e) }  | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 								var keys = e.keys = e.keys || [] | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 								keys.includes(k) | 
					
						
							|  |  |  | 									|| keys.push(k) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 							// cleanup...
 | 
					
						
							|  |  |  | 							} else { | 
					
						
							|  |  |  | 								var keys = e.keys || [] | 
					
						
							|  |  |  | 								keys.splice(keys.indexOf(k), 1) | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 							} }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// cleanup...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				delete e.keys | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}, {skipDisabled: false}) | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-06-12 21:45:51 +03:00
										 |  |  | 	// NOTE: element alignment is done via the browser focus mechanics...
 | 
					
						
							| 
									
										
										
										
											2019-06-03 19:22:01 +03:00
										 |  |  | 	__focus__: function(evt, elem){ | 
					
						
							| 
									
										
										
										
											2019-06-04 19:50:52 +03:00
										 |  |  | 		var that = this | 
					
						
							|  |  |  | 		elem | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 			&& elem.elem | 
					
						
							| 
									
										
										
										
											2019-06-04 19:50:52 +03:00
										 |  |  | 				// update the focused CSS class...
 | 
					
						
							|  |  |  | 				// NOTE: we will not remove this class on blur as it keeps
 | 
					
						
							|  |  |  | 				// 		the selected element indicated...
 | 
					
						
							|  |  |  | 				.run(function(){ | 
					
						
							| 
									
										
										
										
											2019-06-11 17:28:38 +03:00
										 |  |  | 					this.classList.add('focused')  | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 					// take care of visibility...
 | 
					
						
							| 
									
										
										
										
											2019-06-11 17:28:38 +03:00
										 |  |  | 					this.scrollIntoView({ | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 						behavior: (that.options || {}).scrollBehavior || 'auto', | 
					
						
							| 
									
										
										
										
											2019-06-11 17:28:38 +03:00
										 |  |  | 						block: 'nearest', | 
					
						
							|  |  |  | 					}) | 
					
						
							|  |  |  | 				}) | 
					
						
							| 
									
										
										
										
											2019-06-04 19:50:52 +03:00
										 |  |  | 				// set focus...
 | 
					
						
							|  |  |  | 				.focus() }, | 
					
						
							| 
									
										
										
										
											2019-06-10 04:07:55 +03:00
										 |  |  | 	__blur__: function(evt, elem){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		elem | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 			&& elem.elem | 
					
						
							| 
									
										
										
										
											2019-06-10 04:07:55 +03:00
										 |  |  | 				.run(function(){ | 
					
						
							|  |  |  | 					this.classList.remove('focused') | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 					// refocus the dialog...
 | 
					
						
							| 
									
										
										
										
											2019-06-10 04:07:55 +03:00
										 |  |  | 					that.dom | 
					
						
							|  |  |  | 						&& that.dom.focus() }) }, | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	__expand__: function(){ this.update() }, | 
					
						
							|  |  |  | 	__collapse__: function(){ this.update() }, | 
					
						
							|  |  |  | 	__select__: updateElemClass('add', 'selected'), | 
					
						
							|  |  |  | 	__deselect__: updateElemClass('remove', 'selected'), | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 	__disable__: updateElemClass('add', 'disabled', function(){ this.update() }), | 
					
						
							|  |  |  | 	__enable__: updateElemClass('remove', 'disabled', function(){ this.update() }), | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	__hide__: updateElemClass('add', 'hidden'), | 
					
						
							|  |  |  | 	__show__: updateElemClass('remove', 'hidden'), | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-04 04:28:08 +03:00
										 |  |  | 	// Custom events...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-18 20:58:40 +03:00
										 |  |  | 	// NOTE: this is not directly connected to DOM key events...
 | 
					
						
							|  |  |  | 	keyPress: makeEventMethod('keypress',  | 
					
						
							|  |  |  | 		function(evt, key){ | 
					
						
							|  |  |  | 			this.__keyboard_handler(key) }), | 
					
						
							| 
									
										
										
										
											2019-06-23 21:18:43 +03:00
										 |  |  | 	// XXX do we need a default behavior here???
 | 
					
						
							|  |  |  | 	// 		...something like .expand(..)
 | 
					
						
							| 
									
										
										
										
											2019-06-19 01:14:13 +03:00
										 |  |  | 	menu: makeItemEventMethod('menu'), | 
					
						
							| 
									
										
										
										
											2019-06-04 04:28:08 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-21 05:29:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	// Scroll...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-21 04:24:14 +03:00
										 |  |  | 	// position can be:
 | 
					
						
							|  |  |  | 	// 	'start'
 | 
					
						
							|  |  |  | 	// 	'center'
 | 
					
						
							|  |  |  | 	// 	'end'
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX use .options.focusOffsetWhileScrolling / nudgeElement(..)
 | 
					
						
							|  |  |  | 	// 		...only need to determine direction...
 | 
					
						
							|  |  |  | 	// 			'start' -> nudgeElement(this, 'up', elem)
 | 
					
						
							|  |  |  | 	// 			'end' -> nudgeElement(this, 'down', elem)
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	scrollTo: function(pattern, position){ | 
					
						
							|  |  |  | 		var target = this.get(pattern) | 
					
						
							|  |  |  | 		target  | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 			&& target.elem | 
					
						
							| 
									
										
										
										
											2019-06-20 22:06:52 +03:00
										 |  |  | 				.scrollIntoView({ | 
					
						
							|  |  |  | 					behavior: (this.options || {}).scrollBehavior || 'auto', | 
					
						
							|  |  |  | 					block: position || 'center', | 
					
						
							|  |  |  | 				}) }, | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-21 05:29:26 +03:00
										 |  |  | 	// Navigation...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-06-17 00:33:44 +03:00
										 |  |  | 	// hold key repeat on first/last elements + reveal disabled items at
 | 
					
						
							|  |  |  | 	// start/end of list...
 | 
					
						
							| 
									
										
										
										
											2019-06-17 02:50:49 +03:00
										 |  |  | 	prev: focusItem('up'), | 
					
						
							|  |  |  | 	next: focusItem('down'),  | 
					
						
							|  |  |  | 	pageUp: focusPage('up'), | 
					
						
							|  |  |  | 	pageDown: focusPage('down'), | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// XXX focus element above/below...
 | 
					
						
							| 
									
										
										
										
											2019-01-21 05:29:26 +03:00
										 |  |  | 	up: function(){}, | 
					
						
							|  |  |  | 	down: function(){}, | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 	// XXX check if there are elements to the left...
 | 
					
						
							|  |  |  | 	left: function(){ | 
					
						
							|  |  |  | 		var focused = this.focused | 
					
						
							|  |  |  | 		var p | 
					
						
							|  |  |  | 		if(!focused){ | 
					
						
							|  |  |  | 			return this.prev() } | 
					
						
							|  |  |  | 		// collapsable -> collapse...
 | 
					
						
							|  |  |  | 		;(focused.children && !focused.collapsed) ? | 
					
						
							|  |  |  | 			this.collapse() | 
					
						
							|  |  |  | 		// on a nested level -> go up one level... 
 | 
					
						
							|  |  |  | 		: (p = this.parentOf()) && p !== this ? | 
					
						
							|  |  |  | 			this.focus(p) | 
					
						
							| 
									
										
										
										
											2019-06-22 02:25:17 +03:00
										 |  |  | 		// top-level -> prev on top-level...
 | 
					
						
							|  |  |  | 		: this.focus(this.get('prev', {skipNested: true})) | 
					
						
							| 
									
										
										
										
											2019-06-04 02:55:53 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 	// XXX check if there are elements to the right...
 | 
					
						
							|  |  |  | 	right: function(){ | 
					
						
							|  |  |  | 		var focused = this.focused | 
					
						
							|  |  |  | 		if(!focused){ | 
					
						
							|  |  |  | 			return this.next() } | 
					
						
							|  |  |  | 		focused.collapsed ? | 
					
						
							|  |  |  | 			this | 
					
						
							|  |  |  | 				.expand() | 
					
						
							|  |  |  | 			: this.next() }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 01:58:08 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 03:08:58 +03:00
										 |  |  | 	// Filtering/search mode...
 | 
					
						
							| 
									
										
										
										
											2019-06-19 01:58:08 +03:00
										 |  |  | 	// XXX
 | 
					
						
							| 
									
										
										
										
											2019-01-21 05:29:26 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | // XXX should this be a Widget too???
 | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | var HTMLBrowser =  | 
					
						
							|  |  |  | module.HTMLBrowser =  | 
					
						
							|  |  |  | object.makeConstructor('HTMLBrowser',  | 
					
						
							|  |  |  | 		HTMLBrowserClassPrototype,  | 
					
						
							|  |  |  | 		HTMLBrowserPrototype) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | // shorthans...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.Item = HTMLItem | 
					
						
							|  |  |  | module.Browser = HTMLBrowser | 
					
						
							| 
									
										
										
										
											2019-01-24 05:32:21 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-21 05:29:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-28 18:10:01 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Text tree renderer...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This is mainly designed for testing.
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-01-30 05:51:18 +03:00
										 |  |  | // XXX Q: how should the header item and it's sub-list be linked???
 | 
					
						
							| 
									
										
										
										
											2019-01-28 18:10:01 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | var TextBrowserClassPrototype = { | 
					
						
							|  |  |  | 	__proto__: BaseBrowser, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var TextBrowserPrototype = { | 
					
						
							|  |  |  | 	__proto__: BaseBrowser.prototype, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	options: { | 
					
						
							|  |  |  | 		renderIndent: '\t', | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	 | 
					
						
							| 
									
										
										
										
											2019-01-29 04:35:39 +03:00
										 |  |  | 	// NOTE: we do not need .renderGroup(..) here as a group is not 
 | 
					
						
							|  |  |  | 	// 		visible in text...
 | 
					
						
							| 
									
										
										
										
											2019-06-29 07:11:30 +03:00
										 |  |  | 	renderList: function(header, items, footer, options){ | 
					
						
							| 
									
										
										
										
											2019-01-28 18:10:01 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 		return this.renderNested(null, items, null, null, options) | 
					
						
							| 
									
										
										
										
											2019-01-28 18:10:01 +03:00
										 |  |  | 			.join('\n') }, | 
					
						
							| 
									
										
										
										
											2019-01-30 06:04:55 +03:00
										 |  |  | 	renderItem: function(item, i, options){ | 
					
						
							|  |  |  | 		return item.current ? | 
					
						
							| 
									
										
										
										
											2019-06-23 17:32:14 +03:00
										 |  |  | 			`[ ${ item.text } ]` | 
					
						
							|  |  |  |    			: item.text }, | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 	renderNested: function(header, children, context, item, options){ | 
					
						
							| 
									
										
										
										
											2019-01-28 18:10:01 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2019-05-04 04:46:29 +03:00
										 |  |  | 		var nested = children  | 
					
						
							|  |  |  | 			&& children | 
					
						
							| 
									
										
										
										
											2019-02-01 06:08:48 +03:00
										 |  |  | 				.flat() | 
					
						
							|  |  |  | 				.map(function(e){ | 
					
						
							|  |  |  | 					return e instanceof Array ? | 
					
						
							|  |  |  | 						e.map(function(e){  | 
					
						
							|  |  |  | 							return (that.options.renderIndent || '  ') + e }) | 
					
						
							|  |  |  | 						: e }) | 
					
						
							|  |  |  | 				.flat()  | 
					
						
							|  |  |  | 		return ( | 
					
						
							|  |  |  | 			// expanded...
 | 
					
						
							|  |  |  | 			header && nested ? | 
					
						
							|  |  |  | 				[ | 
					
						
							| 
									
										
										
										
											2019-06-02 22:57:15 +03:00
										 |  |  | 					...(header == '   ' ?  | 
					
						
							|  |  |  | 						// blank header...
 | 
					
						
							|  |  |  | 						[]  | 
					
						
							|  |  |  | 						: ['- ' + header]), | 
					
						
							| 
									
										
										
										
											2019-02-01 06:08:48 +03:00
										 |  |  | 					nested, | 
					
						
							|  |  |  | 				] | 
					
						
							|  |  |  | 			// collapsed...
 | 
					
						
							|  |  |  | 			: header ? | 
					
						
							| 
									
										
										
										
											2019-02-23 22:06:42 +03:00
										 |  |  | 				[ '+ ' + header ] | 
					
						
							| 
									
										
										
										
											2019-06-02 22:57:15 +03:00
										 |  |  | 			// nested...
 | 
					
						
							| 
									
										
										
										
											2019-02-01 06:08:48 +03:00
										 |  |  | 			: nested )}, | 
					
						
							| 
									
										
										
										
											2019-01-28 18:10:01 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var TextBrowser =  | 
					
						
							|  |  |  | module.TextBrowser =  | 
					
						
							|  |  |  | object.makeConstructor('TextBrowser',  | 
					
						
							|  |  |  | 		TextBrowserClassPrototype,  | 
					
						
							|  |  |  | 		TextBrowserPrototype) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-21 05:29:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:15:41 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | /********************************************************************** | 
					
						
							|  |  |  | * vim:set ts=4 sw=4 :                               */ return module }) |