天天看點

【單頁應用】view與model相關梳理

前情回顧

根據之前的學習,我們形成了一個view與一個messageCenter

view這塊來說又内建了一套mvc的東西,我們這裡來理一下

首先View一層由三部分組成:

① view

② dataAdpter

③ viewController

view一塊兩個重要資料是模闆以及對應data,一個狀态機status

這裡view隻負責根據狀态取出對應的模闆,而後根據傳入的資料傳回組裝好的html

這裡一個view多種狀态是什麼意思呢?

比如我有一個元件,但是裡面有一圈資料是需要Ajax請求的,是以我的view可能就分為兩個狀态了

init->ajaxSuccess 這樣的話首次加載預設的dom結構,資料加載結束後便再次渲染

PS:這裡再次渲染的時候暫時圖友善是采用将整個DOM結構換掉的手法,雖然簡單粗暴卻不合适,這塊後期優化

這裡資料的變化不由view負責,負責他的是dataAdapter

dataAdpter屬于一個獨立的子產品,可用與多個viewController,dataAdpter内部首先維護着一個觀察者數組,

然後是兩個關鍵的datamodel以及viewmodel

datamodel用于操作,viewmodel會根據datamodel生成最終,然後使用viewmodel進行頁面render,這個就是傳入的data

若是我某一個datamodel對象發生變化便會通知觀察者們,然後對應的view就會得到更新,該過程的發生點控制于viewController

viewController是連接配接view與dataAdpter的樞紐

viewController必須具有view,卻可以沒有dataAdpter,因為不是所有view都需要data才能渲染

我們實際工作中的大量業務邏輯會在viewController中定義完成,然後viewController也分了幾個事件點

① create 觸發onViewBeforeCreate、onViewAfterCreate事件

② show會實際将dom結構轉入并且顯示出來 觸發onViewBeforeShow、onViewAfterShow事件

show的時候會綁定相關事件,事件借鑒于Backbone事件機制,每次注冊前會先移除

③ 而後便是hide事件,他會隐藏我們的dom卻不會移除,對應會有onViewBeforeHide、onViewAfterHide

④ destroy事件,會移除dom結構,并且删除執行個體、釋放自身資源

以上是主流功能,還有一些功能不一定常用,比如我們任務view隐藏後,其所有狀态事件全部應該移除,在show時重新綁定

messageCenter

現在沒有什麼大問題,卻有一個小隐憂,這個消息中心會全局分發,一旦注冊後,在觸發時皆會觸發,這個就有一個問題

我有一個alert元件,我自己内部在初始化時候注冊了一個onShow的事件,我在show的時候真正的執行之

這個看上去沒有什麼問題,但是以下場景會有不一樣的感受

我一個頁面上有兩個alert執行個體的話,我調用其中一個的時候,另一個alert的onShow也會被觸發,這個是我們不願意看見的

換個例子,我們一個頁面上有兩個IScroll,我們如使用messageCenter的話,一個滑動結束觸發對應鍵值事件,很有可能兩邊會同時被觸發

是以,這些都是我們需要關注的問題

下面讓我們來詳細整理

View相關梳理

現在View相關的功能點還不完全成熟,主要糾結點在于modelView改變後,view應該作何反應

若是一小點資料的改變卻會引起整個dom結構的重組,這一點也是緻命的,

其次一個view不同的狀态會組成不同的view,但是一個view組成的html應該有一個容器,此“容器”現階段我們概念感不是很強

所謂容器,不過是有模闆嵌套的場景,後加載出來的html需要放入之前的某一個位置

若是子模闆改變隻會改變對應部分的dom、若是主模闆改變就隻能全部dom重組了!!!

于是我們簡單整理後的代碼如下:

首先來看看我們的view

  1 Dalmatian.View = _.inherit({

  2 

  3   // @description 設定預設屬性

  4   _initialize: function () {

  5 

  6     var DEFAULT_CONTAINER_TEMPLATE = '<div class="view"></div>';

  7     var VIEW_ID = 'dalmatian-view-';

  8 

  9     // @override

 10     // @description template集合,根據status做template的map

 11     // @example

 12     /*

 13     {

 14     init: '<ul><%_.each(list, function(item){%><li><%=item.name%></li><%});%></ul>'//若是字元串表明全局性

 15     ajaxLoading: 'loading',

 16     ajaxSuc: 'success'

 17     }

 18     */

 19     this.templateSet = {};

 20 

 21     // @override

 22     /*

 23     ***這塊我沒有考慮清楚,一般情況下view是不需要在改變的,若是需要改變其實該設定到datamodel中***

 24     這個可以考慮預設由viewController注入給dataModel,然後後面就可操作了......

 25     這裡的包裹器可能存在一定時序關系,這塊後續再整理

 26 

 27     與模闆做映射關系,每個狀态的模闆對象可能對應一個容器,預設為根容器,後期可能會被修改

 28     ajaxLoading: el,

 29     ajaxSuc: selector

 30     */

 31     this.wrapperSet = {};

 32 

 33     this.viewid = _.uniqueId(VIEW_ID);

 34     this.currentStatus = null;

 35     this.defaultContainer = DEFAULT_CONTAINER_TEMPLATE;

 36     this.isNoWrapper = false;

 37 

 38     //全局根元素

 39     this.root = null;

 40     //目前包裹器

 41     this.curWrapper = null;

 42     //目前模闆對應解析後的html結構

 43 

 44   },

 45 

 46   _initRoot: function () {

 47     //根據html生成的dom包裝對象

 48     //有一種場景是使用者的view本身就是一個隻有一個包裹器的結構,他不想要多餘的包裹器

 49     if (!this.isNoWrapper) {

 50       this.root = $(this.defaultContainer);

 51       this.root.attr('id', this.viewid);

 52     }

 53   },

 54 

 55   // @description 構造函數入口

 56   initialize: function (options) {

 57     this._initialize();

 58     this.handleOptions(options);

 59     this._initRoot();

 60 

 61   },

 62 

 63   // @override

 64   // @description 操作構造函數傳入操作

 65   handleOptions: function (options) {

 66     // @description 從形參中擷取key和value綁定在this上

 67     // l_wang options可能不是純淨的對象,而是函數什麼的,這樣需要注意

 68     if (_.isObject(options)) _.extend(this, options);

 69 

 70   },

 71 

 72   //處理包裹器,暫時不予理睬

 73   _handleNoWrapper: function (html) {

 74     //...不予理睬

 75   },

 76 

 77   //根據狀态值擷取目前包裹器

 78   _getCurWrapper: function (status, data) {

 79     //處理root不存在的情況

 80     this._handleNoWrapper();

 81 

 82     //若是以下邏輯無用,那麼這個便是根元素

 83     if (!data.wrapperSet || !data.wrapperSet[status]) { return this.root; }

 84     if (_.isString(data.wrapperSet[status])) { return this.root.find(data.wrapperSet[status]); }

 85 

 86   },

 87 

 88   // @description 通過模闆和資料渲染具體的View

 89   // @param status {enum} View的狀态參數

 90   // @param data {object} 比對View的資料格式的具體資料

 91   // @param callback {functiion} 執行完成之後的回調

 92   render: function (status, data, callback) {

 93 

 94     var templateFn, wrapper;

 95     var template = this.templateSet[status];

 96 

 97     //預設将view中設定的預設wrapper注入值datamodel,datamodel會帶入viewModel

 98     wrapper = this._getCurWrapper(status, data);

 99 

100     if (!wrapper[0]) throw '包裹器參數錯誤';

101     if (!template) return false;

102 

103     //解析目前狀态模闆,編譯成函數

104     templateFn = Dalmatian.template(template);

105     wrapper.html(templateFn(data));

106     this.html = wrapper;

107 

108     this.currentStatus = status;

109 

110     _.callmethod(callback, this);

111     return true;

112 

113   },

114 

115   // @override

116   // @description 可以被複寫,當status和data分别發生變化時候

117   // @param status {enum} view的狀态值

118   // @param data {object} viewmodel的資料

119   update: function (status, data) {

120 

121     if (!this.currentStatus || this.currentStatus !== status) {

122       return this.render(status, data);

123     }

124 

125     // @override

126     // @description 可複寫部分,當資料發生變化但是狀态沒有發生變化時,頁面僅僅變化的可以是局部顯示

127     //              可以通過擷取this.html進行修改

128     _.callmethod(this.onUpdate, this);

129   }

130 });

view基本隻負責根據模闆和資料生成html字元串,有一個不同的點是他需要記錄自己的根元素,這個對我們後續操作有幫助

其中比較關鍵的是templateSet以及wrapperSet,這裡的wrapperSet會被注入給dataAdpter的datamodel,後期便于調整

然後是我們的Adapter

 1 Dalmatian.Adapter = _.inherit({

 2 

 3   // @description 構造函數入口

 4   initialize: function (options) {

 5     this._initialize();

 6     this.handleOptions(options);

 7   },

 8 

 9   // @description 設定預設屬性

10   _initialize: function () {

11     this.observers = [];

12     //    this.viewmodel = {};

13     this.datamodel = {};

14   },

15 

16   // @description 操作構造函數傳入操作

17   handleOptions: function (options) {

18     // @description 從形參中擷取key和value綁定在this上

19     if (_.isObject(options)) _.extend(this, options);

20   },

21 

22   // @override

23   // @description 操作datamodel傳回一個data對象形成viewmodel

24   format: function (datamodel) {

25     return datamodel;

26   },

27 

28   getViewModel: function () {

29     return this.format(this.datamodel);

30   },

31 

32   registerObserver: function (viewcontroller) {

33     // @description 檢查隊列中如果沒有viewcontroller,從隊列尾部推入

34     if (!_.contains(this.observers, viewcontroller)) {

35       this.observers.push(viewcontroller);

36     }

37   },

38 

39   setStatus: function (status) {

40     _.each(this.observers, function (viewcontroller) {

41       if (_.isObject(viewcontroller))

42         viewcontroller.setViewStatus(status);

43     });

44   },

45 

46   unregisterObserver: function (viewcontroller) {

47     // @description 從observers的隊列中剔除viewcontroller

48     this.observers = _.without(this.observers, viewcontroller);

49   },

50 

51   notifyDataChanged: function () {

52     // @description 通知所有注冊的觀察者被觀察者的資料發生變化

53     //    this.viewmodel = this.format(this.datamodel);

54     var data = this.getViewModel();

55     _.each(this.observers, function (viewcontroller) {

56       if (_.isObject(viewcontroller))

57         _.callmethod(viewcontroller.update, viewcontroller, [data]);

58     });

59   }

60 });

他隻負責更新資料,并在資料變化時候通知ViewController處理變化,接下來就是我們的viewController了

 View Code

這個控制器是連接配接view以及Adapter的橋梁,三者合一便可以處理一些問題,接下來看一個簡單的demo

Ajax例子

這段代碼的核心在此

 1 //模拟Ajax請求

 2 function getAjaxData(callback, data) {

 3   setTimeout(function () {

 4     if (!data) {

 5       data = [];

 6       for (var i = 0; i < 5; i++) {

 7         data.push({ title: '我是标題_' + i });

 8       }

 9     }

10     callback(data);

11   }, 1000);

12 }

13 

14 var AjaxView = _.inherit(Dalmatian.View, {

15   _initialize: function ($super) {

16     //設定預設屬性

17     $super();

18 

19     this.templateSet = {

20       init: $('#template-ajax-init').html(),

21       loading: $('#template-ajax-loading').html(),

22       ajaxSuc: $('#template-ajax-suc').html()

23     };

24 

25     this.wrapperSet = {

26       loading: '.cui-error-tips',

27       ajaxSuc: '.cui-error-tips'

28     };

29   }

30 });

32 var AjaxAdapter = _.inherit(Dalmatian.Adapter, {

33   _initialize: function ($super) {

34     $super();

35     this.datamodel = {

36       title: '标題',

37       confirm: '重新整理資料'

38     };

39     this.datamodel.ajaxData = {};

40   },

41 

42   format: function (datamodel) {

43     //處理datamodel生成viewModel的邏輯

44     return datamodel;

45   },

46 

47   ajaxLoading: function () {

48     this.setStatus('loading');

49     this.notifyDataChanged();

50   },

51 

52   ajaxSuc: function (data) {

53     this.datamodel.ajaxData = data;

54     this.setStatus('ajaxSuc');

55     this.notifyDataChanged();

56   }

57 });

58 

59 var AjaxViewController = _.inherit(Dalmatian.ViewController, {

60   _initialize: function ($super) {

61     $super();

62     //設定基本的屬性

63     this.view = new AjaxView();

64     this.adapter = new AjaxAdapter();

65     this.viewstatus = 'init';

66     this.container = '#container';

67   },

68 

69   //顯示後Ajax請求資料

70   onViewAfterShow: function () {

71     this._handleAjax();

72   },

73 

74   _handleAjax: function (data) {

75     this.adapter.ajaxLoading();

76     getAjaxData($.proxy(function (data) {

77       this.adapter.ajaxSuc(data);

78     }, this), data);

79   },

80 

81   events: {

82     'click .cui-btns-sure': function () {

83       var data = this.$el.find('#ajax_data').val();

84       data = eval('(' + data + ')');

85       this._handleAjax(data);

86     }

87   }

88 });

89 

90 var a = new AjaxViewController();

91 a.show();

首先定義view

其次定義資料處理層

最後将兩者合一

重點放到了資料進行中,實際上的邏輯由Controller處理,真正的html又view生成,整個代碼如上......

結語

今天對之前的學習進行了一些整理,由于過程中多數時間在編碼,是以描述少了一點,整個這塊還是有一些問題,我們留待後期解決吧

本文轉自葉小钗部落格園部落格,原文連結:http://www.cnblogs.com/yexiaochai/p/3735139.html,如需轉載請自行聯系原作者

繼續閱讀