超级简单实现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){//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对象中,这样我们后续就可以通过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服务调用,动态事件绑定功能的。很快我会提供支持更多特性的升级版本。

以上所述就是本文的全部内容了,希望能对大家熟练掌握javascript有所帮助。

(0)

相关推荐

  • Javascript MVC框架Backbone.js详解

    随着JavaScript程序变得越来越复杂,往往需要一个团队协作开发,这时代码的模块化和组织规范就变得异常重要了.MVC模式就是代码组织的经典模式. (--MVC介绍.) (1)Model Model表示数据层,也就是程序需要的数据源,通常使用JSON格式表示. (2)View View表示表现层,也就是用户界面,对于网页来说,就是用户看到的网页HTML代码. (3)Controller Controller表示控制层,用来对原始数据(Model)进行加工,传送到View. 由于网页编程不同于客

  • 前端轻量级MVC框架CanJS详解

    选择正确的库 创建一个JS APP没有好的工具是很有难度的,jQuery只是操作DOM的库,没有提供任何创建APP的基础,这就是为什么我们要一个类似CanJS的专门的库. CanJS 是一个轻量级的MVC库,提供你创建一个JS APP所需的工具. CanJS 是一个轻量级的MVC库,提供你创建一个JS APP所需的工具. 它提供有MVC (Model-View-Control) 模式的基本框架,模板动态绑定, route的支持且 内存安全.同时支持 jQuery, Zepto, Mootools

  • Angularjs中的事件广播 —全面解析$broadcast,$emit,$on

    Angularjs中不同作用域之间可以通过组合使用$broadcast,$emit,$on的事件广播机制来进行通信 介绍: $broadcast的作用是将事件从父级作用域传播至子级作用域,包括自己.格式如下:$broadcast(eventName,args) $emit的作用是将事件从子级作用域传播至父级作用域,包括自己,直至根作用域.格式如下:$emit(eventName,args) $on用于在作用域中监控从子级或父级作用域中传播的事件以及相应的数据.格式如下:$on(event,dat

  • 讲解JavaScript的Backbone.js框架的MVC结构设计理念

    什么是Backbone.js? Backbone.js是十大JS框架之首,Backbone.js 是一个重量级js  MVC 应用框架,也是js MVC框架的鼻祖.它通过Models数据模型进行键值绑定及custom事件处理,通过模型集合器Collections提供一套丰富的API用于枚举功能,通过视图Views来进行事件处理及与现有的Application通过JSON接口进行交互. 简而言之,Backbone是实现了web前端MVC模式的js库 什么是MVC? MVC:后端服务器首先(过程1)

  • 12种JavaScript常用的MVC框架比较分析

    本文详细讲述了12种JavaScript常用的MVC框架.分享给大家供大家参考,具体如下: Gordon L. Hempton是西雅图的一位黑客和设计师,他花费了几个月的时间研究和比较了12种流行的JavaScript MVC框架,并在博客中总结了每种框架的优缺点,最终的结果是,Ember.js胜出. 此次比较针对的特性标准有四种,分别是: ① UI绑定(UI Bindings) ② 复合视图(Composed Views) ③ Web表现层(Web Presentation Layer) ④

  • 使用jQuery.form.js/springmvc框架实现文件上传功能

    使用的技术有jquery.form.js框架, 以及springmvc框架.主要实现异步文件上传的同时封装对象,以及一些注意事项. 功能本身是很简单的,但是涉及到一些传递参数类型的问题.例如:jquery的ajax方法与jquery.form.js中的ajaxSubmit方法的参数,具体细节将在下一篇博客中分享. 重点: html表格三要素: action="fileUpload/fileUpload" method="post" enctype="mul

  • jquery.form.js框架实现文件上传功能案例解析(springmvc)

    上一篇 Bootstrap自定义文件上传下载样式(http://www.jb51.net/article/85156.htm)已经有一段时间了,一直在考虑怎么样给大家提交一篇完美的逻辑处理功能.现在我结合自己的实际工作给大家分享一下. 使用的技术有jquery.form.js框架, 以及springmvc框架.主要实现异步文件上传的同时封装对象,以及一些注意事项. 功能本身是很简单的,但是涉及到一些传递参数类型的问题.例如:jquery的ajax方法与jquery.form.js中的ajaxSu

  • Extjs4.1.x 框架搭建 采用Application动态按需加载MVC各模块完美实现

    中午的时候发了第一篇 Extjs4.1.x 框架搭建 采用Application动态按需加载MVC各模块 ,发现实现上还是有问题,有很多理解不到位的地方,晚上详细解决下了,终于实现MVC各模块按需加载了,哈皮. 上篇文章中,关于ExtJs这个玩意的评论就跟java和.Net那个好一样,既然上了贼船,就难下了,而且对于企业级的应用我个人觉得Extjs框架还是不错的,尤其是没有UI设计的团队(苦逼的程序员就兼UI吧),起码难得发现一个做的比较好的UI框架(国产的miniUI貌似看的过去,其他的就有点

  • AngularJs Javascript MVC 框架

    在6月google发布了AngularJs 1.0稳定版, 并宣称:AngularJS可以让你扩展HTML的语法,以便清晰.简洁地表示应用程序中的组件,并允许将标准的HTML作为你的模板语言,AngularJS可以通过双向数据绑定自动从拥有JavaScript对 象(模型)的UI(视图)中同步数据. 开始接触AngularJs是在4月份来到新项目组,这时AngularJs还处于0.8未稳定版,项目中已经开始使用了,并且这套框架应用到了项目整个UI端,服务端也是未稳定的web api,真心佩服团队

  • AngularJs解决跨域问题案例详解(简单方法)

    首先我们做点准备说明,不然你明白我说的是啥意思别人不明白,就算别人明白了那总有人不明白,那你要说了,我的意思是这个说明必须要做了,答案是必须的,为了更好的方便大家理解嘛. 我们以两个主域名或者一个主域名+一个二级域名为例,均可演示跨域问题. 客户端 a.com 服务端 b.com或者s.a.com angularJs版本 V1.2.25 准备工作做得很充分嘛,就差把我们的编辑器是subline暴露出来了,这个一般人我是不告诉他滴. 有人嚷嚷了,这问题老早就有了,你现在提出来有啥意义呢?难不成你还

  • indexedDB bootstrap angularjs之 MVC DOMO (应用示例)

    1.indexedDB(Model) -- 前端浏览器对象型数据库,一般我们后台用的数据库都是关系型数据库.那么indexeddb有什么特点呢: 首先,从字义上它是索引型数据库,从实际使用中可以体现为,它需要为表创建索引才可以根据某个字段来获取数据,而在关系型数据库中,这明显是不需要的. 其次,我不需要行列数据的转化,我只需要通过类似于数组的处理方式: 复制代码 代码如下: objectStore.push(data); 有点像是把一个json对象塞入数据库,是不是很暴力? 2.bootstra

随机推荐