From 6346a1188029cbc9fbeb4014fb4417178f9d0bc4 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Sun, 18 Sep 2016 00:51:43 -0400 Subject: [PATCH] Continuing to audit enter/update selections that need merge() --- modules/lib/d3.combobox.js | 2 +- modules/ui/entity_editor.js | 87 +++++++++++++++++----------- modules/ui/preset.js | 89 ++++++++++++++++++----------- modules/ui/raw_member_editor.js | 34 +++++++---- modules/ui/raw_membership_editor.js | 89 ++++++++++++++++++----------- modules/ui/raw_tag_editor.js | 63 ++++++++++++-------- 6 files changed, 227 insertions(+), 137 deletions(-) diff --git a/modules/lib/d3.combobox.js b/modules/lib/d3.combobox.js index 43bce13b3..56ca04623 100644 --- a/modules/lib/d3.combobox.js +++ b/modules/lib/d3.combobox.js @@ -257,7 +257,7 @@ export function d3combobox() { if (!shown) return; input .property('value', d.value) - .trigger('change'); + .dispatch('change'); event.call('accept', this, d); hide(); } diff --git a/modules/ui/entity_editor.js b/modules/ui/entity_editor.js index 748874b2c..86599ab0e 100644 --- a/modules/ui/entity_editor.js +++ b/modules/ui/entity_editor.js @@ -13,6 +13,7 @@ import { RawTagEditor } from './raw_tag_editor'; import { TagReference } from './tag_reference'; import { preset } from './preset'; + export function EntityEditor(context) { var dispatch = d3.dispatch('choose'), state = 'select', @@ -28,96 +29,106 @@ export function EntityEditor(context) { var rawTagEditor = RawTagEditor(context) .on('change', changeTags); + function entityEditor(selection) { var entity = context.entity(id), tags = _.clone(entity.tags); - var $header = selection.selectAll('.header') + var header = selection.selectAll('.header') .data([0]); // Enter - var $enter = $header.enter().append('div') + var enter = header.enter() + .append('div') .attr('class', 'header fillL cf'); - $enter.append('button') + enter + .append('button') .attr('class', 'fl preset-reset preset-choose') .append('span') .html('◄'); - $enter.append('button') + enter + .append('button') .attr('class', 'fr preset-close') + .on('click', function() { context.enter(Browse(context)); }) .call(Icon(modified ? '#icon-apply' : '#icon-close')); - $enter.append('h3'); + enter + .append('h3'); // Update - $header.select('h3') + header + .merge(enter) + .select('h3') .text(t('inspector.edit')); - $header.select('.preset-close') - .on('click', function() { - context.enter(Browse(context)); - }); - var $body = selection.selectAll('.inspector-body') + var body = selection.selectAll('.inspector-body') .data([0]); // Enter - $enter = $body.enter().append('div') + enter = body.enter() + .append('div') .attr('class', 'inspector-body'); - $enter.append('div') + enter + .append('div') .attr('class', 'preset-list-item inspector-inner') .append('div') .attr('class', 'preset-list-button-wrap') .append('button') .attr('class', 'preset-list-button preset-reset') + .on('click', function() { dispatch.call('choose', this, activePreset); }) .call(tooltip() .title(t('inspector.back_tooltip')) .placement('bottom')) .append('div') .attr('class', 'label'); - $body.select('.preset-list-button-wrap') - .call(reference.button); - - $body.select('.preset-list-item') - .call(reference.body); - - $enter.append('div') + enter + .append('div') .attr('class', 'inspector-border inspector-preset'); - $enter.append('div') + enter + .append('div') .attr('class', 'inspector-border raw-tag-editor inspector-inner'); - $enter.append('div') + enter + .append('div') .attr('class', 'inspector-border raw-member-editor inspector-inner'); - $enter.append('div') + enter + .append('div') .attr('class', 'raw-membership-editor inspector-inner'); - selection.selectAll('.preset-reset') - .on('click', function() { - dispatch.call('choose', this, activePreset); - }); // Update - $body.select('.preset-list-item button') + body = body + .merge(enter); + + body.selectAll('.preset-list-button-wrap') + .call(reference.button); + + body.selectAll('.preset-list-item') + .call(reference.body); + + body.select('.preset-list-item button') .call(PresetIcon() .geometry(context.geometry(id)) .preset(activePreset)); - $body.select('.preset-list-item .label') + body.select('.preset-list-item .label') .text(activePreset.name()); - $body.select('.inspector-preset') + body.select('.inspector-preset') .call(presetEditor .preset(activePreset) .entityID(id) .tags(tags) .state(state)); - $body.select('.raw-tag-editor') + body.select('.raw-tag-editor') .call(rawTagEditor .preset(activePreset) .entityID(id) @@ -125,19 +136,20 @@ export function EntityEditor(context) { .state(state)); if (entity.type === 'relation') { - $body.select('.raw-member-editor') + body.select('.raw-member-editor') .style('display', 'block') .call(RawMemberEditor(context) .entityID(id)); } else { - $body.select('.raw-member-editor') + body.select('.raw-member-editor') .style('display', 'none'); } - $body.select('.raw-membership-editor') + body.select('.raw-membership-editor') .call(RawMembershipEditor(context) .entityID(id)); + function historyChanged() { if (state === 'hide') return; @@ -154,6 +166,7 @@ export function EntityEditor(context) { .on('change.entity-editor', historyChanged); } + function clean(o) { function cleanVal(k, v) { @@ -194,6 +207,7 @@ export function EntityEditor(context) { return out; } + // Tag changes that fire on input can all get coalesced into a single // history operation when the user leaves the field. #2342 function changeTags(changed, onInput) { @@ -214,6 +228,7 @@ export function EntityEditor(context) { } } + entityEditor.modified = function(_) { if (!arguments.length) return modified; modified = _; @@ -221,12 +236,14 @@ export function EntityEditor(context) { .attr('xlink:href', (modified ? '#icon-apply' : '#icon-close')); }; + entityEditor.state = function(_) { if (!arguments.length) return state; state = _; return entityEditor; }; + entityEditor.entityID = function(_) { if (!arguments.length) return id; id = _; @@ -237,6 +254,7 @@ export function EntityEditor(context) { return entityEditor; }; + entityEditor.preset = function(_) { if (!arguments.length) return activePreset; if (_ !== activePreset) { @@ -247,5 +265,6 @@ export function EntityEditor(context) { return entityEditor; }; + return rebind(entityEditor, dispatch, 'on'); } diff --git a/modules/ui/preset.js b/modules/ui/preset.js index b91f2d5c6..c496e160e 100644 --- a/modules/ui/preset.js +++ b/modules/ui/preset.js @@ -10,6 +10,7 @@ import { Icon } from '../svg/index'; import { TagReference } from './tag_reference'; import { fields } from './fields/index'; + export function preset(context) { var dispatch = d3.dispatch('change'), state, @@ -67,10 +68,12 @@ export function preset(context) { return field; } + function fieldKey(field) { return field.id; } + function presets(selection) { selection.call(Disclosure() .title(t('inspector.all_fields')) @@ -83,6 +86,7 @@ export function preset(context) { } } + function content(selection) { if (!fieldsArr) { var entity = context.entity(id), @@ -110,29 +114,37 @@ export function preset(context) { var shown = fieldsArr.filter(function(field) { return field.shown(); }), notShown = fieldsArr.filter(function(field) { return !field.shown(); }); - var $form = selection.selectAll('.preset-form') + + var form = selection.selectAll('.preset-form') .data([0]); - $form.enter().append('div') - .attr('class', 'preset-form inspector-inner fillL3'); + form = form.enter() + .append('div') + .attr('class', 'preset-form inspector-inner fillL3') + .merge(form); - var $fields = $form.selectAll('.form-field') + + var fields = form.selectAll('.form-field') .data(shown, fieldKey); - // Enter + fields.exit() + .remove(); - var $enter = $fields.enter() + // Enter + var enter = fields.enter() .append('div') .attr('class', function(field) { return 'form-field form-field-' + field.id; }); - 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(); }); - var wrap = $label.append('div') + var wrap = label + .append('div') .attr('class', 'form-label-button-wrap'); wrap.append('button') @@ -145,22 +157,21 @@ export function preset(context) { .attr('tabindex', -1) .call(Icon('#icon-undo')); - // Update - $fields.select('.form-label-button-wrap .remove-icon') + // Update + fields = fields + .merge(enter); + + fields.selectAll('.form-label-button-wrap .remove-icon') .on('click', remove); - $fields.select('.modified-icon') + fields.selectAll('.modified-icon') .on('click', revert); - $fields + fields .order() - .classed('modified', function(field) { - return field.modified(); - }) - .classed('present', function(field) { - return field.present(); - }) + .classed('modified', function(field) { return field.modified(); }) + .classed('present', function(field) { return field.present(); }) .each(function(field) { var reference = TagReference(field.reference || {key: field.key}, context); @@ -184,9 +195,6 @@ export function preset(context) { field.input.tags(tags); }); - $fields.exit() - .remove(); - notShown = notShown.map(function(field) { return { title: field.label(), @@ -195,22 +203,35 @@ export function preset(context) { }; }); - var $more = selection.selectAll('.more-fields') + + var more = selection.selectAll('.more-fields') .data((notShown.length > 0) ? [0] : []); - $more.enter().append('div') + more.exit() + .remove(); + + more = more.enter() + .append('div') .attr('class', 'more-fields') .append('label') - .text(t('inspector.add_fields')); + .text(t('inspector.add_fields')) + .merge(more); - var $input = $more.selectAll('.value') + + var input = more.selectAll('.value') .data([0]); - $input.enter().append('input') - .attr('class', 'value') - .attr('type', 'text'); + input.exit() + .remove(); - getSetValue($input, '') + input = input.enter() + .append('input') + .attr('class', 'value') + .attr('type', 'text') + .merge(input); + + input + .call(getSetValue, '') .attr('placeholder', function() { var placeholder = []; for (var field in notShown) { @@ -222,11 +243,6 @@ export function preset(context) { .minItems(1) .on('accept', show)); - $more.exit() - .remove(); - - $input.exit() - .remove(); function show(field) { field = field.field; @@ -248,6 +264,7 @@ export function preset(context) { } } + presets.preset = function(_) { if (!arguments.length) return preset; if (preset && preset.id === _.id) return presets; @@ -256,12 +273,14 @@ export function preset(context) { return presets; }; + presets.state = function(_) { if (!arguments.length) return state; state = _; return presets; }; + presets.tags = function(_) { if (!arguments.length) return tags; tags = _; @@ -269,6 +288,7 @@ export function preset(context) { return presets; }; + presets.entityID = function(_) { if (!arguments.length) return id; if (id === _) return presets; @@ -277,5 +297,6 @@ export function preset(context) { return presets; }; + return rebind(presets, dispatch, 'on'); } diff --git a/modules/ui/raw_member_editor.js b/modules/ui/raw_member_editor.js index 3acb87f99..43df9c0ae 100644 --- a/modules/ui/raw_member_editor.js +++ b/modules/ui/raw_member_editor.js @@ -8,14 +8,17 @@ import { Entity } from '../core/index'; import { Icon } from '../svg/index'; import { displayName } from '../util/index'; + export function RawMemberEditor(context) { var id; + function selectMember(d) { d3.event.preventDefault(); context.enter(Select(context, [d.id])); } + function changeRole(d) { var role = d3.select(this).property('value'); var member = {id: d.id, type: d.type, role: role}; @@ -24,6 +27,7 @@ export function RawMemberEditor(context) { t('operations.change_role.annotation')); } + function deleteMember(d) { context.perform( DeleteMember(d.relation.id, d.index), @@ -34,6 +38,7 @@ export function RawMemberEditor(context) { } } + function rawMemberEditor(selection) { var entity = context.entity(id), memberships = []; @@ -61,24 +66,30 @@ export function RawMemberEditor(context) { } } - function content($wrap) { - var $list = $wrap.selectAll('.member-list') + function content(wrap) { + var list = wrap.selectAll('.member-list') .data([0]); - $list.enter().append('ul') + list.enter() + .append('ul') .attr('class', 'member-list'); - var $items = $list.selectAll('li') + var items = list.selectAll('li') .data(memberships, function(d) { return Entity.key(d.relation) + ',' + d.index + ',' + (d.member ? Entity.key(d.member) : 'incomplete'); }); - var $enter = $items.enter().append('li') + items.exit() + .each(unbind) + .remove(); + + var enter = items.enter() + .append('li') .attr('class', 'member-row form-field') .classed('member-incomplete', function(d) { return !d.member; }); - $enter.each(function(d) { + enter.each(function(d) { if (d.member) { var $label = d3.select(this).append('label') .attr('class', 'form-label') @@ -101,7 +112,7 @@ export function RawMemberEditor(context) { } }); - $enter.append('input') + enter.append('input') .attr('class', 'member-role') .property('type', 'text') .attr('maxlength', 255) @@ -109,19 +120,16 @@ export function RawMemberEditor(context) { .property('value', function(d) { return d.role; }) .on('change', changeRole); - $enter.append('button') + enter.append('button') .attr('tabindex', -1) .attr('class', 'remove button-input-action member-delete minor') .on('click', deleteMember) .call(Icon('#operation-delete')); if (context.taginfo()) { - $enter.each(bindTypeahead); + enter.each(bindTypeahead); } - $items.exit() - .each(unbind) - .remove(); function bindTypeahead(d) { var row = d3.select(this), @@ -163,11 +171,13 @@ export function RawMemberEditor(context) { } } + rawMemberEditor.entityID = function(_) { if (!arguments.length) return id; id = _; return rawMemberEditor; }; + return rawMemberEditor; } diff --git a/modules/ui/raw_membership_editor.js b/modules/ui/raw_membership_editor.js index 755edcb27..561c2bb36 100644 --- a/modules/ui/raw_membership_editor.js +++ b/modules/ui/raw_membership_editor.js @@ -9,14 +9,17 @@ import { Icon } from '../svg/index'; import { Select } from '../modes/index'; import { displayName } from '../util/index'; + export function RawMembershipEditor(context) { var id, showBlank; + function selectRelation(d) { d3.event.preventDefault(); context.enter(Select(context, [d.relation.id])); } + function changeRole(d) { var role = d3.select(this).property('value'); context.perform( @@ -24,6 +27,7 @@ export function RawMembershipEditor(context) { t('operations.change_role.annotation')); } + function addMembership(d, role) { showBlank = false; @@ -44,12 +48,14 @@ export function RawMembershipEditor(context) { } } + function deleteMembership(d) { context.perform( DeleteMember(d.relation.id, d.index), t('operations.delete_member.annotation')); } + function relations(q) { var newRelation = { relation: null, @@ -96,6 +102,7 @@ export function RawMembershipEditor(context) { return result; } + function rawMembershipEditor(selection) { var entity = context.entity(id), memberships = []; @@ -114,40 +121,54 @@ export function RawMembershipEditor(context) { .on('toggled', toggled) .content(content)); + function toggled(expanded) { if (expanded) { selection.node().parentNode.scrollTop += 200; } } - function content($wrap) { - var $list = $wrap.selectAll('.member-list') + + function content(wrap) { + var list = wrap.selectAll('.member-list') .data([0]); - $list.enter().append('ul') - .attr('class', 'member-list'); + list = list.enter() + .append('ul') + .attr('class', 'member-list') + .merge(list); - var $items = $list.selectAll('li.member-row-normal') + + var items = list.selectAll('li.member-row-normal') .data(memberships, function(d) { return Entity.key(d.relation) + ',' + d.index; }); - var $enter = $items.enter().append('li') + items.exit() + .each(unbind) + .remove(); + + var enter = items.enter() + .append('li') .attr('class', 'member-row member-row-normal form-field'); - var $label = $enter.append('label') + var label = enter + .append('label') .attr('class', 'form-label') .append('a') .attr('href', '#') .on('click', selectRelation); - $label.append('span') + label + .append('span') .attr('class', 'member-entity-type') .text(function(d) { return context.presets().match(d.relation, context.graph()).name(); }); - $label.append('span') + label + .append('span') .attr('class', 'member-entity-name') .text(function(d) { return displayName(d.relation); }); - $enter.append('input') + enter + .append('input') .attr('class', 'member-role') .property('type', 'text') .attr('maxlength', 255) @@ -155,71 +176,71 @@ export function RawMembershipEditor(context) { .property('value', function(d) { return d.member.role; }) .on('change', changeRole); - $enter.append('button') + enter + .append('button') .attr('tabindex', -1) .attr('class', 'remove button-input-action member-delete minor') .on('click', deleteMembership) .call(Icon('#operation-delete')); if (context.taginfo()) { - $enter.each(bindTypeahead); + enter.each(bindTypeahead); } - $items.exit() - .each(unbind) - .remove(); if (showBlank) { - var $new = $list.selectAll('.member-row-new') + var newrow = list.selectAll('.member-row-new') .data([0]); - $enter = $new.enter().append('li') + enter = newrow.enter() + .append('li') .attr('class', 'member-row member-row-new form-field'); - $enter.append('input') + enter + .append('input') .attr('type', 'text') .attr('class', 'member-entity-input') .call(d3combobox() .minItems(1) - .fetcher(function(value, callback) { - callback(relations(value)); - }) + .fetcher(function(value, callback) { callback(relations(value)); }) .on('accept', function(d) { - addMembership(d, $new.select('.member-role').property('value')); + addMembership(d, list.selectAll('.member-row-new .member-role').property('value')); })); - $enter.append('input') + enter + .append('input') .attr('class', 'member-role') .property('type', 'text') .attr('maxlength', 255) .attr('placeholder', t('inspector.role')) .on('change', changeRole); - $enter.append('button') + enter + .append('button') .attr('tabindex', -1) .attr('class', 'remove button-input-action member-delete minor') .on('click', deleteMembership) .call(Icon('#operation-delete')); } else { - $list.selectAll('.member-row-new') + list.selectAll('.member-row-new') .remove(); } - var $add = $wrap.selectAll('.add-relation') + + var addrel = wrap.selectAll('.add-relation') .data([0]); - $add.enter() + addrel.enter() .append('button') .attr('class', 'add-relation') - .call(Icon('#icon-plus', 'light')); - - $wrap.selectAll('.add-relation') .on('click', function() { showBlank = true; - content($wrap); - $list.selectAll('.member-entity-input').node().focus(); - }); + content(wrap); + list.selectAll('.member-entity-input').node().focus(); + }) + .call(Icon('#icon-plus', 'light')); + function bindTypeahead(d) { var row = d3.select(this), @@ -261,11 +282,13 @@ export function RawMembershipEditor(context) { } } + rawMembershipEditor.entityID = function(_) { if (!arguments.length) return id; id = _; return rawMembershipEditor; }; + return rawMembershipEditor; } diff --git a/modules/ui/raw_tag_editor.js b/modules/ui/raw_tag_editor.js index b6c3d1243..2427d52a2 100644 --- a/modules/ui/raw_tag_editor.js +++ b/modules/ui/raw_tag_editor.js @@ -7,6 +7,7 @@ import { Disclosure } from './disclosure'; import { Icon } from '../svg/index'; import { TagReference } from './tag_reference'; + export function RawTagEditor(context) { var event = d3.dispatch('change'), showBlank = false, @@ -15,6 +16,7 @@ export function RawTagEditor(context) { tags, id; + function rawTagEditor(selection) { var count = Object.keys(tags).filter(function(d) { return d; }).length; @@ -32,7 +34,8 @@ export function RawTagEditor(context) { } } - function content($wrap) { + + function content(wrap) { var entries = d3.entries(tags); if (!entries.length || showBlank) { @@ -40,61 +43,63 @@ export function RawTagEditor(context) { entries.push({key: '', value: ''}); } - var $list = $wrap.selectAll('.tag-list') + var list = wrap.selectAll('.tag-list') .data([0]); - $list = $list.enter().append('ul') + list = list.enter() + .append('ul') .attr('class', 'tag-list') - .merge($list); + .merge(list); - var $newTag = $wrap.selectAll('.add-tag') + var newTag = wrap.selectAll('.add-tag') .data([0]); - $newTag.enter() + newTag.enter() .append('button') .attr('class', 'add-tag') .call(Icon('#icon-plus', 'light')) - .merge($newTag) + .merge(newTag) .on('click', addTag); - var $items = $list.selectAll('li') + var items = list.selectAll('li') .data(entries, function(d) { return d.key; }); // Enter - var $enter = $items.enter().append('li') + var enter = items.enter() + .append('li') .attr('class', 'tag-row cf'); - $enter.append('div') + enter.append('div') .attr('class', 'key-wrap') .append('input') .property('type', 'text') .attr('class', 'key') .attr('maxlength', 255); - $enter.append('div') + enter.append('div') .attr('class', 'input-wrap-position') .append('input') .property('type', 'text') .attr('class', 'value') .attr('maxlength', 255); - $enter.append('button') + enter.append('button') .attr('tabindex', -1) .attr('class', 'remove minor') .call(Icon('#operation-delete')); if (context.taginfo()) { - $enter.each(bindTypeahead); + enter.each(bindTypeahead); } // Update - $items = $items.merge($enter); + items = items.merge(enter); - $items.order(); + items.order(); - $items.each(function(tag) { + items.each(function(tag) { var isRelation = (context.entity(id).type === 'relation'), reference; if (isRelation && tag.key === 'type') @@ -111,14 +116,14 @@ export function RawTagEditor(context) { .call(reference.body); }); - getSetValue($items.select('input.key') + getSetValue(items.selectAll('input.key') .attr('title', function(d) { return d.key; }) .on('blur', keyChange) .on('change', keyChange), function(d) { return d.key; } ); - getSetValue($items.select('input.value') + getSetValue(items.selectAll('input.value') .attr('title', function(d) { return d.value; }) .on('blur', valueChange) .on('change', valueChange) @@ -126,20 +131,22 @@ export function RawTagEditor(context) { function(d) { return d.value; } ); - $items.select('button.remove') + items.select('button.remove') .on('click', removeTag); - $items.exit() + items.exit() .each(unbind) .remove(); + function pushMore() { if (d3.event.keyCode === 9 && !d3.event.shiftKey && - $list.selectAll('li:last-child input.value').node() === this) { + list.selectAll('li:last-child input.value').node() === this) { addTag(); } } + function bindTypeahead() { var row = d3.select(this), key = row.selectAll('input.key'), @@ -182,6 +189,7 @@ export function RawTagEditor(context) { })); } + function unbind() { var row = d3.select(this); @@ -192,6 +200,7 @@ export function RawTagEditor(context) { .call(d3combobox.off); } + function keyChange(d) { var kOld = d.key, kNew = this.value.trim(), @@ -212,12 +221,14 @@ export function RawTagEditor(context) { event.call('change', this, tag); } + function valueChange(d) { var tag = {}; tag[d.key] = this.value; event.call('change', this, tag); } + function removeTag(d) { var tag = {}; tag[d.key] = undefined; @@ -225,41 +236,47 @@ export function RawTagEditor(context) { d3.select(this.parentNode).remove(); } + function addTag() { // Wrapped in a setTimeout in case it's being called from a blur // handler. Without the setTimeout, the call to `content` would // wipe out the pending value change. setTimeout(function() { showBlank = true; - content($wrap); - $list.selectAll('li:last-child input.key').node().focus(); + content(wrap); + list.selectAll('li:last-child input.key').node().focus(); }, 0); } } + rawTagEditor.state = function(_) { if (!arguments.length) return state; state = _; return rawTagEditor; }; + 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 rebind(rawTagEditor, event, 'on'); }