使用過JavaScript框架(如AngularJS, Backbone 或者Ember)的人都很熟悉在UI(用戶界面,前端)中mvc的工做機理。這些框架實現了MVC,使得在一個單頁面中實現根據須要變化視圖時更加輕鬆,而模型-視圖-控制器(mvc)的核心概念就是:處理傳入請求的控制器、顯示信息的視圖、表示業務規則和數據訪問的模型。javascript
所以,當須要建立這樣一個須要在單個頁面中實現切換出不一樣內容的應用時,咱們一般選擇使用上述框架之一。可是,若是咱們僅僅須要一個在一個url中實現視圖切換的框架,而不須要額外捆綁的功能的話,就沒必要使用象Angular和Ember等複雜的框架。本文就是嘗試使用簡單、有效方法來解決一樣的問題。css
應用中的代碼利用urls中的「#」實現MVC模式的導航。應用以一個缺省的url開始,基於哈希值的代碼加載應用視圖而且將對象-模型應用於視圖模板。html
url格式像下面這樣:前端
http://**Domain Name**/index.html#/**Route Name**
視圖內容必須以{{Property-Name}}的方式綁定對象模型的值和屬性。代碼會查找這個專門的模板格式而且代替對象模型中的屬性值。java
以ajax的方式異步加載的視圖會被放置於頁面的佔位符中。視圖佔位符能夠是任何的元素(理想的狀況是div),可是它必須有一個專門的屬性,代碼根據這個專門的屬性來定位它,這樣一樣有助於代碼的實現。當url改變時,會重複這個場景,另一個視圖被加載。聽起來很簡單吧!下面的流程圖解釋了在這個特定的實現中的消息跳轉。git
咱們以基本的模塊設計模式開始,而且最終用門面設計模式的方式將咱們的libs曝光於全局範圍內。github
; (function (w, d, undefined) { //rest of the code })(window, document);
咱們須要將視圖元素存儲到一個變量中,這樣就能夠屢次使用。web
var _viewElement = null; //element that will be used to render the view
咱們須要一個缺省的路由來應對url中沒有路由信息的狀況,這樣就缺省的視圖就能夠被加載而不是展現空白頁面。ajax
var _defaultRoute = null;
如今咱們來建立咱們的主要MVC對象的構造方法。咱們會把路由信息存儲在「_routeMap」中設計模式
var jsMvc = function () { //mapping object for the routes this._routeMap = {}; }
是時候建立路由對象了,咱們會將路由、模板、控制器的信息存儲在這個對象中。
var routeObj = function (c, r, t) { this.controller = c; this.route = r; this.template = t; }
每個url會有一個專門的路由對象routeObj.全部的這些對象都會被添加到_routeMap對象中,這樣咱們後續就能夠經過key-value的方式獲取它們。
爲了添加路由信息到MVC libs中,咱們須要曝光libs中的一個方法。因此讓咱們建立一個方法,這個方法能夠被各自的控制器用來添加新路由。
jsMvc.prototype.AddRoute = function (controller, route, template) { this._routeMap[route] = new routeObj(controller, route, template); }
方法AddRoute接收3個參數:控制器,路由和模板(contoller, route and template)。他們分別是:
controller:控制器的做用就是訪問特定的路線。
route:路由的路線。這個就是url中#後面的部分。
template:這是外部的html文件,它做爲這個路由的視圖被加載。如今咱們的libs須要一個切入點來解析url,而且爲相關聯的html模板頁面提供服務。爲了完成這個,咱們須要一個方法。
Initialize方法作以下的事情:
獲取視圖相關的元素的初始化。代碼須要一個具備view屬性的元素,這樣能夠被用來在HTML頁面中查找:
設置缺省的路由
驗證視圖元素是否合理
綁定窗口哈希變動事件,當url不一樣哈希值發生變動時視圖能夠被及時更新
最後,啓動mvc
//Initialize the Mvc manager object to start functioning
jsMvc.prototype.Initialize = function () {
var startMvcDelegate = startMvc.bind(this);
//get the html element that will be used to render the view _viewElement = d.querySelector('[view]'); if (!_viewElement) return; //do nothing if view element is not found //Set the default route _defaultRoute = this._routeMap[Object.getOwnPropertyNames(this._routeMap)[0]]; //start the Mvc manager w.onhashchange = startMvcDelegate; startMvcDelegate();
}
在上面的代碼中,咱們從startMvc方法中建立了一個代理方法startMvcDelegate。當哈希值變化時,這個代理都會被調用。下面就是當哈希值變化時咱們作的操做的前後順序:
獲取哈希值
從哈希中獲取路由值
從路由map對象_routeMap中獲取路由對象routeObj
若是url中沒有路由信息,須要獲取缺省的路由對象
最後,調用跟這個路由有關的控制器而且爲這個視圖元素的視圖提供服務
上面的全部步驟都被下面的startMvc方法所實現
//function to start the mvc support function startMvc() { var pageHash = w.location.hash.replace('#', ''), routeName = null, routeObj = null; routeName = pageHash.replace('/', ''); //get the name of the route from the hash routeObj = this._routeMap[routeName]; //get the route object //Set to default route object if no route found if (!routeObj) routeObj = _defaultRoute; loadTemplate(routeObj, _viewElement, pageHash); //fetch and set the view of the route }
下一步,咱們須要使用XML HTTP請求異步加載合適的視圖。爲此,咱們會傳遞路由對象的值和視圖元素給方法loadTemplate。
//Function to load external html data function loadTemplate(routeObject, view) { var xmlhttp; if (window.XMLHttpRequest) { // code for IE7+, Firefox, Chrome, Opera, Safari xmlhttp = new XMLHttpRequest(); } else { // code for IE6, IE5 xmlhttp = new ActiveXObject('Microsoft.XMLHTTP'); } xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { loadView(routeObject, view, xmlhttp.responseText); } } xmlhttp.open('GET', routeObject.template, true); xmlhttp.send(); }
當前只剩加載視圖和將對象模型與視圖模板綁定了。咱們會建立一個空的模型對象,而後傳遞與方法相關的模型來喚醒路由控制器。更新後的模型對象會與先前已經加載的XHR調用中的HTML模板綁定。
loadView方法被用於調用控制器方法,以及準備模型對象。
replaceToken方法被用於與HTML模板一塊兒綁定模型
//Function to load the view with the template function loadView(routeObject, viewElement, viewHtml) { var model = {}; //get the resultant model from the controller of the current route routeObject.controller(model); //bind the model with the view viewHtml = replaceToken(viewHtml, model); //load the view into the view element viewElement.innerHTML = viewHtml; } function replaceToken(viewHtml, model) { var modelProps = Object.getOwnPropertyNames(model), modelProps.forEach(function (element, index, array) { viewHtml = viewHtml.replace('{{' + element + '}}', model[element]); }); return viewHtml; }
最後,咱們將插件曝光於js全局範圍外
//attach the mvc object to the window w['jsMvc'] = new jsMvc();
如今,是時候在咱們單頁應用中使用這個MVC插件。在下一個代碼段中,下面這些會實現:
在web頁面中引入這個代碼
用控制器添加路由信息和視圖模板信息
建立控制器功能
最後,初始化lib。
除了上面咱們須要的連接讓咱們導航到不一樣的路徑外,一個容器元素的視圖屬性包含着視圖模板html。
<!DOCTYPE html> <html> <head> <title>JavaScript Mvc</title> <script src="jsMvc.js"></script> <!--[if lt IE 9]> <script src="jsMvc-ie8.js"></script> <![endif]--> <style type="text/css"> .NavLinkContainer { padding: 5px; background-color: lightyellow; } .NavLink { background-color:black; color: white; font-weight:800; text-decoration:none; padding:5px; border-radius:4px; } .NavLink:hover { background-color:gray; } </style> </head> <body> <h3>Navigation Links</h3> <div class="NavLinkContainer"> <a class="NavLink" href="index.html#/home">Home</a> <a class="NavLink" href="index.html#/contact">Contact</a> <a class="NavLink" href="index.html#/admin">Admin</a> </div> <br /> <br /> <h3>View</h3> <div view></div> <script> jsMvc.AddRoute(HomeController, 'home', 'Views/home.html'); jsMvc.AddRoute(ContactController, 'contact', 'Views/contact.html'); jsMvc.AddRoute(AdminController, 'admin', 'Views/admin.html'); jsMvc.Initialize(); function HomeController(model) { model.Message = 'Hello World'; } function ContactController(model) { model.FirstName = "John"; model.LastName = "Doe"; model.Phone = '555-123456'; } function AdminController(model) { model.UserName = "John"; model.Password = "MyPassword"; } </script> </body> </html>
上面的代碼有一段包含一個爲IE的條件註釋。
<!--[if lt IE 9]> <script src="jsMvc-ie8.js"></script> <![endif]-->
若是IE的版本低於9,那麼function.bind,Object.getOwnPropertyNames和Array.forEach屬性將不會被支持。所以咱們要經過判斷瀏覽器是否低於IE9來反饋代碼是否支持。
其中的內容有home.html, contact.html 和 admin.html 請看下面:
home.html:
{{Message}}
contact.html:
{{FirstName}} {{LastName}} <br /> {{Phone}}
admin.html:
<div style="padding:2px;margin:2px;text-align:left;"> <label for="txtUserName">User Name</label> <input type="text" id="txtUserName" value="{{UserName}}" /> </div> <div style="padding:2px;margin:2px;text-align:left;"> <label for="txtPassword">Password</label> <input type="password" id="txtPassword" value="{{Password}}" /> </div>
完整的代碼能夠從給定的下載連接中獲得。
運行該代碼比較簡單,須要在你喜歡的Web服務器上建立一個Web應用,下面以IIS爲例來講明。
首先在默認站點中新增一個Web應用.
而後設置必填信息:別名,物理路徑,應用池,用戶認證信息,點擊OK。
最後定位到Web應用的內容目錄,瀏覽你想打開的HTML頁面便可。
跑在服務器裏是必要的,由於代碼加載從存儲於外部文件中的視圖,瀏覽器不會容許咱們的代碼在非宿主服務器環境下執行。固然若是你使用Visual Studio那麼直接在目標html文件上右鍵,選擇‘View In Browser’便可。
大部分的現代瀏覽器都支持本代碼。針對IE8及如下的瀏覽器,有一份單獨的代碼來支持,但很不幸,這份代碼遠多於100行。所以這代碼不是百分百跨瀏覽器兼容的,因此當你決定在項目中使用時須要對代碼進行微調。
This example demonstrates這個示例向咱們展現了對於很是明確地需求來講,真不必所有使用js庫和框架來實現。Web應用是資源密集型的,最好只使用必要的代碼而丟掉其餘多餘部分。
目前的代碼能作的就這些了。沒有諸如Web服務調用,動態事件綁定功能的。很快我會提供支持更多特性的升級版本。
英文原文:Nitij - JavaScript MVC Style Framework in Less Than 100 Lines of Code
譯文出處:100 行代碼實現的 JavaScript MVC 樣式框架
譯者:妖怪姐, jingxing05, 無若, 船老大