| 
									
										
										
										
											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-05-03 19:43:22 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							| 
									
										
										
										
											2019-07-06 18:48:57 +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.
 | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | // NOTE: for examples see: Item.nest(..) and Item.group(..)
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // 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-07-06 18:48:57 +03:00
										 |  |  | // Item constructors...
 | 
					
						
							| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | var Items = | 
					
						
							|  |  |  | object.mixinFlat(function(){}, { | 
					
						
							|  |  |  | 	dialog: null, | 
					
						
							|  |  |  | 	called: false, | 
					
						
							| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	// Props...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: writing to .items will reset .called to false...
 | 
					
						
							|  |  |  | 	__items: undefined, | 
					
						
							|  |  |  | 	get items(){ | 
					
						
							|  |  |  | 		return this.__items }, | 
					
						
							|  |  |  | 	set items(value){ | 
					
						
							|  |  |  | 		this.called = false | 
					
						
							|  |  |  | 		this.__items = value }, | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	 | 
					
						
							| 
									
										
										
										
											2019-07-16 19:11:57 +03:00
										 |  |  | 	// Bottons...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// Format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							|  |  |  | 	// 		// Button generator...
 | 
					
						
							|  |  |  | 	// 		<name>: function(item, attr),
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		<name>: [
 | 
					
						
							|  |  |  | 	// 			// text...
 | 
					
						
							|  |  |  | 	// 			//
 | 
					
						
							|  |  |  | 	// 			// NOTE: code is resolved to .buttons[action](..), i.e.
 | 
					
						
							|  |  |  | 	// 			//		a button can reuse other buttons to generate its
 | 
					
						
							|  |  |  | 	// 			//		text...
 | 
					
						
							|  |  |  | 	// 			<code> | <html> | function(item),
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 			// action (optional)...
 | 
					
						
							|  |  |  | 	// 			//
 | 
					
						
							|  |  |  | 	// 			// NOTE: code is resolved to .dialog[action](..)
 | 
					
						
							|  |  |  | 	// 			<code> | function(item),
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 			// disabled predicate (optional)...
 | 
					
						
							|  |  |  | 	// 			function(item),
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 			// attrs (optional)...
 | 
					
						
							|  |  |  | 	// 			{
 | 
					
						
							|  |  |  | 	// 				<name>: <value> | function(item),
 | 
					
						
							|  |  |  | 	// 				...
 | 
					
						
							|  |  |  | 	// 			},
 | 
					
						
							|  |  |  | 	// 		],
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	buttons: { | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	Draw checked checkboz is <attr> is true...
 | 
					
						
							|  |  |  | 		// 	Checkbox('attr')
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	Draw checked checkboz is <attr> is false...
 | 
					
						
							|  |  |  | 		// 	Checkbox('!attr')
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// XXX rename -- distinguish from actual button...
 | 
					
						
							| 
									
										
										
										
											2019-07-16 19:11:57 +03:00
										 |  |  | 		Checkbox: function(item, attr=''){ | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 			return (attr[0] == '!'  | 
					
						
							|  |  |  | 						&& !item[attr.slice(1)])  | 
					
						
							|  |  |  | 					|| item[attr] ?  | 
					
						
							|  |  |  | 				'☐'  | 
					
						
							|  |  |  | 				: '☑' }, | 
					
						
							| 
									
										
										
										
											2019-07-16 19:11:57 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 		// XXX can we make these not use the same icon...
 | 
					
						
							|  |  |  | 		ToggleDisabled: [ | 
					
						
							|  |  |  | 			'Checkbox: "disabled"', | 
					
						
							|  |  |  | 			'toggleDisabled: item', | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				alt: 'Disable/enable item', | 
					
						
							|  |  |  | 				cls: 'toggle-disabled', | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 		ToggleHidden: [ | 
					
						
							|  |  |  | 			'Checkbox: "hidden"', | 
					
						
							|  |  |  | 			'toggleHidden: item', | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				alt: 'Show/hide item', | 
					
						
							|  |  |  | 				cls: 'toggle-hidden', | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 		ToggleSelected: [ | 
					
						
							|  |  |  | 			'Checkbox: "selected"', | 
					
						
							|  |  |  | 			'toggleSelect: item', | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				alt: 'Select/deselect item', | 
					
						
							|  |  |  | 				cls: 'toggle-select', | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 		// NOTE: this button is disabled for all items but the ones with .children...
 | 
					
						
							|  |  |  | 		ToggleCollapse: [ | 
					
						
							|  |  |  | 			function(item){ | 
					
						
							|  |  |  | 				return !item.children ? | 
					
						
							|  |  |  | 						// placeholder...
 | 
					
						
							|  |  |  | 						' ' | 
					
						
							|  |  |  | 					: item.collapsed ? | 
					
						
							|  |  |  | 						'+' | 
					
						
							|  |  |  | 					: '-' }, | 
					
						
							|  |  |  | 			'toggleCollapse: item', | 
					
						
							|  |  |  | 			// disable button for all items that do not have children...
 | 
					
						
							|  |  |  | 			function(item){  | 
					
						
							|  |  |  | 				return 'children' in item }, | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				alt: 'Collapse/expand item', | 
					
						
							|  |  |  | 				cls: function(item){  | 
					
						
							|  |  |  | 					return 'children' in item ?  | 
					
						
							|  |  |  | 						'toggle-collapse'  | 
					
						
							|  |  |  | 						: ['toggle-collapse', 'blank'] }, | 
					
						
							|  |  |  | 			}], | 
					
						
							| 
									
										
										
										
											2019-07-16 19:11:57 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// NOTE: this requires .markDelete(..) action...
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 		Delete: [ | 
					
						
							|  |  |  | 			'×', | 
					
						
							|  |  |  | 			'markDelete: item', | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				alt: 'Mark item for deletion', | 
					
						
							|  |  |  | 				cls: 'toggle-delete', | 
					
						
							|  |  |  | 				//keys: ['Delete', 'd'],
 | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-04 14:18:00 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	// Getters...
 | 
					
						
							| 
									
										
										
										
											2019-01-26 21:32:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	// Last item created...
 | 
					
						
							|  |  |  | 	// XXX not sure about this...
 | 
					
						
							|  |  |  | 	// XXX should this be a prop???
 | 
					
						
							|  |  |  | 	last: function(){ | 
					
						
							|  |  |  | 		return (this.items || [])[this.items.length - 1] }, | 
					
						
							| 
									
										
										
										
											2019-01-22 07:10:49 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	// Constructors/modifiers...
 | 
					
						
							| 
									
										
										
										
											2019-01-22 07:10:49 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	// Group a set of items...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.group(make(..), ..)
 | 
					
						
							|  |  |  | 	//	.group([make(..), ..])
 | 
					
						
							|  |  |  | 	//		-> make
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// Example:
 | 
					
						
							|  |  |  | 	// 	make.group(
 | 
					
						
							|  |  |  | 	// 		make('made item'),
 | 
					
						
							|  |  |  | 	// 		'literal item',
 | 
					
						
							|  |  |  | 	// 		...)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: see notes to collectItems(..) for more info...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX do we need to pass options to groups???
 | 
					
						
							|  |  |  | 	group: function(...items){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		items = items.length == 1 && items[0] instanceof Array ? | 
					
						
							|  |  |  | 			items[0] | 
					
						
							|  |  |  | 			: items | 
					
						
							|  |  |  | 		// replace the items with the group...
 | 
					
						
							|  |  |  | 		this.items.splice(this.items.length, 0, collectItems(this, items)) | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2019-02-05 22:12:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	// Place list in a sub-list of item...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// Examples:
 | 
					
						
							|  |  |  | 	// 	make.nest('literal header', [
 | 
					
						
							|  |  |  | 	// 		'literal item',
 | 
					
						
							|  |  |  | 	// 		make('item'),
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	])
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	make.nest(make('header'), [
 | 
					
						
							|  |  |  | 	// 		'literal item',
 | 
					
						
							|  |  |  | 	// 		make('item'),
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	])
 | 
					
						
							|  |  |  | 	// 	
 | 
					
						
							|  |  |  | 	nest: function(item, list, options){ | 
					
						
							|  |  |  | 		options = options || {} | 
					
						
							|  |  |  | 		//options = Object.assign(Object.create(this.options || {}), options || {})
 | 
					
						
							|  |  |  | 		options = Object.assign({}, | 
					
						
							|  |  |  | 			{ children: list instanceof Array ? | 
					
						
							|  |  |  | 				collectItems(this, list) | 
					
						
							|  |  |  | 				: list }, | 
					
						
							|  |  |  | 			options) | 
					
						
							|  |  |  | 		return item === this ? | 
					
						
							|  |  |  | 			((this.last().children = options.children), this) | 
					
						
							|  |  |  | 			: this(item, options) }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Wrappers...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// this is here for uniformity...
 | 
					
						
							|  |  |  | 	Item: function(value, options){  | 
					
						
							|  |  |  | 		return this(...arguments) }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Empty: function(value){}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Separator: function(){  | 
					
						
							|  |  |  | 		return this('---') }, | 
					
						
							|  |  |  | 	Spinner: function(){  | 
					
						
							|  |  |  | 		return this('...') }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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] | 
					
						
							|  |  |  | 		options.buttons = options.buttons  | 
					
						
							|  |  |  | 			|| this.dialog.options.headingButtons | 
					
						
							|  |  |  | 		return this(value, options) }, | 
					
						
							|  |  |  | 	Action: function(value, options){}, | 
					
						
							|  |  |  | 	ConfirmAction: function(value){}, | 
					
						
							|  |  |  | 	Editable: function(value){}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// lists...
 | 
					
						
							|  |  |  | 	List: function(values){}, | 
					
						
							|  |  |  | 	EditableList: function(values){}, | 
					
						
							|  |  |  | 	EditablePinnedList: function(values){}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Special list components...
 | 
					
						
							|  |  |  | 	//Items.ListPath = function(){},
 | 
					
						
							|  |  |  | 	//Items.ListTitle = function(){},
 | 
					
						
							| 
									
										
										
										
											2019-06-22 06:03:44 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	// XXX EXPERIMENTAL...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// options:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							|  |  |  | 	// 		showOKButton: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	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  | 
					
						
							|  |  |  | 							&& (options || {}).showOKButton ? | 
					
						
							|  |  |  | 						[['$OK', accept]] | 
					
						
							|  |  |  | 						: []), ],  | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				accept ?  | 
					
						
							|  |  |  | 					{open: accept} | 
					
						
							|  |  |  | 					: {}, | 
					
						
							|  |  |  | 				options || {})) }, | 
					
						
							| 
									
										
										
										
											2019-06-22 06:03:44 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +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...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Make item generator...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	makeDisplayItem: function(text, options){ | 
					
						
							|  |  |  | 		var args = [...arguments] | 
					
						
							|  |  |  | 		return function(make, options){ | 
					
						
							|  |  |  | 			make(...args) } }, | 
					
						
							| 
									
										
										
										
											2019-06-22 18:06:39 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	// Make confirm item generator...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX move this to Item.Confirm(..) and reuse that...
 | 
					
						
							|  |  |  | 	makeDisplayConfirm: function(message, accept, reject){ | 
					
						
							|  |  |  | 		return this.makeDisplayItem(message, { | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 			buttons: [ | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 				...[reject instanceof Function ? | 
					
						
							|  |  |  | 					['Cancel', reject] | 
					
						
							|  |  |  | 					: []], | 
					
						
							|  |  |  | 				...[accept instanceof Function ? | 
					
						
							|  |  |  | 					['OK', accept] | 
					
						
							|  |  |  | 					: []], ], }) }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Focused item path...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-07-17 16:48:11 +03:00
										 |  |  | 	// NOTE: this can be called as section generators, so they must 
 | 
					
						
							|  |  |  | 	// 		comply the func(make, options) signature...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	// XXX add search/filter field...
 | 
					
						
							|  |  |  | 	// XXX add path navigation...
 | 
					
						
							|  |  |  | 	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',  | 
					
						
							|  |  |  | 				Object.assign( | 
					
						
							|  |  |  | 					{ | 
					
						
							|  |  |  | 						id: tag, | 
					
						
							|  |  |  | 						cls: 'path',  | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 					options)) | 
					
						
							|  |  |  | 			.last() | 
					
						
							|  |  |  | 		// event handlers...
 | 
					
						
							|  |  |  | 		dialog  | 
					
						
							|  |  |  | 			.off('*', tag) | 
					
						
							|  |  |  | 			.on('focus',  | 
					
						
							|  |  |  | 				function(){ | 
					
						
							|  |  |  | 					e.value = this.pathArray | 
					
						
							|  |  |  | 					this.renderItem(e) }, | 
					
						
							|  |  |  | 				tag)  | 
					
						
							|  |  |  | 		return make }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Item info...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// Show item .info or .alt text.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// This will show info for items that are:
 | 
					
						
							|  |  |  | 	// 	- focused
 | 
					
						
							|  |  |  | 	// 	- hovered (not yet implemented)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-07-17 16:48:11 +03:00
										 |  |  | 	// NOTE: this can be called as section generators, so they must 
 | 
					
						
							|  |  |  | 	// 		comply the func(make, options) signature...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	// XXX use focused elements and not just item...
 | 
					
						
							|  |  |  | 	// XXX add on mouse over...
 | 
					
						
							|  |  |  | 	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',  | 
					
						
							|  |  |  | 				Object.assign( | 
					
						
							|  |  |  | 					{ | 
					
						
							|  |  |  | 						id: tag, | 
					
						
							|  |  |  | 						cls: 'info', | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 					options)) | 
					
						
							|  |  |  | 			.last() | 
					
						
							|  |  |  | 		// event handlers...
 | 
					
						
							|  |  |  | 		dialog | 
					
						
							|  |  |  | 			.off('*', tag) | 
					
						
							|  |  |  | 			.on('focus', | 
					
						
							|  |  |  | 				function(){ | 
					
						
							|  |  |  | 					var focused = this.focused | 
					
						
							|  |  |  | 					e.value = focused.doc | 
					
						
							|  |  |  | 						|| focused.alt | 
					
						
							|  |  |  | 						|| ' ' | 
					
						
							|  |  |  | 					this.renderItem(e) }, | 
					
						
							|  |  |  | 			tag)  | 
					
						
							|  |  |  | 		return make }, | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-17 16:48:11 +03:00
										 |  |  | 	// Instance constructors...
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	__new__: function(_, dialog, constructor){ | 
					
						
							|  |  |  | 		var that = function(){ | 
					
						
							|  |  |  | 			that.called = true | 
					
						
							|  |  |  | 			constructor.call(that, ...arguments) | 
					
						
							|  |  |  | 			return that } | 
					
						
							|  |  |  | 		return that }, | 
					
						
							|  |  |  | 	__init__: function(dialog){ | 
					
						
							|  |  |  | 		this.items = [] | 
					
						
							|  |  |  | 		this.dialog = dialog }, | 
					
						
							|  |  |  | }) | 
					
						
							| 
									
										
										
										
											2019-07-01 04:47:31 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | var Make =  | 
					
						
							|  |  |  | module.Make =  | 
					
						
							| 
									
										
										
										
											2019-08-24 18:39:37 +03:00
										 |  |  | 	object.Constructor('Make', Items) | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2019-07-06 18:48:57 +03:00
										 |  |  | // Base Item...
 | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | var BaseItemClassPrototype = { | 
					
						
							|  |  |  | 	text: function(elem){ | 
					
						
							| 
									
										
										
										
											2019-07-15 15:00:07 +03:00
										 |  |  | 		return elem.value instanceof Array ? | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 				elem.value.join(' ')	 | 
					
						
							|  |  |  | 			: elem.value == null || elem.value instanceof Object ? | 
					
						
							|  |  |  | 				elem.alt || elem.__id  | 
					
						
							| 
									
										
										
										
											2019-07-15 15:00:07 +03:00
										 |  |  | 			: elem.value }, | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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
										 |  |  | 	 | 
					
						
							| 
									
										
										
										
											2019-07-15 15:00:07 +03:00
										 |  |  | 	// item id if explicitly set otherwise its .text...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this will not fall into infinite recursion with .text as 
 | 
					
						
							|  |  |  | 	// 		the later accesses .__id directly...
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-15 15:00:07 +03:00
										 |  |  | 	// normalized .value, .alt or .__id
 | 
					
						
							| 
									
										
										
										
											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-08-16 19:10:03 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// XXX local update/render...
 | 
					
						
							|  |  |  | 	// XXX should we use these in the main render???
 | 
					
						
							|  |  |  | 	update: function(){ | 
					
						
							| 
									
										
										
										
											2019-08-19 18:34:06 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// XXX
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this | 
					
						
							| 
									
										
										
										
											2019-08-16 19:10:03 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 =  | 
					
						
							| 
									
										
										
										
											2019-07-17 00:07:24 +03:00
										 |  |  | object.Constructor('BaseItem',  | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 	BaseItemClassPrototype, | 
					
						
							|  |  |  | 	BaseItemPrototype) | 
					
						
							| 
									
										
										
										
											2019-06-23 17:06:51 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2019-07-06 03:33:21 +03:00
										 |  |  | // View mixin...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This is used as a basis for Browser object wrappers (views) generated
 | 
					
						
							|  |  |  | // via .view(..)
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // NOTE: this is not intended for direct use.
 | 
					
						
							|  |  |  | // NOTE: to call .source methods from inside a view's <method> you can 
 | 
					
						
							|  |  |  | // 		do one of the following:
 | 
					
						
							|  |  |  | // 			// for isolated calls, i.e. calls that may not affect the 
 | 
					
						
							|  |  |  | // 			// view object directly...
 | 
					
						
							|  |  |  | // 			this.source.<method>(..)
 | 
					
						
							|  |  |  | // 			this.__proto__.<method>(..)
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 			// for proper super calls...
 | 
					
						
							|  |  |  | // 			this.__proto__.<method>.call(this, ..)
 | 
					
						
							| 
									
										
										
										
											2019-07-09 18:21:15 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // XXX care must be taken with attribute assignment through the proxy/view 
 | 
					
						
							|  |  |  | // 		object, most of the state of the Browser is stored in mutable 
 | 
					
						
							|  |  |  | // 		objects/props, some are intentionally overwritten by the proxy
 | 
					
						
							|  |  |  | // 		(like .items / .__items, ...) and some are not, but any attribute 
 | 
					
						
							|  |  |  | // 		assignment through the proxy/view if not transferred to the .source
 | 
					
						
							|  |  |  | // 		will not reach it.
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-07-10 04:42:06 +03:00
										 |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							| 
									
										
										
										
											2019-07-09 18:21:15 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-06 03:33:21 +03:00
										 |  |  | // Get the view/mixin source root...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | //	Get .source root...
 | 
					
						
							|  |  |  | // 	getSource(object)
 | 
					
						
							| 
									
										
										
										
											2019-07-06 03:33:21 +03:00
										 |  |  | // 		-> object
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 	Get closest object in .source chain containing attr...
 | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | // 	getSource(object, attr)
 | 
					
						
							| 
									
										
										
										
											2019-07-06 03:33:21 +03:00
										 |  |  | // 		-> object
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // NOTE: a view can be created from a view and so on, so .source may not
 | 
					
						
							|  |  |  | // 		necessarily point to the actual root object...
 | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | var getSource = function(o, attr){ | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 	var cur = o | 
					
						
							|  |  |  | 	while(cur.source  | 
					
						
							|  |  |  | 			&& (!attr  | 
					
						
							|  |  |  | 				|| !cur.hasOwnProperty(attr))){ | 
					
						
							|  |  |  | 		cur = cur.source } | 
					
						
							|  |  |  | 	return cur } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-05 22:16:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-06 03:33:21 +03:00
										 |  |  | // View mixin...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | // This adds the following attrs/props:
 | 
					
						
							|  |  |  | // 	.source
 | 
					
						
							|  |  |  | // 	.rootSource
 | 
					
						
							|  |  |  | // 	.query
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This adds the following methods:
 | 
					
						
							|  |  |  | // 	.isView()
 | 
					
						
							|  |  |  | // 		-> true	
 | 
					
						
							|  |  |  | // 	.sync()
 | 
					
						
							|  |  |  | // 		-> this			
 | 
					
						
							|  |  |  | // 	.end()
 | 
					
						
							|  |  |  | // 		-> source
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // NOTE: options changes are isolated to the view, to change the source 
 | 
					
						
							|  |  |  | // 		options use:
 | 
					
						
							|  |  |  | // 			// to change the parent's options...
 | 
					
						
							|  |  |  | // 			.source.options.x = ...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 			// to change the root options...
 | 
					
						
							|  |  |  | // 			.rootSource.options.x = ...
 | 
					
						
							| 
									
										
										
										
											2019-07-11 02:46:00 +03:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2019-07-14 10:58:24 +03:00
										 |  |  | // XXX can/should we use a Proxy object for this???
 | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | // XXX would be nice to be able to thread a set of options into the view 
 | 
					
						
							| 
									
										
										
										
											2019-07-14 10:58:24 +03:00
										 |  |  | // 		when constructing via .search(..) and friends...
 | 
					
						
							| 
									
										
										
										
											2019-07-05 22:09:15 +03:00
										 |  |  | var BrowserViewMixin = { | 
					
						
							| 
									
										
										
										
											2019-07-06 17:50:59 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-07-05 22:09:15 +03:00
										 |  |  | 	// source: <object>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-07-05 22:16:33 +03:00
										 |  |  | 	// query: [ .. ],
 | 
					
						
							| 
									
										
										
										
											2019-07-06 17:50:59 +03:00
										 |  |  | 	 | 
					
						
							|  |  |  | 	// NOTE: this is not live, changes to this will take effect on next 
 | 
					
						
							|  |  |  | 	// 		view instance creation, to change options assign to .options
 | 
					
						
							|  |  |  | 	// 		or .source.options...
 | 
					
						
							|  |  |  | 	__view_options_defaults__: { | 
					
						
							|  |  |  | 		// Views are flat by default...
 | 
					
						
							| 
									
										
										
										
											2019-07-10 04:42:06 +03:00
										 |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2019-07-13 00:02:15 +03:00
										 |  |  | 		// NOTE: if false with .renderUnique also false and including an
 | 
					
						
							|  |  |  | 		// 		item with .children, the view will render nested elements
 | 
					
						
							|  |  |  | 		// 		twice, once in their respective sub-tree and for the 
 | 
					
						
							|  |  |  | 		// 		second time in the list...
 | 
					
						
							| 
									
										
										
										
											2019-07-14 10:58:24 +03:00
										 |  |  | 		skipNested: true, | 
					
						
							|  |  |  | 	 | 
					
						
							| 
									
										
										
										
											2019-07-13 00:02:15 +03:00
										 |  |  | 		// XXX should we have an ability to skip children if the parent is
 | 
					
						
							|  |  |  | 		// 		not selected???
 | 
					
						
							| 
									
										
										
										
											2019-07-14 10:58:24 +03:00
										 |  |  | 		// XXX might also be a good idea to be able to disable sub-trees...
 | 
					
						
							|  |  |  | 		//skipDisabledTree: true,
 | 
					
						
							| 
									
										
										
										
											2019-07-06 17:50:59 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 	 | 
					
						
							| 
									
										
										
										
											2019-07-10 14:26:01 +03:00
										 |  |  | 	// Construct options by merging option defaults with .source options...
 | 
					
						
							| 
									
										
										
										
											2019-07-06 17:50:59 +03:00
										 |  |  | 	get options(){ | 
					
						
							|  |  |  | 		return (this.__options =  | 
					
						
							|  |  |  | 			this.__options  | 
					
						
							|  |  |  | 				|| this.query[2] | 
					
						
							|  |  |  | 				|| Object.assign( | 
					
						
							|  |  |  | 					{ __proto__: this.source.options || {} }, | 
					
						
							|  |  |  | 					this.__view_options_defaults__ || {}) ) }, | 
					
						
							|  |  |  | 	set options(value){ | 
					
						
							|  |  |  | 		this.__options = value }, | 
					
						
							| 
									
										
										
										
											2019-07-05 22:09:15 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | 	//source: null,
 | 
					
						
							|  |  |  | 	get rootSource(){ | 
					
						
							|  |  |  | 		return getSource(this) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 	// keep the DOM data in one place (.source)...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: this is in contrast to the rest of the props that 
 | 
					
						
							|  |  |  | 	// 		are explicitly local...
 | 
					
						
							|  |  |  | 	// NOTE: these will affect the source only when .render(..) 
 | 
					
						
							|  |  |  | 	// 		is called...
 | 
					
						
							|  |  |  | 	get dom(){ | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | 		return getSource(this, '__dom').dom }, | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 	set dom(value){ | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | 		getSource(this, '__dom').dom = value }, | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 	get container(){ | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | 		return getSource(this, '__container').container }, | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 	set container(value){ | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | 		getSource(this, '__container').container = value }, | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-06 02:45:57 +03:00
										 |  |  | 	// refresh local items if/when diverging from .source...
 | 
					
						
							|  |  |  | 	get items(){ | 
					
						
							|  |  |  | 		return this.hasOwnProperty('__items')  | 
					
						
							|  |  |  | 				&& this.isCurrent() ? | 
					
						
							|  |  |  | 			this.__items | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | 			: this.sync() }, | 
					
						
							| 
									
										
										
										
											2019-07-06 02:45:57 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// check if we are current with .source...
 | 
					
						
							|  |  |  | 	isCurrent: function(){ | 
					
						
							|  |  |  | 		return new Set(Object.values(this.source.index)).has(this.__items[0]) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-05 22:16:33 +03:00
										 |  |  | 	isView: function(){ | 
					
						
							|  |  |  | 		return true }, | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 	end: function(){ | 
					
						
							|  |  |  | 		return this.source }, | 
					
						
							| 
									
										
										
										
											2019-07-05 22:09:15 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// NOTE: we are not simply doing this in .make(..) as we need to be 
 | 
					
						
							|  |  |  | 	// 		able to refresh the data without triggering .make(..) on the 
 | 
					
						
							|  |  |  | 	// 		source object...
 | 
					
						
							|  |  |  | 	// XXX should this be .refresh()???
 | 
					
						
							|  |  |  | 	// 		...if yes what's going to be the difference between it here 
 | 
					
						
							|  |  |  | 	// 		and in the source object???
 | 
					
						
							| 
									
										
										
										
											2019-07-06 17:50:59 +03:00
										 |  |  | 	// 		rename to .sync()??
 | 
					
						
							| 
									
										
										
										
											2019-07-05 22:09:15 +03:00
										 |  |  | 	// XXX how do we handle sections???
 | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | 	sync: function(){ | 
					
						
							| 
									
										
										
										
											2019-07-05 22:16:33 +03:00
										 |  |  | 		var source = this.source | 
					
						
							| 
									
										
										
										
											2019-07-06 17:50:59 +03:00
										 |  |  | 		var [action, args, options] = this.query | 
					
						
							| 
									
										
										
										
											2019-07-05 22:09:15 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-06 02:45:57 +03:00
										 |  |  | 		this.clearCache() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return (this.items =  | 
					
						
							| 
									
										
										
										
											2019-07-06 17:50:59 +03:00
										 |  |  | 			action == 'as-is' ? | 
					
						
							|  |  |  | 				args | 
					
						
							|  |  |  | 			: action instanceof Array ? | 
					
						
							| 
									
										
										
										
											2019-07-05 22:09:15 +03:00
										 |  |  | 				action | 
					
						
							|  |  |  | 					.map(function(e){  | 
					
						
							| 
									
										
										
										
											2019-07-06 02:45:57 +03:00
										 |  |  | 						return source.get(e) }) | 
					
						
							| 
									
										
										
										
											2019-07-05 22:09:15 +03:00
										 |  |  | 			: action ? | 
					
						
							| 
									
										
										
										
											2019-07-05 22:16:33 +03:00
										 |  |  | 				source[action](...args)  | 
					
						
							| 
									
										
										
										
											2019-07-06 02:45:57 +03:00
										 |  |  | 			: source.items.slice()) | 
					
						
							| 
									
										
										
										
											2019-07-05 22:09:15 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 	make: function(){ | 
					
						
							|  |  |  | 		var res = this.__proto__.make(...arguments) | 
					
						
							| 
									
										
										
										
											2019-07-16 17:09:27 +03:00
										 |  |  | 		this.sync() | 
					
						
							| 
									
										
										
										
											2019-07-05 22:09:15 +03:00
										 |  |  | 		return res | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | // XXX if this is the common case shouldn't we set the args as defaults 
 | 
					
						
							|  |  |  | // 		to .View(..) ???
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | var viewWrap = | 
					
						
							|  |  |  | function(context, lst, options){ | 
					
						
							|  |  |  | 	return context.view( | 
					
						
							|  |  |  | 		'as-is',  | 
					
						
							|  |  |  | 		lst,  | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			__proto__: context.options || {}, | 
					
						
							|  |  |  | 			skipNested: 'skipNested' in (options || {}) ?  | 
					
						
							|  |  |  | 				options.skipNested  | 
					
						
							|  |  |  | 				: true, | 
					
						
							|  |  |  | 		}) } | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Make a View wrapper function for use in .run(..)...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | var makeFlatRunViewWrapper =  | 
					
						
							|  |  |  | function(context, options){ | 
					
						
							|  |  |  | 	return function(){ | 
					
						
							|  |  |  | 		return (options || {}).rawResults === true ? | 
					
						
							|  |  |  | 			this | 
					
						
							|  |  |  | 			: viewWrap(context, this, options) } }  | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-06 17:50:59 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // options format:
 | 
					
						
							|  |  |  | // 	{
 | 
					
						
							|  |  |  | // 		// if true this will overwrite the wrapper with false...
 | 
					
						
							|  |  |  | //		//
 | 
					
						
							|  |  |  | // 		// default: undefined
 | 
					
						
							|  |  |  | // 		rawResults: <bool>,
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 		// If present it will be returned...
 | 
					
						
							|  |  |  | // 		wrapper: null | <function>,
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 		// default: true
 | 
					
						
							|  |  |  | // 		skipNested: <bool>,
 | 
					
						
							|  |  |  | // 	}
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | var makeFlatViewWrapper =  | 
					
						
							|  |  |  | function(options){ | 
					
						
							|  |  |  | 	return (options || {}).rawResults === true ? | 
					
						
							|  |  |  | 		false | 
					
						
							|  |  |  | 		: (options.wrapper  | 
					
						
							|  |  |  | 			|| function(res){ | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 				return viewWrap(this, res, options) }) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-06 17:50:59 +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 =  | 
					
						
							| 
									
										
										
										
											2019-07-17 00:07:24 +03:00
										 |  |  | object.Constructor('BrowserEvent',  | 
					
						
							| 
									
										
										
										
											2019-05-23 16:07:57 +03:00
										 |  |  | { | 
					
						
							|  |  |  | 	// 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-07-06 18:48:57 +03:00
										 |  |  | 				rawResults: 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  | 
					
						
							| 
									
										
										
										
											2019-07-25 01:53:39 +03:00
										 |  |  | 					// handler registration...
 | 
					
						
							| 
									
										
										
										
											2019-07-24 17:16:59 +03:00
										 |  |  | 					&& !(resolved instanceof Function) | 
					
						
							| 
									
										
										
										
											2019-07-01 20:13:20 +03:00
										 |  |  | 					&& 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
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-25 03:34:21 +03:00
										 |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							| 
									
										
										
										
											2019-07-06 18:48:57 +03:00
										 |  |  | // Base Browser...
 | 
					
						
							| 
									
										
										
										
											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...
 | 
					
						
							| 
									
										
										
										
											2019-07-15 15:00:07 +03:00
										 |  |  | 		//
 | 
					
						
							|  |  |  | 		// If false and two items have the same key but no .id set a unique
 | 
					
						
							|  |  |  | 		// .id will be generated to distinguish the items.
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: item.id is still required to be unique.
 | 
					
						
							|  |  |  | 		// NOTE: see .__key__(..) and .__id__(..) for key/id generation
 | 
					
						
							|  |  |  | 		// 		specifics.
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// default: false
 | 
					
						
							| 
									
										
										
										
											2019-05-30 19:44:22 +03:00
										 |  |  | 		uniqueKeys: false, | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 22:38:22 +03:00
										 |  |  | 		// if true do not render an item more than once... 
 | 
					
						
							| 
									
										
										
										
											2019-07-15 15:00:07 +03:00
										 |  |  | 		//
 | 
					
						
							|  |  |  | 		// default: true
 | 
					
						
							| 
									
										
										
										
											2019-07-12 23:04:10 +03:00
										 |  |  | 		renderUnique: true, | 
					
						
							| 
									
										
										
										
											2019-07-12 22:38:22 +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-08-01 18:45:29 +03:00
										 |  |  | 	// Root dialog...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	get root(){ | 
					
						
							|  |  |  | 		var cur = this | 
					
						
							|  |  |  | 		while(cur.parent instanceof BaseBrowser){ | 
					
						
							|  |  |  | 			cur = cur.parent | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return cur }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-08-22 17:57:29 +03:00
										 |  |  | 	// 		children: ...,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											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-07-05 21:49:36 +03:00
										 |  |  | 	// XXX should we use .hasOwnProperty(..)???
 | 
					
						
							| 
									
										
										
										
											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){ | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 			delete this.__header | 
					
						
							| 
									
										
										
										
											2019-06-23 21:18:43 +03:00
										 |  |  | 			delete this.__items | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 			delete this.__footer | 
					
						
							| 
									
										
										
										
											2019-06-23 21:18:43 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											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-07-04 19:17:01 +03:00
										 |  |  | 	// XXX should we use .hasOwnProperty(..)???
 | 
					
						
							| 
									
										
										
										
											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 =  | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 			(this.hasOwnProperty('__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-08-30 22:53:41 +03:00
										 |  |  | 		return this.toArray().length }, | 
					
						
							| 
									
										
										
										
											2019-05-10 21:07:17 +03:00
										 |  |  | 	// include collapsed elements...
 | 
					
						
							| 
									
										
										
										
											2019-03-17 21:54:26 +03:00
										 |  |  | 	get lengthTree(){ | 
					
						
							| 
									
										
										
										
											2019-08-30 22:53:41 +03:00
										 |  |  | 		return this.map({iterateCollapsed: true, rawResults: 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-08-30 22:53:41 +03:00
										 |  |  | 		return this.map({iterateAll: true, rawResults: 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...
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 	// 		one way to go:
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	// 			- 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 ids = new Set() | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 		var keys = options.uniqueKeys ?  | 
					
						
							|  |  |  | 			new Set()  | 
					
						
							|  |  |  | 			: null | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 		var make = new Make(this,  | 
					
						
							|  |  |  | 			function(value, opts){ | 
					
						
							|  |  |  | 				var dialog = this.dialog | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// 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...
 | 
					
						
							|  |  |  | 				if(value instanceof BaseBrowser){ | 
					
						
							|  |  |  | 					var item = value | 
					
						
							|  |  |  | 					item.parent = dialog | 
					
						
							|  |  |  | 					item.section = section | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// 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 = dialog.__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) | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 					} | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 					// 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...
 | 
					
						
							|  |  |  | 					// 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...
 | 
					
						
							|  |  |  | 					var item = new dialog.__item__( | 
					
						
							|  |  |  | 						// default item template...
 | 
					
						
							|  |  |  | 						(options.itemTemplate || {})['*'] || {}, | 
					
						
							|  |  |  | 						// item template...
 | 
					
						
							|  |  |  | 						(options.itemTemplate || {})[opts.value] || {}, | 
					
						
							|  |  |  | 						opts, | 
					
						
							|  |  |  | 						{  | 
					
						
							|  |  |  | 							parent: dialog,  | 
					
						
							|  |  |  | 							section, | 
					
						
							|  |  |  | 						}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// XXX do we need both this and the above ref???
 | 
					
						
							|  |  |  | 					item.children instanceof BaseBrowser | 
					
						
							|  |  |  | 						&& (item.children.parent = dialog) | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 				// user extended make...
 | 
					
						
							|  |  |  | 				// XXX differentiate this for header and list...
 | 
					
						
							|  |  |  | 				dialog.__make__ | 
					
						
							|  |  |  | 					&& dialog.__make__(section, item) | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 				// store the item...
 | 
					
						
							|  |  |  | 				this.items.push(item) | 
					
						
							|  |  |  | 				ids.add(key)  | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 		// build the sections...
 | 
					
						
							| 
									
										
										
										
											2019-07-06 02:45:57 +03:00
										 |  |  | 		var reset_index = false | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 		sections | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 			.forEach(function([name, handler]){ | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 				// setup state/closure for make(..)...
 | 
					
						
							| 
									
										
										
										
											2019-06-28 17:48:02 +03:00
										 |  |  | 				ids = new Set() | 
					
						
							| 
									
										
										
										
											2019-07-16 18:52:18 +03:00
										 |  |  | 				section = name | 
					
						
							|  |  |  | 				make.items = that[name] = [] | 
					
						
							| 
									
										
										
										
											2019-06-28 17:48:02 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-06 02:45:57 +03:00
										 |  |  | 				// prepare for index reset...
 | 
					
						
							|  |  |  | 				reset_index = reset_index || name == 'items' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-28 17:48:02 +03:00
										 |  |  | 				// 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-07-16 18:52:18 +03:00
										 |  |  | 				that[name] = make.called ?  | 
					
						
							| 
									
										
										
										
											2019-06-28 17:48:02 +03:00
										 |  |  | 					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?)
 | 
					
						
							| 
									
										
										
										
											2019-07-06 02:45:57 +03:00
										 |  |  | 		if(reset_index){ | 
					
						
							| 
									
										
										
										
											2019-06-29 01:20:02 +03:00
										 |  |  | 			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 | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-06 03:33:21 +03:00
										 |  |  | 	// Data views...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// For View object specifics see: BrowserViewMixin
 | 
					
						
							|  |  |  | 	 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// TODO:
 | 
					
						
							|  |  |  | 	// 	- set correct isolation boundary between this and .source...
 | 
					
						
							|  |  |  | 	// 	- make this a real instance (???)
 | 
					
						
							|  |  |  | 	// 		...do we need this for anything other than doc???
 | 
					
						
							|  |  |  | 	// 	- return from selectors...
 | 
					
						
							|  |  |  | 	// 	- treat .items as cache 
 | 
					
						
							|  |  |  | 	// 		-> reset on parent .make(..)
 | 
					
						
							|  |  |  | 	// 		-> re-acquire data (???)
 | 
					
						
							|  |  |  | 	// 	- take control (optionally), i.e. handle keyboard
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX BUG?: .update(..) from events resolves to the .source...
 | 
					
						
							|  |  |  | 	// 		to reproduce:
 | 
					
						
							|  |  |  | 	// 			dialog
 | 
					
						
							|  |  |  | 	//				.clone([7, 8, 9])
 | 
					
						
							|  |  |  | 	//				.update()
 | 
					
						
							|  |  |  | 	//				.focus()
 | 
					
						
							|  |  |  | 	//				// XXX this will render the base dialog...
 | 
					
						
							|  |  |  | 	//				//		...likely due to that the handler's context 
 | 
					
						
							|  |  |  | 	//				//		resolves to the root and not the clone...
 | 
					
						
							|  |  |  | 	//				.disable()
 | 
					
						
							| 
									
										
										
										
											2019-07-06 17:50:59 +03:00
										 |  |  | 	view: function(action, args, options){ | 
					
						
							| 
									
										
										
										
											2019-07-06 03:33:21 +03:00
										 |  |  | 		var that = this | 
					
						
							|  |  |  | 		return object | 
					
						
							|  |  |  | 			.mixinFlat( | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					__proto__: this, | 
					
						
							|  |  |  | 					source: this, | 
					
						
							|  |  |  | 					query: [...arguments], | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				BrowserViewMixin) }, | 
					
						
							|  |  |  | 	isView: function(){ | 
					
						
							|  |  |  | 		return false }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-28 05:34:09 +03:00
										 |  |  | 	// Data access and iteration...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-25 02:30:23 +03:00
										 |  |  | 	// Walk the browser...
 | 
					
						
							| 
									
										
										
										
											2019-08-22 17:57:29 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 02:30:23 +03:00
										 |  |  | 	// 	Get list of nodes...
 | 
					
						
							|  |  |  | 	// 	.walk()
 | 
					
						
							|  |  |  | 	// 	.walk(null[, options])
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 	// 		-> list
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 02:30:23 +03:00
										 |  |  | 	//	Walk the tree passing each elem to func(..)
 | 
					
						
							|  |  |  | 	// 	.walk(func(..)[, options])
 | 
					
						
							|  |  |  | 	// 		-> list
 | 
					
						
							|  |  |  | 	// 		-> res
 | 
					
						
							| 
									
										
										
										
											2019-08-22 17:57:29 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 02:30:23 +03:00
										 |  |  | 	// 		Handle elem...
 | 
					
						
							|  |  |  | 	// 		 func(elem, index, path, next(..), stop(..))
 | 
					
						
							|  |  |  | 	// 			-> [item, ..]
 | 
					
						
							|  |  |  | 	//	 		-> item
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 02:30:23 +03:00
										 |  |  | 	// 			Ignore current .children...
 | 
					
						
							|  |  |  | 	// 			 next()
 | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | 	// 			 	-> children
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 			Force children processing synchronously...
 | 
					
						
							|  |  |  | 	// 			 next(true)
 | 
					
						
							|  |  |  | 	// 			 	-> res
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 02:30:23 +03:00
										 |  |  | 	// 			Explicitly pass children to be handled...
 | 
					
						
							|  |  |  | 	// 			 next(browser)
 | 
					
						
							|  |  |  | 	// 			 next([elem, ...])
 | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | 	// 			 	-> input
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 	// 			Explicitly pass children to be handled and process them sync...
 | 
					
						
							|  |  |  | 	// 			 next(browser, true)
 | 
					
						
							|  |  |  | 	// 			 next([elem, ...], true)
 | 
					
						
							|  |  |  | 	// 			 	-> input
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 02:30:23 +03:00
										 |  |  | 	// 			Stop walking (return undefined)...
 | 
					
						
							|  |  |  | 	// 			 stop()
 | 
					
						
							| 
									
										
										
										
											2019-08-22 17:57:29 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 02:30:23 +03:00
										 |  |  | 	// 			Stop walking and return res...
 | 
					
						
							|  |  |  | 	// 			 stop(res)
 | 
					
						
							| 
									
										
										
										
											2019-08-23 06:24:17 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 02:30:23 +03:00
										 |  |  | 	// NOTE: stop(..) breaks execution so nothing after it is called
 | 
					
						
							|  |  |  | 	// 		in the function will get reached.
 | 
					
						
							|  |  |  | 	// NOTE: if func(..) returns an array it's content is merged (.flat()) 
 | 
					
						
							|  |  |  | 	// 		into .walk(..)'s return value, this enables it to:
 | 
					
						
							|  |  |  | 	// 			- return more than one value per item by returning an 
 | 
					
						
							|  |  |  | 	// 				array of values
 | 
					
						
							|  |  |  | 	// 			- return no values for an item by returning []
 | 
					
						
							|  |  |  | 	// NOTE: to explicitly return an array from func(..) wrap it in 
 | 
					
						
							|  |  |  | 	// 		another array.
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-23 06:24:17 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// options format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 	// 		// reverse walking...
 | 
					
						
							|  |  |  | 	//		//
 | 
					
						
							|  |  |  | 	// 		// modes:
 | 
					
						
							|  |  |  | 	// 		//	true				- use .defaultReverse
 | 
					
						
							|  |  |  | 	// 		//	'mixed'				- results reversed,
 | 
					
						
							|  |  |  | 	// 		//							handlers called topologically 
 | 
					
						
							|  |  |  | 	// 		//							(i.e. container handled before children 
 | 
					
						
							|  |  |  | 	// 		//							but its return value is placed after)
 | 
					
						
							|  |  |  | 	// 		//	'full'				- results reversed
 | 
					
						
							|  |  |  | 	// 		//							(i.e. container handled after children)
 | 
					
						
							|  |  |  | 	// 		//	'tree'				- results reversed topologically
 | 
					
						
							|  |  |  | 	// 		//							(i.e. container handled after children)
 | 
					
						
							|  |  |  | 	// 		//
 | 
					
						
							|  |  |  | 	// 		// NOTE: in 'full' mode, next(..) has no effect, as when the 
 | 
					
						
							|  |  |  | 	// 		//		container handler is called the children have already 
 | 
					
						
							|  |  |  | 	// 		//		been processed...
 | 
					
						
							|  |  |  | 	// 		reverse: <bool> | 'mixed' | 'full' | 'tree',
 | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 	//		defaultReverse: 'mixed',
 | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 	// 		// if true iterate collapsed children...
 | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 	// 		iterateCollapsed: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 	// 		// if true iterate non-iterable elements... 
 | 
					
						
							|  |  |  | 	// 		iterateNonIterable: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 	//		// shorthand for:
 | 
					
						
							|  |  |  | 	//		//	iterateCollapsed: true, iterateNonIterable: true
 | 
					
						
							|  |  |  | 	// 		iterateAll: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 	// 		// if true call func(..) on inline block containers...
 | 
					
						
							|  |  |  | 	// 		includeInlinedBlocks: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		skipDisabledMode: 'node' | 'branch',
 | 
					
						
							|  |  |  | 	// 		skipDisabled: <bool> | 'node' | 'branch',
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		// skip nested/inlined elements (children)...
 | 
					
						
							|  |  |  | 	// 		skipNested: <bool>,
 | 
					
						
							|  |  |  | 	// 		skipInlined: <bool>,
 | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		// list of sections to iterate...
 | 
					
						
							|  |  |  | 	// 		section: '*' | [ <section>, ... ],
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 		// list of iterable sections...
 | 
					
						
							|  |  |  | 	// 		//
 | 
					
						
							|  |  |  | 	// 		// used when options.section is '*'
 | 
					
						
							|  |  |  | 	// 		sections: [ <section>, ... ]
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-23 06:24:17 +03:00
										 |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 	// XXX might be good to be able to return the partial result via stop(..)
 | 
					
						
							| 
									
										
										
										
											2019-08-30 22:38:05 +03:00
										 |  |  | 	walk: function(func, options){ | 
					
						
							| 
									
										
										
										
											2019-08-22 17:57:29 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2019-08-25 02:30:23 +03:00
										 |  |  | 		var [func=null, options={}, path=[], context={}] = [...arguments] | 
					
						
							| 
									
										
										
										
											2019-08-22 17:57:29 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-23 17:00:55 +03:00
										 |  |  | 		// context...
 | 
					
						
							|  |  |  | 		context.root = context.root || this | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 		context.index = context.index || 0 | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | 		// stop...
 | 
					
						
							|  |  |  | 		var res, StopException | 
					
						
							|  |  |  | 		var stop = context.stop =  | 
					
						
							|  |  |  | 			context.stop  | 
					
						
							|  |  |  | 				|| function(r){  | 
					
						
							|  |  |  | 						res = r | 
					
						
							|  |  |  | 						throw StopException } | 
					
						
							|  |  |  | 					.run(function(){ | 
					
						
							| 
									
										
										
										
											2019-08-30 22:53:41 +03:00
										 |  |  | 						StopException = new Error('StopException.') }) | 
					
						
							| 
									
										
										
										
											2019-08-23 17:00:55 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-23 06:24:17 +03:00
										 |  |  | 		// options...
 | 
					
						
							| 
									
										
										
										
											2019-08-23 17:00:55 +03:00
										 |  |  | 		options = Object.assign( | 
					
						
							|  |  |  | 			Object.create(this.options || {}), | 
					
						
							|  |  |  | 			options) | 
					
						
							|  |  |  | 		// options.reverse...
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 		var reverse = options.reverse === true ?  | 
					
						
							|  |  |  | 			(options.defaultReverse || 'mixed')  | 
					
						
							|  |  |  | 			: options.reverse | 
					
						
							| 
									
										
										
										
											2019-08-23 06:24:17 +03:00
										 |  |  | 		var handleReverse = function(lst){ | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 			return reverse ? | 
					
						
							| 
									
										
										
										
											2019-08-23 06:24:17 +03:00
										 |  |  | 				lst.slice().reverse() | 
					
						
							|  |  |  | 				: lst } | 
					
						
							| 
									
										
										
										
											2019-08-23 17:00:55 +03:00
										 |  |  | 		// options.section...
 | 
					
						
							|  |  |  | 		var sections = options.section == '*' ? | 
					
						
							|  |  |  | 			(options.sections  | 
					
						
							|  |  |  | 				|| ['header', 'items', 'footer']) | 
					
						
							| 
									
										
										
										
											2019-08-23 17:20:18 +03:00
										 |  |  | 			: [options.section || 'items'].flat()  | 
					
						
							|  |  |  | 		// NOTE: sections other than 'items' are included only for the root context...
 | 
					
						
							|  |  |  | 		sections = (context.root !== this | 
					
						
							|  |  |  | 				&& sections.includes('items')) ? | 
					
						
							|  |  |  | 			['items'] | 
					
						
							|  |  |  | 			: sections | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 		// iteration filtering...
 | 
					
						
							|  |  |  | 		var iterateNonIterable = !!(options.iterateAll || options.iterateNonIterable) | 
					
						
							|  |  |  | 		var iterateCollapsed = !!(options.iterateAll || options.iterateCollapsed) | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 		var includeInlinedBlocks = !!options.includeInlinedBlocks | 
					
						
							|  |  |  | 		var skipDisabled = options.skipDisabled === true ? | 
					
						
							|  |  |  | 			options.skipDisabledMode || 'node' | 
					
						
							|  |  |  | 			: options.skipDisabled | 
					
						
							| 
									
										
										
										
											2019-08-23 06:24:17 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 		// item handler generator...
 | 
					
						
							|  |  |  | 		var makeMap = function(path){ | 
					
						
							|  |  |  | 			return function(elem){ | 
					
						
							|  |  |  | 				var p = path | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 				// item...
 | 
					
						
							| 
									
										
										
										
											2019-08-26 19:24:54 +03:00
										 |  |  | 				var inlined = elem instanceof Array  | 
					
						
							|  |  |  | 					|| elem instanceof BaseBrowser | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 				var skipItem =  | 
					
						
							|  |  |  | 					(skipDisabled && elem.disabled) | 
					
						
							|  |  |  | 					|| (!iterateNonIterable && elem.noniterable)  | 
					
						
							| 
									
										
										
										
											2019-08-26 19:24:54 +03:00
										 |  |  | 					|| (!includeInlinedBlocks && inlined) | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 				var p = !skipItem ? | 
					
						
							| 
									
										
										
										
											2019-08-24 18:39:37 +03:00
										 |  |  | 					path.concat(elem.id) | 
					
						
							|  |  |  | 					: p | 
					
						
							|  |  |  | 				var item | 
					
						
							| 
									
										
										
										
											2019-08-26 19:24:54 +03:00
										 |  |  | 				// NOTE: this will calc the value once and return it cached next...
 | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | 				var processItem = function(){ | 
					
						
							| 
									
										
										
										
											2019-08-24 18:39:37 +03:00
										 |  |  | 					return (item =  | 
					
						
							|  |  |  | 						item !== undefined ? | 
					
						
							|  |  |  | 							item | 
					
						
							| 
									
										
										
										
											2019-08-30 22:53:41 +03:00
										 |  |  | 						: !skipItem ? | 
					
						
							|  |  |  | 							[ func ?  | 
					
						
							|  |  |  | 								func.call(that, elem,  | 
					
						
							|  |  |  | 									inlined ?  | 
					
						
							|  |  |  | 										// NOTE: we do not increment index for 
 | 
					
						
							|  |  |  | 										// 		inlined block containers as they 
 | 
					
						
							|  |  |  | 										// 		do not occupy and space...
 | 
					
						
							|  |  |  | 										context.index  | 
					
						
							|  |  |  | 										: context.index++,  | 
					
						
							|  |  |  | 									p, next, stop)  | 
					
						
							|  |  |  | 								: elem ].flat() | 
					
						
							| 
									
										
										
										
											2019-08-24 18:39:37 +03:00
										 |  |  | 						: []) } | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 				// children...
 | 
					
						
							|  |  |  | 				var children = ( | 
					
						
							|  |  |  | 						// skip...
 | 
					
						
							|  |  |  | 						((!iterateCollapsed && elem.collapsed)  | 
					
						
							|  |  |  | 								|| (skipDisabled == 'branch')) ? | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 							false | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 						// inlined...
 | 
					
						
							|  |  |  | 						: !options.skipInlined | 
					
						
							|  |  |  | 								&& (elem instanceof BaseBrowser || elem instanceof Array) ? | 
					
						
							|  |  |  | 							elem | 
					
						
							|  |  |  | 						// nested...
 | 
					
						
							|  |  |  | 						: (!options.skipNested && elem.children) )  | 
					
						
							|  |  |  | 					|| [] | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 				var next = function(elems, now){ | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | 					return (children =  | 
					
						
							|  |  |  | 						// skip...
 | 
					
						
							|  |  |  | 						elems == null ? | 
					
						
							|  |  |  | 							[] | 
					
						
							|  |  |  | 						// force processing now...
 | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 						: now === true || elems === true ? | 
					
						
							|  |  |  | 							processChildren(now && elems) | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | 						// set elems as children...
 | 
					
						
							|  |  |  | 						: elems) } | 
					
						
							|  |  |  | 				var processed | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 				var processChildren = function(elems){ | 
					
						
							|  |  |  | 					elems = elems instanceof Array ?  | 
					
						
							|  |  |  | 						elems  | 
					
						
							|  |  |  | 						: children | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | 					return (processed =  | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 						// nodes processed via next(true), no need to re-process...
 | 
					
						
							|  |  |  | 						elems === processed ? | 
					
						
							|  |  |  | 							[] | 
					
						
							|  |  |  | 						// cached value...
 | 
					
						
							|  |  |  | 						: processed !== undefined ? | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | 							processed | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 						: elems instanceof Array ? | 
					
						
							|  |  |  | 							handleReverse(elems) | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 								.map(makeMap(p)) | 
					
						
							|  |  |  | 								.flat() | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 						: elems instanceof BaseBrowser ? | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | 							// NOTE: this will never return non-array as 
 | 
					
						
							|  |  |  | 							// 		when stop(..) is called it will break
 | 
					
						
							|  |  |  | 							// 		execution and get handled in the catch 
 | 
					
						
							|  |  |  | 							// 		clause below...
 | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 							elems | 
					
						
							| 
									
										
										
										
											2019-08-30 22:38:05 +03:00
										 |  |  | 								.walk(func, options, p, context) | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | 						: []) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// pre-call the item if reverse is not 'full'...
 | 
					
						
							|  |  |  | 				reverse == 'full' | 
					
						
							|  |  |  | 					|| processItem() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// build the result...
 | 
					
						
							|  |  |  | 				return [ | 
					
						
							|  |  |  | 					// item (normal order)...
 | 
					
						
							|  |  |  | 					...!(reverse && reverse != 'tree') ?  | 
					
						
							|  |  |  | 						processItem()  | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 						: [], | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | 					// children...
 | 
					
						
							|  |  |  | 					...processChildren(), | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 					// item (in reverse)...
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 					...(reverse && reverse != 'tree') ?  | 
					
						
							| 
									
										
										
										
											2019-08-25 05:12:19 +03:00
										 |  |  | 						processItem()  | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 						: [], ] } } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-22 17:57:29 +03:00
										 |  |  | 		try { | 
					
						
							| 
									
										
										
										
											2019-08-23 17:00:55 +03:00
										 |  |  | 			return handleReverse( | 
					
						
							|  |  |  | 					sections | 
					
						
							|  |  |  | 						.map(function(section){ | 
					
						
							|  |  |  | 							return that[section] || [] }) | 
					
						
							|  |  |  | 						.flat()) | 
					
						
							| 
									
										
										
										
											2019-08-24 17:32:03 +03:00
										 |  |  | 				.map(makeMap(path)) | 
					
						
							| 
									
										
										
										
											2019-08-22 17:57:29 +03:00
										 |  |  | 				.flat()  | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-25 00:24:09 +03:00
										 |  |  | 		// handle stop(..) and propagate errors...
 | 
					
						
							| 
									
										
										
										
											2019-08-22 17:57:29 +03:00
										 |  |  | 		} catch(e){ | 
					
						
							| 
									
										
										
										
											2019-08-23 06:28:17 +03:00
										 |  |  | 			if(e === StopException){ | 
					
						
							| 
									
										
										
										
											2019-08-22 17:57:29 +03:00
										 |  |  | 				return res } | 
					
						
							|  |  |  | 			throw e } }, | 
					
						
							| 
									
										
										
										
											2019-08-22 15:45:06 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-24 06:12:04 +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-08-30 22:38:05 +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(..)
 | 
					
						
							| 
									
										
										
										
											2019-08-27 19:39:07 +03:00
										 |  |  | 	// 		defaultReverse: 'full' (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-08-31 01:14:07 +03:00
										 |  |  | 	// 	- the handler will also get item path in addition to its index
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:10:26 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-08-27 19:39:07 +03:00
										 |  |  | 	// NOTE: we do not inherit options from this.options here as it 
 | 
					
						
							|  |  |  | 	// 		will be done in .walk(..)
 | 
					
						
							|  |  |  | 	map: function(func, options){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		var args = [...arguments] | 
					
						
							|  |  |  | 		func = (args[0] instanceof Function  | 
					
						
							|  |  |  | 				|| args[0] == null) ?  | 
					
						
							|  |  |  | 			args.shift()  | 
					
						
							|  |  |  | 			: undefined | 
					
						
							|  |  |  | 		options = args.shift() || {} | 
					
						
							|  |  |  | 		options = !options.defaultReverse ? | 
					
						
							|  |  |  | 			Object.assign({}, | 
					
						
							|  |  |  | 				options,  | 
					
						
							|  |  |  | 				{ defaultReverse: 'full' }) | 
					
						
							|  |  |  | 			: options | 
					
						
							| 
									
										
										
										
											2019-08-30 22:38:05 +03:00
										 |  |  | 		return this.walk( | 
					
						
							| 
									
										
										
										
											2019-08-27 19:39:07 +03:00
										 |  |  | 				function(e, i, p){ | 
					
						
							|  |  |  | 					return [func ? | 
					
						
							|  |  |  | 						func.call(that, e, i, p) | 
					
						
							|  |  |  | 						: e] },  | 
					
						
							|  |  |  | 				options)  | 
					
						
							|  |  |  | 			.run(makeFlatRunViewWrapper(this, options)) }, | 
					
						
							| 
									
										
										
										
											2019-08-30 22:38:05 +03:00
										 |  |  | 	// XXX do we need a non-flat version of this???
 | 
					
						
							| 
									
										
										
										
											2019-08-27 19:39:07 +03:00
										 |  |  | 	filter: function(func, options){  | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		options = !(options || {}).defaultReverse ? | 
					
						
							|  |  |  | 			Object.assign({}, | 
					
						
							|  |  |  | 				options || {},  | 
					
						
							|  |  |  | 				{ defaultReverse: 'full' }) | 
					
						
							|  |  |  | 			: options | 
					
						
							| 
									
										
										
										
											2019-08-30 22:38:05 +03:00
										 |  |  | 		return this.walk( | 
					
						
							| 
									
										
										
										
											2019-08-27 19:39:07 +03:00
										 |  |  | 			function(e, i, p){ | 
					
						
							|  |  |  | 				return func.call(that, e, i, p) ? [e] : [] },  | 
					
						
							|  |  |  | 			options) | 
					
						
							|  |  |  | 			.run(makeFlatRunViewWrapper(this, options)) }, | 
					
						
							|  |  |  | 	reduce: function(func, start, options){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		options = !(options || {}).defaultReverse ? | 
					
						
							|  |  |  | 			Object.assign({}, | 
					
						
							|  |  |  | 				options || {},  | 
					
						
							|  |  |  | 				{ defaultReverse: 'full' }) | 
					
						
							|  |  |  | 			: options | 
					
						
							| 
									
										
										
										
											2019-08-30 22:38:05 +03:00
										 |  |  | 		this.walk( | 
					
						
							| 
									
										
										
										
											2019-08-27 19:39:07 +03:00
										 |  |  | 			function(e, i, p){ | 
					
						
							|  |  |  | 				start = func.call(that, start, e, i, p) },  | 
					
						
							|  |  |  | 			options)  | 
					
						
							|  |  |  | 		return start }, | 
					
						
							|  |  |  | 	forEach: function(func, options){  | 
					
						
							|  |  |  | 		this.map2(...arguments) | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	toArray: function(options){ | 
					
						
							|  |  |  | 		return this.map(null, | 
					
						
							|  |  |  | 			Object.assign({}, | 
					
						
							|  |  |  | 				options || {},  | 
					
						
							|  |  |  | 				{rawResults: true})) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-08-27 19:39:07 +03:00
										 |  |  | 	search: function(pattern, func, options){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// parse args...
 | 
					
						
							|  |  |  | 		var args = [...arguments] | 
					
						
							|  |  |  | 		pattern = args.length == 0 ?  | 
					
						
							|  |  |  | 			true  | 
					
						
							|  |  |  | 			: args.shift()  | 
					
						
							|  |  |  | 		func = (args[0] instanceof Function  | 
					
						
							|  |  |  | 				|| args[0] == null) ?  | 
					
						
							|  |  |  | 			args.shift()  | 
					
						
							|  |  |  | 			: undefined | 
					
						
							|  |  |  | 		// NOTE: we do not inherit options from this.options here is it 
 | 
					
						
							|  |  |  | 		// 		will be done in .walk(..)
 | 
					
						
							|  |  |  | 		options = args.shift() || {} | 
					
						
							|  |  |  | 		var context = args.shift() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// 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...
 | 
					
						
							|  |  |  | 		// XXX needs refactoring -- feels overcomplicated...
 | 
					
						
							|  |  |  | 		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),  | 
					
						
							|  |  |  | 									// stop(..)
 | 
					
						
							|  |  |  | 									function stop(v){ | 
					
						
							|  |  |  | 										res = v | 
					
						
							|  |  |  | 										throw Stop }) | 
					
						
							|  |  |  | 								: pattern ] | 
					
						
							|  |  |  | 							// search...
 | 
					
						
							|  |  |  | 							: that.search2(pattern, ...args.slice(1)) }) | 
					
						
							|  |  |  | 					.flat() | 
					
						
							|  |  |  | 					.unique()  | 
					
						
							|  |  |  | 			} catch(e){ | 
					
						
							|  |  |  | 				if(e === Stop){ | 
					
						
							|  |  |  | 					return res } | 
					
						
							|  |  |  | 				throw e } } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// pattern -- normalize and do pattern keywords...
 | 
					
						
							|  |  |  | 		pattern = options.ignoreKeywords ? | 
					
						
							|  |  |  | 				pattern | 
					
						
							|  |  |  | 			: typeof(pattern) == typeof('str') ? | 
					
						
							|  |  |  | 				((pattern === 'all' || pattern == '*') ? | 
					
						
							|  |  |  | 					true | 
					
						
							|  |  |  | 				: pattern == 'first' ? | 
					
						
							|  |  |  | 					0 | 
					
						
							|  |  |  | 				: pattern == 'last' ? | 
					
						
							|  |  |  | 					-1 | 
					
						
							|  |  |  | 				: pattern == 'selected' ? | 
					
						
							|  |  |  | 					function(e){ return !!e.selected } | 
					
						
							|  |  |  | 				: pattern == 'focused' ? | 
					
						
							|  |  |  | 					function(e){ return !!e.focused } | 
					
						
							|  |  |  | 				: pattern) | 
					
						
							|  |  |  | 			: pattern | 
					
						
							|  |  |  | 		// normalize negative index...
 | 
					
						
							|  |  |  | 		if(typeof(pattern) == typeof(123) && pattern < 0){ | 
					
						
							|  |  |  | 			pattern = -pattern - 1 | 
					
						
							|  |  |  | 			options = Object.assign({}, | 
					
						
							|  |  |  | 				options, | 
					
						
							|  |  |  | 				{reverse: 'full'}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// normalize/build the test predicate...
 | 
					
						
							|  |  |  | 		var test = ( | 
					
						
							|  |  |  | 			// all...
 | 
					
						
							|  |  |  | 			pattern === true ? | 
					
						
							|  |  |  | 				pattern | 
					
						
							|  |  |  | 			// predicate...
 | 
					
						
							|  |  |  | 			: pattern instanceof Function ? | 
					
						
							|  |  |  | 				pattern | 
					
						
							|  |  |  | 			// other -> get a compatible test function...
 | 
					
						
							|  |  |  | 			: Object.entries(this.__search_test_generators__) | 
					
						
							|  |  |  | 				.filter(function([key, _]){ | 
					
						
							|  |  |  | 					return !(options.noQueryCheck  | 
					
						
							|  |  |  | 						&& key == 'query') }) | 
					
						
							|  |  |  | 				.reduce(function(res, [_, get]){ | 
					
						
							|  |  |  | 					return res  | 
					
						
							|  |  |  | 						|| get.call(that.__search_test_generators__, pattern) }, false) ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 }, | 
					
						
							| 
									
										
										
										
											2019-08-30 22:38:05 +03:00
										 |  |  | 			options) }, | 
					
						
							| 
									
										
										
										
											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-07-06 17:50:59 +03:00
										 |  |  | 		options = Object.assign( | 
					
						
							|  |  |  | 			{}, | 
					
						
							|  |  |  | 			args.pop() || {}, | 
					
						
							|  |  |  | 			{rawResults: true}) | 
					
						
							| 
									
										
										
										
											2019-05-02 18:18:13 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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)){ | 
					
						
							| 
									
										
										
										
											2019-07-20 15:31:55 +03:00
										 |  |  | 			options.iterateCollapsed = true  | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-06-13 02:54:41 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-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-07-22 18:12:47 +03:00
										 |  |  | 	// 	.renderContext(context)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											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-07-22 18:12:47 +03:00
										 |  |  | 	renderContext: function(context){ | 
					
						
							|  |  |  | 		return context || {} }, | 
					
						
							| 
									
										
										
										
											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-08-31 01:14:07 +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-07-03 03:56:14 +03:00
										 |  |  | 	// 		...current filter implementation is crude, need a way to combine/chain
 | 
					
						
							|  |  |  | 	// 		this with selectors...
 | 
					
						
							|  |  |  | 	// 		...a hacky way to do search is:
 | 
					
						
							|  |  |  | 	// 			s = dialog.search(..)
 | 
					
						
							| 
									
										
										
										
											2019-08-13 18:57:36 +03:00
										 |  |  | 	// 			dialog
 | 
					
						
							|  |  |  | 	// 				.render({
 | 
					
						
							|  |  |  | 	// 					filter: e => s.includes(e) })
 | 
					
						
							| 
									
										
										
										
											2019-07-03 03:56:14 +03:00
										 |  |  | 	// 		the downside here is this is only render, control still uses
 | 
					
						
							|  |  |  | 	// 		the full data...
 | 
					
						
							| 
									
										
										
										
											2019-08-12 14:15:54 +03:00
										 |  |  | 	// XXX make partial render lazy -- i.e. add/remove elements and 
 | 
					
						
							| 
									
										
										
										
											2019-06-12 21:45:51 +03:00
										 |  |  | 	// 		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-08-07 01:01:57 +03:00
										 |  |  | 	// XXX might be a good idea to use this.root === this instead of context.root === this
 | 
					
						
							| 
									
										
										
										
											2019-05-11 03:02:42 +03:00
										 |  |  | 	render: function(options, renderer, context){ | 
					
						
							| 
									
										
										
										
											2019-04-22 02:16:30 +03:00
										 |  |  | 		renderer = renderer || this | 
					
						
							| 
									
										
										
										
											2019-08-28 05:34:09 +03:00
										 |  |  | 		context = renderer.renderContext(context) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		options = context.options = context.options  | 
					
						
							|  |  |  | 			|| Object.assign( | 
					
						
							|  |  |  | 				Object.create(this.options || {}), | 
					
						
							| 
									
										
										
										
											2019-08-30 18:32:23 +03:00
										 |  |  | 				{  | 
					
						
							|  |  |  | 					iterateNonIterable: true, | 
					
						
							|  |  |  | 					includeInlinedBlocks: true, | 
					
						
							|  |  |  | 				},  | 
					
						
							| 
									
										
										
										
											2019-08-28 05:34:09 +03:00
										 |  |  | 				options || {}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var section = options.section || '*' | 
					
						
							|  |  |  | 		section = section == '*' ? | 
					
						
							|  |  |  | 			options.sections | 
					
						
							|  |  |  | 			: section | 
					
						
							|  |  |  | 		section = section instanceof Array && section.length == 1 ? | 
					
						
							|  |  |  | 			section[0] | 
					
						
							|  |  |  | 			: section | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var seen = options.renderUnique ? | 
					
						
							|  |  |  | 			(context.seen = context.seen || new Set()) | 
					
						
							|  |  |  | 			: false | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// build range bounds...
 | 
					
						
							|  |  |  | 		// use .get(..) on full (non-partial) range...
 | 
					
						
							|  |  |  | 		var get_options = Object.assign( | 
					
						
							|  |  |  | 			Object.create(options), | 
					
						
							|  |  |  | 			// 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}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// index getter...
 | 
					
						
							|  |  |  | 		var normIndex = function(i){ | 
					
						
							|  |  |  | 			return (i === undefined || typeof(i) == typeof(123)) ? | 
					
						
							|  |  |  | 				i | 
					
						
							|  |  |  | 				: this.get(i,  | 
					
						
							|  |  |  | 					function(_, i){ return i },  | 
					
						
							|  |  |  | 					get_options) }.bind(this) | 
					
						
							|  |  |  | 		// 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...
 | 
					
						
							|  |  |  | 		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 | 
					
						
							|  |  |  | 		// NOTE: count < 0 is the same as no count / all...
 | 
					
						
							|  |  |  | 		count = count < 0 ?  | 
					
						
							|  |  |  | 			null  | 
					
						
							|  |  |  | 			: count | 
					
						
							|  |  |  | 		// 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})`) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// XXX use this to check if an item is on the path to <from> and
 | 
					
						
							|  |  |  | 		// 		pass it to the skipped topology constructor...
 | 
					
						
							|  |  |  | 		var from_path = context.from_path = | 
					
						
							|  |  |  | 			context.from_path	 | 
					
						
							|  |  |  | 				|| (from != null  | 
					
						
							|  |  |  | 					&& this.get(from,  | 
					
						
							|  |  |  | 						function(e, i, p){ return p },  | 
					
						
							|  |  |  | 						get_options)) | 
					
						
							|  |  |  | 		from_path = from_path instanceof Array | 
					
						
							|  |  |  | 			&& from_path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// root call -> build sections (calling .render(..) per section)...
 | 
					
						
							|  |  |  | 		if(context.root == null && section instanceof Array){ | 
					
						
							|  |  |  | 			// NOTE: we are not passing context down to make each section
 | 
					
						
							|  |  |  | 			// 		independent of the others... (XXX ???)
 | 
					
						
							| 
									
										
										
										
											2019-08-31 16:28:06 +03:00
										 |  |  | 			var s = {} | 
					
						
							| 
									
										
										
										
											2019-08-28 05:34:09 +03:00
										 |  |  | 			section | 
					
						
							|  |  |  | 				.forEach(function(name){ | 
					
						
							|  |  |  | 					s[name] = this.render( | 
					
						
							|  |  |  | 						Object.assign( | 
					
						
							|  |  |  | 							{}, | 
					
						
							|  |  |  | 							options, | 
					
						
							|  |  |  | 							{ | 
					
						
							|  |  |  | 								section: name, | 
					
						
							|  |  |  | 								nonFinalized: true, | 
					
						
							|  |  |  | 							}),  | 
					
						
							|  |  |  | 						renderer) }.bind(this)) | 
					
						
							|  |  |  | 			// setup context for final render...
 | 
					
						
							|  |  |  | 			context.root = this | 
					
						
							|  |  |  | 			return (!options.nonFinalized && context.root === this) ? | 
					
						
							|  |  |  | 				renderer.renderFinalize(s.header, s.items, s.footer, context) | 
					
						
							|  |  |  | 				: s | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// build specific sections...
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			var filter = options.filter | 
					
						
							|  |  |  | 			// do the walk...
 | 
					
						
							| 
									
										
										
										
											2019-08-30 22:38:05 +03:00
										 |  |  | 			var items = this.walk( | 
					
						
							| 
									
										
										
										
											2019-08-28 05:34:09 +03:00
										 |  |  | 				function(elem, i, path, nested){ | 
					
						
							|  |  |  | 					return ( | 
					
						
							|  |  |  | 						// special case: nested <from> elem -> render 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(true), i, context) ] | 
					
						
							|  |  |  | 						// seen...
 | 
					
						
							|  |  |  | 						: seen instanceof Set  | 
					
						
							|  |  |  | 								&& (seen.has(elem)  | 
					
						
							|  |  |  | 									// add to seen and move to next test...
 | 
					
						
							|  |  |  | 									|| !seen.add(elem)) ? | 
					
						
							|  |  |  | 							[] | 
					
						
							|  |  |  | 						// filter -> skip unmatching...
 | 
					
						
							|  |  |  | 						: (filter && !filter.call(this, elem, i, path, section)) ? | 
					
						
							|  |  |  | 							[] | 
					
						
							|  |  |  | 						// out of range -> skip...
 | 
					
						
							| 
									
										
										
										
											2019-08-30 18:32:23 +03:00
										 |  |  | 						// XXX should we stop() here???
 | 
					
						
							| 
									
										
										
										
											2019-08-28 05:34:09 +03:00
										 |  |  | 						: ((from != null && i < from)  | 
					
						
							|  |  |  | 								|| (to != null && i >= to)) ? | 
					
						
							|  |  |  | 							[] | 
					
						
							| 
									
										
										
										
											2019-08-30 18:32:23 +03:00
										 |  |  | 						// inline (list)...
 | 
					
						
							|  |  |  | 						: elem instanceof Array ? | 
					
						
							| 
									
										
										
										
											2019-08-28 05:34:09 +03:00
										 |  |  | 							[ renderer.renderGroup(nested(true), context) ] | 
					
						
							| 
									
										
										
										
											2019-08-30 18:32:23 +03:00
										 |  |  | 						// inline (browser)...
 | 
					
						
							|  |  |  | 						: elem instanceof BaseBrowser ? | 
					
						
							|  |  |  | 							(nested(false),  | 
					
						
							|  |  |  | 								[ renderer.renderGroup(elem.render(options, renderer, context), context) ]) | 
					
						
							| 
									
										
										
										
											2019-08-28 05:34:09 +03:00
										 |  |  | 						// nested...
 | 
					
						
							|  |  |  | 						: elem.children ? | 
					
						
							|  |  |  | 							[ renderer.renderNested( | 
					
						
							|  |  |  | 								renderer.renderNestedHeader(elem, i, context), | 
					
						
							| 
									
										
										
										
											2019-08-30 22:38:05 +03:00
										 |  |  | 								// NOTE: we handle .collapsed here as the nested
 | 
					
						
							|  |  |  | 								// 		browser is one level down and knows nothing 
 | 
					
						
							|  |  |  | 								// 		of it...
 | 
					
						
							|  |  |  | 								((options.iterateCollapsed || !elem.collapsed)  | 
					
						
							|  |  |  | 										&& elem.children instanceof BaseBrowser) ? | 
					
						
							| 
									
										
										
										
											2019-08-30 18:32:23 +03:00
										 |  |  | 									(nested(false), | 
					
						
							|  |  |  | 										elem.children.render(options, renderer, context)) | 
					
						
							|  |  |  | 									: nested(true), | 
					
						
							| 
									
										
										
										
											2019-08-28 05:34:09 +03:00
										 |  |  | 								elem,  | 
					
						
							|  |  |  | 								context) ] | 
					
						
							|  |  |  | 						// normal elem...
 | 
					
						
							|  |  |  | 						: [ renderer.renderItem(elem, i, context) ] ) }, | 
					
						
							|  |  |  | 				options)  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// 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-08-30 22:38:05 +03:00
										 |  |  | 	// XXX .walk(..): shoulc calling. next(..) return the list to the 
 | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 	// 		user and let them handle it???
 | 
					
						
							|  |  |  | 	// 		...currently the user will get the list and each item will 
 | 
					
						
							| 
									
										
										
										
											2019-08-30 22:38:05 +03:00
										 |  |  | 	// 		be added to the stream to the list by .walk(..) this 
 | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 	// 		preventing the user from actually modifying the output...
 | 
					
						
							| 
									
										
										
										
											2019-08-30 18:32:23 +03:00
										 |  |  | 	render2: function(options, renderer, context){ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// XXX args...
 | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 		options = { | 
					
						
							|  |  |  | 			includeInlinedBlocks: true, | 
					
						
							|  |  |  | 			iterateNonIterable: true, | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-08-30 18:32:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// XXX rendering...
 | 
					
						
							|  |  |  | 		var inline = function(lst){ | 
					
						
							|  |  |  | 			return lst } | 
					
						
							|  |  |  | 		var nest = function(header, lst){ | 
					
						
							|  |  |  | 			return [ | 
					
						
							|  |  |  | 				header,  | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 				...(lst || []).map(function(e){  | 
					
						
							| 
									
										
										
										
											2019-08-30 18:32:23 +03:00
										 |  |  | 					return header +'/'+ e })]} | 
					
						
							|  |  |  | 		var	elem = function(e){ | 
					
						
							|  |  |  | 			return e.id || e } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-30 22:38:05 +03:00
										 |  |  | 		return this.walk(function(e, i, p, children){ | 
					
						
							| 
									
										
										
										
											2019-08-30 18:32:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// do not go down child browsers -- use their mechanics for rendering...
 | 
					
						
							|  |  |  | 			;(e instanceof BaseBrowser || e.children instanceof BaseBrowser) | 
					
						
							|  |  |  | 				&& children(false) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return ( | 
					
						
							|  |  |  | 				// inlined...
 | 
					
						
							|  |  |  | 				e instanceof BaseBrowser ? | 
					
						
							|  |  |  | 					inline(e.render2(options, renderer, context)) | 
					
						
							|  |  |  | 				: e instanceof Array ? | 
					
						
							|  |  |  | 					inline(children(true)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// nested...
 | 
					
						
							|  |  |  | 				: e.children instanceof BaseBrowser ? | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 					nest(elem(e),  | 
					
						
							| 
									
										
										
										
											2019-08-30 22:38:05 +03:00
										 |  |  | 						// NOTE: we handle .collapsed here as the nested
 | 
					
						
							|  |  |  | 						// 		browser is one level down and knows nothing 
 | 
					
						
							|  |  |  | 						// 		of it...
 | 
					
						
							|  |  |  | 						(options.iterateCollapsed || !e.collapsed) | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 							&& e.children.render2(options, renderer, context)) | 
					
						
							| 
									
										
										
										
											2019-08-30 18:32:23 +03:00
										 |  |  | 				: e.children instanceof Array ? | 
					
						
							|  |  |  | 					nest(elem(e), children(true)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// normal item...
 | 
					
						
							|  |  |  | 				: elem(e) ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-30 22:23:52 +03:00
										 |  |  | 		}, options) | 
					
						
							| 
									
										
										
										
											2019-08-30 18:32:23 +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-07-19 14:53:26 +03:00
										 |  |  | 				var context = {} | 
					
						
							| 
									
										
										
										
											2019-06-17 05:20:00 +03:00
										 |  |  | 				this | 
					
						
							| 
									
										
										
										
											2019-07-19 14:53:26 +03:00
										 |  |  | 					// XXX this needs access to render context....
 | 
					
						
							|  |  |  | 					.preRender(opts, (opts || {}).renderer, context) | 
					
						
							|  |  |  | 					.render(opts, (opts || {}).renderer, context)  | 
					
						
							| 
									
										
										
										
											2019-07-01 03:22:40 +03:00
										 |  |  | 				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-07-18 15:22:12 +03:00
										 |  |  | 	// Instance constructor...
 | 
					
						
							| 
									
										
										
										
											2019-06-29 21:34:42 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	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 =  | 
					
						
							| 
									
										
										
										
											2019-07-17 00:07:24 +03:00
										 |  |  | object.Constructor('BaseBrowser',  | 
					
						
							| 
									
										
										
										
											2019-01-24 05:56:37 +03:00
										 |  |  | 		BaseBrowserClassPrototype,  | 
					
						
							|  |  |  | 		BaseBrowserPrototype) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-25 20:19:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-06 18:48:57 +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-07-01 20:37:24 +03:00
										 |  |  | 		'#1': 'open: 0', | 
					
						
							|  |  |  | 		'#2': 'open: 1', | 
					
						
							|  |  |  | 		'#3': 'open: 2', | 
					
						
							|  |  |  | 		'#4': 'open: 3', | 
					
						
							|  |  |  | 		'#5': 'open: 4', | 
					
						
							|  |  |  | 		'#6': 'open: 5', | 
					
						
							|  |  |  | 		'#7': 'open: 6', | 
					
						
							|  |  |  | 		'#8': 'open: 7', | 
					
						
							|  |  |  | 		'#9': 'open: 8', | 
					
						
							|  |  |  | 		'#0': 'open: 9', | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-08-19 18:34:06 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-21 02:59:45 +03:00
										 |  |  | 	// XXX EXPERIMENTAL...
 | 
					
						
							| 
									
										
										
										
											2019-08-19 18:34:06 +03:00
										 |  |  | 	// XXX should we use these in the main render???
 | 
					
						
							|  |  |  | 	update: function(){ | 
					
						
							|  |  |  | 		return object | 
					
						
							|  |  |  | 			.parent(HTMLItemPrototype.update, this).call(this, ...arguments) | 
					
						
							|  |  |  | 			.run(function(){ | 
					
						
							|  |  |  | 				var parent = this.parent | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// XXX needs i and context...
 | 
					
						
							| 
									
										
										
										
											2019-08-20 16:27:12 +03:00
										 |  |  | 				// XXX see issue with .elem above...
 | 
					
						
							| 
									
										
										
										
											2019-08-19 18:34:06 +03:00
										 |  |  | 				this.elem = parent.renderItem(this, 0, {}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// XXX handle children...
 | 
					
						
							|  |  |  | 			}) }, | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var HTMLItem =  | 
					
						
							|  |  |  | module.HTMLItem =  | 
					
						
							| 
									
										
										
										
											2019-07-17 00:07:24 +03:00
										 |  |  | object.Constructor('HTMLItem',  | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 	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-07-06 18:48:57 +03:00
										 |  |  | // HTML Browser...
 | 
					
						
							| 
									
										
										
										
											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, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-01 23:48:23 +03:00
										 |  |  | 		// Item options/attributes that get processed directly if given
 | 
					
						
							|  |  |  | 		// at item root and not just via the respective sections...
 | 
					
						
							| 
									
										
										
										
											2019-06-30 19:19:26 +03:00
										 |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2019-07-01 23:48:23 +03:00
										 |  |  | 		shorthandItemClasses: [ | 
					
						
							|  |  |  | 			'focused', | 
					
						
							|  |  |  | 			'selected', | 
					
						
							|  |  |  | 			'disabled', | 
					
						
							|  |  |  | 			'hidden', | 
					
						
							|  |  |  | 		], | 
					
						
							|  |  |  | 		shorthandItemAttrs: [ | 
					
						
							|  |  |  | 			'doc', | 
					
						
							|  |  |  | 			'alt', | 
					
						
							|  |  |  | 		], | 
					
						
							|  |  |  | 		shorthandItemEvents: [], | 
					
						
							| 
									
										
										
										
											2019-02-10 19:28:10 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-01 23:48:23 +03:00
										 |  |  | 		// events not to bubble up the tree...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		itemLocalEvents: [ | 
					
						
							|  |  |  | 			'click', | 
					
						
							| 
									
										
										
										
											2019-02-10 03:02:08 +03:00
										 |  |  | 		], | 
					
						
							| 
									
										
										
										
											2019-07-01 23:48:23 +03:00
										 |  |  | 		//buttonLocalEvents: [],
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-07-01 23:48:23 +03:00
										 |  |  | 		headerButtons: [], | 
					
						
							|  |  |  | 		itemButtons: [], | 
					
						
							|  |  |  | 		footerButtons: [], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Default buttons for Item.Heading(..)
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: the use of this is implemented in Items.Heading(..) see
 | 
					
						
							|  |  |  | 		// 		it for more info...
 | 
					
						
							|  |  |  | 		headingButtons: [ | 
					
						
							|  |  |  | 			'ToggleCollapse', | 
					
						
							| 
									
										
										
										
											2019-06-29 16:09:32 +03:00
										 |  |  | 		], | 
					
						
							| 
									
										
										
										
											2019-02-27 03:35:03 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-01 23:48:23 +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 =  | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 			(this.hasOwnProperty('__keyboard_object')  | 
					
						
							|  |  |  | 					&& this.__keyboard_object) | 
					
						
							| 
									
										
										
										
											2019-06-03 19:22:01 +03:00
										 |  |  | 				|| 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...
 | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 	____keyboard_handler: null, | 
					
						
							| 
									
										
										
										
											2019-06-18 20:58:40 +03:00
										 |  |  | 	get __keyboard_handler(){ | 
					
						
							|  |  |  | 		var options = this.options || {} | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 		return (this.____keyboard_handler =  | 
					
						
							|  |  |  | 			(this.hasOwnProperty('____keyboard_handler')  | 
					
						
							|  |  |  | 					&& this.____keyboard_handler) | 
					
						
							|  |  |  | 				|| keyboard.makePausableKeyboardHandler( | 
					
						
							|  |  |  | 					this.keyboard, | 
					
						
							|  |  |  | 					function(){  | 
					
						
							|  |  |  | 						options.keyboardReportUnhandled | 
					
						
							|  |  |  | 							&& console.log('UNHANDLED 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-07-18 15:22:12 +03:00
										 |  |  | 	// Props..
 | 
					
						
							| 
									
										
										
										
											2019-06-19 02:34:23 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											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???
 | 
					
						
							| 
									
										
										
										
											2019-07-05 21:49:36 +03:00
										 |  |  | 	// XXX do we use .hasOwnProperty(..) here???
 | 
					
						
							| 
									
										
										
										
											2019-03-05 18:15:49 +03:00
										 |  |  | 	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-07-18 15:22:12 +03:00
										 |  |  | 	// page-relative items...
 | 
					
						
							|  |  |  | 	get pagetop(){ | 
					
						
							| 
									
										
										
										
											2019-07-19 15:19:52 +03:00
										 |  |  | 		return this.get('pagetop') }, | 
					
						
							| 
									
										
										
										
											2019-07-18 15:22:12 +03:00
										 |  |  | 	set pagetop(item){ | 
					
						
							| 
									
										
										
										
											2019-07-19 15:19:52 +03:00
										 |  |  | 		this.scrollTo(item, 'start') }, | 
					
						
							| 
									
										
										
										
											2019-07-18 15:22:12 +03:00
										 |  |  | 	get pagebottom(){ | 
					
						
							| 
									
										
										
										
											2019-07-19 15:19:52 +03:00
										 |  |  | 		return this.get('pagebottom') }, | 
					
						
							| 
									
										
										
										
											2019-07-18 15:22:12 +03:00
										 |  |  | 	set pagebottom(item){ | 
					
						
							| 
									
										
										
										
											2019-07-19 15:19:52 +03:00
										 |  |  | 		this.scrollTo(item, 'end') }, | 
					
						
							| 
									
										
										
										
											2019-07-18 15:22:12 +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-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){ | 
					
						
							| 
									
										
										
										
											2019-07-19 15:19:52 +03:00
										 |  |  | 			if(!this.dom){ | 
					
						
							|  |  |  | 				return [] | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 			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
										 |  |  | 					{  | 
					
						
							| 
									
										
										
										
											2019-07-18 15:22:12 +03:00
										 |  |  | 						rawResults: true, | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 						reverse: pos == 'bottom' ?  | 
					
						
							| 
									
										
										
										
											2019-08-27 19:39:07 +03:00
										 |  |  | 							'full' | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 							: false, | 
					
						
							| 
									
										
										
										
											2019-06-30 00:55:20 +03:00
										 |  |  | 						skipDisabled: !(this.options || {}).focusDisabledItems,  | 
					
						
							| 
									
										
										
										
											2019-07-19 15:19:52 +03:00
										 |  |  | 					}) }.bind(this) | 
					
						
							| 
									
										
										
										
											2019-06-12 18:20:32 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-04 04:28:08 +03:00
										 |  |  | 		pattern = arguments[0] =  | 
					
						
							|  |  |  | 			// DOM element...
 | 
					
						
							|  |  |  | 			pattern instanceof HTMLElement ? | 
					
						
							| 
									
										
										
										
											2019-07-19 15:19:52 +03:00
										 |  |  | 				function(e){ return e.dom === 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
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-07-02 02:04:05 +03:00
										 |  |  | 	// The paste code is essentially a hack to work around access issues 
 | 
					
						
							|  |  |  | 	// in different browser engines.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											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-08-03 16:40:09 +03:00
										 |  |  | 	// XXX add a render mode where we replace specific element's .dom...
 | 
					
						
							|  |  |  | 	// 		...this could be done via:
 | 
					
						
							|  |  |  | 	// 			.renderUpdate(..) -- like .renderFinalize(..) but replaces nodes...
 | 
					
						
							|  |  |  | 	// 		needs more thought...
 | 
					
						
							| 
									
										
										
										
											2019-08-04 17:12:24 +03:00
										 |  |  | 	// 		...another idea would be to make the render flow "internal", i.e. each
 | 
					
						
							|  |  |  | 	// 		item renders and places itself item "packing" is done internally 
 | 
					
						
							|  |  |  | 	// 		rather than via outer render combinators...
 | 
					
						
							|  |  |  | 	// 			- create/get root context
 | 
					
						
							|  |  |  | 	// 				- container -> create/clone context + append/replace in parent context
 | 
					
						
							|  |  |  | 	// 					- item -> create/clone + append/replace self in parent context
 | 
					
						
							|  |  |  | 	// 					- ...
 | 
					
						
							|  |  |  | 	// 			- update root context
 | 
					
						
							|  |  |  | 	// 		this approach should make the individual renderers self-contained, i.e.
 | 
					
						
							|  |  |  | 	// 		rendering a single item would update it in the main tree... (needs thought)
 | 
					
						
							| 
									
										
										
										
											2019-08-03 16:40:09 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2019-07-22 18:12:47 +03:00
										 |  |  | 	// Prepare context for maintaining scroll offset...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX need a counterpart to this to finalize the context on any render...
 | 
					
						
							| 
									
										
										
										
											2019-07-23 05:10:15 +03:00
										 |  |  | 	// 		...or a way to tell .render(..) to render / re-render only 
 | 
					
						
							|  |  |  | 	// 		specific items...
 | 
					
						
							| 
									
										
										
										
											2019-07-22 18:12:47 +03:00
										 |  |  | 	renderContext: function(context){ | 
					
						
							|  |  |  | 		context = context || {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-31 01:02:15 +03:00
										 |  |  | 		// prepare for maintaining the scroll position...
 | 
					
						
							|  |  |  | 		// XXX need to do this pre any .render*(..) call...
 | 
					
						
							|  |  |  | 		// 		...something like:
 | 
					
						
							|  |  |  | 		// 			this.getRenderContext(context)
 | 
					
						
							|  |  |  | 		// 		should do the trick...
 | 
					
						
							|  |  |  | 		// 		another way to go might be a context object, but that seems to be 
 | 
					
						
							|  |  |  | 		// 		complicating things...
 | 
					
						
							|  |  |  | 		var ref = context.scroll_reference =  | 
					
						
							|  |  |  | 			context.scroll_reference  | 
					
						
							|  |  |  | 				|| this.focused  | 
					
						
							|  |  |  | 				|| this.pagetop | 
					
						
							|  |  |  | 		context.scroll_offset =  | 
					
						
							|  |  |  | 			context.scroll_offset | 
					
						
							|  |  |  | 			|| ((ref && ref.dom && ref.dom.offsetTop) ? | 
					
						
							|  |  |  | 				ref.dom.offsetTop - ref.dom.offsetParent.scrollTop | 
					
						
							|  |  |  | 				: null) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-06 19:50:07 +03:00
										 |  |  | 		//context.scroll_offset && console.log('renderContext:', context.scroll_offset)
 | 
					
						
							| 
									
										
										
										
											2019-07-22 18:12:47 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		return context  | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											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-07-26 20:29:21 +03:00
										 |  |  | 	// XXX set scroll offset... should it be here?
 | 
					
						
							|  |  |  | 	// 		...would be nice to universally wrap any render operation 
 | 
					
						
							|  |  |  | 	// 		in a context getter/setter that would track the root call and 
 | 
					
						
							|  |  |  | 	// 		trigger an postRender event...
 | 
					
						
							|  |  |  | 	// 		how can we Identify the root call??
 | 
					
						
							|  |  |  | 	//		...the traditional way would be a stack -- pop last elem 
 | 
					
						
							|  |  |  | 	//		means we are done...
 | 
					
						
							| 
									
										
										
										
											2019-07-27 18:46:48 +03:00
										 |  |  | 	//		ways to go with this:
 | 
					
						
							|  |  |  | 	//			- renderer wrapper/generator
 | 
					
						
							|  |  |  | 	//			- a 1:1 item-based render mechanic with single entry point...
 | 
					
						
							| 
									
										
										
										
											2019-07-28 03:24:14 +03:00
										 |  |  | 	//				...this may be a problem as some of the renderers 
 | 
					
						
							|  |  |  | 	//				can call others... (???)
 | 
					
						
							| 
									
										
										
										
											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-07-22 18:12:47 +03:00
										 |  |  | 		var context = this.renderContext(context) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-08-08 04:18:43 +03:00
										 |  |  | 		/* XXX this messes up the 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-07-19 14:53:26 +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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-19 14:53:26 +03:00
										 |  |  | 		// set the scroll offset...
 | 
					
						
							| 
									
										
										
										
											2019-07-22 18:12:47 +03:00
										 |  |  | 		// XXX does not work correctly for all cases yet...
 | 
					
						
							|  |  |  | 		// XXX move this to a seporate method -- need to trigger this 
 | 
					
						
							|  |  |  | 		// 		on render that can affect scroll position, e.g. partial 
 | 
					
						
							|  |  |  | 		// 		render...
 | 
					
						
							|  |  |  | 		// XXX need to trigger the setup for this from .render(..) itself...
 | 
					
						
							| 
									
										
										
										
											2019-07-31 01:02:15 +03:00
										 |  |  | 		if(context.scroll_offset){ | 
					
						
							| 
									
										
										
										
											2019-07-22 01:12:37 +03:00
										 |  |  | 			var ref = this.focused || this.pagetop | 
					
						
							|  |  |  | 			var scrolled = ref.dom.offsetParent  | 
					
						
							| 
									
										
										
										
											2019-07-31 01:02:15 +03:00
										 |  |  | 			//scrolled.scrollTop = 
 | 
					
						
							|  |  |  | 			//	ref.elem.offsetTop - scrolled.scrollTop - context.scroll_offset
 | 
					
						
							|  |  |  | 			scrolled | 
					
						
							|  |  |  | 				&& (scrolled.scrollTop =  | 
					
						
							|  |  |  | 					ref.elem.offsetTop - scrolled.scrollTop - context.scroll_offset) | 
					
						
							| 
									
										
										
										
											2019-07-22 01:12:37 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-07-19 14:53:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-07-22 18:12:47 +03:00
										 |  |  | 		var context = this.renderContext(context) | 
					
						
							| 
									
										
										
										
											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-07-22 18:12:47 +03:00
										 |  |  | 		var context = this.renderContext(context) | 
					
						
							| 
									
										
										
										
											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-07-01 23:48:23 +03:00
										 |  |  | 		;(options.itemLocalEvents || []) | 
					
						
							| 
									
										
										
										
											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-07-22 18:12:47 +03:00
										 |  |  | 		var context = this.renderContext(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-07-22 18:12:47 +03:00
										 |  |  | 		var context = this.renderContext(context) | 
					
						
							| 
									
										
										
										
											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...
 | 
					
						
							| 
									
										
										
										
											2019-07-01 23:48:23 +03:00
										 |  |  | 			.concat( | 
					
						
							|  |  |  | 				(options.shorthandItemClasses || {}) | 
					
						
							|  |  |  | 					.filter(function(cls){  | 
					
						
							|  |  |  | 						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-07-01 23:48:23 +03:00
										 |  |  | 			.concat((options.shorthandItemAttrs || []) | 
					
						
							|  |  |  | 				.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-07-01 23:48:23 +03:00
										 |  |  | 			.concat((options.shorthandItemEvents || []) | 
					
						
							|  |  |  | 				.map(function(evt){  | 
					
						
							| 
									
										
										
										
											2019-02-09 01:39:13 +03:00
										 |  |  | 					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 ? | 
					
						
							| 
									
										
										
										
											2019-07-16 19:11:57 +03:00
										 |  |  | 						[Items.buttons[button].call(that, item)].flat() | 
					
						
							| 
									
										
										
										
											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) | 
					
						
							| 
									
										
										
										
											2019-07-01 23:48:23 +03:00
										 |  |  | 								return options.disableButtonSortcuts ? | 
					
						
							|  |  |  | 									false | 
					
						
							|  |  |  | 									: !text_keys.includes(k) | 
					
						
							|  |  |  | 										&& text_keys.push(k) }) | 
					
						
							| 
									
										
										
										
											2019-07-01 19:33:40 +03:00
										 |  |  | 						: 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-07-01 23:48:23 +03:00
										 |  |  | 					;(options.buttonLocalEvents || options.itemLocalEvents || []) | 
					
						
							| 
									
										
										
										
											2019-02-11 22:06:36 +03:00
										 |  |  | 						.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-08-20 16:27:12 +03:00
										 |  |  | 		// XXX for some reason this messes up navigation...
 | 
					
						
							|  |  |  | 		// 		to reproduce:
 | 
					
						
							|  |  |  | 		// 			- select element with children
 | 
					
						
							|  |  |  | 		// 			- press right
 | 
					
						
							|  |  |  | 		// 				-> blur current elem
 | 
					
						
							|  |  |  | 		// 				-> next elem not selected...
 | 
					
						
							| 
									
										
										
										
											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-07-19 14:53:26 +03:00
										 |  |  | 	__preRender__: function(evt, options, renderer, context){ | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2019-07-19 14:53:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 		// 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???
 | 
					
						
							| 
									
										
										
										
											2019-07-01 20:37:24 +03:00
										 |  |  | 									that | 
					
						
							|  |  |  | 										.focus(e)  | 
					
						
							|  |  |  | 										.open(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 | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-07-18 15:22:12 +03:00
										 |  |  | 		}, {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-07-01 20:37:24 +03:00
										 |  |  | 	__open__: function(evt, elem){ this.focus(elem) }, | 
					
						
							| 
									
										
										
										
											2019-08-05 17:30:39 +03:00
										 |  |  | 	// XXX when expanding an element at the bottom of the screen (i.e. 
 | 
					
						
							|  |  |  | 	// 		when the expanded tree is not visible) need to nudge the 
 | 
					
						
							|  |  |  | 	// 		element up to reveal the expanded subtree...
 | 
					
						
							|  |  |  | 	// 		...would also be logical to "show" the expanded tree but 
 | 
					
						
							|  |  |  | 	// 		keeping the focused elem in view...
 | 
					
						
							| 
									
										
										
										
											2019-08-04 17:12:24 +03:00
										 |  |  | 	/* XXX there is a problem with .update() propagation up the nested  | 
					
						
							| 
									
										
										
										
											2019-08-01 18:45:29 +03:00
										 |  |  | 	// 		dialogs -- we lose context...
 | 
					
						
							|  |  |  | 	// 		...see .renderContext(..) / .renderFinalize(..) for details...
 | 
					
						
							| 
									
										
										
										
											2019-08-02 16:09:48 +03:00
										 |  |  | 	// 		There are two routs to make this uniform:
 | 
					
						
							|  |  |  | 	// 			1) make some events (update) root-only
 | 
					
						
							|  |  |  | 	// 			2) make local updates possible (connect .dom to parent's dom)
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	__expand__: function(){ this.update() }, | 
					
						
							|  |  |  | 	__collapse__: function(){ this.update() }, | 
					
						
							| 
									
										
										
										
											2019-08-01 18:45:29 +03:00
										 |  |  | 	/*/  | 
					
						
							| 
									
										
										
										
											2019-08-05 17:30:39 +03:00
										 |  |  | 	// XXX make this a "local" update -- i.e. only update the subtree... 
 | 
					
						
							| 
									
										
										
										
											2019-08-01 18:45:29 +03:00
										 |  |  | 	__expand__: function(){ this.root.update() }, | 
					
						
							|  |  |  | 	__collapse__: function(){ this.root.update() }, | 
					
						
							|  |  |  | 	//*/
 | 
					
						
							| 
									
										
										
										
											2019-06-16 21:14:23 +03:00
										 |  |  | 	__select__: updateElemClass('add', 'selected'), | 
					
						
							|  |  |  | 	__deselect__: updateElemClass('remove', 'selected'), | 
					
						
							| 
									
										
										
										
											2019-08-05 17:30:39 +03:00
										 |  |  | 	// XXX would be more logical to update only the sepcific elements...
 | 
					
						
							| 
									
										
										
										
											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 =  | 
					
						
							| 
									
										
										
										
											2019-07-17 00:07:24 +03:00
										 |  |  | object.Constructor('HTMLBrowser',  | 
					
						
							| 
									
										
										
										
											2019-06-23 19:56:07 +03:00
										 |  |  | 		HTMLBrowserClassPrototype,  | 
					
						
							|  |  |  | 		HTMLBrowserPrototype) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 =  | 
					
						
							| 
									
										
										
										
											2019-07-17 00:07:24 +03:00
										 |  |  | object.Constructor('TextBrowser',  | 
					
						
							| 
									
										
										
										
											2019-01-28 18:10:01 +03:00
										 |  |  | 		TextBrowserClassPrototype,  | 
					
						
							|  |  |  | 		TextBrowserPrototype) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-21 05:29:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-06 18:48:57 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2019-07-07 17:37:05 +03:00
										 |  |  | // shorthands...
 | 
					
						
							| 
									
										
										
										
											2019-07-06 18:48:57 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | module.Item = HTMLItem | 
					
						
							|  |  |  | module.Browser = HTMLBrowser | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-24 16:15:41 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-20 04:39:03 +03:00
										 |  |  | /********************************************************************** | 
					
						
							|  |  |  | * vim:set ts=4 sw=4 :                               */ return module }) |