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
peopleas the tag, a component is created wherever<people></people>appears in a template.The
scopeobject on aComponentcontains the component's state, data, and behavior. Here, it specifies how toremovea person from the list:The template for the component itself is passed via the
templateproperty. This can either be an external file or a string. Eachliusescan-click, which declares an event binding. Here,removeinside the component's scope will be called with the relevantpeopleobject as an argument.This behaves similarly to the
can.Controlfrom 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
tabswidget 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
scopeobject 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
TabsViewModelneeds:Since TabsViewModel is a
can.Map, thepanelsproperty is automatically converted to acan.List. Theactiveproperty references thepanelobject 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
tabscomponent needs a list of panel titles that willactivatethat panel when clicked. By callingactivatewith a panel as the argument, the properties of thepanelcan be manipulated. By changing thevisibleproperty 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
tabscomponent withpanelcomponents inside it. Thetemplateobject 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
tabscomponent contains panels, which are also defined as components. The tabs template contains the logic for whether the panel is visible (visibleis controlled by the tabs component'sactivatemethod).Each panel's
scopecontains a title, which should be taken from thetitleattribute in the<panel>tag. If you want to set the string value of a Component's attribute as ascopevariable, use@'.In addition to the
scopeproperty, a component has aneventsproperty. Thiseventsproperty uses acan.Controlinstantiated inside the component to handle events.Since we defined behavior for adding panels on the parent
tabscomponent, we should use this method whenever apanelis inserted into the page (and aninsertedevent is triggered). To add the panel to thetabscomponent's scope, we call theaddPanelmethod 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.