@mobilabs/view
v1.0.2
Published
A companion View library for building web applications
Downloads
4
Readme
View
View
is a companion View library for building web applications. On the opposite of React, VueJS and Angular, View focuses on the viewing operations only. Besides, View
has a very fast learning curve as it doesn't introduce any new directives, pseudo-code, etc. Thus, View
is only intended to create web components, insert them into the DOM and update them.
If you master, HTML, CSS, Javascript and the DOM, you are almost done!
View
is intended to run on ECMAScript 2015 (ES6) compliant browsers.
Quick Startup
View introduces the concept of View Component
only. And, as a View Component
is just a plain old Javascript object, it is easy to understand.
A View component
looks like this:
const C = View.Component({
render() {
return '<div><p>Hi!</p></div>';
},
});
It is just a Javascript object that inherits from View Component
. In its minimalist form, it contains only a render
method that returns an XML string.
When your View Component
is defined, you just have to attach it to the DOM through the static method render:
View.render({
el: '#app',
children: { '<C />': C },
template: `
<div>
<C />
</div>
`,
});
And you get that in the DOM:
<div id="app">
<div id="i9t0img1">
<div id="ih4423b9">
<p>Hi!</p>
</div>
</div>
</div>
Simple isn't!
But of course, you can do much more. You can encapsulate a component inside another component, you can interact with a component, you can send and receive messages from components, you can create animated components, etc.
- View
Installation
install @mobilabs/view --save
Create a View
The simplest View
View.render({
template: `
<div>
<p>Hi!</p>
</div>
`,
});
As nothing else as an HTML block is defined here, View
renders the block in the body of the HTML page like this:
<body>
<div>
<p>Hi!</p>
</div>
</body>
A more concrete View
const C = View.Component({
render() {
return '<div><p>Hi!</p></div>';
},
});
View.render({
el: '#app',
children: { '<C />': C },
template: `
<div>
<C />
</div>
`,
});
In the example above, we rendered the component C
into the DOM and we attached it to the node with the id
app
.
The property el
could be an id
, a class
or a DOM element
.
The property children
associates the tag <C />
to the View Component
C
.
The property template is just an XML string. A template always consists of a set of HTML tags surrounded by a div
, a header
or a footer
tag.
If it isn't the case, the template is ignored and View.render()
prints a warning message
to the browser's console.
The tag <C />
gives the position of the component C
in the template.
A component tag is always a string starting with <
and ending with />
(the space is mandatory).
If everything goes well, the DOM contains:
<div id="app">
<div id="ihihjzw6">
<div id="ie6sr3dm">
<p>Hi!</p>
</div>
</div>
</div>
As you can notice, each component has a unique id
. This id
is randomly created by View.render()
.
View.render()
returns an object. This object is the root component
. All the other components are the children of the root component
.
So, to get the view
object, you have to write:
const view = View.render({
el: '#app',
children: { '<C />': C },
template: `
<div>
<C />
</div>
`,
});
This object view
allows us to interact with the children components. We will see how later on.
Create a View Component
The simplest View Component
const C = View.Component({
render() {
return '<div><p>Hi!</p></div>';
},
});
This component C
inherits from View.Component
. In its simplest form, it implements a method render
only that returns a simple XML string.
From now, we call template
The XML string returned by the method render
.
A View component template is always a set of HTML tags surrounded by a div
, a header
or a footer
tag.
If it isn't the case, the component won't be inserted into the DOM and a warning message will be displayed to the browser's console.
A more concrete View Component
const C = View.Component({
init() {
this.message = 'Hi';
},
render() {
return `<div><p>${this.message}</p></div>`;
},
});
View.component
provides an empty init
method that is executed before the component is rendered.
Thus, you can implements the init
method and use it to initialize a few variables and include them in the template as it is a simple Javascript string.
A Named View Component
You can provide a name to your View component
. This can be done through the method init
like this:
const C = View.Component({
init() {
this.name = 'hello_comp';
},
render() {
return '<div><p>Hi!</p></div>';
},
});
Naming a component is useful to interact with it. You will see how later on.
A composite View Component
As said in the introduction a View component
can insert another View component
following the principle of the Russian dolls
.
So, you can do:
const C1 = View.Component({
render() {
return `
<div>
<h2>My Subtitle</h2>
</div>
`;
},
});
const C2 = View.Component({
render() {
this.children = { 'C1 />': C1 };
return `
<div>
<h1>My Title</h1>
<C1 />
</div>
`;
},
});
const view = View.render({
el: '#app',
children: { '<C2 />': C2 },
template: `
<div>
<C2 />
</div>
`,
});
As you can notice, C1
is a simple View component
while C2
is a View component
that includes another View component
.
The tag >C1 />
defines the position of the C1
component inside the template of the C2
component.
The variable this.children
links the tag <C1 />
with the variable C1
that defines the C1 component
.
After the View.render()
method is executed, the DOM contains:
<div id="app">
<div id="iehfbl6v">
<div id="i9nsypb1">
<h1>My Title</h1>
<div id="ip7l4e96">
<h2>My Subtitle</h2>
</div>
</div>
</div>
</div>
Extend the View Component with custom methods
const C1 = View.Component({
updateTile(title) {
this.$('h1').text(title);
},
render() {
return `
<div>
<h1>---</h1>
</div>
`;
},
});
As you can notice, we added to this View component
the custom method updateTitle
. This method changes the title when it is called (we will see later on how to call it).
updateTitle
relies on a set of primitives to interact with the component node in the DOM.
The method $
has a behaviour similar to jQuery. $('h1')
selects the child h1
and .text(title)
replaces the text in between the two tags <h1></h1>
.
$
can interact only with the node of the component it belongs to. With $
you cannot access to the parent node of this component or the sibling nodes.
$
is prepositioned to the root node of the component. So,
this.$().addClass('aaa');
will add the class aaa
to the first div
of the node like this:
<div id="i9t0img1" class="aaa">
<h1>Title</h1>
</div>
So, now you have learned how to interact with the component into the DOM.
Add DOM Events to View Component
This is an example of a simple counter:
const C = View.Component({
init() {
this.props.options.counter = 0;
},
events() {
let { counter } = this.props.options;
// Increment the counter:
this.$('.plus').on('click', () => {
counter += 1;
this.$('h1').text(counter);
});
// Decrement the counter:
this.$('.minus').on('click', () => {
counter -= 1;
this.$('h1').text(counter);
});
},
render() {
return `
<div>
<h1>${this.props.options.counter}</h1>
<button class="minus">-</button>
<button class="plus">+</button>
</div>
`;
},
});
When the user clicks on the button +
, the code:
this.$('.plus').on('click', () => {
counter += 1;
this.$('h1').text(counter);
});
increments the variable counter
and inserts its new value between the tags <h1></h1>
.
And, when the user clicks on the button -
, the code:
this.$('.minus').on('click', () => {
counter -= 1;
this.$('h1').text(counter);
});
decrements the variable counter
and inserts its new value between the tags <h1></h1>
.
The events
method is automatically called when the component is instantiated.
Thus, you just need to insert your code inside the method events
and your code is automatically executed when the component is rendered.
You have learned now how to implement DOM events.
Create an animation
You can easily animate an element in the DOM with the method $().animate
like this:
const C = View.Component({
animate(...args) {
this.$('.rect').animate(...args);
},
render() {
return `
<div>
<div class="rect" style="position: absolute; top: 0; left: 0; width: 100px; height: 100px; border: 1px solid red;"></div>
</div>`;
}
});
In the above example, we have defined a rectangle with a width and height of 100px
positioned at the top left corner of the browser window.
This component has a custom method animate
that processes an animation thanks to:
this.$('.rect').animate(...args);
If you pass the arguments:
animate({ top: '500px', left: '800px' }, 'slow', 'swing', callback)
The rectangle moves from (0, 0) to (500, 800) when the method animate
is called (we will see later on how to call a component method).
Send and Receive Messages
Of course, you can create a View component with two methods to send and receive messages from a caller like this:
const C1 = View.Component({
sendMessage() {
return 'my message';
},
listenMessage(message) {
// do want you want with this message
},
render() {
return `
<div>
<h1>Title</h1>
</div>
`;
},
});
But, this is not very convenient.
View
implements a message bus
that allows components to communicate between them or to the outside word.
Thus, a component can send or receive messages like this:
const C1 = View.Component({
events() {
this.$listen('c1:title', (data) => {
this.updateTitle(data);
this.$emit('c1:feedback', 'Done!');
});
},
updateTitle(title) {
this.$('h3').text(title);
},
render() {
return `
<div>
<h3>???</h3>
</div>
`;
}
});
This component listens for the event c1:title
from an emitter (another component or the outside world). When it receives the message, it passes the received payload to the method updateTitle
(that updates the title in the DOM) and then it sends the message Done!
to listeners (anybody can listen an event) listening the event c1:feedback
.
Naming an event with two substrings separated with the character :
is purely conventional. Here the first substring is the name of the component and the second substring defines the nature of the event. It is up to you to choose your convention.
The method $listen
is defined inside the method events
to be called automatically when the component is instantiated.
Now, you know how to send and receive messages to View components
.
Hyperscript
Until now, we defined a template as an XML string. View.Component
implements an hyperscript
converter. Thus, if you prefer, you can define your template like this:
const H = View.Component({
render: function() {
const h = this.$hyperscript;
return h('div', { class: 'h' },
h('p', { style: 'font-weight: bold; font-style: italic;' }, 'My paragraph ...')
);
}
});
The syntax is the following:
h('HTML tag', 'attribute', 'value')
Or, you can replace the value by a child node like this:
h('HTML tag', 'attribute',
h('HTML tag', 'attribute', 'value')
)
You can also inserts a child component like this:
const H = View.Component({
render: function() {
const h = this.$hyperscript;
return h('div', null,
h('p', null, 'My paragraph ...'),
h(Ooo, { name: 'Ooo', options: { title: 'ooo' } })
);
}
});
This example shows how to insert the component Ooo
. The property name
defines the tag name of the component without the surrounding < />
.
Well, you can now write your template with hyperscript
if you prefer.
Interact with View Components
We learned, so far, how to create a View component
, add custom methods, render the component, change part of the component in the DOM, manage DOM events, send and receive messages.
There are still an operation we haven't learned. How to call the methods of a component. Remember, we explain earlier that View.render
returns the root component object
:
const view = View.render({
el: '#app',
children: { '<C />': C },
template: `
<div>
<C />
</div>
`,
});
<div id="app">
<div id="ihihjzw6"> <----- the root component object
<div id="ie6sr3dm">
<p>Hi!</p>
</div>
</div>
</div>
The root component object
is a View.Component
. Thus, it inherits of all the methods defined by View.Component
.
Among these methods, there is one interesting method: $getChild
.
You can use this method to retrieve a child component by its tag
, its id
or its name
:
const child = view.$getChild('i2qyl7qa');
The method $getChild
parses all the component tree (all levels) to find a children with the id
i2qyl7qa
. In case of success, it returns the component object otherwise it returns null.
The method $getChild
parses entirely a branch before parsing a second branch. Thus, if a child of the first child matches, it stops. If a child of the second child has the same tag, id or name, it won't never be reached.
Once, you get a child object, you can process it by calling its standard or custom methods.
Now, you know how to call a View component
method
.
A better way
As we already explained View components
can listen and send messages to their counterparts or to the outside world.
Thus, sending message is an option to require actions from a component.
For instance, we can request an action from the footer component by doing this:
view.$listen('footer:feedback', (message) => {
// process message
});
view.$emit('footer:setCopyright', 'Copyright (c) John Doe. All rights reserved.');
$emit
sends an event to the footer
component with a payload
. $listen
and waits for a response.
Reference
View
Static Methods | Description
Component(options) | returns the child component constructor,
render({...}) | renders the components in the DOM and returns the root component object,
restore(view), | restores the DOM to its initial state,
append({...}) | adds a component to a defined component as the last child,
prepend({...}) | adds a component to a defined component as the first child,
remove({...}) | removes a component from a defined component,
plugin({name, ref}) | attaches a plugin,
View Component Methods
Methods | Description
$(sel) | returns an object to manipulate the comp. in the DOM,
$hyperscript(h) | returns an XML string of the hyperscript template,
$getIdAndName() | returns the component's Id and name,
$getChildren() | returns the list of the first level children,
$getChild(tag/id/name) | returns the matching child object,
$show() | turns the component visible,
$hide | turns the component invisible,
$listen(event, handler) | listens a message,
$emit(event, payload) | sends a message,
$() methods
Methods | Description
$() | selects the View Component and returns this,
$(sel) | selects the child element with the attribute 'sel' and returns this,
$().id | returns the id of selected element,
$()[0] | returns the selected DOM element,
$().select(el) | selects the child element with the attribute 'sel' and returns this
$().selectChild(n) | selects the nth child,
$().parent() | selects the parent node,
$().firstParent() | selects the root parent node if defined,
$().find(sel) | returns the NodeList of the matching children,
$().tag() | returns the nag name of the selected element,
$().html() | returns the child element(s) of the selected element.
$().html(xml) | replaces the child element by a new element defined by the passed-in XML string and returns this,
$().empty() | removes all the child nodes and returns this,
$().append('tag') | inserts the element (defined by the HTML tag) after the last child node and returns this,
$().appendTextChild('text') | appends a text child node to the selected element and return this,
$().appendBefore('tag') | inserts the element before the selected element and returns this,
$().appendAfter('tag') | inserts the element after the selected element and returns this,
$().replace('tag') | replaces the selected element by the passed-in element and returns this,
$().appendHTML(xml) | inserts the element (defined by the passed-in XML string) after the last child node and returns this,
$().prepend(xml) | inserts the element before the first child node and returns this,
$(el).after(xml)¹ | inserts the element after the selected element and returns this,
$(el).before(xml)¹ | inserts the element before the selected element and returns this,
$(el)replaceWith(xml)¹ | replaces the selected element by the passed-in element and returns this,
$().text() | returns the contents of the selected element,
$().text('string') | replaces the text contents and returns this,
$().clone(deep) | clones the selected node if deep is false, clones node and childs if deep is true,
$().firstChild() | returns the first child,
$().insertChildBefore(n, c) | inserts the child 'n' before the child 'c' and returns this,
$().removeChild(child) | removes the child 'child' and returns this,
$().replaceChild(n, c) | replaces the child 'c' by the child 'n' and returns this,
$().children() | returns a DOM object with all the node children,
$().childIndex() | returns the child index (0 for the first child),
$().getRect() | returns the position and size of the node,
$().css(style) | returns the style value,
$().css(style, value) | sets the style value and returns this,
$().getClassList() | returns a DOMTokenList (getClassList() is a wrapper around classList),
$().addClass('className') | adds 'className' to the selected element and returns this,
$().addClasses([...]) | adds an array of classes and returns this,
$().removeClass('className') | removes 'className' from the selected element and returns this,
$().removeClasses([...]) | removes an array of classes and returns this,
$().toggleClass('className') | adds or removes 'className' to/from the selected element and returns this,
$().hasClass('class') | returns true if the node has the class 'class' or false if not,
$().attr(attribute) | returns the attribute value of the selected element,
$().attr(attribute, value) | sets the attribute of the selected element,
$().removeAttr(attribute) | removes the attribute from the selected element,
$().animate(props, d, e, cb) | changes dynamically the CSS attributes,
$().diff(XMLString) | updates the selected DOM elements that differ from the passed-in template and return this,
$(el).on(event, listener) | adds an event listener to the selected child and returns this.
$(el).off(event, listener) | removes the attached event listener from the selected child and returns this.
¹ after, before and replacewith could only apply to a child element and not to the Component itself,
License
MIT.