1 前言
Ractive.js是一款入门容易却功能强大的JS库,它的主旨是模板+数据=UI,数据的双向绑定,DOM节点的实时更新,事件处理等多个有用的功能。它吸取了AngularJS中的一些灵感,因此两者在有些地方是有相似之处的。对于一些中小型项目,完全可以用这个框架作为前端框架进行开发,可以快速建立起项目的前端模型。
RactiveJS 内置了基本的选择器模块,Template引擎,封装的Event,Node等对象。
1.1 Hello Ractive(Demo01)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test2-Ractive</title>
<script src='http://cdn.ractivejs.org/latest/ractive.min.js'></script>
</head>
<body>
<h1>This is Ractive Test</h1>
<div id="container"></div>
<script type="text/ractive" id="template">
<p>{{greeting}}, {{name}}!</p>
</script>
<script>
var ractive = new Ractive({
el : '#container',
template : '#template',
data : {name : 'ractive',greeting: 'hello'}
})
</script>
</body>
</html>
首先需要引入ractive的库文件(最新的稳定版本是0.73):
<script src='http://cdn.ractivejs.org/latest/ractive.min.js'></script>
RactiveJS需要一个模板,将输入传入模板,再将模板渲染到指定的html文档中。RactiveJS支持字符串或者script text 类型的容器。(只要type不等于text/javascripe即可)
<script type="text/ractive" id="template">
<p>{{greeting}}, {{name}}!</p>
</script>
然后进行双向绑定(two-way-binding),必须使用new字符构造一个Ractive对象,其中:
- el是要模板要插入的容器,可以使用节点、ID或者CSS选择符指定容器
- template是要被插入的模板,可以是一个选择器,一段文本。或RactiveJS (Ractive.parse())预编译的模板对象
- data是需要绑定的数据源,也就是要传入到模板之中的数据对象,在data里面指定的变量可以随意在template随意使用
<script>
var ractive = new Ractive({
el : '#container',
template : '#template',
data : {name : 'ractive',greeting: 'hello'}
})
</script>
下面的指令可以实时更改传入模板的name的值
ractive.set('name', "tom")
ractive.set('greeting', "bye")
Ractive与其他模板库的不同之处在于数据更新是局部的,对于通常的模板库,如果想要更改模板的数据,需要对整个视图进行重新渲染,也就意味着你需要删除所有已经创建的DOM节点,这是对内存的极大浪费。
在Ractive中,我们可以操作已经创建的视图,实现数据的局部更新。在数据set的过程中,RactiveJS会将
标签中的内容分成四个节点:
- {{greeting}}
- 空格
- {{name}}
- !
Ractive会将这四个节点对应的变量的引用缓存起来,当数据改变时则重新计算对应节点的变量值,而不改变其他无关节点。精确地改变文本节点的值比替换元素节点的速度快很多,尤其是在局部更改数据时。
1.2 更改元素属性(Demo02)
ReactiveJS可以通过类似的方式更改元素的属性:
<p style='color: {{color}}; font-size: {{size}}em; font-family: {{font}};'>
{{greeting}} {{name}}!
</p>
在渲染时只要增加对应的数据即可:
var ractive = new Ractive({
el: '#container',
template: '#template',
data: {
greeting: 'Ractive',
name: 'world',
color: 'purple',
size: 4,
font: 'Georgia'
}
});
也可以通过set更改元素的属性:
ractive.set({
color: 'red',
font: 'Comic Sans MS',
size: 10
});
主要注意的是,上面的代码虽然更改了元素的三个特性,但是Reactive认为它们都属于一个属性,所以只对DOM进行了一次更改。
1.3 获取数据(Demo03)
前面提到了,Ractive通过set可以设置变量值,同样,通过get可以获取变量值:
<body>
<div id="container"></div>
<script type="text/ractive" id="template">
<button id='count'>Times this button has been clicked: {{counter}}</button>
</script>
<script>
var ractive1 = new Ractive({
el : '#container',
template : '#template',
data : {
'counter' : 0
}
});
var button = document.getElementById('count');
button.addEventListener('click', function(){
ractive1.set('counter', ractive1.get('counter') + 1)
})
</script>
</body>
上面的代码中,增加counter变量并初始化为0,在通过get获取该变量的值,并为按钮添加时间,实现点击计数的功能。
传统的模要实现上述功能可能会更复杂,需要在每次点击后移除后再重新插入button,并且需要每次手动绑定、解绑事件,或者通过时间代理实现,相比Ractive来说这些其实都不是必须的。
1.4 使用外部template文档(Demo04)
- 可以使用nodeJS的require和exports来实现
- 使用ajax实现,但是要注意ajax的异步执行:
<script>
$.ajax({
url : 'test2.tpl',
dataType : 'html',
async :false,
success : function(data){
var ractive = new Ractive({
el : '#container',
template : data,
data:{
jays : [111,222,333],
jay : this[0]
}
});
}
});
</script>
2 Nested properties
Ractive使用了Mustache语法,支持嵌套属性嵌套
2.1 通过.访问属性(Demo05)
看下面的例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Ractive Demo05</title>
<script src='../libs/ractive.js'></script>
<script src='../libs/jquery.min.js'></script>
</head>
<body>
<h1>This is the fifth demo of Ractive.</h1>
<div id="container"></div>
<button onClick="changeValue()">Change Value(BAD)</button>
<button onClick="changeValueGood()">Change Value(GOOD)</button>
<script type="text/ractive" id="template">
<h2>Country profile</h2>
<P><b style="color:red">{{country.name}}'s</b> capital is <b style="color:red">{{country.capital.name}}</b> which locates at
<b style="color:blue">{{country.capital.lat}}</b> and <b style="color:blue">{{country.capital.lon}}</b>.</p>
</script>
<script>
// 实例ractive对象
var ractive = new Ractive({
el: '#container',
template: '#template',
data: {
country: {
name: 'China',
capital: {
name: 'Beijing',
lat : 501,
lon : 299
}
}
}
});
// BAD
function changeValue() {
ractive.set('country', {
name : 'USA'
})
}
// GOOD
function changeValueGood() {
ractive.set('country.name','USA')
}
</script>
</body>
</html>
在模板中可以使用.语法来获取对应的嵌套的属性。在文档渲染后也可以通过set改嵌套属性的一个或者多个。
ractive1.set('country', {
name : '日本'
})
使用上面的这种方式就会使country的其他属性默认为null,在文档中不予以渲染。所有如果仅仅要更改一个或属性可以使用下面这种形式:
ractive1.set('country.name','日本')
2.2 通过{{#value}}{{/value}}访问属性(Demo06)
上面通过类似JS属性的访问方法访问嵌套属性还是比较繁琐的,可以使用块(section)来为属性提供作用域,例如上面的模板就可以更改为:
<h2>Country profile</h2>
{{#with country}}
<P>{{name}} has a climate which is {{temperature}} and {{rainfall}}</p>
{{#with capital}}
<p>Its population is {{population}} and its capital is {{name}} and locates at {{lat}} and {{lon}}.</p>
{{/with}}
{{/with}}
需要注意的有:
- 指明作用域块其实可以不必加with关键词,可以直接使用#和/表明块:
<h2>Country profile</h2>
{{#country}}
<P><b style="color:red">{{name}}</b> has a lot of famous cities like:
<b style="color:blue">{{cityName}}...</b>
{{/country}}
但是Ractive官方文档建议加上with关键词,因为在上面的代码中,Ractive遇到{{#country}}之后会自己根据块的值去确定使用with、if还是each来渲染这个块。
所以显式的声明块的处理类型对于程序的可读性来说还是非常有益的。
- 一个块中应该包含一对完整的标签,不应该将其在循环中打开,比如:
{{#capital}}
<p>Its population is {{population}} and its capital is {{name}} and locates at {{lat}} and {{lon}}.
{{/country}}
</p>
这样程序就会报错。
- 在上面的代码中有两个name属性,其中属于capital块中的name属性有两个作用域,当最近的作用域可以找到对应的值时就取这个值,如果最近的作用域中没有对应值时就向上寻找,知道最外层的data的作用域。(与JS变量的函数中变量的作用域类似)
3 Expressions 自定义表达式(方法)(demo07)
对demo05的代码修改:
data : {
country : {
name : 'China',
temperature : 'cold',
rainfall : 'excessive',
population : '120000000',
capital : {
name : 'beijing',
lat : 501,
lon : 299
}
}
}
可以像JS一样,定义的属性可以是一个函数(也就是对应的方法),在模板中调用这个方法对传入的数据进行处理,比如说可以再data中定义一个format的方法对数字进行处理:
data : {
country : {
name : 'China',
temperature : 'cold',
rainfall : 'excessive',
population : '120000000',
capital : {
name : 'beijing',
lat : 501,
lon : 299
}
}
format: function ( num ) {
if ( num > 1000000000 ) return ( num / 1000000000 ).toFixed( 1 ) + ' billion';
if ( num > 1000000 ) return ( num / 1000000 ).toFixed( 1 ) + ' million';
if ( num > 1000 ) return ( Math.floor( num / 1000 ) ) + ',' + ( num % 1000 );
return num;
}
}
在模板中使用这个方法只需要直接在常规的Mustaches中增加这个方法,将被处理的变量用括号括起来即可:
<p>Its population is {{format(population)}} and its capital is {{name}} and locates at {{lat}} and {{lon}}.</p>
最后的结果将120000000显示为120.0 million
4 Event proxies (Demo08)
在Ractive.JS中,像下面这样定义事件代理:
<button on-click='activate'>Activate!</button>
然后这样定义事件:
ractive.on("activate", function(event){
// 'this'指向ractive
// 'event'包含了事件代理的信息
alert("ok!ok!")
}
如果在开发工具上查看button元素会发现,元素上并没有绑定on-click事件。当Ractive.JS解析模板时,它可以自动区别对待以on-开头的属性。
4.1 绑定多个事件
可以一次性的绑定多个事件:
ractive1.on({
myClick : function(){
alert("okokok")
},
yourClick : function(){
alert("nonono")
}
})
4.2 解除事件
可以使用off或者cancel来解除事件绑定
reactive.off('myClick', 回调函数)
这种方法只要没有使用匿名函数作为回调函数就可以成功
// remove ALL 'select' handlers
ractive.off( 'select' );
// remove all handlers of ANY type
ractive.off();
也可以使用cancel方法取消绑定:
var click1 = ractive1.on({
myClick : test
});
click1.cancel()
或者当使用了ractive.teardown()移除了ractive元素,则绑定的事件也会自动解除
4.3 自定义事件
自定义事件是通过插件实现的,插件下载地址在这里。
5 Conditional sections
有时需要根据不同的条件来确定不同的view。使用{{#if value}}{{/if}}来进行条件判断
{{#if value}}
{{else}}
{{/if}}
看一个例子:
// 模板
{{#if value}}
<p> you are 太棒了</p>
{{else}}
<p>123321</p>
{{/if}}
// JS文件
var ractive1 = new Ractive({
el : '#container',
template : '#template',
data : {
value : false
}
});
// 显示123321
6 List sections (Demo09)
使用{{#each list}}来对数组对象进行循环,看下面的例子:
<body>
<div id="container"></div>
<script type="text/ractive" id="template">
<table>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>能力</th>
</tr>
{{#each superHeroes}}
<tr>
<td>{{name}}</td>
<td>{{age}}</td>
<td>{{ability}}</td>
</tr>
{{/each}}
</table>
</script>
<script>
var xmen = [{name : "金刚狼", age : 23, ability : 'eat'},
{name : "金刚狗", age : 32, ability : 'sleep'},
{name : "金刚熊", age : 23, ability : 'run'}];
var ractive1 = new Ractive({
el : '#container',
template : '#template',
data : {
superHeroes : xmen
}
});
ractive1.set('superHeroes[0].name', '金刚猫')
</script>
</body>
6.1 获得数组序号
Mustache并没能提供一个很好的方式获得数组序号,所以RactiveJS引入了序列变量(index reference)
{{#each list:num}}
<!-- inside here, {{num}} is the index -->
{{/each}}
通过将num声明为序列变量,就可以像使用其他变量一样使用它。将上面的表格的循环数据改为:
<table>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年龄</th>
<th>能力</th>
</tr>
{{#each superHeroes:num}}
<tr>
<td>{{num+1}}</td>
<td>{{name}}</td>
<td>{{age}}</td>
<td>{{ability}}</td>
</tr>
{{/each}}
</table>
这样就可以获得各组的序号。
6.2 增加数组项
可以使用ractive.update('list')来更新表格数据。注意,如果没有给此方法传入变量,则它会更新所有元素。
也可以直接使用数组方法(push,pop,shift,unshift,splice,sort,reverse)方法自动更新数组。如果不想让上述方法自动更新数据,可以再初始化选项中设置了modifyArrays: false。
<script>
var xmen = [{name : "金刚狼", age : 23, ability : 'eat'},
{name : "金刚狗", age : 32, ability : 'sleep'},
{name : "金刚熊", age : 23, ability : 'run'}];
var ractive1 = new Ractive({
el : '#container',
template : '#template',
data : {
superHeroes : xmen
}
});
xmen.push({name : "金刚兔", age : 123, ability : 'eat'});
xmen.reverse();
// 自动更新
ractive1.update('superHeroes');
// ractive1.push('superHeroes', {name: "金刚兔", age: 123, ability: 'eat'});
//ractive1.reverse('superHeroes');
</script>
7 Two-way binding(Demo10)
7.1 简单的双向绑定
Ractive的输入输出的双向绑定的使用方法和常规的html标签有不同之处,对于不同类型的input来说:
- text:数据绑定到value上
- radio,checkbox:value数据绑定到各自input的name上(checked绑定的是布尔值)
- select:各个option的value绑定到select的value上
7.2 文本输入框的双向绑定
实现数据的双向绑定很简单:
<div id="container"></div>
<script type="text/ractive" id="template">
<label>
输入你的名字: <input type='text' value={{value}}>
</label>
<p>Hello, {{value}}</p>
</script>
<script>
var ractive = new Ractive({
el : '#container',
template : '#template'
})
</script>
可以在Ractive的初始化选项中对双向绑定进行设置,
lazy: true // 数据只有在blur时才会更新
twoway: false // 关闭双向绑定
7.3 observe
上面的这种绑定其实很少使用,因为通常情况下我们都希望对绑定的数据进行处理,所以需要用到ractive.observe()方法
ractive.observe( 'name', function ( newValue, oldValue ) {
// doSomethingWith( newValue );
});
7.4 radio和checkbox的双向绑定
对于radio和checkbox的绑定,可以将数据绑到name属性上,{{name}}和被选定的input的value有进行了绑定。
<script type="text/ractive" id="template">
<label><input type='checkbox' name='{{checkboxValue}}' value='value1' checked>value1</label>
<label><input type='checkbox' name='{{checkboxValue}}' value='value2'>value2</label>
<p>Your checkbox choice is {{checkboxValue}}</p>
<label><input type='radio' name='{{radioValue}}' value='value3' checked>value3</label>
<label><input type='radio' name='{{radioValue}}' value='value4'>value4</label>
<p>Your radio choice is {{radioValue}}</p>
</script>
7.5 select的双向绑定
将各个option的value绑定到了select的value上:
<script type="text/ractive" id="template">
<select value='{{value}}'>
<option value='111'>1</option>
<option value='222'>2</option>
<option value='3333'>3</option>
</select>
<p>this is {{value}} </p>
</script>
也可以用each循环实现这个结构:
<script type="text/ractive" id="template">
<select value='{{selectValue}}'>
{{#each selectValues}}<option>{{this}}</option>{{/each}}
</select>
<p>Your select choice: {{selectValue}}</p>
</script>
<script>
var ractive = new Ractive({
el : '#container',
template : '#template',
data:{
selectValues: [111, 222, 333],
selectValue: 333// 初始默认值
});
</script>
上面的代码中:
- this指代的是当前的上下文环境,当在循环中使用this时,它指代的就是循环的各个对象,可以使用{{.}}代替{{this}}
- 还是需要注意,select最终选择的value绑定到了select的value上面,data中的selectValue是给定select的初始值
7.6 select的多重选择
在select标签中增加了multiple属性后,RactiveJS会将选择的选项组成一个数组,提供使用者使用
8 Lifecycle eventes(Demo11)
每一个Ractive实例都是有生命周期的:创建-渲染-修改-销毁,在初始化中增加生命周期的处理事件
var ractive = new Ractive({
el: '#container',
template: '#template',
on : {
init : function(){
console.log('init')
}
}
});
常用的的生命周期事件包括:
- construct:在new Ractive()刚刚初始化时发生,此时所有设置都未生效
- config:在所有配置选项执行后发生
- init:当实例渲染前发生,可以利用这个事件为实例创建事件监听器(proxy event listeners or onservers)
- render:在每次实例渲染后发生
- complete:在render完成之后,任何转换(transition)完成后发生
- update:当调用ractive.update()时发生
- unrender:每次实例unrender时发生
- teardown:当实例被销毁后发生
- insert:当调用ractive.insert()时发生
- detach:当调用ractive.detach()时发生
9 component (demo12)
很多情况下我们需要将代码和操作封装为一个单独的可以复用的组件(component)。
组件是通过Ractive.extent()方法创建的,这个方法可以自定义模板、初始化自定义方法、默认参数等等。
9.1 初始化选项(Initiallisation Options)
大部分的Ractive的常规的初始化选项对组件都是使用的,特别是生命周期事件,包括但不限于:
- onconstruct:在实例创建后立刻并只触发一次,将所有参数传入初始化的组建中
- onconfig:在所有配置选项执行后立刻并只触发一次
- oninit:当组件渲染前立刻并只触发一次,可以利用这个事件为实例创建事件监听器(proxy event listeners or onservers)
- onrender:在组件渲染后触发
- oncompleate:在组件渲染之后,任何转换(transition)完成后触发,此时组件DOM是可用的
- isolated:上述事件都是函数,这个选项是一个布尔值,默认为fasle。默认情况下组件可以调用父辈组件的变量,如果设置为true则不可以。
9.2 创建组件并插入
创建组件使用Ractive.extent()方法:
在父级模板的Ractive实例对象中注册组件,在父级模板中直接使用组件,这种方法模板传入的数据通过在模板中定义相关的属性来传入(也就是说标签中的定义属性与组件实例的data中的变量是一致的)
<div id="container"></div>
<script type="text/ractive" id='template'>
<div class='application'>
<h1>This is my widget</h1>
<widget message='{{foo}}'/>
</div>
</script>
9.3 具体步骤
1 首先创建一个组件并通过oninit初始化方法和参数
// 创建一个组件
var MyWidget = Ractive.extend({
template : '<div on-click="activate">{{message}}</div>',
// 初始化方法
oninit: function () {
var self = this;
var message = self.get('message');
self.on('activate', function () {
alert('从父组件传过来的数据是' + message)
});
}
},
// 默认参数
data : {
message : 'No message specified, using the default'
}
});
2 然后将组件加入到父级模板中(注册组件)
//创建一个Ractive实例
var ractive = new Ractive({
el : '#container',
template : "#template",
//注册组件
components :{
widget : MyWidget
}
});
3 在父级模板中使用组件
<div class='application'>
<h1>This is my widget</h1>
<widget message='click me' />
</div>
成功。
9.4 组件的数据绑定
可以为组件传入一个模板变量:
<widget message='{{foo}}' />
这样就将组件的变量与父级模板中变量联系起来了。
ar ractive = new Ractive({
el : '#container',
template : "#template",
//注册组件
components :{
widget : MyWidget
},
data : {
foo : 'fool'
}
});
9.5 数据作用域
组件会创建一个新的数据作用域,所以参数不会污染父级模板的原始参数