From eb999c47c6bdef073676802492e97378fb839e1f Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 17:54:55 -0500 Subject: [PATCH] Switch to comboboxes --- combobox.html | 16 ++-- css/app.css | 46 +++++----- index.html | 1 + js/id/ui/inspector.js | 29 +++--- js/id/ui/preset.js | 14 ++- js/lib/d3.combobox.js | 199 +++++++++++++++++++++++++++--------------- 6 files changed, 184 insertions(+), 121 deletions(-) diff --git a/combobox.html b/combobox.html index 0d97698b0..fbe6ae058 100644 --- a/combobox.html +++ b/combobox.html @@ -140,21 +140,19 @@
-
+ diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 6ab4f512a..ca0b61707 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -11,14 +11,14 @@ iD.ui.inspector = function() { var entity = selection.datum(); var iwrap = selection.append('div') - .attr('class','inspector content'); + .attr('class','inspector content'), + head = iwrap.append('div') + .attr('class', 'head inspector-inner fillL'), + h2 = head.append('h2'); - var head = iwrap.append('div') - .attr('class', 'head inspector-inner fillL'); - - var h2 = head.append('h2'); h2.append('span') .attr('class', 'icon big icon-pre-text big-' + entity.geometry(graph)); + var name = h2.append('input') .attr('placeholder', 'name') .property('value', function() { @@ -150,14 +150,18 @@ iD.ui.inspector = function() { var inputs = row.append('div') .attr('class', 'input-wrap'); - inputs.append('input') + inputs.append('span') + .attr('class', 'key-wrap') + .append('input') .property('type', 'text') .attr('class', 'key') .attr('maxlength', 255) .property('value', function(d) { return d.key; }) .on('change', function(d) { d.key = this.value; event.change(); }); - inputs.append('input') + inputs.append('span') + .attr('class', 'input-wrap-position') + .append('input') .property('type', 'text') .attr('class', 'value') .attr('maxlength', 255) @@ -253,7 +257,7 @@ iD.ui.inspector = function() { geometry = entity.geometry(graph), row = d3.select(this), key = row.selectAll('.key'), - value = row.selectAll('.value'); + value = row.selectAll('.input-wrap-position'); function sort(value, data) { var sameletter = [], @@ -278,14 +282,15 @@ iD.ui.inspector = function() { }); }, 500))); - value.call(d3.typeahead() - .data(_.debounce(function(_, callback) { + var valueinput = value.select('input'); + value.call(d3.combobox() + .fetcher(_.debounce(function(_, __, callback) { taginfo.values({ key: key.property('value'), geometry: geometry, - query: value.property('value') + query: valueinput.property('value') }, function(err, data) { - if (!err) callback(sort(value.property('value'), data)); + if (!err) callback(sort(valueinput.property('value'), data)); }); }, 500))); } diff --git a/js/id/ui/preset.js b/js/id/ui/preset.js index 73885de38..a4b77078e 100644 --- a/js/id/ui/preset.js +++ b/js/id/ui/preset.js @@ -76,14 +76,12 @@ iD.ui.preset = function() { }); break; case 'select': - i = this.append('select'); - var options = d.values.slice(); - options.unshift(''); - i.selectAll('option') - .data(options) - .enter() - .append('option') - .text(String); + var w = this.append('span').attr('class', 'input-wrap-position'); + i = w.append('input'); + w.call(d3.combobox() + .data([''].concat(d.values.slice()).map(function(o) { + return { value: o, title: o }; + }))); break; } if (i) { diff --git a/js/lib/d3.combobox.js b/js/lib/d3.combobox.js index 165456563..0273856d2 100644 --- a/js/lib/d3.combobox.js +++ b/js/lib/d3.combobox.js @@ -1,16 +1,21 @@ d3.combobox = function() { var event = d3.dispatch('accept'), - autohighlight = false, - autofilter = false, - input, - container, - data; + container, input, shown = false, data = []; + + var fetcher = function(val, data, cb) { + cb(data.filter(function(d) { + return d.title + .toString() + .toLowerCase() + .indexOf(val.toLowerCase()) !== -1; + })); + }; var typeahead = function(selection) { - var hidden, idx = autohighlight ? 0 : -1; - - var rect = selection.select('input').node().getBoundingClientRect(); - + var idx = -1, + rect = selection.select('input') + .node() + .getBoundingClientRect(); input = selection.select('input'); container = selection @@ -25,104 +30,174 @@ d3.combobox = function() { }); carat = selection - .insert('div', ':first-child') + .insert('a', ':first-child') .attr('class', 'combobox-carat') - .text('+') .style({ position: 'absolute', - left: (rect.width - 20) + 'px', + left: rect.width + 'px', top: '0px' }) - .on('click', function() { - update(); - show(); - }); + .on('mousedown', stop) + .on('click', click); - selection - .on('keyup.typeahead', key); + function stop() { + // prevent the form element from blurring. it blurs + // on mousedown + d3.event.stopPropagation(); + d3.event.preventDefault(); + } - hidden = false; + function click() { + d3.event.preventDefault(); + d3.event.stopPropagation(); + update(); + show(); + // focus the node so that a click outside of the + // combo box will hide it + input.node().focus(); + } - function hide() { - idx = autohighlight ? 0 : -1; - hidden = true; + function blur() { + // hide the combobox whenever the input element + // loses focus + slowHide(); } function show() { container.style('display', 'block'); + shown = true; + } + + function hide() { + idx = -1; + container.style('display', 'none'); + shown = false; } function slowHide() { - if (autohighlight && container.select('a.selected').node()) { - select(container.select('a.selected').datum()); - event.accept(); - } window.setTimeout(hide, 150); } - - selection - .on('focus.typeahead', show) - .on('blur.typeahead', slowHide); - - function key() { - var len = container.selectAll('a').data().length; - if (d3.event.keyCode === 40) { - idx = Math.min(idx + 1, len - 1); - return highlight(); - } else if (d3.event.keyCode === 38) { - idx = Math.max(idx - 1, 0); - return highlight(); - } else if (d3.event.keyCode === 13) { - if (container.select('a.selected').node()) { - select(container.select('a.selected').datum()); - } - event.accept(); - hide(); - } else { - update(); + function keydown() { + if (!shown) return; + switch (d3.event.keyCode) { + // down arrow + case 40: + next(); + d3.event.preventDefault(); + break; + // up arrow + case 38: + prev(); + d3.event.preventDefault(); + break; + // escape, tab + case 9: + case 13: + d3.event.preventDefault(); + break; } + d3.event.stopPropagation(); + } + + function keyup() { + switch (d3.event.keyCode) { + // escape + case 27: + hide(); + break; + // escape, tab + case 9: + case 13: + if (!shown) return; + accept(); + break; + default: + update(); + d3.event.preventDefault(); + } + d3.event.stopPropagation(); + } + + function accept() { + if (container.select('a.selected').node()) { + select(container.select('a.selected').datum()); + } + hide(); + } + + function next() { + var len = container.selectAll('a').data().length; + idx = Math.min(idx + 1, len - 1); + highlight(); + } + + function prev() { + idx = Math.max(idx - 1, 0); + highlight(); } function highlight() { container .selectAll('a') .classed('selected', function(d, i) { return i == idx; }); + var height = container.node().offsetHeight, + top = container.select('a.selected').node().offsetTop, + selectedHeight = container.select('a.selected').node().offsetHeight; + if ((top + selectedHeight) < height) { + container.node().scrollTop = 0; + } else { + container.node().scrollTop = top; + } } function update() { - function run(data) { - container.style('display', function() { - return data.length ? 'block' : 'none'; - }); + function render(data) { + if (data.length) show(); + else hide(); var options = container - .selectAll('a') + .selectAll('a.combobox-option') .data(data, function(d) { return d.value; }); options.enter() .append('a') .text(function(d) { return d.value; }) + .attr('class', 'combobox-option') .attr('title', function(d) { return d.title; }) .on('click', select); options.exit().remove(); options - .classed('selected', function(d, i) { return i == idx; }); + .classed('selected', function(d, i) { return i == idx; }) + .order(); } - if (typeof data === 'function') data(selection, run); - else run(data); + fetcher.apply(selection, [ + selection.select('input').property('value'), + data, render]); } + // select the choice given as d function select(d) { input .property('value', d.value) .trigger('change'); - container.style('display', 'none'); + event.accept(d); + hide(); } + + input + .on('blur.typeahead', blur) + .on('keydown.typeahead', keydown) + .on('keyup.typeahead', keyup); + }; + typeahead.fetcher = function(_) { + if (!arguments.length) return fetcher; + fetcher = _; + return typeahead; }; typeahead.data = function(_) { @@ -131,17 +206,5 @@ d3.combobox = function() { return typeahead; }; - typeahead.autofilter = function(_) { - if (!arguments.length) return autofilter; - autofilter = _; - return typeahead; - }; - - typeahead.autohighlight = function(_) { - if (!arguments.length) return autohighlight; - autohighlight = _; - return typeahead; - }; - return d3.rebind(typeahead, event, 'on'); };