CanJS加载方式:
- 直接引用js库(canjs官网可以定制插件一起打包下载) l
- AMD(requirejs)
<html>
<head>
<title>CanJS Tutorial</title>
</head>
<body>
<scriptsrc="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.js"></script>
<scriptsrc="http://canjs.com/release/latest/can.jquery.js"></script>
<scripttype="text/javascript">
$(function(){
// Your tutorial code here
});
</script>
</body>
</html>
can.Construct可以简单的理解为CanJs的基类, Observables, Models, Controls都是基它的.
创建自己的construct,通过调用can.Construct.extend方法,并且传递两个参数,第一个参数是它的静态属性,第二个参数是对象(原型)属性.如果只传了一个参数,那么该参数当作对象(原型)属性.
var PrivateTodo = Todo.extend({},{
isSecret: function() {
return true;
}
});
var p = new PrivateTodo();
p.allowedToEdit(); // false
在第二个参数中的init方法,创建一个实例时会调用.
var Todo = can.Construct.extend({
init: function(owner) {
this.owner = owner;
},
allowedToEdit: function() {
return true;
}
});
var t = new Todo("me");
t.owner; // 'me'
在init中调用父类的init方法(斜体部分)
var PrivateTodo = can.Construct.extend({
init: function(owner, isShared) {
*can.Construct.prototype.init.apply(this, arguments);*
this.isShared = isShared;
},
allowedToEdit: function(){
return this.owner === "me" || this.isShared;
}
});
Observables:可以操作对象的数据并且可以监听数据的改变做相应的处理
Observables包含如下三种类型:
其中can.Map和can.List是常用的Observable类型,Models和can.rout是基于can.Map.后面会讲到的can.Component的scope属性也是一个can.Map类型
创建一个Map需要传递一个对象(JSON)类型参数,创建一个List需要传递一个数组
var pagination=new can.Map({page:1, perPage:25, count:1388})
pagination.attr('perPage');// 25
var hobbies=new can.List(['programming','bball','party rocking']);
hobbies.attr(2);// 'partying'
Map和List使用attr()方法来读取、修改、添加属性操作,使用removeAttr()方法来删除属性操作
pagination.attr('perPage'); // 25
pagination.attr('perPage',50);
pagination.attr('perPage'); // 50
pagination.attr({page:10, lastVisited:1});
pagination.attr();// {page: 10, perPage: 50, count: 1388, lastVisited: 1}
pagination.removeAttr('count');
pagination.attr();// {page: 10, perPage: 50, lastVisited: 1}
当一个Map类型对象使用attr()方法修改属性时会触发两个事件,一个change事件和一个与被修改的属性同名的事件,可以通过使用bind来监听这些事件,通过unbind去掉对应的监听
paginate.bind('change',function(event, attr, how, newVal, oldVal){
attr; // 'perPage'
how; // 'set'
newVal;// 30
oldVal;// 50
});
paginate.bind('perPage',function(event, newVal, oldVal){
newVal;// 30
oldVal;// 50
});
paginate.attr('perPage',30);
paginate.each(function(val, key){
console.log(key+': '+ val);
});
// page: 10
// perPage: 30
// lastVisited: 1
Paginate= can.Map.extend({
limit:100,
offset:0,
count:Infinity,
page:function(){
returnMath.floor(this.attr('offset')/this.attr('limit'))+1;
},
next:function(){
this.attr('offset',this.attr('offset')+this.attr('limit'));
}
});
var pageInfo=newPaginate();
pageInfo.attr("offset")//-> 0
pageInfo.next();
pageInfo.attr("offset")//-> 100
pageInfo.page() //-> 2
Can.List继承can.Map,所以他有Map的功能,同时还有一些其他的一些常用的方法,当这些方法修改了List,如下所示:
同时我们也可以对List做一些监听,常用的如下所示,详见API
Computedvalues(计算值,可读,可写,可监听),简单计算使用can.compute(value)创建,一样可以可读,可写,可监听
var age = can.compute(25),
previousAge = 0;
// read the Compute's value
age(); // 25
// listen for changes in the Compute's value
age.bind('change', function(ev, newVal, oldVal) {
previousAge = oldVal;
});
// set the Compute's value
age(26);
age(); // 26
previousAge; // 25
组合计算使用can.compute(getterFunction)创建
var name = new can.Map({
first: 'Alice',
last: 'Liddell'
});
var fullName = can.compute(function() {
// We use attr to read the values
// so the compute knows what to listen to.
return name.attr('first') + ' ' + name.attr('last');
});
var previousName = '';
fullName(); // 'Alice Liddell'
fullName.bind('change', function(ev, newVal, oldVal) {
previousName = oldVal;
});
name.attr({
first: 'Allison',
last: 'Wonderland'
});
fullname(); // 'Allison Wonderland'
previousName; // 'Alice Liddell'
转换计算(Converted Computes)
// progress ranges from 0 to 1.
var project = new can.Map({ progress: 0.3 });
var progressPercentage = can.compute(function(newVal) {
if(newVal !== undefined) {
// set a value
project.attr('progress', newVal / 100);
} else {
// get the value
return project.attr('progress') * 100;
}
});
percentage(); // 30
// Setting percentage...
percentage(75);
// ...updates project.progress!
project.attr('progress'); // .75
使用can.Model,特定的静态方法与后台交互(RESTful API)。常用的有:
var Todo = can.Model({
findAll: 'GET /todos',
findOne: 'GET /todos/{id}',
create: 'POST /todos',
update: 'PUT /todos/{id}',
destroy: 'DELETE /todos/{id}'
}, {});
var dishesTask = new Todo({description: 'Do the dishes.'});
从服务中接受数据can.Model.findAll方法接收一组Model对象
Todo.findAll({},function(todos){
// todos is a can.Model.List of Todo Models.
},function(xhr){
// handle errors
});
can.Model.findOne同findAll一样,但只接收一个Model对象
update、create 、destroy
调用save方法时,如果该model有Id,则调用update,否则调用create
var shopping=newTodo({description:"Go grocery shopping."});
shopping.save(function(saved){
// saved is the saved Todo
saved.attr('description','Remember the milk.');
saved.save();
});
调用destroy方法 删除一个model对象,调用destroy
var cats=newTodo({description:"Feed the cats."});
cats.save(function(saved){
saved.destroy(function(destroyed){
// destroyed is the Todo that was destroyed
});
});
因为Model也是一种特殊的Observes,也可以和其他的Observers一样 bind相同事件,使用Model.bind(eventType, handler(event, model))来监听某Model类型的事件,使用model.bind(eventType, handler(event))来监听具体的model对象的的事件
Model三种特殊事件:
var mop=newTodo({description:'Mop the floor.'});
mop.bind('created',function(ev, created){
// created is the created Todo
});
mop.save();
也可以在Model类上bind上述三种事件,表示任何该类型的对象的操作(created、updated,destroyed)都会监听到
Todo.bind('created',function(ev, created){
// created is the created Todo
});
ModelLists (provided by can.Model.List) are Lists whose items are Models. When one of aModel List’s elements are destroyed, that element is removed from the list.
Todo.findAll({}, function(todos) {
todos.length; // 5
todos[0].destroy(function() {
todos.length; // 4
}
}
can.view,根据提供的Model数据渲染模板,并返回一个documentFragment 元素,EJS和 Mustache两种模板语言可以动态的bind到Observes.
Observes change—-> update your template in the DOM
两种方式:从srcipt标签加载(id)和从url加载
Sricpt标签加载
定义
<script type="text/ejs" id="todoList">
<% can.each(this,function(val, key){%>
<li><%= val.attr('description')%></li>
<%});%>
</script>
通过ID加载
Todo.findAll({},function(todos){
$('#nav').html(can.view('todoList', todos))
});
通过url加载,模版内容是单独的文件
Todo.findAll({},function(todos){
$('#nav').html(can.view('todos/todos.ejs', todos))
});
can.view('todos.ejs',{
todos:Todo.findAll().
user:User.findOne({id:5})
}).then(function(fragment){
document.getElementById('todos').appendChild(fragment);
});
Mustache是一个弱逻辑的模版语言,mustache提供了一些简单好用的标签来渲染绑定的Observes对象,我们可以使用自定义的Helpers(后面会讲到)来扩展它功能.
此处我们Mustache的官网地址http://mustache.github.io/;can只使用的Mustache的一些javascrpt简单的功能.
定义
<script id="template" type="text/mustache">
<h1>Welcome {{user}}!</h1>
<p>You have {{messages}} messages.</p>
</script>
使用
var data = new can.Map({
user: 'Tina Fey',
messages: 0
});
var template = can.view("#template", data)
document.body.appendChild(template);
生成的Html
<h1>Welcome Tina Fey!</h1>
<p>You have 0 messages.</p>
有两种方式Script Tags和URL两种方式
Script Tags方式:在html中嵌入模版脚本;使用
<script id="mytemplate" type="text/mustache">
My body lies over the {{.}}
</script>
var template= can.view("#mytemplate",'water');
can.$(document.body).append(template);
URL方式:把模版内容写在单独的一个文件中
var template = can.view('//lib/views/mytemplate.mustache', dataToPass);
can.$(document.body).append(template);
6.2.1 基本标签
{{key}} 输出html转义后的值
{{{key}}} 同{{}},但是输出的是非转义的值
{{! note }} 模版中的注释
{{#key}}BLOCK{{/key}} 有两个功能:一是处理简单的逻辑判断,并根据结果渲染BLOCK.,二是遍历一个非空数组(后续详细介绍)
{{^key}}BLOCK{{/key}} 判断是key的值为false时,渲染BLOCK
{{>key}} 嵌套模版;其中key是子模版的路径/Id
注:undefined, null, false, ”,0,和[]视为false,其他的视为ture
6.2.2 辅助标签
{{helper args…}}
调用Mustache辅助函数,将其返回值插入模板
{{#helper}}
{{#if key}}BLOCKA{{else}}BLOCKB{{/if}}
简单的逻辑判断 如果key是true则渲染BLOCKA 否则渲染BLOCKB
{{#each key}}
遍历一个数组/对象.当遍历数组时可以使用{{@index}}来获得索引;遍历一个数组时,可以使用{{@key}} 来获得对象的属性key
{{data name}}
在el上bind一个当前的contenxt,通过can.data(el,name)去获取
{{(el)->CODE}}
在el上执行内嵌回调代码,常用于对el上处理某操作 ;如{{(el)->el.hammer()}},对元素加上手持事件支持(hammer插件)
上下文对象:(待续)
Mustache可以注册一个方法用于在模版中调用,这样的方法就是Helpers,这个相当有用,因为Mustache是弱逻辑的模版,我们可以通过Helper来扩展一些逻辑处理.
模版
<scripttype="text/mustache"id="todosList">
{{#todos}}
<li>{{uppercase description}}</li>
{{/todos}}
</script>
脚本
var fragment= can.view('todosList',{todos: ['aa','bb']},{
uppercase:function(str){
return str.toUppercase();
}
});
这就定义了一个转大写的helper方法
6.4.1 Global helpers
全局的Helpers 使用can.mustache.registerHelper或者Mustache.registerHelper去定义;全局Helpers对于任何Mustache模版都可以使用,这样相当于定义了公用的方法,任何地方都可以使用.
Mustache.registerHelper('money', function(price, option) {
var temp = price;
if ( typeof price == 'function') {
temp = price();
}
return can.mustache.safeString((temp/100).toFixed(1));
});
示例中定义了一个对金额转化的helper,把传入的值除以100之后保留一位小数
6.4.2 Datahelpers
DataHelpe就是{{data name}}标签,在元素el上bind一个当前的context,通过can.data(el,name)去获取bind的context.示例:
<script type="text/mustache"id="nameDiv">
<div {{data 'name'}} id ='test1'>{{myName}}</div>
</script>
$(body).append(can.view('nameDiv',{ myName:'JIM'}));
var obj= can.data($('#test1'),'name'); //获取的 obj = { myName:'JIM'}
Controls 是Constructs的子类,它直接控制Model(由can.mode创建)并且结合View(Mustache由can.view创建)进行页面显示.
示例:
var Todos = can.Control({
default:{
age:23,
name: 'sheldon1'
}
},
{
init: function(el, options) {
var self = this;
Todo.findAll({}, function(todos) {
self.element.html(can.view('todoList', todos));
});
}
});
初始化一个Todos的实例如下:
var todosList = new Todos('#todos', {name:'sheldon',age:'27'});
第一个参数可以是选择器,元素,节点列表,这些在init方法中对应第一个参数中的el,此时的el已经是一个Dom元素对象.
第二个参数,options就是传入的json对象和上面提到的defaults里的值进行合并,可以用options.name来取得相对应的值。
Controls 会自动绑定那些看起来像事件的实例方法,如下面实例中的’li click’方法,这些方法传递两个参数,第一个是事件触发的元素,第二个是事件本身.
can.Control使用了事件托管,所以当添加或者移除元素时,不需要重新绑定事件处理。
varTodos= can.Control({
init:function(el, options){
var self=this;
Todo.findAll({},function(todos){
self.element.html(can.view('todoList', todos));
});
},
'li click':function(el, ev){
console.log('You clicked '+ el.text());
},
'li .destroy click':function(el, ev){
var li= el.closest('li'),
todo = li.data('todo');
todo.destroy();
}
});
上面示例中当点击li .destroy元素时,调用了model的destory,从模型中删除了该对象,这样会同时把页面的对应的页面元素也删除掉。
如果在event handler中包含一个占位符,can.Control会在Control的option查找对应的占位符key的值,然后在window中查找.
var Todos = can.Control({
defaults: {
destroyEvent: 'click'
}
},{
init: function(el, options) {
var self = this;
Todo.findAll({}, function(todos) {
self.element.html(can.view(this.options.view, todos));
});
},
'li .destroy {destroyEvent}': function(el, ev) {
var li = el.closest('li'),
todo = li.data('todo');
todo.destroy();
}
});
new Todos('#todos', {destroyEvent; 'mouseenter'});
Control可以通过调用on 方法Unbind和rebind所有的Control的event handlers,这个在Control开始监听一个指定的model或者改变监听的model时很常用。
varEditor= can.Control({
setDesc:function(){
this.element.val(this.options.todo.description);
},
// change what Todo this Control points at
todo:function(todo){
this.options.todo= todo;
this.on();
this.setDesc();
},
// listen for changes in the Todo
'{todo} updated':function(){
this.setDesc();
},
// when the input changes, update the Todo
' change':function(el, ev){
this.options.todo.attr('description', el.val());
this.options.todo.save();
}
});
var todo1=newTodo({id:7, description:'Take out the trash.'}),
todo2 =newTodo({id:8, description:'Wash the dishes.'}),
editor =newEditor('#editor');
// start editing the first Todo
editor.todo(todo1);
// switch to editing the second Todo
editor.todo(todo2);
Control调用destroy 方法会去除绑定的监听事件,同时移除它关联的元素,但是不回去移除页面上的页面元素。
var list=newTodos('#todos');
$('#todos').length;// 1
list.destroy();
$('#todos').length;// 1
可是,当Control对应的页面元素被删除掉时,Control会去调用destroy 。
当一个应用在Body元素上添加Control和监听事件,我们可以通过调用$(document.body).empty().清除该Control的所有数据
Component 可以很容易的结合observables,templates,controls的功能特性。
can.Component.extend({
tag:"my-element",
scope:{
visible:true,
toggle:function(){
this.attr("visible",!this.attr("visible"))
}
},
template:"<div can-click='toggle'>"+
"{{#isVisible}}"+
"<content/>"+
"{{else}}"+
"I am hidden"+
"{{/isVisible}}"+
"</div>",
helpers:{
isVisible:function(options){
returnthis.attr("visible")?
options.fn(): options.inverse();
}
},
events:{
"inserted":function(){
console.log("you add a my-element to the page")
}
}
})
Tag:Component的标签,非Html解析的元素,当自定义的tab标签在模板中出现时,就会在这个标签元素上创建一个Component实例
can.Component.extend({tag:"todos-editor"});
vartemplate= can.mustache("Here is my <todos-editor>todos-editor element</todos-editor>")
var frag = template();
frag.childNodes[1].nodeName//-> "todos-editor element"
注:tag必须小写,带有大写会出现找不到对应的tag问题
可以是模版的字符串,也可使用can.view指向模版文件,或者使用can.Mustache指向一个已经定义好的Mustache模版
可以在模版中使用<content></context>
获取Component标签元素之间的内容
模版中的动态内容请参考第六章,这里要说明的是模版可以访问scope中的定义的属性/方法和helpers中定义的辅助方法.
Scope:一个can.map对象,作用在template上,在template中可以访问scope中的属性,
var template = can.mustache("<h1>Todo: {{mytodo.name}}</h1><todos-editor todo='mytodo'></todos-editor>");
var mytodo = new can.Map({name: "Do the dishes"});
can.Component.extend({
tag: "todos-editor",
template: "{{#if todo}}<input type='text'can-value='todo.name'/>{{/if}}",
scope: {
placeholder:"@"
}
});
var frag = template({ mytodo: mytodo})
document.body.appendChild(frag)
注:can-value 是对于Scope中的属性和表单元素值双线绑定;即修改scope对于的属性表单元素的值也一起修改,修改表单元素的值Scope中对应的属性的值也同样会改变.使用@
符号获取Component标签元素中的属性值
这里着重说明下:
8.3.1 Scope bindings
在scope中添加模板上的事件处理
can.Component.extend({
tag:"todos-editor",
template:"<form can-click='toggle'>Editor: "+
"{{#if visible}}<input type='text'/>{{/if}}"+
"</form>",
scope:{
visible:true,
toggle:function(context, el, ev){
this.attr("visible",!this.attr("visible"))
}
}
})
注: can-click=’toggle’表示给元素绑定click事件,事件触发调用的是scope中定义的方法.
8.3.2 Scope value functions
可以在template中访问Scope中的方法
can.Component.extend({
tag:"todos-editor",
template:"<form can-click='toggle'>{{visibility}}: "+
"{{#if visible}}<input type='text'/>{{/if}}"+
"</form>",
scope:{
visible:true,
toggle:function(context, el, ev){
this.attr("visible",!this.attr("visible"))
},
visibility:function(){
returnthis.attr("visible")?
"visible":"invisible"
}
}
})
8.3.3 Passing values to a component’s scope
通过在Component的元素上设置属性,可以向Component传递Scope上的数据
var template = can.mustache("<h1>Todo: {{mytodo.name}}</h1>"+
"<todos-editor todo='mytodo'></todos-editor>")
var mytodo = new can.Map({name: "Do the dishes"})
can.Component.extend({
tag: "todos-editor",
template: "{{#if todo}}"+
"<input type='text' can-value='todo.name'/>"+
"{{/if}}",
scope: {}
})
var frag = template({
mytodo: mytodo
})
document.body.appendChild(frag)
见Mustache的helpers说明
Events中可以定义对Route的监听,对Socpe中的对象监听(创建/修改/删除等),可以Dom元素上监听事件,也可以监听window上的事件
can.route 是CanJS路由的核心功能,也是一个特殊的Observe,当window.location.hash的值有变动时,can.route的属性值也会更新;同样,can.route的属性值有变动时,window.location.hash的值也会有更新。
可以给can.route附加一个传递URL属性的模板.Eg:
// Give can.route a template.
can.route(':type/:id');
// If you set a hash that looks like the route...
window.location.hash='#!todos/5';
// ... the route data changes accordingly.
can.route.attr();// {type: 'todos', id: 5}
// If the route data is changed...
can.route.attr({type:'users', id:29});
// ...the hash is changed using the template.
window.location.hash;// '#!users/7'
// You can also supply defaults for routes.
can.route('',{type:'recipe'});
// Then if you change the hash...
window.location.hash='';
// ...the route data reflects the defaults.
can.route.attr();// {type: 'recipe'}
因为can.route也是一个Observe,所以也可以在它上面绑定监听事件
can.route.bind('id',function(ev, newVal, oldVal){
console.log('The hash\'s id changed.');
});
你也可以在Control或者Component中监听 routingevents:
varRouting= can.Control({
'route':function(){
// Matches every routing change, but gets passed no data.
},
'todos/:id route':function(data){
// Matches routes like #!todos/5,
// and will get passed {id: 5} as data.
},
':type/:id route':function(data){
// Matches routes like #!recipes/5,
// and will get passed {id: 5, type: 'recipes'} as data.
}
})
can.route.url: 根据当前的route,添加route属性并构造hash值 URL
can.route(':type/:id',{type:'todos'});
can.route.url({id:7});// #!todos/7
can.route.link: 同can.route.url功能类似,但它是用来构造HTML中的A(链接元素)的href,同时也可为A元素添加一些属性。
var a= can.route.link(
'Todo 5',
{id:7},
{className:'button'}
);
a;// <a href="#!todos/7" class="button">Todo 5</a>