mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-10-29 18:30:09 +00:00 
			
		
		
		
	split out the generic dialogs.js + some minor tweaks...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
		
							parent
							
								
									4ead136b4d
								
							
						
					
					
						commit
						88d1bcc4ba
					
				
							
								
								
									
										102
									
								
								ui/TODO.otl
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								ui/TODO.otl
									
									
									
									
									
								
							| @ -111,57 +111,6 @@ Roadmap | ||||
| [_] 32% Gen 3 current todo | ||||
| 	[_] 65% High priority | ||||
| 		[_] make buildcache sort images via data AND file name... | ||||
| 		[X] BUG: sorting (dialog) will mess up the order... | ||||
| 		| Procedure: | ||||
| 		| 	- shift-s + sort in a way that changes the order | ||||
| 		| 	- move next till the spot where the order changed | ||||
| 		|		-- the next/prev action will jump around... | ||||
| 		| | ||||
| 		| probably due to a bug in reloading... | ||||
| 		| | ||||
| 		| NOTE: sorting is done correctly... | ||||
| 		| | ||||
| 		| Workaround: | ||||
| 		| 	- sort, save, then F5 | ||||
| 		| 	 | ||||
| 		[X] BUG: sorting breaks when at or near the end of a ribbon... | ||||
| 		| | ||||
| 		| Race condition... | ||||
| 		| | ||||
| 		| Procedure: | ||||
| 		| 	- go to end of a ribbon | ||||
| 		| 	- shift-s  | ||||
| 		| 		- select a sort method | ||||
| 		| 		- click "OK" | ||||
| 		| | ||||
| 		| NOTE: this will not break of sorting will not change the order of | ||||
| 		| 		visible images... | ||||
| 		| 		thus, if this the above procedure does not break do one of: | ||||
| 		| 			- ctrl-r (reverse) before sorting | ||||
| 		| 			- check "Descending" in the sort dialog | ||||
| 		| NOTE: this breaks because current the current image is not  | ||||
| 		| 		yet loaded/created when reloadViewer(..) tries to focus it... | ||||
| 		| | ||||
| 		| Temporary workaround: | ||||
| 		| 	because there is nothing wrong with sorting itself, just the | ||||
| 		| 	UI, the resulting state can be fixed by simply reloading the  | ||||
| 		| 	viewer (reloadViewer(true) or ctrl-alt-r) | ||||
| 		|  | ||||
| 		| NOTE: appears to affect beginning of the ribbon too... | ||||
| 		[X] BUG: sorting mis-aligns ribbons in some cases... | ||||
| 		| Example: | ||||
| 		| 				 oooo...	--[reverse]->	   ...oooo | ||||
| 		| 		...oooo[o]oooo...					...oooo[o]oooo... | ||||
| 		| | ||||
| 		| 		Should be: | ||||
| 		| 				 oooo...	--[reverse]->	 ...oooo | ||||
| 		| 		...oooo[o]oooo...					...oooo[o]oooo... | ||||
| 		| | ||||
| 		| 	The above can happen when, for example, sorting the images via data | ||||
| 		| 	and then sorting them in the same way with reverse checked... | ||||
| 		| | ||||
| 		| XXX is this related to? | ||||
| 		|	BUG: sorting breaks when at or near the end of a ribbon... | ||||
| 		[_] BUG: panels: open/close events get triggered on panel drag/sort... | ||||
| 		[_] buildcache: add option to control image sort... | ||||
| 		[X] buildcache: add ability to process multiple dirs... | ||||
| @ -561,6 +510,57 @@ Roadmap | ||||
| 		| 	drops to last placeholder | ||||
| 		| | ||||
| 		[_] single image mode transition (alpha-blend/fade/none) | ||||
| 		[X] BUG: sorting (dialog) will mess up the order... | ||||
| 		| Procedure: | ||||
| 		| 	- shift-s + sort in a way that changes the order | ||||
| 		| 	- move next till the spot where the order changed | ||||
| 		|		-- the next/prev action will jump around... | ||||
| 		| | ||||
| 		| probably due to a bug in reloading... | ||||
| 		| | ||||
| 		| NOTE: sorting is done correctly... | ||||
| 		| | ||||
| 		| Workaround: | ||||
| 		| 	- sort, save, then F5 | ||||
| 		| 	 | ||||
| 		[X] BUG: sorting breaks when at or near the end of a ribbon... | ||||
| 		| | ||||
| 		| Race condition... | ||||
| 		| | ||||
| 		| Procedure: | ||||
| 		| 	- go to end of a ribbon | ||||
| 		| 	- shift-s  | ||||
| 		| 		- select a sort method | ||||
| 		| 		- click "OK" | ||||
| 		| | ||||
| 		| NOTE: this will not break of sorting will not change the order of | ||||
| 		| 		visible images... | ||||
| 		| 		thus, if this the above procedure does not break do one of: | ||||
| 		| 			- ctrl-r (reverse) before sorting | ||||
| 		| 			- check "Descending" in the sort dialog | ||||
| 		| NOTE: this breaks because current the current image is not  | ||||
| 		| 		yet loaded/created when reloadViewer(..) tries to focus it... | ||||
| 		| | ||||
| 		| Temporary workaround: | ||||
| 		| 	because there is nothing wrong with sorting itself, just the | ||||
| 		| 	UI, the resulting state can be fixed by simply reloading the  | ||||
| 		| 	viewer (reloadViewer(true) or ctrl-alt-r) | ||||
| 		|  | ||||
| 		| NOTE: appears to affect beginning of the ribbon too... | ||||
| 		[X] BUG: sorting mis-aligns ribbons in some cases... | ||||
| 		| Example: | ||||
| 		| 				 oooo...	--[reverse]->	   ...oooo | ||||
| 		| 		...oooo[o]oooo...					...oooo[o]oooo... | ||||
| 		| | ||||
| 		| 		Should be: | ||||
| 		| 				 oooo...	--[reverse]->	 ...oooo | ||||
| 		| 		...oooo[o]oooo...					...oooo[o]oooo... | ||||
| 		| | ||||
| 		| 	The above can happen when, for example, sorting the images via data | ||||
| 		| 	and then sorting them in the same way with reverse checked... | ||||
| 		| | ||||
| 		| XXX is this related to? | ||||
| 		|	BUG: sorting breaks when at or near the end of a ribbon... | ||||
| 		[X] BUG: shifting image left/right marks and bookmarks it... | ||||
| 		[X] Might be a good idea to use sparse arrays for things like marks... | ||||
| 		| eliminate:	 | ||||
|  | ||||
| @ -144,6 +144,8 @@ function statusProgress(msg, tracker){ | ||||
| 
 | ||||
| // Bubble up actions in the deferred chain
 | ||||
| //
 | ||||
| // i.e. proxy them form one deferred (from) to the next (to)...
 | ||||
| //
 | ||||
| // Will chain progress/notify and if only_progress is not set, also
 | ||||
| // done/resolve and fail/reject from "from" to "to" deferred objects.
 | ||||
| //
 | ||||
|  | ||||
| @ -20,6 +20,7 @@ | ||||
| <script src="lib/jli.js"></script> | ||||
| <script src="lib/keyboard.js"></script> | ||||
| <script src="lib/scroller.js"></script> | ||||
| <script src="lib/dialogs.js"></script> | ||||
| <script src="lib/panels.js"></script> | ||||
| <script src="lib/editor.js"></script> | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										632
									
								
								ui/lib/dialogs.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										632
									
								
								ui/lib/dialogs.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,632 @@ | ||||
| /********************************************************************** | ||||
| *  | ||||
| * | ||||
| * | ||||
| **********************************************************************/ | ||||
| 
 | ||||
| //var DEBUG = DEBUG != null ? DEBUG : true
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * Modal dialogs... | ||||
| */ | ||||
| 
 | ||||
| /********************************************************* Helpers ***/ | ||||
| 
 | ||||
| // Set element text and tooltip
 | ||||
| //
 | ||||
| // NOTE: when text is a list, we will only use the first and the last 
 | ||||
| // 		elements...
 | ||||
| // NOTE: if tip_elem is not given then both the text and tip will be set
 | ||||
| // 		on text_elem
 | ||||
| //
 | ||||
| // XXX add support for quoted '|'...
 | ||||
| function setTextWithTooltip(text, text_elem, tip_elem){ | ||||
| 	text_elem = $(text_elem) | ||||
| 	tip_elem = tip_elem == null ? text_elem : tip_elem | ||||
| 
 | ||||
| 	if(typeof(text) != typeof('str')){ | ||||
| 		tip = text | ||||
| 	} else { | ||||
| 		var tip = text.split(/\s*\|\s*/) | ||||
| 	} | ||||
| 
 | ||||
| 	// set elemnt text...
 | ||||
| 	text_elem | ||||
| 		.html(tip[0]) | ||||
| 
 | ||||
| 	// do the tooltip...
 | ||||
| 	tip = tip.slice(1) | ||||
| 	tip = tip[tip.length-1] | ||||
| 	if(tip != null && tip.trim().length > 0){ | ||||
| 		$('<span class="tooltip-icon tooltip-right"> *</span>') | ||||
| 			.attr('tooltip', tip) | ||||
| 			.appendTo(tip_elem) | ||||
| 	} | ||||
| 
 | ||||
| 	return text_elem | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function getOverlay(root){ | ||||
| 	root = $(root) | ||||
| 	var overlay = root.find('.overlay-block') | ||||
| 	if(overlay.length == 0){ | ||||
| 		return $('<div class="overlay-block">'+ | ||||
| 					'<div class="background"/>'+ | ||||
| 					'<div class="content"/>'+ | ||||
| 				'</div>').appendTo(root) | ||||
| 	} | ||||
| 	return overlay | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function showInOverlay(root, data){ | ||||
| 	root = $(root) | ||||
| 
 | ||||
| 	var overlay = getOverlay(root) | ||||
| 	 | ||||
| 
 | ||||
| 	if(data != null){ | ||||
| 		var container = $('<table width="100%" height="100%"><tr><td align="center" valign="center">'+ | ||||
| 								'<div class="dialog"/>'+ | ||||
| 							'</td></tr></table>') | ||||
| 		var dialog = container.find('.dialog') | ||||
| 
 | ||||
| 		//overlay.find('.background')
 | ||||
| 		//	.click(function(){ hideOverlay(root) })
 | ||||
| 
 | ||||
| 		dialog | ||||
| 			.append(data) | ||||
| 			.on('click', function(evt){  | ||||
| 				evt.stopPropagation()  | ||||
| 			}) | ||||
| 		overlay.find('.content') | ||||
| 			.on('click', function(){  | ||||
| 				overlay.trigger('close') | ||||
| 				hideOverlay(root)  | ||||
| 			}) | ||||
| 			.on('close accept', function(){ | ||||
| 				//hideOverlay(root) 
 | ||||
| 			}) | ||||
| 			.append(container) | ||||
| 	} | ||||
| 
 | ||||
| 	root.addClass('overlay') | ||||
| 
 | ||||
| 	return overlay | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function hideOverlay(root){ | ||||
| 	root.removeClass('overlay') | ||||
| 	root.find('.overlay-block') | ||||
| 		.trigger('close') | ||||
| 		.remove() | ||||
| } | ||||
| 
 | ||||
| function isOverlayVisible(root){ | ||||
| 	return getOverlay(root).css('display') != 'none' | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * Field definitions... | ||||
| */ | ||||
| 
 | ||||
| var FIELD_TYPES = { | ||||
| 	// a simple hr...
 | ||||
| 	//
 | ||||
| 	// format:
 | ||||
| 	// 		'---'
 | ||||
| 	// 		Three or more '-'s
 | ||||
| 	hr: { | ||||
| 		type: 'hr', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<hr>', | ||||
| 		test: function(val){ | ||||
| 			return /\-\-\-+/.test(val) | ||||
| 		}, | ||||
| 	}, | ||||
| 	// a simple br...
 | ||||
| 	//
 | ||||
| 	// format:
 | ||||
| 	// 		'   '
 | ||||
| 	// 		Three or more spaces
 | ||||
| 	br: { | ||||
| 		type: 'br', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<br>', | ||||
| 		test: function(val){ | ||||
| 			return /\s\s\s+/.test(val) | ||||
| 		}, | ||||
| 	}, | ||||
| 	// format:
 | ||||
| 	// 	{
 | ||||
| 	// 		html: <html-block>
 | ||||
| 	// 	}
 | ||||
| 	html: { | ||||
| 		type: 'html', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="html-block"/>', | ||||
| 		test: function(val){ | ||||
| 			return val.html != null | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			if(typeof(value.html) == typeof('str')){ | ||||
| 				field.html(value.html) | ||||
| 			} else { | ||||
| 				field.append(value.html) | ||||
| 			} | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	// format: 
 | ||||
| 	// 		string
 | ||||
| 	// XXX add datalist option...
 | ||||
| 	// XXX make this textarea compatible...
 | ||||
| 	text: { | ||||
| 		type: 'text', | ||||
| 		text: null, | ||||
| 		default: '', | ||||
| 		html: '<div class="field string">'+ | ||||
| 				'<span class="text"></span>'+ | ||||
| 				'<input type="text" class="value">'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return typeof(val) == typeof('abc') | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			$(field).find('.value').attr('value', value)  | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			return $(field).find('.value').attr('value')  | ||||
| 		}, | ||||
| 	},	 | ||||
| 
 | ||||
| 	// format: 
 | ||||
| 	// 		true | false
 | ||||
| 	bool: { | ||||
| 		type: 'bool', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="field checkbox">'+ | ||||
| 				'<label><input type="checkbox" class="value">'+ | ||||
| 				'<span class="text"></span></label>'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return val === true || val === false | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			if(value){ | ||||
| 				$(field).find('.value').attr('checked', '')  | ||||
| 			} else { | ||||
| 				$(field).find('.value').removeAttr('checked')  | ||||
| 			} | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			return $(field).find('.value').attr('checked') == 'checked' | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	// NOTE: this will not work without node-webkit...
 | ||||
| 	// format: 
 | ||||
| 	// 		{ dir: <default-path> }
 | ||||
| 	dir: { | ||||
| 		type: 'dir', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="field checkbox">'+ | ||||
| 				'<span class="text"></span>'+ | ||||
| 				'<input type="file" class="value" nwdirectory />'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return typeof(val) == typeof({}) && 'dir' in val | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			field.find('.value').attr('nwworkingdir', value.dir) | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			var f = $(field).find('.value')[0].files | ||||
| 			if(f.length == 0){ | ||||
| 				return '' | ||||
| 			} | ||||
| 			return f[0].path | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	// NOTE: this will not work without node-webkit...
 | ||||
| 	// format: 
 | ||||
| 	// 		{ dir: <default-path> }
 | ||||
| 	// XXX add datalist option...
 | ||||
| 	ndir: { | ||||
| 		type: 'ndir', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="field dir">'+ | ||||
| 				'<span class="text"></span>'+ | ||||
| 				'<input type="text" class="path"/>'+ | ||||
| 				'<button class="browse">Browse</button>'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return typeof(val) == typeof({}) && 'ndir' in val | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			var that = this | ||||
| 
 | ||||
| 			// NOTE: we are attaching the file browser to body to avoid 
 | ||||
| 			// 		click events on it closing the dialog...
 | ||||
| 			// 		...for some reason stopPropagation(...) does not do 
 | ||||
| 			// 		the job...
 | ||||
| 			var file = $('<input type="file" class="value" nwdirectory/>') | ||||
| 				.attr('nwworkingdir', value.ndir) | ||||
| 				.change(function(){ | ||||
| 					var p = file[0].files | ||||
| 					if(p.length != 0){ | ||||
| 						field.find('.path').val(p[0].path) | ||||
| 					} | ||||
| 					file.detach() | ||||
| 					// focus+select the path field...
 | ||||
| 					// NOTE: this is here to enable fast select-open 
 | ||||
| 					// 		keyboard cycle (tab, enter, <select path>, 
 | ||||
| 					// 		enter, enter)...
 | ||||
| 					field.find('.path') | ||||
| 						.focus() | ||||
| 						.select() | ||||
| 				}) | ||||
| 				.hide() | ||||
| 			field.find('.path').val(value.ndir) | ||||
| 
 | ||||
| 			field.find('.browse').click(function(){ | ||||
| 				file | ||||
| 					// load user input path...
 | ||||
| 					.attr('nwworkingdir', field.find('.path').val()) | ||||
| 					.appendTo($('body')) | ||||
| 					.click() | ||||
| 			}) | ||||
| 
 | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			return field.find('.path').val() | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	// format: 
 | ||||
| 	// 		['a', 'b', 'c', ...]
 | ||||
| 	//
 | ||||
| 	// an item can be of the folowing format:
 | ||||
| 	// 		<text> ['|' 'default' | 'disabled' ] [ '|' <tool-tip> ]
 | ||||
| 	//
 | ||||
| 	// NOTE: only one 'default' item should be present.
 | ||||
| 	// NOTE: if no defaults are set, then the first item is checked.
 | ||||
| 	choice: { | ||||
| 		type: 'choice', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="field choice">'+ | ||||
| 				'<span class="text"></span>'+ | ||||
| 				'<div class="item"><label>'+ | ||||
| 					'<input type="radio" class="value"/>'+ | ||||
| 					'<span class="item-text"></span>'+ | ||||
| 				'</label></div>'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return typeof(val) == typeof([]) && val.constructor.name == 'Array' | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			var t = field.find('.text').html() | ||||
| 			t = t == '' ? Math.random()+'' : t | ||||
| 			var item = field.find('.item').last() | ||||
| 			for(var i=0; i < value.length; i++){ | ||||
| 				// get options...
 | ||||
| 				var opts = value[i] | ||||
| 					.split(/\|/g) | ||||
| 					.map(function(e){ return e.trim() }) | ||||
| 
 | ||||
| 				var val = item.find('.value') | ||||
| 				val.val(opts[0]) | ||||
| 
 | ||||
| 				// set checked state...
 | ||||
| 				if(opts.slice(1).indexOf('default') >= 0){ | ||||
| 					val.prop('checked', true) | ||||
| 					opts.splice(opts.indexOf('default'), 1) | ||||
| 				} else { | ||||
| 					val.prop('checked', false) | ||||
| 				} | ||||
| 
 | ||||
| 				// set disabled state...
 | ||||
| 				if(opts.slice(1).indexOf('disabled') >= 0){ | ||||
| 					val.prop('disabled', true) | ||||
| 					opts.splice(opts.indexOf('disabled'), 1) | ||||
| 					item.addClass('disabled') | ||||
| 				} else { | ||||
| 					val.prop('disabled', false) | ||||
| 					item.removeClass('disabled') | ||||
| 				} | ||||
| 
 | ||||
| 				setTextWithTooltip(opts, item.find('.item-text')) | ||||
| 
 | ||||
| 				item.appendTo(field) | ||||
| 
 | ||||
| 				item = item.clone() | ||||
| 			} | ||||
| 			var values = field.find('.value') | ||||
| 				.attr('name', t) | ||||
| 			// set the default...
 | ||||
| 			if(values.filter(':checked:not([disabled])').length == 0){ | ||||
| 				values.filter(':not([disabled])').first() | ||||
| 					.prop('checked', true) | ||||
| 			} | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			return $(field).find('.value:checked').val() | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	// format: 
 | ||||
| 	// 	{ 
 | ||||
| 	// 		select: ['a', 'b', 'c', ...] 
 | ||||
| 	// 		// default option (optional)...
 | ||||
| 	// 		default: <number> | <text>
 | ||||
| 	// 	}
 | ||||
| 	select: { | ||||
| 		type: 'select', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="field choice">'+ | ||||
| 				'<span class="text"></span>'+ | ||||
| 				'<select>'+ | ||||
| 					'<option class="option"></option>'+ | ||||
| 				'</select>'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return 'select' in val | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			var t = field.find('.text').text() | ||||
| 			var item = field.find('.option').last() | ||||
| 			var select = field.find('select') | ||||
| 			for(var i=0; i < value.select.length; i++){ | ||||
| 				item | ||||
| 					.html(value.select[i]) | ||||
| 					.val(value.select[i]) | ||||
| 				item.appendTo(select) | ||||
| 
 | ||||
| 				item = item.clone() | ||||
| 			} | ||||
| 			if(value.default != null){ | ||||
| 				if(typeof(value.default) == typeof(123)){ | ||||
| 					field.find('.option') | ||||
| 						.eq(value.default) | ||||
| 							.attr('selected', '') | ||||
| 				} else { | ||||
| 					field.find('.option[value="'+ value.default +'"]') | ||||
| 						.attr('selected', '') | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			return $(field).find('.option:selected').val() | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	// NOTE: a button can have state...
 | ||||
| 	// format: 
 | ||||
| 	// 	{ 
 | ||||
| 	// 		// click event handler...
 | ||||
| 	// 		button: <function>, 
 | ||||
| 	// 		// optional, button text (default 'OK')...
 | ||||
| 	// 		text: <button-label>,
 | ||||
| 	// 		// optional, initial state setup...
 | ||||
| 	// 		default: <function>,
 | ||||
| 	// 	}
 | ||||
| 	button: { | ||||
| 		type: 'button', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="field button">'+ | ||||
| 				'<span class="text"></span>'+ | ||||
| 				'<button class="button"></button>'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return 'button' in val | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			var btn = $(field).find('button') | ||||
| 				.click(value.button) | ||||
| 				.html(value.text == null ? 'OK' : value.text) | ||||
| 			if('default' in value){ | ||||
| 				value.default(btn) | ||||
| 			} | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			return $(field).attr('state') | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * Constructors... | ||||
| */ | ||||
| 
 | ||||
| // Show a complex form dialog
 | ||||
| //
 | ||||
| // This will build a form and collect it's data on "accept" specified by
 | ||||
| // the config object...
 | ||||
| //
 | ||||
| // config format:
 | ||||
| //	{
 | ||||
| //		// simple field...
 | ||||
| //		<field-description>: <default-value>,
 | ||||
| //
 | ||||
| //		...
 | ||||
| //	}	
 | ||||
| //
 | ||||
| // <field-description> and split in two with a "|" the section before will
 | ||||
| // show as the field text and the text after as the tooltip.
 | ||||
| // Example:
 | ||||
| // 		"field text | field tooltip..."
 | ||||
| //
 | ||||
| // field's default value determines it's type:
 | ||||
| // 	bool		- checkbox
 | ||||
| // 	string		- textarea
 | ||||
| //
 | ||||
| // see FIELD_TYPES for supported field types.
 | ||||
| //
 | ||||
| // NOTE: if btn is set to false explicitly then no button will be 
 | ||||
| // 		rendered in the form dialog.
 | ||||
| // NOTE: to include a literal "|" in <field-description> just escape it
 | ||||
| // 		like this: "\|"
 | ||||
| //
 | ||||
| // XXX add form testing...
 | ||||
| // XXX add undefined field handling/reporting...
 | ||||
| function formDialog(root, message, config, btn, cls){ | ||||
| 	cls = cls == null ? '' : cls | ||||
| 	btn = btn == null ? 'OK' : btn | ||||
| 	root = root == null ? $('.viewer') : root | ||||
| 
 | ||||
| 	var form = $('<div class="form"/>') | ||||
| 	var data = {} | ||||
| 	var res = $.Deferred() | ||||
| 
 | ||||
| 	// handle message and btn...
 | ||||
| 	if(message.trim().length > 0){ | ||||
| 		setTextWithTooltip(message, $('<div class="text"/>')) | ||||
| 			.appendTo(form) | ||||
| 	} | ||||
| 
 | ||||
| 	// build the form...
 | ||||
| 	for(var t in config){ | ||||
| 		var did_handling = false | ||||
| 		for(var f in FIELD_TYPES){ | ||||
| 			if(FIELD_TYPES[f].test(config[t])){ | ||||
| 				var field = FIELD_TYPES[f] | ||||
| 				var html = $(field.html) | ||||
| 
 | ||||
| 				// setup text and data...
 | ||||
| 				setTextWithTooltip(t, html.find('.text'), html) | ||||
| 
 | ||||
| 				if(field.set != null){ | ||||
| 					field.set(html, config[t]) | ||||
| 				} | ||||
| 
 | ||||
| 				if(field.get != null){ | ||||
| 					// NOTE: this is here to isolate t and field.get values...
 | ||||
| 					// 		...is there a better way???
 | ||||
| 					var _ = (function(title, getter){ | ||||
| 						html.on('resolve', function(evt, e){ | ||||
| 							data[title] = getter(e) | ||||
| 						}) | ||||
| 					})(t, field.get) | ||||
| 				} | ||||
| 
 | ||||
| 				form.append(html) | ||||
| 
 | ||||
| 				did_handling = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// handle unresolved fields...
 | ||||
| 		if(!did_handling){ | ||||
| 			console.warn('formDialog: not all fields understood.') | ||||
| 			// XXX skipping field...
 | ||||
| 			// XXX
 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// add button...
 | ||||
| 	if(btn !== false){ | ||||
| 		var button = $('<button class="accept">'+btn+'</button>') | ||||
| 		form.append(button) | ||||
| 	} else { | ||||
| 		var button = null | ||||
| 	} | ||||
| 
 | ||||
| 	var overlay = showInOverlay(root, form) | ||||
| 		.addClass('dialog ' + cls) | ||||
| 		.on('accept', function(){ | ||||
| 			form.find('.field').each(function(_, e){ | ||||
| 				$(e).trigger('resolve', [$(e)]) | ||||
| 			}) | ||||
| 
 | ||||
| 			// XXX test if all required stuff is filled...
 | ||||
| 			res.resolve(data, form) | ||||
| 
 | ||||
| 			hideOverlay(root) | ||||
| 		}) | ||||
| 		.on('close', function(){ | ||||
| 			res.reject() | ||||
| 
 | ||||
| 		}) | ||||
| 
 | ||||
| 	if(button != null){ | ||||
| 		button.click(function(){ | ||||
| 			overlay.trigger('accept') | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	// focus an element...
 | ||||
| 	// NOTE: if first element is a radio button set, focus the checked
 | ||||
| 	//		element, else focus the first input...
 | ||||
| 	form.ready(function(){  | ||||
| 		// NOTE: we are using a timeout to avoid the user input that opened
 | ||||
| 		// 		the dialog to end up in the first field...
 | ||||
| 		setTimeout(function(){ | ||||
| 			var elem = form.find('.field input').first() | ||||
| 			if(elem.attr('type') == 'radio'){ | ||||
| 				form.find('.field input:checked') | ||||
| 					.focus() | ||||
| 					.select() | ||||
| 			} else { | ||||
| 				elem | ||||
| 					.focus() | ||||
| 					.select() | ||||
| 			} | ||||
| 		}, 100) | ||||
| 	}) | ||||
| 
 | ||||
| 	return res | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /************************************************ Standard dialogs ***/ | ||||
| // NOTE: these return a deferred that will reflect the state of the 
 | ||||
| // 		dialog, and the progress of the operations that it riggers...
 | ||||
| //
 | ||||
| // XXX might be a good idea to be able to block the ui (overlay + progress
 | ||||
| // 		bar?) until some long/critical operations finish, to prevent the
 | ||||
| // 		user from breaking things while the ui is inconsistent...
 | ||||
| 
 | ||||
| function alertDialog(){ | ||||
| 	var message = $.makeArray(arguments).join(' ') | ||||
| 	return formDialog(null, String(message), {}, false, 'alert') | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function promptDialog(message, dfl, btn){ | ||||
| 	btn = btn == null ? 'OK' : btn | ||||
| 	var res = $.Deferred() | ||||
| 	formDialog(null, message, {'': ''+(dfl == null ? '' : dfl)}, btn, 'prompt') | ||||
| 		.done(function(data){ res.resolve(data['']) }) | ||||
| 		.fail(function(){ res.reject() }) | ||||
| 	return res | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* | ||||
| function confirmDialog(){ | ||||
| } | ||||
| */ | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * vim:set ts=4 sw=4 :                                                */ | ||||
							
								
								
									
										611
									
								
								ui/ui.js
									
									
									
									
									
								
							
							
						
						
									
										611
									
								
								ui/ui.js
									
									
									
									
									
								
							| @ -14,6 +14,7 @@ var STATUS_QUEUE_TIME = 200 | ||||
| 
 | ||||
| var CONTEXT_INDICATOR_UPDATERS = [] | ||||
| 
 | ||||
| 
 | ||||
| // this can be:
 | ||||
| // 	- 'floating'
 | ||||
| // 	- 'panel'
 | ||||
| @ -613,617 +614,9 @@ function closeProgressBar(name){ | ||||
| 
 | ||||
| 
 | ||||
| /********************************************************************** | ||||
| * Modal dialogs... | ||||
| * Dialogs... | ||||
| */ | ||||
| 
 | ||||
| /********************************************************* Helpers ***/ | ||||
| 
 | ||||
| // Set element text and tooltip
 | ||||
| //
 | ||||
| // NOTE: when text is a list, we will only use the first and the last 
 | ||||
| // 		elements...
 | ||||
| // NOTE: if tip_elem is not given then both the text and tip will be set
 | ||||
| // 		on text_elem
 | ||||
| //
 | ||||
| // XXX add support for quoted '|'...
 | ||||
| function setTextWithTooltip(text, text_elem, tip_elem){ | ||||
| 	text_elem = $(text_elem) | ||||
| 	tip_elem = tip_elem == null ? text_elem : tip_elem | ||||
| 
 | ||||
| 	if(typeof(text) != typeof('str')){ | ||||
| 		tip = text | ||||
| 	} else { | ||||
| 		var tip = text.split(/\s*\|\s*/) | ||||
| 	} | ||||
| 
 | ||||
| 	// set elemnt text...
 | ||||
| 	text_elem | ||||
| 		.html(tip[0]) | ||||
| 
 | ||||
| 	// do the tooltip...
 | ||||
| 	tip = tip.slice(1) | ||||
| 	tip = tip[tip.length-1] | ||||
| 	if(tip != null && tip.trim().length > 0){ | ||||
| 		$('<span class="tooltip-icon tooltip-right"> *</span>') | ||||
| 			.attr('tooltip', tip) | ||||
| 			.appendTo(tip_elem) | ||||
| 	} | ||||
| 
 | ||||
| 	return text_elem | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function getOverlay(root){ | ||||
| 	root = $(root) | ||||
| 	var overlay = root.find('.overlay-block') | ||||
| 	if(overlay.length == 0){ | ||||
| 		return $('<div class="overlay-block">'+ | ||||
| 					'<div class="background"/>'+ | ||||
| 					'<div class="content"/>'+ | ||||
| 				'</div>').appendTo(root) | ||||
| 	} | ||||
| 	return overlay | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function showInOverlay(root, data){ | ||||
| 	root = $(root) | ||||
| 
 | ||||
| 	var overlay = getOverlay(root) | ||||
| 	 | ||||
| 
 | ||||
| 	if(data != null){ | ||||
| 		var container = $('<table width="100%" height="100%"><tr><td align="center" valign="center">'+ | ||||
| 								'<div class="dialog"/>'+ | ||||
| 							'</td></tr></table>') | ||||
| 		var dialog = container.find('.dialog') | ||||
| 
 | ||||
| 		//overlay.find('.background')
 | ||||
| 		//	.click(function(){ hideOverlay(root) })
 | ||||
| 
 | ||||
| 		dialog | ||||
| 			.append(data) | ||||
| 			.on('click', function(evt){  | ||||
| 				evt.stopPropagation()  | ||||
| 			}) | ||||
| 		overlay.find('.content') | ||||
| 			.on('click', function(){  | ||||
| 				overlay.trigger('close') | ||||
| 				hideOverlay(root)  | ||||
| 			}) | ||||
| 			.on('close accept', function(){ | ||||
| 				//hideOverlay(root) 
 | ||||
| 			}) | ||||
| 			.append(container) | ||||
| 	} | ||||
| 
 | ||||
| 	root.addClass('overlay') | ||||
| 
 | ||||
| 	return overlay | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function hideOverlay(root){ | ||||
| 	root.removeClass('overlay') | ||||
| 	root.find('.overlay-block') | ||||
| 		.trigger('close') | ||||
| 		.remove() | ||||
| } | ||||
| 
 | ||||
| function isOverlayVisible(root){ | ||||
| 	return getOverlay(root).css('display') != 'none' | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| var FIELD_TYPES = { | ||||
| 	// a simple hr...
 | ||||
| 	//
 | ||||
| 	// format:
 | ||||
| 	// 		'---'
 | ||||
| 	// 		Three or more '-'s
 | ||||
| 	hr: { | ||||
| 		type: 'hr', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<hr>', | ||||
| 		test: function(val){ | ||||
| 			return /\-\-\-+/.test(val) | ||||
| 		}, | ||||
| 	}, | ||||
| 	// a simple br...
 | ||||
| 	//
 | ||||
| 	// format:
 | ||||
| 	// 		'   '
 | ||||
| 	// 		Three or more spaces
 | ||||
| 	br: { | ||||
| 		type: 'br', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<br>', | ||||
| 		test: function(val){ | ||||
| 			return /\s\s\s+/.test(val) | ||||
| 		}, | ||||
| 	}, | ||||
| 	// format:
 | ||||
| 	// 	{
 | ||||
| 	// 		html: <html-block>
 | ||||
| 	// 	}
 | ||||
| 	html: { | ||||
| 		type: 'html', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="html-block"/>', | ||||
| 		test: function(val){ | ||||
| 			return val.html != null | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			if(typeof(value.html) == typeof('str')){ | ||||
| 				field.html(value.html) | ||||
| 			} else { | ||||
| 				field.append(value.html) | ||||
| 			} | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	// format: 
 | ||||
| 	// 		string
 | ||||
| 	// XXX add datalist option...
 | ||||
| 	// XXX make this textarea compatible...
 | ||||
| 	text: { | ||||
| 		type: 'text', | ||||
| 		text: null, | ||||
| 		default: '', | ||||
| 		html: '<div class="field string">'+ | ||||
| 				'<span class="text"></span>'+ | ||||
| 				'<input type="text" class="value">'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return typeof(val) == typeof('abc') | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			$(field).find('.value').attr('value', value)  | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			return $(field).find('.value').attr('value')  | ||||
| 		}, | ||||
| 	},	 | ||||
| 
 | ||||
| 	// format: 
 | ||||
| 	// 		true | false
 | ||||
| 	bool: { | ||||
| 		type: 'bool', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="field checkbox">'+ | ||||
| 				'<label><input type="checkbox" class="value">'+ | ||||
| 				'<span class="text"></span></label>'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return val === true || val === false | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			if(value){ | ||||
| 				$(field).find('.value').attr('checked', '')  | ||||
| 			} else { | ||||
| 				$(field).find('.value').removeAttr('checked')  | ||||
| 			} | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			return $(field).find('.value').attr('checked') == 'checked' | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	// NOTE: this will not work without node-webkit...
 | ||||
| 	// format: 
 | ||||
| 	// 		{ dir: <default-path> }
 | ||||
| 	dir: { | ||||
| 		type: 'dir', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="field checkbox">'+ | ||||
| 				'<span class="text"></span>'+ | ||||
| 				'<input type="file" class="value" nwdirectory />'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return typeof(val) == typeof({}) && 'dir' in val | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			field.find('.value').attr('nwworkingdir', value.dir) | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			var f = $(field).find('.value')[0].files | ||||
| 			if(f.length == 0){ | ||||
| 				return '' | ||||
| 			} | ||||
| 			return f[0].path | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	// NOTE: this will not work without node-webkit...
 | ||||
| 	// format: 
 | ||||
| 	// 		{ dir: <default-path> }
 | ||||
| 	// XXX add datalist option...
 | ||||
| 	ndir: { | ||||
| 		type: 'ndir', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="field dir">'+ | ||||
| 				'<span class="text"></span>'+ | ||||
| 				'<input type="text" class="path"/>'+ | ||||
| 				'<button class="browse">Browse</button>'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return typeof(val) == typeof({}) && 'ndir' in val | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			var that = this | ||||
| 
 | ||||
| 			// NOTE: we are attaching the file browser to body to avoid 
 | ||||
| 			// 		click events on it closing the dialog...
 | ||||
| 			// 		...for some reason stopPropagation(...) does not do 
 | ||||
| 			// 		the job...
 | ||||
| 			var file = $('<input type="file" class="value" nwdirectory/>') | ||||
| 				.attr('nwworkingdir', value.ndir) | ||||
| 				.change(function(){ | ||||
| 					var p = file[0].files | ||||
| 					if(p.length != 0){ | ||||
| 						field.find('.path').val(p[0].path) | ||||
| 					} | ||||
| 					file.detach() | ||||
| 					// focus+select the path field...
 | ||||
| 					// NOTE: this is here to enable fast select-open 
 | ||||
| 					// 		keyboard cycle (tab, enter, <select path>, 
 | ||||
| 					// 		enter, enter)...
 | ||||
| 					field.find('.path') | ||||
| 						.focus() | ||||
| 						.select() | ||||
| 				}) | ||||
| 				.hide() | ||||
| 			field.find('.path').val(value.ndir) | ||||
| 
 | ||||
| 			field.find('.browse').click(function(){ | ||||
| 				file | ||||
| 					// load user input path...
 | ||||
| 					.attr('nwworkingdir', field.find('.path').val()) | ||||
| 					.appendTo($('body')) | ||||
| 					.click() | ||||
| 			}) | ||||
| 
 | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			return field.find('.path').val() | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	// format: 
 | ||||
| 	// 		['a', 'b', 'c', ...]
 | ||||
| 	//
 | ||||
| 	// an item can be of the folowing format:
 | ||||
| 	// 		<text> ['|' 'default' | 'disabled' ] [ '|' <tool-tip> ]
 | ||||
| 	//
 | ||||
| 	// NOTE: only one 'default' item should be present.
 | ||||
| 	// NOTE: if no defaults are set, then the first item is checked.
 | ||||
| 	choice: { | ||||
| 		type: 'choice', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="field choice">'+ | ||||
| 				'<span class="text"></span>'+ | ||||
| 				'<div class="item"><label>'+ | ||||
| 					'<input type="radio" class="value"/>'+ | ||||
| 					'<span class="item-text"></span>'+ | ||||
| 				'</label></div>'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return typeof(val) == typeof([]) && val.constructor.name == 'Array' | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			var t = field.find('.text').html() | ||||
| 			t = t == '' ? Math.random()+'' : t | ||||
| 			var item = field.find('.item').last() | ||||
| 			for(var i=0; i < value.length; i++){ | ||||
| 				// get options...
 | ||||
| 				var opts = value[i] | ||||
| 					.split(/\|/g) | ||||
| 					.map(function(e){ return e.trim() }) | ||||
| 
 | ||||
| 				var val = item.find('.value') | ||||
| 				val.val(opts[0]) | ||||
| 
 | ||||
| 				// set checked state...
 | ||||
| 				if(opts.slice(1).indexOf('default') >= 0){ | ||||
| 					val.prop('checked', true) | ||||
| 					opts.splice(opts.indexOf('default'), 1) | ||||
| 				} else { | ||||
| 					val.prop('checked', false) | ||||
| 				} | ||||
| 
 | ||||
| 				// set disabled state...
 | ||||
| 				if(opts.slice(1).indexOf('disabled') >= 0){ | ||||
| 					val.prop('disabled', true) | ||||
| 					opts.splice(opts.indexOf('disabled'), 1) | ||||
| 					item.addClass('disabled') | ||||
| 				} else { | ||||
| 					val.prop('disabled', false) | ||||
| 					item.removeClass('disabled') | ||||
| 				} | ||||
| 
 | ||||
| 				setTextWithTooltip(opts, item.find('.item-text')) | ||||
| 
 | ||||
| 				item.appendTo(field) | ||||
| 
 | ||||
| 				item = item.clone() | ||||
| 			} | ||||
| 			var values = field.find('.value') | ||||
| 				.attr('name', t) | ||||
| 			// set the default...
 | ||||
| 			if(values.filter(':checked:not([disabled])').length == 0){ | ||||
| 				values.filter(':not([disabled])').first() | ||||
| 					.prop('checked', true) | ||||
| 			} | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			return $(field).find('.value:checked').val() | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	// format: 
 | ||||
| 	// 	{ 
 | ||||
| 	// 		select: ['a', 'b', 'c', ...] 
 | ||||
| 	// 		// default option (optional)...
 | ||||
| 	// 		default: <number> | <text>
 | ||||
| 	// 	}
 | ||||
| 	select: { | ||||
| 		type: 'select', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="field choice">'+ | ||||
| 				'<span class="text"></span>'+ | ||||
| 				'<select>'+ | ||||
| 					'<option class="option"></option>'+ | ||||
| 				'</select>'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return 'select' in val | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			var t = field.find('.text').text() | ||||
| 			var item = field.find('.option').last() | ||||
| 			var select = field.find('select') | ||||
| 			for(var i=0; i < value.select.length; i++){ | ||||
| 				item | ||||
| 					.html(value.select[i]) | ||||
| 					.val(value.select[i]) | ||||
| 				item.appendTo(select) | ||||
| 
 | ||||
| 				item = item.clone() | ||||
| 			} | ||||
| 			if(value.default != null){ | ||||
| 				if(typeof(value.default) == typeof(123)){ | ||||
| 					field.find('.option') | ||||
| 						.eq(value.default) | ||||
| 							.attr('selected', '') | ||||
| 				} else { | ||||
| 					field.find('.option[value="'+ value.default +'"]') | ||||
| 						.attr('selected', '') | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			return $(field).find('.option:selected').val() | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	// NOTE: a button can have state...
 | ||||
| 	// format: 
 | ||||
| 	// 	{ 
 | ||||
| 	// 		// click event handler...
 | ||||
| 	// 		button: <function>, 
 | ||||
| 	// 		// optional, button text (default 'OK')...
 | ||||
| 	// 		text: <button-label>,
 | ||||
| 	// 		// optional, initial state setup...
 | ||||
| 	// 		default: <function>,
 | ||||
| 	// 	}
 | ||||
| 	button: { | ||||
| 		type: 'button', | ||||
| 		text: null, | ||||
| 		default: false, | ||||
| 		html: '<div class="field button">'+ | ||||
| 				'<span class="text"></span>'+ | ||||
| 				'<button class="button"></button>'+ | ||||
| 			'</div>', | ||||
| 		test: function(val){ | ||||
| 			return 'button' in val | ||||
| 		}, | ||||
| 		set: function(field, value){ | ||||
| 			var btn = $(field).find('button') | ||||
| 				.click(value.button) | ||||
| 				.html(value.text == null ? 'OK' : value.text) | ||||
| 			if('default' in value){ | ||||
| 				value.default(btn) | ||||
| 			} | ||||
| 		}, | ||||
| 		get: function(field){  | ||||
| 			return $(field).attr('state') | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| // Show a complex form dialog
 | ||||
| //
 | ||||
| // This will build a form and collect it's data on "accept" specified by
 | ||||
| // the config object...
 | ||||
| //
 | ||||
| // config format:
 | ||||
| //	{
 | ||||
| //		// simple field...
 | ||||
| //		<field-description>: <default-value>,
 | ||||
| //
 | ||||
| //		...
 | ||||
| //	}	
 | ||||
| //
 | ||||
| // <field-description> and split in two with a "|" the section before will
 | ||||
| // show as the field text and the text after as the tooltip.
 | ||||
| // Example:
 | ||||
| // 		"field text | field tooltip..."
 | ||||
| //
 | ||||
| // field's default value determines it's type:
 | ||||
| // 	bool		- checkbox
 | ||||
| // 	string		- textarea
 | ||||
| //
 | ||||
| // see FIELD_TYPES for supported field types.
 | ||||
| //
 | ||||
| // NOTE: if btn is set to false explicitly then no button will be 
 | ||||
| // 		rendered in the form dialog.
 | ||||
| // NOTE: to include a literal "|" in <field-description> just escape it
 | ||||
| // 		like this: "\|"
 | ||||
| //
 | ||||
| // XXX add form testing...
 | ||||
| // XXX add undefined field handling/reporting...
 | ||||
| function formDialog(root, message, config, btn, cls){ | ||||
| 	cls = cls == null ? '' : cls | ||||
| 	btn = btn == null ? 'OK' : btn | ||||
| 	root = root == null ? $('.viewer') : root | ||||
| 
 | ||||
| 	var form = $('<div class="form"/>') | ||||
| 	var data = {} | ||||
| 	var res = $.Deferred() | ||||
| 
 | ||||
| 	// handle message and btn...
 | ||||
| 	if(message.trim().length > 0){ | ||||
| 		setTextWithTooltip(message, $('<div class="text"/>')) | ||||
| 			.appendTo(form) | ||||
| 	} | ||||
| 
 | ||||
| 	// build the form...
 | ||||
| 	for(var t in config){ | ||||
| 		var did_handling = false | ||||
| 		for(var f in FIELD_TYPES){ | ||||
| 			if(FIELD_TYPES[f].test(config[t])){ | ||||
| 				var field = FIELD_TYPES[f] | ||||
| 				var html = $(field.html) | ||||
| 
 | ||||
| 				// setup text and data...
 | ||||
| 				setTextWithTooltip(t, html.find('.text'), html) | ||||
| 
 | ||||
| 				if(field.set != null){ | ||||
| 					field.set(html, config[t]) | ||||
| 				} | ||||
| 
 | ||||
| 				if(field.get != null){ | ||||
| 					// NOTE: this is here to isolate t and field.get values...
 | ||||
| 					// 		...is there a better way???
 | ||||
| 					var _ = (function(title, getter){ | ||||
| 						html.on('resolve', function(evt, e){ | ||||
| 							data[title] = getter(e) | ||||
| 						}) | ||||
| 					})(t, field.get) | ||||
| 				} | ||||
| 
 | ||||
| 				form.append(html) | ||||
| 
 | ||||
| 				did_handling = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// handle unresolved fields...
 | ||||
| 		if(!did_handling){ | ||||
| 			console.warn('formDialog: not all fields understood.') | ||||
| 			// XXX skipping field...
 | ||||
| 			// XXX
 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// add button...
 | ||||
| 	if(btn !== false){ | ||||
| 		var button = $('<button class="accept">'+btn+'</button>') | ||||
| 		form.append(button) | ||||
| 	} else { | ||||
| 		var button = null | ||||
| 	} | ||||
| 
 | ||||
| 	var overlay = showInOverlay(root, form) | ||||
| 		.addClass('dialog ' + cls) | ||||
| 		.on('accept', function(){ | ||||
| 			form.find('.field').each(function(_, e){ | ||||
| 				$(e).trigger('resolve', [$(e)]) | ||||
| 			}) | ||||
| 
 | ||||
| 			// XXX test if all required stuff is filled...
 | ||||
| 			res.resolve(data, form) | ||||
| 
 | ||||
| 			hideOverlay(root) | ||||
| 		}) | ||||
| 		.on('close', function(){ | ||||
| 			res.reject() | ||||
| 
 | ||||
| 		}) | ||||
| 
 | ||||
| 	if(button != null){ | ||||
| 		button.click(function(){ | ||||
| 			overlay.trigger('accept') | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	// focus an element...
 | ||||
| 	// NOTE: if first element is a radio button set, focus the checked
 | ||||
| 	//		element, else focus the first input...
 | ||||
| 	form.ready(function(){  | ||||
| 		// NOTE: we are using a timeout to avoid the user input that opened
 | ||||
| 		// 		the dialog to end up in the first field...
 | ||||
| 		setTimeout(function(){ | ||||
| 			var elem = form.find('.field input').first() | ||||
| 			if(elem.attr('type') == 'radio'){ | ||||
| 				form.find('.field input:checked') | ||||
| 					.focus() | ||||
| 					.select() | ||||
| 			} else { | ||||
| 				elem | ||||
| 					.focus() | ||||
| 					.select() | ||||
| 			} | ||||
| 		}, 100) | ||||
| 	}) | ||||
| 
 | ||||
| 	return res | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /************************************************ Standard dialogs ***/ | ||||
| // NOTE: these return a deferred that will reflect the state of the 
 | ||||
| // 		dialog, and the progress of the operations that it riggers...
 | ||||
| //
 | ||||
| // XXX might be a good idea to be able to block the ui (overlay + progress
 | ||||
| // 		bar?) until some long/critical operations finish, to prevent the
 | ||||
| // 		user from breaking things while the ui is inconsistent...
 | ||||
| 
 | ||||
| var _alert = alert | ||||
| function alert(){ | ||||
| 	var message = Array.apply(null, arguments).join(' ') | ||||
| 	return formDialog(null, String(message), {}, false, 'alert') | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| var _prompt = prompt | ||||
| function prompt(message, dfl, btn){ | ||||
| 	btn = btn == null ? 'OK' : btn | ||||
| 	var res = $.Deferred() | ||||
| 	formDialog(null, message, {'': ''+(dfl == null ? '' : dfl)}, btn, 'prompt') | ||||
| 		.done(function(data){ res.resolve(data['']) }) | ||||
| 		.fail(function(){ res.reject() }) | ||||
| 	return res | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* | ||||
| function confirm(){ | ||||
| } | ||||
| */ | ||||
| 
 | ||||
| 
 | ||||
| function detailedAlert(text, description, button){ | ||||
| 	return formDialog(null, '', {'': { | ||||
| 		html: $('<details/>') | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user