Controllers
import {Controller} from 'cx/ui';Controllers are used to concentrate business logic required for views. This includes preparing data for
rendering, calculating values, reacting on changes, defining callbacks, etc.
Controllers are assigned to widgets using the controller attribute. Once assigned, controllers are passed
down the widget tree to all descendants.
onInit, onDestroy
onInit method is invoked once the controller is created. Similarly, onDestroy is called
just before the controller is destroyed.
onInit method is a good place to initialize or fetch data and define triggers and computable values.
class TabController extends Controller {
onInit() {
this.store.set('$page.tabOpenTime', 0);
this.timer = setInterval(() => {
this.store.update('$page.tabOpenTime', t => t + 1);
}, 1000);
}
onDestroy() {
clearInterval(this.timer);
}
}Triggers
Triggers execute a given callback when specified data changes.
Triggers are defined using the addTrigger method which takes four arguments.
The first argument is the trigger's name which can later be used to remove the trigger using the
removeTrigger method.
The second argument is a list of bindings to be monitored.
The third argument is a function which will be executed when any of the bindings change.
The fourth argument should be a boolean value which controls whether or not the trigger should be
immediately executed.
Triggers are used to:
- load data from the server when selection changes
- implement complex data behavior
class CbController extends Controller {
onInit() {
this.addTrigger('t1', ['$page.cb1'], cb1 => {
this.store.set('$page.cb2', !cb1);
});
this.addTrigger('t2', ['$page.cb2'], cb2 => {
this.store.set('$page.cb1', !cb2);
});
}
}Computed Values
Computed values are automatically calculated based on other data, similar to how spreadsheet formulas work. The following example shows how the population is calculated by selecting individual cities from the drop-down.
Computed values are defined using the addComputable method which takes three arguments.
The first argument is a binding path where computed data will be stored.
The second argument is a list of bindings. Values pointed by these bindings will be used as input parameters
for calculation. The third argument is a function which computes the data.
Computed values are commonly used to:
- filter data
- provide additional data based on selection
- aggregate data
class InfoController extends Controller {
onInit() {
this.store.set('$page.cities', [{id: 'ams', text: 'Amsterdam', population: '1.6M'}, {id: 'bg',text: 'Belgrade',population: '3M'}]);
this.addComputable('$page.city', ['$page.cities', '$page.cityId'], (options, id) => {
return options.find(o=>o.id == id);
});
}
}Any method defined in a controller can be invoked from the view code. This is commonly used for event callbacks.
For simple controller invocations, use short syntax by assigning just the name of the
controller method to the handler. In this case, arguments passed over to the method will be event and instance.
When using names of callbacks, it's possible to invoke methods defined higher in the ancestor controller tree.
invokeParentMethod
In some cases we might need to call the parent Controller's method from within the child Controller that contains part of the business logic.
In such cases, we can use invokeParentMethod as shown in the example below.
invokeParentMethod accepts the name of the parent method, followed by any number of arguments the method expects.
// Open fiddle to see the entire code example
// https://fiddle.cxjs.io/?f=4m9CnnAH
class NewTaskController extends Controller {
addTask() {
let task = this.store.get("$page.task");
this.store.delete("$page.task");
// addNewTask method is defined in TodoListController
this.invokeParentMethod("addNewTask", task);
}
}invokeMethod
invokeMethod behaves similarly to invokeParentMethod with the difference that the current Controller instance is checked first
if it contains the specified method. This is useful if we are invoking the Controller's method from an inline event handler:
<div
controller={{
onSubscribe(email) {
alert("Your email is: " + email);
}
}}
layout={LabelsTopLayout}
>
<TextField label="Email" value-bind="$page.email" />
<Button
text="Subscribe"
onClick={(e, {controller, store}) => {
let email = store.get('$page.email')
// do some additional logic...
controller.invokeMethod('onSubscribe', email);
}}
/>
</div>invokeParent accepts the name of the method, followed by any number of arguments the method expects.