`

JavaScriptMVC之快速开发

阅读更多

有些同学反映说,需要看太多的篇章才能明白如何使用JavaScriptMVC来开发,可不可以 用一篇把主要用到技术介绍一下,这样就可以快速入门,并且可以快速用到开发项目的。 这篇文章就是这个目的,下面我们来讲述如何快速开发。 也就是我们习惯的的开发,自己创建项目,模块等。

不过不管怎样,我们都需要下载JavaScriptMVC包,下载它,然后把它解压到我们的项目中。 解压完JavaScriptMVC包后,我们看到4个文件夹,文件列表如下:

documentjs - documentation engine
funcunit   - testing app
jquery     - jquery and jQueryMX plugins
steal      - dependency management
js         - JS command line for Linux/Mac
js.bat     - JS command line for Windows
 



注意:这个库文件夹,我们统称为根目录。

获取JavaScriptMVC运行

JMVC使用steal来管理。Steal加载脚本。像JMVC中使用到特性$.Controller和$.View,steal是这样加载的:

steal('jquery/controller','jquery/view/ejs',function(){
   //code that uses controller and view goes here
})
 



在使用steal之前,你需要把steal脚本添加到你的页面中。在根目录下创建一个todos文件夹,
然后在这个文件夹下面 创建一个todos.html和todos.js文件:

ROOT\
    documentjs\
    jquery\
    funcunit\
    steal\
    todos\
      todos.js
      todos.html 
 



修改todos.html文件,把steal.js和todos.js脚本加载进来:

<!DOCTYPE html>
<html>
<head></head>
<body>
  <ul id='todos'></ul>
  <input id='editor'/>
  <script type='text/javascript'
          src='../steal/steal.js?todos/todos.js'>
  </script>
</body>
</html> 
 




在浏览器中打开这个页面,然后使用debug工具可以看到steal.js和todos.js已经加载进来。

Steal steal([paths])

Steal是使用来加载脚本,样式,甚至CoffeeScript,LESS和模板到你的程序中。当然这个只是Steal其中的一个特性。 Path是假设相对于根目录的。这就意味下述加载jquery/class/class.js文件是没有问题的:

steal('jquery/class/class.js'); 
 


 
你可以使用./来加载相对于目前文件夹的文件:

steal('./helpers.js')
 


 
Steal也支持CSS,下述就是加载todos/todos.css:

steal('./todos.css') 
 


 
因为加载像jquery/class/class.js这种路径太普遍,如果你没有提供后缀名为.js的文件,Steal将会加载把文件夹名称加.js后缀的文件。 像下述也是加载jquery/class/class.js文件:

steal('jquery/class')
 


 
Steal是一个异步的载入器,所以你不能这样写:

steal('jquery/class')
$.Class 
 



应该是这样:

steal('jquery/class', function(){
  $.Class
})
 



对于这个程序,我们将加载jQueryMX的常用插件。添加最终结果如下述:

steal('jquery/class',
      'jquery/model',
      'jquery/dom/fixture',
      'jquery/view/ejs',
      'jquery/controller',
      'jquery/controller/route',
      function($){

}) 
 


 
下面讲述的都当我们开发todos程序时需要使用到的各个插件。

$.Class

$.class([name],[staticProperties],[prototypeProps])
 
从构造函数可以看出$.Class是使用来创建一个对象的。
$.Controller和$.Model都使用到它。
创建自定义类,调用$.Class,然后给传递以下参数:
1、name 类名
2、staticProperties 静态属性
3、prototypeProperties 成员属性

$.Class使用的原型链,所以子类很容易扩展它,如下:

steal('jquery/class', function(){

  $.Class("Todo",{
    init : function(){},

    author : function(){ ... },

    coordinates : function(){ ... },

    allowedToEdit: function(account) {
     return true;
    }
  });

  Todo('PrivateTodo',{
    allowedToEdit: function(account) {
      return account.owns(this);
    }
  })

}); 
 



$.Class还提供了在父类中使用简短的a_super方法可以调用父类的方法:

var SecureNote = Todo({
  allowedToEdit: function(account) {
    return this._super(account) &&
       this.isSecure();
  }
})
 


构造器/初始化 new Class(arg1,arg2)

当一个类的构造函数被调用时,$.Class将实例化并且调用init函数,它接收参数。

$.Class('Todo',{
  init : function(text) {
    this.text = text
  },
  read : function(){
    console.log(this.text);
  }
})

var todo = new Todo("Hello World");
todo.read() 
 


 
注:在init执行前还有一个setup方法,setup能使用来改变参数,然后再传递给init方法。

Model $.Model(name,staticProperties,prototypeProperties)

数据模型对于任何程序都是主要的。它包含数据和逻辑为一体。 你用你的专用方法去继承

$.Model $.Model提供了一个Set方法管理变化。 创建一个数据模型,调用$.Model,传递以下参数:

1、name 类名
2、staticProperties 静态属性,包含findAll,findOne,create,update,destroy属性
3、prototypeProperties 成员属性 下述就是使用$.Model来创建一个数据模型:

steal('jquery/class',
      'jquery/controller',
      'jquery/model',
      'jquery/view/ejs',
      'jquery/dom/fixture',
      function($){

  $.Model('Todo',{
    findAll : "GET /todos",
    findOne : "GET /todos/{id}",
    create  : "POST /todos",
    update  : "PUT /todos/{id}",
    destroy : "DELETE /todos/{id}"
  },
  {})
});
 



注:在你的浏览器中试试下述的指令。

new $.Model(attributes)

创建一个todo实例:

var todo = new Todo({name: "do the dishes"}); 
 



attr model.attr(name,[value])

$.Model.prototype.attr 可以读取和设置数据模型实例中属性。

todo.attr('name') //-> "do the dishes"

todo.attr('name', "wash the dishes" );

todo.attr() //-> {name: "wash the dishes"}

todo.attr({name: "did the dishes"}); 
 



与服务端交互

Model使用静态函数findAll,findOne,create,update和destroy函数去创建,检索,更新和删除数据模型在服务端。 现在你可以在Todo中调用这些函数与服务端做些数据变化的交互了。 执行下面这句代码:

Todo.findAll({}); 

 

我们可以在浏览器的Debug工具看到一个GET/todos请求。

如果你的服务端没有这个/todos服务,这个请求会出错。为了没有后台,我们也可以正常开发, JavaScriptMVC给我们提供了一个$.fixture,它可以模拟服务端的服务。

$.fixture

$.fixture(url,fixture(original,settings,headers))


Fixtures需要一个模拟请求的特定URL。Fixture的回调函数需要的参数如下:
1、original 原文
2、settings Ajax设置
3、headers 请求头部 它把返回的参数数组传递给jQuery的Ajax传送完成回调系统:

return [ 200, "success", {json: []}, {} ];
 


像: 模拟todo服务,在下述代码中添加回调函数:

// our list of todos
var TODOS = [
    {id: 1, name: "wake up"},
    {id: 2, name: "take out trash"},
    {id: 3, name: "do dishes"}
];
// findAll
$.fixture("GET /todos", function(){
  return [TODOS]
});

// findOne
$.fixture("GET /todos/{id}", function(orig){
  return TODOS[(+orig.data.id)-1];
})

// create
var id= 4;
$.fixture("POST /todos", function(){
  return {id: (id++)}
})

// update
$.fixture("PUT /todos/{id}", function(){
  return {};
})

// destroy
$.fixture("DELETE /todos/{id}", function(){
  return {};
})
 


 
现在你可以使用Model的Ajax方法去执行增删查改todos了。

findAll findAll(params,success(todos),error())

findAll返回多个todos:

Todo.findAll({}, function( todos ) {
  console.log( todos );
})
 


findOne findOne(params,success(todo),error())

findOne返回单个todo:

Todo.findOne({}, function( todo ) {
  console.log( todo );
}) 
 



save todo.save(success(todo),error())

Save 可以创建或者修改实例,如果实例已经存在则是修改,否则是创建。 创建一个todo实例,然后调用它的save方法,即可实现在服务端创建一个Todo:

var todo = new Todo({name: "mow lawn"})
todo.save(function(todo){
  console.log( todo );
})
 



在上面已经创建Todo实例的基础上,我们修改它的属性值,然后再执行save方法,那么它就是修改服务端的todo。

var todo = new Todo({name: "mow lawn"});
todo.save( function(todo){
  console.log("created", todo );

  todo.attr("name", "mow my lawn")
  todo.save( function( todo ) {
    console.log("updated", todo );
  })
})
 


destroy todo.destroy(success(todo),error())


Destroy删除服务端的一条记录。

var todo = new Todo({name: "mow lawn"});
todo.save( function(todo){
  console.log("created", todo );

  todo.destroy( function( todo ) {
    console.log("destroyed", todo );
  })
})
 


 
bind todo.bind(event,handler(ev,todo))

监听数据模型的变化是MVC结构的相关内容。Model让你可以给一个模型实例或者所有模型实例绑定变化监听。 例如,你可以监听一个数据模型实例的创建:

var todo = new Todo({name: "mow lawn"});
todo.bind('created', function(ev, todo){
  console.log("created", todo );
})
todo.save()
 



你可以给这个数据模型绑定创建监听:

Todo.bind('created', function(ev, todo){
  console.log("created", todo );
}) 
 



Model产生下述的事件:
1、created - 监听创建事件
2、updated - 监听修改事件
3、destroyed - 监听删除事件

$.fn.model $(el).model([modelInstance]) 给HTML元素绑定数据模型实例是非常有用的,它可以让我们在后续操作该模型提供方便。 jQuery.fn.model插件就是用来给一个HTML元素设置和获取一个数据模型实例的。 设置实例:

var li = $('<li>').model( new Todo({id: 5}) )
                  .appendTo("#todos");
 


 
这将给<li>元素的class添加"todo todo_5"样式。那我们就可以通过<li>获取这个实例了:

li.model().id //-> 5
 



elements todo.elements([context])

通过数据模型实例获取对应的HTML元素。Context元素让我们在这个元素内检索数据模型实例对应的HTML元素。

todo.elements('#todos'); 
 



View $.View(idOrUrl,data)

使用$.View很容易用JS模板创建HTML。通过它:

1、可以根据脚本标签的ID来使用这个模板在内容。
2、把数据传递给该模板 接着它返回的就是模板被渲染后的结果。例如,下述给todos.html添加模板:

<script type='text/ejs' id='todosEJS'>
  <% for(var i = 0; i < this.length; i++ ){ %>
    <li><%= this[i].name %></li>
  <% } %>
</script> 
 


 
用一个todos列表渲染它:

 Todo.findAll( {}, function( todos ){
     console.log( $.View( 'todosEJS', todos ) );
 }); 
 



$.View也可以通过URL来做为一个模板位置。创建一个todos/todos.ejs文件,它的内容如下:

<% for(var i = 0; i < this.length; i++ ){ %>
  <li><%= this[i].name %></li>
<% } %>
 


渲染它:

Todo.findAll( {}, function( todos ){
  console.log( $.View( 'todos.ejs', todos ) );
}); 
 



$.View可以使用任何的模板语言,像JAML,TMPL,Mustache等,和给它们加强以下功能:

1、可以通过脚本标签或者文件加载模板
2、用jQuery修饰符使用模板,像HTML
3、模板缓存
4、支持延迟
5、压缩模板

Modifiers - el.modifier(idOrUrl,data)

$.View重写了jQuery的HTML修饰符after,append,before,html,prepend,replaceWidth,和 text,允许你这样写:

Todo.findAll( {}, function( todos ){
  $('#todos').html( 'todos.ejs', todos );
}); 
 



想要看到上面代码执行的结果,要确保todos.html中有一个#todos的元素:

<ul id='todos'></ul> 
 


 
延迟

$.Model的Ajax方法返回一个延迟对象。$.View接受延迟对象,

$('#todos').html('todos.ejs', Todo.findAll() )
 



这个语句表示直到Todo.findAll发送Ajax请求完成的结果传递给todos.ejs模板,最后渲染模板。

Hookup

$.View.hookup可以实现在模板元素执行回调。这些回调函数都是模板已经插入到DOM中后被调用。 你可以在元素中这样调用jQuery方法:

<li <%= ($el) -> $el.fadeIn() %> style='display:none'>
  <%= this[i].name %>
</li>
 


在我们现在代码,添加一个返回带箭头函数语法的神奇的标签(<%= %>) ,这个参数传递给函数将封装元素。 如果我们想把Model添加到EJS模板中的元素上。修改todos.ejs:

<% $.each(this, function(i, todo){ %>
  <li <%= ($el) -> $el.model(todo) %>>
    <%= todo.name %>
    <a href="javascript://" class='destroy'>X</a>
  </li>
<% }) %>
 


 
又或者修改成如下:

<% $.each(this, function(i, todo){ %>
  <li <%= todo %>>
    <%= todo.name %>
    <a href="javascript://" class='destroy'>X</a>
  </li>
<% }) %> 
 




它们的功能都是一样,那么为什么这段代码也执行了回调函数el.model(todo)呢,那里因为我们EJS提供了默认的$.View.hookup方法, 如果以上面这种编写,它会自动调用$.fn.model来给元素绑定模型。

Controller

$.Controller(name,classProps,prototypeProps)$.Controller创建可组织的,释放内存泄漏,快速执行,标准的jQuery控件。它使用来创建UI控件像tabs,grids和菜单, 让我们开发一个基本的todos控件,它能显示todos列表和删除。在todos.js添加下述代码:

$.Controller("Todos",{
  "init" : function( element , options ){
    this.element.html('todos.ejs', Todo.findAll() )
  }
}) 
 



我们把这个控件创建在#todos元素上。

new Todos('#todos', {}); 
 



init $.Controller.prototype.init(element,options)



Init是当一个新控制器实例被创建后被调用。它接收2个参数:
1、element 这个元素需要传递给控制器。它可以是一个JQuery元素,一个原始元素,或者一个CSS选择器。在控制器实例中this.element指的 就是这个元素。

2、options 这个参数传递给一个新的控制器,扩展控制静态属性defaults。那么在控制器我们就可以 使用this.options来使用这些参数。 它可以传递其它一些参数给新控制器。例如:

$.Controller("Todos",
{
  defaults : {template: 'todos.ejs'}
},
{
  "init" : function( element , options ){
    element.html(options.template, Todo.findAll() )
  }
})

new Todos( document.body.firstElementChild );
new Todos( $('#todos'), {template: 'specialTodos.ejs'})
 



element this.element

this.element指的就是被控制器封装后的元素。

options this.options

this.options 是传递给控制的第二个参数它合并到控制器的静态default属性中。

监听事件

控制器自动绑定属性方法,它看起来像事件句柄。 监听<li>元素的点击事件:

$.Controller("Todos",{
  "init" : function( element , options ){
    this.element.html('todos.ejs', Todo.findAll() )
  },
  "li click" : function(li, event){
    console.log("You clicked", li.text() )

    // let other controls know what happened
    li.trigger('selected');
  }
})
 


 
当一个<li>元素被点击后,"li click"将被调用。 控制器使用事件委托,所以你能给<li>元素添加事件而不需要重复绑定事件句柄。 当点击X链接后,会删除一条todo记录。

$.Controller("Todos",{
  "init" : function( element , options ){
    this.element.html('todos.ejs', Todo.findAll() )
  },
  "li click" : function(li){
    li.trigger('selected', li.model() );
  },
  "li .destroy click" : function(el, ev){
    // get the li element that has the model
    var li = el.closest('.todo');

    // get the model
    var todo = li.model()

    //destroy it
    todo.destroy(function(){
      // remove the element
      li.remove();
    });
  }
})
 



事件句柄模板第一部分 - 事件

通过点位符来实现定制事件行为。下述就是允许我们定制销毁一个todo定制事件:

$.Controller("Todos",{
  "init" : function( element , options ){ ... },
  "li click" : function(li){ ... },

  "li .destroy {destroyEvent}" : function(el, ev){
    // previous destroy code here
  }
})

// create Todos with this.options.destroyEvent
new Todos("#todos",{destroyEvent: "mouseenter"}) 
 




控制中的{name}的值是从控制器的this.options或者Window中检索到的,然后替换它。 下面的代码实现的定制事件就是从Window中检索替换的:

$.Controller("Todos",{
  "init" : function( element , options ){ ... },
  "li click" : function(li){ ... },

  "li .destroy {Events.destroy}" : function(el, ev){
    // previous destroy code here
  }
})

// Events config
Events = {destroy: "click"};

// Events.destroy is looked up on the window.
new Todos("#todos") 
 




事件句柄前面的选择器也可以模板化。

事件句柄模板第二部分 - 对象

控制器也可以使用事件句柄模板给一个对象绑定事件。这个对于避免内存泄漏非常关键,所以在MVC程序会经常碰到。
如果这个{name}是一个对象,这个对象将被绑定。 例如,下述是点击窗口会出现一个tooltip的监听:

$.Controller("Tooltip",{
  "{window} click" : function(el, ev){
    // hide only if we clicked outside the tooltip
    if(! this.element.has(ev.target ) {
      this.element.remove();
    }
  }
})

// create a Tooltip
new Tooltip( $('<div>INFO</div>').appendTo(el) ) 
 




当我们想监听一个数据模型更新时,就可以使用事件句柄模板来监听数据模型的变化。

$.Controller("Todos",{
  "init" : function( element , options ){
    this.element.html('todos.ejs', Todo.findAll() )
  },
  "li click" : function(li){
    li.trigger('selected', li.model() );
  },
  "li .destroy click" : function(el, ev){
    el.closest('.todo')
      .model()
      .destroy();
    ev.stopPropagation();
  },
  "{Todo} destroyed" : function(Todo, ev, destroyedTodo){
    destroyedTodo.elements(this.element)
                 .remove();
  },
  "{Todo} updated" : function(Todo, ev, updatedTodo){
    updatedTodo.elements(this.element)
               .replaceWith('todos.ejs',[updatedTodo]);
  }
})

new Todos("#todos");
 
 


这样,代码看来非常好看,非常符合MVC模型的要求。

destroy controller.destroy()

$.Controller.prototype.destroy将释放HTML元素的控制器的事件句柄。但是不会删除这个HTML元素。

var todo = new Todos("#todos")
todo.destroy(); 
 



当一个绑定有控制器的HTML元素从页面上销毁掉,那么绑定到这个HTML元素上的控制器的destroy自动会被调用。

 

new Todos("#todos")
$("#todos").remove();
 



注:如果程序使用事件句柄模板在HTML中的Body元素上,那么执行$(document.body).empty()将把所有数据释放。

update controller.update(options)

$.Controller.prototype.update 更新控制器的this.options和重新绑定所有事件句柄。 当你想监听一个特别的模型时非常有用:

$.Controller('Editor',{
  update : function(options){
    this._super(options)
    this.setName();
  },
  // a helper that sets the value of the input
  // to the todo's name
  setName : function(){
    this.element.val(this.options.todo.name);
  },
  // listen for changes in the todo
  // and update the input
  "{todo} updated" : function(){
    this.setName();
  },
  // when the input changes
  // update the todo instance
  "change" : function(){
    var todo = this.options.todo
    todo.attr('name',this.element.val() )
    todo.save();
  }
});

var todo1= new Todo({id: 6, name: "trash"}),
    todo2 = new Todo({id: 6, name: "dishes"});

// create the editor;
var editor = new Editor("#editor");

// show the first todo
editor.update({todo: todo1})

// switch it to the second todo
editor.update({todo: todo2});
 




注:因为我们重写了update,我们必须调用_super。

路由

$.route是JavaScriptMVC路由功能的核心。它是一个观察者对象,监听window.location.hash的变化。它是一个非常完善的路由行为,这个指南有很复杂。但是,它也可以使用到比较基础的用例中。在控制器中使用"route"事件来监听路由:

$.Controller("Routing",{
  "route" : function(){
    // matches empty hash, #, or #!
  },
  "todos/:id route" : function(data){
    // matches routes like #!todos/5
  }
})

// create routing controller
new Routing(document.body);
 



上面代码表达的意思:名为"route"的函数是当没有路由数据时被调用。而"todos/:id route"是有像{id: 6}这样的数据时被调用。我们可以通过改变路由数据来更新路由:

$.route.attr('id','6') // location.hash = #!todos/6
 



或者自己设置hash的值:

var hash = $.route.url({id: 7}) // #!todos/7
location.hash = hash;
 



下面升级一点让Routing控制器去监听".todo selected" 事件和更新路由。当路由更新后,它从服务端检索到的Todo数据更新到对应的编辑控件中。

$.Controller("Routing",{
  init : function(){
    this.editor = new Editor("#editor")
    new Todos("#todos");
  },
  // the index page
  "route" : function(){
     $("#editor").hide();
  },
  "todos/:id route" : function(data){
    $("#editor").show();
    Todo.findOne(data, $.proxy(function(todo){
      this.editor.update({todo: todo});
    }, this))
  },
  ".todo selected" : function(el, ev, todo){
    $.route.attr('id',todo.id);
  }
});

// create routing controller
new Routing(document.body);
 



FuncUnit

JavaScriptMVC使用FuncUnit来测试。FuncUnit提供编写功能测试的API,它能模拟鼠标和键盘动作。创建一个FuncUnit测试:

1、创建一个测试文件,它引入funcunit模块。

2、创建一个funcunit.html页面加载你的测试文件。

在todos目录下,新建一个fununit.html文件,并且添加以下HTML:

<html>
  <head>
    <link rel="stylesheet" type="text/css"
      href="../funcunit/qunit/qunit.css" />
    <script type='text/javascript'
      src='../steal/steal.js?todos/funcunit.js'></script>
  </head>
  <body>
    <h1 id="qunit-header">Todos Test Suite</h1>
    <h2 id="qunit-banner"></h2>
    <div id="qunit-testrunner-toolbar"></div>
    <h2 id="qunit-userAgent"></h2>
    <div id="test-content"></div>
    <ol id="qunit-tests"></ol>
    <div id="qunit-test-area"></div>
  </body>
</html>
 



现在新建fununit.js文件并且添加以下代码:

steal('funcunit', function(){

  module('todos')

  test('todos test', function(){
    ok(true, "the test loads");
  })

})
 



在浏览器中打开fununit.html。一个测试用例通过。

编写一个测试

使用S.open是通知测试打开这个todos页面。

S.open("//todos/todos.html");
 



这个页面一打开,我们选择第一个todo点击它:

S(".todo:first").click();
 



S是复制jQuery的$作法添加到FuncUnit的API中。这个编辑输入框将出现。什么等待命令告诉FuncUnit将等待:

S("#editor").val("wake up", "First Todo added correctly");
 



第2个参数是一个断言信息。如下述一样,在Steal回调函数中替换成下面的代码:

module('todos', {
  setup: function(){
    S.open("//todos/todos.html");
  }
})

test('open first todo', function(){
  S(".todo:first").click();
  S("#editor").val("wake up", "First Todo added correctly");
})
 



刷新funcunit.html页面。你将看到在一个独立的窗口打开页面和执行这个测试。自动化 上面这些测试也可以自动化,通过控制台执行下面的命令:

./js funcunit/run selenium todos/funcunit.html
 

 

0
0
分享到:
评论
3 楼 lyndon.lin 2012-06-05  
你先去看看这篇吧,找找感觉。
http://lyndon-lin.iteye.com/blog/1469332
2 楼 Jabbar2011 2012-06-05  
感觉用起来太复杂了,看上去都不想用
1 楼 maizi5401 2012-05-23  
源代码呢? 我要下载源代码

相关推荐

Global site tag (gtag.js) - Google Analytics