diff --git a/ui (gen4)/experiments/browse-dialog.html b/ui (gen4)/experiments/browse-dialog.html index 64232b27..bbe572e4 100755 --- a/ui (gen4)/experiments/browse-dialog.html +++ b/ui (gen4)/experiments/browse-dialog.html @@ -122,6 +122,9 @@ + + + @@ -796,6 +513,8 @@ $(function(){ +
+
diff --git a/ui (gen4)/experiments/browse-dialog.js b/ui (gen4)/experiments/browse-dialog.js new file mode 100755 index 00000000..e4d085cc --- /dev/null +++ b/ui (gen4)/experiments/browse-dialog.js @@ -0,0 +1,346 @@ +/********************************************************************** +* +* +* +**********************************************************************/ + +//var DEBUG = DEBUG != null ? DEBUG : true + + +/*********************************************************************/ + +// 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 = $('
') + .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($('
') + .addClass('v-block path')) + } + + browser + .append($('
') + .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: +// - opne +// - update +// - select (???) +// XXX add "current selection" to the path... +var BrowserPrototype = { + dom: null, + + options: { + //path: null, + //show_path: null, + }, + + // XXX this should prevent event handler deligation... + keyboard: { + '.browse':{ + Up: 'prev', + Backspace: 'Up', + Down: 'next', + Left: 'pop', + Right: 'push', + + Enter: 'action', + Esc: 'close', + }, + }, + + // 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 + return this.dom.find('.path .dir') + .map(function(i, e){ return $(e).text() }) + .toArray() + }, + set path(value){ + console.log('!!!', value) + // XXX normalize path... + return this.update(value) + }, + + // update path... + // XXX trigger an "update" event... + update: function(path){ + var browser = this.dom + var that = this + + var p = browser.find('.path').empty() + var l = browser.find('.list').empty() + + // fill the path field... + path.forEach(function(e){ + p.append($('
') + .addClass('dir') + .click(function(){ that.pop() }) + .text(e)) + }) + + // fill the children list... + this.list(path) + .forEach(function(e){ + l.append($('
') + .click(function(){ that.push() }) + .text(e)) + }) + + return this + }, + + // internal actions... + + // Select a list element... + // + // Select first/last child + // .select('first') + // .select('last') + // -> elem + // + // Select previous/lext child + // .select('prev') + // .select('next') + // -> elem + // + // Deselect + // .select('none') + // -> elem + // + // Get selected element if it exists, null otherwise... + // .select('!') + // -> elem + // -> $() + // + // Select element by sequence number + // .select() + // -> elem + // + // Select element by its text... + // .select('""') + // -> elem + // + // .select() + // -> elem + // + // This will return a jQuery object. + // + // + // XXX revise return values... + // XXX Q: should this trigger a "select" event??? + select: function(elem){ + var browser = this.dom + var elems = browser.find('.list div') + + if(elems.length == 0){ + return $() + } + + elem = elem || this.select('!') + // if none selected get the first... + elem = elem.length == 0 ? 'first' : elem + + // first/last... + if(elem == 'first' || elem == 'last'){ + return this.select(elems[elem]()) + + // prev/next... + } else if(elem == 'prev' || elem == 'next'){ + var to = this.select('!', browser)[elem]('.list div') + if(to.length == 0){ + return this.select(elem == 'prev' ? 'last' : 'first', browser) + } + this.select('none') + return this.select(to) + + // deselect... + } else if(elem == 'none'){ + return elems + .filter('.selected') + .removeClass('selected') + + // strict... + } else if(elem == '!'){ + return elems.filter('.selected') + + // number... + } else if(typeof(elem) == typeof(123)){ + return this.select($(elems[elem])) + + // string... + } else if(typeof(elem) == typeof('str') + && /^'.*'$|^".*"$/.test(elem.trim())){ + elem = elem.trim().slice(1, -1) + return this.select(browser.find('.list div') + .filter(function(i, e){ + return $(e).text() == elem + })) + + // element... + } else { + this.select('none') + return elem.addClass('selected') + } + }, + + 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 + }, + // pop an element off the path / go up one level... + pop: function(){ + var browser = this.dom + var path = this.path + var dir = path.pop() + + this.update(path) + + this.select('"'+dir+'"') + + return this + }, + next: function(elem){ + if(elem != null){ + this.select(elem) + } + this.select('next') + return this + }, + prev: function(elem){ + if(elem != null){ + this.select(elem) + } + this.select('prev') + return this + }, + + focus: function(){ + this.dom.focus() + return thsi + }, + + // 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 + } + + var path = this.path.push(elem.text()) + + var res = this.open(path) + + return res + }, + + // extension methods... + open: function(path){ + var m = this.options.list + return m ? m.call(this, path) : path + }, + list: function(path){ + var m = this.options.list + return m ? m.call(this, path) : path + }, + isTraversable: null, + + // XXX need to get a container.... + // XXX prepare/merge options... + // XXX setup instance events... + __init__: function(parent, options){ + // XXX merge options... + // XXX + this.options = options + + // build the dom... + var dom = this.dom = this.constructor.make(options) + + // add keyboard handler... + dom.keydown( + keyboard.makeKeyboardHandler( + this.keyboard, + // XXX + function(k){ window.DEBUG && console.log(k) }, + 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 : */