Previous recipes have demonstrated how to change page content and introduced
event handling. The following recipes will introduce can.Component,
which allows for straightforward widget construction by packaging
template, state, and event handling code in one place.
While similar behavior can be accomplished with can.Control,
building a Component enables building reusable widgets using custom
HTML tags.
Create a Component
The previous recipe that displays a list of people can instead
be represented as a component.
<people></people>
By specifying people as the tag, a component is created wherever <people></people>
appears in a template.
can.Component.extend({
tag: 'people',
The scope object on a Component contains the component's state, data,
and behavior. Here, it specifies how to remove a person from the list:
scope: {
people: people,
remove: function( person ) {
var people = this.attr("people");
var index = people.indexOf(person);
people.splice(index, 1);
}
}
});
The template for the component itself is passed via the template
property. This can either be an external file or a string.
Each li uses can-click, which declares an event binding.
Here, remove inside the component's
scope will be called with the relevant people object
as an argument.
This behaves similarly to the can.Control from above.
However, the <people> tag can be used without having
any knowledge about the inner workings of the widget.
Using declarative HTML tags, a component can be used
without writing any javascript. The template, state,
and behavior are all combined into one Component.
Build a Tabs Widget
A tabs widget could be instantiated with the following HTML:
A designer that understands HTML can put together a template for a tabs
widget without understanding anything other than the syntax.
This is one of the most useful features of components.
Tabs Widget Behavior
Before implementing the component itself, we’ll
define an observable view model--the scope object
of the UI element. This makes the code modular and easier
to manage (and also allows for unit testing).
In order to accurately represent a tabs widget,
a TabsViewModel needs:
An observable list of panels
A state variable with the active panel
Helper methods to add, remove, and activate panels
Since TabsViewModel is a can.Map, the panels property is
automatically converted to a can.List.
The active property references the panel object
that should currently be displayed.
var TabsViewModel = can.Map.extend({
panels: [],
active: null,
addPanel: function( panel ){
var panels = this.attr("panels");
panels.push(panel);
panel.attr("visible", false);
//activate panel if it is the first one
if ( panels.attr("length") === 1 ){
this.activate( panel );
}
},
removePanel: function( panel ){
var panels = this.attr("panels");
var index = panels.indexOf(panel);
panels.splice(index, 1);
//activate a new panel if panel being removed was the active panel
if( this.attr("active") === panel ){
panels.attr("length") ? this.activate(panels[0]) : this.attr("active", null)
}
},
activate: function( panel ){
var active = this.attr("active")
if( active !== panel ){
active && active.attr("visible", false);
this.attr("active", panel.attr("visible", true));
}
}
});
Tabs Widget Component
Now that the view model is defined, making a component is simply
a matter of defining the way the tabs widget is displayed.
The template for a tabs component needs a list of panel titles
that will activate that panel when clicked. By calling activate
with a panel as the argument, the properties of the panel can
be manipulated. By changing the visible property of a panel,
a template can be used to display or hide the panel accordingly.
For this component, our template should look something like this:
A designer can create a tabs component with panel components inside it.
The template object on the tabs component's scope needs to be able to render
the content that is inside of the <tabs> tag. To do this, we simply use the
<content> tag, which will render everything within the component's tags:
The tabs component contains panels, which are also defined
as components. The tabs template contains the logic for whether
the panel is visible (visible is controlled by the tabs
component's activate method).
Each panel's scope contains a title, which should be
taken from the title attribute in the <panel> tag.
If you want to set the string value of a Component's
attribute as a scope variable, use @'.
In addition to the scope property, a component has an
events property.
This events property uses a can.Control instantiated inside
the component to handle events.
Since we defined behavior for adding panels on the parent
tabs component, we should use this method whenever a panel
is inserted into the page (and an inserted event is triggered).
To add the panel to the tabs component's scope, we call the
addPanel method by accessing the parent scope with this.element.parent().scope():
With this component, any time a <tabs> element with
<panel> elements is put in a page, a tabs widget will
automatically be created. This allows application behavior
and design to be compartmentalized from each other.
Previous recipes have demonstrated how to change page content and introduced event handling. The following recipes will introduce
can.Component
, which allows for straightforward widget construction by packaging template, state, and event handling code in one place.While similar behavior can be accomplished with
can.Control
, building a Component enables building reusable widgets using custom HTML tags.Create a Component
The previous recipe that displays a list of people can instead be represented as a component.
By specifying
people
as the tag, a component is created wherever<people></people>
appears in a template.The
scope
object on aComponent
contains the component's state, data, and behavior. Here, it specifies how toremove
a person from the list:The template for the component itself is passed via the
template
property. This can either be an external file or a string. Eachli
usescan-click
, which declares an event binding. Here,remove
inside the component's scope will be called with the relevantpeople
object as an argument.This behaves similarly to the
can.Control
from above. However, the<people>
tag can be used without having any knowledge about the inner workings of the widget. Using declarative HTML tags, a component can be used without writing any javascript. The template, state, and behavior are all combined into one Component.Build a Tabs Widget
A tabs widget could be instantiated with the following HTML:
A designer that understands HTML can put together a template for a
tabs
widget without understanding anything other than the syntax. This is one of the most useful features of components.Tabs Widget Behavior
Before implementing the component itself, we’ll define an observable view model--the
scope
object of the UI element. This makes the code modular and easier to manage (and also allows for unit testing).In order to accurately represent a tabs widget, a
TabsViewModel
needs:Since TabsViewModel is a
can.Map
, thepanels
property is automatically converted to acan.List
. Theactive
property references thepanel
object that should currently be displayed.Tabs Widget Component
Now that the view model is defined, making a component is simply a matter of defining the way the tabs widget is displayed.
The template for a
tabs
component needs a list of panel titles that willactivate
that panel when clicked. By callingactivate
with a panel as the argument, the properties of thepanel
can be manipulated. By changing thevisible
property of a panel, a template can be used to display or hide the panel accordingly.For this component, our template should look something like this:
A designer can create a
tabs
component withpanel
components inside it. Thetemplate
object on the tabs component's scope needs to be able to render the content that is inside of the<tabs>
tag. To do this, we simply use the<content>
tag, which will render everything within the component's tags:The
tabs
component contains panels, which are also defined as components. The tabs template contains the logic for whether the panel is visible (visible
is controlled by the tabs component'sactivate
method).Each panel's
scope
contains a title, which should be taken from thetitle
attribute in the<panel>
tag. If you want to set the string value of a Component's attribute as ascope
variable, use@'
.In addition to the
scope
property, a component has anevents
property. Thisevents
property uses acan.Control
instantiated inside the component to handle events.Since we defined behavior for adding panels on the parent
tabs
component, we should use this method whenever apanel
is inserted into the page (and aninserted
event is triggered). To add the panel to thetabs
component's scope, we call theaddPanel
method by accessing the parent scope withthis.element.parent().scope()
:With this component, any time a
<tabs>
element with<panel>
elements is put in a page, a tabs widget will automatically be created. This allows application behavior and design to be compartmentalized from each other.