I made and maintained for a few years a browser extension and I can remember that I would often write tons of code that would later make good features. However those were not easy to maintain and I had two things I wanted to do: one is that I would have a way to write code that could be reused accross the entire application and the second to centralize events.
userinterface.js was built around the idea that logic relating to how the visual looks and how the visual works should be distinguished.
At the same time userinterface.js is providing new ways to write and organize your UI.
Another advantage of using userinterface.js is code reusability through principles of separation of concerns as well UI mechanisms abstraction.
None, 100% pure Vanilla JS :)
To get started with userinterface.js all you need to do is scaffold a new project by following the instructions on the userinterface.js-skeleton repository.
In order to be able to use userinterface.js in the browser you can use git submodules.
Run git submodule add https://github.com/thoughtsunificator/userinterface.js.git lib/userinterface.js
at the root of your project.
userinterface.js is located in the lib/
folder at the root of your project.
A Model
is an object representation of a Node.
It has three required properties depending on the method: name
, method
and properties
or callback
,
The name
property will be the identifier of your model it will be used whenever you need to run your model.
The method
property will describe how your model should be ran.
The properties
and callback
properties will contain the properties of your Elements.
A Model
often goes along with a Binding and an Object.
We create a model named simple model
with the method appendChild
it has a two LI
element children that have the className simplemodel
and textContent My first simple model
.
Yes you got it, every properties (except children
) will be set to the element.
Code:
UserInterface.model({
name: "simplemodel",
method: UserInterface.appendChild,
properties: {
tagName: "li", // required
className: "simplemodel",
textContent: "My first simple model"
}
});
UserInterface.runModel("simplemodel", { parentNode: document.querySelector("ul") });
Output:
<ul>
<li class="simplemodel">My first simple model</li>
</ul>
In the previous example we created a simple model, but what if we wanted to do more and add some children to it ?
The children
property is here for that, it is an Array where you can specify child elements.
UserInterface.model({
name: "children",
method: UserInterface.appendChild,
properties: {
tagName: "div",
className: "model",
children: [
{
tagName: "div",
className: "child",
textContent: "My first child"
// and so on..
}
]
}
});
UserInterface.runModel("children", { parentNode: document.body });
Output:
<body>
<div class="model">
<div class="child">My first child</div>
</div>
</body>
Models are required to have either the properties
property or callback
property, but exactly what does the callback
property do ?
It is used when you want to echo some data in your model.
For example here, we have a model called echomodel
that has the callback
property. This property works the same as the properties
property does except that an extra step is added before your model is ran.
The callback
will return a properties
object accordingly to the data you passed through runModel
.
UserInterface.model(
name: "echomodel",
method: UserInterface.appendChild,
callback: data => ({
tagName: "p",
className: "echomodel",
textContent: "My "+data.text+" model"
})
);
UserInterface.runModel("echomodel", { parentNode: document.body, data: {"text": "echo" } });
Output:
<p class="echomodel">My echo model</p>
A Binding
is a callback function that, when bound to a model, is automatically called whenever the model has ran.
Bindings
will make your models more alive, an example of that would be adding an event listener to your model, that is the place where you will be doing it.
You can also do much more such as using event listeners to connect all of your models together!
A Binding is way to give life to your models enabling them to do things whenever their respective method is executed. That means if you want to add a listener to an Element that's where you will be doing it.
In this example we will change the textContent of our model root element.
UserInterface.model({
name: "button",
method: UserInterface.appendChild,
properties: {
tagName: "button"
}
});
UserInterface.bind("button", function(element) {
element.textContent = "bound";
});
UserInterface.runModel("button", { parentNode: document.body });
Output:
<button>bound</button>
Objects
are the backbone of your models they will store and manipulate data for your Binding
.
That's where you want to hide the complicated stuff.
Listeners allow your models to communicate with each others.
In this example we are creating and running a model called myModel
that will himself run another model and pass it the context myObj
.
Contexts represent a reserved area for models to communicate with each others, they're often represented as Object but could pretty much be anything.
After the second model ran it will listen to the "greeting" announce
.
Did you notice the event listener in our first model ? Yes, whenever our first model is clicked it will announce
"greeting" to the context myObj
and pass it an empty object as data. This empty object could also be anything that you want to pass to the other model.
When your model receives an announce it also comes along with data.
UserInterface.model({
name: "myModel",
method: UserInterface.appendChild,
properties: {
tagName: "div"
}
});
UserInterface.model({
name: "someobscuremodel",
method: UserInterface.appendChild,
properties: {
tagName: "div"
}
});
UserInterface.bind("myModel", function(element) {
const _myObj = new Obj();
element.addEventListener("click", function() {
UserInterface.announce(_myObj, "greeting", {});
});
UserInterface.runModel("someobscuremodel", { parentNode: document.body, bindingArgs:[_myObj] });
});
UserInterface.bind("someobscuremodel", function(element, myObj) {
UserInterface.listen(myObj, "greeting", function(data) {
// do something useful with data or greet back
});
});
UserInterface.runModel("button", { parentNode: document.body });
-
appendChild
Append your model to the target -
insertBefore
Insert your model before the target -
removeElement
Remove the target -
replaceElement
Replace the target with your model -
updateElement
Update the target according to your model -
wrapElement
Wrap the target inside your model -
removeListeners
Remove the listeners of the target
- model(model)
Load a model
- bind(name, callback)
Link a model to a "binding", that is a callback function
- runModel(name, parameters)
Update the DOM accordingly to a model
- createNodes(properties) ⇒
Array.<Element>
Transform a model into one or many Elements
- getModelProperties(name, [data]) ⇒
Object
Returns the properties of a model
- listen(context, title, callback)
Load a listener
- removeListener(listener)
Remove a listener
- announce(context, title, content)
Message one or many listeners
Load a model
Kind: global function
Param | Type | Description |
---|---|---|
model | object |
|
model.name | string |
The name of the model |
model.method | string |
One of the following methods name: appendChild, insertBefore, removeElement, updateElement, replaceElement, wrapElement, clearListeners |
model.properties | Object |
Processed properties along with any properties an Element¹ can have |
model.callback | function |
Callback of processed properties along with any properties an Element¹ can have |
[model.properties.children] | Array.<Object> |
An array of the "properties" object |
Link a model to a "binding", that is a callback function
Kind: global function
Param | Type | Description |
---|---|---|
name | string |
The name of the model |
callback | function |
The function binding the model |
Update the DOM accordingly to a model
Kind: global function
Param | Type | Description |
---|---|---|
name | string |
The name of the model |
parameters | Object |
The parameters of the model |
parameters.parentNode | Element |
The target Node |
[parameters.data] | Object |
The data that will be echoed on the model |
[parameters.bindingArgs] | Array |
The arguments that go along with the binding |
Transform a model into one or many Elements
Kind: global function
Returns: Array.<Element>
- An array of Elements¹
Param | Type | Description |
---|---|---|
properties | Object | function |
Processed properties along with any properties a Element can have or a callback returning them |
Returns the properties of a model
Kind: global function
Returns: Object
- The "properties" object of the model
Param | Type | Description |
---|---|---|
name | string |
The name of the model |
[data] | Object |
The data that will be echoed on the model |
Load a listener
Kind: global function
Param | Type | Description |
---|---|---|
context | * |
Where the announce will be broadcasted |
title | string |
The content of the message |
callback | function |
[removeListener description]
Kind: global function
Param | Type | Description |
---|---|---|
listener | Object |
Message one or many listeners
Kind: global function
Param | Type | Description |
---|---|---|
context | * |
Where the announce will be broadcasted |
title | string |
The title of the announce |
content | * |
The content of the announce |
UserInterface.js could not find the model specified when calling UserInterface.bind
.
UserInterface.js could not find the model specified when calling UserInterface.runModel
.
Open an issue if you think your issue is not listed above.
userinterface.js also provides a collection that contains a few basic models to get you started.
- userinterface.js-suraidaa
- userinterface.js-puissance4
- userinterface.js-consoru
- userinterface.js-calculator
- userinterface.js-paint
npm install
Then simply run npm test
to run the tests