This is a d3 plugin for building interactive web interfaces. It provides data-reactive components with a simple and flexible API.
- Modern javascript
- Minimal footprint - use only what you need
- Built on top of d3
- Installing
- Getting Started
- View
- Model
- Directives
- Components
- Expressions
- Plugins
- Providers
- Other Frameworks
- D3 plugins
If you use NPM, npm install d3-view
.
Otherwise, download the latest release.
You can also load directly from giottojs.org,
as a standalone library or
unpkg.
AMD, CommonJS, and vanilla environments are supported. In vanilla, a d3 global is exported.
Try d3-view in your browser.
<script src="https://d3js.org/d3-array.v1.min.js"></script>
<script src="https://d3js.org/d3-collection.v1.min.js"></script>
<script src="https://d3js.org/d3-color.v1.min.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
<script src="https://d3js.org/d3-ease.v1.min.js"></script>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<script src="https://d3js.org/d3-timer.v1.min.js"></script>
<script src="https://d3js.org/d3-array.v1.min.js"></script>
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script>
<script src="https://d3js.org/d3-transition.v1.min.js"></script>
<script src="https://giottojs.org/latest/d3-let.min.js"></script>
<script src="https://giottojs.org/latest/d3-view.min.js"></script>
<script>
var vm = d3.view();
...
vm.mount("#my-element");
</script>
d3.view
is a d3 plugin for building
data driven web interfaces. It is not a framework as such, but you can easily
build one on top of it.
Importantly, this library does not make any choice for you, it is build on top of the modular d3 library following very similar design patterns.
To create a view object for you application, invoke the d3.view
function
var vm = d3.view({
model: {...},
props: [...],
components: {...},
directives: {...}
});
You can create more than one view:
var vm2 = d3.view({
model: {},
props: [...],
components: {},
directives: {}
});
All properties in the input object are optionals and are used to initialised the view with custom data (model), components and directives.
With the exception of the mount and use methods, the view API is available once the view has been mounted to an HTML element, i.e. once the mount method has been called.
The model bound to the view, the combo gives the name to the object, the view-model object.
The parent of a view, always undefined, a view is always the root element of a view mounted DOM.
Root HTMLElement of the view.
Create a new HTML Element with the given tag. Return a [d3.selection][] of the new element.
Mount a view model into the HTML element
.
The view only affect element
and its children.
This method can be called once only for a given view model.
Install a plugin into the view model. This method can be called several times with as many plugins as one needs, however it can be called only before the view is mounted into an element.
At the core of the library we have the Hierarchical Reactive Model. The Model is comparable to angular scope but its implementation is different.
-
Hierarchical: a root model is associated with a d3-view object.
-
Reactive:
A model can be associated with more than one element, new children model are created for elements that needs a new model.
For example, a component that specify the model
object during initialisation, creates its own model,
a child model of the model associated with its parent element.
Get the ancestor of the model if it exists. It it does not exist, this is the root model.
Get an attribute value from the model, traversing the tree. If the attribute
is not available in the model,
it will recursively retrieve it from its parent.
Set an attribute value in the model, traversing the tree. If the attribute is not a reactive attribute it becomes one.
Same as $set bit for a group of attribute-value pairs.
Add a callback
to a model reactive attribute
. The callback is invoked when
the attribute change value only. It is possible to pass the callback
only, in which
case it is triggered when any of the model own attributes change.
Remove all callbacks from reactive attributes
Crate a child model with prototypical inheritance from the model.
var a = d3.viewModel({foo: 4});
var b = model.$child({bla: 3});
b.foo // 4
b.bla // 3
b.parent // a
Create a child model, with no inheritance from the parent (an isolated model).
var a = d3.viewModel({foo: 4});
var b = model.$new({bla: 3});
b.foo // undefined
b.bla // 3
b.parent // a
Directives are special attributes with the d3-
prefix.
Directive attribute values are expected to be binding expressions.
The library provides several directives for every day task.
For example the d3-html
directive binds an expression to the inner
Html of the element containing the directive:
<div id="entry">
...
<p d3-html='paragrah'></p>
...
</div>
Here the paragraph
is a reactive attribute of the View model.
d3.view({
model: {
paragraph: 'TODO'
}
}).mount('#entry');
The d3-attr directive creates a one-way binding between a model property and an HTML element attribute
<input d3-attr-name="code" d3-attr-placeholder="description || code">
The attr
can be omitted for class
, name
, disabled
,
readonly
and required
.
<input d3-name="code" d3-class="bright ? 'bright' : 'dull'">
code
and bright
are reactive properties of the view-model.
As the name suggest, the d3-for directive can be used to repeat the
element once per item from a collection. Each element gets its own model,
where the given loop variable is set to the current collection item,
and index
is set to the item index or key.
The d3-html directive creates a one-way-binding between a model property and the innerHtml property of the hosting HTML element. You can use it to attach html or text to element dynamically.
With d3-if you can create conditionals.
The d3-model directive is a special directive and the first to be mounted, if available, into the hosting element. As the name suggests, the directive creates a new model on the element based on data from parent models.
The d3-on directive attaches an event listener to the element. The event type is denoted by the argument.
The expression should be model method call, the event callback
. if the attribute is omitted
it is assumed to be a click
event.
The event callback
listens to native DOM events only.
<button d3-on-click="submit()">Submit</button>
The d3-show directive display or hide an element. The display style is preserved.
The d3-value directive establish a two-way data binding for HTML elements supporting the value property. The binding is two ways because
- an update in the model attribute causes an update in the HTML value property
- an update in the HTML value property causes an update in the model attribute
Creating a custom directive involve the following steps:
- Create a (reusable) directive object:
var mydir = {
create (expression) {
return expression;
},
mount (model) {
return model;
},
refresh (model, value) {
},
destroy () {
}
};
- Add the directive to the view constructor:
var vm = d3.view({
el: '#entry',
directives: {
mydir: mydir
}
};
- Use the directive via the
d3-mydir
attribute.
A directive is customized via the four methods highlighted above.
None of the method needs implementing, and indeed for some directive
the refresh
method is the only one which needs attention.
Directives can also be added via plugins
The HTML Element hosting the directive, available after initialisation and therefore accessible by all API methods.
The parsed expression, available after the create method has been called.
The create
method is called once only, at the end of directive initialisation, no binding with the HTML element or model has yet occurred.
The expression
is the attribute value, a string, and it is not yet parsed.
This method must return the expression for parsing (it doesn't need to be the same as the input expression).
However, if it returns nothing, the directive is not executed.
The mount
method is called once only, at the beginning of the binding process with the HTML element.
The expression returned by the create
method
has been parsed and available in the this.expression
attribute.
This method must return the model for binding (it doesn't need to be the same as the input model, but usually it is).
However, if it returns nothing, the binding execution is aborted.
This method is called every time the model associated with the element hosting the directive, has changed value. It is also called at the end of a successful mount.
Called when the element hosting the directive is removed from the DOM.
Components help you extend basic HTML elements to encapsulate reusable code.
They are custom elements that d3.view
attach specified behavior to.
In order to use components you need to register them with the view
object:
d3.view({
components: {
tag1: component1,
...
tagN: componentN
}
});
A component is either an object:
var component1 = {
render: function () {
return this.htmlElement('<p>Very simple component</p>');
}
};
or a function, the component render method:
function component1 () {
return this.htmlElement('<p>Another very simple component</p>');
}
# component.el
The HTML element created by the component render method. Available after the component is mounted.
# component.model
The model bound to the component
A component is defined by the render method. However, there optional properties and methods that can be used to customize construction and lifecycle of a component.
var component = {
model: {...},
props: [...],
init (options) {
},
render (data, attr) {
},
mounted () {
},
destroy () {
}
};
The optional props
array can specify a set of html attributes which
contribute to the component data.
The html properties can contain
- JSON strings
- Model attribute name
A function or an object which specifies the default values of the component model.
Once the component has been mounted, this is becomes the model associated with the component and therefore an API property of the component.
Some component have their own model, other they use the model of the parent component.
Hook called once only at the beginning of the component initialisation process, before it is mounted into the DOM.
This is the only required hook. It is called once only while the component is being mounted into the DOM and must return a single HTMLElement or a selector with one node only. The returned element replaces the component element in the DOM. Importantly, this function can also return a Promise which resolve in an HTMLElement or selector.
- data is the data object in the component element
- attrs is an object containing the key-value of attributes in the component element
Hook called after the component has been mounted in to the DOM. In this state the component has the full API available and all its children elements are mounted too.
Called when the component HTML element is removed from the DOM.
The most important part of a component is the render
method. This sections
deals with the API available to the component once it is created.
The API is very similar to the view-api since components and views share
the same constructor.
Events object which can be used for registering event listeners or firing events.
The parent component. If not defined this is the root view, not a component.
The view object the component belongs to.
Component unique identifier
The text we put inside directive's values are called binding expressions
.
In d3-view, a binding expression consists of a single JavaScript expression
but not operations. The difference between expressions and operations is akin
to the difference between a cell in an Excel spreadsheet vs. a proper JavaScript program.
Valid expression are:
"The sun" // literal
theme // An identifier (a property of a model)
dosomething() // A function
[theme, number] // Arrays of identifiers
x ? "Hi" : "goodbye" // Conditionals
and complex combinations of the above
user.groups().join(", ")
[theme, user.groups(), "Hi"]
Expression can be created via the javascript API:
var expression = viewExpression(<expression string>);
The original expression string passed to the Expression constructor.
Evaluate an expression with data from a given model
. The model
can be a model instance or a vanilla object.
Same as expression.eval but does not throw an exception if evalutation fails. Instead it logs the error end returns nothing.
Array of identifiers (model properties) in the expression.
Plugins, usually, add functionality to a view-model. There is no strictly defined scope for a plugin but there are typically several types of plugins you can write:
- Add a group of components
- Add a group of directives
- Add some components methods by attaching them to components prototype.
- Add providers to the
view.providers
object
A plugin can be an object with the install
method or a simple
function (same signature as the install method).
var myPlugin = {
install: function (vm) {
// add a component
vm.addComponent('alert', {
model: {
style: "alert-info",
text: "Hi! this is a test!"
},
render: function () {
return view.htmlElement('<div class="alert" :class="style" d3-html="text"></div>');
}
});
// add another component
vm.addComponent('foo', ...);
// add a custom directive
vm.addDirective('mydir', {
refresh: function (model, value) {
...
}
});
}
}
A plugin is installed in a view via the chainable use
method:
var vm = d3.view();
vm.use(myPlugin).use(anotherPlugin);
This library include a form plugin for creating dynamic forms from JSON layouts.
The plugin adds the d3form
component to the view-model:
import {view, viewForms} from 'd3-view';
var vm = view().use(viewForms);
If you are using rollup to compile your javascript application, the form plugin will be included in your compiled file only if
import {viewForms} from 'd3-view';
is present somewhere in your code. Otherwise, it will be eliminated thanks to tree-shaking.
# form.setSubmit()
Sets the form model formSubmitted
and formSubmitted
reactive attribute to true
and
returns a Promise which resolves in true
or false
depending if the form inputs pass validation.
# form.isValid()
Check if the form inputs pass validation, return true
or false
.
It is possible to use bootstrap layouts for d3 forms by importing and using the viewBootstrapForms
plugin:
import {view, viewForms, viewBootstrapForms} from 'd3-view';
var vm = view().use(viewForms).use(viewBootstrapForms);
TODO
In order of complexity