From 88d1bcc4ba5ad912e5b06759bd85bc35b0096e39 Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Thu, 30 Jan 2014 05:56:27 +0400 Subject: [PATCH] split out the generic dialogs.js + some minor tweaks... Signed-off-by: Alex A. Naanou --- ui/TODO.otl | 102 ++++---- ui/files.js | 2 + ui/index.html | 1 + ui/lib/dialogs.js | 632 ++++++++++++++++++++++++++++++++++++++++++++++ ui/ui.js | 611 +------------------------------------------- 5 files changed, 688 insertions(+), 660 deletions(-) create mode 100755 ui/lib/dialogs.js diff --git a/ui/TODO.otl b/ui/TODO.otl index 766c1f4e..0e5cd935 100755 --- a/ui/TODO.otl +++ b/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: diff --git a/ui/files.js b/ui/files.js index 09d0c62b..0203747d 100755 --- a/ui/files.js +++ b/ui/files.js @@ -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. // diff --git a/ui/index.html b/ui/index.html index 64e21914..c73e302c 100755 --- a/ui/index.html +++ b/ui/index.html @@ -20,6 +20,7 @@ + diff --git a/ui/lib/dialogs.js b/ui/lib/dialogs.js new file mode 100755 index 00000000..01d24d2e --- /dev/null +++ b/ui/lib/dialogs.js @@ -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){ + $(' *') + .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 $('
'+ + '
'+ + '
'+ + '
').appendTo(root) + } + return overlay +} + + +function showInOverlay(root, data){ + root = $(root) + + var overlay = getOverlay(root) + + + if(data != null){ + var container = $('
'+ + '
'+ + '
') + 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: '
', + test: function(val){ + return /\-\-\-+/.test(val) + }, + }, + // a simple br... + // + // format: + // ' ' + // Three or more spaces + br: { + type: 'br', + text: null, + default: false, + html: '
', + test: function(val){ + return /\s\s\s+/.test(val) + }, + }, + // format: + // { + // html: + // } + html: { + type: 'html', + text: null, + default: false, + html: '
', + 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: '
'+ + ''+ + ''+ + '
', + 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: '
'+ + ''+ + '
', + 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: } + dir: { + type: 'dir', + text: null, + default: false, + html: '
'+ + ''+ + ''+ + '
', + 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: } + // XXX add datalist option... + ndir: { + type: 'ndir', + text: null, + default: false, + html: '
'+ + ''+ + ''+ + ''+ + '
', + 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 = $('') + .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, '+ + ''+ + '
'+ + '
', + 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: | + // } + select: { + type: 'select', + text: null, + default: false, + html: '
'+ + ''+ + ''+ + '
', + 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: , + // // optional, button text (default 'OK')... + // text: , + // // optional, initial state setup... + // default: , + // } + button: { + type: 'button', + text: null, + default: false, + html: '
'+ + ''+ + ''+ + '
', + 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... +// : , +// +// ... +// } +// +// 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 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 = $('
') + var data = {} + var res = $.Deferred() + + // handle message and btn... + if(message.trim().length > 0){ + setTextWithTooltip(message, $('
')) + .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 = $('') + 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 : */ diff --git a/ui/ui.js b/ui/ui.js index 8689a3e2..602b2a38 100755 --- a/ui/ui.js +++ b/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){ - $(' *') - .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 $('
'+ - '
'+ - '
'+ - '
').appendTo(root) - } - return overlay -} - - -function showInOverlay(root, data){ - root = $(root) - - var overlay = getOverlay(root) - - - if(data != null){ - var container = $('
'+ - '
'+ - '
') - 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: '
', - test: function(val){ - return /\-\-\-+/.test(val) - }, - }, - // a simple br... - // - // format: - // ' ' - // Three or more spaces - br: { - type: 'br', - text: null, - default: false, - html: '
', - test: function(val){ - return /\s\s\s+/.test(val) - }, - }, - // format: - // { - // html: - // } - html: { - type: 'html', - text: null, - default: false, - html: '
', - 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: '
'+ - ''+ - ''+ - '
', - 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: '
'+ - ''+ - '
', - 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: } - dir: { - type: 'dir', - text: null, - default: false, - html: '
'+ - ''+ - ''+ - '
', - 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: } - // XXX add datalist option... - ndir: { - type: 'ndir', - text: null, - default: false, - html: '
'+ - ''+ - ''+ - ''+ - '
', - 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 = $('') - .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, '+ - ''+ - '
'+ - '
', - 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: | - // } - select: { - type: 'select', - text: null, - default: false, - html: '
'+ - ''+ - ''+ - '
', - 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: , - // // optional, button text (default 'OK')... - // text: , - // // optional, initial state setup... - // default: , - // } - button: { - type: 'button', - text: null, - default: false, - html: '
'+ - ''+ - ''+ - '
', - 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... -// : , -// -// ... -// } -// -// 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 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 = $('
') - var data = {} - var res = $.Deferred() - - // handle message and btn... - if(message.trim().length > 0){ - setTextWithTooltip(message, $('
')) - .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 = $('') - 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: $('
')