| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | /********************************************************************** | 
					
						
							|  |  |  | *  | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | **********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //var DEBUG = DEBUG != null ? DEBUG : true
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | // XXX add a hook to render the content of an element...
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | // XXX NOTE: the widget itself does not need a title, that's the job for
 | 
					
						
							|  |  |  | //		a container widget (dialog, field, ...)
 | 
					
						
							|  |  |  | //		...it can be implemented trivially via an attribute and a :before
 | 
					
						
							|  |  |  | //		CSS class...
 | 
					
						
							|  |  |  | var BrowserClassPrototype = { | 
					
						
							|  |  |  | 	// construct the dom...
 | 
					
						
							|  |  |  | 	make: function(options){ | 
					
						
							|  |  |  | 		var browser = $('<div>') | 
					
						
							|  |  |  | 			.addClass('browse') | 
					
						
							|  |  |  | 			// make thie widget focusable...
 | 
					
						
							|  |  |  | 			// NOTE: tabindex 0 means automatic tab indexing and -1 means 
 | 
					
						
							|  |  |  | 			//		focusable bot not tabable...
 | 
					
						
							|  |  |  | 			//.attr('tabindex', -1)
 | 
					
						
							|  |  |  | 			.attr('tabindex', 0) | 
					
						
							|  |  |  | 			// focus the widget if something inside is clicked...
 | 
					
						
							|  |  |  | 			.click(function(){ | 
					
						
							|  |  |  | 				$(this).focus() | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if(options.path == null || options.show_path){ | 
					
						
							|  |  |  | 			browser | 
					
						
							|  |  |  | 				.append($('<div>') | 
					
						
							|  |  |  | 					   .addClass('v-block path')) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		browser | 
					
						
							|  |  |  | 			.append($('<div>') | 
					
						
							|  |  |  | 				   .addClass('v-block list')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return browser | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX Q: should we make a base list dialog and build this on that or
 | 
					
						
							|  |  |  | //		simplify this to implement a list (removing the path and disbling
 | 
					
						
							|  |  |  | //		traversal)??
 | 
					
						
							|  |  |  | // XXX need a search/filter field...
 | 
					
						
							|  |  |  | // XXX need base events:
 | 
					
						
							| 
									
										
										
										
											2015-03-22 02:12:30 +03:00
										 |  |  | //		- open
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | //		- update
 | 
					
						
							|  |  |  | //		- select (???)
 | 
					
						
							|  |  |  | // XXX add "current selection" to the path...
 | 
					
						
							|  |  |  | var BrowserPrototype = { | 
					
						
							|  |  |  | 	dom: null, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-18 03:34:15 +03:00
										 |  |  | 	// option defaults and doc...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX add enable/disable filter option...
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	options: { | 
					
						
							|  |  |  | 		//path: null,
 | 
					
						
							|  |  |  | 		//show_path: null,
 | 
					
						
							| 
									
										
										
										
											2015-05-18 03:34:15 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// handle keys that are not bound...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: to disable, set ot undefined.
 | 
					
						
							|  |  |  | 		logKeys: function(k){ window.DEBUG && console.log(k) }, | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX this should prevent event handler deligation...
 | 
					
						
							|  |  |  | 	keyboard: { | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 		// filter mappings...
 | 
					
						
							|  |  |  | 		Filter: { | 
					
						
							|  |  |  | 			pattern: '.browse .path div.cur[contenteditable]', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// keep text edeting action from affecting the seelction...
 | 
					
						
							|  |  |  | 			ignore: [ | 
					
						
							|  |  |  | 					'Backspace', | 
					
						
							|  |  |  | 					'Left', | 
					
						
							|  |  |  | 					'Right', | 
					
						
							|  |  |  | 					'Enter', | 
					
						
							|  |  |  | 					'Esc', | 
					
						
							| 
									
										
										
										
											2015-06-18 01:41:57 +03:00
										 |  |  | 					'/', | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 				], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Enter: 'action!', | 
					
						
							|  |  |  | 			Esc: 'stopFilter!', | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		General: { | 
					
						
							|  |  |  | 			pattern: '.browse', | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-18 02:34:06 +03:00
										 |  |  | 			Up: 'prev!', | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 			Backspace: 'Up', | 
					
						
							| 
									
										
										
										
											2015-06-18 02:34:06 +03:00
										 |  |  | 			Down: 'next!', | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 			Left: 'pop', | 
					
						
							|  |  |  | 			Right: 'push', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Enter: 'action', | 
					
						
							|  |  |  | 			Esc: 'close', | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			'/': 'startFilter!', | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// base api...
 | 
					
						
							|  |  |  | 	// NOTE: to avoid duplicating and syncing data, the actual path is 
 | 
					
						
							|  |  |  | 	//		stored in DOM...
 | 
					
						
							|  |  |  | 	// XXX does the path includes the currently selected element?
 | 
					
						
							|  |  |  | 	get path(){ | 
					
						
							|  |  |  | 		var skip = false | 
					
						
							| 
									
										
										
										
											2015-03-21 18:20:59 +03:00
										 |  |  | 		return this.dom.find('.path .dir:not(.cur)') | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 			.map(function(i, e){ return $(e).text() }) | 
					
						
							|  |  |  | 			.toArray() | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	set path(value){ | 
					
						
							|  |  |  | 		// XXX normalize path...
 | 
					
						
							|  |  |  | 		return this.update(value) | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// update path...
 | 
					
						
							| 
									
										
										
										
											2015-03-22 02:12:30 +03:00
										 |  |  | 	// 	- build the path
 | 
					
						
							|  |  |  | 	// 	- build the element list
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	// XXX trigger an "update" event...
 | 
					
						
							|  |  |  | 	update: function(path){ | 
					
						
							| 
									
										
										
										
											2015-03-22 02:12:30 +03:00
										 |  |  | 		path = path || this.path | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 		var browser = this.dom | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var p = browser.find('.path').empty() | 
					
						
							|  |  |  | 		var l = browser.find('.list').empty() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-21 18:20:59 +03:00
										 |  |  | 		var c = [] | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 		// fill the path field...
 | 
					
						
							|  |  |  | 		path.forEach(function(e){ | 
					
						
							| 
									
										
										
										
											2015-03-21 18:20:59 +03:00
										 |  |  | 			c.push(e) | 
					
						
							|  |  |  | 			var cur = c.slice() | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 			p.append($('<div>') | 
					
						
							|  |  |  | 				.addClass('dir') | 
					
						
							| 
									
										
										
										
											2015-03-21 18:20:59 +03:00
										 |  |  | 				.click(function(){ | 
					
						
							|  |  |  | 					that | 
					
						
							|  |  |  | 						.update(cur.slice(0, -1))  | 
					
						
							|  |  |  | 						.select('"'+cur.pop()+'"') | 
					
						
							|  |  |  | 				}) | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 				.text(e)) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-21 18:20:59 +03:00
										 |  |  | 		// add current selction indicator...
 | 
					
						
							|  |  |  | 		p.append($('<div>') | 
					
						
							|  |  |  | 			.addClass('dir cur') | 
					
						
							|  |  |  | 			.click(function(){ | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 				that.startFilter() | 
					
						
							| 
									
										
										
										
											2015-03-22 02:12:30 +03:00
										 |  |  | 				//that.update(path.concat($(this).text())) 
 | 
					
						
							| 
									
										
										
										
											2015-06-18 01:41:57 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				// XXX HACK: prevents the field from bluring when clicked...
 | 
					
						
							|  |  |  | 				// 			...need to find a better way...
 | 
					
						
							|  |  |  | 				that._hold_blur = true | 
					
						
							|  |  |  | 				setTimeout(function(){ delete that._hold_blur }, 20) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			// XXX for some reason this gets triggered when clicking ano 
 | 
					
						
							|  |  |  | 			// 		is not triggered when entering via '/'
 | 
					
						
							|  |  |  | 			.on('blur', function(){ | 
					
						
							|  |  |  | 				// XXX HACK: prevents the field from bluring when clicked...
 | 
					
						
							|  |  |  | 				// 			...need to find a better way...
 | 
					
						
							|  |  |  | 				if(!that._hold_blur){ | 
					
						
							|  |  |  | 					that.stopFilter() | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				//that.stopFilter()
 | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			.keyup(function(){ | 
					
						
							|  |  |  | 				that.filter($(this).text()) | 
					
						
							| 
									
										
										
										
											2015-03-21 18:20:59 +03:00
										 |  |  | 			})) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 		// fill the children list...
 | 
					
						
							|  |  |  | 		this.list(path) | 
					
						
							|  |  |  | 			.forEach(function(e){ | 
					
						
							|  |  |  | 				l.append($('<div>') | 
					
						
							| 
									
										
										
										
											2015-03-21 18:20:59 +03:00
										 |  |  | 					.click(function(){ | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 						if(!$(this).hasClass('disabled')){ | 
					
						
							|  |  |  | 							that.update(that.path.concat([$(this).text()]))  | 
					
						
							|  |  |  | 						} | 
					
						
							| 
									
										
										
										
											2015-03-21 18:20:59 +03:00
										 |  |  | 					}) | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 					.text(e)) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 	// internal actions...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-22 02:12:30 +03:00
										 |  |  | 	// XXX pattern modes:
 | 
					
						
							|  |  |  | 	// 		- lazy match
 | 
					
						
							|  |  |  | 	// 			abc		-> *abc*		-> ^.*abc.*$
 | 
					
						
							|  |  |  | 	// 			ab cd	-> *ab*cd*		-> ^.*ab.*cd.*$
 | 
					
						
							|  |  |  | 	// 		- glob
 | 
					
						
							|  |  |  | 	// 		- regex
 | 
					
						
							|  |  |  | 	// XXX sort:
 | 
					
						
							|  |  |  | 	// 		- as-is
 | 
					
						
							|  |  |  | 	// 		- best match
 | 
					
						
							| 
									
										
										
										
											2015-06-18 01:41:57 +03:00
										 |  |  | 	// XXX add deep-mode filtering...
 | 
					
						
							|  |  |  | 	// 		if '/' is in the pattern then we list down and combine paths...
 | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 	filter: function(pattern, non_matched, sort){ | 
					
						
							| 
									
										
										
										
											2015-03-22 02:12:30 +03:00
										 |  |  | 		var that = this | 
					
						
							|  |  |  | 		var browser = this.dom | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-30 17:42:48 +03:00
										 |  |  | 		// show all...
 | 
					
						
							|  |  |  | 		if(pattern == null || pattern.trim() == '*'){ | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 			browser.find('.filtered-out') | 
					
						
							|  |  |  | 				.removeClass('filtered-out') | 
					
						
							|  |  |  | 			// clear the highlighing...
 | 
					
						
							|  |  |  | 			browser.find('.list b') | 
					
						
							|  |  |  | 				.replaceWith(function() { return this.innerHTML }) | 
					
						
							| 
									
										
										
										
											2015-03-22 02:12:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-30 17:42:48 +03:00
										 |  |  | 		// basic filter...
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 			var l = browser.find('.list>div:not(disabled)') | 
					
						
							| 
									
										
										
										
											2015-03-22 02:12:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-30 17:42:48 +03:00
										 |  |  | 			l.each(function(i, e){ | 
					
						
							|  |  |  | 				e = $(e) | 
					
						
							|  |  |  | 				var t = e.text() | 
					
						
							|  |  |  | 				var i = t.search(pattern) | 
					
						
							|  |  |  | 				if(i < 0){ | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 					e | 
					
						
							|  |  |  | 						.addClass('filtered-out') | 
					
						
							|  |  |  | 						.removeClass('selected') | 
					
						
							| 
									
										
										
										
											2015-03-30 17:42:48 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					e.html(t.replace(pattern, pattern.bold())) | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 						.removeClass('filtered-out') | 
					
						
							| 
									
										
										
										
											2015-03-30 17:42:48 +03:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this | 
					
						
							| 
									
										
										
										
											2015-03-22 02:12:30 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 	// XXX start search/filter...
 | 
					
						
							|  |  |  | 	// 		- set content editable
 | 
					
						
							|  |  |  | 	// 		- triger filterig on modified
 | 
					
						
							|  |  |  | 	// 		- disable nav in favor of editing
 | 
					
						
							|  |  |  | 	// 		- enter/blur to exit edit mode
 | 
					
						
							|  |  |  | 	// 		- esc to cancel and reset
 | 
					
						
							|  |  |  | 	// XXX BUG: when starting with '/' key the '/' gets appended to the
 | 
					
						
							|  |  |  | 	// 		field...
 | 
					
						
							| 
									
										
										
										
											2015-06-18 00:23:23 +03:00
										 |  |  | 	// XXX make this a toggler...
 | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 	startFilter: function(){ | 
					
						
							|  |  |  | 		var range = document.createRange() | 
					
						
							|  |  |  | 		var selection = window.getSelection() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		var e = this.dom.find('.path .dir.cur') | 
					
						
							|  |  |  | 			.text('') | 
					
						
							|  |  |  | 			.attr('contenteditable', true) | 
					
						
							|  |  |  | 			.focus() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// place the cursor...
 | 
					
						
							|  |  |  | 		range.setStart(e[0], 0) | 
					
						
							|  |  |  | 		range.collapse(true) | 
					
						
							|  |  |  | 		// XXX
 | 
					
						
							|  |  |  | 		selection.removeAllRanges() | 
					
						
							|  |  |  | 		selection.addRange(range) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	stopFilter: function(){ | 
					
						
							|  |  |  | 		this.filter('*') | 
					
						
							|  |  |  | 		this.dom.find('.path .dir.cur') | 
					
						
							|  |  |  | 			.text('') | 
					
						
							|  |  |  | 			.removeAttr('contenteditable') | 
					
						
							|  |  |  | 		this | 
					
						
							|  |  |  | 			.focus() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	get filtering(){ | 
					
						
							|  |  |  | 		return this.dom.find('.path .dir.cur[contenteditable]').length > 0  | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2015-06-18 00:23:23 +03:00
										 |  |  | 	toggleFilterMode: function(){ | 
					
						
							|  |  |  | 		this.dom.toggleClass('show-filtered-out') | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Select a list element...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2015-05-17 03:52:21 +03:00
										 |  |  | 	//	Get selected element if it exists, otherwise select and return 
 | 
					
						
							|  |  |  | 	//	the first...
 | 
					
						
							|  |  |  | 	//	.select()
 | 
					
						
							|  |  |  | 	//		-> elem
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Get selected element if it exists, null otherwise...
 | 
					
						
							|  |  |  | 	//	.select('!')
 | 
					
						
							|  |  |  | 	//		-> elem
 | 
					
						
							|  |  |  | 	//		-> $()
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	//	Select first/last child
 | 
					
						
							|  |  |  | 	//	.select('first')
 | 
					
						
							|  |  |  | 	//	.select('last')
 | 
					
						
							|  |  |  | 	//		-> elem
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2015-05-17 03:52:21 +03:00
										 |  |  | 	//	Select previous/next child
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	//	.select('prev')
 | 
					
						
							|  |  |  | 	//	.select('next')
 | 
					
						
							|  |  |  | 	//		-> elem
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Deselect
 | 
					
						
							|  |  |  | 	//	.select('none')
 | 
					
						
							|  |  |  | 	//		-> elem
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Select element by sequence number
 | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 	//	NOTE: negative numbers count from the tail.
 | 
					
						
							|  |  |  | 	//	NOTE: overflowing selects the first/last element.
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	//	.select(<number>)
 | 
					
						
							|  |  |  | 	//		-> elem
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Select element by its text...
 | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 	//	NOTE: if text matches one of the reserved commands above use 
 | 
					
						
							|  |  |  | 	//		quotes to escape it...
 | 
					
						
							|  |  |  | 	//	.select('<text>')
 | 
					
						
							|  |  |  | 	//	.select("'<text>'")
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	//	.select('"<text>"')
 | 
					
						
							|  |  |  | 	//		-> elem
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 	//	Select element via a regular expression...
 | 
					
						
							|  |  |  | 	//	.select(<elem>)
 | 
					
						
							|  |  |  | 	//		-> elem
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	//	.select(<elem>)
 | 
					
						
							|  |  |  | 	//		-> elem
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// This will return a jQuery object.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 	// NOTE: if multiple matches occur this will select the first.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX revise return values...
 | 
					
						
							|  |  |  | 	// XXX Q: should this trigger a "select" event???
 | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 	// XXX on string/regexp mismatch this will select the first, is this correct???
 | 
					
						
							| 
									
										
										
										
											2015-06-18 01:41:57 +03:00
										 |  |  | 	// XXX handle scrollTop to show the selected element...
 | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 	select: function(elem, filtering){ | 
					
						
							|  |  |  | 		var pattern = '.list div:not(.disabled):not(.filtered-out)' | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 		var browser = this.dom | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 		var elems = browser.find(pattern) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		filtering = filtering == null ? this.filtering : filtering | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if(elems.length == 0){ | 
					
						
							|  |  |  | 			return $() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 		elem = elem == 0 ? 'first' : elem | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 		elem = elem || this.select('!') | 
					
						
							|  |  |  | 		// if none selected get the first...
 | 
					
						
							|  |  |  | 		elem = elem.length == 0 ? 'first' : elem | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// first/last...
 | 
					
						
							|  |  |  | 		if(elem == 'first' || elem == 'last'){ | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 			return this.select(elems[elem](), filtering) | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 		 | 
					
						
							|  |  |  | 		// prev/next...
 | 
					
						
							|  |  |  | 		} else if(elem == 'prev' || elem == 'next'){ | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 			var to = this.select('!', filtering)[elem + 'All'](pattern).first() | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 			if(to.length == 0){ | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 				return this.select(elem == 'prev' ? 'last' : 'first', filtering) | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 			this.select('none', filtering) | 
					
						
							|  |  |  | 			return this.select(to, filtering) | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// deselect...
 | 
					
						
							|  |  |  | 		} else if(elem == 'none'){ | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 			if(!filtering){ | 
					
						
							|  |  |  | 				browser.find('.path .dir.cur').empty() | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 			return elems | 
					
						
							|  |  |  | 				.filter('.selected') | 
					
						
							|  |  |  | 				.removeClass('selected') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// strict...
 | 
					
						
							|  |  |  | 		} else if(elem == '!'){ | 
					
						
							|  |  |  | 			return elems.filter('.selected') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// number...
 | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 		// NOTE: on overflow this will get the first/last element...
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 		} else if(typeof(elem) == typeof(123)){ | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 			return this.select($(elems.slice(elem)[0] || elems.slice(-1)[0] ), filtering) | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// string...
 | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 		// XXX on mismatch this will select the first, is this correct???
 | 
					
						
							|  |  |  | 		} else if(typeof(elem) == typeof('str')){ | 
					
						
							|  |  |  | 			if(/^'.*'$|^".*"$/.test(elem.trim())){ | 
					
						
							|  |  |  | 				elem = elem.trim().slice(1, -1) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 			return this.select(browser.find(pattern) | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 					.filter(function(i, e){ | 
					
						
							|  |  |  | 						return $(e).text() == elem | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 					}), filtering) | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 		// regexp...
 | 
					
						
							|  |  |  | 		// XXX on mismatch this will select the first, is this correct???
 | 
					
						
							|  |  |  | 		} else if(elem.constructor === RegExp){ | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 			return this.select(browser.find(pattern) | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 					.filter(function(i, e){ | 
					
						
							|  |  |  | 						return elem.test($(e).text()) | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 					}), filtering) | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 		// element...
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 			elem = $(elem).first() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(elem.length == 0){ | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 				this.select(null, filtering) | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2015-06-18 00:13:30 +03:00
										 |  |  | 				this.select('none', filtering) | 
					
						
							|  |  |  | 				if(!filtering){ | 
					
						
							|  |  |  | 					browser.find('.path .dir.cur').text(elem.text()) | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2015-06-18 02:34:06 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// handle scroll position...
 | 
					
						
							|  |  |  | 				var p = elem.scrollParent() | 
					
						
							|  |  |  | 				var S = p.scrollTop() | 
					
						
							|  |  |  | 				var H = p.height() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				var h = elem.height() | 
					
						
							|  |  |  | 				var t = elem.offset().top - p.offset().top | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				var D = 3 * h  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// XXX there is an error here...
 | 
					
						
							|  |  |  | 				// too low...
 | 
					
						
							|  |  |  | 				if(t+h+D > H){ | 
					
						
							|  |  |  | 					p.scrollTop(S + (t+h+D) - H) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// too high...
 | 
					
						
							|  |  |  | 				} else if(t < D){ | 
					
						
							|  |  |  | 					p.scrollTop(S + t - D) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 				return elem.addClass('selected') | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-17 03:52:21 +03:00
										 |  |  | 	// Select next element...
 | 
					
						
							|  |  |  | 	next: function(elem){ | 
					
						
							|  |  |  | 		if(elem != null){ | 
					
						
							|  |  |  | 			this.select(elem) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		this.select('next') | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	// Select previous element...
 | 
					
						
							|  |  |  | 	prev: function(elem){ | 
					
						
							|  |  |  | 		if(elem != null){ | 
					
						
							|  |  |  | 			this.select(elem) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		this.select('prev') | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Push an element to path / go down one level...
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	push: function(elem){ | 
					
						
							|  |  |  | 		var browser = this.dom  | 
					
						
							|  |  |  | 		var elem = this.select(elem || '!') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// nothing selected, select first and exit...
 | 
					
						
							|  |  |  | 		if(elem.length == 0){ | 
					
						
							|  |  |  | 			this.select() | 
					
						
							|  |  |  | 			return this | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var path = this.path | 
					
						
							|  |  |  | 		path.push(elem.text()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// if not traversable call the action...
 | 
					
						
							|  |  |  | 		if(this.isTraversable != null  | 
					
						
							|  |  |  | 				&& (this.isTraversable !== false | 
					
						
							|  |  |  | 					|| ! this.isTraversable(path))){ | 
					
						
							|  |  |  | 			return this.action(path) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.path = path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.select() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2015-05-17 03:52:21 +03:00
										 |  |  | 	// Pop an element off the path / go up one level...
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	pop: function(){ | 
					
						
							|  |  |  | 		var browser = this.dom | 
					
						
							|  |  |  | 		var path = this.path | 
					
						
							|  |  |  | 		var dir = path.pop() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.update(path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.select('"'+dir+'"') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	focus: function(){ | 
					
						
							|  |  |  | 		this.dom.focus() | 
					
						
							| 
									
										
										
										
											2015-03-21 18:20:59 +03:00
										 |  |  | 		return this | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX think about the API...
 | 
					
						
							|  |  |  | 	// XXX trigger an "open" event...
 | 
					
						
							|  |  |  | 	action: function(){ | 
					
						
							|  |  |  | 		var elem = this.select('!') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// nothing selected, select first and exit...
 | 
					
						
							|  |  |  | 		if(elem.length == 0){ | 
					
						
							|  |  |  | 			this.select() | 
					
						
							|  |  |  | 			return this | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 		var path = this.path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		path.push(elem.text()) | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		var res = this.open(path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return res | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// extension methods...
 | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 	// XXX this is wrong...
 | 
					
						
							|  |  |  | 	// 		...need to actually open something...
 | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	open: function(path){  | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 		path = path || this.path | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 		var m = this.options.list | 
					
						
							|  |  |  | 		return m ? m.call(this, path) : path | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	list: function(path){ | 
					
						
							| 
									
										
										
										
											2015-05-25 20:02:47 +03:00
										 |  |  | 		path = path || this.path | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 		var m = this.options.list | 
					
						
							| 
									
										
										
										
											2015-03-22 02:12:30 +03:00
										 |  |  | 		return m ? m.call(this, path) : [] | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 	isTraversable: null, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX need to get a container....
 | 
					
						
							|  |  |  | 	// XXX setup instance events...
 | 
					
						
							|  |  |  | 	__init__: function(parent, options){ | 
					
						
							| 
									
										
										
										
											2015-05-18 03:34:15 +03:00
										 |  |  | 		options = options || {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// merge options...
 | 
					
						
							|  |  |  | 		var opts = Object.create(this.options) | 
					
						
							|  |  |  | 		Object.keys(options).forEach(function(n){ opts[n] = options[n] }) | 
					
						
							|  |  |  | 		options = this.options = opts | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// build the dom...
 | 
					
						
							|  |  |  | 		var dom = this.dom = this.constructor.make(options) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// add keyboard handler...
 | 
					
						
							|  |  |  | 		dom.keydown( | 
					
						
							|  |  |  | 			keyboard.makeKeyboardHandler( | 
					
						
							|  |  |  | 				this.keyboard, | 
					
						
							| 
									
										
										
										
											2015-05-18 03:34:15 +03:00
										 |  |  | 				options.logKeys, | 
					
						
							| 
									
										
										
										
											2015-03-21 16:45:20 +03:00
										 |  |  | 				this)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// attach to parent...
 | 
					
						
							|  |  |  | 		if(parent != null){ | 
					
						
							|  |  |  | 			parent.append(dom) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// load the initial state...
 | 
					
						
							|  |  |  | 		this.update(this.path) | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* | 
					
						
							|  |  |  | var Browser =  | 
					
						
							|  |  |  | //module.Browser = 
 | 
					
						
							|  |  |  | object.makeConstructor('Browser',  | 
					
						
							|  |  |  | 		BrowserClassPrototype,  | 
					
						
							|  |  |  | 		BrowserPrototype) | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /********************************************************************** | 
					
						
							|  |  |  | * vim:set ts=4 sw=4 :                                                */ |