d3-metatable
Version:
a table element for heterogenous objects
255 lines (212 loc) • 9.13 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-selection'), require('d3-dispatch'), require('d3-array'), require('d3-collection')) :
typeof define === 'function' && define.amd ? define(['exports', 'd3-selection', 'd3-dispatch', 'd3-array', 'd3-collection'], factory) :
(factory((global.d3 = global.d3 || {}),global.d3,global.d3,global.d3,global.d3));
}(this, (function (exports,d3Selection,d3Dispatch,d3Array,d3Collection) { 'use strict';
var metatable = function (options) {
var dispatcher = d3Dispatch.dispatch('change', 'rowfocus', 'renameprompt', 'deleteprompt');
var _renamePrompt = true;
var _deletePrompt = true;
options = options || {};
var config = {
newCol: options.newCol !== false,
renameCol: options.renameCol !== false,
deleteCol: options.deleteCol !== false
};
function table(selection) {
selection.each(function(d) {
var sel = d3Selection.select(this),
table;
var keyset = d3Collection.set();
d.map(Object.keys).forEach(function(k) {
k.forEach(function(_) {
keyset.add(_);
});
});
var keys = keyset.values();
bootstrap();
paint();
dispatcher.preventprompt = function(which) {
switch(which) {
case 'rename':
_renamePrompt = false;
break;
case 'delete':
_deletePrompt = false;
break;
}
};
function bootstrap() {
var controls = sel
.selectAll('.controls')
.data([d])
.enter()
.append('div')
.attr('class', 'controls');
if (config.newCol) {
controls.append('a')
.text('New column')
.attr('href', '#')
.attr('class', 'button icon plus')
.on('click', function() {
d3Selection.event.preventDefault();
var name = prompt('column name');
if (name) {
keys.push(name);
paint();
}
});
}
var enter = sel.selectAll('table').data([d]).enter().append('table');
var thead = enter.append('thead');
var tbody = enter.append('tbody');
var tr = thead.append('tr');
table = sel.select('table');
}
function paint() {
var th = table
.select('thead')
.select('tr')
.selectAll('th')
.data(keys, function(d) { return d; });
th.exit().remove();
var thEnter = th.enter()
.append('th')
.text(String);
var actionLinks = thEnter
.append('div')
.attr('class', 'small');
if (config.deleteCol) {
actionLinks
.append('a')
.attr('href', '#')
.attr('class', 'icon trash')
.text('Delete')
.on('click', deleteClick);
}
if (config.renameCol) {
actionLinks
.append('a')
.attr('href', '#')
.attr('class', 'icon pencil')
.text('Rename')
.on('click', renameClick);
}
var tr = table.select('tbody').selectAll('tr')
.data(function(d) { return d; });
tr.exit().remove();
tr = tr.enter().append('tr').merge(tr);
var td = tr.selectAll('td')
.data(keys, function(d) { return d; });
td.exit().remove();
td.enter()
.append('td')
.append('textarea')
.attr('field', String);
function deleteClick(d) {
d3Selection.event.preventDefault();
var name = d;
dispatcher.call('deleteprompt', dispatcher, d, completeDelete);
if (_deletePrompt && confirm('Delete column ' + name + '?')) {
completeDelete(d);
}
_deletePrompt = true;
}
function completeDelete(name) {
keys.splice(keys.indexOf(name), 1);
tr.selectAll('textarea')
.data(function(d, i) {
var m = d3Collection.map(d);
m.remove(name);
var reduced = mapToObject(m);
dispatcher.call('change', dispatcher, reduced, i);
return {
data: reduced,
index: i
};
});
paint();
}
function renameClick(d) {
d3Selection.event.preventDefault();
var name = d;
dispatcher.call('renameprompt', dispatcher, d, completeRename);
var newname = (_renamePrompt) ?
prompt('New name for column ' + name + '?') :
undefined;
if (_renamePrompt && newname) {
completeRename(newname, name);
}
_renamePrompt = true;
}
function completeRename(value, name) {
keys.splice(keys.indexOf(name), 1, value);
tr.selectAll('textarea')
.data(function(d, i) {
var m = d3Collection.map(d);
m.set(value, m.get(name));
m.remove(name);
var reduced = mapToObject(m);
dispatcher.call('change', dispatcher, reduced, i);
return {
data: reduced,
index: i
};
});
paint();
}
function coerceNum(x) {
var fl = parseFloat(x);
if (fl.toString() === x) return fl;
else return x;
}
function write(d) {
d.data[d3Selection.select(this).attr('field')] = coerceNum(this.value);
dispatcher.call('change', dispatcher, d.data, d.index);
}
function mapToObject(m) {
return m.entries()
.reduce(function(memo, d) {
memo[d.key] = d.value;
return memo;
}, {});
}
tr.selectAll('textarea')
.data(function(d, i) {
return d3Array.range(keys.length).map(function() {
return {
data: d,
index: i
};
});
})
.classed('disabled', function(d) {
return d.data[d3Selection.select(this).attr('field')] === undefined;
})
.property('value', function(d) {
var value = d.data[d3Selection.select(this).attr('field')];
return !isNaN(value) ? value : value || '';
})
.on('keyup', write)
.on('change', write)
.on('click', function(d) {
if (d.data[d3Selection.select(this).attr('field')] === undefined) {
d.data[d3Selection.select(this).attr('field')] = '';
paint();
}
})
.on('focus', function(d) {
dispatcher.call('rowfocus', dispatcher, d.data, d.index);
});
}
});
}
table.on = function () {
dispatcher.on.apply(dispatcher, arguments);
return table;
};
return table;
};
exports.metatable = metatable;
Object.defineProperty(exports, '__esModule', { value: true });
})));