100 行代码实现的 JavaScript MVC 样式框架

介绍

使用过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

clipboard.png

写代码

咱们以基本的模块设计模式开始,而且最终用门面设计模式的方式将咱们的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, &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}}

clipboard.png

contact.html:

{{FirstName}} {{LastName}}
<br />
{{Phone}}

clipboard.png

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>

clipboard.png

完整的代码能够从给定的下载连接中获得。

如何运行代码

运行该代码比较简单,须要在你喜欢的Web服务器上建立一个Web应用,下面以IIS为例来讲明。

首先在默认站点中新增一个Web应用.

clipboard.png

而后设置必填信息:别名,物理路径,应用池,用户认证信息,点击OK。

clipboard.png

最后定位到Web应用的内容目录,浏览你想打开的HTML页面便可。

clipboard.png

跑在服务器里是必要的,由于代码加载从存储于外部文件中的视图,浏览器不会容许咱们的代码在非宿主服务器环境下执行。固然若是你使用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, 无若, 船老大

相关文章
相关标签/搜索