diff --git a/Makefile b/Makefile index 9c7e33783..125957615 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ all: \ js/lib/d3.keybinding.js \ js/lib/d3.one.js \ js/lib/d3.size.js \ + js/lib/d3.trigger.js \ js/lib/d3.typeahead.js \ js/lib/jxon.js \ js/lib/lodash.js \ diff --git a/css/app.css b/css/app.css index bc637da8c..cd810678b 100644 --- a/css/app.css +++ b/css/app.css @@ -489,7 +489,7 @@ button.Browse .label { .tag-row input { width: 50%; - border-right: 0; + border-left: 0; } .tag-row input.key { @@ -500,9 +500,8 @@ button.Browse .label { border-top: 1px solid #ccc; } -.tag-row input.value { - position: relative; - border-right: 1px solid #ccc; +.tag-row input.key { + border-left: 1px solid #ccc; } .input-wrap::after { @@ -531,8 +530,16 @@ button.Browse .label { border-top: 1px solid #ccc; } -.tag-row-empty button { - display: none; +.inspector-inner .add-tag-row { + width: 100%; + padding-right: 70px; +} + +.inspector-inner .add-tag { + width: 50%; + height: 30px; + border: 1px solid #ccc; + border-top: 0; } /* Map Controls */ diff --git a/index.html b/index.html index ae1d3570b..b0153fd9a 100644 --- a/index.html +++ b/index.html @@ -20,6 +20,7 @@ + diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 10ce265d0..db9377d94 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -2,7 +2,7 @@ iD.Inspector = function() { var event = d3.dispatch('changeTags', 'changeWayDirection', 'update', 'remove', 'close', 'splitWay'), taginfo = iD.taginfo(), - inspectorwrap; + tagList; function inspector(selection) { var entity = selection.datum(); @@ -21,12 +21,22 @@ iD.Inspector = function() { var inspectorbody = selection.append('div') .attr('class', 'inspector-body'); - inspectorwrap = inspectorbody.append('ul') + var inspectorwrap = inspectorbody.append('div') .attr('class', 'inspector-inner tag-wrap fillL2'); inspectorwrap.append('h4') .text('Edit tags'); + tagList = inspectorwrap.append('ul'); + + inspectorwrap.append('div').attr('class', 'add-tag-row').append('button') + .attr('class', 'add-tag') + .text('+ Add New Tag') + .on('click', function() { + addTag(); + tagList.selectAll('li:last-child input.key').node().focus(); + }); + var formsel = drawTags(entity.tags); inspectorbody.append('div') @@ -99,27 +109,33 @@ iD.Inspector = function() { function drawTags(tags) { tags = d3.entries(tags); - tags.push({ key: '', value: ''}); - var li = inspectorwrap.selectAll('li') + if (!tags.length) { + tags = [{key: '', value: ''}]; + } + + var li = tagList.selectAll('li') .data(tags, function(d) { return d.key; }); li.exit().remove(); - var row = li.enter().append('li').attr('class','tag-row'); - var inputs = row.append('div').attr('class','input-wrap'); + var row = li.enter().append('li') + .attr('class', 'tag-row'); - li.classed('tag-row-empty', function(d) { return d.key === ''; }); + var inputs = row.append('div') + .attr('class', 'input-wrap'); inputs.append('input') .property('type', 'text') .attr('class', 'key') - .property('value', function(d) { return d.key; }); + .property('value', function(d) { return d.key; }) + .on('change', function(d) { d.key = this.value; }); inputs.append('input') .property('type', 'text') .attr('class', 'value') .property('value', function(d) { return d.value; }) + .on('change', function(d) { d.value = this.value; }) .on('keydown.push-more', pushMore); inputs.each(bindTypeahead); @@ -129,7 +145,8 @@ iD.Inspector = function() { .attr('class','remove minor') .on('click', removeTag); - removeBtn.append('span').attr('class', 'icon remove'); + removeBtn.append('span') + .attr('class', 'icon remove'); var helpBtn = row.append('button') .attr('tabindex', -1) @@ -160,14 +177,15 @@ iD.Inspector = function() { return 'http://taginfo.openstreetmap.org/keys/' + d.key; }); - helpBtn.append('span').attr('class', 'icon inspect'); + helpBtn.append('span') + .attr('class', 'icon inspect'); return li; } function pushMore() { - if (d3.event.keyCode === 9) { - drawTags(inspector.tags()); + if (d3.event.keyCode === 9 && tagList.selectAll('li:last-child input.value').node() === this) { + addTag(); } } @@ -195,6 +213,12 @@ iD.Inspector = function() { })); } + function addTag() { + var tags = inspector.tags(); + tags[''] = ''; + drawTags(tags); + } + function removeTag(d) { var tags = inspector.tags(); delete tags[d.key]; @@ -208,7 +232,7 @@ iD.Inspector = function() { inspector.tags = function () { var tags = {}; - inspectorwrap.selectAll('li').each(function() { + tagList.selectAll('li').each(function() { var row = d3.select(this), key = row.selectAll('.key').property('value'), value = row.selectAll('.value').property('value'); diff --git a/js/lib/d3.trigger.js b/js/lib/d3.trigger.js new file mode 100644 index 000000000..59b7cd5a5 --- /dev/null +++ b/js/lib/d3.trigger.js @@ -0,0 +1,7 @@ +d3.selection.prototype.trigger = function (type) { + this.each(function() { + var evt = document.createEvent('HTMLEvents'); + evt.initEvent(type, true, true); + this.dispatchEvent(evt); + }); +}; diff --git a/js/lib/d3.typeahead.js b/js/lib/d3.typeahead.js index a93da8877..a7164ca5f 100644 --- a/js/lib/d3.typeahead.js +++ b/js/lib/d3.typeahead.js @@ -36,7 +36,7 @@ d3.typeahead = function() { if (d3.event.keyCode === 40) idx++; if (d3.event.keyCode === 38) idx--; if (d3.event.keyCode === 13) { - selection.property('value', container.select('a.selected').datum().value); + select(container.select('a.selected').datum()); hide(); } @@ -57,11 +57,16 @@ d3.typeahead = function() { .append('a') .text(function(d) { return d.value; }) .attr('title', function(d) { return d.title; }) - .on('click', function(d) { selection.property('value', d.value); }); + .on('click', select); options.exit().remove(); }); } + + function select(d) { + selection.property('value', d.value) + .trigger('change'); + } }; typeahead.data = function(_) { diff --git a/test/index.html b/test/index.html index 3d0a995c3..f45262f73 100644 --- a/test/index.html +++ b/test/index.html @@ -22,6 +22,7 @@ + diff --git a/test/spec/ui/inspector.js b/test/spec/ui/inspector.js index 5c6760354..6d4175ef3 100644 --- a/test/spec/ui/inspector.js +++ b/test/spec/ui/inspector.js @@ -1,15 +1,20 @@ describe("iD.Inspector", function () { var inspector, element, tags = {highway: 'residential'}, - entity = iD.Entity({type: 'node', id: "n12345", tags: tags}); + entity; - beforeEach(function () { + function render() { inspector = iD.Inspector(); element = d3.select('body') .append('div') .attr('id', 'inspector-wrap') .datum(entity) .call(inspector); + } + + beforeEach(function () { + entity = iD.Entity({type: 'node', id: "n12345", tags: tags}); + render(); }); afterEach(function () { @@ -22,9 +27,9 @@ describe("iD.Inspector", function () { }); it("returns updated tags when input values have changed", function () { - element.selectAll(".tag-row-empty input.key").property('value', 'k'); - element.selectAll(".tag-row-empty input.value").property('value', 'v'); - expect(inspector.tags()).to.eql({highway: 'residential', k: 'v'}); + element.selectAll("input.key").property('value', 'k'); + element.selectAll("input.value").property('value', 'v'); + expect(inspector.tags()).to.eql({k: 'v'}); }); }); @@ -33,17 +38,22 @@ describe("iD.Inspector", function () { expect(element.selectAll("input[value=residential]")).not.to.be.empty; }); - it("creates one trailing pair of empty input elements", function () { + it("creates a pair of empty input elements if the entity has no tags", function () { + element.remove(); + entity = entity.update({tags: {}}); + render(); + expect(element.selectAll("input.value").property('value')).to.be.empty; + expect(element.selectAll("input.key").property('value')).to.be.empty; + }); + + it("adds tags when clicking the add button", function () { + element.selectAll("button.add-tag").trigger('click'); expect(element.selectAll("input")[0][2].value).to.be.empty; expect(element.selectAll("input")[0][3].value).to.be.empty; }); - it("sets the 'tag-row-empty' class on the placeholder row", function () { - expect(element.selectAll(".tag-row:last-child").classed('tag-row-empty')).to.be.true; - }); - it("removes tags when clicking the remove button", function () { - happen.click(element.selectAll("button.remove").node()); + element.selectAll("button.remove").trigger('click'); expect(inspector.tags()).to.eql({}); }); @@ -51,7 +61,7 @@ describe("iD.Inspector", function () { var spy = sinon.spy(); inspector.on('close', spy); - happen.click(element.select('.close').node()); + element.select('.close').trigger('click'); expect(spy).to.have.been.calledWith(entity); }); @@ -60,7 +70,7 @@ describe("iD.Inspector", function () { var spy = sinon.spy(); inspector.on('changeTags', spy); - happen.click(element.select('.apply').node()); + element.select('.apply').trigger('click'); expect(spy).to.have.been.calledWith(entity, tags); }); @@ -69,7 +79,7 @@ describe("iD.Inspector", function () { var spy = sinon.spy(); inspector.on('remove', spy); - happen.click(element.select('.delete').node()); + element.select('.delete').trigger('click'); expect(spy).to.have.been.calledWith(entity); });