d3-view
Version:
View-model d3 plugin for building interactive data-driven web interfaces
135 lines (121 loc) • 3.32 kB
JavaScript
import { assign, isArray, isString } from "d3-let";
import properties from "../utils/htmlprops";
import { addAttributes, formChild } from "./utils";
//
// Mixin for all form elements
export const formElement = {
props: ["form"],
addChildren(sel, form) {
var children = this.props.children;
if (children) {
if (!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 (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
export default 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;
}
});