標籤:cut alt pen class document input 函數定義 data r.js
初衷
上一篇已經實現了資料的雙向繫結,但model的控制範圍是整個文檔。在實際project中必需要有作用範圍,以便做ui模組的拆分。
這一篇,我們希望實現像angularjs一樣的控制器繼承:
1. 父controller的Model能夠在子controller裡被訪問到
2. 子controller的model不影響父controller
3. controller繼承關係在html中指定。而不是js中指定
目標
html裡,用isi-controller屬性去聲明控制器:
<body> <div isi-controller="ParentController"> <input data-bind="name"> <div isi-controller="SubController"> <input data-bind="name"> </div> </div></body
希望上面的input name 改了。以下的會跟著變,而以下的變了,上面的不變。
js裡,用和上面isi-controller屬性值同名的函數定義控制器:
function ParentController() { var model = new Model(); model.set(‘name‘, ‘parent‘);}function ParentController() { var model = new Model(); model.set(‘name‘, ‘sub‘);}
對使用者來說,僅僅要寫這些。就完事兒了。
實現版本號碼1
這個版本號碼採用最簡單直觀的思路:架構去找$(‘[isi-controller]’)的元素。然後給這些元素分別去綁定監聽器、運行控制器函數
代碼先列了:
index.html:
<html> <head> <title>simple MVVM</title> <script src="js/ParentController.js"></script> <script src="js/SubController.js"></script> <script src="js/frame_v2.js"></script> </head> <body isi-controller="ParentController"> <input type="text" data-bind="name"> <div isi-controller="SubController"> <input type="text" data-bind="name"> </div> </body></html>
ParentController.js:
function ParentController() { var model = new Model(); model.set(‘name‘, ‘parent‘);}
SubController.js:
function SubController() { var model = new Model(); model.set(‘name‘,‘sub‘);}
frame_v2.js: (對比上一篇,主要修改在綁監聽器和new Model的自己主動化)
var pubsub = ... //見上一篇var Model = ... //見上一篇// listener capture view changes --> publish model.change eventvar changeHandler = function(event) { var target = event.target, propName = target.getAttribute(‘data-bind‘); if( propName && propName !== ‘‘ ) { pubsub.pub(‘model.change‘, propName, target.value); } event.stopPropagation();}/*----------- Init --------------*/window.onload = function() { /* first step: * find controllers‘ dom */ var controllerRanges = document.querySelectorAll(‘[isi-controller]‘); /* second step: * bind listeners for each controllers‘ range, * view.change event --> change each controllers‘ range */ for(var i=0, len=controllerRanges.length; i<len; i++) { controllerRanges[i].addEventListener(‘change‘, changeHandler, false); // view.change event --> change view (function(index){ pubsub.sub(‘view.change‘, function(propName, newVal) { var elements = controllerRanges[index].querySelectorAll(‘[data-bind=‘ + propName +‘]‘), tagName; for(var i=0,l=elements.length; i<l; i++) { tagName = elements[i].tagName.toLowerCase(); if(tagName===‘input‘ || tagName===‘textarea‘ || tagName===‘select‘) { elements[i].value = newVal; } else { elements[i].innerHTML = newVal; } } }); })(i); } /* third step: * execute each controller function */ for(var i=0, len=controllerRanges.length; i<len; i++) { var controllerName = controllerRanges[i].getAttribute(‘isi-controller‘); eval(controllerName+‘()‘); }}
看看效果:
悲劇了。
沒有實現第二個初衷:子控制器不影響父控制器。
這個問題該怎樣解決呢?
版本號碼二,子不影響父
細緻看代碼,之所以會出現故障,是由於view.change通道的作用範圍是有問題的。無論哪個model發出的view.change事件,兩個控制器的view都會改變。
所以,我們給公布view.change事件的時候,多公布一個控制器名。好讓接收view.change的時候知道應不應該修改html:
var Model = function(controllerName) { var model = { controllerName:controllerName, props: {}, set: function(propName, value) { this.props[propName] = value; pubsub.pub(‘view.change‘, propName, value, this.controllerName); //就是這裡!
} }
控制器裡new Model的時候注意把controller的名字初始化進去:
ParentController.js:
function ParentController() { var model = new Model(‘ParentController‘); model.set(‘name‘, ‘parent‘);}
最後接收view.change通道訊息的時候,推斷下controllerName:
pubsub.sub(‘view.change‘, function(propName, newVal, controllerName) { .... thisControllerName = controllerRanges[index].getAttribute(‘isi-controller‘), if(thisControllerName !== controllerName) return; ....}
當然。監聽器公布model.change的時候也是一樣。要把控制器名稱公布出去。代碼就不貼了。
再看看效果:
妥了
全部代碼:
github/victorisildur
很easy的js雙向繫結架構(二):控制器繼承