[英]Understanding the VS2013 MVC 5 SPA template
我已经开始使用Visual Studio 2013附带的MVC 5单页面应用程序模板。我对Knockout.js
非常熟悉,虽然我没有和Sammy.js
在一起, Sammy.js
我一直在阅读它它看起来并不那么复杂。
我似乎无法理解的是MVC 5 SPA模板如何结合这些技术,或者Visual Studio团队为模板所考虑的内容作为示例; 该模板提供了一个home.viewModel.js
文件,该文件应该作为起点,但我似乎无法理解如何使用Sammy.js
路由添加更多视图。 如果只有他们提供了第二个局部视图和视图模型。
所以,短篇小说,我真正的问题是,
home.viewmodel.js
的方式显示链接到路径#users
的局部视图,以便我可以从#home
向后导航到#users
? Sammy.js
路由定义在users.viewModel.js
中users.viewModel.js
? 以下代码仅用于额外的参考/上下文,但为了回答问题,可能没有必要。
假设我创建了一个部分视图_Users.cshtml
,由UserController
,它是一个MVC控制器,而不是一个WebAPI
控制器,我希望通过Sammy.js
路由显示该部分视图,为此我创建了一个users.viewModel.js
。 现在...
提供的Index.cshtml
视图如下所示:
@section SPAViews {
@Html.Partial("_Home")
}
@section Scripts{
@Scripts.Render("~/bundles/knockout")
@Scripts.Render("~/bundles/app")
}
我认为这意味着应用程序“shell”页面,其中部分视图的其余部分将被加载以替换_Home
部分的内容。 问题是在home.viewmodel.js
上初始化Sammy
路由而不传入将保存内容的元素的选择器,就像这样
Sammy(function () {
this.get('#home', function () {
// more code here
}
而不是,例如
Sammy("#content", function () {
this.get('#home', function () {
// more code here
}
我应该放置_Users
一起局部_Home
从一开始就使得Index
视图看起来像这样?
@section SPAViews {
@Html.Partial("_Home")
@Html.Partial("_Users")
}
@section Scripts{
@Scripts.Render("~/bundles/knockout")
@Scripts.Render("~/bundles/app")
}
当然,这将同时显示两个视图,这不是我们想要的。
我的users.viewmodel.js
看起来像这样:
function UsersViewModel(app, dataModel) {
var self = this;
Sammy(function () {
this.get('#users', function () {
// the following line only makes sense if _Users is not
// called from Index.cshtml
//this.load(app.dataModel.shoppingCart).swap();
});
});
return self;
}
app.addViewModel({
name: "Users",
bindingMemberName: "users",
factory: UsersViewModel
});
我已经尝试过使用Sammy.js
swap
方法,但由于我的_Users
视图是部分的,并且Sammy
没有设置为对特定元素进行操作,因此整个页面被替换...并且浏览器的后退按钮似乎没有工作。
对于大量的文本感到抱歉,如果这是一个非常微不足道的问题。 令我困扰的是,即使在完成文档之后,我似乎无法自己解决这个问题。
我自己磕磕绊绊地设法应用自己的小“黑客”来解决这个问题。
在比较“旧”模板和新模板时,我注意到Sammy.js
更多地嵌入模板中。 虽然这是一件好事,但原始开箱即with
装订结合显示你的观点已被打破。
要应用此修复程序,首先需要了解with
绑定的敲除。 在默认的home
视图中有
<!-- ko with: home-->
只有在成员home
存在时才应确保home view
可见性的声明。 在这种情况下,全名是app.home
如果我们检查这个成员名称,我们会看到它是一个定义的计算成员,例如(app.viewmodel.js):
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
if (!dataModel.getAccessToken()) {
//omitted for clearity
if (fragment.access_token) {
//omitted for clearity
} else {
//omitted for clearity
}
}
return self.Views[options.name];
});
如您所见,它始终从Views
集合返回完整的初始化视图。
如果我们将其与旧模板进行比较,我们可以在此处看到更改:
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
if (self.view() !== viewItem) {
return null;
}
return new options.factory(self, dataModel);
});
如果当前视图不是目标viewItem
则返回null。 这对于with
约束力的淘汰赛至关重要。
对这两个模板的进一步检查显示与sammy.js
的更好集成。 它的一个关键部分在于viewmodels(home.viewmodel.js):
Sammy(function () {
this.get('#home', function () {
});
this.get('/', function () { this.app.runRoute('get', '#home') });
});
由于sammy.js
正在处理导航,因此未设置封装在app.view()
的前面提到的viewItem
。 对于淘汰赛绑定来说,这也是至关重要的。
所以,我提出的修复方法如下:
app.viewmodel.js
// Add binding member to AppViewModel (for example, app.home);
self[options.bindingMemberName] = ko.computed(function () {
if (!dataModel.getAccessToken()) {
//omitted for clearity
if (fragment.access_token) {
//omitted for clearity
} else {
//omitted for clearity
}
}
///change start here
if (self.view() !== viewItem) {
return null;
}
return self.Views[options.name];
});
并在每个自定义视图模型中:
home.viewmodel.js
Sammy(function () {
this.get('#home', function () {
app.view(self); //this line is added
});
this.get('/', function () { this.app.runRoute('get', '#home') });
});
免责声明:由于我刚刚启动并运行,我没有时间分析任何不必要的副作用。 此外,改变默认模板的核心并不是很令人满意,因此欢迎更好的解决方案。
当然,这将同时显示两个视图,这不是我们想要的。
实际上,在许多情况下,这正是您想要的(或者,您希望他们的存在并控制其可见性。)除了viewmodel上的visibility属性和一些JS帮助器方法(或类)来显示/隐藏您的视图(通过视图模型引用,通常也与特定URL相关联。)
伪_Home.cshtml :
<!-- ko with: $root.home -->
<div data-bind="visible: isVisible">
<!-- view markup/etc here -->
</div>
<!-- /ko -->
伪: app.viewmanager.js
MyViewManager = function () {
this.registerView = function(route, selector, viewmodel) {/**/};
this.showView = function(selector, callback) {};
this.cancelView = function(callback) {/**/};
this.showModal = function(selector, callback) {/**/};
this.closeModal = function(selector, callback) {/**/};
}
这些将处理与History API
集成以进行路由/深度链接,以及用于显示/隐藏DOM元素的淘汰(通过IsVisible绑定)。 当然,上面的'registerView'将取代默认支架中的addViewModel
。 所有这些,IMO,都是垃圾。
多年来,我一直在MVC框架之上开发SPA。 MVC5 SPA模板很受欢迎,但它有问题。 正确的深层链接,视图模型初始化和视图管理是更明显的问题,但有一点肘部油脂,您可以轻松编写您需要的代码。
我还发现SPAViews
部分没用,并且更喜欢使用RenderBody进行部分传递,这需要对_Layout.cshtml
一些修改。 毕竟,对于一个足够大的SPA,你最终会在一个页面/视图中提供几乎所有的主要视图(很少见到SPA中的Ajax部分,甚至是大的。)并且SPAViews
部分提供的唯一值是_Layout中的位置,有效地复制了RenderBody()的功能(因为SPA的主体总是一组不可见的视图。)
是的,它确实令人困惑,而且似乎没有太多的文档。 我怀疑做事方式的数量是如此之大,以至于他们不得不将其烘焙一半。 FWIW我通过简单的页面导航完成,将以下内容添加到app.viewmodel
navigator = function () {
self.view(viewItem); //THIS IS ADDED
window.location.hash = options.bindingMemberName;
};
在index.cshtml我有这个:
@section SPAViews {
<!-- ko if: app.view() === app.Views.Login -->
@Html.Partial("_login")
<!-- /ko -->
<!-- ko if: app.view() === app.Views.MyDashboard -->
@Html.Partial("_myDashboard")
}
我认为他们可能期望你设置的方式是让整体视图状态以某种方式绑定到'app'viewmodel(更改视图,视图observable以某种方式重新排列页面)。 除了上面的简单方法,还不确定应该如何做到最好。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.