当前位置: 首页 > 工具软件 > CanJS > 使用案例 >

CanJS基础教程

袁谭三
2023-12-01

1 加载Loading CanJS

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>

2 Constructor Functions

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

2.1 初始化

在第二个参数中的init方法,创建一个实例时会调用.

var Todo = can.Construct.extend({
  init: function(owner) {
    this.owner = owner;
  },
  allowedToEdit: function() {
    return true;
  }
});
var t = new Todo("me");
t.owner; // 'me'

2.2 继承

在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;
  }
});

3 可监控的对象Observables

Observables:可以操作对象的数据并且可以监听数据的改变做相应的处理
Observables包含如下三种类型:

  • can.Map - Used forObjects.
  • can.List - Used for Arrays.
  • can.compute - Used for values/compute.

其中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'

3.1 操作属性

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}

3.2 事件监听

当一个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);

3.3 使用each遍历Map属性

paginate.each(function(val, key){
 console.log(key+': '+ val);
});
// page: 10
// perPage: 30
// lastVisited: 1

3.4 扩展Map对象

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

3.5 监控List对象 Observable Arrays

Can.List继承can.Map,所以他有Map的功能,同时还有一些其他的一些常用的方法,当这些方法修改了List,如下所示:

  • indexOf, which looks for an item in a List.
  • pop, which removes the last item from a List.
  • push, which adds an item to the end of a List.
  • shift, which removes the first item from a List.
  • unshift, which adds an item to the front of a List.
  • splice, which removes and inserts items anywhere in a List.

同时我们也可以对List做一些监听,常用的如下所示,详见API

  • the change event fires onevery change to a List.
  • the set event is fired when an element is set.
  • the add event is firedwhen an element is added to the List.
  • the remove event is firedwhen an element is removed from the List.
  • the length event is firedwhen the length of the List changes.

3.6 监控计算 Computed values

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

4 模型及数据交互

4.1 创建Model

使用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.'});

4.2 与服务交互

从服务中接受数据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
    });
});

4.3 事件监听

因为Model也是一种特殊的Observes,也可以和其他的Observers一样 bind相同事件,使用Model.bind(eventType, handler(event, model))来监听某Model类型的事件,使用model.bind(eventType, handler(event))来监听具体的model对象的的事件
Model三种特殊事件:

  • created, when an instance iscreated on the server.
  • updated, when an instance isupdated on the server.
  • destroyed, when an instance isdestroyed on the server.
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
});

4.4 Model Lists

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  
    }
}

5 模板Templates(View层)

can.view,根据提供的Model数据渲染模板,并返回一个documentFragment 元素,EJS和 Mustache两种模板语言可以动态的bind到Observes.
Observes change—-> update your template in the DOM

5.1 加载模板

两种方式:从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))
  });

5.2 Passing Deferreds(传递延迟对象)

can.view('todos.ejs',{
    todos:Todo.findAll().
    user:User.findOne({id:5})
}).then(function(fragment){
    document.getElementById('todos').appendChild(fragment);
});

6 Mustache模版

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>

6.1 加载/定义模版的方式

有两种方式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 模版标签

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插件)

6.3 Context

上下文对象:(待续)

6.4 Helpers

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'}

7 控制层 Controls

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来取得相对应的值。

7.1 Listening to events

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,从模型中删除了该对象,这样会同时把页面的对应的页面元素也删除掉。

7.2 Templating event handlers

如果在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'});

7.3 Rebinding events

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);

7.4 Destroying Controls

Control调用destroy 方法会去除绑定的监听事件,同时移除它关联的元素,但是不回去移除页面上的页面元素。

var list=newTodos('#todos');
$('#todos').length;// 1
list.destroy();
$('#todos').length;// 1

可是,当Control对应的页面元素被删除掉时,Control会去调用destroy 。
当一个应用在Body元素上添加Control和监听事件,我们可以通过调用$(document.body).empty().清除该Control的所有数据

8 组件 Components

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标签,用于在定义的标签中嵌入该component模版的内容.
  • scope –can.Map结构,指定该Component的Model,用来渲染Component 的模版.
  • template – Component的模版,渲染后的模版内容会嵌入到Component的THtml标签中.
  • helpers – 作用于Component的mustache模版辅助方法.
  • events – 类似Control的事件监听.

8.1 Tag

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问题

8.2 Template

可以是模版的字符串,也可使用can.view指向模版文件,或者使用can.Mustache指向一个已经定义好的Mustache模版
可以在模版中使用<content></context>获取Component标签元素之间的内容
模版中的动态内容请参考第六章,这里要说明的是模版可以访问scope中的定义的属性/方法和helpers中定义的辅助方法.

8.3 Scope

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标签元素中的属性值

这里着重说明下:

  1. 在Scope和helpers中定义的方法可以通过this.获取在Scope中方法/属性,在events中定义的方法要通过this.scope.
    获取在Scope中方法/属性.
  2. 访问Scope中的属性时,可以用 .属性名和 .attr(“属性名”) 两种方式访问,对于不会被监听的属性建议使用 .属性名访问. 对于会被用在Observes的,则建议使用.attr(“属性名”)中.
  3. 修改Scope属性:不被模版中访问的的属性建议使用直接赋值(this.aa = 123),而在模版中会被访问的属性使用.attr(“属性名”,属性值)方式修改属性值

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)

8.4 Helpers

见Mustache的helpers说明

8.5 Events

Events中可以定义对Route的监听,对Socpe中的对象监听(创建/修改/删除等),可以Dom元素上监听事件,也可以监听window上的事件

9 Routing

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'}

9.1 Listening to events

因为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>

10 Utility Functions(见API)

 类似资料: