Discuz教程网

JavaScript中的View-Model使用介绍

[复制链接]
authicon dly 发表于 2011-9-10 21:23:04 | 显示全部楼层 |阅读模式
构成
这是一个十分常见的微博列表页面,类似于新浪微博。上周末,在心无旁骛情况下,一共用了5个对象,产出400行代码,实践出一种代码组织模式。

使任务轻松完成的代码有4个方面的要素组成:
要素组成
模型Reply、Forward
视图CommentEditor、ReplyList、ForwardList
模板jQuery.tmpl
异步任务jQuery.Deferred分部介绍
模型
模型只与数据有关,它能够产生、过滤、保存、验证数据,并且仅此而已。

如下例,留言模型在调用保存方法时,只接收JSON参数,并且只返回一个异步任务,实际处理时同步或异步的返回结果并不重要。
在此进行的验证的原因是,它是一个开放的对象,是与服务器交互的最后一道门槛。
另外,它本身也不处理验证失败的情况——由视图调用时选择性地处理,可能会弹出一个消息提示或直接忽略再进行重试。
  1. // 留言模型
  2. var Reply = {
  3. cache : {},
  4. // { sourceid : id,page_size : 10,page_num : 1 }
  5. fetch : function(data) {
  6. return $.post('/ajax/blog/reply/list',data||{}).success(function(resp) {
  7. resp.ok && resp.list &&
  8. $.each(resp.list,function(k,v) {
  9. return Reply.cache[v.id] = v;
  10. });
  11. });
  12. },
  13. // filter('name','king')
  14. filter : function(prop,val) {
  15. return $.grep(this.cache,function(r){ return r[prop] === val });
  16. },
  17. // { content : '想说就说',sourceid : 1001 }
  18. create : function(data) {
  19. // promise
  20. var dfd = $.Deferred(), now = $.now();
  21. if( (now - this.create.timestamp)/1000 < 10 ){
  22. dfd.reject({message:'您发表得太快了,休息一下吧',type:'warn'})
  23. }else if(!data || !data.sourceid){
  24. dfd.reject({message:'非法操作',type:'error'})
  25. }else if(!data.content){
  26. dfd.reject({message:'评论内容不能为空',type:'warn'})
  27. }else{
  28. this.create.timestamp = now;
  29. dfd = $.post('/ajax/blog/reply/create',data);
  30. }
  31. return dfd.promise();
  32. }
  33. };
  34. Reply.create.timestamp = Forward.create.timestamp = $.now() - 1e4;
复制代码

视图
视图是浏览器页面上的可视部分,每个视图对象含有一个关联的 jQuery 对象作为属性(instance.$el),类似于UI组件中的DOM容器。

视图还有两个一致的方法:

render 方法用于从模型获取数据,并且根据定义好的模板将数据渲染到HTML页面上。
activate 方法用于激活视图,同时绑定相关的DOM事件,所有事件至多委托到$el为止。
这个示例中,CommentEditor是父视图,ReplyList和ForwardList是互斥显示的两个子视图,父子视图之间相互保存引用。
  1. // 回复列表视图
  2. var ReplyList = function(options) {
  3. var opt = this.opt = $.extend({
  4. el : '',
  5. parent : null
  6. },options||{});

  7. this.parent = opt.parent;
  8. this.$el = $(opt.el);
  9. this.activate();
  10. };
  11. ReplyList.prototype = {
  12. render : function() {
  13. var self = this;
  14. Reply.fetch({
  15. page_size : 10, page_num : 1,
  16. sourceid : self.parent.getBlogId()
  17. })
  18. .done(function(data) {
  19. self.$el.html( self.$list = $.tmpl(tpl_reply_list,data) );
  20. });
  21. return self;
  22. },
  23. activate : function() {
  24. this.$el.delegate('a.del',$.proxy(this.del,this))
  25. }
  26. // ...
  27. }

  28. // 评论编辑器视图
  29. CommentEditor.prototype = {
  30. activate : function() {
  31. this.$el.delegate('a.save',$.proxy(this.save,this))
  32. },
  33. save : function() {
  34. var self = this, data = { content : self.getContent(),sourceid : self.getBlogId() };
  35. var task_r = Reply.create(data);
  36. var task_f = Forward.create(data);
  37. // 转发、评论同时进行
  38. $.when(task_r,task_f).then(function(t1,t2) {
  39. // 保存成功,更新视图或关闭
  40. },function(data) {
  41. // 模型验证出错,或远程服务器错误
  42. Sys.info(data.message,data.type);
  43. });
  44. return self;
  45. },
  46. switchView : function(type) {
  47. // 切换子视图
  48. var view_opt = {el:this.$sublist.empty(),parent:this};
  49. if(type === 'reply'){
  50. $label.show();
  51. this.$submit.val('评论');
  52. this.sublist = new ReplyList(view_opt).render();
  53. }else{
  54. $label.hide();
  55. this.$submit.val('转发');
  56. this.sublist = new ForwardList(view_opt).render();
  57. }
  58. }
  59. // ...
  60. }
复制代码

模板
模板可以消除繁琐、丑陋的字符串拼接,它的作用是能够直接由js对象生成HTML片断。

模板中可以直接遍历对象,套用预定义的函数,来对一些数据进行格式化,比如时间函数nicetime:
  1. // 回复列表模板
  2. var tpl_reply_list = '<ul class="ui-reply-list">\
  3. {{each list}}\
  4. <li data-id="${id}">\
  5. <a class="name" href="/${userid}">${name}:</a>\
  6. <p>${content}</p>\
  7. <time pubdate>${nicetime(timestamp)}</time><a class="del" href="javascript:;">删除</a>\
  8. </li>\
  9. {{/each}}\
  10. </ul>';
复制代码

异步任务
Deferred Object 的直译是延迟对象,但是理解成异步任务更为恰当。异步任务能够消除多层嵌套的回调,让代码书写和阅读更为便利。

从上面的模型和视图的代码中可以明显地看出,使用了异步任务之后,代码变得更加平面化了。

$.Deferred 方法新建的是一个双向任务队列:成功回调函数队列和失败回调函数队列;任务的状态也分为两种:成功和失败,分别可以用isResolved或isRejected来检查任务的当前状态、用resolve或reject修改任务状态。

promise 方法返回任务的只读副本,此副本上不能修改任务状态。毫无疑问,模型应该始终只返回 promise 对象。(注:只读副本仍然可以再次调用 promise 方法再次返回只读副本)

在Reply.create方法中,能够更好地处理自定义的异步任务,而不是直接返回原生的ajax异步任务:
  1. // var dfd = $.Deferred();
  2. $.post('/ajax/blog/reply/create',data)
  3. .success(function(json) {
  4. if(json && json.ok){
  5. dfd.resolve(json.list);
  6. }else{
  7. dfd.reject({message:json.message||'获取失败',type:'error'});
  8. }
  9. })
  10. .fail(function() {
  11. dfd.reject({message:'服务暂时不可用',type:'error'})
  12. });
复制代码

目的及结论
为什么拆散成这样?

收获:可维护性,清晰的API调用、消除二层以上的if语句、消除二层以上的回调语句、每个函数控制在二十行之内。

结果:没有过多的重复代码,所有的功能都被打包好了。



上一篇:Jquery中对数组的操作代码
下一篇:ASP备份SQL Server数据库改进版
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1314学习网 ( 浙ICP备10214163号 )

GMT+8, 2025-5-2 20:16

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表