使用過 JavaScript框架(如 AngularJS, Backbone 或Ember)的人都很熟悉在UI(使用者介面,前端)中mvc的工作機制。這些框架實現了MVC,使得在一個單一頁面中實現根據需要變化視圖時更加輕鬆,而模型-視圖-控制器(mvc)的核心概念是:處理傳入請求的控制器、顯示資訊的視圖、表示業務規則和資料存取的模型。
因此,當需要建立這樣一個需要在單一頁面中實作切換出不同內容的應用程式時,我們通常會選擇使用上述框架之一。但是,如果我們僅僅需要一個在一個url中實現視圖切換的框架,而不需要額外捆綁的功能的話,就不必使用象Angular和Ember等複雜的框架。本文就是嘗試使用簡單、有效方法來解決同樣的問題。
應用程式中的程式碼利用urls中的「#」實作MVC模式的導覽。應用以一個預設的url開始,基於哈希值的程式碼載入應用視圖並且將物件-模型應用於視圖模板。
url格式像下面這樣:
http://Domain Name/index.html#/Route Name
視圖內容必須以{{Property-Name}}的方式綁定物件模型的值和屬性。程式碼會找這個專門的模板格式並且代替物件模型中的屬性值。
以ajax的方式非同步載入的視圖會被放置在頁面的佔位符中。視圖佔位符可以是任何的元素(理想的情況是p),但是它必須有一個專門的屬性,程式碼根據這個專門的屬性來定位它,這樣同樣有助於程式碼的實作。當url改變時,會重複這個場景,另一個視圖被載入。聽起來很簡單吧!下面的流程圖解釋了在這個特定的實作中訊息的跳轉。
我們以基本的模組設計模式開始,並且最終以門面設計模式的方式將我們的libs曝光於全域範圍內。
; (function (w, d, undefined) { //rest of the code })(window, document);
我們需要將視圖元素儲存到一個變數中,這樣就可以多次使用。
var _viewElement = null; //element that will be used to render the view
我們需要一個缺省的路由來應對url中沒有路由資訊的情況,這樣就缺省的視圖就可以被載入而不是展示空白頁面。
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方法做如下的事情:
1)取得視圖相關的元素的初始化。程式碼需要一個具有view屬性的元素,這樣可以用來在HTML頁面中尋找:
2)設定預設的路由
#3)驗證視圖元素是否合理
4)绑定窗口哈希变更事件,当url不同哈希值发生变更时视图可以被及时更新
5)最后,启动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 。当哈希值变化时,这个代理都会被调用。下面就是当哈希值变化时我们做的操作的先后顺序:
1)获取哈希值
2)从哈希中获取路由值
3)从路由map对象_routeMap中获取路由对象routeObj
4)如果url中没有路由信息,需要获取缺省的路由对象
5)最后,调用跟这个路由有关的控制器并且为这个视图元素的视图提供服务
上面的所有步骤都被下面的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插件。在下一个代码段中,下面这些会实现:
1)在web页面中引入这个代码
2)用控制器添加路由信息和视图模板信息
3)创建控制器功能
4)最后,初始化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> <p 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> </p> <br /> <br /> <h3>View</h3> <p view></p> <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:
<p style="padding:2px;margin:2px;text-align:left;"> <label for="txtUserName">User Name</label> <input type="text" id="txtUserName" value="{{UserName}}" /> </p> <p style="padding:2px;margin:2px;text-align:left;"> <label for="txtPassword">Password</label> <input type="password" id="txtPassword" value="{{Password}}" /> </p>
完整的代码可以从给定的下载链接中得到。
运行该代码比较简单,需要在你喜欢的Web服务器上创建一个Web应用,下面以IIS为例来说明。
首先在默认站点中新增一个Web应用.
然后设置必填信息:别名,物理路径,应用池,用户认证信息,点击OK。
最后定位到Web应用的内容目录,浏览你想打开的HTML页面即可。
跑在服务器里是必要的,因为代码加载从存储于外部文件中的视图,浏览器不会允许我们的代码在非宿主服务器环境下执行。当然如果你使用Visual Studio那么直接在目标html文件上右键,选择‘View In Browser’即可。
大部分的现代浏览器都支持本代码。针对IE8及以下的浏览器,有一份单独的代码来支持,但很不幸,这份代码远多于100行。因此这代码不是百分百跨浏览器兼容的,所以当你决定在项目中使用时需要对代码进行微调。
This example demonstrates這個範例向我們展示了對於非常明確地需求來說,真沒必要全部使用js函式庫和框架來實作。 Web應用程式是資源密集的,最好只使用必要的程式碼而丟掉其他多餘部分。
目前的程式碼能做的就這些了。沒有諸如Web服務調用,動態事件綁定功能的。很快我會提供支援更多特性的升級版本。
Download JavaScript-Mvc.zip – 4.6 KB JavaScript Mvc on Github Live Demo
############################################################################################################11以上是實作JavaScript MVC樣式框架的簡單程式碼實例的詳細內容。更多資訊請關注PHP中文網其他相關文章!