LOGO

Components

Edit

Component, the basic unit of San, is an independent unit of data, logic, and view. From a page perspective, a component is an extension of an HTML element; from a functional mode perspective, a component is a ViewModel.

Component defining

The most basic way to define a component is to inherit from san.Component. San also provides the san.inherits method for inheritance.

1
2
3
4
5
6
7
8
9
10
function MyApp(options) {
san.Component.call(this, options);
}
san.inherits(MyApp, san.Component);

MyApp.prototype.template = '<ul><li s-for="item in list">{{item}}</li></ul>';

MyApp.prototype.attached = function () {
this.data.set('list', ['san', 'er', 'esui', 'etpl', 'esl']);
};

Then you can use this component by creating an instance with ‘new’ method. Obviously, if you want the component to appear on the page, you need to call the attach method to render the component inside root DOM.

1
2
var myApp = new MyApp();
myApp.attach(document.body);

The advantage of defining components in an inherited way is that when you use ESNext, you can naturally use extends.

tips: Since ESNext itself does not support overwriting prototype, San supports the static property. You should define template/filters/components properties as static property when use extends a component in ESNext.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import {Component} from 'san';

class HelloComponent extends Component {

constructor(options) {
super(options);
// .....
}

static template = '<p>Hello {{name}}!</p>';

initData() {
return {name: 'San'}
}
}

new HelloComponent().attach(document.body);

When defining a component in non-ESNext way, it is boring to create a function, call san.inherits and define various prototypes. San provides a shortcut method san.defineComponent to make it easy.

1
2
3
4
5
6
7
var MyApp = san.defineComponent({
template: '<ul><li s-for="item in list">{{item}}</li></ul>',

attached: function () {
this.data.set('list', ['san', 'er', 'esui', 'etpl', 'esl']);
}
});

Lifecycle

San components use HTML-based template syntax in order too keep their lifecycles consistent with WebComponents.

  • compiled - Compiled the template of a component
  • inited - Initialized a component instance with template and data
  • created - Created a component element
  • attached - Attached a component to DOM
  • detached - Detached a component from DOM
  • disposed - Destoryed a component instance

The lifecycle of a component has some of these features.

  • The lifecycle represents the state of the component. The essence of the lifecycle is state management.
  • At the different stages of the life cycle, the component will trigger the corresponding hook functions.
  • States coexist such as attached and created.
  • States are mutually exclusive such as attached and detached , disposed and others.
  • Some time points do not represent the state of the component, only a certain behavior. The hook function is also triggered when the behavior is completed. For example, updated represents the completion of the view change caused by each data change.

Through the hook function of the life cycle, we can customize to do something in different lifecycles. For example, in the attached lifecycle initiate a request to get data, and then update the data to refresh the view.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var ListComponent = san.defineComponent({
template: '<ul><li s-for="item in list">{{item}}</li></ul>',

initData: function () {
return {
list: []
};
},

attached: function () {
requestList().then(this.updateList.bind(this));
},

updateList: function (list) {
this.data.set('list', list);
}
});

The following diagram details the lifecycle of the component

View

Component Template

When defining a component, you can assign a component’s view template through the template.

1
2
3
4
5
6
7
8
9
10
11
12
13
san.defineComponent({
template: '<div>'
+ '<label><input type="checkbox" value="errorrik" checked="{= online =}">errorrik</label>'
+ '<label><input type="checkbox" value="otakustay" checked="{= online =}">otakustay</label>'
+ '<label><input type="checkbox" value="firede" checked="{= online =}">firede</label>'
+ '</div>',

initData: function () {
return {
online: ['errorrik', 'otakustay']
};
}
});

Usually, it’s not friendly to write HTML snippets in JavaScript. We can write templates in separate files and manage them through tools or loaders.

Referencing a template by webpack + ESNext:

1
TODO

Referencing a template via the text plugin in an AMD environment

1
2
3
4
5
6
7
8
9
san.defineComponent({
template: require('text!./template.html'),

initData: function () {
return {
online: ['errorrik', 'otakustay']
};
}
});

attention. It is a rule of San template that is for a component to return one HTML element. You should group a list of children with adding root element.

1
2
3
4
<dl>
<dt>name - email</dt>
<dd s-for="p in persons" title="{{p.name}}">{{p.name}}({{dept}}) - {{p.email}}</dd>
</dl>

The HTML element corresponding to the component may be specified by its owner component through the view template, and the view template does not directly declare the corresponding HTML element. You can now specify the HTML element of the view template as template.

1
<template class="ui-timepicker">{{ value | valueText }}</template>

slot

The position of a slot can be declared in the view template by slot, the content of which can be defined by the outer component. Refer to the slot document for more details.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var Panel = san.defineComponent({
template: '<div>'
+ ' <div class="head" on-click="toggle">title</div>'
+ ' <p style="{{fold | yesToBe(\'display:none\')}}"><slot></slot></p>'
+ '</div>',

toggle: function () {
this.data.set('fold', !this.data.get('fold'));
}
});

var MyComponent = san.defineComponent({
components: {
'ui-panel': Panel
},

template: '<div><ui-panel>Hello San</ui-panel></div>'
});

/* MyComponent rendered
<div>
<div class="head">title</div>
<p style="display:none">Hello San</p>
</div>
*/

el

The properties of the component instance el represents the HTML element of the component, which can be passed in via option when the component is initialized.

Most of the time you don’t need to care about it when writing components. But if you pass el when you initialize the component, it means that the component has this element as the component root element. And the element will:

  • not render the view using the default template
  • not create a root element
  • directly call compiled, created, attached lifecycle

Sometimes we want the initial view to be direct HTML for the first time, not by the component rendering. But at the same time, we want components to manage data, logic, and views for us, as well as subsequent user interactions and view transformations through component management. In this case, you can pass in an existing element via el.

The component will take the element passed in by el as the component root element and parse out the view structure. This process is called component reversal. Refer to the component reversal document for more.

Data

All component data related operations are provided by the data property of the component instance.

Retrieving Data

Retrieve data through the data.get method.

1
2
3
4
5
6
san.defineComponent({
attached: function () {
var params = this.data.get('params');
this.data.set('list', getList(params[1]));
}
});

The data.get method accepts a string representing the property accessor, so the above example can also be written like this:

1
2
3
4
5
6
san.defineComponent({
attached: function () {
var param = this.data.get('params[1]');
this.data.set('list', getList(param));
}
});

Manipulating Data

data provides some methods of data manipulation. Refer to the data method document for more.

Initializing Data

When the component is instantiated, you can pass the data option to specify the component’s initial data.

1
2
3
4
5
6
7
8
9
10
var MyApp = san.defineComponent({
template: '<ul><li s-for="item in list">{{item}}</li></ul>'
});

var myApp = new MyApp({
data: {
list: ['san', 'er', 'esui', 'etpl', 'esl']
}
});
myApp.attach(document.body);

Passing in the initial data when new a component is not a common parttern. In general, If you want to set initial data for each instance when you define a component, you can specify it in the initData method. The initData method returns initial data for the component instance.

1
2
3
4
5
6
7
8
9
10
11
12
var MyApp = san.defineComponent({
template: '<ul><li s-for="item in list">{{item}}</li></ul>',

initData: function () {
return {
list: ['san', 'er', 'esui', 'etpl', 'esl']
};
}
});

var myApp = new MyApp();
myApp.attach(document.body);

Computed Data

Sometimes, the value of a data item may be computed from other data items, and we can define computed to compute data. computed is an object, the key is the name of the computed data item, and value is a function that returns the value of the data item.

1
2
3
4
5
6
7
8
9
10
san.defineComponent({
template: '<a>{{name}}</a>',

// name 数据项由 firstName 和 lastName 计算得来
computed: {
name: function () {
return this.data.get('firstName') + ' ' + this.data.get('lastName');
}
}
});

In this case, item name is a computed data, whose value computed from firstName and lastName data item.

tips: In functions of computing data, you can only use the this.data.get method to get the values of data items. You cannot call a component method with this.method or set the component data with this.data.set.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
san.defineComponent({
template: '<a>{{info}}</a>',

// name 数据项由 firstName 和 lastName 计算得来
computed: {
name: function () {
return this.data.get('firstName') + ' ' + this.data.get('lastName');
},

info: function () {
return this.data.get('name') + ' - ' + this.data.get('email');
}
}
});

The computed data item can depend on another computed data item. In the above example, the name item that the info item depends on is a computed data item. However, be careful when using it, do not form a circular dependency between the computed data items.

Filter

Interpolation is a common way of presenting data when defining view templates. When writing interpolation, we often use filter to convert the data into a form suitable for view presentation.

1
{{createTime | dateFormat('yyyy-MM-dd')}}

Built-in filter

San has several filters built in for common scenarios

  • html - Escaping HTML. This filter is used by default when no filter is specified.
  • url - Escaping URL
  • raw - No escaping. Use this filter when you don’t want to use HTML escaping

Customized filter

By defining the component’s filters member, you can specify which filters the component’s view template can use.

1
2
3
4
5
6
7
8
9
san.defineComponent({
template: '<a>{{createTime | dateFormat("yyyy-MM-dd")}}</a>',

filters: {
dateFormat: function (value, format) {
return moment(value).format(format);
}
}
});

The first parameter of the filter function is the data value corresponding to the expression. The parameters passed in the filter call are followed by the second parameter.

tips: Given the independence of components, San does not provide a way to register global filters. Defining the filters used by components must be in their own filters.

Component Level

We know that under the component system, components must be nestable tree relationships. Let’s do some explanation from a piece of code below. In the code below, AddForm internally uses two custom components: ui-calendar and ui-timepicker.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Template -->
<div class="form">
<input type="text" class="form-title" placeholder="标题" value="{= title =}">
<textarea class="form-desc" placeholder="备注" value="{= desc =}"></textarea>

<div>Expected completion time:
<ui-calendar value="{= endTimeDate =}" s-ref="endDate"></ui-calendar>
<ui-timepicker value="{= endTimeHour =}" s-ref="endHour"></ui-timepicker>
</div>

<div class="form-op">
<button type="button" on-click="submit">ok</button>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
var AddForm = san.defineComponent({
// template

components: {
'ui-timepicker': require('../ui/TimePicker'),
'ui-calendar': require('../ui/Calendar')
},

submit: function () {
this.ref('endDate')
this.ref('endHour')
}
});

components

Components typically use other components by declaring custom elements.

Which subcomponent types can be used by the component view, which must first be specified by the component’s components member. In components object, the key is the name of a custom element, and the value is component class.

tips:Given the independence of components, San does not provide a way to register global components. Components must declare which components they use internally in their own components.

Some components may use themselves in content, such as tree nodes. We can set the value of this item in components to a string self

1
2
3
4
5
6
7
var Node = san.defineComponent({
// template

components: {
'ui-node': 'self'
}
});

owner and parent

The concept of owner and parent has been clarified by react, but it is still specific here.

owner of a component is whoever creates the one and manages its lifetime, interactive communication, etc. owner must be a component. owner of ui-calendar is component AddForm.

parent of a component is whoever would be the containing ancestor of the DOM hierarchy in view. parent of ui-calendar is the outer divparent has no direct meaning to component management.

ref

When declaring a subcomponent with a name specified by s-ref, you can call it from the ref method of the owner component instance.

tips: With declarative initialization, data binding, and event binding, we rarely need to get an instance of a subcomponent in the owner component. Although San provides this approach, when you use it, please think about whether you want to do this.

message

With the dispatch method, components can dispatch messages to the upper layers of the component tree.

1
2
3
4
5
6
7
8
9
10
var SelectItem = san.defineComponent({
template: '<li on-click="select"><slot></slot></li>',

select: function () {
var value = this.data.get('value');

// 向组件树的上层派发消息
this.dispatch('UI:select-item-selected', value);
}
});

The message will be passed up the component tree until it encounters the first component that processes the message. We can declare messages to be processed by the component in messages. messages is an object, the key is a message’s name, and the value is a function of message handler that receives a parameter object containing the target (the component that dispatches the message) and the value (the value of the message).

1
2
3
4
5
6
7
8
9
10
11
12
13
var Select = san.defineComponent({
template: '<ul><slot></slot></ul>',

// Declare messages that the component will process
messages: {
'UI:select-item-selected': function (arg) {
var value = arg.value;
this.data.set('value', value);

// arg.target Get the component that dispatches the message
}
}
});

Messages are primarily used for components to communicate with upper-level components of non-owner. For example, the owner of the component SelectItem in the slot is the upper-level component. And it needs to communicate with Select.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
san.defineComponent({
components: {
'ui-select': Select,
'ui-selectitem': SelectItem
},

template: ''
+ '<div>'
+ ' <ui-select value="{=value=}">'
+ ' <ui-selectitem value="1">one</ui-selectitem>'
+ ' <ui-selectitem value="2">two</ui-selectitem>'
+ ' <ui-selectitem value="3">three</ui-selectitem>'
+ ' </ui-select>'
+ '</div>'
});

Dynamic Subcomponents

3.10.0 and latter versions support s-is directive to dynamically determine the class of subcomponents.

  • The value of s-is directive can be an expression, and its evaluated value SHOULD be a string.
  • The component class to be used is the one defined in components with a key equal to the value of s-is expression.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var BLabel = san.defineComponent({
template: '<b>{{text}}</b>'
});
var ULabel = san.defineComponent({
template: '<u>{{text}}</u>'
});
var App = san.defineComponent({
components: {
'BLabel': BLabel,
'ULabel': ULabel
},
template: '<div><text s-is="type" text="{{name}}"/></div>'
});
(new App({
data: {
name: 'San',
type: 'BLabel'
}
})).attach(document.body);

Manually Created Subcomponents

In some scenarios, we want components to not create subcomponents when their own views are rendered, but rather to have the flexibility to create subcomponents at some point in the future. For example

  • It is inconvenient using declarative way while parent of floating layer(subcomponent) is not in its root element el.
  • The list needs to be created and displayed only when the user clicks

Be careful to create subcomponents manually. Here are some tips to note, and the following code fragment also gives some clarification.

  • Manually created subcomponents don’t have to be declared in components
  • Avoid recreation. A simple approach is to keep a reference to the created subcomponents instance and check if it’s already exists before creating.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
san.defineComponent({
mainClick: function () {
if (!this.layer) {
this.layer = new Layer();
this.layer.attach(document.body);

// Alternatively, uncomment the following line and remove the `disposed` handler
// this.children.push(this.layer);
}

this.layer.show();
},

disposed: function () {
if (this.layer) {
this.layer.dispose();
}

this.layer = null;
}
});

In 3.7.0 and latter versions, manually created subcomponents supports owner and source options.

Specify owner to wire the subcomponent with its owner:

  • The owner can receive messages dispatched from the subcomponent.
  • The subcomponent will be automatically disposed when its owner is disposed.

Note:

If owner is specified, do NOT manually push the subcomponent into its owner’s children. Or the subcomponent can be disposed multiple times.

Specify source to bind the manually created subcomponent to its owner:

  • data binding (including 2-way data binding)
  • events
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Binding data and events of a manually created subcomponent to its owner via `owner` and `source` options.
// 3.7.0+
var Person = san.defineComponent({
template: '<div>'
+ ' <input type="text" value="{=name=}">'
+ ' <input type="text" value="{=email=}">'
+ ' <button on-click="done">Done</button>'
+ '</div>',

done: function () {
this.fire('done', {
name: this.data.get('name'),
email: this.data.get('email')
});
}
});

var MyApp = san.defineComponent({
template: '<div>'
+ ' name: {{author.name}}; email{{author.email}}'
+ ' <button on-click="edit">edit</button>'
+ '</div>',

edit: function () {
if (!this.editor) {
this.editor = new Person({
owner: this,
source: '<x-person name="{{author.name}}" email="{{author.email}}" on-done="editDone($event)"/>'
});
this.editor.attach(document.body)
}
},

editDone: function (e) {
this.data.set('author', e);
}
});

var myApp = new MyApp({
data: {
author: {
name: 'erik',
email: 'errorrik@gmail.com'
}
}
});
myApp.attach(document.body);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 2-way data binding of a manually created subcomponent to its owner via `owner` and `source` options.
// 3.7.0+
var Person = san.defineComponent({
template: '<div>'
+ ' <input type="text" value="{=name=}">'
+ ' <input type="text" value="{=email=}">'
+ '</div>'
});

var MyApp = san.defineComponent({
template: '<div>'
+ ' name: {{author.name}}; email{{author.email}}'
+ ' <button on-click="edit">edit</button>'
+ '</div>',

edit: function () {
if (!this.editor) {
this.editor = new Person({
owner: this,
source: '<x-person name="{=author.name=}" email="{=author.email=}"/>'
});
this.editor.attach(document.body)
}
}
});

var myApp = new MyApp({
data: {
author: {
name: 'erik',
email: 'errorrik@gmail.com'
}
}
});
myApp.attach(document.body);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Dispatch events from a manually created subcomponent to its owner via the `owner` option.
// 3.7.0+
var Person = san.defineComponent({
template: '<div>'
+ ' <input type="text" value="{=name=}">'
+ ' <input type="text" value="{=email=}">'
+ ' <button on-click="done">Done</button>'
+ '</div>',

done: function () {
this.dispatch('person-done', {
name: this.data.get('name'),
email: this.data.get('email')
});
}
});

var MyApp = san.defineComponent({
template: '<div>'
+ ' name: {{author.name}}; email{{author.email}}'
+ ' <button on-click="edit">edit</button>'
+ '</div>',

edit: function () {
if (!this.editor) {
this.editor = new Person({
owner: this,
source: '<x-person name="{{author.name}}" email="{{author.email}}"/>'
});
this.editor.attach(document.body)
}
},

messages: {
'person-done': function (e) {
this.data.set('author', e.value);
}
}
});

var myApp = new MyApp({
data: {
author: {
name: 'erik',
email: 'errorrik@gmail.com'
}
}
});
myApp.attach(document.body);

Note: In cases where the manually created subcomponent with source option specified is expected to be created multiple times, the source template can be compiled manually to avoid San compiling it each time the subcomponent is created, as a measure of performance improvement.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Compiling source manually
// 3.7.0+
var PersonDetail = san.defineComponent({
template: '<div>'
+ ' {{name}}, {{email}}'
+ ' <button on-click="close">close</button>'
+ '</div>',

close: function () { this.el.style.display = 'none' },
open: function () { this.el.style.display = 'block' }
});

var Person = san.defineComponent({
template: '<div>'
+ ' name: {{info.name}}'
+ ' <button on-click="showDetail">detail</button>'
+ '</div>',

// compile source manually
detailSource: san.parseTemplate('<x-person name="{{info.name}}" email="{{info.email}}"/>')
.children[0],

showDetail: function () {
if (!this.detail) {
this.detail = new PersonDetail({
owner: this,
source: this.detailSource
});
this.detail.attach(document.body)
}

this.detail.open();
}
});

var MyApp = san.defineComponent({
template: '<div><x-p s-for="p in members" info="{{p}}" /></div>',

components: {
'x-p': Person
}
});

var myApp = new MyApp({
data: {
members: [
{ name: 'errorrik', email: 'errorrik@what.com' },
{ name: 'otakustay', email: 'otakustay@what.com' }
]
}
});

In 3.7.1 and latter versions, the source template allows child elements to specify slot contents to be inserted into the manually created subcomponent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Specifying slot contents
// 3.7.1+
var Dialog = san.defineComponent({
template: '<span><slot name="title"/><slot/></span>'
});

var MyApp = san.defineComponent({
template: '<div><button on-click="alterStrong">alter strong</button></div>',

attached: function () {
if (!this.dialog) {
this.dialog = new Dialog({
owner: this,
source: '<x-dialog>'
+ '<h2 slot="title">{{title}}</h2>'
+ '<b s-if="strongContent">{{content}}</b><u s-else>{{content}}</u>'
+ '</x-dialog>'
});
this.dialog.attach(this.el);
}
},

alterStrong: function () {
this.data.set('strongContent', !this.data.get('strongContent'));
}
});

var myApp = new MyApp({
data: {
title: 'MyDialog',
content: 'Hello San',
strongContent: true
}
});

Asynchronous Components

Version:>= 3.7.0

createComponentLoader function returns a component loader. When a component loader is specified in components, the component will be loaded asynchronously: the target component will not be rendered during attaching, instead it’ll be rendered after. The features of asynchronous components include:

  • Each loader returned by createComponentLoader will be loaded only once. In other words, the load method will be called only once.
  • The target component is ensured to be rendered asynchronously. Even if the loading has already completed, the target component rendering will still be queued as a macro task after the current component finished rendering.

createComponentLoader accepts an asynchronous function as its argument, in which case the returned Promise should be resolved as a component class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var InputComponent = san.defineComponent({
template: '<input type="text" value="{{value}}"/>'
});

// simulate the loading process, will be resolved in 1 second
var inputLoader = san.createComponentLoader(function () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(InputComponent);
}, 1000);
});
});

var MyApp = san.defineComponent({
components: {
'x-input': inputLoader
},

template: '<div><x-input value="{{name}}"/></div>'
});

var myApp = new MyApp({
data: {
name: 'San'
}
});
myApp.attach(document.body);

Typically, asynchronous components all have a certain complexity. We can specify a placeholder to be shown while the component is loading. In this case, pass an object with a load property specifying the loader function and a placeholder property specifying the placeholder component to the createComponentLoader function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var InputComponent = san.defineComponent({
template: '<input type="text" value="{{value}}"/>'
});

var LabelComponent = san.defineComponent({
template: '<u>{{value}}</u>'
});

// simulate the loading process, will be resolved in 1 second
var inputLoader = san.createComponentLoader({
load: function () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(InputComponent);
}, 1000);
});
},

placeholder: LabelComponent
});

var MyApp = san.defineComponent({
components: {
'x-input': inputLoader
},

template: '<div><x-input value="{{name}}"/></div>'
});

var myApp = new MyApp({
data: {
name: 'San'
}
});
myApp.attach(document.body);

And a fallback component can be specified in case of load failure. Remember to reject the Promise if it fails.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var LabelComponent = san.defineComponent({
template: '<u>{{value}}</u>'
});

// simulate the loading process, will be resolved in 1 second
var inputLoader = san.createComponentLoader({
load: function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject();
}, 1000);
});
},

fallback: LabelComponent
});

var MyApp = san.defineComponent({
components: {
'x-input': inputLoader
},

template: '<div><x-input value="{{name}}"/></div>'
});

var myApp = new MyApp({
data: {
name: 'San'
}
});
myApp.attach(document.body);

The fallback component can also be specified dynamically by the loader. Simply reject the Promise with a component class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var LabelComponent = san.defineComponent({
template: '<u>{{value}}</u>'
});

// simulate the loading process, will be resolved in 1 second
var inputLoader = san.createComponentLoader(function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject(LabelComponent);
}, 1000);
});
});

var MyApp = san.defineComponent({
components: {
'x-input': inputLoader
},

template: '<div><x-input value="{{name}}"/></div>'
});

var myApp = new MyApp({
data: {
name: 'San'
}
});
myApp.attach(document.body);

Error Capture

Version:>= 3.10.7

The error hook called when an error from any descendent component is captured. The hook receives three arguments: the error, the component instance that triggered the error, and a string containing information on where the error was captured.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Child = san.defineComponent({
template: '<h1>test</h1>',
attached: function () {
throw new Error('error');
}
});
var myApp = san.defineComponent({
template: '<div><x-child /></div>',
components: {
'x-child': Child
},
error: function (err, instance, info) {
// some code
}
});
myApp.attach(myApp);

The error informance info includes:

  • hook:{{ hookName }}: Captured in lifecycle hooks.
  • initData: Captured in initData function.
  • computed:{{ computedName }}: Captured in computed functions.
  • watch:{{ watchName }}: Captured in data watch handlers.
  • message:{{ messageName }}: Captured in message handlers.
  • filter:{{ filterName }}: Captured in filers.
  • event:{{ eventName }}: Captured in event handlers.
  • transitionCreate: Captured in Animation Controller Creator.
  • transitionEnter: Captured in Enter Animation Controller.
  • transitionLeave: Captured in Leave Animation Controller.