Continuing to audit enter/update selections that need merge()

This commit is contained in:
Bryan Housel
2016-09-18 00:51:43 -04:00
parent a6d683eaf7
commit 6346a11880
6 changed files with 227 additions and 137 deletions
+1 -1
View File
@@ -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();
}
+53 -34
View File
@@ -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');
}
+55 -34
View File
@@ -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');
}
+22 -12
View File
@@ -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;
}
+56 -33
View File
@@ -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;
}
+40 -23
View File
@@ -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');
}