本来是说周末研究一下单页应用来着,先是想发出来可以监督自己学习下去,结果谁知道这个水很深,我连着就赔了一个周末进去,到尽头还没搞完。。。
这次这个框架并不是我一个字写出来的,而是参考了同事的框架,我师傅(http://www.cnblogs.com/aaronjs/)说得好:
传承总比再造好,于是便有了这次的demo,其实就是吸收精华啊!!!
到今天有了一个大概的样子,可以拿出来看看了。其中的Model与localstorege相关也加上了,但是暂时没有用得到的场景。
这个代码后面点应该会持续更新,到我认为比较合适的阶段就会放出来了。。。。 于是我们继续吧!
整体思路
我们先来构思下整个框架的构成(这里参照了同事的框架):
依赖库
① requireJS
② jquery
③ underscore.js
PS:本来还想用backbone的,想下还是算了,研究性目的,不要搞的过于复杂吧
程序流程
基本结构我是这么思考的:
① 我们在网页中输入URL后便进入Index,以requireJS加载主框架APP
② APP.js做一些初始化操作,其主要功能便是观察hash变化(URL变化)
若是改变(初始化进入、或者想去其他页面),便根据规则获取URL相关信息,初始化新的VIEW,进行显示,并将URL压人HASH。
③ 实现VIEW类,其具有基本几个事件(onCreate、onLoad、onRender、onHide)
一个VIEW可以对应其HTML模板(可以0个或者多个)
④ 事件操作使用jquery的吧
PS:暂时这个样子吧,我们边写边改,看最后能做到什么样子,这里还是画个图
于是,我们开始辛苦的基础实现吧,毕竟我们现在还神马都米有!!!
基础实现之继承
其实,我们是面向对象的,若是不面向对象应该会被喷吧,于是我们来一起完成一个基础的继承类吧:
1 var b = {};//base 2 var slice = [].slice; 3 4 b.Class = function (supClass, childAttr) { 5 //若是传了第一个类,便继承之;否则实现新类 6 if (typeof supClass === 'object') { 7 childAttr = supClass; 8 supClass = function () { }; 9 } 10 11 //定义我们创建的类12 var newClass = function () { 13 this._propertys_(); 14 this.init.apply(this, arguments); 15 }; 16 newClass.prototype = new supClass(); 17 18 var supInit = newClass.prototype.init || function () { }; 19 var childInit = childAttr.init || function () { }; 20 var _supAttr = newClass.prototype._propertys_ || function () { }; 21 var _childAttr = childAttr._propertys_ || function () { }; 22 23 for (var k in childAttr) { 24 //_propertys_中作为私有属性 25 childAttr.hasOwnProperty(k) && (newClass.prototype[k] = childAttr[k]); 26 } 27 28 //继承的属性有可能重写init方法 29 if (arguments.length && arguments[0].prototype && arguments[0].prototype.init === supInit) { 30 //重写新建类,初始化方法,传入其继承类的init方法 31 newClass.prototype.init = function () { 32 var scope = this; 33 var args = [function () { 34 supInit.apply(scope, arguments); 35 } ]; 36 childInit.apply(scope, args.concat(slice.call(arguments))); 37 }; 38 } 39 40 //内部属性赋值 41 newClass.prototype._propertys_ = function () { 42 _supAttr.call(this); 43 _childAttr.call(this); 44 }; 45 46 //成员属性 47 for (var k in supClass) { 48 supClass.hasOwnProperty(k) && (newClass[k] = supClass[k]); 49 } 50 return newClass; 51 };
代码还是很好理解的:
PS:我们这个框架建立的所有类,都会经过他!!!
可以传递两个两个参数:需要继承的类,新建类的一些参数,这里我们来稍微走一下这个流程:
一个参数
① 我们先初始化一个新的类newClass,并且实例化他,他就会调用本身的两个方法:
this._propertys_() 用于实例化本身属性 this.init.apply(this, arguments) init为我们定义的,每个类都会调用的初始化方法
② 让我们新建的类(newClass)继承自我们传入的类
③ 初始化四个变量,显然这种情况他们都是空函数
④ 将传入的子属性的值给予新建类(newClass),这里只有一个参数便忽略吧
⑤ 29行,由于我们的类继承自supClass,所以这里是满足条件的,我们看看他里面干了些什么:
他重新定义了,我们创建类的init函数(我们说过他在自身属性初始化完毕后会执行)。
这里我们为他的参数新增了一个方法,也就是父类的init方法,只不过其this指向为子类。
newClass.prototype.init = function () { var scope = this; var args = [function () { supInit.apply(scope, arguments); } ]; childInit.apply(scope, args.concat(slice.call(arguments))); };
⑥ 41行,大家注意_propertys_这个函数,他是用于我们为自身属性赋值的,等下我们举个例子
⑦ 最后将父类的成员对象赋给子类(因为我们知道成员是不会被继承的)
⑧ 返回新建类,流程结束
情况我这里不多做介绍,我们再说下两个参数的情况就好。
两个参数
我们这种场景用的也很多,第一个参数为要继承的类,第二个参数为新建类的一些初始化信息,我们来走一次流程吧:
① 传入两个参数,要继承的类;新建类的一些属性和原型方法
② 12行同样初始化我们的新类,我们实例化时,首先会执行_propertys_方法为自身赋值,然后执行初始化方法(可以传递参数哦,参数是new时候传递的):
var newClass = function () { this._propertys_(); this.init.apply(this, arguments);//此处的参数请注意 };
③ 让新类继承自传入的类,此时我们的newClass.prototype其实就拥有很多方法了,包括init与_propertys_
④ 18-21行,初始化四个后面会用到的函数,各位自己注意其意图
⑤ 23-26行,将将传入的子属性对象,赋予newClass,_propertys_也作为原型对象了
⑥ 29-38行,将父类的init作为参数传递给子类的init,由子类决定是不是要执行
⑦ 最后的就不多说了
好了,到此各位应该了解他的作用了,我们来一个简单的例子吧:
简单例子
实现一个父类Bird(鸟类),及其子类Chicken(鸡)Duck(鸭)
首先,我们来看鸟类都能呼吸,都有名字与年龄,所以我们这么干:
1 var Bird = new b.Class({ 2 //作为自身属性将被调用,里面必须采用this.XX的方式书写 3 _propertys_: function () { 4 this.name = '鸟类'; 5 this.age = 0; 6 }, 7 //一定会被执行的初始化方法 8 init: function () { 9 alert('一定会执行'); 10 }, 11 //原型方法 12 breathe: function () { 13 alert('我能呼吸'); 14 } 15 }); 16 17 var b = new Bird({des: '测试init方法是否能捕获参数'}); 18 19 var s = '';
这里我们的init自动执行了,并且,其会用到传入的参数!!!
这是我们得到的鸟类实例。
好了,我们来实例化一个鸡的类,并且他拥有嚎叫打鸣的本领,与性别的特征
1 var Chicken = new b.Class(Bird, { 2 _propertys_: function () { 3 this.sex = '公'; 4 }, 5 //一定会被执行的初始化方法 6 init: function () { 7 alert('我是一只鸡'); 8 }, 9 //原型方法 10 howl: function () { 11 alert('我能打鸣'); 12 } 13 });
这里,父类的init会执行,子类的init会执行,而且,子类的init函数会默认带上父类的init方法
init: function (superInit) { alert('我是一只鸡'); },
PS:虽说父类一定会执行,但是若是在此调用父类的superInit方法的话,this指向是子类哦!!!
好了,这个家伙结束,我们进入下一个核心VIEW
基础实现之视图
视图(View)是我们框架的第二个核心,我们来看看到底可能会有哪些view呢?
① 核心之,页面VIEW
这是最重要的view,他应该包含整个页面的逻辑,从初始化到最后的展示,事件绑定等应该一应俱全
② 各个级别组件,弹出层,提示层,遮盖层......
这些都应该是view,但是,先不管那么多,我们来实现我们的核心吧!!!
思考过程
此处使用backbone的视图到变得简单了,我们来简单思考下我们页面视图形成的流程吧:
① 用户第一次进入界面,访问index,由main导向app,app获得url,获取其相关参数(list)
② app根据list实例化view,view调用自身init方法,慢慢开始构建页面,并会触发各个流程
PS:在此我认为每个页面view应该统一继承一个父类,于是我们来尝试性试试吧
第一版
1 b.AbstractView = b.Class({ 2 //基本view应该具有的属性 3 _propertys_: function () { 4 this.id = (new Date()).getTime(); //唯一pageID 5 this.rootBox = $('body'); //视图容器 6 this.root = $('<div/>'); //视图的根元素,可进行设置 7 this.header = null; 8 this.footer = null; 9 this.template = '';//可能的模板 10 this.isCreated = false;//是否创建完毕 11 this.status = b.AbstractView.STATE_NOTCREATE;//当前状态 12 }, 13 init: function () { 14 }, 15 //定义将要用到的事件,其中元素选取都会以root为标准,所以使用内部提供函数吧 16 events: { 17 'selector,eventType': 'func' 18 }, 19 //默认属性 20 attrs: { 21 }, 22 //获取视图元素 23 find: function (selector) { 24 return this.root.find(selector); 25 }, 26 //创建dom 27 create: function (opts) { 28 if(!this.isCreated && this.status != b.AbstractView.STATE_ONCREATE) { 29 var attr = opts && opts.attr; 30 var html = this.createHtml(); 31 this.initRoot(attr);//初始化root 32 this.hide(); 33 this.rootBox.append(this.root); 34 this.root.html(html); 35 this.trigger('onCreate');//触发正在创建事件,其实这里都创建完了 36 this.status = b.AbstractView.STATE_ONCREATE; 37 this.isCreated = true; 38 this.bindEvent(); 39 } 40 }, 41 //呈现/渲染视图 42 show: function (callback) { 43 if(this.status == b.AbstractView.STATE_ONSHOW) { 44 return; 45 } 46 this.create(); 47 this.root.show(); 48 this.trigger('onShow'); 49 this.status = b.AbstractView.STATE_ONSHOW 50 callback && (typeof callback == 'function') && callback.call(this); 51 this.trigger('onLoad'); 52 }, 53 //隐藏dom 54 hide: function (callback) { 55 if(!this.root || this.status == b.AbstractView.STATE_ONHIDE) { 56 return; 57 } 58 this.root.hide(); 59 this.trigger('onHide'); 60 this.status = b.AbstractView.STATE_ONHIDE; 61 callback && (typeof callback == 'function') && callback(); 62 }, 63 //事件绑定 64 bindEvent: function () { 65 var events = this.events; 66 for(var k in events) { 67 var sec_type = k.replace(/\s/i, '').split(','); 68 var func = events[k]; 69 if(sec_type &&sec_type.length == 2 && typeof func == 'function') { 70 var selector = sec_type[0]; 71 var type = sec_type[1]; 72 var scope = this; 73 this.find(selector).on(type, function () { 74 func.call(scope, $(this)); 75 }) 76 } 77 } 78 }, 79 //此处可以配合模板与相关参数组成html 80 //解析模板也放到此处 81 createHtml: function () { 82 throw new Error('请重新定义createHtml方法'); 83 }, 84 initRoot: function () { 85 var attr = this.attrs; 86 if(!attr) { 87 return; 88 } 89 for(var k in attr) { 90 if(k == 'className') { 91 this.root.attr('class', attr[k]); 92 }else { 93 this.root.attr(k, attr[k]); 94 } 95 } 96 this.root.attr('id', this.id); 97 }, 98 //触发事件 99 trigger: function (k, args) { 100 var event = this[k]; 101 args = args || []; 102 if(event && typeof event == 'function') { 103 event.apply(this, args) 104 } 105 }, 106 setRootBox: function (dom) { 107 this.rootBox = dom; 108 }, 109 setAttr: function (k, v) { 110 this.root.attr(k, v); 111 }, 112 getAttr: function (k) { 113 return this.root.attr(k); 114 }, 115 setCss: function (k, v) { 116 this.root.css(k, v); 117 }, 118 getCss: function (k) { 119 return this.root.css(k); 120 }, 121 //dom创建后执行 122 onCreate: function () { 123 124 }, 125 //dom创建后数据加载时执行,用于加载后执行我们的逻辑 126 onLoad: function () { 127 128 }, 129 //dom创建后,未显示 130 onShow: function () { 131 132 }, 133 //dom隐藏前 134 onHide: function () { 135 136 } 137 }); 138 139 //组件状态,未创建 140 b.AbstractView.STATE_NOTCREATE = 'notCreate'; 141 //组件状态,已创建但未显示 142 b.AbstractView.STATE_ONCREATE = 'onCreate'; 143 //组件状态,已显示 144 b.AbstractView.STATE_ONSHOW = 'onShow'; 145 //组件状态,已隐藏 146 b.AbstractView.STATE_ONHIDE = 'onHide';
代码注释写的很详细了,我这里就不多说了,我们来用一个例子试一试:
1 <!DOCTYPE html> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta charset="utf-8" /> 5 <title></title> 6 <script src="res/libs/jquery.js" type="text/javascript"></script> 7 <script src="res/test/c.base.js" type="text/javascript"></script> 8 </head> 9 <body> 10 11 </body> 12 <script type="text/javascript"> 13 var PageView = b.Class(b.AbstractView, { 14 _propertys_: function () { 15 this.template = '我是叶小钗'; 16 }, 17 init: function (superInit) { 18 console.log(superInit); 19 console.log('init'); 20 }, 21 createHtml: function () { 22 var htm = [ 23 '<header>标题</header>', 24 '<div class="main">', 25 '<input type="text" >, 26 '<input type="button" >, 27 this.template, 28 '</div>', 29 '<footer>页尾</footer>' 30 ].join(''); 31 return htm; 32 }, 33 attrs: { 34 'data-id': 'test', 35 className: 'yexiaoc' 36 }, 37 events: { 38 '#bt,click': function (el) { 39 var txt = this.find('#txt'); 40 alert(txt.val()) 41 } 42 }, 43 onCreate: function () { 44 console.log('onCreate'); 45 }, 46 //dom创建后数据加载时执行,用于加载后执行我们的逻辑 47 onLoad: function () { 48 console.log('onLoad'); 49 }, 50 //dom创建后,未显示 51 onShow: function () { 52 console.log('onShow'); 53 }, 54 //dom隐藏前 55 onHide: function () { 56 console.log('onHide'); 57 } 58 }); 59 var view = new PageView(); 60 view.show(); 61 var s = ''; 62 </script> 63 </html>
初步实现我们的期望
继承之实现APP
我们这里APP要干的事情,与其说担任MVC中控制器的角色,不如说他就是充当了一下路由选择的角色,根据不同的URL导向不同的view,并且会管理hash。
由于我们会处理request请求,压入hash以达到后退功能有效,所以这里先来实现一个hash类吧
实现Hash对象
先来一个辅助函数,用于计算某个字符在数组的位置:
1 var indexOf = function (k, arr) { 2 if (!arr) { 3 return -1; 4 } 5 //若是对象本身便居然indexof,便使用自身的,比如字符串 6 if (arr.indexOf) { 7 return arr.indexOf(k); 8 } 9 for (var i = 0, len = arr.length; i < len; i++) { 10 if (arr[i] == k) { 11 return i; 12 } 13 } 14 return -1; 15 };
PS:这个hash的实现不算太好,后面也许会改动
1 b.Hash = b.Class({ 2 _propertys_: function () { 3 this.keys = []; 4 this.values = []; 5 }, 6 init: function (obj) { 7 (typeof obj == 'object') || (obj = {}); //??? 8 for (var k in obj) { 9 if (obj.hasOwnProperty(k)) { 10 this.keys.push(k); 11 this.values.push(obj[k]); 12 } 13 } 14 }, 15 length: function () { 16 return this.keys.length; 17 }, 18 getItem: function (k) { 19 var index = indexOf(k, this.keys); 20 if (index < 0) { 21 return null; 22 } 23 return this.keys[index]; 24 }, 25 getKey: function (i) { 26 return this.keys[i]; 27 }, 28 getValue: function (i) { 29 return this.values[i]; 30 }, 31 add: function (k, v) { 32 return this.push(k, v); 33 }, 34 del: function (k) { 35 var index = indexOf(k, this.keys); 36 return this.delByIndex(index); 37 }, 38 delByIndex: function (index) { 39 if (index < 0) return this; 40 this.keys.splice(index, 1); 41 this.vaules.splice(index, 1); 42 return this; 43 }, 44 //移除栈顶hash,并返回 45 pop: function () { 46 if (!this.keys.length) return null; 47 this.keys.pop(); 48 return this.values.pop(); 49 }, 50 push: function (k, v, order) { 51 if (typeof k == 'object' && !v) { 52 for (var i in k) { 53 if (k.hasOwnProperty(i)) { 54 this.push(i, k[i], order); 55 } 56 } 57 } else { 58 var index = indexOf(k, this.keys); 59 if (index < 0 || order) { 60 if (order) this.del(k); 61 this.keys.push[k]; 62 this.values.push[v]; 63 } else { 64 this.values[index] = v; 65 } 66 } 67 }, 68 //查找hash表,返回key 69 indexOf: function (v) { 70 var index = indexOf(v, this.vaules); 71 if (index >= 0) { 72 return this.keys[index]; 73 } 74 return -1; 75 }, 76 each: function (handler) { 77 if (typeof handler == 'function') { 78 for (var i = 0, len = this.length(); i < len; i++) { 79 handler.call(this, this.keys[i], this.vaules[i]); 80 } 81 } 82 }, 83 getObj: function () { 84 var obj = {}; 85 for (var i = 0, len = this.length(); i < len; i++) { 86 obj[this.keys[i]] = this.values[i]; 87 } 88 return obj; 89 } 90 });
此hash对象基本就是数组的写照,各位可以对照着看,于是我们继续我们的app
app雏形
1 var Application = new b.Class({ 2 _propertys_: function () { 3 var scope = this; 4 this.webRoot = ''; //应用跟目录 5 this.head = $('head'); 6 this.body = $('body'); 7 this.viewRoot = 'views/'; //视图所在目录 8 this.defaultView = 'index'; //默认加载视图 9 10 this.request; //请求对象 11 this.viewPath; //当前请求视图路径,解析request得出 12 this.mainFrame; //主框架 13 this.viewPort; //视图框架 14 this.stateDom; //状态栏 15 16 this.views = new b.Hash(); //views保存浏览器存储的hash 17 this.curView; //当前视图 18 this.interface = {}; //提供给视图访问的接口,暂时不管 19 this.history = []; //历史记录 20 21 // this.stopListening = false;//是否开启监听 22 23 this.onHashChange = function () { 24 scope.history.push(window.location.href); 25 var url = decodeURIComponent(window.location.hash.replace(/^#+/i, '')).toLowerCase(); 26 scope._onHashChange(url); 27 }; 28 29 this.lastHash = ''; 30 this.lastFullHash = ''; 31 this.isChangeHash = false; //hash是否发生变化 32 }, 33 init: function (opts) { 34 //为属性赋值 35 opts = opts || {}; 36 for (var k in opts) { 37 this[k] = opts[k]; 38 } 39 this.createViewPort(); 40 this.bindEvent(); //事件绑定 41 }, 42 43 //创建app页面基本框架,此处不能使用id,因为。。。 44 createViewPort: function () { 45 var htm = [ 46 '<div class="main-frame">', 47 '<div class="main-viewport"></div>', 48 '<div class="main-state"></div>', 49 '</div>' 50 ].join(''); 51 this.mainframe = $(htm); 52 this.viewport = this.mainframe.find('.main-viewport'); 53 this.statedom = this.mainframe.find('.main-state'); 54 var body = $('body'); 55 body.html(''); 56 body.append(this.mainframe); 57 }, 58 //!!!!!!非常重要哦!!!!!! 59 bindEvent: function () { 60 var scope = this; 61 //暂时不使用requireJS 62 // requirejs.onError = function () {}; 63 $(window).bind('hashchange', this.onHashChange); 64 }, 65 _onHashChange: function (url) { 66 url = url.replace(/^#+/i, ''); 67 var req = this.parseHash(url); 68 69 this.request = req; 70 this.viewPath = this.viewPath || this.defaultView; 71 this.loadView(this.viewPath); //!!!重要的视图加载 72 }, 73 //该方法慢慢看吧。。。 74 parseHash: function (hash) { 75 var fullhash = hash, 76 hash = hash.replace(/([^\|]*)(?:\|.*)?$/img, '$1'), 77 h = /^([^?&|]*)(.*)?$/i.exec(hash), 78 vp = h[1] ? h[1].split('!') : [], 79 viewpath = (vp.shift() || '').replace(/(^\/+|\/+$)/i, ''), 80 path = vp.length ? vp.join('!').replace(/(^\/+|\/+$)/i, '').split('/') : [], 81 q = (h[2] || '').replace(/^\?*/i, '').split('&'), 82 query = {}, y; 83 this.isChangeHash = !!(!this.lastHash && fullhash === this.lashFullHash) || !!(this.lastHash && this.lastHash !== hash); 84 if (q) { 85 for (var i = 0; i < q.length; i++) { 86 if (q[i]) { 87 y = q[i].split('='); 88 y[1] ? (query[y[0]] = y[1]) : (query[y[0]] = true); 89 } 90 } 91 } 92 93 this.lastHash = hash; 94 this.lashFullHash = fullhash; 95 return { 96 viewpath: viewpath, 97 path: path, 98 query: query, 99 root: location.pathname + location.search 100 }; 101 }, 102 //!!!非常重要 103 loadView: function (viewPath) { 104 var id = viewPath; 105 var scope = this; 106 //此处本来应该判断是否已经有该视图,但是我们暂时不管,我们只要加载了相关视图就算成功 107 /* 108 一些操作 109 */ 110 111 //此处应该加载我们的js文件 112 $.getScript(this.buildUrl(viewPath), function () { 113 var view = new PageView(); 114 view.show(); 115 scope.viewport.append(curView.$el); 116 var s = ''; 117 }); 118 //!!!暂时不使用requireJS 119 // var self = this; 120 // requirejs([this.buildUrl(path)], function (View) { 121 // callback && callback.call(self, View); 122 // }); 123 }, 124 buildUrl: function (path) { 125 return this.viewRoot = path; 126 } 127 });
好了,至此,我们粗制滥造版app结束,我们来试试先,再一并讲解其主要流程。
简单测试
html代码:
1 <!DOCTYPE html> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta charset="utf-8" /> 5 <title></title> 6 <script src="res/libs/jquery.js" type="text/javascript"></script> 7 <script src="res/test/c.base.js" type="text/javascript"></script> 8 </head> 9 <body> 10 </body> 11 <script src="res/test/app.js" type="text/javascript"></script> 12 <script type="text/javascript"> 13 var app = new Application(); 14 15 </script> 16 </html>
base代码:
1 /// <reference path="../libs/underscore.js" /> 2 3 /// <reference path="../libs/jquery.js" /> 4 5 /// <reference path="../libs/require.js" /> 6 7 /// <reference path="../libs/require.text.js" /> 8 9 10 11 12 var b = {};//base 13 var slice = [].slice; 14 var indexOf = function (k, arr) { 15 if (!arr) { 16 return -1; 17 } 18 //若是对象本身便居然indexof,便使用自身的,比如字符串 19 if (arr.indexOf) { 20 return arr.indexOf(k); 21 } 22 for (var i = 0, len = arr.length; i < len; i++) { 23 if (arr[i] == k) { 24 return i; 25 } 26 } 27 return -1; 28 }; 29 30 b.Class = function (supClass, childAttr) { 31 //若是传了第一个类,便继承之;否则实现新类 32 if (typeof supClass === 'object') { 33 childAttr = supClass; 34 supClass = function () { }; 35 } 36 37 // var supProto = supClass.prototype; 38 var newClass = function () { 39 this._propertys_ && this._propertys_(); 40 this.init && this.init.apply(this, arguments); 41 }; 42 newClass.prototype = new supClass(); 43 44 var supInit = newClass.prototype.init || function () { }; 45 var childInit = childAttr.init || function () { }; 46 var _supAttr = newClass.prototype._propertys_ || function () { }; 47 var _childAttr = childAttr._propertys_ || function () { }; 48 49 for (var k in childAttr) { 50 //_propertys_中作为私有属性 51 childAttr.hasOwnProperty(k) && (newClass.prototype[k] = childAttr[k]); 52 } 53 54 //继承的属性有可能重写init方法 55 if (arguments.length && arguments[0].prototype && arguments[0].prototype.init === supInit) { 56 //重写新建类,初始化方法,传入其继承类的init方法 57 newClass.prototype.init = function () { 58 var scope = this; 59 var args = [function () { 60 supInit.apply(scope, arguments); 61 } ]; 62 childInit.apply(scope, args.concat(slice.call(arguments))); 63 }; 64 } 65 66 //内部属性赋值 67 newClass.prototype._propertys_ = function () { 68 _supAttr.call(this); 69 _childAttr.call(this); 70 }; 71 72 //成员属性 73 for (var k in supClass) { 74 supClass.hasOwnProperty(k) && (newClass[k] = supClass[k]); 75 } 76 return newClass; 77 }; 78 79 b.AbstractView = b.Class({ 80 //基本view应该具有的属性 81 _propertys_: function () { 82 this.id = (new Date()).getTime(); //唯一pageID 83 this.rootBox = $('body'); //视图容器 84 this.root = $('<div/>'); //视图的根元素,可进行设置 85 this.header = null; 86 this.footer = null; 87 this.template = '';//可能的模板 88 this.isCreated = false;//是否创建完毕 89 this.status = b.AbstractView.STATE_NOTCREATE;//当前状态 90 }, 91 init: function () { 92 }, 93 //定义将要用到的事件,其中元素选取都会以root为标准,所以使用内部提供函数吧 94 events: { 95 'selector,eventType': 'func' 96 }, 97 //默认属性 98 attrs: { 99 }, 100 //获取视图元素 101 find: function (selector) { 102 return this.root.find(selector); 103 }, 104 //创建dom 105 create: function (opts) { 106 if(!this.isCreated && this.status != b.AbstractView.STATE_ONCREATE) { 107 var attr = opts && opts.attr; 108 var html = this.createHtml(); 109 this.initRoot(attr);//初始化root 110 this.hide(); 111 this.rootBox.append(this.root); 112 this.root.html(html); 113 this.trigger('onCreate');//触发正在创建事件,其实这里都创建完了 114 this.status = b.AbstractView.STATE_ONCREATE; 115 this.isCreated = true; 116 this.bindEvent(); 117 } 118 }, 119 //呈现/渲染视图 120 show: function (callback) { 121 if(this.status == b.AbstractView.STATE_ONSHOW) { 122 return; 123 } 124 this.create(); 125 this.root.show(); 126 this.trigger('onShow'); 127 this.status = b.AbstractView.STATE_ONSHOW 128 callback && (typeof callback == 'function') && callback.call(this); 129 this.trigger('onLoad'); 130 }, 131 //隐藏dom 132 hide: function (callback) { 133 if(!this.root || this.status == b.AbstractView.STATE_ONHIDE) { 134 return; 135 } 136 this.root.hide(); 137 this.trigger('onHide'); 138 this.status = b.AbstractView.STATE_ONHIDE; 139 callback && (typeof callback == 'function') && callback(); 140 }, 141 //事件绑定 142 bindEvent: function () { 143 var events = this.events; 144 for(var k in events) { 145 var sec_type = k.replace(/\s/i, '').split(','); 146 var func = events[k]; 147 if(sec_type &&sec_type.length == 2 && typeof func == 'function') { 148 var selector = sec_type[0]; 149 var type = sec_type[1]; 150 var scope = this; 151 this.find(selector).on(type, function () { 152 func.call(scope, $(this)); 153 }) 154 } 155 } 156 }, 157 //此处可以配合模板与相关参数组成html 158 //解析模板也放到此处 159 createHtml: function () { 160 throw new Error('请重新定义createHtml方法'); 161 }, 162 initRoot: function () { 163 var attr = this.attrs; 164 if(!attr) { 165 return; 166 } 167 for(var k in attr) { 168 if(k == 'className') { 169 this.root.attr('class', attr[k]); 170 }else { 171 this.root.attr(k, attr[k]); 172 } 173 } 174 this.root.attr('id', this.id); 175 }, 176 //触发事件 177 trigger: function (k, args) { 178 var event = this[k]; 179 args = args || []; 180 if(event && typeof event == 'function') { 181 event.apply(this, args) 182 } 183 }, 184 setRootBox: function (dom) { 185 this.rootBox = dom; 186 }, 187 setAttr: function (k, v) { 188 this.root.attr(k, v); 189 }, 190 getAttr: function (k) { 191 return this.root.attr(k); 192 }, 193 setCss: function (k, v) { 194 this.root.css(k, v); 195 }, 196 getCss: function (k) { 197 return this.root.css(k); 198 }, 199 //dom创建后执行 200 onCreate: function () { 201 202 }, 203 //dom创建后数据加载时执行,用于加载后执行我们的逻辑 204 onLoad: function () { 205 206 }, 207 //dom创建后,未显示 208 onShow: function () { 209 210 }, 211 //dom隐藏前 212 onHide: function () { 213 214 } 215 }); 216 217 //组件状态,未创建 218 b.AbstractView.STATE_NOTCREATE = 'notCreate'; 219 //组件状态,已创建但未显示 220 b.AbstractView.STATE_ONCREATE = 'onCreate'; 221 //组件状态,已显示 222 b.AbstractView.STATE_ONSHOW = 'onShow'; 223 //组件状态,已隐藏 224 b.AbstractView.STATE_ONHIDE = 'onHide'; 225 226 b.Hash = b.Class({ 227 _propertys_: function () { 228 this.keys = []; 229 this.values = []; 230 }, 231 init: function (obj) { 232 (typeof obj == 'object') || (obj = {}); //??? 233 for (var k in obj) { 234 if (obj.hasOwnProperty(k)) { 235 this.keys.push(k); 236 this.values.push(obj[k]); 237 } 238 } 239 }, 240 length: function () { 241 return this.keys.length; 242 }, 243 getItem: function (k) { 244 var index = indexOf(k, this.keys); 245 if (index < 0) { 246 return null; 247 } 248 return this.keys[index]; 249 }, 250 getKey: function (i) { 251 return this.keys[i]; 252 }, 253 getValue: function (i) { 254 return this.values[i]; 255 }, 256 add: function (k, v) { 257 return this.push(k, v); 258 }, 259 del: function (k) { 260 var index = indexOf(k, this.keys); 261 return this.delByIndex(index); 262 }, 263 delByIndex: function (index) { 264 if (index < 0) return this; 265 this.keys.splice(index, 1); 266 this.vaules.splice(index, 1); 267 return this; 268 }, 269 //移除栈顶hash,并返回 270 pop: function () { 271 if (!this.keys.length) return null; 272 this.keys.pop(); 273 return this.values.pop(); 274 }, 275 push: function (k, v, order) { 276 if (typeof k == 'object' && !v) { 277 for (var i in k) { 278 if (k.hasOwnProperty(i)) { 279 this.push(i, k[i], order); 280 } 281 } 282 } else { 283 var index = indexOf(k, this.keys); 284 if (index < 0 || order) { 285 if (order) this.del(k); 286 this.keys.push[k]; 287 this.values.push[v]; 288 } else { 289 this.values[index] = v; 290 } 291 } 292 }, 293 //查找hash表,返回key 294 indexOf: function (v) { 295 var index = indexOf(v, this.vaules); 296 if (index >= 0) { 297 return this.keys[index]; 298 } 299 return -1; 300 }, 301 each: function (handler) { 302 if (typeof handler == 'function') { 303 for (var i = 0, len = this.length(); i < len; i++) { 304 handler.call(this, this.keys[i], this.vaules[i]); 305 } 306 } 307 }, 308 getObj: function () { 309 var obj = {}; 310 for (var i = 0, len = this.length(); i < len; i++) { 311 obj[this.keys[i]] = this.values[i]; 312 } 313 return obj; 314 } 315 });