d3-view
Version:
View-model d3 plugin for building interactive data-driven web interfaces
3,775 lines • 97.6 kB
JavaScript
// d3-view v1.5.2 Copyright 2018 quantmind.com
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-let'), require('d3-selection'), require('d3-transition'), require('d3-timer'), require('d3-dispatch')) :
typeof define === 'function' && define.amd ? define(['exports', 'd3-let', 'd3-selection', 'd3-transition', 'd3-timer', 'd3-dispatch'], factory) :
(factory((global.d3 = global.d3 || {}),global.d3,global.d3,global.d3,global.d3,global.d3));
}(this, (function (exports,d3Let,d3Selection,d3Transition,d3Timer,d3Dispatch) { 'use strict';
var version = "1.5.2";
const isAbsolute = new RegExp("^([a-z]+://|//)"),
isRelative = new RegExp("^[.]{0,2}/"),
hasOwnProperty = Array.prototype.hasOwnProperty;
const viewLibs = new Map();
const isAbsoluteUrl = url => isAbsolute.test(url);
const viewResolve = (name, options) => {
var dist = viewLibs.get(name),
main = name,
path = null,
base = typeof location !== "undefined" ? location : "";
if (options) {
if (typeof options.base === "string") base = options.base;
path = removeFrontSlash(options.path);
}
if (dist) {
path = path || dist.main;
main = removeBackSlash(dist.origin || main);
if (dist.version) main = `${name}@${dist.version}`;
if (path) main = `${main}/${path}`;
} else if (path) {
if (isAbsolute.test(main)) main = new URL(main, base).origin;
else if (isRelative.test(main)) main = "";
main = `${main}/${path}`;
}
if (isAbsolute.test(main)) {
return main;
} else if (isRelative.test(main)) {
return new URL(main, base).href;
} else {
if (!main.length || /^[\s._]/.test(main) || /\s$/.test(main))
throw new Error("illegal name");
return "https://unpkg.com/" + main;
}
};
const viewRequireFrom = (resolver, root) => {
const modules = new Map(),
queue = [],
map = queue.map,
some = queue.some,
getRoot = () => {
return !root && typeof window !== "undefined" ? window : root;
},
requireRelative = base => {
return name => {
var url = resolver(name + "", base),
module = modules.get(url);
if (!module)
modules.set(
url,
(module = new Promise((resolve, reject) => {
root = getRoot();
const script = root.document.createElement("script");
script.onload = function() {
try {
resolve(queue.pop()(requireRelative(url)));
} catch (error) {
reject(new Error("invalid module"));
}
script.remove();
};
script.onerror = function() {
reject(new Error("unable to load module"));
script.remove();
};
script.async = true;
script.src = url;
root.define = define;
root.document.head.appendChild(script);
}))
);
return module;
};
},
requireOne = requireRelative(null);
function require(name) {
return arguments.length > 1
? Promise.all(map.call(arguments, requireOne)).then(merge)
: requireOne(name);
}
require.root = getRoot;
define.amd = {};
return require;
function define(name, dependencies, factory) {
if (arguments.length < 3) (factory = dependencies), (dependencies = name);
if (arguments.length < 2) (factory = dependencies), (dependencies = []);
queue.push(
some.call(dependencies, isexports)
? function(require) {
var exports = {};
return Promise.all(
map.call(dependencies, function(name) {
return isexports((name += "")) ? exports : require(name);
})
).then(function(dependencies) {
factory.apply(null, dependencies);
return exports;
});
}
: function(require) {
return Promise.all(map.call(dependencies, require)).then(function(
dependencies
) {
return typeof factory === "function"
? factory.apply(null, dependencies)
: factory;
});
}
);
}
};
const viewRequire = viewRequireFrom(viewResolve);
// INTERNALS
const isexports = name => name + "" === "exports";
const removeFrontSlash = path => {
if (typeof path === "string" && path.substring(0, 1) === "/")
path = path.substring(1);
return path;
};
const removeBackSlash = path => {
if (typeof path === "string" && path.substring(path.length - 1) === "/")
path = path.substring(0, path.substring);
return path;
};
const merge = modules => {
var o = {},
i = -1,
n = modules.length,
m,
k;
while (++i < n) {
for (k in (m = modules[i])) {
if (hasOwnProperty.call(m, k)) {
if (m[k] == null) Object.defineProperty(o, k, { get: getter(m, k) });
else o[k] = m[k];
}
}
}
return o;
};
const getter = (object, name) => {
return () => object[name];
};
d3Let.logger.debug = null;
var providers = {
//
require: viewRequire,
//
resolve: viewResolve,
//
// log messages
logger: d3Let.logger,
//
// callbacks when page is loaded in browser
readyCallbacks: [],
// Set/unset debug
setDebug(active) {
if (!arguments.length || active)
this.logger.debug = d3Let.isFunction(active) ? active : defaultDebug;
else this.logger.debug = null;
},
fetch: d3Let.inBrowser ? window.fetch : null
};
function defaultDebug(msg) {
this.info(msg);
}
function HttpResponse(response, data) {
this.response = response;
this.data = data;
this.status = response.status;
this.headers = response.headers;
this.description = response.statusText;
}
function HttpError(response, data, description) {
this.response = response;
this.data = data;
this.status = response.status;
this.headers = response.headers;
this.description = description || response.statusText;
}
function jsonResponse(response) {
var ct = (response.headers.get("content-type") || "").split(";")[0];
if (ct === "application/json")
return response.json().then(data => new HttpResponse(response, data));
else {
var msg =
response.status < 300
? `Expected "application/json" content type, got "${ct}"`
: null;
throw new HttpError(response, null, msg);
}
}
function textResponse(response) {
if (response.status < 300)
return response.text().then(data => new HttpResponse(response, data));
else {
throw new HttpError(response);
}
}
HttpError.prototype = Error.prototype;
var asSelect = el => (!el || !d3Let.isFunction(el.size) ? d3Selection.select(el) : el);
const prefix = "[d3-view]";
var warn = msg => providers.logger.warn(`${prefix} ${msg}`);
const template = (source, context) => {
if (d3Let.isString(source)) {
if (context) {
var s = compile(source);
if (!s) return source;
source = s;
} else return source;
}
return source(context);
};
const htmlElement = (source, context, ownerDocument) => {
ownerDocument = ownerDocument || document;
var el = d3Selection.select(ownerDocument.createElement("div"));
el.html(template(source, context));
var children = el.node().children;
if (children.length !== 1)
warn(
`viewElement function should return one root element only, got ${
children.length
}`
);
return children[0];
};
const compile = text => {
var compile = providers.compileTemplate;
if (compile) return compile(text);
warn(
"No compileTemplate function available in viewProviders, cannot render template"
);
};
//
// Base d3-view Object
// =====================
//
var base = {
// available once mounted
// this is the browser window.document unless we are mounting on jsdom
ownerDocument: null,
//
// d3-view object
isd3: true,
//
providers: providers,
//
// Create a view element from a template and optional context
viewElement(source, context, ownerDocument) {
return htmlElement(source, context, ownerDocument || this.ownerDocument);
},
//
select(selector) {
if (typeof selector === "string")
return d3Selection.select(this.ownerDocument || document).select(selector);
return d3Selection.select(selector);
},
//
selectAll(selector) {
if (typeof selector === "string")
return d3Selection.select(this.ownerDocument || document).selectAll(selector);
return d3Selection.selectAll(selector);
},
//
// Shortcut for fetch function in providers
fetch(url, options) {
var fetch = providers.fetch;
return arguments.length == 1 ? fetch(url) : fetch(url, options);
},
//
fetchText(url, ...x) {
return this.fetch(url, ...x).then(textResponse);
},
//
json(url, ...x) {
return this.fetch(url, ...x).then(jsonResponse);
},
//
// render a template from a url
renderFromUrl(url, context, asElement = true) {
var cache = this.cache;
if (cache.has(url))
return Promise.resolve(render(cache.get(url), context, asElement));
return this.fetchText(url).then(response => {
cache.set(url, response.data);
return render(response.data, context, asElement);
});
},
//
// render from a distribution name. Use d3-require resolve method to find the url
renderFromDist(dist, path, context, asElement = true) {
var resolve = providers.resolve;
return this.renderFromUrl(
resolve(dist, { path: path }),
context,
asElement
);
},
//
on(el, name, callback) {
el = asSelect(el);
if (callback === null) return el.on(name, null);
else el.on(name, () => callback(d3Selection.event));
},
//
selectChildren(el) {
if (!arguments.length) el = this.el;
return this.selectAll(Array.prototype.slice.call(el.children));
},
//
domEvent() {
return d3Selection.event;
},
//
logError(err) {
if (err.stack) providers.logger.error(err);
else providers.logger.error(`[${this.name}] ${err}`);
return this;
},
//
logWarn(msg) {
providers.logger.warn(`[${this.name}] ${msg}`);
return this;
},
//
logInfo(msg) {
providers.logger.info(`[${this.name}] ${msg}`);
return this;
},
//
logDebug(msg) {
if (providers.logger.debug) providers.logger.debug(`[${this.name}] ${msg}`);
return this;
},
require() {
const root = this.ownerDocument.defaultView;
if (!root.d3) root.d3 = {};
let require = root.d3.require;
if (!require) {
if (this.providers.require.root() === root)
require = this.providers.require;
else require = viewRequireFrom(viewResolve, root);
root.d3.require = require;
}
return require.apply(undefined, arguments);
}
};
const render = (text, context, asElement) =>
asElement ? htmlElement(text, context) : template(text, context);
providers.transition = {
duration: 0
};
var base$1 = Object.assign(base, {
//
// return a transition object if possible
transition(sel) {
if (!arguments.length) sel = this.sel;
var duration = this.transitionDuration(sel);
if (duration > 0) return sel.transition(this.uid).duration(duration);
},
transitionDuration(sel) {
if (!arguments.length) sel = this.sel;
if (sel && sel.size()) {
var duration = sel.attr("data-transition-duration");
return +(duration || providers.transition.duration);
}
return 0;
}
});
function formCheck(field, wrappedEl, fieldEl) {
var data = field.props;
fieldEl.classed("custom-control-input", true);
return field.wrapTemplate(wrappedEl, groupTpl(data));
}
function groupTpl(data) {
return `
<div class="custom-control custom-checkbox">
<slot></slot>
<label class="custom-control-label" for="${data.id}">${
data.label
}</label>
</div>
`;
}
var error = (field, wrappedEl) => {
return field.wrapTemplate(
wrappedEl,
`<slot></slot><div class="invalid-feedback" d3-html="error"></div>`
);
};
var formGroup = (field, wrappedEl, fieldEl) => {
var data = field.props,
size = data.size !== undefined ? data.size : data.form.props.size,
fc = size ? `form-control form-control-${size}` : "form-control";
fieldEl
.classed(fc, true)
.attr(
"d3-class",
`[${data.required ||
false} ? "form-control-required" : null, showError ? "is-invalid" : null]`
);
return field.wrapTemplate(
wrappedEl,
`<div class="form-group"><slot></slot></div>`
);
};
function help(field, wrappedEl, fieldEl) {
var data = field.props;
if (data.help) {
fieldEl.attr("aria-describedby", `help-${data.id}`);
return field.wrapTemplate(wrappedEl, helpTpl(data));
} else return wrappedEl;
}
function helpTpl(data) {
return `<slot></slot><small id="help-${
data.id
}" class="form-text text-muted">${data.help}</small>`;
}
function inputGroup(field, wrappedEl, fieldEl) {
var data = field.props,
ig = data.group;
if (!ig) return wrappedEl;
var gid = `g${fieldEl.attr("id")}`;
fieldEl.attr("aria-describedby", gid);
return field.wrapTemplate(wrappedEl, groupTpl$1(gid, ig));
}
function groupTpl$1(gid, group) {
return `
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text" id="${gid}">${group}</span>
</div>
<slot></slot>
</div>
`;
}
function label(field, wrappedEl) {
var data = field.props;
return field.wrapTemplate(wrappedEl, labelTpl(data));
}
function labelTpl(data) {
var label = data.label || data.name;
return `<label for=${
data.id
} class="control-label" d3-class="[srOnly ? 'sr-only' : null]">${label}</label>
<slot></slot>`;
}
const groupTpl$2 = `<div class="form-group">
<slot></slot>
</div>`;
function submit(field, wrappedEl, fieldEl) {
var data = field.props,
theme = data.theme || "primary";
fieldEl.classed("btn", true).classed(`btn-${theme}`, true);
return field.wrapTemplate(wrappedEl, groupTpl$2);
}
const bootstrap = {
input: ["error", "inputGroup", "label", "help", "formGroup"],
checkbox: ["error", "formCheck", "help", "formGroup"],
textarea: ["error", "label", "help", "formGroup"],
select: ["error", "label", "help", "formGroup"],
submit: ["submit"],
wrappers: {
error,
label,
formGroup,
inputGroup,
formCheck,
submit,
help
}
};
//
// Bootstrap plugin
// ===================
//
// Simply add a new form extension to wrap form fields
//
var plugin = {
install(vm) {
if (!vm.$formExtensions)
return vm.logWarn(
"form bootstrap requires the form plugin installed first!"
);
vm.$formExtensions.push(wrapBootstrap);
}
};
function wrapBootstrap(field, wrappedEl, fieldEl) {
var wrappers =
bootstrap[fieldEl.attr("type")] ||
bootstrap[fieldEl.node().tagName.toLowerCase()];
if (!wrappers) return wrappedEl;
let wrap;
wrappers.forEach(wrapper => {
wrap = bootstrap.wrappers[wrapper];
if (wrap) wrappedEl = wrap(field, wrappedEl, fieldEl);
else field.logWarn(`Could not find form field wrapper ${wrapper}`);
});
return wrappedEl;
}
//
// Form Actions
var actions = {
submit: submit$1
};
const messages = {
default: "<strong>Error!</strong> Could not submit form"
};
const endpointDefaults = {
contentType: "application/json",
method: "POST"
};
//
// Submit action
function submit$1(e) {
var submit = this,
view = submit.$$view,
form = view.props.form,
endpoint = d3Let.assign({}, endpointDefaults, submit.props.endpoint);
if (e) {
e.preventDefault();
e.stopPropagation();
}
var data = form.$inputData(),
options = {};
if (!endpoint.url) {
view.logError("No url, cannot submit form");
return;
}
if (endpoint.contentType === "application/json") {
options.headers = {
"Content-Type": endpoint.contentType
};
options.body = JSON.stringify(data);
} else {
options.body = new FormData();
for (var key in data) options.body.append(key, data[key]);
}
// Flag the form as submitted
if (!form.$setSubmit()) {
// form not valid, don't bother with request
form.$setSubmitDone();
} else {
options.method = endpoint.method;
view.json(endpoint.url, options).then(done, done);
}
function done(response) {
form.$setSubmitDone();
if (response.status && response.headers) form.$response(response);
else
form.$emit("formMessage", {
level: "error",
message: messages[response.message] || messages.default
});
}
}
var properties = new Map([
["disabled", "disabled"],
["readonly", "readOnly"],
["required", "required"]
]);
const componentsFromType = {
text: "input",
email: "input",
password: "input",
checkbox: "input",
number: "input",
date: "input",
url: "input",
"datetime-local": "input"
};
const formComponent = child => {
var type = child.type || "text";
return componentsFromType[type] || type;
};
const addAttributes = (el, attributes) => {
var expr, attr;
if (!d3Let.isObject(attributes)) return;
for (attr in attributes) {
expr = attributes[attr];
if (d3Let.isObject(expr)) expr = JSON.stringify(expr);
el.attr(attr, expr || "");
}
};
function formChild(child) {
var component = formComponent(child);
return document.createElement(`d3-form-${component}`);
}
//
// Mixin for all form elements
const formElement = {
props: ["form"],
addChildren(sel, form) {
var children = this.props.children;
if (children) {
if (!d3Let.isArray(children)) {
this.logError(
`children should be an array of fields, got ${typeof children}`
);
return sel;
}
if (form)
children.forEach(c => {
c.form = form;
});
sel
.selectAll(".d3form")
.data(children)
.enter()
.append(formChild)
.attr("form", "form")
.classed("d3form", true);
}
return sel;
},
init(el) {
addAttributes(el, this.props.attributes);
properties.forEach((prop, key) => {
var value = this.props[key];
if (value) {
if (d3Let.isString(value)) el.attr(`d3-attr-${key}`, value);
else el.property(prop, true);
}
});
return el;
},
// wrap the form element with extensions
wrap(fieldEl) {
var wrappedEl = fieldEl;
this.root.$formExtensions.forEach(extension => {
wrappedEl = extension(this, wrappedEl, fieldEl) || wrappedEl;
});
return wrappedEl;
},
wrapTemplate(sel, template) {
var outer = this.createElement("div").html(template),
slot = outer.select("slot");
if (!slot.size()) {
this.logWarn("template does not provide a slot element");
return sel;
}
var target = this.select(slot.node().parentNode);
sel.nodes().forEach(function(node) {
target.insert(() => {
return node;
}, "slot");
});
slot.remove();
return this.selectAll(outer.node().children);
}
};
// A mixin for all form field components
var field = d3Let.assign({}, formElement, {
model: {
value: null,
error: "",
isDirty: null,
changed: false,
srOnly: false,
placeholder: "",
showError: {
reactOn: ["error", "isDirty"],
get() {
if (this.error) return this.isDirty;
return false;
}
},
// default validate function does nothing, IMPORTANT!
$validate() {}
},
init(el) {
// call parent method
formElement.init.call(this, el);
const props = this.props,
model = this.model;
if (!props.name) {
this.logError("Input field without a name");
return el;
}
el.attr("name", props.name);
if (!props.placeholder) props.placeholder = props.label || props.name;
//
// give name to model (for debugging info messages)
model.name = props.name;
//
// bind to the value property (two-way binding when used with d3-value)
model.$on("value", () => {
// set isDirty to false if first time here, otherwise true
if (model.isDirty === null) {
model.isDirty = false;
} else {
model.isDirty = true;
model.changed = true;
}
// trigger a change event in the form
// required for form method such as $isValid
props.form.$change();
model.$emit("formFieldChange", model);
});
return el;
},
mounted() {
// add this model to the form inputs object
this.props.form.inputs[this.props.name] = this.model;
}
});
var map = obj => {
if (obj && obj.constructor === Object) obj = Object.entries(obj);
return new Map(obj);
};
const required = {
set(el, data) {
if (data.required) el.property("required", true);
},
validate(el, value) {
if (el.property("required"))
if (!value) return "required";
else if (value === "") {
// this is valid, no need to continue with the remaining validators
return true;
}
}
};
const minLength = {
set(el, data) {
var value = +data.minLength;
if (value === value) el.attr("minlength", value);
},
validate(el, value) {
var l = +el.attr("minlength");
if (l === l && l > 0 && value.length < l)
return `too short - ${l} characters or more expected`;
}
};
const maxLength = {
set(el, data) {
var value = +data.maxLength;
if (value === value) el.attr("maxlength", value);
},
validate(el, value) {
var l = +el.attr("maxlength");
if (l === l && l > 0 && value && value.length > l)
return `too long - ${l} characters or less expected`;
}
};
const minimum = {
set(el, data) {
var value = +data.minimum;
if (value === value) el.attr("min", value);
},
validate(el, value) {
var r = range(el);
if (r && +value < r[0]) return `must be greater or equal ${r[0]}`;
}
};
const maximum = {
set(el, data) {
var value = +data.maximum;
if (value === value) el.attr("max", value);
},
validate(el, value) {
var r = range(el);
if (r && +value > r[1]) return `must be less or equal ${r[1]}`;
}
};
var validators = map({
required,
minLength,
maxLength,
minimum,
maximum
});
const range = el => {
var l0 = el.attr("min"),
l1 = el.attr("max");
l0 = l0 === null ? -Infinity : +l0;
l1 = l1 === null ? Infinity : +l1;
return [l0, l1];
};
// validator singleton
var validators$1 = (vm, el) => {
validators.forEach(validator => {
if (validator.set) validator.set(el, vm.props);
});
vm.model.$on("value.validate", validate);
vm.model.$validate = validate;
};
function validate() {
var vm = this.$$view,
value = this.value,
el =
vm.sel.attr("id") === this.props.id
? vm.sel
: vm.sel.select(`#${this.props.id}`),
msg;
for (let validator of validators.values()) {
msg = validator.validate(el, value);
if (msg) {
if (msg === true) msg = "";
break;
}
}
this.error = msg || "";
}
const checks = ["checkbox", "radio"];
//
// Input element
var input = d3Let.assign({}, field, {
render() {
const el = this.init(this.createElement("input", true));
el.attr("type", this.props.type || "text").attr("d3-value", "value");
if (checks.indexOf(el.attr("type")) === -1 && this.props.placeholder)
el.attr("placeholder", this.props.placeholder);
validators$1(this, el);
return this.wrap(el);
}
});
//
// Select element
var select = d3Let.assign({}, field, {
model: d3Let.assign(
{
options: []
},
field.model
),
render() {
const el = this.init(this.createElement("select", true));
this.model.options = asOptions(this.model.options);
el.attr("d3-value", "value")
.attr("placeholder", this.props.placeholder)
.append("option")
.attr("d3-for", "option in options")
.attr("d3-html", "option.label")
.attr("d3-attr-value", "option.value");
validators$1(this, el);
return this.wrap(el);
}
});
const asOptions = options => {
if (!d3Let.isArray(options)) options = [];
options.forEach((opt, idx) => {
if (d3Let.isArray(opt))
opt = {
value: opt[0],
label: opt[1] || opt[0]
};
else if (d3Let.isString(opt))
opt = {
value: opt,
label: opt
};
options[idx] = opt;
});
return options;
};
//
// Fieldset element
var fieldset = d3Let.assign({}, formElement, {
render() {
const tag = this.props.tag || "fieldset",
el = this.init(this.createElement(tag, true));
return this.addChildren(el);
}
});
//
// Submit element
var submit$2 = d3Let.assign({}, formElement, {
model: {
$submit() {
if (this.$event && this.$event.defaultPrevented) return;
this.props.form.actions.submit.call(this, this.$event);
}
},
render() {
const tag = this.props.tag || "button",
el = this.init(this.createElement(tag, true));
el.attr("type", this.props.type || "submit")
.attr("name", this.props.name || "_submit_")
.attr("d3-on-click", "$submit()")
.html(this.props.label || "submit");
return this.wrap(el);
}
});
//
// Textarea element
var textarea = d3Let.assign({}, field, {
render() {
var el = this.init(this.createElement("textarea", true));
el.attr("placeholder", this.props.placeholder).attr("d3-value", "value");
validators$1(this, el);
return this.wrap(el);
}
});
//
// Form Responses
// ====================
//
// To add/override responses:
//
// import viewForms from 'd3-view'
//
// viewForms.responses.myresponse = function (data, status, headers) {
// ...
// }
var responses = {
default: defaultResponse,
redirect
};
// The default response emit a formMessage to event to parent models
function defaultResponse(response) {
var level =
response.status < 300
? "info"
: response.status < 500
? "warning"
: "error";
this.$emit("formMessage", {
level: level,
data: response.data,
response: response
});
}
function redirect(response) {
var location = this.$$view.providers.location;
location.href = response.data.redirectTo || "/";
}
// Main form component
var form = d3Let.assign({}, formElement, {
// Allow to specify and initial values
props: ["url", "values", "form"],
components: {
"d3-form-fieldset": fieldset,
"d3-form-input": input,
"d3-form-textarea": textarea,
"d3-form-select": select,
"d3-form-submit": submit$2
},
model: {
form: null, // parent form
formSubmitted: false,
formPending: false,
$isValid(submitting) {
let inp,
valid = true;
for (var key in this.inputs) {
inp = this.inputs[key];
if (submitting) inp.isDirty = true;
inp.$validate();
if (inp.error) valid = false;
}
return valid;
},
$setSubmit() {
this.formSubmitted = true;
this.formPending = true;
return this.$isValid(true);
},
$setSubmitDone() {
this.formPending = false;
},
$inputData() {
var inputs = this.inputs,
data = {},
value;
for (var key in inputs) {
value = inputs[key].value;
if (value || input.changed) data[key] = value;
}
return data;
},
//
// response from a server submit
$response(response) {
if (response.data) {
if (this.props.resultHandler) {
var handler = responses[this.props.resultHandler];
if (!handler)
this.$$view.logError(
`Could not find ${this.props.resultHandler} result handler`
);
else handler.call(this, response);
} else {
responses.default.call(this, response);
}
} else this.$responseError(response);
},
//
// bad response from server submit
$responseError(response) {
this.$emit("formMessage", {
level: "error",
data: response.data,
response: response
});
}
},
render() {
if (this.props.url)
return this.json(this.props.url).then(response =>
this.build(response.data)
);
else return this.build();
},
childrenMounted() {
var model = this.model,
values = this.props.values;
if (values)
Object.keys(values).forEach(key => {
var inp = model.inputs[key];
if (inp) inp.value = values[key];
});
},
build(props) {
if (props) this.props = d3Let.assign(this.props, props);
const form = this.init(
this.createElement("form", true).attr("novalidate", "")
),
model = this.model;
//
model.$formExtensions = this.root.$formExtensions || [];
model.inputs = {};
model.actions = {};
//
// Form actions
for (var key in actions) {
var action = this.props[key];
if (d3Let.isString(action)) action = this.model.$get(action);
model.actions[key] = action || actions[key];
}
return this.addChildren(form, model);
}
});
// Forms plugin
var plugin$1 = {
install(vm) {
vm.addComponent("d3form", form);
// list of form Extensions
vm.$formExtensions = [];
},
actions,
responses,
validators
};
if (d3Let.inBrowser && window.MutationObserver) {
// DOM observer
// Check for changes in the DOM that leads to visual actions
const observer = new window.MutationObserver(Manager);
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
}
//
// Clears element going out of scope
function Manager(records) {
let sel, nodes, node, vm;
records.forEach(record => {
nodes = record.removedNodes;
if (!nodes || !nodes.length) return;
vm = record.target ? d3Selection.select(record.target).view() : null;
for (let i = 0; i < nodes.length; ++i) {
node = nodes[i];
if (!node.querySelectorAll || node.__d3_component__) continue;
sel = d3Selection.select(node);
if (vm || sel.view()) {
sel.selectAll("*").each(destroy);
destroy.call(nodes[i]);
}
}
});
}
function destroy() {
var dirs = this.__d3_directives__,
view = this.__d3_view__;
if (dirs) {
dirs.all.forEach(d => d.destroy());
delete this.__d3_directives__;
}
if (view) {
view.destroy();
delete this.__d3_view__;
}
}
function $push(attr, value) {
var array = this[attr].slice();
array.push(value);
this[attr] = array;
return this;
}
function $splice(attr, idx, count) {
var array = this[attr].slice();
if (arguments.length == 2) array.splice(idx);
else if (arguments.length == 3) array.splice(idx, count);
this[attr] = array;
return this;
}
let UID = 0;
const prefix$1 = "d3v";
// Add a unique identifier to an object
function uid(o) {
var uid = prefix$1 + ++UID;
if (arguments.length) {
Object.defineProperty(o, "uid", {
get: function() {
return uid;
}
});
return o;
} else return uid;
}
//
// Evaluate a callback (optional) with given delay (optional)
//
// if delay is not given or 0, the callback is evaluated at the next tick
// of the event loop.
// Calling this method multiple times in the same event loop tick produces
// always the initial promise
var debounce = (callback, delay) => {
let promise = null,
self,
args;
function debounce(...rest) {
self = this;
args = rest;
if (promise !== null) return promise;
promise = new Promise((resolve, reject) => {
d3Timer.timeout(() => {
promise = null;
try {
resolve(callback ? callback.apply(self, args) : undefined);
} catch (err) {
reject(err);
}
}, delay);
});
return promise;
}
debounce.promise = () => promise;
return debounce;
};
var ddispatch = () => {
let triggered = 0;
const events = d3Dispatch.dispatch("change");
function change() {
events.apply("change", this, arguments);
triggered += 1;
}
return {
change,
on(typename, callback) {
if (arguments.length < 2) return events.on(typename);
events.on(typename, callback);
return this;
},
trigger: debounce(change),
triggered() {
return triggered;
}
};
};
//
// Initialise a model
function asModel(model, initials, parent, isolated) {
var events = new Map(),
Child = null;
// event handler for any change in the model
events.set("", ddispatch());
Object.defineProperties(uid(model), {
$events: {
get() {
return events;
}
},
parent: {
get() {
return parent;
}
},
isolated: {
get() {
return isolated;
}
}
});
model.$child = $child;
model.$update(initials);
function $child(o) {
if (Child === null) Child = createChildConstructor(model);
return new Child(o);
}
}
function createChildConstructor(model) {
function Child(initials) {
asModel(this, initials, model, false);
}
Child.prototype = model;
return Child;
}
var slice = (obj, idx = 0) => Array.prototype.slice.call(obj, idx);
// trigger change event on a model reactive attribute
function $change(attribute) {
var name = arguments.length ? attribute : "",
event = this.$events.get(name),
args = slice(arguments, 1);
if (event) event.trigger.apply(this, args);
else if (!this.isolated) this.parent.$change(name);
else warn(`attribute '${name}' is not a reactive property this model`);
return this;
}
//
// Connect a model attribute with another model attribute
// The attribute is read-only on the model an it reacts on parent changes
function $connect(attr, parentAttr, owner) {
if (this.$events.has(attr))
return this.$logWarn(
`cannot connect ${attr} attribute, it is already reactive`
);
parentAttr = parentAttr || attr;
owner = (owner || this).$owner(parentAttr);
if (!owner)
return this.$logWarn(`cannot find model with attribute ${parentAttr}`);
//
const dd = ddispatch();
this.$events.set(attr, dd);
Object.defineProperty(this, attr, {
get() {
return owner[parentAttr];
},
set() {
this.$logWarn(
`Cannot set "${attr}" value because it is owned by a parent model`
);
}
});
owner.$events.get(parentAttr).on(`change.${this.uid}`, () => {
dd.change.apply(this, arguments);
if (owner != this) this.$events.get("").change.apply(this);
});
}
const serialTypes = new Set([Boolean, String, Number]);
function $data() {
var model = this,
data = {},
keys = Array.from(this.$events.keys()).concat(Object.keys(this)),
value;
keys.forEach(key => {
if (key && key.substring(0, 1) !== "$") {
value = getValue(model[key]);
if (value !== undefined) data[key] = value;
}
});
return data;
}
function getValue(value) {
if (!value) return value;
else if (value.constructor === Array) return value.map(getValue);
else if (typeof value.$data === "function") return value.$data();
else if (serialTypes.has(value.constructor)) return value;
}
//
// Emit a custom event Name up the chain of parent models
function $emit(eventName, data) {
var name = "$" + eventName;
propagate(this, name, data, this);
return this;
}
function propagate(model, name, data, originModel) {
if (!model) return;
if (model.hasOwnProperty(name) && d3Let.isFunction(model[name])) {
if (model[name](data, originModel) === false) return;
}
propagate(model.parent, name, data, originModel);
}
//
//check if an attribute is a reactive attribute for the model (or its prototypical parent)
function $isReactive(attr) {
if (!this.$events.has(attr)) {
if (!this.parent || this.isolated) return false;
return this.parent.$isReactive(attr);
}
return true;
}
// remove event handlers
function $off(attr) {
if (attr === undefined) this.$events.forEach(event => removeEvent(event));
else {
var bits = attr.split("."),
type = bits.splice(0, 1)[0],
event = this.$events.get(type);
if (event) removeEvent(event, bits.join("."));
}
}
function removeEvent(event, name) {
if (name) event.on(`change.${name}`, null);
else event.on("change", null);
}
// Add change callback to a model reactive attribute
function $on(name, callback) {
// When no name is provided, wait for changes on this model - no its parents
if (arguments.length === 1 && d3Let.isFunction(name)) {
callback = name;
name = "";
}
var bits = name.split("."),
key = bits[0],
event = getEvent(this, key);
if (!event)
return warn(`Cannot bind to "${key}" - no such reactive property`);
// event from a parent model, add model uid to distinguish it from other child callbacks
if (!this.$events.get(key)) bits.push(this.uid);
bits[0] = "change";
return event.on(bits.join("."), callback);
}
function getEvent(model, name) {
var event = model.$events.get(name);
if (!event && model.parent) return getEvent(model.parent, name);
return event;
}
//
// Return the model owning the reactive attribute attr
function $owner(attr) {
if (!this.$events.has(attr))
return this.parent ? this.parent.$owner(attr) : undefined;
return this;
}
// Check if a value is a vanilla javascript object
var isVanillaObject = value => value && value.constructor === Object;
// $set a reactive attribute for a Model
//
// Set the value of an attribute in the model
// If the attribute is not already reactive make it as such.
//
function $set(key, value) {
// property not reactive - make it as such
if (key.substring(0, 1) === "$") {
if (this.constructor.prototype[key])
this.$logWarn(`Cannot set attribute method ${key}, it is protected`);
else this[key] = value;
} else if (!this.$events.get(key)) reactive(this, key, value);
else this[key] = value;
}
const isModel = value =>
d3Let.isObject(value) && value.toString() === "[object d3Model]";
const reactive = (model, key, value) => {
var lazy;
model.$events.set(key, ddispatch());
Object.defineProperty(model, key, property());
// Create a new model if value is an object
value = typeValue(value);
// Trigger the callback once for initialization
model.$change(key);
function update(newValue) {
if (lazy) newValue = lazy.get.call(model);
if (newValue === value) return;
// trigger lazy callbacks
//
// Fire model events
if (providers.logger.debug) {
var modelName = model.$$name || "model";
providers.logger.debug(`[d3-model] updating ${modelName}.${key}`);
}
model.$change(key, value).$change();
value = typeValue(newValue, value);
}
function property() {
var prop = {
get: function() {
return value;
}
};
if (d3Let.isFunction(value)) value = { get: value };
// calculated attribute
if (isVanillaObject(value) && d3Let.isFunction(value.get)) {
lazy = value;
value = lazy.get.call(model);
if (lazy.reactOn)
lazy.reactOn.forEach(name => {
model.$on(`${name}.${key}`, update);
});
else
model.$logWarn(
`reactive lazy property ${key} does not specify 'reactOn' list or properties`
);
if (d3Let.isFunction(lazy.set)) prop.set = lazy.set;
} else prop.set = update;
return prop;
}
function typeValue(newValue, oldValue) {
if (newValue === oldValue) return oldValue;
else if (d3Let.isArray(newValue)) return arrayValue(newValue, oldValue);
else if (isModel(oldValue)) return modelValue(newValue, oldValue);
else return isVanillaObject(newValue) ? model.$new(newValue) : newValue;
}
function arrayValue(newValue, oldValue) {
if (isModel(oldValue)) oldValue.$off();
if (!d3Let.isArray(oldValue)) oldValue = [];
for (let i = 0; i < newValue.length; ++i)
newValue[i] = typeValue(newValue[i], oldValue[i]);
return newValue;
}
function modelValue(newValue, oldValue) {
if (isVanillaObject(newValue)) {
oldValue.$update(newValue);
return oldValue;
} else {
oldValue.$off();
return typeValue(newValue);
}
}
};
const modelStr = "[object d3Model]";
function toString() {
return modelStr;
}
// Update a model with reactive model data
function $update(data, replace) {
if (data) {
replace = arguments.length === 2 ? replace : true;
for (var key in data) {
if (replace || this[key] === undefined) this.$set(key, data[key]);
}
}
return this;
}
//
// Model class
//
// The model is at the core of d3-view reactive data component
function Model(initials, parent) {
asModel(this, initials, parent, true);
}
function model(initials) {
return new Model(initials);
}
model.prototype = Model.prototype = {
constructor: Model,
// Public API methods
toString,
$on,
$change,
$connect,
$emit,
$update,
$set,
$new,
$off,
$isReactive,
$owner,
$data,
$push,
$splice,
$logWarn(msg) {
if (this.$$view) this.$$view.logWarn(msg);
else warn(msg);
}
};
Object.defineProperties(Model.prototype, {
root: {
get() {
return this.parent ? this.parent.root : this;
}
},
isolatedRoot: {
get() {
return !this.isolated && this.parent ? this.parent.isolatedRoot : this;
}
}
});
function $new(initials) {
return new Model(initials, this);
}
var dashify = (str, options) => {
if (typeof str !== "string") {
throw new TypeError("expected a string");
}
return str
.trim()
.replace(/([a-z])([A-Z])/g, "$1-$2")
.replace(/\W/g, m => (/[À-ž]/.test(m) ? m : "-"))
.replace(/^-+|-+$/g, "")
.replace(/-{2,}/g, m => (options && options.condense ? "-" : m))
.toLowerCase();
};
const DATAPREFIX = "data";
var dataAttributes = attrs => {
let p, bits;
return Object.keys(attrs).reduce((o, key) => {
bits = key.split("-");
if (bits.length > 1 && bits[0] === DATAPREFIX) bits = bits.splice(1);
p = bits.reduce((s, key, idx) => {
s += idx ? key.substring(0, 1).toUpperCase() + key.substring(1) : key;
return s;
}, "");
o[p] = attrs[key];
return o;
}, {});
};
var maybeJson = value => {
if (d3Let.isString(value)) {
try {
return JSON.parse(value);
} catch (msg) {
return value;
}
}
return value;
};
function sel(o) {
Object.defineProperty(o, "sel", {
get() {
return d3Selection.select(this.el);
}
});
return o;
}
function Cache() {
this.active = true;
this.data = new Map();
}
Cache.prototype = {
constructor: Cache,
set(key, value) {
if (this.active) this.data.set(key, value);
},
get(key) {
if (this.active) return this.data.get(key);
},
has(key) {
if (this.active) return this.data.has(key);
return false;
},
clear() {
return this.data.clear();
},
delete(key) {
return this.data.delete(key);
}
};
// Code originally from https://github.com/soney/jsep
// Copyright (c) 2013 Stephen Oney, http://jsep.from.so/
// Code modified and adapted to work with d3-view
// This is the full set of types that any JSEP node can be.
// Store them here to save space when minified
const code = {
COMPOUND: "Compound",
IDENTIFIER: "Identifier",
MEMBER_EXP: "MemberExpression",
LITERAL: "Literal",
THIS_EXP: "ThisExpression",
CALL_EXP: "CallExpression",
UNARY_EXP: "UnaryExpression",
BINARY_EXP: "BinaryExpression",
LOGICAL_EXP: "LogicalExpression",
CONDITIONAL_EXP: "ConditionalExpression",
ARRAY_EXP: "ArrayExpression"
};
var PERIOD_CODE = 46, // '.'
COMMA_CODE = 44, // ','
SQUOTE_CODE = 39, // single quote
DQUOTE_CODE = 34, // double quotes
OPAREN_CODE = 40, // (
CPAREN_CODE = 41, // )
OBRACK_CODE = 91, // [
CBRACK_CODE = 93, // ]
QUMARK_CODE = 63, // ?
SEMCOL_CODE = 59, // ;
COLON_CODE = 58, // :
throwError = function(message, index) {
var error = new Error(message + " at character " + index);
error.index = index;
error.description = message;
throw error;
},
// Operations
// ----------
// Set `t` to `true` to save space (when minified, not gzipped)
t = true,
// Use a quickly-accessible map to store all of the unary operators
// Values are set to `true` (it really doesn't matter)
unary_ops = { "-": t, "!": t, "~": t, "+": t },
// Also use a map for the binary operations but set their values to their
// binary precedence for quick reference:
// see [Order of operations](http://en.wikipedia.org/wiki/Order_of_operations#Programming_language)
binary_ops = {
"||": 1,
"&&": 2,
"|": 3,
"^": 4,
"&": 5,
"==": 6,
"!=": 6,
"===": 6,
"!==": 6,
"<": 7,
">": 7,
"<=": 7,
">=": 7,
"<<": 8,
">>": 8,
">>>": 8,
"+": 9,
"-": 9,
"*": 10,
"/": 10,
"%": 10
},
// Get return the longest key length of any object
getMaxKeyLen = function(obj) {
var max_len = 0,
len;
for (var key in obj) {
if ((len = key.length) > max_len && obj.hasOwnProperty(key)) {
max_len = len;
}
}
return max_len;
},
max_unop_len = getMaxKeyLen(unary_ops),
max_binop_len = getMaxKeyLen(binary_ops),
// Literals
// ----------
// Store the values to return for the various literals we may encounter
literals = {
true: true,
false: false,
null: null
},
// Except for `this`, which is special. This could be changed to something like `'self'` as well
this_str = "this",
// Returns the precedence of a binary operator or `0` if it isn't a binary operator
binaryPrecedence = function(op_val) {
return binary_ops[op_val] || 0;
},
// Utility function (gets called from multiple places)
// Also note that `a && b` and `a || b` are *logical* expressions, not binary expressions
createBinaryExpression = function(operator, left, right) {
var type =
operator === "||" || operator === "&&"
? code.LOGICAL_EXP
: code.BINARY_EXP;
return {
type: type,
operator: operator,
left: left,
right: right
};
},
// `ch` is a character code in the next three functions
isDecimalDigit = function(ch) {
return ch >= 48 && ch <= 57; // 0...9
},
isIdentifierStart = function(ch) {
return (
ch === 36 ||
ch === 95 || // `$` and `_`
(ch >= 65 && ch <= 90) || // A...Z
(ch >= 97 && ch <= 122) || // a...z
(ch >= 128 && !binary_ops[String.fromCharCode(ch)])
); // any non-ASCII that is not an operator
},
isIdentifierPart = function(ch) {
return (
ch === 36 ||
ch === 95 || // `$` and `_`
(ch >= 65 && ch <= 90) || // A...Z
(ch >= 97 && ch <= 122) || // a...z
(ch >= 48 && ch <= 57) || // 0...9
(ch >= 128 && !binary_ops[String.fromCharCode(ch)])
); // any non-ASCII that is not an operator
},
// Parsing
// -------
// `expr` is a string with the passed in expression
jsep = function(expr) {
// `index` stores the character number we are currently at while `length` is a constant
// All of the gobbles below will modify `index` as we move along
var index = 0,
charAtFunc = expr.charAt,
charCodeAtFunc = expr.charCodeAt,
exprI = function(i) {
return charAtFunc.call(expr, i);
},
exprICode = function(i) {
return charCodeAtFunc.call(expr, i);
},
length = expr.length,
// Push `index` up to the next non-space character
gobbleSpaces = function() {
var ch = exprICode(index);
// space or tab
while (ch === 32 || ch === 9) {
ch = exprICode(++index);
}
},
// The main parsing function. Much of this code is dedicated to ternary expressions
gobbleExpression = function() {
var test = gobbleBinaryExpression(),
consequent,
alternate;
gobbleSpaces();
if (exprICode(index) === QUMARK_CODE) {
// Ternary expression: test ? consequent : alternate
index++;
consequent = gobbleExpression();
if (!consequent) {
throwError("Expected expression", index);
}
gobbleSpaces();
if (exprICode(index) === COLON_CODE) {
index++;
alternate = gobbleExpression();
if (!alternate) {
throwError("Expected expression", index);
}
return {
type: code.CONDITIONAL_EXP,
test: test,
consequent: consequent,
alternate: alternate
};
} else {
throwError("Expected :", index);
}
} else {
return test;
}
},
// Search for the operation portion of the string (e.g. `+`, `===`)
// Start by taking the longest possible binary operations (3 characters: `===`, `!==`, `>>>`)
// and move down from 3 to 2 to 1 character until a matching binary operation is found
// then, return that binary operation
gobbleBinaryOp = function() {
gobbleSpaces();
var to_check = expr.substr(index, max_binop_len),
tc_len = to_check.length;
while (tc_len > 0) {
if (binary_ops.hasOwnProperty(to_check)) {
index += tc_len;
return to_check;
}
to_check = to_check.substr(0, --tc_len);
}
return false;
},
// This function is responsible for gobbling an individual expression,
// e.g. `1`, `1+2`, `a+(b*2)-Math.sqrt(2)`
gobbleBinaryExpression = function() {
var node, biop, prec, stack, biop_info, left, right, i;
// First, try to get the leftmost thing
// Then, check to see if there's a binary operator operating on that leftmost thing
left = gobbleToken();
biop = gobbleBinaryOp();
// If there wasn't a binary operator, just return the leftmost node
if (!biop) {
return left;
}
// Otherwise, we need to start a stack to properly place the binary operations in their
// precedence structure
biop_info = { value: biop, prec: binaryPrecedence(biop) };
right = gobbleToken();
if (!right) {
throwError("Expected expression after " + biop, index);
}
stack = [left, biop_info, right];
// Properly deal with precedence using [recursive descent](http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm)
while ((biop = gobbleBinaryOp())) {
prec = binaryPrecedence(biop);
if (prec === 0) {
break;
}
biop_info = { value: biop, prec: prec };
// Reduce: make a binary expression from the three topmost entries.
while (stack.length > 2 && prec <= stack[stack.length - 2].prec) {
right = stack.pop();
biop = stack.pop().value;
left = stack.pop();
node = createBinaryExpression(biop, left, right);
stack.push(node);
}
node = gobbleToken();
if (!node) {
throwError("Expected expression after " + biop, index);
}
stack.push(biop_info, node);
}
i = stack.length - 1;
node = stack[i];
while (i > 1) {
node = createBinaryExpression(stack[i - 1].value, stack[i - 2], node);
i -= 2;
}
return node;
},
// An individual part of a binary expression:
// e.g. `foo.bar(baz)`, `1`, `"abc"`, `(a % 2)` (because it's in parenthesis)
gobbleToken = function() {
var ch, to_check, tc_len;
gobbleSpaces();
ch = exprICode(index);
if (isDecimalDigit(ch) || ch === PERIOD_CODE) {
// Char code 46 is a dot `.` which can start off a numeric literal
return gobbleNumericLiteral();
} else if (ch === SQUOTE_CODE || ch === DQUOTE_CODE) {
// Single or double quotes
return gobbleStringLiteral();
} else if (isIdentifierStart(ch) || ch === OPAREN_CODE) {
// open parenthesis
// `foo`, `bar.baz`
return gobbleVariable();
} else if (ch === OBRACK_CODE) {
return gobbleArray();
} else {
to_check = expr.substr(index, max_unop_len);
tc_len = to_check.length;
while (tc_len > 0) {
if (unary_ops.hasOwnProperty(to_check)) {
index += tc_len;
return {
type: code.UNARY_EXP,
operator: to_check,
argument: gobbleToken(),
prefix: true
};
}
to_check = to_check.substr(0, --tc_len);
}
return false;
}
},
// Parse simple numeric literals: `12`, `3.4`, `.5`. Do this by using a string to
// keep track of everything in the numeric literal and then calling `parseFloat` on that string
gobbleNumericLiteral = function() {
var number = "",
ch,
chCode;
while (isDecimalDigit(exprICode(index))) {
number += exprI(index++);
}
if (exprICode(index) === PERIOD_CODE) {
// can start with a decimal marker
number += exprI(index++);
while (isDecimalDigit(exprICode(index))) {
number += exprI(index++);
}
}
ch = exprI(index);
if (ch === "e" || ch === "E") {
// exponent marker
number += exprI(index++);
ch = exprI(index);
if (ch === "+" || ch === "-") {
// exponent sign
number += exprI(index++);
}
while (isDecimalDigit(exprICode(index))) {
//exponent itself
number += exprI(index++);
}
if (!isDecimalDigit(exprICode(index - 1))) {
throwError(
"Expected exponent (" + number + exprI(index) + ")",
index
);
}
}
chCode = exprICode(index);
// Check to make sure this isn't a variable name that start with a number (123abc)
if (isIdentifierStart(chCode)) {
throwError(
"Variable names cannot start with a number (" +
number +
exprI(index) +
")",
index
);
} else if (chCode === PERIOD_CODE) {
throwError("Unexpected period", index);
}
return {
type: code.LITERAL,
value: parseFloat(number),
raw: number
};
},
// Parses a string literal, staring with single or double quotes with basic support for escape codes
// e.g. `"hello world"`, `'this is\nJSEP'`
gobbleStringLiteral = function() {
var str = "",
quote = exprI(index++),
closed = false,
ch;
while (index < length) {
ch = exprI(index++);
if (ch === quote) {
closed = true;
break;
} else if (ch === "\\") {
// Check for all of the common escape codes
ch = exprI(index++);
switch (ch) {
case "n":
str += "\n";
break;
case "r":
str += "\r";
break;
case "t":
str += "\t";
break;
case "b":
str += "\b";
break;
case "f":
str += "\f";
break;
case "v":
str += "\x0B";
break;
default:
str += "\\" + ch;
}
} else {
str += ch;
}
}
if (!closed) {
throwError('Unclosed quote after "' + str + '"', index);
}
return {
type: code.LITERAL,
value: str,
raw: quote + str + quote
};
},
// Gobbles only identifiers
// e.g.: `foo`, `_value`, `$x1`
// Also, this function checks if that identifier is a literal:
// (e.g. `true`, `false`, `null`) or `this`
gobbleIdentifier = function() {
var ch = exprICode(index),
start = index,
identifier;
if (isIdentifierStart(ch)) {
index++;
} else {
throwError("Unexpected " + exprI(index), index);
}
while (index < length) {
ch = exprICode(index);
if (isIdentifierPart(ch)) {
index++;
} else {
break;
}
}
identifier = expr.slice(start, index);
if (literals.hasOwnProperty(identifier)) {
return {
type: code.LITERAL,
value: literals[identifier],
raw: identifier
};
} else if (identifier === this_str) {
return { type: code.THIS_EXP };
} else {
return {
type: code.IDENTIFIER,
name: identifier
};
}
},
// Gobbles a list of arguments within the context of a function call
// or array literal. This function also assumes that the opening character
// `(` or `[` has already been gobbled, and gobbles expressions and commas
// until the terminator character `)` or `]` is encountered.
// e.g. `foo(bar, baz)`, `my_func()`, or `[bar, baz]`
gobbleArguments = function(termination) {
var ch_i,
args = [],
node,
closed = false;
while (index < length) {
gobbleSpaces();
ch_i = exprICode(index);
if (ch_i === termination) {
// done parsing
closed = true;
index++;
break;
} else if (ch_i === COMMA_CODE) {
// between expressions
index++;
} else {
node = gobbleExpression();
if (!node || node.type === code.COMPOUND) {
throwError("Expected comma", index);
}
args.push(node);
}
}
if (!closed) {
throwError("Expected " + String.fromCharCode(termination), index);
}
return args;
},
// Gobble a non-literal variable name. This variable name may include properties
// e.g. `foo`, `bar.baz`, `foo['bar'].baz`
// It also gobbles function calls:
// e.g. `Math.acos(obj.angle)`
gobbleVariable = function() {
var ch_i, node;
ch_i = exprICode(index);
if (ch_i === OPAREN_CODE) {
node = gobbleGroup();
} else {
node = gobbleIdentifier();
}
gobbleSpaces();
ch_i = exprICode(index);
while (
ch_i === PERIOD_CODE ||
ch_i === OBRACK_CODE ||
ch_i === OPAREN_CODE
) {
index++;
if (ch_i === PERIOD_CODE) {
gobbleSpaces();
node = {
type: code.MEMBER_EXP,
computed: false,
object: node,
property: gobbleIdentifier()
};
} else if (ch_i === OBRACK_CODE) {
node = {
type: code.MEMBER_EXP,
computed: true,
object: node,
property: gobbleExpression()
};
gobbleSpaces();
ch_i = exprICode(index);
if (ch_i !== CBRACK_CODE) {
throwError("Unclosed [", index);
}
index++;
} else if (ch_i === OPAREN_CODE) {
// A function call is being made; gobble all the arguments
node = {
type: code.CALL_EXP,
arguments: gobbleArguments(CPAREN_CODE),
callee: node
};
}
gobbleSpaces();
ch_i = exprICode(index);
}
return node;
},
// Responsible for parsing a group of things within parentheses `()`
// This function assumes that it needs to gobble the opening parenthesis
// and then tries to gobble everything within that parenthesis, assuming
// that the next thing it should see is the close parenthesis. If not,
// then the expression probably doesn't have a `)`
gobbleGroup = function() {
index++;
var node = gobbleExpression();
gobbleSpaces();
if (exprICode(index) === CPAREN_CODE) {
index++;
return node;
} else {
throwError("Unclosed (", index);
}
},
// Responsible for parsing Array literals `[1, 2, 3]`
// This function assumes that it needs to gobble the opening bracket
// and then tries to gobble the expressions as arguments.
gobbleArray = function() {
index++;
return {
type: code.ARRAY_EXP,
elements: gobbleArguments(CBRACK_CODE)
};
},
nodes = [],
ch_i,
node;
while (index < length) {
ch_i = exprICode(index);
// Expressions can be separated by semicolons, commas, or just inferred without any
// separators
if (ch_i === SEMCOL_CODE || ch_i === COMMA_CODE) {
index++; // ignore separators
} else {
// Try to gobble each expression individually
if ((node = gobbleExpression())) {
nodes.push(node);
// If we weren't able to find a binary expression and are out of room, then
// the expression passed in probably has too much
} else if (index < length) {
throwError('Unexpected "' + exprI(index) + '"', index);
}
}
}
// If there's only one expression just try returning the expression
if (nodes.length === 1) {
return nodes[0];
} else {
return {
type: code.COMPOUND,
body: nodes
};
}
};
/**
* @method jsep.addUnaryOp
* @param {string} op_name The name of the unary op to add
* @return jsep
*/
jsep.addUnaryOp = function(op_name) {
max_unop_len = Math.max(op_name.length, max_unop_len);
unary_ops[op_name] = t;
return this;
};
/**
* @method jsep.addBinaryOp
* @param {string} op_name The name of the binary op to add
* @param {number} precedence The precedence of the binary op (can be a float)
* @return jsep
*/
jsep.addBinaryOp = function(op_name, precedence) {
max_binop_len = Math.max(op_name.length, max_binop_len);
binary_ops[op_name] = precedence;
return this;
};
/**
* @method jsep.addLiteral
* @param {string} literal_name The name of the literal to add
* @param {*} literal_value The value of the literal
* @return jsep
*/
jsep.addLiteral = function(literal_name, literal_value) {
literals[literal_name] = literal_value;
return this;
};
/**
* @method jsep.removeUnaryOp
* @param {string} op_name The name of the unary op to remove
* @return jsep
*/
jsep.removeUnaryOp = function(op_name) {
delete unary_ops[op_name];
if (op_name.length === max_unop_len) {
max_unop_len = getMaxKeyLen(unary_ops);
}
return this;
};
/**
* @method jsep.removeBinaryOp
* @param {string} op_name The name of the binary op to remove
* @return jsep
*/
jsep.removeBinaryOp = function(op_name) {
delete binary_ops[op_name];
if (op_name.length === max_binop_len) {
max_binop_len = getMaxKeyLen(binary_ops);
}
return this;
};
/**
* @method jsep.removeLiteral
* @param {string} literal_name The name of the literal to remove
* @return jsep
*/
jsep.removeLiteral = function(literal_name) {
delete literals[literal_name];
return this;
};
function evaluate(self, expr, nested) {
switch (expr.type) {
case code.IDENTIFIER:
return self[expr.name];
case code.LITERAL:
return nested ? self[expr.value] : expr.value;
case code.ARRAY_EXP:
return expr.elements.map(elem => {
return evaluate(self, elem);
});
case code.LOGICAL_EXP:
case code.BINARY_EXP:
return binaryExp(
expr.operator,
evaluate(self, expr.left),
evaluate(self, expr.right)
);
case code.CALL_EXP:
return callExpression(self, expr.callee, expr.arguments);
case code.MEMBER_EXP:
return evaluate(evaluate(self, expr.object), expr.property, true);
case code.CONDITIONAL_EXP:
return evaluate(self, expr.test)
? evaluate(self, expr.consequent)
: evaluate(self, expr.alternate);
case code.UNARY_EXP:
return unaryExp(expr.operator, evaluate(self, expr.argument));
}
}
// extract identifiers
function identifiers(expr, all) {
if (arguments.length === 1) all = new Set();
switch (expr.type) {
case code.IDENTIFIER:
all.add(expr.name);
break;
case code.ARRAY_EXP:
expr.elements.forEach(elem => identifiers(elem, all));
break;
case code.LOGICAL_EXP:
case code.BINARY_EXP:
identifiers(expr.left, all);
identifiers(expr.right, all);
break;
case code.CALL_EXP:
identifiers(expr.callee, all);
expr.arguments.forEach(elem => identifiers(elem, all));
break;
case code.MEMBER_EXP:
all.add(fullName(expr));
break;
case code.CONDITIONAL_EXP:
identifiers(expr.test, all);
identifiers(expr.consequent, all);
evaluate(expr.alternate, all);
break;
case code.UNARY_EXP:
identifiers(expr.argument, all);
break;
}
return all;
}
function callExpression(self, callee, args) {
var func;
args = args.map(arg => {
return evaluate(self, arg);
});
if (callee.type !== code.IDENTIFIER) {
self = evaluate(self, callee.object);
callee = callee.property;
}
func = self[callee.name];
if (!func)
throw new EvalError(`callable "${callee.name}" not found in context`);
return func.apply(self, args);
}
function unaryExp(op, arg) {
if (!unaryFunctions[op])
unaryFunctions[op] = new Function("arg", `return ${op} arg`);
return unaryFunctions[op](arg);
}
function binaryExp(op, a, b) {
if (!binaryFunctions[op])
binaryFunctions[op] = new Function("a", "b", `return a ${op} b`);
return binaryFunctions[op](a, b);
}
function fullName(expr) {
if (expr.type === code.IDENTIFIER) return expr.name;
else return `${fullName(expr.object)}.${expr.property.name}`;
}
const unaryFunctions = {};
const binaryFunctions = {};
// tiny javascript expression parser
const proto = {
eval(model) {
return evaluate(model, this.parsed);
},
safeEval(model) {
try {
return evaluate(model, this.parsed);
} catch (msg) {
warn(`Could not evaluate <<${this.expr}>> expression: ${msg}`);
}
},
// evaluate identifiers from a model
identifiers() {
return identifiers(this.parsed);
}
};
function Expression(expr) {
this.codes = code;
this.expr = expr;
this.parsed = jsep(expr);
}
Expression.prototype = proto;
function viewExpression(expr) {
return new Expression(expr);
}
//
// Directive Prototype
//
// Directives are special attributes with the d3- prefix.
// Directive attribute values are expected to be binding expressions.
// A directive’s job is to reactively apply special behavior to the DOM
// when the value of its expression changes.
//
// A directive can implement one or more of the directive methods:
//
// * create
// * mount
// * refresh
// * destroy
//
const prototype = {
// hooks
create(expression) {
return expression;
},
// pre mount
preMount() {},
mount(model$$1) {
return model$$1;
},
refresh() {},
destroy() {},
removeAttribute() {
this.el.removeAttribute(this.name);
},
// Execute directive
execute(model$$1) {
if (!this.active) return;
this.removeAttribute();
this.identifiers = [];
model$$1 = this.mount(model$$1);
// If model returned, bind the element to its properties
if (model$$1) this.bindModel(model$$1);
},
bindModel(model$$1) {
var dir = this,
error = false,
events = model$$1.$$view.events,
refresh = function() {
let value = dir.expression ? dir.expression.eval(model$$1) : dir.data;
dir.refresh(model$$1, value);
dir.passes++;
events.call("directive-refresh", undefined, dir, model$$1, value);
};
//
// get the cache instance
dir.cache = model$$1.$$view.cache;
// Bind expression identifiers with model
let bits, target, attr, i;
if (dir.data) {
dir.data = model$$1.$new(dir.data);
dir.identifiers.push({
model: dir.data,
attr: ""
});
} else if (!this.expression) {
dir.identifiers.push({
model: model$$1,
attr: ""
});
} else {
var modelEvents = new Map();
this.expression.identifiers().forEach(identifier => {
bits = identifier.split(".");
target = model$$1;
attr = null;
for (i = 0; i < bits.length - 1; ++i) {
target = target[bits[i]];
if (!d3Let.isObject(target)) {
attr = bits.slice(0, i + 1).join(".");
dir.logError(
`"${attr}" is not an object, cannot bind to "${identifier}" identifier`
);
error = true;
break;
}
}
// process attribute
if (attr === null) {
if (!(target instanceof model)) {
dir.logError(
`${identifier} is not a reactive model. Cannot bind to it`
);
error = true;
} else addTarget(modelEvents, target, bits[bits.length - 1]);
}
});
// register with model reactive properties
modelEvents.forEach(target => {
// if we are listening to all event simply bind to the model changes
if (target.events.has(""))
dir.identifiers.push({
model: target.model,
attr: ""
});
else
target.events.forEach(attr => {
dir.identifiers.push({
model: target.model,
attr: attr
});
});
});
}
if (error) return;
this.identifiers.forEach(identifier => {
var event = `${identifier.attr}.${dir.uid}`;
identifier.model.$on(event, refresh);
});
this.bindDestroy(model$$1);
refresh();
},
bindDestroy(model$$1) {
var dir = this,
destroy = this.destroy;
// bind destroy to the model
dir.destroy = function() {
dir.identifiers.forEach(identifier => {
identifier.model.$off(`${identifier.attr}.${dir.uid}`);
});
if (dir.data) dir.data.$off();
try {
destroy.call(dir, model$$1);
} finally {
model$$1.$emit("destroyDirective", dir);
}
};
}
};
// Directive constructor
function createDirective(obj) {
function Directive(el, attr, arg) {
this.el = el;
this.ownerDocument = el.ownerDocument;
this.name = attr.name;
this.arg = arg;
this.passes = 0;
var expr = sel(uid(this)).create(attr.value);
if (expr) {
try {
this.expression = viewExpression(expr);
} catch (e) {
try {
this.data = JSON.parse(expr);
} catch (m) {
this.logError(e);
}
}
}
if (!this.active)
this.active = Boolean(!attr.value || this.expression || this.data);
}
Directive.prototype = { ...base$1, ...prototype, ...obj };
function directive(el, attr, arg) {
return new Directive(el, attr, arg);
}
directive.prototype = Directive.prototype;
return directive;
}
function addTarget(modelEvents, model$$1, attr) {
var value = arguments.length === 3 ? model$$1[attr] : undefined;
//
// a method of the model, event is at model level
if (d3Let.isFunction(value) || arguments.length === 2)
getTarget(modelEvents, model$$1).events.add("");
// value is another model, events at both target model level and value model level
else if (value instanceof model) {
addTarget(modelEvents, value);
model$$1 = model$$1.$owner(attr);
if (model$$1) getTarget(modelEvents, model$$1).events.add("");
} else {
// make sure attr is a reactive property of model
model$$1 = model$$1.$owner(attr) || model$$1;
if (!model$$1.$isReactive(attr)) model$$1.$set(attr, value);
getTarget(modelEvents, model$$1).events.add(attr);
}
}
function getTarget(modelEvents, model$$1) {
var target = modelEvents.get(model$$1.uid);
if (!target) {
target = {
model: model$$1,
events: new Set()
};
modelEvents.set(model$$1.uid, target);
}
return target;
}
// No value, it has its own directive
const attributes = [
"name",
"class",
"disabled",
"readonly",
"required",
"href"
];
var getdirs = (element, vm) => {
const dirs = new Directives();
for (let i = 0; i < element.attributes.length; ++i) {
let attr = element.attributes[i],
bits = attr.name.split("-"),
dirName = bits[0] === "d3" ? bits[1] : null,
arg;
if (dirName) {
if (vm) {
arg = bits.slice(2).join("-");
if (!arg && attributes.indexOf(dirName) > -1) {
arg = dirName;
dirName = "attr";
}
var directive = vm.directives.get(dirName);
if (directive) dirs.add(directive(element, attr, arg));
else
vm.logWarn(
`${
element.tagName
} cannot find directive "${dirName}". Did you forget to register it?`
);
}
} else dirs.attrs[attr.name] = attr.value;
}
return dirs;
};
// Directives container
function Directives() {
this.attrs = {};
this.all = [];
}
Directives.prototype = {
size() {
return this.all.length;
},
pop: function(dir) {
var index = this.all.indexOf(dir);
if (index > -1) {
dir.removeAttribute();
this.all.splice(index, 1);
}
return dir;
},
add(dir) {
this.all.push(dir);
},
forEach(callback) {
this.all.forEach(callback);
},
preMount() {
let dir;
for (let i = 0; i < this.all.length; ++i) {
dir = this.all[i];
if (dir.preMount()) return this.pop(dir);
}
},
execute(model) {
if (!this.size()) return;
return Promise.all(this.all.map(d => d.execute(model)));
}
};
// Extend selection prototype with new methods
d3Selection.selection.prototype.mount = mount;
d3Selection.selection.prototype.view = view;
d3Selection.selection.prototype.model = model$1;
d3Selection.selection.prototype.directives = directives;
function directives(vm) {
const node = this.node();
let dirs = node.__d3_directives__;
if (dirs === undefined) {
dirs = getdirs(node, vm);
// no point in storing the directive object if there are no directives or the node is not a component
node.__d3_directives__ =
dirs.size() || getComponent(node, vm) ? dirs : null;
}
return dirs ? dirs : getdirs(node);
}
function model$1() {
var vm = this.view();
return vm ? vm.model : null;
}
function view(value) {
if (arguments.length) {
return this.property("__d3_view__", value);
} else {
var element = this.node(),
view = element ? element.__d3_view__ : null,
parent = element ? element.parentNode : null;
while (parent && !view) {
view = parent.__d3_view__;
parent = parent.parentNode;
}
return view;
}
}
//
// mount function on a d3 selection
// Use this function to mount the selection
// This method returns nothing or a promise
function mount(data) {
var promises = [];
this.each(function() {
var view = d3Selection.select(this).view();
if (view) promises.push(mountElement(this, view, data));
else warn("Cannot mount, no view object available to mount to");
});
return Promise.all(promises);
}
// INTERNALS
// mount an element into a given model
const mountElement = (element, vm, data) => {
if (!element || !element.tagName) return;
var component = getComponent(element, vm),
directives = d3Selection.select(element).directives(vm),
preMount = directives.preMount();
if (preMount) return preMount.execute(vm.model);
else {
if (component) {
return component({ parent: vm })
.mount(element, data)
.then(cm => {
directives.execute(cm.model);
return cm;
});
} else {
directives.execute(vm.model);
return Promise.all(
slice(element.children).map(c => mountElement(c, vm, data))
);
}
}
};
const getComponent = (element, vm) => {
return vm.components.get(element.tagName.toLowerCase());
};
const protoView = {
doMount(el) {
return asView(this, el);
}
};
// prototype for both views and components
const protoComponent = {
//
// hooks
render() {},
childrenMounted() {},
mounted() {},
destroy() {},
//
// Mount the component into an element
// If this component is already mounted, or it is mounting, it does nothing
mount(el, data) {
if (mounted(this)) return;
const sel$$1 = asSelect(el);
el = sel$$1.node();
if (!el) {
this.logWarn(
`element not defined, pass an identifier or an HTMLElement object`
);
return Promise.resolve(this);
}
// set the owner document
this.ownerDocument = el.ownerDocument;
//
const directives = sel$$1.directives(this);
let props = d3Let.assign(dataAttributes(directives.attrs), sel$$1.datum(), data),
extra = maybeJson(d3Let.pop(props, "props")),
value,
model$$1,
parentModel;
if (d3Let.isObject(extra)) props = d3Let.assign(extra, props);
else if (this.parent && this.parent.props[extra])
props = d3Let.assign({}, this.parent.props[extra], props);
// fire mount event
this.events.call("mount", undefined, this, el, props);
// pick parent model & props
if (this.parent) {
//
parentModel = this.parent.model;
model$$1 = d3Let.pop(props, "model");
if (model$$1 && !model$$1.$child) model$$1 = parentModel[model$$1];
if (!model$$1) model$$1 = parentModel.$new();
else model$$1 = model$$1.$child();
//
// Get props from parent view
Object.keys(this.props).forEach(key => {
value = this.parent.props[props[key]];
if (value === undefined) {
value = maybeJson(props[key]);
if (value !== undefined) props[key] = value;
// default value if available
else if (this.props[key] !== undefined) props[key] = this.props[key];
} else props[key] = value;
});
//
// get props object from parent if props is defined
if (d3Let.isString(extra)) props = d3Let.assign({}, this.parent.props[extra], props);
} else {
props = d3Let.assign(this.props, props);
model$$1 = model();
}
// add reactive model properties
Object.keys(this.model).forEach(key => {
value = d3Let.pop(props, key);
if (value !== undefined) {
if (d3Let.isString(value) && parentModel && parentModel.$isReactive(value))
model$$1.$connect(key, value, parentModel);
else model$$1.$set(key, maybeJson(value));
} else if (model$$1[key] === undefined) model$$1.$set(key, this.model[key]);
});
this.model = bindView(this, model$$1);
//
// create the new element from the render function
if (!props.id) props.id = model$$1.uid;
this.props = props;
//
return this.doMount(el);
},
createElement(tag, props) {
const doc = this.ownerDocument || document;
const sel$$1 = this.select(doc.createElement(tag));
if (props) {
sel$$1.attr("id", this.props.id);
if (this.props.class) sel$$1.classed(this.props.class, true);
}
return sel$$1;
},
doMount(el) {
let newEl;
try {
newEl = this.render(el);
} catch (error) {
newEl = Promise.reject(error);
}
if (!newEl || !newEl.then) newEl = Promise.resolve(newEl);
return newEl
.then(element => compile$1(this, el, element))
.catch(exc => error$1(this, el, exc));
},
use(plugin) {
if (d3Let.isObject(plugin)) plugin.install(this);
else plugin(this);
return this;
},
addComponent(name, obj) {
name = dashify(name);
var component = createComponent(name, obj);
this.components.set(name, component);
return component;
},
addDirective(name, obj) {
name = dashify(name);
var directive = createDirective(obj);
this.directives.set(name, directive);
return directive;
}
};
// factory of View and Component constructors
const createComponent = (name, o, coreDirectives, coreComponents) => {
if (d3Let.isFunction(o)) o = { render: o };
var obj = d3Let.assign({}, o),
classComponents = extendComponents(new Map(), d3Let.pop(obj, "components")),
classDirectives = extendDirectives(new Map(), d3Let.pop(obj, "directives")),
model$$1 = d3Let.pop(obj, "model"),
props = d3Let.pop(obj, "props");
function Component(options) {
var parent = d3Let.pop(options, "parent"),
components = map(parent ? parent.components : coreComponents),
directives = map(parent ? parent.directives : coreDirectives),
events = parent
? parent.events
: d3Dispatch.dispatch(
"message",
"created",
"mount",
"mounted",
"error",
"directive-refresh"
),
cache = parent ? parent.cache : new Cache();
classComponents.forEach((comp, key) => {
components.set(key, comp);
});
classDirectives.forEach((comp, key) => {
directives.set(key, comp);
});
extendComponents(components, d3Let.pop(options, "components"));
extendDirectives(directives, d3Let.pop(options, "directives"));
Object.defineProperties(this, {
name: {
get() {
return name;
}
},
components: {
get() {
return components;
}
},
directives: {
get() {
return directives;
}
},
parent: {
get() {
return parent;
}
},
root: {
get() {
return parent ? parent.root : this;
}
},
cache: {
get() {
return cache;
}
},
uid: {
get() {
return this.model.uid;
}
},
events: {
get() {
return events;
}
}
});
this.props = asObject(props, d3Let.pop(options, "props"));
this.model = asObject(model$$1, d3Let.pop(options, "model"));
this.events.call("created", undefined, this);
}
Component.prototype = d3Let.assign({}, base$1, protoComponent, obj);
function component(options) {
return new Component(options);
}
component.prototype = Component.prototype;
return component;
};
// Used by both Component and view
const extendComponents = (container, components) => {
map(components).forEach((obj, key) => {
key = dashify(key);
container.set(key, createComponent(key, obj, protoComponent));
});
return container;
};
const extendDirectives = (container, directives) => {
map(directives).forEach((obj, key) => {
key = dashify(key);
container.set(key, createDirective(obj));
});
return container;
};
//
// Finalise the binding between the view and the model
// inject the model into the view element
// call the mounted hook and can return a Promise
const asView = (vm, element) => {
Object.defineProperty(sel(vm), "el", {
get: function() {
return element;
}
});
// Apply model to element and mount
return vm
.select(element)
.view(vm)
.mount()
.then(() => vmMounted(vm));
};
const mounted = vm => {
if (vm.isMounted === undefined) {
vm.isMounted = false;
return false;
} else if (vm.isMounted) {
vm.logWarn(`component already mounted`);
} else {
vm.isMounted = true;
// invoke mounted component hook
vm.mounted();
// invoke the view mounted events
vm.events.call("mounted", undefined, vm);
}
return true;
};
// Internals
//
// Component/View mounted
// =========================
//
// This function is called when a component/view has all its children added
const vmMounted = vm => {
var parent = vm.parent;
vm.childrenMounted();
if (parent && !parent.isMounted) {
const event = `mounted.${vm.uid}`;
vm.events.on(event, cm => {
if (cm === parent) {
vm.events.on(event, null);
mounted(vm);
}
});
} else mounted(vm);
return vm;
};
// Compile a component model
// This function is called once a component has rendered the component element
const compile$1 = (cm, origEl, element) => {
if (d3Let.isString(element)) {
const props = Object.keys(cm.props).length ? cm.props : null;
element = cm.viewElement(element, props, origEl.ownerDocument);
}
element = asSelect(element);
const size = element.size();
if (!size)
throw new Error(
"render() must return a single HTML node. It returned nothing!"
);
else if (size !== 1) cm.logWarn("render() must return a single HTML node");
element = element.node();
//
// mark the original element as component
origEl.__d3_component__ = true;
// Insert before the component element
if (origEl.parentNode) origEl.parentNode.insertBefore(element, origEl);
// remove the component element
cm.select(origEl).remove();
//
return asView(cm, element);
};
// Invoked when a component cm has failed to render
const error$1 = (cm, origEl, exc) => {
cm.logWarn(`failed to render due to the unhandled exception reported below`);
cm.logError(exc);
cm.events.call("error", undefined, cm, origEl, exc);
return cm;
};
const asObject = (value, opts) => {
if (d3Let.isFunction(value)) value = value();
if (d3Let.isArray(value))
value = value.reduce((o, key) => {
o[key] = undefined;
return o;
}, {});
return d3Let.assign({}, value, opts);
};
const bindView = (view, model$$1) => {
Object.defineProperties(model$$1, {
$$view: {
get() {
return view;
}
},
$$name: {
get() {
return view.name;
}
},
props: {
get() {
return view.props;
}
}
});
return model$$1;
};
//
// d3-attr-<attr> directive
// ==============================
//
// Create a one-way binding between a model and a HTML element attribute
//
var attr = {
create(expression) {
if (!this.arg)
return this.logWarn(
"Cannot bind to empty attribute. Specify :<attr-name>"
);
return expression;
},
refresh(model, value) {
if (this.arg === "class") return this.refreshClass(value);
if (d3Let.isArray(value))
return this.logWarn(`Cannot apply array to attribute ${this.arg}`);
var prop = properties.get(this.arg);
if (prop) this.sel.property(prop, value || false);
else this.sel.attr(this.arg, value || null);
},
refreshClass(value) {
var sel = this.sel;
if (!d3Let.isArray(value)) value = [value];
if (this.oldValue)
this.oldValue.forEach(entry => {
if (entry) sel.classed(entry, false);
});
this.oldValue = value.map(entry => {
var exist = true;
if (d3Let.isArray(entry)) {
exist = entry[1];
entry = entry[0];
}
if (entry) sel.classed(entry, exist);
return entry;
});
}
};
//
// d3-for directive
// ======================
//
// Repeat a element over an array of items and establish
// a one way binding between the array and the Dom
var d3For = {
create(expression) {
var bits = [];
expression
.trim()
.split(" ")
.forEach(v => {
v ? bits.push(v) : null;
});
if (bits.length !== 3 || bits[1] != "in")
return this.logWarn(
`directive requires "item in expression" template, got "${expression}"`
);
this.itemName = bits[0];
this.itemClass = `for${this.uid}`;
return bits[2];
},
preMount() {
return true;
},
mount(model) {
this.creator = this.el;
this.el = this.creator.parentNode;
// remove the creator from the DOM
d3Selection.select(this.creator).remove();
if (this.el) return model;
},
refresh(model, items) {
if (!d3Let.isArray(items)) return;
let d;
var creator = this.creator,
itemClass = this.itemClass,
selector = `.${itemClass}`,
itemName = this.itemName,
sel = this.sel,
allItems = sel.selectAll(selector),
entries = allItems
.filter(function() {
d = this.__d3_view__.model[itemName];
return items.indexOf(d) > -1;
})
.data(items),
exits = allItems
.filter(function() {
d = this.__d3_view__.model[itemName];
return items.indexOf(d) === -1;
})
.classed(itemClass, false),
vm = sel.view();
let forComponent = vm.components.get(creator.tagName.toLowerCase());
if (!forComponent) forComponent = createComponent("forView", protoView);
let x, el, fel, tr;
(this.transition(exits) || exits).style("opacity", 0).remove();
// Add all missing entries
entries
.enter()
.append(() => {
el = creator.cloneNode(true);
fel = vm.select(el);
if (vm.transitionDuration(fel) > 0) fel.style("opacity", 0);
return el;
})
.each(function(d, index) {
x = { index: index };
x[itemName] = d;
forComponent({
model: x,
parent: vm
})
.mount(this, { model: vm.model })
.then(fv => {
fv.sel.classed(itemClass, true);
// replace the item with a property from the model
// This allow for reactivity when d is an object
items[index] = fv.model[itemName];
tr = fv.transition();
if (tr) tr.style("opacity", 1);
});
});
sel.selectAll(selector).each(function(d) {
// update model itemName property
this.__d3_view__.model[itemName] = d;
});
}
};
//
// d3-html
// =============
// attach html or text to the innerHtml property and mount components if required
//
// Usage:
// <div id="foo" d3-html="output"></div>
var html = {
refresh(model, html) {
if (d3Let.isNumber(html)) html = "" + html;
if (d3Let.isString(html)) {
var dir = this,
sel = this.sel,
transition = this.passes ? this.transition(sel) : null;
if (transition) {
transition.style("opacity", 0).on("end", () => {
sel.html(html);
dir.selectChildren().mount();
dir.transition(sel).style("opacity", 1);
});
} else {
sel.html(html);
this.selectChildren().mount();
}
}
}
};
//
// d3-if
// =============
//
// Show or hide an element
//
var d3If = {
mount(model) {
var sel = this.sel;
this.display = sel.style("display");
this.opacity = +sel.style("opacity") || 1;
if (!this.display || this.display === "none") this.display = "block";
return model;
},
refresh(model, value) {
var sel = this.sel,
transition = this.passes ? this.transition(sel) : null;
if (value) sel.style("display", this.display);
else if (!transition) sel.style("display", "none");
if (transition) {
if (value) transition.style("opacity", 1);
else
transition
.style("opacity", 0)
.on("end", () => sel.style("display", "none"));
} else sel.style("opacity", value ? this.opacity : 0);
}
};
//
// d3-on directive
//
// A one-way data binding from dom events to model properties/methods
// Event listeners are on the DOM, not on the model
var on = {
mount(model) {
var eventName = this.arg || "click",
expr = this.expression;
// DOM event => model binding
this.on(this.sel, `${eventName}.${this.uid}`, event => {
var md = model.$child();
md.$event = event;
expr.eval(md);
});
this.bindDestroy(model);
// Does not return the model so that model data binding is not performed
},
destroy() {
var eventName = this.arg || "click";
this.on(this.sel, `${eventName}.${this.uid}`, null);
}
};
const base$2 = {
on(model, attrName) {
var refresh = refreshFunction(this, model, attrName),
uid = model.uid;
// DOM => model binding
this.sel.on(`input.${uid}`, refresh).on(`change.${uid}`, refresh);
},
off(model) {
var uid = model.uid;
this.sel.on(`input.${uid}`, null).on(`change.${uid}`, null);
},
value(value) {
if (arguments.length) this.sel.property("value", value);
else return this.sel.property("value");
}
};
function createValueType(proto) {
function ValueType(el) {
sel(this).el = el;
}
ValueType.prototype = d3Let.assign({}, base$2, proto);
return ValueType;
}
function refreshFunction(vType, model, attrName) {
return debounce(() => {
model.$set(attrName, vType.value());
});
}
var checkbox = createValueType({
value(value) {
if (arguments.length) this.sel.property("checked", value);
else return this.sel.property("checked");
}
});
var input$1 = createValueType();
var select$1 = createValueType({
value(value) {
var sel = this.sel,
options = sel.selectAll("option"),
values = value,
opt;
if (arguments.length) {
if (!d3Let.isArray(values)) values = [value || ""];
options.each(function() {
opt = d3Selection.select(this);
value = opt.attr("value") || "";
opt.property("selected", values.indexOf(value) > -1);
});
} else {
values = [];
options.each(function() {
opt = d3Selection.select(this);
if (opt.property("selected")) values.push(opt.attr("value") || "");
});
if (sel.property("multiple")) return values;
else return values[0] || "";
}
}
});
var types = {
input: input$1,
textarea: input$1,
select: select$1,
checkbox
};
//
// d3-value directive
// ===================
//
// Two-way data binding for HTML elements supporting the value property
var value = {
create(expression) {
var type = this.sel.attr("type"),
tag = this.el.tagName.toLowerCase(),
ValueType = types[type] || types[tag];
if (!ValueType) {
this.logWarn(`Cannot apply d3-value directive to ${tag}`);
return;
}
this.tag = new ValueType(this.el);
return expression;
},
mount(model) {
var expr = this.expression;
// TODO: relax this constraint
if (expr.parsed.type !== expr.codes.IDENTIFIER) {
this.logWarn(
`support identifiers only, got "${expr.parsed.type}": ${
this.expression
}`
);
return;
}
var attrName = this.expression.expr;
//
// Create the model reactive attribute
model.$set(attrName, this.tag.value());
// register dom event
this.tag.on(model, attrName);
return model;
},
refresh(model, value) {
this.tag.value(value);
},
destroy(model) {
this.tag.off(model);
}
};
var directives$1 = {
attr,
html,
value,
on,
for: d3For,
if: d3If
};
// Core Directives
const coreDirectives = extendDirectives(new Map(), directives$1);
// the root view constructor
var main$1 = config =>
createComponent("view", protoView, coreDirectives)(config);
// Add callback to execute when the DOM is ready
var dom = callback => {
providers.readyCallbacks.push(callback);
/* istanbul ignore next */
if (document.readyState !== "complete") {
document.addEventListener("DOMContentLoaded", _completed);
// A fallback to window.onload, that will always work
window.addEventListener("load", _completed);
} else domReady();
};
/* istanbul ignore next */
const _completed = () => {
document.removeEventListener("DOMContentLoaded", _completed);
window.removeEventListener("load", _completed);
domReady();
};
const domReady = () => {
let callback;
while (providers.readyCallbacks.length) {
callback = providers.readyCallbacks.shift();
d3Timer.timeout(callback);
}
};
var slugify = str => {
str = str.replace(/^\s+|\s+$/g, ""); // trim
str = str.toLowerCase();
// remove accents, swap ñ for n, etc
var from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;";
var to = "aaaaeeeeiiiioooouuuunc------";
for (var i = 0, l = from.length; i < l; i++) {
str = str.replace(new RegExp(from.charAt(i), "g"), to.charAt(i));
}
str = str
.replace(/[^a-z0-9 -]/g, "") // remove invalid chars
.replace(/\s+/g, "-") // collapse whitespace and replace by -
.replace(/-+/g, "-"); // collapse dashes
return str;
};
exports.viewVersion = version;
exports.viewBase = base$1;
exports.viewBootstrapForms = plugin;
exports.viewForms = plugin$1;
exports.view = main$1;
exports.viewModel = model;
exports.viewExpression = viewExpression;
exports.jsep = jsep;
exports.isAbsoluteUrl = isAbsoluteUrl;
exports.viewLibs = viewLibs;
exports.viewRequire = viewRequire;
exports.viewRequireFrom = viewRequireFrom;
exports.viewResolve = viewResolve;
exports.viewDashify = dashify;
exports.viewDebounce = debounce;
exports.viewReady = dom;
exports.HttpError = HttpError;
exports.HttpResponse = HttpResponse;
exports.jsonResponse = jsonResponse;
exports.viewProviders = providers;
exports.viewSelect = asSelect;
exports.viewSlugify = slugify;
exports.viewElement = htmlElement;
exports.viewTemplate = template;
exports.viewUid = uid;
exports.viewWarn = warn;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=d3-view.js.map