Singal Page App:使用Knockout和RequireJS创建高度模块化的单页应用引擎
Singal Page App
开篇扯淡
距离上一篇文章已经有好几个月,也不是没有时间记录点东西,主要是换了新的工作,在一家外资工作,目前的工作内容大多都是前端开发,新接触的东西因为时间原因,大多还不成体系,所以这么长时间什么都没记录下来,也正是因为新的工作内容,才有了今天这篇文章。
这篇文章是我自己的博客项目的前端重写,因为目前ASP.NET API和单页应用的流行,结合目前工作中用到的东西,我决定把我的博客项目的前端部分整个重写,(以前的就是一坨…)
步入正题
背景知识
RequireJS http://www.requirejs.org/
Knockout http://knockoutjs.com/
BootStrap http://getbootstrap.com/
PubSubJS https://github.com/mroderick/PubSubJS
如果没有接触过的朋友请自行谷歌百度吧,就不浪费口水,额,键盘啦,什么,没有jQuery,呵呵,呵呵,正如Knockout官方文档里说的,Everyoue loves jquery。
RequireJS我用来做模块加载器,Knockout做MVVM分离也是爽到没朋友(谁用谁知道),Bootstrap搭建界面布局,PubSub,看着名字就知道啦。
文档结构

Libs:放置上文中提到的各种框架和工具;
App:主要的工作目录,articleList、catalog、articleViewer分别代表整个前端应用中的一个组件,对应的.html文件是他们自身的视图模板;
Utilities:存放一些工具类,如检测设备、格式化Url和字符串等;
Layout:只有一个文件,存放了整个前端应用的模板,可以通过更改这个文件,来改变各个组件的表现形式。
服务端API准备
为这个示例,我只准备了三个服务端API:
GetCatalog 得到文章类型目录:
namespace MiaoBlog.Controllers.API
{
public class CatalogController:ApiController
{
public IEnumerable<CategoryView> Get()
{
GetAllCategoriesResponse response = articleCatalogService.GetAllCategories();
return response.Categories;
}
}
}
GetArticlesByCategory 根据类型ID和页面编号获取文章目录,
GetArticle 根据文章ID获取文章内容等信息:
namespace MiaoBlog.Controllers.API
{
public class ArticlesController:ApiController
{
public IEnumerable<ArticleSummaryView> GetArticlesByCategory(int categoryId, int pageNumber)
{
GetArticlesByCategoryRequest request = GenerateArticlesByCategoryRequestFrom(categoryId, pageNumber-1);
GetArticlesByCategoryResponse response = articleCatalogService.GetArticlesByCategory(request);
return response.Articles;
} public ArticleDetailPageView GetArticle(int id)
{
ArticleDetailPageView detailView = new ArticleDetailPageView();
GetArticleRequest request = new GetArticleRequest() { ArticleId = id };
GetArticleResponse response = articleCatalogService.GetArticle(request);
ArticleView articleView = response.Article;
detailView.Article = articleView;
detailView.Comments = response.Comments;
detailView.Tags = response.Tags;
return detailView;
} private static GetArticlesByCategoryRequest GenerateArticlesByCategoryRequestFrom(int categoryId, int index)
{
GetArticlesByCategoryRequest request = new GetArticlesByCategoryRequest();
request.NumberOfResultsPerPage = int.Parse(ApplicationSettingsFactory.GetApplicationSettings().NumberOfResultsPerPage);
request.Index = index;
request.CategoryId = categoryId;
request.ExcerptLength = int.Parse(ApplicationSettingsFactory.GetApplicationSettings().ExcerptLength);
return request;
}
}
}
Require配置与系统配置
这里我用到的Require的几个常用插件:domReady、css、text.
paths配置了引用的js的别称:
paths:{
'lib/jquery': './Libs/jquery-1.11.1',
'lib/underscore': './Libs/underscore',
'lib/unserscore/string': './Libs/underscore.min',
'lib/backbone':'./Libs/backbone',
'lib/backbone/eproxy':'./Libs/backbone.eproxy',
'lib/backbone/super': './Libs/backbone.super',
'lib/pubsub': './Libs/pubsub',
'r/css': './Libs/css',
'r/less': './Libs/less',
'r/text': './Libs/text',
'r/domReady': './Libs/domReady',
'r/normailize': './Libs/normalize',
'pubsub': './Libs/pubsub',
'lib/ko': './Libs/knockout-3.2.0',
'utility': './Utilities/utility',
'util/matrix2d': './Utilities/matrix2d',
'util/meld':'./Utilities/meld',
'lib/bootstrap': './Libs/bootstrap-3.2.0/dist/js/bootstrap',
'lib/bootstrap/css': './Libs/bootstrap-3.2.0/dist/css/'
},
shim的配置略过;
然后就是require的调用入口了,从这里启动整个前端应用:
require(['lib/jquery', 'r/domReady', 'lib/underscore', 'config', 'r/text!Layout/template.html', 'r/css!lib/bootstrap/css/bootstrap.css', 'lib/bootstrap', ], function ($, domReady, _, config, template) {
domReady(function () {
var rootContainer = $("body").find("[data-container='root']");
var oTemplate=$(template);
var modules = $("[data-module]",oTemplate);
_.each(modules, function (module, index) {
require(["App/" + $(module).attr("data-module")], function (ModuleClass) {
var combineConfig = _.defaults(config[$(module).attr("data-module")], config.application);
var oModule = new ModuleClass(combineConfig);
oModule.load();
oModule.render(modules[index]);
});
});
rootContainer.append(oTemplate);
});
});
这里看到了template.html通过r/text引入,上文中提到过,它就是整个应用程序的模板文件,先看一下它的结构我再接着解释代码内容:
<div class="container">
<div class="row">
<div class="col-lg-3" data-module="catalog"></div>
<div class="col-lg-9" data-module="articleList"></div>
</div>
<div data-module="articleViewer"></div>
</div>
define(function () {
return {
application: {
Events: {
SWITCH_CATEGORY:"Miaoblog_Switch_Category",
OPEN_ARTICLE:"Miaoblog_Open_Article"
}
},
catalog: {
APIs: {
GetCatalog: {
Url: "http://localhost:15482/api/Catalog"
}
}
},
articleList: {
APIs: {
GetArticleList: {
Url: "http://localhost:15482/api/Articles",
ParamsFormat: "categoryId={0}&pageNumber={1}"
}
}
},
articleViewer: {
APIs: {
GetArticle: {
Url:"http://localhost:15482/api/Articles/{0}"
}
}
}
};
});
模块中的工作
就已catalog模块为例,先贴上代码,再做解释:
/// <reference path="../Libs/require.js" />
define(['lib/jquery', 'lib/ko', 'lib/underscore','pubsub', 'r/text!App/catalogList.html'],
function ($, ko,_, hub,template) {
var app = function (config) {
this.catalogList = null;
this.oTemplate = $(template);
this.config = config;
} _.extend(app.prototype, {
load: function () {
var self = this;
$.ajax({
type: "GET",
async: false,
url: self.config.APIs.GetCatalog.Url,
dataType: "json",
success: function (data) {
self.catalogList = data;
},
error: function (jqXHR, textStatus, error) {
console.log(error);
}
});
},
render: function (container) {
var self = this;
var oContainer = $(container);
var list = {
categories: ko.observableArray(),
switchCategory: function (selected) {
//alert("Hello world"+selected.Id);
hub.publish(self.config.Events.SWITCH_CATEGORY, selected.Id);
}
};
list.categories(self.catalogList);
oContainer.append(this.oTemplate);
ko.applyBindings(list, this.oTemplate.get(0));
}
}); return app;
});

<ul class="nav nav-pills nav-stacked" data-bind="foreach:categories">
<li>
<a href="#" data-bind="attr:{categoryId:Id},click:$parent.switchCategory">
<!--ko text: Name--><!--/ko-->
<span class="badge pull-right" data-bind="text:ArticlesCount"></span>
</a>
</li>
</ul>
在数据和视图两者间,我使用了Knockout进行绑定,它的优势在文档中有详细的描述,如果您想了解的话,就在文章开始找链接吧;
模块间的工作
上一节中提到了Pubsub发布了一个事件出去,意图是希望文章列表或者其他什么关心这个事件的组件去做它自己的工作,在这个示例中当然就只有articleList这个组件了,来看一下这个组件的代码:
/// <reference path="../Libs/require.js" />
define(['lib/jquery', 'lib/ko', 'lib/underscore', 'utility', 'pubsub', 'r/text!App/articleList.html','r/css!App/CommonStyle/style.css'],
function ($, ko, _, utility,hub,layout) {
var app = function (config) {
this.config = config;
this.oTemplate = $(layout);
this.currentPageArticles = null;
this.currentCategoryId = null;
this.currentPageNumber = null;
this.articleListViewModel = null;
} _.extend(app.prototype, {
initialize:function(){
}, load: function () {
var self = this;
hub.subscribe(this.config.Events.SWITCH_CATEGORY, function (msg, data) {
self.switchCategory(data);
});
}, render: function (container) {
var self = this;
var oContainer = $(container);
this.articleListViewModel = {
articles: ko.observableArray(),
openArticle: function (selected) {
hub.publish(self.config.Events.OPEN_ARTICLE, selected.Id);
}
};
oContainer.append(this.oTemplate);
ko.applyBindings(this.articleListViewModel, this.oTemplate.get(0));
}, switchCategory: function (categoryId) {
var self = this;
self.currentCategoryId = categoryId;
self.currentPageNumber = 1;
$.ajax({
type: "GET",
async: true,
url: utility.FormatUrl(false,self.config.APIs.GetArticleList,categoryId,self.currentPageNumber),
dataType: "json",
success: function (data) {
self.articleListViewModel.articles(data);
}
});
},
turnPage: function (pageNumber) { }
}); return app;
}
);
烂图赏鉴


代码送上,仅供吐槽
onedrive就不用了,虽然很搞到上,但是谁知道哪天就又…你懂的
百度网盘地址:http://pan.baidu.com/s/1o6meoKa
Singal Page App:使用Knockout和RequireJS创建高度模块化的单页应用引擎的更多相关文章
- bootstrap + requireJS+ director+ knockout + web API = 一个时髦的单页程序
也许单页程序(Single Page Application)并不是什么时髦的玩意,像Gmail在很早之前就已经在使用这种模式.通常的说法是它通过避免页面刷新大大提高了网站的响应性,像操作桌面应用程序 ...
- 实战使用Axure设计App,使用WebStorm开发(2) – 创建 Ionic 项目
系列文章 实战使用Axure设计App,使用WebStorm开发(1) – 用Axure描述需求 实战使用Axure设计App,使用WebStorm开发(2) – 创建 Ionic 项目 实战使 ...
- Nodejs之MEAN栈开发(六)---- 用Angular创建单页应用(上)
在上一节中我们学会了如何在页面中添加一个组件以及一些基本的Angular知识,而这一节将用Angular来创建一个单页应用(SPA).这意味着,取代我们之前用Express在服务端运行整个网站逻辑的方 ...
- ABP示例程序-使用AngularJs,ASP.NET MVC,Web API和EntityFramework创建N层的单页面Web应用
本片文章翻译自ABP在CodeProject上的一个简单示例程序,网站上的程序是用ABP之前的版本创建的,模板创建界面及工程文档有所改变,本文基于最新的模板创建.通过这个简单的示例可以对ABP有个更深 ...
- 七天学会ASP.NET MVC(七)——创建单页应用
系列文章 七天学会ASP.NET MVC (一)——深入理解ASP.NET MVC 七天学会ASP.NET MVC (二)——ASP.NET MVC 数据传递 七天学会ASP.NET MVC (三)— ...
- 【单页应用】全局控制器app应该干些什么?
前言 之前,我们形成了页面片相关的mvc结构,但是该结构还仅适用于view(页面)级,那么真正的全局控制器app应该干些什么事情呢?我觉得至少需要干这些: 功能点 ① 提供URL解析机制,以便让控制器 ...
- 【读书笔记】WebApi 和 SPA(单页应用)--knockout的使用
Web API从MVC4开始出现,可以服务于Asp.Net下的任何web应用,本文将介绍Web api在单页应用中的使用.什么是单页应用?Single-Page Application最常用的定义:一 ...
- One Page Scroll – 实现苹果风格的单页滚动效果
单页滚动网站已经被广泛使用了有一段时间了,它们对于快速提供信息是很有用的.One Page Scroll 是一个 jQuery 插件,简化了创建此类网站的步骤,只需创建 HTML 结构,进行简单设置, ...
- 七天学会ASP.NET MVC(七)——创建单页应用 【转】
http://www.cnblogs.com/powertoolsteam/p/MVC_Seven.html 系列文章 七天学会ASP.NET MVC (一)——深入理解ASP.NET MVC 七天学 ...
随机推荐
- Android Dalvikvm 内存管理理解
网上非常多文件介绍了 jvm 内存管理的理论,但在 Dalvikvm 中,到底是怎样实现的. 这几天猛看了 Dalvikvm 的源码,说一下我的理解: 在大层面上讲跟理论一样,jvm 把内存分成了一些 ...
- Eclipse 发布到网站的附加产品的形式 Update Site
Eclipse 发布到网站的附加产品的形式 Update Site 通过Update Site Project项目将自己做的插件产品公布到公网上,给客户或其它測试人员下载和应用,这样自己的插件就以网站 ...
- 用Owin Host实现脱离IIS跑Web API单元测试
开发笔记:用Owin Host实现脱离IIS跑Web API单元测试 今天在开发一个ASP.NET Web API项目写单元测试时,实在无法忍受之前的笨方法,决定改过自新. 之前Web API的单 ...
- 2014 Multi-University Training Contest 1/HDU4861_Couple doubi(数论/法)
解题报告 两人轮流取球,大的人赢,,, 贴官方题解,,,反正我看不懂.,,先留着理解 关于费马小定理 关于原根 找规律找到的,,,sad,,, 非常easy找到循环节为p-1,每个循环节中有一个非零的 ...
- IOS View传统的价值观之间
1.采用NSUserDefaults通过值,这样的方法不限于传送少量数据的: 比方你要传一个float的值.在须要传的时候用 [[NSUserDefaults standardUserDefaults ...
- Java设计模式(七)策略模式 模板模式
(十三)策略模式 策略图案限定了多个封装算法,该算法可以相互替换包.法的客户.借用还有一位大神的样例. interface ICalculator{ public int calculate(Stri ...
- POJ1743---Musical Theme(+后缀数组二分法)
Description A musical melody is represented as a sequence of N (1<=N<=20000)notes that are int ...
- PeopleRank从社交网络中发现个体价值
阅读导读: 1.什么是PeopleRank? 2.PeopleRank和PageRank有什么差别? 3.PR分析微博数据时,怎样对微博单个账号评分? 4.R语言怎样递归计算矩阵特征值? 5.怎样计算 ...
- StyleCop中有一些官方自己写好的检测规则下面就是英文的解释
在StyleCop中有一些官方自己写好的检测规则下面就是英文的解释 文档规则 1.SA1600:ElementsMustBeDocumented元素必须添加注释 2.SA1601: PartialEl ...
- mac_Mac item2常用快捷键
整理使用 iTerm 2 过程中得常用快捷键,Mac 原来自带的终端工具 Terminal 不好用是出了名的,虽然最近几个版本苹果稍微做了些优化,功能上,可用性方面增强不少,无奈有个更好用的 Iter ...