100 行代码实现的 JavaScript MVC 样式框架
介绍
使用过 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的方式异步加载的视图会被放置于页面的占位符中。视图占位符可以是任何的元素(理想的情况是div),但是它必须有一个专门的属性,代码根据这个专门的属性来定位它,这样同样有助于代码的实现。当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>
<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服务调用,动态事件绑定功能的。很快我会提供支持更多特性的升级版本。
原文地址:http://www.codeproject.com/Articles/869488/JavaScript-MVC-Style-Framework-in-Less-Than-Lines
100 行代码实现的 JavaScript MVC 样式框架的更多相关文章
- 100行代码让您学会JavaScript原生的Proxy设计模式
面向对象设计里的设计模式之Proxy(代理)模式,相信很多朋友已经很熟悉了.比如我之前写过代理模式在Java中实现的两篇文章: Java代理设计模式(Proxy)的四种具体实现:静态代理和动态代理 J ...
- 100行代码实现现代版Router
原文:http://www.html-js.com/article/JavaScript-version-100-lines-of-code-to-achieve-a-modern-version ...
- 【转】100行代码实现最简单的基于FFMPEG+SDL的视频播放器
FFMPEG工程浩大,可以参考的书籍又不是很多,因此很多刚学习FFMPEG的人常常感觉到无从下手.我刚接触FFMPEG的时候也感觉不知从何学起. 因此我把自己做项目过程中实现的一个非常简单的视频播放器 ...
- 用JavaCV改写“100行代码实现最简单的基于FFMPEG+SDL的视频播放器 ”
FFMPEG的文档少,JavaCV的文档就更少了.从网上找到这篇100行代码实现最简单的基于FFMPEG+SDL的视频播放器.地址是http://blog.csdn.net/leixiaohua102 ...
- 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)【转】
转自:http://blog.csdn.net/leixiaohua1020/article/details/8652605 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] ...
- GuiLite 1.2 发布(希望通过这100+行代码来揭示:GuiLite的初始化,界面元素Layout,及消息映射的过程)
经过开发群的长期验证,我们发现:即使代码只有5千多行,也不意味着能够轻松弄懂代码意图.痛定思痛,我们发现:虽然每个函数都很简单(平均长度约为30行),可以逐个击破:但各个函数之间如何协作,却很难说明清 ...
- 【编程教室】PONG - 100行代码写一个弹球游戏
大家好,欢迎来到 Crossin的编程教室 ! 今天跟大家讲一讲:如何做游戏 游戏的主题是弹球游戏<PONG>,它是史上第一款街机游戏.因此选它作为我这个游戏开发系列的第一期主题. 游戏引 ...
- 100行代码实现HarmonyOS“画图”应用,eTS开发走起!
本期我们给大家带来的是"画图"应用开发者Rick的分享,希望能给你的HarmonyOS开发之旅带来启发~ 介绍 2021年的华为开发者大会(HDC2021)上,HarmonyOS ...
- 100行代码搞定抖音短视频App,终于可以和美女合唱了。
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由视频咖 发表于云+社区专栏 本文作者,shengcui,腾讯云高级开发工程师,负责移动客户端开发 最近抖音最近又带了一波合唱的节奏,老 ...
随机推荐
- CentOS编译安装vim
#删除系统自带的vim rpm -qa | grep vim rpm -e vim-X11-7.4.629-5.el6_8.1.x86_64 vim-filesystem-7.4.629-5.el6_ ...
- Golang之字符串格式化
字符串格式化 // Go 之 字符串格式化 // // Copyright (c) 2015 - Batu // package main import ( "fmt" ) typ ...
- JavaWEB springmvc 使用定时任务
1.配置web.xml 在web.xml配置使用springmvc框架,其他配置略. <display-name>xxx.com</display-name> <!-- ...
- 行为类模式(十):模板方法(Template Method)
定义 定义一个操作中的算法的骨架,而将步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤. UML 优点 模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复 ...
- centos安装man中文手册
第一步下载man中文手册压缩包 //下载 wget http://pkgs.fedoraproject.org/repo/pkgs/man-pages-zh-CN/manpages-zh-1.5.1. ...
- Theories of Deep Learning
https://stats385.github.io/readings Lecture 1 – Deep Learning Challenge. Is There Theory? Readings D ...
- (转)CTP: 平昨仓与平今仓,log轻轻告诉你.......
转自:http://blog.csdn.net/wowotuo/article/details/43242663 CTP的相关文档告诉我们,中金所和三大商品交易所中,只有上期所区分平今仓和平昨仓.也就 ...
- 【Qt】qt库结构及示例
QT库结构 Qt图形库是一个组织严谨的C++类库,其结构如图所示 细说Qt库 Qt类库中包含了上百个类,结构十分复杂,上图展示了Qt_3.2类库的基本结构. Qt类库中的类可以分成两种类型: 一种是直 ...
- 大量的源文件添加到Android.mk的问题
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := game_shared LOCAL_MODULE_FILENAME ...
- Python实现堆数据结构
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/3/18 19:47 # @Author : baoshan # @Site ...