domodel is front-end library that organizes the user interface into models (look) and bindings (behavior) it follows the principle of separation of concerns, it also introduce elements of the observable pattern for the communication between the different parts of the user interface.
npx create-domodel-app [name]
npm install domodel
A model is a JSON representation of a DOM Element.
A model can also be used to refer to both the Model and its Binding as a whole, that is a component (a search bar model for example).
Let's take this Model for example:
export default {
tagName: "button"
}
That would the equivalent of:
const button = document.createElement("button")
Next, a Model with children:
export default {
tagName: "div",
children: [
{
tagName: "h2",
identifier: "headline",
textContent: "Unveil a new world"
}
]
}
Notice the textContent
property. You can set any Element properties in this fashion.
If you want to set an
attribute
use the attributes object property.
The identifier
allows your binding to track a model and manipulate it in within the available hooks.
Most properties listed in your model comes from the Element class.
It also includes:
tagName
-string
- Which is passed tocreateElement
children
-Array
- To add children to an Elementidentifier
-string
- To save and retrieve a NodechildModel
- ChildModel - For model nesting
To add a Model to the DOM we use the Core.run method provided by the Core module.
Create a main.js
in src/
, it is the entry point module that is defined in your index.html
:
src/main.js
import { Core } from "domodel" // first we're importing DOModel
// It is preferred to use camel case and suffix model names with "Model" and binding names with "Binding" such as: RectangleModel and RectangleBinding.
import Model from "./model/model.js" // the model we defined earlier, it is our super model
import ModelBinding from ".model/model.binding.js" // the binding we will be defining .bindinglater
window.addEventListener("DOMContentLoaded", function() {
Core.run(Model, {
method: Core.METHOD.APPEND_CHILD, // This is the default method and will append the children to the given target.
binding: new ModelBinding({ myProp: "hello :)" }), // we're creating an instance of our binding (which extends the Binding class provided by DOModel) and passing it to the run method.
target: document.body // the node we want to target in this case it is the node where we want to append the child node using appendChild.
})
})
Create the associated Binding:
src/model/model.binding.js
import { Core } from "domodel" // you could import the library again and run yet another model inside this model
class ModelBinding extends Binding {
onCreated() {
// access your model root element through the root property: this.root
// access identifier with the identifier property:
this.elements.headline.textContent = "The new world was effectively unveiled before my very eyes"
// you might even run another model inside this model
}
}
export default ModelBinding
-
APPEND_CHILD
Append your model totarget
-
INSERT_BEFORE
Insert your model beforetarget
-
INSERT_AFTER
Insert your model aftertarget
-
REPLACE_NODE
Replacetarget
with your model -
WRAP_NODE
Wraptarget
inside your model -
PREPEND
Insert your model before the first child oftarget
These are available through Core.METHOD.
While a Model defines the look of a component, a Binding defines its behavior.
The following are hooks that your binding can implement to add specific behaviors to your models.
This method will be called before your model is added to the DOM.
This method will be called immediately after your model is added to the DOM.
The following properties are made available from within the the instance of a Binding:
root
Root Element of your model.identifier
Hosts individual Element previously tagged in the definition of the model (see Model properties).
Listen to a given observable event. See Binding.listen.
This is the preferred method for listening to events as any listener will be cleaned up when your binding is removed using Binding.remove.
Synonym of Core.run, with the differences being :
target
property is set to the current binding root element.- A hierarchy of models is created using Binding._children. Making it easier to remove them using Binding.remove.
Remove the model from the DOM. See Binding.remove.
An Observable is a way for your models to communicate with each other and store their states.
src/object/observable-example.js
import { Observable } from "domodel"
class ExampleObservable extends Observable {
// you can have a constructor
// getter setter...
// or even better, you could have methods.
}
export default ExampleObservable
Here we associate the EventListener with our current binding and give it observable
as the observable to register the events to.
src/model/model.binding.js
import { Observable, Binding } from "domodel"
import ModelEventListener from "/model/model.event.js"
class ModelBinding extends Binding {
constructor(observable) {
super(new ModelEventListener(observable))
}
}
export default ModelBinding
Any method inside an EventListener
is automatically registered as a listener to the given observable.
src/model/model.event.js
import { EventListener } from "domodel"
class ModelEventListener extends EventListener {
message(data) {
console.log(data)
}
}
export default ModelEventListener
Useful when you want to listen to other parts your UI.
src/model/model.binding.js
import { Observable, Binding } from "domodel"
class ModelBinding extends Binding {
onCreated() {
const observable = new Observable()
observable.listen("message", data => {
console.log(data)
})
}
}
export default ModelBinding
Binding.listen also works on non-Observable targets.
src/model/model.binding.js
import { Observable } from "domodel"
class ModelBinding extends Binding {
onCreated() {
const observable = new Observable()
observable.emit("message", { /* data go here */ })
}
}
export default ModelBinding
You can also emit to a non-Observable target using Binding.emit.
Running your model:
import { Core, Observable } from "domodel"
import Model from "/model/model.js"
import ModelBinding from "/model/model.binding.js"
const observable = new Observable()
Core.run(Model, { target: document.body, binding: new ModelBinding({ observable }) })
src/model/application.js
import Model from "./model.js"
export default {
tagName: "div",
children: [
Model
]
}
src/model/application.binding.js
import { Core } from "domodel"
import Model from "./model.js"
import ModelBinding from "./model.binding.js"
class extends Binding {
onCreated() {
Core.run(Model, { target: this.root, binding: new ModelBinding() })
}
}
export default class
src/model/application.js
import Model from "./model.js"
import ModelBinding from "./model.binding.js"
export default {
tagName: "div",
children: [
{
childModel: {
model: Model,
binding: ModelBinding // optionnal
arguments: [] // optionnal
identifier: "model" // optionnal
// Any other property is ignored.
}
}
]
}
In some cases, you might want to reference to a nested model.
You can use the identifier
, it will reference to an instance of the Binding you specified, in this case it would be an instance of ModelBinding
.
Accessing the reference:
src/model/model.binding.js
import { Binding } from "domodel" // you could import the library again and run yet another model inside this model
class extends Binding {
onCreated() {
console.log(this.identifiers.model) // returns an Identifier
// and much more...
}
}
export default class
You can alter an existing model using the ModelChain
API:
const MyModel = {
tagName: "div",
children: [
{
tagName: "div",
identifier: "title"
}
]
}
const MyModelChain = new ModelChain(MyModel).after("title", {
tagName: "div",
textContent: "A description"
})
Core.run(MyModelChain.definition, { target: document.body })
Available methods:
ModelChain.prepend
ModelChain.append
ModelChain.replace
ModelChain.before
ModelChain.after
See http://domodel.unificator.me.
See https://github.com/topics/domodel-extension.
See https://github.com/topics/domodel-demo.
npm install
npm test