本文介绍了一种基于URL哈希值的轻量级JavaScript MVC框架实现方案。通过解析URL中的路由信息,利用XMLHttpRequest异步加载外部HTML模板,并将对象模型属性绑定至视图中的特定占位符,从而实现无需重型框架的单页面应用视图切换。
介绍
介绍
熟悉AngularJS、Backbone或Ember等JavaScript框架的开发者,对MVC(模型-视图-控制器)在用户界面中的运作机制应当不陌生。这些框架通过实现MVC架构,简化了单页面应用中视图随需求动态变化的过程。MVC的核心概念包括:负责处理传入请求的控制器、负责展示信息的视图,以及封装业务规则与数据访问的模型。
因此,当开发需要在单个页面内切换不同内容的应用时,通常倾向于选择上述成熟框架。然而,如果仅需实现基于单一URL的视图切换,且不需要框架附带的大量额外功能,使用Angular或Ember等复杂框架显得过于冗余。本文旨在探讨一种简单且有效的方法来解决这一特定需求。
概念
概念
该应用的代码利用URL中的“#”(哈希值)来实现MVC模式下的导航机制。应用启动时默认指向一个初始URL,系统根据URL中的哈希值加载对应的应用视图,并将对象模型数据应用于视图模板中。
URL的格式大致如下:
http://Domain Name/index.html#/Route Name
视图内容需通过{{Property-Name}}的格式绑定对象模型的值和属性。代码会识别这种特定的模板格式,并用对象模型中对应的属性值进行替换。
通过Ajax异步加载的视图内容将被放置在页面的占位符中。视图占位符可以是任意HTML元素(理想情况下为div),但它必须包含一个特定的属性,代码依据该属性来定位占位符,这也有助于代码逻辑的实现。当URL改变时,此过程会重复执行,加载另一个视图。这一机制听起来十分简单,下方的流程图解释了在此特定实现中的消息跳转逻辑。

代码实现
写代码
我们从基本的模块设计模式开始,最终通过门面设计模式将我们的库(libs)暴露给全局范围。
;(function(w,d,undefined){//restofthecode})(window,document);
我们需要将视图元素存储到一个变量中,以便在多处复用。
var_viewElement=null;//elementthatwillbeusedtorendertheview
我们需要设置一个默认路由,以应对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对象中,以便后续通过键值对方式获取它们。
为了将路由信息添加到MVC库中,我们需要暴露库中的一个方法。因此,让我们创建一个方法,该方法可被各自的控制器用来添加新路由。
jsMvc.prototype.AddRoute = function (controller, route, template) {
this._routeMap[route] = new routeObj(controller, route, template);
}
方法AddRoute接收3个参数:控制器、路由和模板(controller, route and template)。它们分别是:
controller:控制器的作用是访问特定的路线。
route:路由的路径。即URL中#符号后面的部分。
template:这是外部HTML文件,作为该路由的视图被加载。现在我们的库需要一个切入点来解析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)从路由映射对象_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)最后,初始化库。
除了上面我们需要的链接让我们导航到不同的路径外,一个容器元素的视图属性包含着视图模板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服务调用,动态事件绑定功能的。很快我会提供支持更多特性的升级版本。
-
Download JavaScript-Mvc.zip - 4.6 KB
-
JavaScript Mvc on Github
-
Live Demo
原文地址:http://www.codeproject.com/Articles/869488/JavaScript-MVC-Style-Framework-in-Less-Than-Lines
以上所述就是本文的全部内容了,希望能对大家熟练掌握javascript有所帮助。