在示例( 基本导航组合与服务器的通信) 中,对 html5/javascript单页面应用的剖析

分享于 

80分钟阅读

Web开发

  繁體

介绍

什么是单页应用程序( SPA )?

普通HTML应用程序/网站每次用户想要访问新页面时都从服务器获取每个页面。 但是,switch 应用程序在开始时将应用程序的所有页面带来,并使用JavaScript在页面的页面之间进行。 这使HTML应用程序与本机或者Silverlight应用程序相当响应,因为页之间的导航不需要对服务器进行循环。 这种优势是以更高的应用程序加载时间为代价的。 加载,应用程序的方法有一些,使用户在加载通用的HTML应用程序时相同。

如果你认为,应用程序非常类似于Silverlight应用程序,大多数Silverlight应用程序在开始时都可以在页面之间导航。 因此,对于Silverlight开发人员来说,对SPA概念的理解应该非常自然。

导航状态

浏览器导航将唯一的URL映射到浏览器应用程序的特定导航状态。 "应用程序的导航状态"确定应用程序中显示或者隐藏的页面或者区域,或者显示它们的显示方式。

浏览器解释URL并将应用程序设置为相应的状态。 这是 比如 需要的,使用户可以按浏览器"背面"和"正向"导航按钮( 浏览器存储 URL,当用户按下某个浏览器导航按钮,URL改变,应用程序状态相应改变) 来导航浏览器应用程序。 同样,当外部页面引用应用程序中某个特定页面( 或者状态) ( 而不是应用程序页面的默认页面) 时,这。

反过来也是 true: 一旦应用程序的状态改变,因为用户按下一个标签或者超链接移动到另一页,浏览器URL也应该改变为对应于新状态的页面。

对于普通的( 非 spa ) HTML应用程序,导航自然是( 页面根据相应的URL从服务器加载)。 然而,为了实现url和应用程序的导航状态之间的one-to-one映射,SPA应用程序需要使用一些JavaScript魔术。 这里我们提供了一个新功能,使得SPA导航比使用其他已有的框架更容易实现。

同样的导航概念可以适用于任何本地的单一窗口/多页应用程序。

SPA应用程序的组成

根据我的经验,构建SPA应用程序会产生大型的HTML文件,因为应用程序由多个页面组成,所有页面。 大型HTML文件可能难以理解和修改。 我创建了允许将温泉分割成多个文件并在客户机上加载和组装它们的功能。 这里还讨论了这个功能。

BPF框架

作为WPF和Silverlight的优秀粉丝,我称新框架BPF为浏览器表示基础。 它的最终目的是提供在WPF和Silverlight中可以找到的功能,以及。 目前它提供了一些通用的用途实用功能,导航和组合能力,这些功能将。 框架的组成部分依赖于 JQuery,框架的其余部分是独立的,并且可以在没有安装JQuery的情况下使用。

有两个使用早期版本的BPF库构建的温泉: 我自己的商业网站 awebpros.compaperplusne.com。 注意 paperplusne.com 网站没有正确的价格数据,它的所有项目( 对于没有价格数据的项目,我把 $1000作为价格)。 你仍然可以通过它订购一些塑料产品,并且有人会与你联系。

SPA服务器通信

Silverlight或者桌面应用程序一样,通过从服务器( 通常以JSON形式形式) 获取所需的数据而不是通常的HTML应用程序。 我提供了一些与本文中的服务器通信的示例。

本文的组织

我们展示了普通和 SPA html/javascript站点最简单的可能示例。 我们讨论它们之间的差异。

我们讨论了导航,并展示了BPF框架的导航功能。

我们用BPF框架讨论了这种。

我们讨论使用 ASP.NET MVC与服务器通信,并提供相应的示例。

注意:本文主要关注业务逻辑而不是UI设计。 我们以最简单的方式展示温泉浴场,不需要担心它们的外观。

前提条件

你需要了解HTML和 JavaScript,以便阅读和理解本文。 我指的是 HTML5,JavaScript,knockout,JQuery,引导 Silverlight/WPF/C# 上瘾者的指南。 第 1部分JavaScript和 DOM 作为良好的html/JavaScript入门。

在本文中,我使用一些非常基本的JQuery功能来检测HTML文档何时加载并提供一种多平台绑定。 来自和事件的几段段落应该能够覆盖这个信息。

讨论组合时,我们还使用JQuery选择器。 你可以按照的JQuery链接来了解它们。

我使用 jquery tabs 控件进行导航,但在文本中解释了与之相关的任何内容。

尽管我们试图解释文章本身的所有内容,但涉及与服务器通信的部分可能需要对 ASP MVC的一些基本理解。

大系列的一部分

本文可以被看作"HTML5,JavaScript,knockout,JQuery,恢复 Silverlight/WPF/C# 上瘾者指南"系列的第 3部分,其中也包含了 HTML5.JavaScript。knockout。JQuery。以及用于恢复 Silverlight/WPF/C#。 第 1部分- JavaScript和 DOM 和 HTML5,JavaScript,knockout,JQuery,指南,用于恢复 Silverlight/WPF/C#。 第 2部分- 使用 SVG,knockout 和 MVVM Pattern 构建太阳系动画。

SPA基础

演示两页SPA和两页普通HTML应用程序示例

这里介绍两个非常简单的两个页面HTML应用程序: 一个普通的和另一个。 普通HTML应用程序的代码位于TwoPageOrdinaryHTMLApp解决方案下,而SPA的代码位于TwoPageSPA解决方案下。

两个应用程序都提供完全相同的用户体验。 HTML窗口顶部有两个与两个页面对应的超链接。 单击顶部超链接显示页面的第一个文本,同时单击另一页显示页面的它的他文本:

普通HTML应用程序有两个HTML页面 Page1.htm 和 Page2.htm. 在页面( 一个到另一个到另一个) 和页面文本的顶部都有超链接。 对于 Page1.htm 页面文本是"这是 1页",它是红色的。 对于 page2.htm,文本是"这是 2页",它是蓝色的。 下面是 Page1.htm 代码:

<body><!-- hyper links for choosing the page --><ul><li><ahref="Page1.htm">choose page1</a></li><li><ahref="Page2.htm">choose page2</a></li></ul><!-- page 1 message colored in red --><pstyle="font-size:40px;color:red">This is page 1</p></body>

若要启动 TwoPageOrdinaryHTMLApp,请在 studio explorer的可以视解决方案中右击 Page1.htm 文件,如果想在默认浏览器或者"浏览"选项中选择"在浏览器中查看"选项。 你可以通过单击顶部的超链接来在页面之间进行 switch。

SPA解决方案使用 JQuery ( 如果你打开脚本文件夹,你会看到一堆JQuery文件)。 你可以像 中描述的那样将JQuery安装为一个NuGet包。

TwoPageSPA项目中只有一个HTML文件: index.html。你可以通过右键单击解决方案资源管理器中的这里文件来启动应用程序,并选择"在浏览器中运行"。 index.html 包含包含页面的超链接和文本的HTML部分。 它还有一个用于在页面之间切换的JavaScript代码。

下面是 index.html file: 中的HTML代码

<body><ul><!-- href='#' is needed only to help the links look like links --><li><aid="page1Link"href="#">choose page1</a></li><li><aid="page2Link"href="#">choose page2</a></li></ul><!-- page 2 message colored in blue --><!-- in the beginning page2 is not visible (display set to 'none') --><pid="page2"style="font-size:40px;color:blue;display:none">This is page 2</p><!-- page 1 message colored in red --><pid="page1"style="font-size:40px;color:red">This is page 1</p></body>

如你所见,页链接不包含有效的引用- 它们的href 属性设置为'#',这样它们就像链接( 有蓝色的颜色并将鼠标 cursor 改为"")。

两个页面的文本都放置在链接中。 第 2页在开始时不可以见( 它的display 属性设置为 none,因这里,它首先被放置,因为它不会被移动)。

页面切换由放置 below 代码的JavaScript代码完成:

// use $(document).ready function to// make sure that the code executes only after the document's// DOM has been loaded into the browser.$(document).ready(function () {
 // when page1Link link is clicked, page1 shows,// page2 hides $(page1Link).bind("click", function () {
 $(page1).show();
 $(page2).hide();
 });
 // when page2Link link is clicked, page2 shows,// page1 hides $(page2Link).bind("click", function () {
 $(page1).hide();
 $(page2).show();
 });
});

我们使用jquery函数绑定将事件处理程序附加到超链接上的click 事件。 事件触发时,我们显示其中一个页面的内容并隐藏另一个页面的内容。

然而,我们可以看到,两个应用程序的行为有不同。 在普通HTML应用程序中更改页时,浏览器URL也会发生更改( 比如。 切换页面后,从 http://localhost:23033/Page1.htm 到 http://localhost:23033/Page2.htm).,你可以使用浏览器上的"背面"按钮来访问上一页。 另外,如果你希望用户访问 page2.htm 而不访问 Page1 ( 它是应用程序的默认页面),你只需给出一个指向Page2的超链接 http://localhost:23033/Page2.htm。

不过,如果你尝试在SPA中使用页面,你会发现URL不会改变,返回按钮不工作,也没有办法给用户一个直接链接到页面页面。 这里问题将在本文专门用于SPA导航部分的部分中进行讨论。

将 jQuery UI 选项卡用于SPA页

当你想在页面之间进行 switch 时,有标签而不是超链接看起来更好。 jQuery UI 选项卡可以用于这里目的。

jQuery UI 是构建在JQuery之上的一个GUI包。 你可以通过与JQuery相同的方式通过NuGet安装它。

使用 jQuery UI 选项卡的代码示例位于TwoPagesSPAWithTabs解决方案下。 它由 index.htm 文件以及来自JQuery和 jQuery UI 包的文件组成。 index.htm 文件包含对 jQuery UI 样式表的引用: <link href="Content/themes/base/jquery.ui.all.css" rel="stylesheet" type="text/css"/> 在页面的顶部,( 样式表是 jQuery UI 包安装的一部分)。

下面是 index.htm file: 中的HTML代码

<body><ulid="pageTabs"><!-- hrefs of the links point to the ids of the pages' contents --><li><ahref="#page1">choose page1</a></li><li><ahref="#page2">choose page2</a></li></ul><!-- page 1 message colored in red --><pid="page1"style="font-size:40px;color:red">This is page 1</p><!-- page 2 message colored in blue --><pid="page2"style="font-size:40px;color:blue">This is page 2</p></body>

注意,代码顶部的超链接的href 属性指向包含页内容( 比如 )的HTML标记的id s。 带有 href="#page1"的超链接指向标记 <p id="page1".. . )。 这是让 jQuery UI的功能找出哪些内容属于哪个标签。

JavaScript代码也很简单:

$(document).ready(function () {
 $("body").tabs(); // turns the hyperlinks into tabs});

以下是应用程序的外观:

前面的示例一样,URL没有连接到选项卡- 更改选项卡不会更改URL并更改 URL。

BPF框架和导航

这里我们介绍了BPF框架的导航功能。

许多温泉浴场使用 Sammy.js 框架进行导航。 Sammy.js 允许将不同的URL散列映射到不同的JavaScript函数中。 当SPA具有许多不同的导航状态,并且具有一些可能的层次结构时,这种映射可能与实现不同。 但是,dtmf库实现了导航状态和URL哈希之间的映射,而且它的余的代码很少。

对于当前版本的vmkernel框架,它依赖于 hashchange 文档事件来检测散列的变化和更改的导航状态。 IE7不支持 hashchange 事件,因此导航功能将在它的上工作。 ,浏览器用户的共享目前小于 1%,因这里我相信这个问题不必解决。

BPF框架的代码位于BPF文件夹下。 它也可以从 github.com/npolyak/bpf。 为访问所有它的功能所需的唯一文件是 bpf.min. js ;目前为止( 在 2012年12月的开头,它非常小- 大约 12 Kb )。 这个文件是通过捆绑和缩小其他JavaScript文件得到的,也可以在BPF文件夹中找到,我将不时地引用这个文件。

带导航功能的两页 SPA

我们将首先使用that来修改简单的两个页面,使用表示 上面的选项卡,这样可以导航。 示例位于TwoPageSPAWithNavigation解决方案下。 这个项目和TwoPageSPAWithTabs的唯一区别在于 index.html 文件底部的JavaScript代码。 在TwoPageSPAWithTabs项目中,在 $(document).ready() 方法中由一行 $("body").tabs(); 组成的代码。 此行的目的是将超链接转换为带有相应内容的JQuery选项卡。 TwoPageSPAWithNavigation中的JavaScript代码更复杂:

// get the tabs DOM elementsvar tabs = $("body").tabs(); // turns the hyperlinks into tabs// JQTabsNavAdaptor is a special constructro that adapts// the JQuery tabs to become usable by the navigation frameworkvar tabsAdaptor = new bpf.nav.JQTabsNavAdaptor(tabs);// create the navigation framework nodevar tabNode = new bpf.nav.Node(tabsAdaptor);// connect the navigation framework node to the browser's url hashbpf.nav.connectToUrlHash(tabNode);// set the url to correspond to the return bpf.nav.setKeySegmentToHash(tabNode);

不要试图理解全部,因为详细的导航功能设计将在不久的将来讨论。 简而言之,我们使用 bpf.nav.Node 功能将导航状态连接到浏览器url的各个部分。 bpf.nav.JQTabsNavAdaptor 适应JQuery标签,因此得到的修改对象有 bpf.nav.Node 预期的方法。 bpf.nav.connectToUrlHash(tabNode) 在节点和 URL ( 节点对URL做出响应,反之亦然) 之间建立了双向连接。 末行 bpf.nav.setKeySegmentToHash(tabNode) 将初始URL设置为 MATCH 从 bpf.nav.Node 结构获得的结构。

运行示例显示与TwoPageSPAWithTabs非常相似,但选项卡具有映射到它们的唯一 URL,当你更改选项卡时。 相应"背面"和"正向"浏览器按钮现在在标签之间工作和 switch。 在浏览器中输入"http://localhost:48476/Index.html#page2."的URL也会使你直接进入第二个标签页。

一个更复杂的JQuery标签

HierarchicalSPAWithNavigation示例提供了更有趣的选项卡层次结构。 顶级选项卡包含子选项卡。 每个选项卡/子选项卡组合都映射到它的自己的URL。

尝试启动应用程序并使用标签。 以下是应用程序的外观:

注意我们在Page2页面中显示 SubPage2. 这里组合对应于URL哈希: "#page2.page2subtab2."如果更改了选项卡,哈希将相应地改变。

你可以看到哈希总是以'#'开始,以'。'字符结尾。 导航状态层次结构不同级别对应的链接由句点('。') 字符分隔。 这里示例中的链接,MATCH的href 值的链接删除了'#'前缀字符。

下面是创建导航( 再次不要非常难于理解它,因为我们将在下一节中讨论它。)的代码:

// get the tabs DOM elementsvar tabs = $("body").tabs(); // turns top level hyperlinks into tabsvar page1SubTabs = $("#page1SubTabs").tabs(); // turns page 1 hyperlinks into tabsvar page2SubTabs = $("#page2SubTabs").tabs(); // turns page 2 hyperlink into tabs// creates the top level node (JQTabsNavAdaptor is used).var topLevelNode = bpf.nav.getJQTabsNode(tabs);// creates a child node and adds it to top level node// as child of"page1".bpf.nav.addJQTabsChild(topLevelNode, "page1", page1SubTabs);// creates a child node and adds it to top level node// as child of"page2".bpf.nav.addJQTabsChild(topLevelNode, "page2", page2SubTabs);// connect the navigation framework to the browser's url hashbpf.nav.connectToUrlHash(topLevelNode);// set the url to correspond to the current selection patternreturn bpf.nav.setKeySegmentToHash(topLevelNode);

导航功能的详细设计

导航功能位于BPF库的"命名空间" bpf.nav 下。 它依赖于 bpf.utils 功能,而且它的部分依赖于JQuery和 jQuery UI ( 虽然没有他们可以工作)。

导航功能的目的是让开发人员定义一些导航状态并将它的映射到一个独特的浏览器 URL。

导航状态假定为层次结构- 在顶层可以有多个状态( 比如 )。 上一示例中的页 1和页 2 )。 每个都可以有子状态( 子页面)。 每个子状态都可以有自己的子状态 等等,应用程序的总导航状态由每个级别( 或者级别状态) 中的状态的唯一选择决定。 每个级别状态映射到一个由句点('。') 字符在URL哈希中绑定的链接。 它被附加到与父级状态对应的链接。 图像 below 描述了状态层次结构的示例,对应于级别状态和相应端点旁边的哈希字符串: 比如 page1/subpage2选择将导致 hash="#page1.subpage2.":

我们调用哈希段所绑定的url的每一部分都被。 例如哈希"#page1.subpage2"有两个段: ""还有"subpage2.每个段映射到在该级别选择的级别状态。 在每个级别上,只能选择一个级别状态( 有时,假设在某个级别上没有选择任何级别状态是很方便的)。

导航状态由级别状态组成。 每个导航节点都可以有几个属于它的级别子状态。 不能选择这些级别的子状态之一作为当前级别状态。 每个级别状态都有一个与其关联的字符串。 这里字符串唯一地标识同一节点的子状态之间的状态。 这里字符串将成为在选定级别状态时在url的哈希内由两个句点字符绑定的段。 总哈希是由与高级状态相对应的级别状态相对应的序列来决定的。

捕获级别状态树信息的主要功能是 bpf.nav.Node 位于 NavigationNode.js 文件中。 它将 bpf.nav.NodeBase 提供的功能扩展或者扩展到 NavigationNodeBase.js 文件中的( 在 C# 意义中)。 这个子类化是由 bpf.nav.Node 构造函数在它的代码中调用 bpf.nav.NodeBase 构造函数实现的。 因为另一个类 bpf.nav.ProductNode 同样是从同一个 bpf.nav.NodeBase 类派生的,所以我们需要子类。 bpf.nav.ProductNode 功能将在 below 中讨论。 这里继承关系显示在图 below 中:

bpf.nav.Node 引用其父和子对象( 如果有的话)。 bpf.nav.Node 父对象和子级也从 bpf.nav.NodeBase 类派生。 bpf.nav.Node的子元素可以通过函数 bpf.nav.NodeBase.getChild(key) 通过字符串键访问。 键映射到url散列的对应段。

每个 bpf.nav.Node 对象包装在节点级别负责切换状态的其他对象。 如 上面 示例所示,此类对象可以是 jQuery UI 选项卡对象。 bpf.nav.Node 假定包装对象满足某些要求- 它应该在Java或者 C# 中有一些方法和字段,即包装对象应该实现一些接口。 下面是它应该用 C# 表示法实现的接口:

publicinterface ISelectable
{
 // event that fires when a selection changes at the selectable// object's level. // This event is implemented as bpf.nav.SimpleEvent in JavaScriptevent Action onSelectionChagned;
 // returns the currently selected key (hashSegment)// if no key is selected, it returns nullstring getSelectedKey();
 // does the selection specified by the key (or hashSegment)voidselect(string key);
 // unselects the currently selected key (if any and if the object allows it).void unselect();
} 

JavaScript中的大多数实体不支持 上面 接口,所以我们必须使用适配器来适应我们处理的对象( ISelectable 接口)。 jQuery UI tabs对象适配器被称为 bpf.nav.JQTabsNavAdaptor,它是BPF库的一部分。

适配器包含控制选择( 在本例中是 jQuery UI 选项卡对象)的对象,并通过提供 bpf.nav.Node 期望的方法实现 ISelectable 接口。

另一个适配器内置于通过 bpf.nav.CheckboxNavAdaptor 类的复选框适配器。 它提供了将级别状态与复选框的选中状态和未选中状态相关联的方法。

bpf.nav.Node ( 通过 bpf.nav.NodeBase ) 具有以下重要方法:

  • setSelectedKeySegments(urlHash) - 设置节点和它的子代的选择,以将作为字符串传递给函数的url内的分段 MATCH。
  • getTotalHash() - 返回对应于节点本身及它的子代的选择的url的哈希值。 哈希有前导('#') 和尾随句点('。') 字符。
这两个方法提供了在某些可选对象中更新选择的方法( 比如。 jQuery UI 选项卡) 一旦改变了 URL,反之亦然- 在 bpf.nav.Node 层次结构中选择更改之后改变 URL。

视图模型选择示例

本节需要一些 Knockoutjs插件库的知识。 使用这个链接或者其他许多资源,你可以了解 Knockoutjs插件,包括使用 SVG。和 MVVM Pattern 构建的太阳系动画。

这里节的示例位于ParamNavigationTest解决方案下。 我们在脚本/viewmodels目录下的文件 StudentViewModel.js 文件中创建了一个视图模型。 它定义了 Student 类型对象 studentVM.students的Collection。 每个 Student 都有一个 name 字段和 course 对象的Collection。 每个课程有两个字段 courseNamegrade。 视图模型 studentVM 具有 selectedStudentKnockoutjs 可见字段。 每个 Student 对象都有一个可以观察的selectedCourse 字段。

视图位于 html/index.html 文件中。 它提供了指向顶层 Student 对象的超链接。 链接显示学生的NAME。 点击链接后,选择相应的学生,浏览器窗口显示学生所选学生的课程中所选学生的课程信息。 当课程链接被单击时,浏览器显示学生 NAME,课程 NAME 和学生的级别:

我们的任务是为每个学生提供一个唯一的网址散列- 课程选择。

当然,我们可以为每个学生和课程对象使用 上面 所描述的bpf.nav.Node 功能。 然而,如果这些对象的数量非常高,或者学生或者课程集合在整个应用程序中发生了变化,那么这一点。

of库有一个位于 NavigationFactoryNode.js 文件中的类 bpf.nav.FactoryNode,它将 bpf.nav.Node 对象作为应用程序需要的,并将它们添加到节点 Collection。

我们使用 bpf.nav.KoObservableNavAdaptor 作为适配器对象来适应可见对象。 bpf.nav.KoObservableNavAdaptor 构造函数采用 3个参数:

  • 可见
  • 从视图模型 Collection 中通过散列段( 或者密钥) 获取对象的函数
  • 在视图模型 Collection 中从每个对象获取键的函数

下面我们将导航状态连接到 index.html file: 中的视图模型

// we create the adaptor for the selected student observablevar studentLevelObservableAdaptor =
 new bpf.nav.KoObservableNavAdaptor
 (
 studentVM.selectedStudent,
 // key to student function function (key) {
 var foundStudent = _.chain(studentVM.students).filter(function (student) {
 return student.name === key;
 }).first().value();
 return foundStudent;
 },
 // student to key function function (student) {
 return student.name;
 }
 );var topLevelNode = new bpf.nav.FactoryNode(
 studentLevelObservableAdaptor,
 null,
 // child node producing function function (key, data) {
 var childObj = data.getChildObjectByKey(key);
 if (!childObj)
 return;
 // create adapted child objectvar adaptedChildObj =
 new bpf.nav.KoObservableNavAdaptor
 (
 childObj.selectedCourse,
 function (courseKey) {
 return _.chain(childObj.courses).
 filter(function (universityCourse) {
 return universityCourse.courseName === courseKey;
 }).first().value();
 },
 function (course) {
 return course.courseName;
 }
 );
 // create the nodereturnnew bpf.nav.Node(
 adaptedChildObj
 );
 }
);
bpf.nav.connectToUrlHash(topLevelNode);
bpf.nav.setKeySegmentToHash(topLevelNode); 

笛卡尔产品导航

假设你的浏览器分为两个部分。 每个部分都有标签。 你可以在每个部分中选择一个标签。 你还希望为每个可能的标签组合指定一个 URL。 导航状态的结果空间将是浏览器的每个部分中状态的笛卡尔乘积。

我们可以将浏览器分成两个上的部分,从而进一步使任务复杂化。 我们也可以假设这样的笛卡尔产品不仅可以出现在顶部状态,而且可以在任何状态。

SimpleProductNavigationTest解决方案包含了我们在本节开始时描述的场景。 浏览器页面分为两个部分。 每个部分都有标签我们记录页面的两个部分的选项卡选择的每个组合:

注意图 上面 上的总URL哈希是"#(topics/TopicA)(othertopics/SomeOtherTopicA).".的部分,对应于笛卡尔产品的组件的部分被放置在圆括号内。 括号内的散列部分以唯一标识笛卡尔产品中那个部分的键开始。 键与括号中的其余字符串的左斜杠分开。

我们使用 bpf.nav.ProductNode 将URL散列映射到状态的笛卡尔乘积。 它的用法示例位于SimpleProductNavigationTest项目中 index.html 文件的底部:

// create topics JQuery UI tabsvar topics = $("#MyTopics").tabs();// create otherTopics tabsvar otherTopics = $("#SomeOtherTopics").tabs();// top level node is a product node for// the Cartesian product of the statesvar topLevelNode = new bpf.nav.ProductNode();// add"topics" node to be a child of topLevelNode under key"topics"bpf.nav.addJQTabsChild(topLevelNode, "topics", topics);// add"otherTopics" node to be a child of topLevelNode under key"othertopics"bpf.nav.addJQTabsChild(topLevelNode, "othertopics", otherTopics);// create the one to one relationship between the states and the URLbpf.nav.connectToUrlHash(topLevelNode);// change the original URL to be based on the navigation statesbpf.nav.setKeySegmentToHash(topLevelNode); 

bpf.nav.ProductNode 位于 ProductNavigationNode.js 文件中。 就像 bpf.nav.Node 一样,它是一个 bpf.nav.NodeBase 类。 它重写了函数 setSelectedKeySegmentsRecursivegetUrlRecursivechainUnselect的实现。

一个更复杂的例子可以在ComplexProductNavigationTest解决方案中找到。 在标签中包含一个长的标签序列,在两个层次上有笛卡尔乘积出现- - 顶端和TopicA标签下。

下面是SPA的外观:

图 上面的URL的总散列部分是"#(topics/TopicA.(a1subs1/A1Subtopic1.A1Sub1Sub2)(a1subs2/A2Subtopic2))(othertopics/SomeOtherTopicA).".,你可以使用它来查看浏览器导航按钮是否真的正常工作。

下面是应用程序的HTML代码:

<table><tr><tdstyle="vertical-align: top"><divid="MyTopics"><ul><li><ahref="#TopicA">TopicA</a></li><li><ahref="#TopicB">TopicB</a></li></ul><divid="TopicA"> My topic A
 <table><tr><tdstyle="vertical-align: top;"><divid="A1Subtopics"><ul><li><ahref="#A1Subtopic1">A1Sub1</a></li><li><ahref="#A1Subtopic2">A1Sub2</a></li></ul><divid="A1Subtopic1"> A1 Sub Topic1
 <divid="A1Sub1Sub"><ul><li><ahref="#A1Sub1Sub1">A1S1S1</a></li><li><ahref="#A1Sub1Sub2">A1S1S2</a></li></ul><divid="A1Sub1Sub1">A1 Sub1 Sub1</div><divid="A1Sub1Sub2">A1 Sub1 Sub2</div></div></div><divid="A1Subtopic2">A1 Sub Topic2</div></div></td><tdstyle="vertical-align: top;"><divid="A2Subtopics"><ul><li><ahref="#A2Subtopic1">A2Sub1</a></li><li><ahref="#A2Subtopic2">A2Sub2</a></li></ul><divid="A2Subtopic1">A2 Sub Topic1</div><divid="A2Subtopic2">A2 Sub Topic2</div></div></td></tr></table></div><divid="TopicB"> The Topic B
 </div></div></td><tdstyle="vertical-align: top"><divid="SomeOtherTopics"><ul><li><ahref="#SomeOtherTopicA">AnotherA</a></li><li><ahref="#SomeOtherTopicB">AnotherB</a></li></ul><divid="SomeOtherTopicA">Some Other A </div><divid="SomeOtherTopicB"style="background-color: pink">Some Other B</div></div></td></tr></table>

下面是 $(document).ready() 函数中的JavaScript代码:

// create topics JQuery UI tabsvar topics = $("#MyTopics").tabs();// create otherTopics tabsvar otherTopics = $("#SomeOtherTopics").tabs();var A1Subtopics = $("#A1Subtopics").tabs();var A2Subtopics = $("#A2Subtopics").tabs();var A1Sub1Sub = $("#A1Sub1Sub").tabs();// top level node is a product node for// the Cartesian product of the statesvar topLevelNode = new bpf.nav.ProductNode();// add"topics" node to be a child of topLevelNode under key"topics"var topicsNode = bpf.nav.addJQTabsChild(topLevelNode, "topics", topics);// add"otherTopics" node to be a child of topLevelNode under key"othertopics"bpf.nav.addJQTabsChild(topLevelNode, "othertopics", otherTopics);var aSubtopicsProductNode = new bpf.nav.ProductNode();
topicsNode.addChildNode("TopicA", aSubtopicsProductNode);var A1SubNode = bpf.nav.addJQTabsChild(aSubtopicsProductNode, "a1subs1", A1Subtopics);
bpf.nav.addJQTabsChild(aSubtopicsProductNode, "a1subs2", A2Subtopics);
bpf.nav.addJQTabsChild(A1SubNode, "A1Subtopic1", A1Sub1Sub);// create the one to one relationship between the states and the URLbpf.nav.connectToUrlHash(topLevelNode);// change the original URL to be based on the navigation statesbpf.nav.setKeySegmentToHash(topLevelNode);

利用BPF框架进行SPA组合

正如我在介绍中所提到的,由于所有页面都被加载并切换页面,所以of可能会导致大的HTML文件。

rtc框架提供了一种将HTML功能分解为较小文件(。叫做BPF插件或者简单插件)的方法,并将它的组合在客户端。 它还允许插件引用其他插件来创建插件层次结构。 这里外,你可以将JavaScript代码处理为在同一HTML文件中提供的功能,然后在组合期间调用这里功能。 这是我试图模仿 html/javascript领域概念背后的wpf/silverlight代码的尝试。

所有BPF组合的功能都位于 Composite.js 文件中,它依赖于 JQuery。

简单插件示例

让我们看一下SimpleCompositionSample项目下的示例。 这个项目的HTML目录包含两个HTML文件 index.html - 主文件和 APlugin.html - 一个包含插件代码的文件。 要启动应用程序,右键单击解决方案资源管理器中的index.html 文件并选择"在浏览器中查看"选项。

下面是 index.html file: 中的HTML代码

<divstyle="font-size:30px;color:red">This is the main Module</div><imgid="busyIndicator"src="../Images/busy_indicator.gif"style="vertical-align:central;margin-left:50%"/><!-- plugin will get into this div below --><divid="APluginContainer1"></div><!-- call plugin's function to change its color to 'blue' for the plugin 上面 --><buttonid="changePluginColorButton1">Change 1st Plugin Text to Blue</button><!-- plugin will get into this div below --><divid="APluginContainer2"style="margin-top:40px"></div><!-- call plugin's function to change its color to 'blue' for the plugin 上面 --><buttonid="changePluginColorButton2">Change 2nd Plugin Text to Blue</button>

在主HTML文件中唯一可见的文本是"这是主要模块"。 有两个用于插入插件内容的<div> 标记: "APluginContainer1"还有"aplugincontainer2"它的中每个都有一个按钮,调用插件后面的代码来改变对应插件实例的颜色。

你可能会注意到,在主模块的两个位置中插入了相同的插件。

下面是 Index.html的JavaScript代码:

// this event will fire after all the plugins and their// descendant plugins are loadedvar compositionReady = new bpf.utils.EventBarrier();// the composition ready event fires after everything all the plugins// and their descendants have been loaded into the main module.compositionReady.addSimpleEventHandler(function (success) {
 $("#busyIndicator").hide();
 $("#changePluginColorButton1").click(function () {
 // after changePluginColorButton1 is clicked, the// the function changeColorBackToBlue defined within the plugin// will be called and will only affect APluginContainer1 bpf.control("#APluginContainer1").call("changeColorToBlue");
 });
 // after changePluginColorButton2 is clicked, the// the function changeColorBackToBlue defined within the plugin// will be called and will only affect APluginContainer2 $("#changePluginColorButton2").click(function () {
 bpf.control("#APluginContainer2").call("changeColorToBlue");
 })
});// gets the plugin from file APlugin.html and inserts it into the // element pointed to by the selector"#APluginContainer1"bpf.cmpst.getPlugin("APlugin.html", null, "#APluginContainer1", compositionReady);// gets the plugin from file APlugin.html and inserts it into the // element pointed to by the selector"#APluginContainer2"bpf.cmpst.getPlugin("APlugin.html", null, "#APluginContainer2", compositionReady); 

两个 bpf.cmpst.getPlugin 调用底部,将插件加载到主 MODULE 中,将它的插入到DOM中的两个不同位置。 在所有插件及其后代加载到主 MODULE 之后,调用 compositionReady 事件处理程序。 它隐藏了"busyindicator",它给按钮的事件分配了调用 changeColorToBlue code=behind的函数的回调:

bpf.control("#APluginContainer1").call("changeColorToBlue");

函数 bpf.control(jquery-selector) 返回包含在传递给它的jquery-selector 所指向元素的插件后面的代码。 我们使用由BPF框架创建的函数 call() 来调用后面的代码。 函数的第一个参数是要调用的方法的NAME。 如果该方法有一些输入参数,则可以在方法名之后添加它们。

现在,让我们看一下插件代码:

<div id="aPlugin" style="font-size:25px"> This is a plugin</div><!--script tag should be marked by data-type="script-interface"in order for it to be recognized as containing the plugin's 'code-behind' -->
<script type="text/javascript" data-type="script-interface">
 (function () {
//this function returns the plugin's 'code-behind' - 
 // a JSON object consisting the functions. Two function names are // reserved"preRender" and"postRender" - the first executes // before the plugin is inserted into the parent module's DOM// the second executes after the plugin is inserted into the parent's// module dome. // other functions can be called at any later stage - at the developer's will// 'this' object for every function except for preRender contains the parameters// of the element into which this plugin attached. The most important parameter is// currentDOM - the DOM of the parent element.// All the JQuery selectors acting within the plugin should start with// this.currentDOM.find(... to make sure that we are only modifying our instance// of the plugin and not some other instances.// The other important field within"this" object is"codeBehind". It gives// access to the code-behind function returned below.// Note that all the function names should be in quotes in order for eval method// to work at the moment when the plugin is inserted into the plugin cache.return {
 "postRender": function (compositionReadyEventBarrier) {
 // change color to bluethis.currentDOM.find("#aPlugin").css("color", "green");
 },
 "changeColorToBlue": function () {
 // change the color to bluethis.currentDOM.find("#aPlugin").css("color", "blue");
 }
 };
 })();</script>

插件的HTML只是显示文本"这是一个插件"。

标记为 data-type="script-interface" 属性的<脚本> 标记被认为包含插件后面的代码。 应该只有一个 <脚本> 属性。 脚本属性的其余部分可以添加到插件中以增强智能感知,但是在插件加载到主 MODULE 中时将被删除。 包含在 <脚本> 中所需的所有JavaScript引用都应该放置在主模块中。

<脚本> 标记后面的代码包含一个匿名函数,该函数返回定义各种方法的JSON对象。 当插件加载到主 MODULE 中时,在方法后面有两个预定义的代码,称为( 如果它们存在): preRenderpostRender - 第一个被调用之前,插件加载后的第二个。

除了 preRender 以外,方法后面的所有代码都将一个特殊对象作为它的this 变量。 这个对象包含 currentDOM 字段,它是父 MODULE 中的元素的一个 JQuery DOM对象,插件被附加到这个对象中。

注意:如果你想找到属于代码后面的插件,应该通过调用 this.currentDOM.find(selector) 函数而不是使用 $(selector) 函数来完成。 它的原因是,如果在主 MODULE 中插入了同一个插件,除非使用 this.currentDOM.find 方法,否则将会将所有修改应用于多个实例。 请注意,由于我们的插件拥有和 id: id="aPlugin",插件插入到主 MODULE 中两次,你将在主 MODULE 中有两个不同的元素。 由于我总是使用 this.currentDOM.find 来解决这个实例,因这里我经验很好。 如果你认为它是错误的( 之后是HTML的pdf,在DOM中不应该有两个具有相同within的元素),你可以避免在插件内使用,而不是使用唯一的类名。

当插件插入主模块后,postRender 函数将插件文本的颜色改变为绿色。

changeColorToBlue 后面的代码方法将插件文本的颜色改为蓝色。

下面是我们从主 MODULE ( 文件 index.html )的JavaScript代码调用 changeColorToBlue 函数的方法:

bpf.control("#APluginContainer1").call("changeColorToBlue"); 

当你运行应用程序时,你将获得绿色插件的两个实例的文本。 当你单击插件实例将变成蓝色的相应插件实例下的按钮,而另一个则保持相同的颜色。 下面是单击了instance插件plugin按钮后应用程序的图像:

顶部插件实例的文本变成蓝色,而底部插件实例的文本保持绿色。

插件组合示例链

ChainOfPluginsCompositionSample解决方案包含一个示例,主 MODULE 加载插件,然后加载另一个插件,我们称它为sub插件。 示例的所有HTML文件都位于HTML文件夹下。 index.html 是包含主 MODULE的文件。 你可以通过右键单击应用程序并选择"在浏览器中查看"选项来启动应用程序。 以下是应用程序启动后的映像:

按钮会改变插件和子插件文本的颜色。

index.html 文件的代码与以前的示例不包含任何新的内容。 大多数"有趣"代码都位于 APlugin.html file: 内

<div id="aPlugin" style="font-size:25px"> This is a plugin
 <div id="subPluginContainer"></div> <Button id="changePluginColorButton">Change Plugin Color to Blue</Button> <Button id="changeSubPluginColorButton">Change SUB Plugin Color to Black</Button></div><scriptsrc="../Scripts/jquery-1.8.3.js"></script><scriptsrc="../Scripts/BPF/bpf.js"></script><scripttype="text/javascript"data-type="script-interface"> (function () {
 return {
 "postRender": function (compositionReadyEventBarrier) {
 var self = this;
 // change color to green self.currentDOM.find("#aPlugin").css("color", "green");
 // create a child event barrier - it will fire// once the sub-plugin and all of its// descendants are loaded.// And the parent event barrier won't fire until it fires.var subCompositionReady = 
 compositionReadyEventBarrier.createChildEventBarrier();
 subCompositionReady.addSimpleEventHandler(function () {
 // changes the color of the plugin itself to blue. $("#changePluginColorButton").click(function () {
 self.currentDOM.find("#aPlugin").css("color", "blue");
 });
 // on changeSubPluginColorButton click call the sub-plugin's// method changeColorToBlack $("#changeSubPluginColorButton").click(function () {
 bpf.control("#subPluginContainer", self).call("changeColorToBlack");
 });
 });
 // get sub-plugin note that you need to pass 'this' object// as the second argument to getPlugin function bpf.cmpst.getPlugin("ASubPlugin.html", self, "#subPluginContainer", subCompositionReady);
 }
 };
 })();</script>

当我们在插件中调用 getPlugin() 函数时,我们传递 self (。与 this 变量相同) 作为第二个参数,而不是像我们在主模块中所做的那样。 还要注意,为了获取插件内的代码到子插件后面的代码,我们需要将 self 作为 bpf.control() 函数的第二个参数: bpf.control("#subPluginContainer", self).call("changeColorToBlack");

组合和导航示例

CompositionAndNavigationSample解决方案演示如何组合BPF组合和导航。 我们在主 MODULE 级别创建两个选项卡。 我们也有一个插件的标签。 插件被添加到主 MODULE 中的一个选项卡的内容中。 我们使用 bpf.nav.Node 对象在插件选项卡周围创建一个导航节点,并将它作为模块结构的主节点连接。 下面是 index.html file: 中的主要模块代码

<!DOCTYPEhtml><htmlxmlns="http://www.w3.org/1999/xhtml"><head><linkhref="../Content/themes/base/jquery-ui.css"rel="stylesheet"/><title></title></head><body><!-- links to be converted to tabs --><ulid="pageTabs"><li><ahref="#page1">Page1</a></li><li><ahref="#page2">Page2</a></li></ul><!-- page 1 message colored in red --><divid="page1"style="color:red"> This is page 1
 <divid="pluginContainer"></div></div><!-- page 2 message colored in blue --><divid="page2"style="color:blue">This is page 2</div></body></html><scriptsrc="../Scripts/jquery-1.8.3.min.js"></script><scriptsrc="../Scripts/jquery-ui-1.9.2.min.js"></script><scriptsrc="../Scripts/BPF/bpf.js"></script><script> $(document).ready(function () {
 // create the tabs, store a reference to the tabs.var topLevelTabs = $("body").tabs();
 // create the top level bpf.nav.Node object around the tabs.var topLevelNode = bpf.nav.getJQTabsNode(topLevelTabs);
 var compositionReady = new bpf.utils.EventBarrier();
 compositionReady.addSimpleEventHandler(function () {
 // this function is called after all the plugins are loaded (rendered)// we call"connectToParentNode" function on the plugin// passing the topLevelNode to it, in order to connect// the plugin's tabs to the bpf navigation framework. bpf.control("#pluginContainer").call("connectToParentNode", topLevelNode);
 // connect the navigation framework with the URL's hash bpf.nav.connectToUrlHash(topLevelNode);
 // update the current URL bpf.nav.setKeySegmentToHash(topLevelNode);
 });
 // get the plugin bpf.cmpst.getPlugin("APlugin.html", null, "#pluginContainer", compositionReady);
 });</script>

要将插件节点导航到主 MODULE,我们将函数 connectToParentNode 调用到插件后面的代码,将父节点( topLevelNode ) 作为参数:

bpf.control("#pluginContainer").call("connectToParentNode", topLevelNode);

下面是插件( 来自 APlugin.html 文件)的代码:

 <div id="subTabPlugin"> <ul id="subTabs"> <li><a href="#subPage1">SUB Page1</a></li> <li><a href="#subPage2">SUB Page2</a></li></ul> <!-- page 1 message colored in red -->
 <div id="subPage1" style="color:green"> This is SUB page 1</div> <!-- page 2 message colored in blue -->
 <div id="subPage2" style="color:purple">This is SUB page 2</div></div><scriptsrc="../Scripts/jquery-1.8.3.min.js"></script><scriptsrc="../Scripts/jquery-ui-1.9.2.min.js"></script><scriptsrc="../Scripts/BPF/bpf.js"></script><scripttype="text/javascript"data-type="script-interface"> (function () {
 var pluginTabs;
 return {
 "postRender": function (compositionReadyEventBarrier) {
 // create tabs within the plugin and store the// tab object to be used later pluginTabs = this.currentDOM.find("#subTabPlugin").tabs();
 },
 "connectToParentNode": function (parentNode) {
 // create the navigation node at the plugin level and // connect it with the parent navigation node. bpf.nav.addJQTabsChild(parentNode, "page1", pluginTabs);
 }
 };
 })();</script>

方法 postRender 用超链接创建标签,并在插件中存储对它们的引用,方法是调用 pluginTabs = this.currentDOM.find("#subTabPlugin").tabs();

方法 connectToParentNode 负责连接父节点的导航。 这里方法从主 MODULE 调用。 ( 当然我们可以连接 postRender 函数中的导航节点,但是我们想通过创建一个单独的方法 connectToParentNode 来演示更多的组合功能。

参数化插件

ParameterizedPluginSample展示了如何创建具有不同外观的相同插件的实例。 SimpleCompositionSample一样,在 index.html file: 中包含的主 MODULE 中插入了相同插件的两个实例

// gets the plugin from file APlugin.html and inserts it into the // element pointed to by the selector"#APluginContainer1"bpf.cmpst.getPlugin(
 "APlugin.html",
 null,
 "#APluginContainer1",
 compositionReady,
 {
 fontSize: "50px",
 color: "blue" }
);// gets the plugin from file APlugin.html and inserts it into the // element pointed to by the selector"#APluginContainer2"bpf.cmpst.getPlugin(
 "APlugin.html",
 null,
 "#APluginContainer2",
 compositionReady,
 { // pass the object containing postRenderArguments fontSize: "20px", 
 color: "green" }
); 

bpf.cmpst.getPlugin 函数的最后一个参数是JSON对象,可以通过 this.postRenderArguments 对象在插件后面的postRender 函数中访问。 下面是插件的代码:

 {
 "postRender": function (compositionReadyEventBarrier) {
 var fontSize = this.postRenderArguments.fontSize; // get font size from the argumentsvar color = this.postRenderArguments.color; // get color from the argumentsthis.currentDOM.find("#aPlugin").css("font-size", fontSize);
 this.currentDOM.find("#aPlugin").css("color", color);
 } 
}

结果,我们有相同的插件文本显示不同的font-size 和颜色:

顶部插件实例的字体大小为 50px 和蓝色,而底部是一个- 20px 和绿色的颜色。

通用Silverlight插件示例

这里我介绍一个用于Silverlight应用程序的通用BPF插件。

Silverlight应用通常打包为一个带有. xap 扩展的文件。 为了将Silverlight应用嵌入到页面中,你必须拥有页面引用 Silverlight.js 文件( 它来自微软)。 你还必须在HTML应用程序中编写以下HTML代码:

<objecttype="application/x-silverlight-2"style="display: block; text-align: center; width: 100%; height: 100%"><paramname="source"value="../ClientBin/MySilverlightApp.xap"/><!-- url to Silverlight app --><paramname="onError"value="onSilverlightError"/><paramname="background"value="white"/><paramname="minRuntimeVersion"value="5.0.61118.0"/><paramname="autoUpgrade"value="true"/><ahref="http://go.microsoft.com/fwlink/?LinkID=149156&v=5.0.61118.0"style="text-decoration: none"><imgsrc="http://go.microsoft.com/fwlink/?LinkId=161376"alt="Get Microsoft Silverlight"style="border-style: none"/></a></object>

这是一大块HTML代码,我的想法是创建一个within插件,用于在HTML应用程序中使用它。 BPF插件的一个参数当然是应用程序的Silverlight URL。 定位应用程序,比如 宽度,高度,边距可能需要其他参数。

你可能想知道为什么Silverlight插件与其他的BPF插件是不同的。 为什么我们不能简单地使用前面小节中的信息创建一个参数化插件? 我的答案是"将问题重定向到 microsoft"。 在IE8和IE9上,<对象> 标记与JQuery的行为非常奇怪- 尝试使用JQuery解析对象标记的DOM,完全缩减它的大小。 除了 my,在 IE 代码被插入到主模块之前,必须设置Silverlight应用程序URL当代码已经插入代码时,将不会改变Silverlight应用程序。 事实上,这是因为这些问题,我添加了 preRender 函数到后面要调用的代码在插件添加到主模块。

这里示例的代码位于SilverlighPluginSample解决方案下。 下面是运行应用程序时得到的内容- ( 我可以让自己自己升级一周,这是我公司的company ):

为了解决我描述的上面 插件问题,我们改变了插件代码,代替了 <myobject>的<对象> 标记。 另外,为了将URL参数放置在正确的位置,我们使用"___xapfileplaceholder__ _"字符串作为占位符。 在 preRender 方法中将这些字符串替换为正确的字符串,然后将代码插入到主 MODULE 中。 我们使用 this.getDownloadedHtml() 函数( 在 preRender 方法中,它实际上返回完整的插件HTML代码) 获取了Silverlight插件的HTML代码。

下面是 SilverlightContainerPlugin.html file: 中的代码

<divclass="slContainerDiv"><myobjecttype="application/x-silverlight-2"style="display: block; text-align: center; width: 100%; height: 100%"><paramname="source"value="___XAPFilePlaceHolder___"/><paramname="onError"value="onSilverlightError"/><paramname="background"value="white"/><paramname="minRuntimeVersion"value="5.0.61118.0"/><paramname="autoUpgrade"value="true"/><ahref="http://go.microsoft.com/fwlink/?LinkID=149156&v=5.0.61118.0"style="text-decoration: none"><imgsrc="http://go.microsoft.com/fwlink/?LinkId=161376"alt="Get Microsoft Silverlight"style="border-style: none"/></a></myobject></div><scriptsrc="../Scripts/jquery-1.8.2.js"></script><scripttype="text/javascript"data-type="script-interface"> (function () {
 return {
 "preRender": function (preRenderArgs) {
 // get original html of the pluginvar originalHtml = this.getDownloadedHtml()
 // replace"myobject" string with"object" and // ___XAPFilePlaceHolder___ with the Silverlight app url parameter.var modifiedHtml = originalHtml.replace(/myobject/gi, "object").replace("___XAPFilePlaceHolder___", preRenderArgs.slAppUrl);
 // replace the HTML code of the pluginthis.setDownloadedHtml(modifiedHtml);
 },
 "postRender": function () {
 var args = this.postRenderArguments;
 var slContainerDiv = this.currentDOM.find(".slContainerDiv");
 // position the pluginif (args) {
 slContainerDiv.css("margin-top", args.marginTop);
 slContainerDiv.css("margin-bottom", args.marginBottom);
 slContainerDiv.css("width", args.width);
 slContainerDiv.css("height", args.height);
 }
 },
 }
 })();</script>

以下是插件在主模块中的插入方式:

bpf.cmpst.getPlugin(
 "SilverlightContainerPlugin.html",
 this,
 "#AWebProsLogo",
 compositionReady,
 { // post-render args marginTop: "auto",
 marginBottom: "auto",
 width: "150px",
 height: "150px" },
 { // pre-render args slAppUrl: "../ClientBin/AnimatedBanner.xap" }
); 

这个插件不是BPF库的一部分,但它非常有用,因此它在 github.com/npolyak/bpf/tree/master/BPF/UsefulBPFPlugins 发布。

使用 ASP.NET MVC的SPA服务器通信

这里我们展示了SPA如何与 ASP.NET MVC服务器通信。 前两节不同,本节不包含任何新内容- 它只是提供了一些与服务器通信的不同示例的教程。

简单获取请求示例从服务器返回字符串

就像在介绍中提到的,一旦SPA运行,它应该只加载应用程序的字符串或者JSON数据,而不加载HTML代码。 在SPAServerStringCommunications解决方案中可以找到一个非常简单的例子来发送和获取来自ASP服务器的一些字符串。

要启动解决方案,右键单击 index.html 文件并选择"在浏览器中查看"。 下面是应用程序启动后的外观:

进入可以编辑区域并单击按钮浏览器将向服务器发送 NAME 并从服务器接收字符串"你好 <你的名字>"并以红色显示 上面:

SPAServerStringCommunications项目被创建为 ASP.NET 4空项目。 创建项目后,我删除模型和视图文件夹,并将"打招呼"控制器添加到控制器文件夹。 我还从脚本文件夹中删除了所有脚本,并将JQuery安装为一个NuGet包。

HelloControler 只包含一个采用 name 参数并返回在 Json 函数中封装的字符串 "Hello" + name的方法 GetHelloMessage:

public ActionResult GetHelloMessage(string name)
{
 // AllowGet flag states that the method can be accessed// by GET HTTP request.return Json("Hello" + name, JsonRequestBehavior.AllowGet);
}

在这种情况下 Json 函数只是包装一个字符串,因此字符串被返回到服务器。

通过连接控制器 NAME,正斜杠和函数 NAME 来获取 GetHelloMessage 函数的relative URL: "/hello/gethellomessage".

项目客户端的html/javascript位于 index.html 文件中。 下面是客户端代码的HTML部分:

<body><div>Enter name</div><!-- name input --><inputid="nameInput"type="text"name="value"style="width:200px"/><!-- div to display the text returning from the server --><divid="serverText"style="min-height:50px;color:red;margin-top:10px"></div><!-- button to trigger communication with the server --><buttonid="getHelloStringButton">Get Server String</button></body>

下面是 JavaScript:

$(document).ready(function () {
 $("#getHelloStringButton").click(function () { // on button click// get name valuevar name = nameInput.value;
 // send query string to the server.///hello/gethellomessag is the relative url// second argument is the query string// 3rd argument is the function called on successful reply from the server $.get("/hello/gethellomessage", "name=" + name, function (returnedMessage) {
 // write the text coming fromt he server into// serverText element. $("#serverText").text(returnedMessage);
 });
 });
});

JavaScript代码使用jquery函数的$.get 向 relative url"/hello/gethellomessage" 发送请求,并调用函数将来自服务器的任何内容插入到 serverText 元素中。 查询( 它是来自客户端的获取请求的一部分)的结构是"name= <value-that-you'输入的>"。 查询中的name 将映射到控制器函数的name 参数,该值将成为控制器函数的name 参数值。

通常,当一个获取查询有多个参数时,应该将查询构建为 name1=<val1>&name2=<val1>... 在服务器端,控制器函数参数应该具有与获取查询相同的参数名。 在这种情况下,解析会自动发生,参数的值与查询中的值相同。

从服务器获取复杂对象

在前面的示例中,我们向服务器发送了一个字符串来获取字符串。 SPAComplexObjectGetter项目返回一个JSON对象,它具有一些结构,以响应get请求。 要运行项目,请在 Visual Studio 右键单击解决方案资源管理器中的index.html 文件,选择"在浏览器中查看"。 单击按钮"获取电影信息"后,你将看到以下内容:

前面的示例不同,SPAComplexObjectGetter项目具有一个 非空 模型文件夹,其中包含两个类: CinemaMovie。每个 Movie 都有一个 Title 和一个发布的YearCinema 具有 nameDescriptionMovie 对象的Collection,称为 MoviesCinema 默认构造函数创建一个 Cinema 对象,并将它的填充为包含两个 Movie 对象的数据。 本示例的目的是展示客户端如何请求这里电影信息,从服务器转移到服务器,并显示在客户端。

"控制器"文件夹包含只有一个方法的DataController.cs 文件:

public ActionResult GetCinema()
{
 // create a cinema object populated with data Cinema cinema = new Cinema();
 // send it in JSON form back to the clientreturn Json(cinema, JsonRequestBehavior.AllowGet);
}

DataController 中调用方法 GetCinema的relative URL是"/data/getcinema".

现在让我们看看 index.html 文件中的客户机代码。 HTML代码非常简单,由 div 添加电影信息,以及用于触发与服务器的交换的按钮:

<body><divid="cinemaInfo"style="margin-bottom:20px"></div><Buttonid="getCinemaInfoButton">Get Cinema Info</Button></body>

JavaScript代码更复杂,但就像前面示例的代码一样,它使用JQuery函数 $.get 向服务器发送:请求。

$(document).ready(function () {
 $("#getCinemaInfoButton").click(function () { // on button click// send query string to the server.///hello/gethellomessag is the relative url// second argument an empty string since our query does not have parameters// 3rd argument is the function called on successful reply from the server $.get("/data/getcinema", "", function (cinema) {
 // clear the info displayed from // the previous click (if any). $("#cinemaInfo").contents().remove();
 // add cinema information to"cinemaInfo" div element $("#cinemaInfo").append("Name:" + cinema.Name);
 $("#cinemaInfo").append("<div></div>");
 $("#cinemaInfo").append("Description:" + cinema.Description);
 $("#cinemaInfo").append("<div style='margin-top:5px'></div>");
 $("#cinemaInfo").append("Movies:");
 $("#cinemaInfo").append("<div></div>");
 // add information about individual moviesfor (var idx = 0; idx < cinema.Movies.length; idx++) {
 var movie = cinema.Movies[idx];
 $("#cinemaInfo").append
 (
 "" +
 "Title:" + movie.Title +
 "," +
 "year:" + movie.Year
 );
 $("#cinemaInfo").append("<div></div>");
 }
 });
 });
});

由于我们的请求没有任何参数,所以函数 $.get的查询部分是空的。

从服务器返回的对象由变量 Cinema 表示,变量是调用请求成功时调用的函数的输入变量。 在这个函数中,我们形成文本并将它的附加到"cinemainfo"div标签。 返回的Cinema 对象具有字段 nameDescription 以及称为 MoviesMovie 对象的array。 每个 Movie 对象都有 TitleYear 字段。 实际上,返回的JSON对象模仿 Cinema 类型的服务器端 C# 对象。

通过打开浏览器 比如 Chrome 并将URL键入控制器函数 GetCinema,可以查看从服务器返回的JSON对象: "http://localhost:29863/data/getcinema".,你将在浏览器中看到返回的JSON字符串。

将复杂数据对象发布到服务器

获取前两个示例中提供的请求作为URL的一部分构建查询,如果要将一组名称和值发送到服务器。 当需要从客户端传输复杂JSON对象到服务器时,应使用POST请求。

在本节中我们将考虑同一个 Cinema 模型,只在客户机上创建模型,发送给服务器,然后显示在客户端。

这里子区域的样例位于SPA_POSTingObjectToServer解决方案下。

服务器具有相同的模型类: 由于数据来自客户端,CinemaMovieCinema 默认值是空的。 ChangeCinemaDataDataController 方法通过将另一个电影"火星的安妮"添加到电影的Collection 来改变来自客户端的数据。 然后它将修改后的Cinema 对象返回给客户端。

为了解决服务器故障的情况,我还让服务器请求每次都失败。 为了标记失败,服务器将 Response StatusCode更改为字符串,并将"serverError"返回为字符串。 下面是服务器代码:

staticbool evenRequest = true;// url=/data/changecinemadatapublic ActionResult ChangeCinemaData(Cinema cinema)
{
 evenRequest =!evenRequest;
 // report error on every even requestif (evenRequest)
 {
 Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
 return Json("serverError");
 }
 // add movie"Anne of Planet Mars" // to Cinema data coming from the client // and return the modified Cinema data// back to the client cinema.Movies.Add
 (
 new Movie
 {
 Title = "Anne of Planet Mars",
 Year = 3000 }
 );
 return Json(cinema);
}

要注意,服务器将把来自客户端的数据解释为 Cinema 类型的对象,只要对象的结构正确。

客户端代码位于 index.html 文件中。 HTML包含两个 div 标记- 一个用于原始 Cinema 对象的视觉表示,另一个用于保存从服务器返回的Cinema 对象。 在它们之间还有一个按钮用来触发与服务器的交换。 服务器回复的可视表示形式以红色表示。

<body>
 <!-- Here we place the original cinema data-->
 <div id="originalCinemaInfo" style="margin-bottom:20px"></div> <!-- clicking this button sends the cinema object to the server
 and places the result returning from the server under 
 serverCinemaInfo tag -->
 <Button id="changeCinemaInfoFromServerButton">Change Cinema Info via Server</Button> <div style="color:blue;margin-top:20px">Reply from the Server:</div> <!-- here we create a visual representation for Cinema object 
 coming from the server -->
 <div id="serverCinemaInfo" style="margin-bottom:20px;color:Red"></div></body>

下面是JavaScript代码:

$(document).ready(function () {
 // we create a Cinema object on the clientvar cinema = {
 Name: "Mohawk Mall Cinema",
 Description: "An OK Cinema",
 Movies: [
 {
 Title: "Anne of Green Gables",
 Year: 1985 },
 {
 Title: "Anne of Avonlea",
 Year: 1987 }
 ]
 };
 // display the visual representation of the original // cinema object displayCinemaInfo($("#originalCinemaInfo"), cinema);
 var cinemaJsonString = JSON.stringify(cinema);
 $("#changeCinemaInfoButton").click(function () {
 $.ajax({
 type: "POST",
 dataType: "json",
 contentType: "application/json; charset=utf-8;",
 url: "/data/changecinemadata",
 data: cinemaJsonString,
 success: function (changedCinemaFromServer) {
 // display the visual representation of the server modified // cinema object displayCinemaInfo($("#serverCinemaInfo"), changedCinemaFromServer);
 },
 error: function (resultFromServer) {
 // clear the previous contet of serverCinemaInfo element $("#serverCinemaInfo").contents().remove();
 // display the error message $("#serverCinemaInfo").append("Server Error");
 }
 });
 });
});

我们使用通用 $.ajax 方法向服务器发送POST请求。 这个方法允许我们在服务器错误和请求的内容类型时指定要调用的函数。

在消息正文中发送的数据是我们的Cinema JSON对象被 JSON.stringify 函数转换成字符串。 有些老浏览器仍然不支持这个函数,因此,对 json2.min.js 文件的引用被添加到。 这里文件来自json2软件包的NuGet安装。

以下是从服务器成功回复后SPA的外观:

下面是出现故障时的情况:

摘要

本文讨论了单页应用程序( 温泉)。 我们定义温泉并展示大量的温泉样本。 本文中的所有信息和功能都是我希望知道的,并且在几个月前我刚开始构建温泉浴场时已经。

本文介绍了一个新的JavaScript库调用。 这里库用于两个目的:

  • 导航- 使用浏览器url和导航按钮导航你的SPA。
  • 组合- 展示如何使用BPF库从客户端的不同HTML页面组成 SPA。
最后,我希望BPF库将包含许多其他功能。

本文最后,我将讨论SPA应用程序如何与服务器联系。

确认

I Pluralsight表示,我在单页面应用。Web API。Knockout和 jQuery 介绍了 SPA SPA。 从那以后我就把网站建成温泉。 感谢他和其他Pluralsighters的温暖。

COM  Server  COMM  sam  Html5  Basic  
相关文章