介绍

  使用过 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, &apos;home&apos;, &apos;Views/home.html&apos;);
        jsMvc.AddRoute(ContactController, &apos;contact&apos;, &apos;Views/contact.html&apos;);
        jsMvc.AddRoute(AdminController, &apos;admin&apos;, &apos;Views/admin.html&apos;);
        jsMvc.Initialize();         function HomeController(model) {
            model.Message = &apos;Hello World&apos;;
        }         function ContactController(model) {
            model.FirstName = "John";
            model.LastName = "Doe";
            model.Phone = &apos;555-123456&apos;;
        }         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

js mvc框架的更多相关文章

  1. 全端开发必备!10个最好的 Node.js MVC 框架

      Node.js 是最流行的 JavaScript 服务端平台,它允许建立可扩展的 Web 应用程序.Node.js 包含不同类型的框架,如 MVC 框架.全栈框架.REST API  以及大量的服 ...

  2. Mithril – 构建杰出 Web 应用的 JS MVC 框架

    Mithril 是一个客户端的 Javascript MVC 框架.它是一个工具,使应用程序代码分为数据层,UI 层和粘合层.提供了一个模板引擎与一个虚拟的 DOM diff 实现,用于高性能渲染,支 ...

  3. 【360开源】thinkjs:基于Promise的Node.js MVC框架 (转)

    thinkjs是360奇舞团开源的一款Node.js MVC框架,该框架底层基于Promise来实现,很好的解决了Node.js里异步回调的问题.360奇舞团(奇虎75Team),是奇虎360公司We ...

  4. 十款最佳Node.js MVC框架

    十款最佳Node.js MVC框架摘要:Node.js是JavaScript中最为流行的框架之一,易于创建可扩展的Web应用.本文分享十款最佳的JavaScript框架. Node.js是JavaSc ...

  5. IceMx.Mvc 我的js MVC 框架 开篇

    开篇 这篇文章是后补的,前端时间想写一些对于js开发的一些理解,就直接写了,后来发现很唐突,所以今天在这里补一个开篇. 我的js Mvc 框架 基于实用设计,过分设计等于没设计.本着简单的原则,它只实 ...

  6. IceMx.Mvc 我的js MVC 框架 一、html代码的分离(视图)

    介绍 本人菜鸟,一些自己的浅薄见解,望各位大神指正. 本框架有以下优点 1.简单(调用简单.实现简单.不过度设计) 2.视图.控制器.模型分离(分离对于维护十分有必要) 3.组件化(每一个mvc模块儿 ...

  7. IceMx.Mvc 我的js MVC 框架 三、动手来写一个评论模块儿

    介绍 本人菜鸟,一些自己的浅薄见解,望各位大神指正. 本框架有以下优点 1.简单(调用简单.实现简单.不过度设计) 2.视图.控制器.模型分离(分离对于维护十分有必要) 3.组件化(每一个mvc模块儿 ...

  8. IceMx.Mvc 我的js MVC 框架五、完善植物大战僵尸(雏形版增加动画)

    有图有真相 说明 实在找不到僵尸的素材,从网上扒了一个山寨的僵尸,只有4张的一个连续图片,所以动作有点僵硬,植物的图片是自己处理的,非专业所以……咳咳!. 背景是自己抠下来2块儿拼接的,看在这么辛苦的 ...

  9. 私人定制,十款最佳Node.js MVC框架

    Node.js是JavaScript中最为流行的框架之一,易于创建可扩展的Web应用.本文分享十款最佳的JavaScript框架. Node.js是JavaScript中最为流行的框架之一,易于创建可 ...

随机推荐

  1. 爬虫之pyquery库

    官方文档:https://pyquery.readthedocs.io/en/latest/ PyQuery是一个强大又灵活的网页解析库.如果你觉得正则写起来太麻烦.BeautifulSoup语法太难 ...

  2. [bzoj4027][HEOI2015][兔子与樱花] (树形dp思想+玄学贪心)

    Description 很久很久之前,森林里住着一群兔子.有一天,兔子们突然决定要去看樱花.兔子们所在森林里的樱花树很特殊.樱花树由n个树枝分叉点组成,编号从0到n-1,这n个分叉点由n-1个树枝连接 ...

  3. [转] 探讨JS合并两个数组的方法

    我们在项目过程中,有时候会遇到需要将两个数组合并成为一个的情况. 比如: 1 2 var a = [1,2,3]; var b = [4,5,6]; 有两个数组a.b,需求是将两个数组合并成一个.方法 ...

  4. noip模拟赛 猜数字

    题目描述 LYK在玩猜数字游戏. 总共有n个互不相同的正整数,LYK每次猜一段区间的最小值.形如[li,ri]这段区间的数字的最小值一定等于xi. 我们总能构造出一种方案使得LYK满意.直到…… LY ...

  5. noip模拟赛 捡金币

    问题描小空正在玩一个叫做捡金币的游戏.游戏在一个被划分成 n行 n列的网格状场地中进行.每一个格子中都放着若干金币,并且金币的数量会随着时间而不断变化. 小空的任务就是在网格中移动,拾取尽量多的金币. ...

  6. 如何高效读写百万级的Excel?

    高效读取百万级数据 接上一篇介绍的高效写文件之后,最近抽时间研究了下Excel文件的读取.概括来讲,poi读取excel有两种方式:用户模式和事件模式. 然而很多业务场景中的读取Excel仍然采用用户 ...

  7. centos7 mysql安装与用户设置

    1.环境:Centos 7.0 64位2.mysql版本:5.73.安装:https://dev.mysql.com/doc/refman/5.7/en/installing.html3.1.创建my ...

  8. hust 1017

    题意:求01矩阵的精确覆盖. 分析:本来想学习dancing links来解决数独问题,发现dancing links最初解决的问题是精确覆盖,于是就找到这道题来做了.这种NPC问题只能用DFS暴搜的 ...

  9. NetCore实现全局异常捕捉统一处理

    做net项目时候,在Global.asax文件中可以通过Application_Error方法全局捕获异常并处理后统一跳转到自定义的错误页面. 下面是我个人在NetCore项目中实现全局捕获异常并统一 ...

  10. QSettings读写注冊表、配置文件

    简述 普通情况下.我们在开发软件过程中,都会缓存一些信息到本地,能够使用轻量级数据库sqlite.也能够操作注冊表.读写配置文件. 关于QSettings的使用前面已经介绍过了.比較具体,见" ...