【單頁應用】一起來單頁應用吧,實現簡單微博功能!(下)


前言

PS:回家發現代碼沒拷貝回來,明天繼續吧。。。

開始之前先扯點其它的,我上午去理發,居然才7元,但是那個阿媽給我搞成和尚的造型了,我想說點什么,但轉念一想節約點洗頭水也好!!!

PS:其實主要原因是我掉頭發。。。。。。

好了,接昨天的內容:【單頁應用】一起來單頁應用吧,實現簡單微博功能!(上)

昨天我們一起准備了兩大核心,繼承與view,雖說有問題,而且必定有問題,但是我們暫時不管他。

我們先繼續先往下面寫,到最后整體流程拉通后再一起優化就好,現在去糾結也不一定有結果,於是我們開始吧。

繼承之實現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 });
base核心

app代碼

  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 var Application = new b.Class({
 12     _propertys_: function () {
 13         var scope = this;
 14         this.webRoot = ''; //應用跟目錄
 15         this.head = $('head');
 16         this.body = $('body');
 17         this.viewRoot = 'res/test/'; //視圖所在目錄
 18         this.defaultView = 'index'; //默認加載視圖
 19 
 20         this.request; //請求對象
 21         this.viewPath; //當前請求視圖路徑,解析request得出
 22         this.mainFrame; //主框架
 23         this.viewPort; //視圖框架
 24         this.stateDom; //狀態欄
 25 
 26         this.views = new b.Hash(); //views保存瀏覽器存儲的hash
 27         this.curView; //當前視圖
 28         this.interface = {}; //提供給視圖訪問的接口,暫時不管
 29         this.history = []; //歷史記錄
 30 
 31         //        this.stopListening = false;//是否開啟監聽
 32 
 33         this.onHashChange = function () {
 34             scope.history.push(window.location.href);
 35             var url = decodeURIComponent(window.location.hash.replace(/^#+/i, '')).toLowerCase();
 36             scope._onHashChange(url);
 37         };
 38 
 39         this.lastHash = '';
 40         this.lastFullHash = '';
 41         this.isChangeHash = false; //hash是否發生變化
 42     },
 43     init: function (opts) {
 44         console.log('app init');
 45         //為屬性賦值
 46         opts = opts || {};
 47         for (var k in opts) {
 48             this[k] = opts[k];
 49         }
 50         this.createViewPort();
 51         this.bindEvent(); //事件綁定
 52     },
 53 
 54     //創建app頁面基本框架,此處不能使用id,因為。。。
 55     createViewPort: function () {
 56         var htm = [
 57             '<div class="main-frame">',
 58                 '<div class="main-viewport"></div>',
 59                 '<div class="main-state"></div>',
 60             '</div>'
 61         ].join('');
 62         this.mainframe = $(htm);
 63         this.viewport = this.mainframe.find('.main-viewport');
 64         this.statedom = this.mainframe.find('.main-state');
 65         var body = $('body');
 66         body.html('');
 67         body.append(this.mainframe);
 68     },
 69     //!!!!!!非常重要哦!!!!!!
 70     bindEvent: function () {
 71         var scope = this;
 72         //暫時不使用requireJS
 73         //        requirejs.onError = function () {};
 74         $(window).bind('hashchange', this.onHashChange);
 75     },
 76     _onHashChange: function (url) {
 77         url = url.replace(/^#+/i, '');
 78         var req = this.parseHash(url);
 79 
 80         this.request = req;
 81         this.viewPath = this.viewPath || this.defaultView;
 82         this.loadView(this.viewPath); //!!!重要的視圖加載
 83     },
 84     //該方法慢慢看吧。。。
 85     parseHash: function (hash) {
 86         var fullhash = hash,
 87                 hash = hash.replace(/([^\|]*)(?:\|.*)?$/img, '$1'),
 88                 h = /^([^?&|]*)(.*)?$/i.exec(hash),
 89                 vp = h[1] ? h[1].split('!') : [],
 90                 viewpath = (vp.shift() || '').replace(/(^\/+|\/+$)/i, ''),
 91                 path = vp.length ? vp.join('!').replace(/(^\/+|\/+$)/i, '').split('/') : [],
 92                 q = (h[2] || '').replace(/^\?*/i, '').split('&'),
 93                 query = {}, y;
 94         this.isChangeHash = !!(!this.lastHash && fullhash === this.lashFullHash) || !!(this.lastHash && this.lastHash !== hash);
 95         if (q) {
 96             for (var i = 0; i < q.length; i++) {
 97                 if (q[i]) {
 98                     y = q[i].split('=');
 99                     y[1] ? (query[y[0]] = y[1]) : (query[y[0]] = true);
100                 }
101             }
102         }
103 
104         this.lastHash = hash;
105         this.lashFullHash = fullhash;
106         return {
107             viewpath: viewpath,
108             path: path,
109             query: query,
110             root: location.pathname + location.search
111         };
112     },
113     //!!!非常重要
114     loadView: function (viewPath) {
115         var id = viewPath;
116         var scope = this;
117         //此處本來應該判斷是否已經有該視圖,但是我們暫時不管,我們只要加載了相關視圖就算成功
118         /*
119         一些操作
120         */
121 
122         var path = this.buildUrl(viewPath);
123 
124         //此處應該加載我們的js文件
125         $.getScript(this.buildUrl(viewPath) + '.js', function () {
126             var view = new PageView();
127             view.show();
128             scope.viewport.append(view.root);
129             var s = '';
130         });
131         //!!!暫時不使用requireJS
132         //        var self = this;
133         //        requirejs([this.buildUrl(path)], function (View) {
134         //            callback && callback.call(self, View);
135         //        });
136     },
137     buildUrl: function (path) {
138         return this.viewRoot + path;
139     }
140 });
APP

文件結構

測試結果

我可恥的覺得自己成功了一半了。。。

基本流程講解

app是我們整個框架的核心,我們來簡單講解一下:

① 在整個_propertys_函數中,定義了我們app會用到的一些實例屬性、方法

② init方法(一定會執行,因為我們使用了c.base的方法創建類),他主要干了兩件事:

創建基本dom結構;

綁定事件,這個綁定事件就非常重要了

③ 59行開始,便是該文件的核心之一,這里為window綁定了hashchange事件,於是hash一旦改變(以#號方式),那么就會觸發onhashchange事件

④ 觸發hashchange事件后,會獲得請求url#后面的參數,根據約定,他就是我們請求的view的路徑,所以開始執行loadView方法,開始加載視圖

⑤ 102行,開始加載視圖,這里本應該使用requireJS,但是這里便於講解,便於各位理解,我們暫時沒有使用,於是動態加載我們的index文件,在成功后實例化view並且將view裝入我們的視口中。

⑥ 整體流程結束

於是我們的粗制濫造版本也結束了。

階段總結

好了,我們現在回過頭來看看我們整個框架現在變成什么樣了。

① 我們擁有實現類的通用b.class

② 我們有了我們的抽象視圖類

③ 我們有了我們的control application

假使以上流程都流程問題不大,那么我們整個功能其實就七七八八了,因為還差的model可以輕松補上。

但是明顯我們現在的APP是有缺陷的,而且缺陷比較大,比如我們的hash相關其實根本沒有用上,所以現在我們來完善他們吧!

RequireJS組織代碼

在這里,我們便開始使用我們的RequireJS了,然后將我們的整個邏輯慢慢鋪開,首先我將我們的文件目錄分開。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
</body>
<script src="res/libs/require.js" data-main="main" type="text/javascript"></script>
</html>
html頁
window.BASEURL = '';
require.config({
    baseUrl: BASEURL,
    shim: {
        $: {
            exports: 'jQuery'
        },
        _: {
            exports: '_'
        }
    },
    paths: {
        '$': 'res/libs/jquery',
        '_': 'res/libs/underscore',
        'text': 'res/libs/require.text',

        'c': 'res/common/c',
        'cBase': 'res/common/c.base',
        'cView': 'res/common/c.view',
        'app': 'res/app',


        's': 's'
    }
});

require(['app'], function (APP) {
    new APP();
});
main
define(['$', 'c'], function ($, c) {
    
    var Application = new c.base.Class({
        _propertys_: function () {
            var scope = this;
            this.webRoot = ''; //應用跟目錄
            this.head = $('head');
            this.body = $('body');
            this.viewRoot = 'views/'; //視圖所在目錄
            this.defaultView = 'index'; //默認加載視圖

            this.request; //請求對象
            this.viewPath; //當前請求視圖路徑,解析request得出
            this.mainFrame; //主框架
            this.viewPort; //視圖框架
            this.stateDom; //狀態欄

            this.views = new c.base.Hash(); //views保存瀏覽器存儲的hash
            this.curView; //當前視圖
            this.interface = {}; //提供給視圖訪問的接口,暫時不管
            this.history = []; //歷史記錄

            //        this.stopListening = false;//是否開啟監聽

            this.onHashChange = function () {
                scope.history.push(window.location.href);
                var url = decodeURIComponent(window.location.hash.replace(/^#+/i, '')).toLowerCase();
                scope._onHashChange(url);
            };

            this.lastHash = '';
            this.lastFullHash = '';
            this.isChangeHash = false; //hash是否發生變化
        },
        init: function (opts) {
            console.log('app init');
            //為屬性賦值
            opts = opts || {};
            for (var k in opts) {
                this[k] = opts[k];
            }
            this.createViewPort();
            this.bindEvent(); //事件綁定
        },

        //創建app頁面基本框架,此處不能使用id,因為。。。
        createViewPort: function () {
            var htm = [
            '<div class="main-frame">',
                '<div class="main-viewport"></div>',
                '<div class="main-state"></div>',
            '</div>'
        ].join('');
            this.mainframe = $(htm);
            this.viewport = this.mainframe.find('.main-viewport');
            this.statedom = this.mainframe.find('.main-state');
            var body = $('body');
            body.html('');
            body.append(this.mainframe);
        },
        //!!!!!!非常重要哦!!!!!!
        bindEvent: function () {
            var scope = this;
            requirejs.onError = function (e) {
                if (e && e.requireModules) {
                    for (var i = 0; i < e.requireModules.length; i++) {
                        console.error((e.requireModules[i] || '').replace(self.viewRootPath, '') + '頁面不存在!');
                    }
                }
            };

            $(window).bind('hashchange', this.onHashChange);
        },
        _onHashChange: function (url) {
            url = url.replace(/^#+/i, '');
            var req = this.parseHash(url);

            this.request = req;
            this.viewPath = this.viewPath || this.defaultView;
            this.loadView(this.viewPath); //!!!重要的視圖加載
        },
        //該方法慢慢看吧。。。
        parseHash: function (hash) {
            var fullhash = hash,
                hash = hash.replace(/([^\|]*)(?:\|.*)?$/img, '$1'),
                h = /^([^?&|]*)(.*)?$/i.exec(hash),
                vp = h[1] ? h[1].split('!') : [],
                viewpath = (vp.shift() || '').replace(/(^\/+|\/+$)/i, ''),
                path = vp.length ? vp.join('!').replace(/(^\/+|\/+$)/i, '').split('/') : [],
                q = (h[2] || '').replace(/^\?*/i, '').split('&'),
                query = {}, y;
            this.isChangeHash = !!(!this.lastHash && fullhash === this.lashFullHash) || !!(this.lastHash && this.lastHash !== hash);
            if (q) {
                for (var i = 0; i < q.length; i++) {
                    if (q[i]) {
                        y = q[i].split('=');
                        y[1] ? (query[y[0]] = y[1]) : (query[y[0]] = true);
                    }
                }
            }

            this.lastHash = hash;
            this.lashFullHash = fullhash;
            return {
                viewpath: viewpath,
                path: path,
                query: query,
                root: location.pathname + location.search
            };
        },
        //!!!非常重要
        loadView: function (viewPath) {
            var id = viewPath;
            var scope = this;
            var path = this.buildUrl(viewPath);

            requirejs([path], function (View) {
                var view = new View();
                view.show();
                scope.viewport.append(view.root);
                var s = '';
            });
        },
        buildUrl: function (path) {
            return this.viewRoot + path;
        }
    });

    return Application;
});
app
1 define(['cBase', 'cView'], function (base, view) {
2     var c = {
3         base: base,
4         view: view
5     };
6 
7     return c;
8 });
c
  1 define([], function () {
  2 
  3     var base = {}; //base
  4     var slice = [].slice;
  5     var indexOf = function (k, arr) {
  6         if (!arr) {
  7             return -1;
  8         }
  9         //若是對象本身便居然indexof,便使用自身的,比如字符串
 10         if (arr.indexOf) {
 11             return arr.indexOf(k);
 12         }
 13         for (var i = 0, len = arr.length; i < len; i++) {
 14             if (arr[i] == k) {
 15                 return i;
 16             }
 17         }
 18         return -1;
 19     };
 20 
 21     base.Class = function (supClass, childAttr) {
 22         //若是傳了第一個類,便繼承之;否則實現新類
 23         if (typeof supClass === 'object') {
 24             childAttr = supClass;
 25             supClass = function () { };
 26         }
 27 
 28         //    var supProto = supClass.prototype;
 29         var newClass = function () {
 30             this._propertys_ && this._propertys_();
 31             this.init && this.init.apply(this, arguments);
 32         };
 33         newClass.prototype = new supClass();
 34 
 35         var supInit = newClass.prototype.init || function () { };
 36         var childInit = childAttr.init || function () { };
 37         var _supAttr = newClass.prototype._propertys_ || function () { };
 38         var _childAttr = childAttr._propertys_ || function () { };
 39 
 40         for (var k in childAttr) {
 41             //_propertys_中作為私有屬性
 42             childAttr.hasOwnProperty(k) && (newClass.prototype[k] = childAttr[k]);
 43         }
 44 
 45         //繼承的屬性有可能重寫init方法
 46         if (arguments.length && arguments[0].prototype && arguments[0].prototype.init === supInit) {
 47             //重寫新建類,初始化方法,傳入其繼承類的init方法
 48             newClass.prototype.init = function () {
 49                 var scope = this;
 50                 var args = [function () {
 51                     supInit.apply(scope, arguments);
 52                 } ];
 53                 childInit.apply(scope, args.concat(slice.call(arguments)));
 54             };
 55         }
 56 
 57         //內部屬性賦值
 58         newClass.prototype._propertys_ = function () {
 59             _supAttr.call(this);
 60             _childAttr.call(this);
 61         };
 62 
 63         //成員屬性
 64         for (var k in supClass) {
 65             supClass.hasOwnProperty(k) && (newClass[k] = supClass[k]);
 66         }
 67         return newClass;
 68     };
 69     
 70     base.Hash = base.Class({
 71         _propertys_: function () {
 72             this.keys = [];
 73             this.values = [];
 74         },
 75         init: function (obj) {
 76             (typeof obj == 'object') || (obj = {}); //???
 77             for (var k in obj) {
 78                 if (obj.hasOwnProperty(k)) {
 79                     this.keys.push(k);
 80                     this.values.push(obj[k]);
 81                 }
 82             }
 83         },
 84         length: function () {
 85             return this.keys.length;
 86         },
 87         getItem: function (k) {
 88             var index = indexOf(k, this.keys);
 89             if (index < 0) {
 90                 return null;
 91             }
 92             return this.keys[index];
 93         },
 94         getKey: function (i) {
 95             return this.keys[i];
 96         },
 97         getValue: function (i) {
 98             return this.values[i];
 99         },
100         add: function (k, v) {
101             return this.push(k, v);
102         },
103         del: function (k) {
104             var index = indexOf(k, this.keys);
105             return this.delByIndex(index);
106         },
107         delByIndex: function (index) {
108             if (index < 0) return this;
109             this.keys.splice(index, 1);
110             this.vaules.splice(index, 1);
111             return this;
112         },
113         //移除棧頂hash,並返回
114         pop: function () {
115             if (!this.keys.length) return null;
116             this.keys.pop();
117             return this.values.pop();
118         },
119         push: function (k, v, order) {
120             if (typeof k == 'object' && !v) {
121                 for (var i in k) {
122                     if (k.hasOwnProperty(i)) {
123                         this.push(i, k[i], order);
124                     }
125                 }
126             } else {
127                 var index = indexOf(k, this.keys);
128                 if (index < 0 || order) {
129                     if (order) this.del(k);
130                     this.keys.push[k];
131                     this.values.push[v];
132                 } else {
133                     this.values[index] = v;
134                 }
135             }
136         },
137         //查找hash表,返回key
138         indexOf: function (v) {
139             var index = indexOf(v, this.vaules);
140             if (index >= 0) {
141                 return this.keys[index];
142             }
143             return -1;
144         },
145         each: function (handler) {
146             if (typeof handler == 'function') {
147                 for (var i = 0, len = this.length(); i < len; i++) {
148                     handler.call(this, this.keys[i], this.vaules[i]);
149                 }
150             }
151         },
152         getObj: function () {
153             var obj = {};
154             for (var i = 0, len = this.length(); i < len; i++) {
155                 obj[this.keys[i]] = this.values[i];
156             }
157             return obj;
158         }
159     });
160 
161     return base;
162 
163 });
c.base
define(['$', 'cBase'], function ($, b) {

    var AbstractView = b.Class({
        //基本view應該具有的屬性
        _propertys_: function () {
            this.id = (new Date()).getTime(); //唯一pageID
            this.rootBox = $('body'); //視圖容器
            this.root = $('<div/>'); //視圖的根元素,可進行設置
            this.header = null;
            this.footer = null;
            this.template = ''; //可能的模板
            this.isCreated = false; //是否創建完畢
            this.status = AbstractView.STATE_NOTCREATE; //當前狀態
        },
        init: function () {
        },
        //定義將要用到的事件,其中元素選取都會以root為標准,所以使用內部提供函數吧
        events: {
            'selector,eventType': 'func'
        },
        //默認屬性
        attrs: {
        },
        //獲取視圖元素
        find: function (selector) {
            return this.root.find(selector);
        },
        //創建dom
        create: function (opts) {
            if (!this.isCreated && this.status != AbstractView.STATE_ONCREATE) {
                var attr = opts && opts.attr;
                var html = this.createHtml();
                this.initRoot(attr); //初始化root
                this.hide();
                this.rootBox.append(this.root);
                this.root.html(html);
                this.trigger('onCreate'); //觸發正在創建事件,其實這里都創建完了
                this.status = AbstractView.STATE_ONCREATE;
                this.isCreated = true;
                this.bindEvent();
            }
        },
        //呈現/渲染視圖
        show: function (callback) {
            if (this.status == AbstractView.STATE_ONSHOW) {
                return;
            }
            this.create();
            this.root.show();
            this.trigger('onShow');
            this.status = AbstractView.STATE_ONSHOW
            callback && (typeof callback == 'function') && callback.call(this);
            this.trigger('onLoad');
        },
        //隱藏dom
        hide: function (callback) {
            if (!this.root || this.status == AbstractView.STATE_ONHIDE) {
                return;
            }
            this.root.hide();
            this.trigger('onHide');
            this.status = AbstractView.STATE_ONHIDE;
            callback && (typeof callback == 'function') && callback();
        },
        //事件綁定
        bindEvent: function () {
            var events = this.events;
            for (var k in events) {
                var sec_type = k.replace(/\s/i, '').split(',');
                var func = events[k];
                if (sec_type && sec_type.length == 2 && typeof func == 'function') {
                    var selector = sec_type[0];
                    var type = sec_type[1];
                    var scope = this;
                    this.find(selector).on(type, function () {
                        func.call(scope, $(this));
                    })
                }
            }
        },
        //此處可以配合模板與相關參數組成html
        //解析模板也放到此處
        createHtml: function () {
            throw new Error('請重新定義createHtml方法');
        },
        initRoot: function () {
            var attr = this.attrs;
            if (!attr) {
                return;
            }
            for (var k in attr) {
                if (k == 'className') {
                    this.root.attr('class', attr[k]);
                } else {
                    this.root.attr(k, attr[k]);
                }
            }
            this.root.attr('id', this.id);
        },
        //觸發事件
        trigger: function (k, args) {
            var event = this[k];
            args = args || [];
            if (event && typeof event == 'function') {
                event.apply(this, args)
            }
        },
        setRootBox: function (dom) {
            this.rootBox = dom;
        },
        setAttr: function (k, v) {
            this.root.attr(k, v);
        },
        getAttr: function (k) {
            return this.root.attr(k);
        },
        setCss: function (k, v) {
            this.root.css(k, v);
        },
        getCss: function (k) {
            return this.root.css(k);
        },
        //dom創建后執行
        onCreate: function () {

        },
        //dom創建后數據加載時執行,用於加載后執行我們的邏輯
        onLoad: function () {

        },
        //dom創建后,未顯示
        onShow: function () {

        },
        //dom隱藏前
        onHide: function () {

        }
    });

    //組件狀態,未創建
    AbstractView.STATE_NOTCREATE = 'notCreate';
    //組件狀態,已創建但未顯示
    AbstractView.STATE_ONCREATE = 'onCreate';
    //組件狀態,已顯示
    AbstractView.STATE_ONSHOW = 'onShow';
    //組件狀態,已隱藏
    AbstractView.STATE_ONHIDE = 'onHide';

    return AbstractView;

});
c.view
define(['$', 'cBase', 'cView'], function ($, b, AbstractView) {
    var View = b.Class(AbstractView, {
        _propertys_: function () {
            this.template = '我是葉小釵';
        },
        init: function (superInit) {
            console.log(superInit);
            console.log('init');
        },
        createHtml: function () {
            var htm = [
            '<header>標題</header>',
            '<div class="main">',
                '<input type="text" id="txt" />',
                '<input type="button" id="bt" value="點擊我" />',
                this.template,
            '</div>',
            '<footer>頁尾</footer>'
            ].join('');
            return htm;
        },
        attrs: {
            'data-id': 'test',
            className: 'yexiaoc'
        },
        events: {
            '#bt,click': function (el) {
                var txt = this.find('#txt');
                alert(txt.val())
            }
        },
        onCreate: function () {
            console.log('onCreate');
        },
        //dom創建后數據加載時執行,用於加載后執行我們的邏輯
        onLoad: function () {
            console.log('onLoad');
        },
        //dom創建后,未顯示
        onShow: function () {
            console.log('onShow');
        },
        //dom隱藏前
        onHide: function () {
            console.log('onHide');
        }
    });

    return View;
});
index

至此我們的代碼便完全分離開了,接下來我們來細化我們的app文件。

細化app

待續......

 

結語

寫了幾個小時了,有點累,休息下,我們晚上在繼續

 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM