diff --git a/js/id/ui/disclosure.js b/js/id/ui/disclosure.js index e0ab45584..2370d3bb3 100644 --- a/js/id/ui/disclosure.js +++ b/js/id/ui/disclosure.js @@ -1,47 +1,51 @@ iD.ui.Disclosure = function() { - var title, + var dispatch = d3.dispatch('toggled'), + title, expanded = false, - content = function () {}, - dispatch = d3.dispatch('toggled'), - link, container; - - function toggle() { - expanded = !expanded; - link.classed('expanded', expanded); - container.call(iD.ui.Toggle(expanded)); - dispatch.toggled(expanded); - } + content = function () {}; var disclosure = function(selection) { - link = selection.append('a') + var $link = selection.selectAll('.hide-toggle') + .data([0]); + + $link.enter().append('a') .attr('href', '#') - .attr('class', 'hide-toggle') - .text(title) + .attr('class', 'hide-toggle'); + + $link.text(title) .on('click', toggle) .classed('expanded', expanded); - container = selection.append('div') - .classed('hide', !expanded); + var $body = selection.selectAll('div') + .data([0]); - container.call(content); + $body.enter().append('div'); + + $body.classed('hide', !expanded) + .call(content); + + function toggle() { + expanded = !expanded; + $link.classed('expanded', expanded); + $body.call(iD.ui.Toggle(expanded)); + dispatch.toggled(expanded); + } }; disclosure.title = function(_) { + if (!arguments.length) return title; title = _; - if (link) link.text(_); return disclosure; }; disclosure.expanded = function(_) { - if (link && expanded !== _) { - toggle(); - } else { - expanded = _; - } + if (!arguments.length) return expanded; + expanded = _; return disclosure; }; disclosure.content = function(_) { + if (!arguments.length) return content; content = _; return disclosure; }; diff --git a/js/id/ui/entity_editor.js b/js/id/ui/entity_editor.js index 044345427..bed9d68b9 100644 --- a/js/id/ui/entity_editor.js +++ b/js/id/ui/entity_editor.js @@ -1,127 +1,124 @@ -iD.ui.EntityEditor = function(context, entity) { +iD.ui.EntityEditor = function(context) { var event = d3.dispatch('choose'), - presets = context.presets(), - id = entity.id, - tags = _.clone(entity.tags), - preset, - selection_, - presetUI, - rawTagEditor, - rawMemberEditor, - rawMembershipEditor; + id, + preset; - function browse() { - context.enter(iD.modes.Browse(context)); - } + function entityEditor(selection) { + var entity = context.entity(id), + tags = _.clone(entity.tags); - function update() { - var entity = context.hasEntity(id); - if (!entity) return; + var $header = selection.selectAll('.header') + .data([0]); - tags = _.clone(entity.tags); + // Enter - // change preset if necessary (undos/redos) - var newmatch = presets.match(entity, context.graph()); - if (newmatch !== preset) { - entityEditor(selection_, newmatch); - return; - } - - presetUI.change(tags); - rawTagEditor.tags(tags); - if (rawMemberEditor) rawMemberEditor.change(); - rawMembershipEditor.change(); - } - - function entityEditor(selection, newpreset) { - selection_ = selection; - var geometry = entity.geometry(context.graph()); - - if (!preset) preset = presets.match(entity, context.graph()); - - // preset was explicitly chosen - if (newpreset) { - tags = preset.removeTags(tags, geometry); - - newpreset.applyTags(tags, geometry); - preset = newpreset; - } - - selection.html(''); - - var messagewrap = selection.append('div') + var $enter = $header.enter().append('div') .attr('class', 'header fillL cf'); - messagewrap.append('button') + $enter.append('button') .attr('class', 'preset-reset fl ') - .on('click', function() { - event.choose(preset); - }) .append('span') .attr('class', 'icon back'); - messagewrap.append('h3') - .attr('class', 'inspector-inner') - .text(t('inspector.editing_feature', { feature: preset.name() })); + $enter.append('h3') + .attr('class', 'inspector-inner'); - messagewrap.append('button') + $enter.append('button') .attr('class', 'preset-close fr') - .on('click', browse) .append('span') .attr('class', 'icon close'); - var editorwrap = selection.append('div') + // Update + + $header.select('h3') + .text(t('inspector.editing_feature', {feature: preset.name()})); + + $header.select('.preset-reset') + .on('click', function() { + event.choose(preset); + }); + + $header.select('.preset-close') + .on('click', function() { + context.enter(iD.modes.Browse(context)); + }); + + var $body = selection.selectAll('.inspector-body') + .data([0]); + + // Enter + + $enter = $body.enter().append('div') .attr('class', 'tag-wrap inspector-body fillL2'); - editorwrap.append('div') - .attr('class', 'col12 inspector-inner preset-icon-wrap') + $enter.append('div') + .attr('class', 'preset-icon-wrap inspector-inner col12') .append('div') - .attr('class','fillL') + .attr('class', 'fillL'); + + $enter.append('div') + .attr('class', 'inspector-preset cf fillL col12'); + + $enter.append('div') + .attr('class', 'raw-tag-editor inspector-inner col12'); + + $enter.append('div') + .attr('class', 'raw-member-editor inspector-inner col12'); + + $enter.append('div') + .attr('class', 'raw-membership-editor inspector-inner col12'); + + $enter.append('div') + .attr('class', 'inspector-external-links inspector-inner col12'); + + // Update + + $body.select('.preset-icon-wrap .fillL') .call(iD.ui.PresetIcon() - .geometry(context.geometry(entity.id)) + .geometry(context.geometry(id)) .preset(preset)); - presetUI = iD.ui.preset(context, entity, preset) - .on('change', changeTags); + $body.select('.inspector-preset') + .call(iD.ui.preset(context) + .preset(preset) + .entityID(id) + .tags(tags) + .on('change', changeTags)); - var tageditorpreset = editorwrap.append('div') - .attr('class', 'inspector-preset cf fillL col12') - .call(presetUI); - - rawTagEditor = iD.ui.RawTagEditor(context, entity) - .on('change', changeTags); - - editorwrap.append('div') - .attr('class', 'inspector-inner raw-tag-editor col12') - .call(rawTagEditor, preset.id === 'other'); + $body.select('.raw-tag-editor') + .call(iD.ui.RawTagEditor(context) + .preset(preset) + .entityID(id) + .tags(tags) + .on('change', changeTags)); if (entity.type === 'relation') { - rawMemberEditor = iD.ui.RawMemberEditor(context, entity); - - editorwrap.append('div') - .attr('class', 'inspector-inner raw-membership-editor col12') - .call(rawMemberEditor); + $body.select('.raw-member-editor') + .style('display', 'block') + .call(iD.ui.RawMemberEditor(context) + .entityID(id)); + } else { + $body.select('.raw-member-editor') + .style('display', 'none') } - rawMembershipEditor = iD.ui.RawMembershipEditor(context, entity); + $body.select('.raw-membership-editor') + .call(iD.ui.RawMembershipEditor(context) + .entityID(id)); - editorwrap.append('div') - .attr('class', 'inspector-inner raw-membership-editor col12') - .call(rawMembershipEditor); + $body.select('.inspector-external-links') + .call(iD.ui.ViewOnOSM(context) + .entityID(id)); - var viewOnOSM = iD.ui.ViewOnOSM(context); - - editorwrap.append('div') - .attr('class', 'col12 inspector-inner inspector-external-links') - .call(viewOnOSM, entity); - - presetUI.change(tags); - rawTagEditor.tags(tags); - - changeTags(); + function historyChanged() { + var entity = context.hasEntity(id); + if (!entity) return; + preset = context.presets().match(entity, context.graph()); + entityEditor(selection); + } context.history() - .on('change.entity-editor', update); + .on('change.entity-editor', historyChanged); } function clean(o) { @@ -134,15 +131,40 @@ iD.ui.EntityEditor = function(context, entity) { } function changeTags(changed) { - tags = clean(_.extend(tags, changed)); - var entity = context.hasEntity(id); - if (entity && !_.isEqual(entity.tags, tags)) { + var entity = context.entity(id), + tags = clean(_.extend({}, entity.tags, changed)); + + if (!_.isEqual(entity.tags, tags)) { context.perform( - iD.actions.ChangeTags(entity.id, tags), + iD.actions.ChangeTags(id, tags), t('operations.change_tags.annotation')); } } + entityEditor.entityID = function(_) { + if (!arguments.length) return id; + id = _; + preset = context.presets().match(context.entity(id), context.graph()); + return entityEditor; + }; + + entityEditor.preset = function(_) { + if (!arguments.length) return preset; + + var entity = context.entity(id), + geometry = context.geometry(id), + tags = preset.removeTags(entity.tags, geometry); + + preset = _; + tags = preset.applyTags(tags, geometry); + + context.perform( + iD.actions.ChangeTags(id, tags), + t('operations.change_tags.annotation')); + + return entityEditor; + }; + entityEditor.close = function() { // Blur focused element so that tag changes are dispatched // See #1295 diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index ae762beab..9c2e74000 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -1,70 +1,59 @@ iD.ui.Inspector = function(context) { - var presetList, - entityEditor, + var presetList = iD.ui.PresetList(context), + entityEditor = iD.ui.EntityEditor(context), entityID, newFeature = false; function inspector(selection) { + selection.style('display', 'block'); - var reselect = selection.html(), - entity = context.entity(entityID); + var $wrap = selection.selectAll('.panewrap') + .data([0]); - selection - .html('') - .style('display', 'block') - .style('right', '-500px') - .style('opacity', 1) - .transition() - .duration(reselect ? 0 : 200) - .style('right', '0px'); + var $enter = $wrap.enter().append('div') + .attr('class', 'panewrap'); - var panewrap = selection - .append('div') - .classed('panewrap', true); + $enter.append('div') + .attr('class', 'grid-pane pane'); - var presetLayer = panewrap - .append('div') - .classed('pane grid-pane', true); + $enter.append('div') + .attr('class', 'tag-pane pane'); - var tagLayer = panewrap - .append('div') - .classed('pane tag-pane', true); + var $presetPane = $wrap.select('.grid-pane') + .call(presetList + .entityID(entityID) + .autofocus(newFeature) + .on('choose', setPreset)); - presetList = iD.ui.PresetList(context, entity) - .autofocus(newFeature) - .on('choose', function(preset) { - panewrap - .transition() - .style('right', '0%'); + var $editorPane = $wrap.select('.tag-pane') + .call(entityEditor + .entityID(entityID) + .on('choose', showList)); - tagLayer.call(entityEditor, preset); - }); + $wrap.style('right', context.entity(entityID).isUsed(context.graph()) ? '-0%' : '-100%'); - entityEditor = iD.ui.EntityEditor(context, entity) - .on('choose', function(preset) { - panewrap - .transition() - .style('right', '-100%'); + function showList(preset) { + $wrap.transition() + .style('right', '-100%'); - presetList - .current(preset) - .autofocus(true); + $presetPane.call(presetList + .preset(preset) + .autofocus(true)); + } - presetLayer.call(presetList); - }); + function setPreset(preset) { + $wrap.transition() + .style('right', '0%'); - if (entity.isUsed(context.graph())) { - panewrap.style('right', '-0%'); - tagLayer.call(entityEditor); - } else { - panewrap.style('right', '-100%'); - presetLayer.call(presetList); + $editorPane.call(entityEditor + .preset(preset)); } } inspector.close = function(selection) { entityEditor.close(); - selection.html(''); + + selection.style('display', 'none'); }; inspector.entityID = function(_) { diff --git a/js/id/ui/preset.js b/js/id/ui/preset.js index 17800535f..422de834e 100644 --- a/js/id/ui/preset.js +++ b/js/id/ui/preset.js @@ -1,12 +1,11 @@ -iD.ui.preset = function(context, entity, preset) { - var original = context.graph().base().entities[entity.id], - event = d3.dispatch('change'), - fields = [], - tags = {}, - formwrap, - formbuttonwrap; +iD.ui.preset = function(context) { + var event = d3.dispatch('change'), + fields, + preset, + tags, + id; - function UIField(field, show) { + function UIField(field, entity, show) { field = _.clone(field); field.input = iD.ui.preset[field.type](field, context) @@ -29,160 +28,155 @@ iD.ui.preset = function(context, entity, preset) { }; field.modified = function() { + var original = context.graph().base().entities[entity.id]; return _.any(field.keys, function(key) { return original ? tags[key] !== original.tags[key] : tags[key]; }); }; + field.revert = function() { + var original = context.graph().base().entities[entity.id], + t = {}; + field.keys.forEach(function(key) { + t[key] = original ? original.tags[key] : undefined; + }); + return t; + }; + return field; } - fields.push(UIField(context.presets().field('name'))); - - var geometry = entity.geometry(context.graph()); - preset.fields.forEach(function(field) { - if (field.matchGeometry(geometry)) { - fields.push(UIField(field, true)); - } - }); - - context.presets().universal().forEach(function(field) { - if (preset.fields.indexOf(field) < 0) { - fields.push(UIField(field)); - } - }); - function fieldKey(field) { return field.id; } - function shown() { - return fields.filter(function(field) { return field.shown(); }); - } + function presets(selection) { + if (!fields) { + var entity = context.entity(id), + geometry = context.geometry(id); - function notShown() { - return fields.filter(function(field) { return !field.shown(); }); - } + fields = [UIField(context.presets().field('name'), entity)]; - function show(field) { - field.show = true; - render(); - field.input.focus(); - } + preset.fields.forEach(function(field) { + if (field.matchGeometry(geometry)) { + fields.push(UIField(field, entity, true)); + } + }); - function revert(field) { - d3.event.stopPropagation(); - d3.event.preventDefault(); - var t = {}; - field.keys.forEach(function(key) { - t[key] = original ? original.tags[key] || '' : ''; - }); - event.change(t); - } + context.presets().universal().forEach(function(field) { + if (preset.fields.indexOf(field) < 0) { + fields.push(UIField(field, entity)); + } + }); + } - function render() { - var selection = formwrap.selectAll('.form-field') - .data(shown(), fieldKey); + var shown = fields.filter(function(field) { return field.shown(); }), + notShown = fields.filter(function(field) { return !field.shown(); }); - var enter = selection.enter() + var $fields = selection.selectAll('.form-field') + .data(shown, fieldKey); + + // Enter + + var $enter = $fields.enter() .insert('div', '.more-buttons') - .style('opacity', 0) .attr('class', function(field) { return 'form-field form-field-' + field.id + ' fillL col12'; }); - enter.transition() - .style('max-height', '0px') - .style('padding-top', '0px') - .style('opacity', '0') - .transition() - .duration(200) - .style('padding-top', '10px') - .style('max-height', '240px') - .style('opacity', '1') - .each('end', function(d) { - d3.select(this).style('max-height', ''); - }); - - var label = enter.append('label') + var $label = $enter.append('label') .attr('class', 'form-label') .attr('for', function(field) { return 'preset-input-' + field.id; }) .text(function(field) { return field.label(); }); - label.each(function(field) { - d3.select(this) - .call(field.reference.button); - }); - - label.append('button') + $label.append('button') .attr('class', 'modified-icon minor') .attr('tabindex', -1) - .on('click', revert) .append('div') - .attr('class','icon undo'); + .attr('class', 'icon undo'); - enter.each(function(field) { - d3.select(this) - .call(field.input) - .call(field.reference.body); - }); + // Update - selection + $fields.select('.modified-icon') + .on('click', revert); + + $fields.select('.form-label') .each(function(field) { - field.input.tags(tags); - }) - .classed('modified', function(field) { - return field.modified(); + d3.select(this) + .call(field.reference.button); }); - selection.exit() + $fields + .classed('modified', function(field) { + return field.modified(); + }) + .each(function(field) { + d3.select(this) + .call(field.input) + .call(field.reference.body); + + field.input.tags(tags); + }); + + $fields.exit() .remove(); - var addFields = formbuttonwrap.selectAll('.preset-add-field') - .data(notShown(), fieldKey); + var $more = selection.selectAll('.more-buttons') + .data([0]); - addFields.enter() + $more.enter().append('div') + .attr('class', 'more-buttons inspector-inner col12'); + + var $buttons = $more.selectAll('.preset-add-field') + .data(notShown, fieldKey); + + $buttons.enter() .append('button') .attr('class', 'preset-add-field') - .on('click', show) .call(bootstrap.tooltip() .placement('top') .title(function(d) { return d.label(); })) .append('span') .attr('class', function(d) { return 'icon ' + d.icon; }); - addFields.exit() + $buttons.on('click', show); + + $buttons.exit() .transition() .style('opacity', 0) .remove(); - return selection; + function show(field) { + field.show = true; + presets(selection); + field.input.focus(); + } + + function revert(field) { + d3.event.stopPropagation(); + d3.event.preventDefault(); + event.change(field.revert()); + } } - function presets(selection) { - selection.html(''); - - formwrap = selection; - - formbuttonwrap = selection.append('div') - .attr('class', 'col12 more-buttons inspector-inner'); - - render(); - } - - presets.rendered = function() { - return _.flatten(shown().map(function(field) { return field.keys; })); - }; - presets.preset = function(_) { if (!arguments.length) return preset; preset = _; + fields = null; return presets; }; - presets.change = function(_) { + presets.tags = function(_) { + if (!arguments.length) return tags; tags = _; - render(); + // Don't reset fields here. + return presets; + }; + + presets.entityID = function(_) { + if (!arguments.length) return id; + id = _; + fields = null; return presets; }; diff --git a/js/id/ui/preset/access.js b/js/id/ui/preset/access.js index dcfcff10f..56e568674 100644 --- a/js/id/ui/preset/access.js +++ b/js/id/ui/preset/access.js @@ -4,14 +4,19 @@ iD.ui.preset.access = function(field, context) { items; function access(selection) { - var wrap = selection.append('div') - .attr('class', 'cf preset-input-wrap'); + var wrap = selection.selectAll('.preset-input-wrap') + .data([0]); - items = wrap.append('ul').selectAll('li') + wrap.enter().append('div') + .attr('class', 'cf preset-input-wrap') + .append('ul'); + + items = wrap.select('ul').selectAll('li') .data(field.keys); - var enter = items.enter() - .append('li') + // Enter + + var enter = items.enter().append('li') .attr('class', function(d) { return 'cf preset-access-' + d; }); enter.append('span') @@ -25,13 +30,17 @@ iD.ui.preset.access = function(field, context) { .attr('type', 'text') .attr('class', 'preset-input-access') .attr('id', function(d) { return 'preset-input-access-' + d; }) - .on('change', change) - .on('blur', change) .each(function(d) { d3.select(this) .call(d3.combobox() .data(access.options(d))); }); + + // Update + + wrap.selectAll('.preset-input-access') + .on('change', change) + .on('blur', change); } function change(d) { diff --git a/js/id/ui/preset/address.js b/js/id/ui/preset/address.js index 04073bf0d..7d0cf23f4 100644 --- a/js/id/ui/preset/address.js +++ b/js/id/ui/preset/address.js @@ -1,5 +1,4 @@ iD.ui.preset.address = function(field, context) { - var event = d3.dispatch('change'), housename, housenumber, @@ -36,45 +35,54 @@ iD.ui.preset.address = function(field, context) { } function address(selection) { - var wrap = selection.append('div') + var wrap = selection.selectAll('.preset-input-wrap') + .data([0]); + + // Enter + + var enter = wrap.enter().append('div') .attr('class', 'preset-input-wrap'); - housename = wrap.append('input') + enter.append('input') .property('type', 'text') .attr('placeholder', field.t('placeholders.housename')) .attr('class', 'addr-housename') - .attr('id', 'preset-input-' + field.id) - .on('blur', change) - .on('change', change); + .attr('id', 'preset-input-' + field.id); - housenumber = wrap.append('input') + enter.append('input') .property('type', 'text') .attr('placeholder', field.t('placeholders.number')) - .attr('class', 'addr-number') - .on('blur', change) - .on('change', change); + .attr('class', 'addr-number'); - street = wrap.append('input') + enter.append('input') .property('type', 'text') .attr('placeholder', field.t('placeholders.street')) - .attr('class', 'addr-street') - .on('blur', change) - .on('change', change) - .call(d3.combobox().data(getStreets())); + .attr('class', 'addr-street'); - city = wrap.append('input') + enter.append('input') .property('type', 'text') .attr('placeholder', field.t('placeholders.city')) - .attr('class', 'addr-city') + .attr('class', 'addr-city'); + + enter.append('input') + .property('type', 'text') + .attr('placeholder', field.t('placeholders.postcode')) + .attr('class', 'addr-postcode'); + + // Update + + housename = wrap.select('.addr-housename'); + housenumber = wrap.select('.addr-number'); + street = wrap.select('.addr-street'); + city = wrap.select('.addr-city'); + postcode = wrap.select('.addr-postcode'); + + wrap.selectAll('input') .on('blur', change) .on('change', change); - postcode = wrap.append('input') - .property('type', 'text') - .attr('placeholder', field.t('placeholders.postcode')) - .attr('class', 'addr-postcode') - .on('blur', change) - .on('change', change); + street + .call(d3.combobox().data(getStreets())); } function change() { diff --git a/js/id/ui/preset/check.js b/js/id/ui/preset/check.js index d4a9f78de..2efbdc028 100644 --- a/js/id/ui/preset/check.js +++ b/js/id/ui/preset/check.js @@ -1,5 +1,4 @@ iD.ui.preset.check = function(field) { - var event = d3.dispatch('change'), values = ['', 'yes', 'no'], value = '', @@ -8,28 +7,33 @@ iD.ui.preset.check = function(field) { label; var check = function(selection) { - selection.classed('checkselect', 'true'); - label = selection.append('label') + label = selection.selectAll('.preset-input-wrap') + .data([0]); + + var enter = label.enter().append('label') .attr('class', 'preset-input-wrap'); - box = label.append('input') + enter.append('input') .property('indeterminate', true) .attr('type', 'checkbox') .attr('id', 'preset-input-' + field.id); - text = label.append('span') + enter.append('span') .text('unknown') .attr('class', 'value'); - box.on('click', function() { - var t = {}; - t[field.key] = values[(values.indexOf(value) + 1) % 3]; - check.tags(t); - event.change(t); - d3.event.stopPropagation(); - }); + box = label.select('input') + .on('click', function() { + var t = {}; + t[field.key] = values[(values.indexOf(value) + 1) % 3]; + check.tags(t); + event.change(t); + d3.event.stopPropagation(); + }); + + text = label.select('span.value'); }; check.tags = function(tags) { diff --git a/js/id/ui/preset/combo.js b/js/id/ui/preset/combo.js index 033576961..95dd9e47d 100644 --- a/js/id/ui/preset/combo.js +++ b/js/id/ui/preset/combo.js @@ -1,14 +1,18 @@ iD.ui.preset.combo = function(field) { - var event = d3.dispatch('change'), input; function combo(selection) { var combobox = d3.combobox(); - input = selection.append('input') + input = selection.selectAll('input') + .data([0]); + + input.enter().append('input') .attr('type', 'text') - .attr('id', 'preset-input-' + field.id) + .attr('id', 'preset-input-' + field.id); + + input .on('change', change) .on('blur', change) .call(combobox); @@ -37,7 +41,6 @@ iD.ui.preset.combo = function(field) { } } - function change() { var t = {}; t[field.key] = input.property('value').replace(' ', '_'); diff --git a/js/id/ui/preset/defaultcheck.js b/js/id/ui/preset/defaultcheck.js index f871e826d..c9638c349 100644 --- a/js/id/ui/preset/defaultcheck.js +++ b/js/id/ui/preset/defaultcheck.js @@ -1,19 +1,22 @@ iD.ui.preset.defaultcheck = function(field) { - var event = d3.dispatch('change'), input; - var check = function(selection) { + function check(selection) { + input = selection.selectAll('input') + .data([0]); - input = selection.append('input') + input.enter().append('input') .attr('type', 'checkbox') - .attr('id', 'preset-input-' + field.id) + .attr('id', 'preset-input-' + field.id); + + input .on('change', function() { var t = {}; t[field.key] = input.property('checked') ? field.value || 'yes' : undefined; event.change(t); }); - }; + } check.tags = function(tags) { input.property('checked', !!tags[field.key] && tags[field.key] !== 'no'); diff --git a/js/id/ui/preset/input.js b/js/id/ui/preset/input.js index 416457805..2056a18ed 100644 --- a/js/id/ui/preset/input.js +++ b/js/id/ui/preset/input.js @@ -8,10 +8,15 @@ iD.ui.preset.url = function(field) { input; function i(selection) { - input = selection.append('input') + input = selection.selectAll('input') + .data([0]); + + input.enter().append('input') .attr('type', field.type) .attr('id', 'preset-input-' + field.id) - .attr('placeholder', field.placeholder || '') + .attr('placeholder', field.placeholder || ''); + + input .on('blur', change) .on('change', change); @@ -23,23 +28,25 @@ iD.ui.preset.url = function(field) { } if (field.type == 'number') { - input.attr('type', 'text'); - var numbercontrols = selection.append('div') + var spinControl = selection.selectAll('.spin-control') + .data([0]); + + var enter = spinControl.enter().append('div') .attr('class', 'spin-control'); - numbercontrols - .append('button') - .attr('class', 'increment') - .on('click', function() { - pm(input.node(), 1); - }); - numbercontrols - .append('button') - .attr('class', 'decrement') - .on('click', function() { - pm(input.node(), -1); + enter.append('button') + .datum(1) + .attr('class', 'increment'); + + enter.append('button') + .datum(-1) + .attr('class', 'decrement'); + + spinControl.selectAll('button') + .on('click', function(d) { + pm(input.node(), d); }); } } diff --git a/js/id/ui/preset/localized.js b/js/id/ui/preset/localized.js index 9fe5b8b52..6561683f9 100644 --- a/js/id/ui/preset/localized.js +++ b/js/id/ui/preset/localized.js @@ -5,29 +5,38 @@ iD.ui.preset.localized = function(field, context) { input, localizedInputs, wikiTitles; function i(selection) { + input = selection.selectAll('.localized-main') + .data([0]); - input = selection.append('input') + input.enter().append('input') .attr('type', 'text') .attr('id', 'preset-input-' + field.id) .attr('class', 'localized-main') - .attr('placeholder', field.placeholder || '') + .attr('placeholder', field.placeholder || ''); + + input .on('blur', change) .on('change', change); - var translateButton = selection.append('button') - .attr('class', 'button-input-action localized-add minor') - .on('click', addBlank); + var translateButton = selection.selectAll('.localized-add') + .data([0]); - translateButton.append('span') + translateButton.enter().append('button') + .attr('class', 'button-input-action localized-add minor') + .call(bootstrap.tooltip() + .title(t('translate.translate')) + .placement('left')) + .append('span') .attr('class', 'icon plus'); - translateButton.call(bootstrap.tooltip() - .title(t('translate.translate')) - .placement('left')); + translateButton + .on('click', addBlank); - localizedInputs = selection.append('div') + localizedInputs = selection.selectAll('.localized-wrap') + .data([0]); + + localizedInputs.enter().append('div') .attr('class', 'localized-wrap'); - } function addBlank() { diff --git a/js/id/ui/preset/maxspeed.js b/js/id/ui/preset/maxspeed.js index fdcb07bdd..926ce7e64 100644 --- a/js/id/ui/preset/maxspeed.js +++ b/js/id/ui/preset/maxspeed.js @@ -14,9 +14,14 @@ iD.ui.preset.maxspeed = function(field, context) { combobox = d3.combobox(); var unitCombobox = d3.combobox().data(['km/h', 'mph'].map(comboValues)); - input = selection.append('input') + input = selection.selectAll('#preset-input-' + field.id) + .data([0]); + + input.enter().append('input') .attr('type', 'text') - .attr('id', 'preset-input-' + field.id) + .attr('id', 'preset-input-' + field.id); + + input .on('change', change) .on('blur', change) .call(combobox); @@ -30,9 +35,14 @@ iD.ui.preset.maxspeed = function(field, context) { }); }); - unitInput = selection.append('input') + unitInput = selection.selectAll('input.maxspeed-unit') + .data([0]); + + unitInput.enter().append('input') .attr('type', 'text') - .attr('class', 'maxspeed-unit') + .attr('class', 'maxspeed-unit'); + + unitInput .on('blur', changeUnits) .on('change', changeUnits) .call(unitCombobox); diff --git a/js/id/ui/preset/radio.js b/js/id/ui/preset/radio.js index 5d6cd771a..3af0a8d07 100644 --- a/js/id/ui/preset/radio.js +++ b/js/id/ui/preset/radio.js @@ -6,28 +6,38 @@ iD.ui.preset.radio = function(field) { function radio(selection) { selection.classed('preset-radio', true); - var buttonwrap = selection.append('div') + var wrap = selection.selectAll('.preset-input-wrap') + .data([0]); + + wrap.enter().append('div') .attr('class', 'preset-input-wrap toggle-list radio-wrap'); - buttons = buttonwrap.selectAll('button') - .data(field.options || field.keys) - .enter() - .append('button') - .text(function(d) { return field.t('options.' + d, { 'default': d }); }) + buttons = wrap.selectAll('button') + .data(field.options || field.keys); + + buttons.enter().append('button') + .text(function(d) { return field.t('options.' + d, { 'default': d }); }); + + buttons .on('click', function(d) { buttons.classed('active', function(e) { return d === e; }); change(); }); - buttonwrap.append('button') - .attr('class','remove') - .on('click', function() { - buttons.classed('active', false); - change(); - }) + var remove = wrap.selectAll('button.remove') + .data([0]); + + remove.enter().append('button') + .attr('class', 'remove') .text(t('inspector.remove')) .append('span') .attr('class', 'icon remove'); + + remove + .on('click', function() { + buttons.classed('active', false); + change(); + }); } function change() { diff --git a/js/id/ui/preset/textarea.js b/js/id/ui/preset/textarea.js index ec623ba9a..83fe0c21c 100644 --- a/js/id/ui/preset/textarea.js +++ b/js/id/ui/preset/textarea.js @@ -4,10 +4,15 @@ iD.ui.preset.textarea = function(field) { input; function i(selection) { - input = selection.append('textarea') + input = selection.selectAll('textarea') + .data([0]); + + input.enter().append('textarea') .attr('id', 'preset-input-' + field.id) .attr('placeholder', field.placeholder || '') - .attr('maxlength', 255) + .attr('maxlength', 255); + + input .on('blur', change) .on('change', change); } diff --git a/js/id/ui/preset/wikipedia.js b/js/id/ui/preset/wikipedia.js index c3f1dd5ea..6ac4b205d 100644 --- a/js/id/ui/preset/wikipedia.js +++ b/js/id/ui/preset/wikipedia.js @@ -33,26 +33,39 @@ iD.ui.preset.wikipedia = function(field, context) { }); }); - lang = selection.append('input') + lang = selection.selectAll('input.wiki-lang') + .data([0]); + + lang.enter().append('input') .attr('type', 'text') - .attr('class', 'wiki-lang') + .attr('class', 'wiki-lang'); + + lang .on('blur', changeLang) .on('change', changeLang) .call(langcombo); - title = selection.append('input') + title = selection.selectAll('input.wiki-title') + .data([0]); + + title.enter().append('input') .attr('type', 'text') .attr('class', 'wiki-title') - .attr('id', 'preset-input-' + field.id) + .attr('id', 'preset-input-' + field.id); + + title .on('blur', change) .on('change', change) .call(titlecombo); - link = selection.append('a') + link = selection.selectAll('a.wiki-link') + .data([0]); + + link.enter().append('a') .attr('class', 'wiki-link button-input-action minor') - .attr('target', '_blank'); - link.append('span') - .attr('class','icon out-link'); + .attr('target', '_blank') + .append('span') + .attr('class', 'icon out-link'); } function changeLang() { diff --git a/js/id/ui/preset_icon.js b/js/id/ui/preset_icon.js index 61862f49d..60eb7f7ea 100644 --- a/js/id/ui/preset_icon.js +++ b/js/id/ui/preset_icon.js @@ -2,32 +2,40 @@ iD.ui.PresetIcon = function() { var preset, geometry; function presetIcon(selection) { - selection.append('div') - .attr('class', function() { - var s = 'preset-icon-fill icon-' + geometry; - for (var i in preset.tags) { - s += ' tag-' + i + ' tag-' + i + '-' + preset.tags[i]; - } - return s; - }); + var $fill = selection.selectAll('.preset-icon-fill') + .data([0]); + + $fill.enter().append('div'); + + $fill.attr('class', function() { + var s = 'preset-icon-fill icon-' + geometry; + for (var i in preset.tags) { + s += ' tag-' + i + ' tag-' + i + '-' + preset.tags[i]; + } + return s; + }); var fallbackIcon = geometry === 'line' ? 'other-line' : 'marker-stroked'; - selection.append('div') - .attr('class', function() { - var icon = preset.icon || fallbackIcon, - klass = 'feature-' + icon + ' preset-icon'; + var $icon = selection.selectAll('.preset-icon') + .data([0]); - var featureicon = iD.data.featureIcons[icon]; - if (featureicon && featureicon[geometry]) { - klass += ' preset-icon-' + geometry; - } else if (icon === 'multipolygon') { - // Special case (geometry === 'area') - klass += ' preset-icon-relation'; - } + $icon.enter().append('div'); - return klass; - }); + $icon.attr('class', function() { + var icon = preset.icon || fallbackIcon, + klass = 'feature-' + icon + ' preset-icon'; + + var featureicon = iD.data.featureIcons[icon]; + if (featureicon && featureicon[geometry]) { + klass += ' preset-icon-' + geometry; + } else if (icon === 'multipolygon') { + // Special case (geometry === 'area') + klass += ' preset-icon-relation'; + } + + return klass; + }); } presetIcon.preset = function(_) { diff --git a/js/id/ui/preset_list.js b/js/id/ui/preset_list.js index a441da478..15162f159 100644 --- a/js/id/ui/preset_list.js +++ b/js/id/ui/preset_list.js @@ -1,15 +1,12 @@ -iD.ui.PresetList = function(context, entity) { +iD.ui.PresetList = function(context) { var event = d3.dispatch('choose'), - presets, current, + id, + preset, autofocus = false; - function browse() { - context.enter(iD.modes.Browse(context)); - } - function presetList(selection) { - var geometry = entity.geometry(context.graph()); - presets = context.presets().matchGeometry(geometry); + var geometry = context.geometry(id), + presets = context.presets().matchGeometry(geometry); selection.html(''); @@ -20,7 +17,7 @@ iD.ui.PresetList = function(context, entity) { .attr('class', 'inspector-inner') .text(t('inspector.choose')); - if (current) { + if (preset) { messagewrap.append('button') .attr('class', 'preset-choose') .on('click', event.choose) @@ -29,7 +26,9 @@ iD.ui.PresetList = function(context, entity) { } else { messagewrap.append('button') .attr('class', 'close') - .on('click', browse) + .on('click', function() { + context.enter(iD.modes.Browse(context)); + }) .append('span') .attr('class', 'icon close'); } @@ -41,7 +40,7 @@ iD.ui.PresetList = function(context, entity) { d3.event.keyCode === d3.keybinding.keyCodes['⌦'])) { d3.event.preventDefault(); d3.event.stopPropagation(); - iD.operations.Delete([entity.id], context)(); + iD.operations.Delete([id], context)(); } else if (search.property('value').length === 0 && (d3.event.ctrlKey || d3.event.metaKey) && d3.event.keyCode === d3.keybinding.keyCodes.z) { @@ -108,7 +107,7 @@ iD.ui.PresetList = function(context, entity) { items.enter().append('div') .attr('class', function(item) { return 'preset-list-item preset-' + item.preset.id.replace('/', '-'); }) - .classed('current', function(item) { return item.preset === current; }) + .classed('current', function(item) { return item.preset === preset; }) .each(function(item) { d3.select(this).call(item); }) @@ -132,7 +131,7 @@ iD.ui.PresetList = function(context, entity) { wrap.append('button') .attr('class', 'preset-list-button') .call(iD.ui.PresetIcon() - .geometry(context.geometry(entity.id)) + .geometry(context.geometry(id)) .preset(preset)) .on('click', item.choose) .append('div') @@ -182,7 +181,7 @@ iD.ui.PresetList = function(context, entity) { wrap.append('button') .attr('class', 'preset-list-button') .call(iD.ui.PresetIcon() - .geometry(context.geometry(entity.id)) + .geometry(context.geometry(id)) .preset(preset)) .on('click', item.choose) .append('div') @@ -215,9 +214,15 @@ iD.ui.PresetList = function(context, entity) { return presetList; }; - presetList.current = function(_) { - if (!arguments.length) return current; - current = _; + presetList.entityID = function(_) { + if (!arguments.length) return id; + id = _; + return presetList; + }; + + presetList.preset = function(_) { + if (!arguments.length) return preset; + preset = _; return presetList; }; diff --git a/js/id/ui/raw_member_editor.js b/js/id/ui/raw_member_editor.js index d33b09061..003e39ea5 100644 --- a/js/id/ui/raw_member_editor.js +++ b/js/id/ui/raw_member_editor.js @@ -1,32 +1,5 @@ -iD.ui.RawMemberEditor = function(context, entity) { - var list, disclosure; - - var rawMemberEditor = function(selection) { - function toggled(expanded) { - if (expanded) { - selection.node().parentNode.scrollTop += 200; - } - } - - disclosure = iD.ui.Disclosure() - .title(t('inspector.all_members')) - .expanded(true) - .on('toggled', toggled) - .content(content); - - selection.call(disclosure); - }; - - rawMemberEditor.change = function() { - drawMembers(); - }; - - function content(wrap) { - list = wrap.append('ul') - .attr('class', 'member-list'); - - drawMembers(); - } +iD.ui.RawMemberEditor = function(context) { + var id; function selectMember(d) { context.enter(iD.modes.Select(context, [d.member.id])); @@ -35,75 +8,97 @@ iD.ui.RawMemberEditor = function(context, entity) { function changeRole(d) { var role = d3.select(this).property('value'); context.perform( - iD.actions.ChangeMember(entity.id, _.extend({}, d.member, {role: role}), d.index), + iD.actions.ChangeMember(id, _.extend({}, d.member, {role: role}), d.index), t('operations.change_role.annotation')); } function deleteMember(d) { context.perform( - iD.actions.DeleteMember(entity.id, d.index), + iD.actions.DeleteMember(id, d.index), t('operations.delete_member.annotation.' + context.geometry(d.member.id))); } - function drawMembers() { - var memberships = []; - - entity = context.hasEntity(entity.id); - if (!entity) return; + function rawMemberEditor(selection) { + var entity = context.entity(id), + memberships = []; entity.members.forEach(function(member, index) { memberships.push({member: member, index: index, entity: context.hasEntity(member.id)}); }); - disclosure.title(t('inspector.all_members') + ' (' + memberships.length + ')'); + selection.call(iD.ui.Disclosure() + .title(t('inspector.all_members') + ' (' + memberships.length + ')') + .expanded(true) + .on('toggled', toggled) + .content(content)); - var li = list.selectAll('li') - .data(memberships, function(d) { return iD.Entity.key(entity) + ',' + d.index; }); - - var row = li.enter().append('li') - .attr('class', 'member-row form-field'); - - row.each(function(d) { - if (d.entity) { - var member = d3.select(this).append('label') - .attr('class', 'form-label') - .append('a') - .attr('href', '#') - .on('click', selectMember); - - member.append('span') - .attr('class', 'member-entity-type') - .text(function(d) { return context.presets().match(d.entity, context.graph()).name(); }); - - member.append('span') - .attr('class', 'member-entity-name') - .text(function(d) { return iD.util.localeName(d.entity); }); - - } else { - d3.select(this).append('label') - .attr('class', 'form-label member-incomplete') - .text(t('inspector.incomplete')); + function toggled(expanded) { + if (expanded) { + selection.node().parentNode.scrollTop += 200; } - }); + } - row.append('input') - .attr('class', 'member-role') - .property('type', 'text') - .attr('maxlength', 255) - .attr('placeholder', t('inspector.role')) - .property('value', function(d) { return d.member.role; }) - .on('change', changeRole); + function content($wrap) { + var $list = $wrap.selectAll('.member-list') + .data([0]); - row.append('button') - .attr('tabindex', -1) - .attr('class', 'remove button-input-action member-delete minor') - .on('click', deleteMember) - .append('span') - .attr('class', 'icon delete'); + $list.enter().append('ul') + .attr('class', 'member-list'); - li.exit() - .remove(); + var $items = $list.selectAll('li') + .data(memberships, function(d) { return iD.Entity.key(entity) + ',' + d.index; }); + + var $enter = $items.enter().append('li') + .attr('class', 'member-row form-field'); + + $enter.each(function(d) { + if (d.entity) { + var $label = d3.select(this).append('label') + .attr('class', 'form-label') + .append('a') + .attr('href', '#') + .on('click', selectMember); + + $label.append('span') + .attr('class', 'member-entity-type') + .text(function(d) { return context.presets().match(d.entity, context.graph()).name(); }); + + $label.append('span') + .attr('class', 'member-entity-name') + .text(function(d) { return iD.util.localeName(d.entity); }); + + } else { + d3.select(this).append('label') + .attr('class', 'form-label member-incomplete') + .text(t('inspector.incomplete')); + } + }); + + $enter.append('input') + .attr('class', 'member-role') + .property('type', 'text') + .attr('maxlength', 255) + .attr('placeholder', t('inspector.role')) + .property('value', function(d) { return d.member.role; }) + .on('change', changeRole); + + $enter.append('button') + .attr('tabindex', -1) + .attr('class', 'remove button-input-action member-delete minor') + .on('click', deleteMember) + .append('span') + .attr('class', 'icon delete'); + + $items.exit() + .remove(); + } } + rawMemberEditor.entityID = function(_) { + if (!arguments.length) return id; + id = _; + return rawMemberEditor; + }; + return rawMemberEditor; }; diff --git a/js/id/ui/raw_membership_editor.js b/js/id/ui/raw_membership_editor.js index 415592455..8a6652e99 100644 --- a/js/id/ui/raw_membership_editor.js +++ b/js/id/ui/raw_membership_editor.js @@ -1,32 +1,5 @@ -iD.ui.RawMembershipEditor = function(context, entity) { - var list, disclosure; - - var rawMembershipEditor = function(selection) { - function toggled(expanded) { - if (expanded) { - selection.node().parentNode.scrollTop += 200; - } - } - - disclosure = iD.ui.Disclosure() - .title(t('inspector.all_relations')) - .expanded(true) - .on('toggled', toggled) - .content(content); - - selection.call(disclosure); - }; - - rawMembershipEditor.change = function() { - drawMemberships(); - }; - - function content(wrap) { - list = wrap.append('ul') - .attr('class', 'member-list'); - - drawMemberships(); - } +iD.ui.RawMembershipEditor = function(context) { + var id; function selectRelation(d) { context.enter(iD.modes.Select(context, [d.relation.id])); @@ -45,8 +18,9 @@ iD.ui.RawMembershipEditor = function(context, entity) { t('operations.delete_member.annotation.' + context.geometry(d.member.id))); } - function drawMemberships() { - var memberships = []; + function rawMembershipEditor(selection) { + var entity = context.entity(id), + memberships = []; context.graph().parentRelations(entity).forEach(function(relation) { relation.members.forEach(function(member, index) { @@ -56,46 +30,70 @@ iD.ui.RawMembershipEditor = function(context, entity) { }) }); - disclosure.title(t('inspector.all_relations') + ' (' + memberships.length + ')'); + selection.call(iD.ui.Disclosure() + .title(t('inspector.all_relations') + ' (' + memberships.length + ')') + .expanded(true) + .on('toggled', toggled) + .content(content)); - var li = list.selectAll('li') - .data(memberships, function(d) { return iD.Entity.key(d.relation) + ',' + d.index; }); + function toggled(expanded) { + if (expanded) { + selection.node().parentNode.scrollTop += 200; + } + } - var row = li.enter().append('li') - .attr('class', 'member-row form-field'); + function content($wrap) { + var $list = $wrap.selectAll('.member-list') + .data([0]); - relationLabel = row.append('label') - .attr('class', 'form-label') - .append('a') - .attr('href', '#') - .on('click', selectRelation); + $list.enter().append('ul') + .attr('class', 'member-list'); - relationLabel.append('span') - .attr('class','member-entity-type') - .text(function(d) { return context.presets().match(d.relation, context.graph()).name(); }); + var $items = $list.selectAll('li') + .data(memberships, function(d) { return iD.Entity.key(d.relation) + ',' + d.index; }); - relationLabel.append('span') - .attr('class', 'member-entity-name') - .text(function(d) { return iD.util.localeName(d.relation); }); + var $enter = $items.enter().append('li') + .attr('class', 'member-row form-field'); - row.append('input') - .attr('class', 'member-role') - .property('type', 'text') - .attr('maxlength', 255) - .attr('placeholder', t('inspector.role')) - .property('value', function(d) { return d.member.role; }) - .on('change', changeRole); + var $label = $enter.append('label') + .attr('class', 'form-label') + .append('a') + .attr('href', '#') + .on('click', selectRelation); - row.append('button') - .attr('tabindex', -1) - .attr('class', 'remove button-input-action member-delete minor') - .on('click', deleteMembership) - .append('span') - .attr('class', 'icon delete'); + $label.append('span') + .attr('class','member-entity-type') + .text(function(d) { return context.presets().match(d.relation, context.graph()).name(); }); - li.exit() - .remove(); + $label.append('span') + .attr('class', 'member-entity-name') + .text(function(d) { return iD.util.localeName(d.relation); }); + + $enter.append('input') + .attr('class', 'member-role') + .property('type', 'text') + .attr('maxlength', 255) + .attr('placeholder', t('inspector.role')) + .property('value', function(d) { return d.member.role; }) + .on('change', changeRole); + + $enter.append('button') + .attr('tabindex', -1) + .attr('class', 'remove button-input-action member-delete minor') + .on('click', deleteMembership) + .append('span') + .attr('class', 'icon delete'); + + $items.exit() + .remove(); + } } + rawMembershipEditor.entityID = function(_) { + if (!arguments.length) return id; + id = _; + return rawMembershipEditor; + }; + return rawMembershipEditor; }; diff --git a/js/id/ui/raw_tag_editor.js b/js/id/ui/raw_tag_editor.js index 32a5c66ad..ff8886509 100644 --- a/js/id/ui/raw_tag_editor.js +++ b/js/id/ui/raw_tag_editor.js @@ -1,195 +1,211 @@ -iD.ui.RawTagEditor = function(context, entity) { +iD.ui.RawTagEditor = function(context) { var event = d3.dispatch('change'), taginfo = iD.taginfo(), - disclosure, - list; + preset, + tags, + id; + + function rawTagEditor(selection) { + var count = Object.keys(tags).filter(function(d) { return d; }).length; + + selection.call(iD.ui.Disclosure() + .title(t('inspector.all_tags') + ' (' + count + ')') + .expanded(iD.ui.RawTagEditor.expanded || preset.isFallback()) + .on('toggled', toggled) + .content(content)); - function rawTagEditor(selection, other) { function toggled(expanded) { iD.ui.RawTagEditor.expanded = expanded; if (expanded) { selection.node().parentNode.scrollTop += 200; } } - - disclosure = iD.ui.Disclosure() - .title(t('inspector.all_tags')) - .expanded(iD.ui.RawTagEditor.expanded || other) - .on('toggled', toggled) - .content(content); - - selection.call(disclosure); } - function content(wrap) { - list = wrap.append('ul') - .attr('class', 'tag-list'); + function content($wrap) { + var entries = d3.entries(tags); - var newTag = wrap.append('button') - .attr('class', 'add-tag col6') - .on('click', addTag); - - newTag.append('span') - .attr('class', 'icon plus light'); - - newTag.append('span') - .attr('class', 'label') - .text(t('inspector.new_tag')); - } - - function drawTags(tags) { - - var count = Object.keys(tags).filter(function(d) { return d; }).length; - disclosure.title(t('inspector.all_tags') + ' (' + count + ')'); - - tags = d3.entries(tags); - - if (!tags.length) { - tags = [{key: '', value: ''}]; + if (!entries.length) { + entries = [{key: '', value: ''}]; } - tags.forEach(function(tag) { - tag.reference = iD.ui.TagReference({key: tag.key}); + entries.forEach(function(entry) { + entry.reference = iD.ui.TagReference({key: entry.key}); }); - var li = list.html('') - .selectAll('li') - .data(tags, function(d) { return d.key; }); + var $list = $wrap.selectAll('.tag-list') + .data([0]); - li.exit().remove(); + $list.enter().append('ul') + .attr('class', 'tag-list'); - var row = li.enter().append('li') + var $newTag = $wrap.selectAll('.add-tag') + .data([0]); + + var $enter = $newTag.enter().append('button') + .attr('class', 'add-tag col6'); + + $enter.append('span') + .attr('class', 'icon plus light'); + + $enter.append('span') + .attr('class', 'label') + .text(t('inspector.new_tag')); + + $newTag.on('click', addTag); + + var $items = $list.selectAll('li') + .data(entries, function(d) { return d.key; }); + + // Enter + + $enter = $items.enter().append('li') .attr('class', 'tag-row cf'); - row.append('div') + $enter.append('div') .attr('class', 'key-wrap') .append('input') .property('type', 'text') .attr('class', 'key') - .attr('maxlength', 255) - .property('value', function(d) { return d.key; }) - .on('blur', keyChange) - .on('change', keyChange); + .attr('maxlength', 255); - function keyChange(d) { - d.key = this.value; - event.change(rawTagEditor.tags()); - } - - row.append('div') + $enter.append('div') .attr('class', 'input-wrap-position col6') .append('input') .property('type', 'text') .attr('class', 'value') - .attr('maxlength', 255) - .property('value', function(d) { return d.value; }) + .attr('maxlength', 255); + + $enter.append('button') + .attr('tabindex', -1) + .attr('class', 'remove minor') + .append('span') + .attr('class', 'icon delete'); + + // Update + + $items.order(); + + $items.select('input.key') + .property('value', function(d) { + return d.key; + }) + .on('blur', keyChange) + .on('change', keyChange); + + $items.select('input.value') + .property('value', function(d) { + return d.value; + }) .on('blur', valueChange) .on('change', valueChange) .on('keydown.push-more', pushMore); - function valueChange(d) { - d.value = this.value; - event.change(rawTagEditor.tags()); - } + $items.select('button.remove') + .on('click', removeTag); - row.each(bindTypeahead); - - row.append('button') - .attr('tabindex', -1) - .attr('class','remove minor') - .on('click', removeTag) - .append('span') - .attr('class', 'icon delete'); - - row.each(function(tag) { + $items.each(function(tag) { d3.select(this) + .each(bindTypeahead) .call(tag.reference.button) .call(tag.reference.body); }); - return li; - } + $items.exit() + .remove(); - function pushMore() { - if (d3.event.keyCode === 9 && - list.selectAll('li:last-child input.value').node() === this && - !d3.event.shiftKey) { - addTag(); - d3.event.preventDefault(); + function keyChange(d) { + var tag = {}; + tag[this.value] = d.value; + d.key = this.value; // Maintain DOM identity through the subsequent update. + event.change(tag); } - } - function bindTypeahead() { - var geometry = entity.geometry(context.graph()), - row = d3.select(this), - key = row.selectAll('input.key'), - value = row.selectAll('input.value'); + function valueChange(d) { + var tag = {}; + tag[d.key] = this.value; + event.change(tag); + } - function sort(value, data) { - var sameletter = [], - other = []; - for (var i = 0; i < data.length; i++) { - if (data[i].value.substring(0, value.length) === value) { - sameletter.push(data[i]); - } else { - other.push(data[i]); - } + function pushMore() { + if (d3.event.keyCode === 9 && !d3.event.shiftKey && + $list.selectAll('li:last-child input.value').node() === this) { + addTag(); + d3.event.preventDefault(); } - return sameletter.concat(other); } - key.call(d3.combobox() - .fetcher(function(value, __, callback) { - taginfo.keys({ - debounce: true, - geometry: geometry, - query: value - }, function(err, data) { - if (!err) callback(sort(value, data)); - }); - })); + function bindTypeahead() { + var geometry = context.geometry(id), + row = d3.select(this), + key = row.selectAll('input.key'), + value = row.selectAll('input.value'); - value.call(d3.combobox() - .fetcher(function(value, __, callback) { - taginfo.values({ - debounce: true, - key: key.property('value'), - geometry: geometry, - query: value - }, function(err, data) { - if (!err) callback(sort(value, data)); - }); - })); - } + function sort(value, data) { + var sameletter = [], + other = []; + for (var i = 0; i < data.length; i++) { + if (data[i].value.substring(0, value.length) === value) { + sameletter.push(data[i]); + } else { + other.push(data[i]); + } + } + return sameletter.concat(other); + } - function addTag() { - var tags = rawTagEditor.tags(); - tags[''] = ''; - drawTags(tags); - list.selectAll('li:last-child input.key').node().focus(); - } + key.call(d3.combobox() + .fetcher(function(value, __, callback) { + taginfo.keys({ + debounce: true, + geometry: geometry, + query: value + }, function(err, data) { + if (!err) callback(sort(value, data)); + }); + })); - function removeTag(d) { - var tags = rawTagEditor.tags(); - tags[d.key] = ''; - event.change(tags); - delete tags[d.key]; - drawTags(tags); - } - - rawTagEditor.tags = function(tags) { - if (!arguments.length) { - tags = {}; - list.selectAll('li').each(function() { - var row = d3.select(this), - key = row.selectAll('.key').property('value'), - value = row.selectAll('.value').property('value'); - if (key !== '') tags[key] = value; - }); - return tags; - } else { - drawTags(tags); + value.call(d3.combobox() + .fetcher(function(value, __, callback) { + taginfo.values({ + debounce: true, + key: key.property('value'), + geometry: geometry, + query: value + }, function(err, data) { + if (!err) callback(sort(value, data)); + }); + })); } + + function addTag() { + tags[''] = ''; + content($wrap); + $list.selectAll('li:last-child input.key').node().focus(); + } + + function removeTag(d) { + tags[d.key] = ''; + event.change(tags); + } + } + + rawTagEditor.preset = function(_) { + if (!arguments.length) return preset; + preset = _; + return rawTagEditor; + }; + + rawTagEditor.tags = function(_) { + if (!arguments.length) return tags; + tags = _; + return rawTagEditor; + }; + + rawTagEditor.entityID = function(_) { + if (!arguments.length) return id; + id = _; + return rawTagEditor; }; return d3.rebind(rawTagEditor, event, 'on'); diff --git a/js/id/ui/tag_reference.js b/js/id/ui/tag_reference.js index 8a50afed6..0ec2060de 100644 --- a/js/id/ui/tag_reference.js +++ b/js/id/ui/tag_reference.js @@ -31,25 +31,32 @@ iD.ui.TagReference = function(tag) { } tagReference.button = function(selection) { - button = selection.append('button') - .attr('tabindex', -1) - .attr('class', 'tag-reference-button minor') - .on('click', function() { - d3.event.stopPropagation(); - d3.event.preventDefault(); - if (showing) { - tagReference.hide(); - } else { - tagReference.load(); - } - }); + button = selection.selectAll('.tag-reference-button') + .data([0]); - button.append('span') + var enter = button.enter().append('button') + .attr('tabindex', -1) + .attr('class', 'tag-reference-button minor'); + + enter.append('span') .attr('class', 'icon inspect'); + + button.on('click', function () { + d3.event.stopPropagation(); + d3.event.preventDefault(); + if (showing) { + tagReference.hide(); + } else { + tagReference.load(); + } + }); }; tagReference.body = function(selection) { - body = selection.append('div') + body = selection.selectAll('.tag-reference-body') + .data([0]); + + body.enter().append('div') .attr('class', 'tag-reference-body cf') .style('max-height', '0') .style('opacity', '0'); @@ -68,6 +75,8 @@ iD.ui.TagReference = function(tag) { docs = findLocal(docs); } + body.html(''); + if (!docs || !docs.description) { body.append('p').text(t('inspector.no_documentation_key')); tagReference.show(); diff --git a/js/id/ui/view_on_osm.js b/js/id/ui/view_on_osm.js index 55077e405..ce0c282f0 100644 --- a/js/id/ui/view_on_osm.js +++ b/js/id/ui/view_on_osm.js @@ -1,19 +1,32 @@ iD.ui.ViewOnOSM = function(context) { - return function(selection, entity) { + var id; + + function viewOnOSM(selection) { + var entity = context.entity(id); + selection.style('display', entity.isNew() ? 'none' : null); - var osmLink = selection.selectAll('.view-on-osm') - .data([entity]); + var $link = selection.selectAll('.view-on-osm') + .data([0]); - var enter = osmLink.enter().append('a') + var $enter = $link.enter().append('a') .attr('class', 'view-on-osm') .attr('target', '_blank'); - enter.append('span') + $enter.append('span') .attr('class', 'icon icon-pre-text out-link'); - enter.append('span') + + $enter.append('span') .text(t('inspector.view_on_osm')); - osmLink.attr('href', context.connection().entityURL(entity)); + $link.attr('href', context.connection().entityURL(entity)); } + + viewOnOSM.entityID = function(_) { + if (!arguments.length) return id; + id = _; + return viewOnOSM; + }; + + return viewOnOSM; }; diff --git a/js/lib/d3.combobox.js b/js/lib/d3.combobox.js index 1b11fc37e..dfc985b6c 100644 --- a/js/lib/d3.combobox.js +++ b/js/lib/d3.combobox.js @@ -12,16 +12,25 @@ d3.combobox = function() { }; var combobox = function(input) { - var idx = -1, container, shown = false; + var idx = -1, + container = d3.select(document.body) + .selectAll('div.combobox') + .filter(function(d) { return d === input.node(); }), + shown = !container.empty(); input .classed('combobox-input', true) .each(function() { var parent = this.parentNode, sibling = this.nextSibling; - d3.select(parent) - .insert('div', function() { return sibling; }) - .attr('class', 'combobox-carat') + + var carat = d3.select(parent).selectAll('.combobox-carat') + .data([0]); + + carat.enter().insert('div', function() { return sibling; }) + .attr('class', 'combobox-carat'); + + carat .on('mousedown', function () { // prevent the form element from blurring. it blurs // on mousedown @@ -50,6 +59,7 @@ d3.combobox = function() { if (!shown) { container = d3.select(document.body) .insert('div', ':first-child') + .datum(input.node()) .attr('class', 'combobox') .style({ position: 'absolute', @@ -201,18 +211,18 @@ d3.combobox = function() { .selectAll('a.combobox-option') .data(data, function(d) { return d.value; }); - options.enter() - .append('a') - .text(function(d) { return d.value; }) + options.enter().append('a') .attr('class', 'combobox-option') - .attr('title', function(d) { return d.title; }) - .on('click', select); - - options.exit().remove(); + .text(function(d) { return d.value; }); options + .attr('title', function(d) { return d.title; }) .classed('selected', function(d, i) { return i == idx; }) + .on('click', select) .order(); + + options.exit() + .remove(); } fetcher.apply(input, [value, data, render]); @@ -234,7 +244,7 @@ d3.combobox = function() { input.node().focus(); update(''); - if (!container) return; + if (container.empty()) return; var entries = container.selectAll('a'), height = container.node().scrollHeight / entries[0].length,