General information
In the context of Derby 0.6, components are derby templates moved out into a separate scope. This is an index.html file from one of the previous examples (TODO list).
index.html
Body:
and new-todo:
are templates. Let’s make new-todo:
a component. In order to do that derby application has to register it:
So, we need to pass a function as a second argument, which is going to handle this component. Let’s bind input to a reactive variable and create an on-submit event handler. That is how the code would look like if there were no components:
Since there are no components, certain problems occur:
- global scope is bloated
- addNewTodo function is added to app.proto -- we do NOT want spaghetti code in a big application
And here
new-todo:
is a component:
Now, there is a separate scope in the
new-todo:
template: all global collections and _page
are invisible. todo
is local and it is not accessible in the global scope. Encapsulation is a great thing. As for addNewTodo
(handler function), it is insideNewTodo
class and this way it doesn’t bloat the application.
Derby components are ui-elements, which are used to hide the implementation details of the specific visual block. Note that components are not implied to load data. Data has to be loaded on a controller level.
What is the interface of components? How do we pass arguments to the components and how do we get results?
We pass arguments just like to a regular template via attributes and as an enclosed html content. Results are returned by the use of events. Let’s set a class to the component and give a placeholder to the input. We are going to get the input data via the event handler:
index.html
The component takes 2 arguments:
placeholder
and inputClass
and returns addtodo
. addtodo
is an event which we redirect to todos-list
component, where it is handled by TodosList.prototype.add
. Note that when we created a component instance, we assigned alias list to it using a keyword as
. This way we could write list.add()
in on-addtodo
handler. As a result, new-todo
is isolated but still todos-list component handles todos list. This way, the responsibilities are separated.Interface component
Ability to pass arguments to components is inherited from templates, so most of functionality is similar. Templates (like components) in derby html files are similar to functions. We can also call templates from other templates.
Template (component) declaration syntax:
attributes
, element
and arrays
attributes are optional.element attribute
By default template declaration and template call looks like this:
It is not always convenient to do it this way. For example, we may want to call the template with a specific name not via view tag but using template name as a tag name. In this case, we are going to need an element attribute:
Or we can do it this way:
As you can see, there is no closing tag because it has no contents. What does this mean? It is an inexplicit content parameter.
When we call a template, we use either view tag or a tag named by element attribute:
It turns out that when calling a template we can put some text or enclosed html (which will be passed inside the template by an inexplicit parameter
@content
) between the opening and closing tag. Let’s replace caption with @content
:
It is very convenient because it allows to hide the details and simplify top level code.
attributes attribute
Imagine we have a task: html code which is passed to the template inside the template can’t be a solid block inserted into a specific place. Suppose, there is a widget which has a header, a footer and the main content. That’s how we call it:
Inside the widget template there is some complex html and we need to be able to insert these 3 blocks separately (that is, header, footer and body). To do this we need attributes:
Instead of body we could have used content since everything that is listed in attributes goes to the content.
Note that everything that was listed in attributes must appear in the internal block (which we insert into the template) only once. Let’s use template attribute arrays:
We set 2 names when declaring the template:
arrays=”option/options”
:- option — name of html element inside dropdown on component call
- options — name of the array with the elements inside the template (the elements inside this array are going to be objects, and all the option attributes will be fields inside these objects, whereas the contents of the object will be a content field).
javascript part of component
The template becomes a component if there is a function-constructor registered for this component.
The component has predetermined functions which are called at certain points, that is, create, init and destroy.
init()
init function is called both on server and client side before rendering the component. Its purpose is to initialize the internal model of the component, set default values, create references.
create()
It is called only on client side before rendering the component. We need it to register event handlers, plug in client libraries, subscribe for data (but it’s antipattern), run reactive functions of the component, etc.
destroy()
It is called when the component is being destroyed. We need it for final clean-up:
- release memory
- stop reactive functions
- remove client libraries.
this inside the component handlers has:
model
, app
, dom
(except init
), all aliases to dom-elements and components created within the component, parent-reference to parent-component and everything we put inside prototype function-constructor of the component.
So,
this.model
in the component has model of this particular component only. Here, model is a scoped one. If you need to address the global scope, use this.model.root
or this.app.model
.app
is a derby application instance and we can do a lot of things with it, for example:
Using dom we can add handlers to DOM-events, e.g.:
To understand this example you need to understand that this.toggleButton and this.menu are aliases to DOM-elements, which were declared in the template with the help of as. Look here
All dom functions (
on
, once
, removeListeners
) can take 4 parameters: type, [target], listener, [userCapture]
. Target is an element which is being handled. If target is not specified, it equals to document.The remaining 3 parameters are similar to parameters of addEventListener
(type, listener[, useCapture]
)
Aliases to dom-elements are set inside the template with the keyword as:
Moving component outside the application into a separate module
We need to create a folder for the component. We put js, html, css files in this folder. The component is registered in the application with
app.component
function (which has one parameter - function constructor):
Let’s look at the example:
tabs/index.js
tabs/index.html
Take a look at this line:
Here derby gets component name (it is not specified in the template since index is used). The algorithm is simple: the last segment of the path is taken.
Suppose,
_dirname
is /home/zag2art/work/project/src/components/tabs
. It means that we can access this component as tabs
, e.g.:
This is how we plug the component in the application:
It is very convenient tо write components as separate modules, e.g.: http://www.npmjs.org/package/d-d3-barchart
No comments:
Post a Comment