了解VS2013 MVC 5 SPA模板

Ser*_*eit 14 sammy.js knockout.js single-page-application visual-studio-2013 asp.net-mvc-5

我已经开始使用Visual Studio 2013附带的MVC 5的单页应用程序模板.我非常熟悉Knockout.js,虽然我不在,但Sammy.js我一直在阅读它并且它似乎不太适合这一切都很复杂.

我似乎无法理解的是MVC 5 SPA模板如何结合这些技术,或者Visual Studio团队为模板所考虑的内容作为示例; 除了其他方面,模板提供了一个home.viewModel.js应该作为起点的文件,但我似乎无法理解如何使用Sammy.js路径添加更多视图.如果只有他们提供了第二个局部视图和视图模型.

我的问题

所以,短篇小说,我真正的问题是,

  1. 如何以#users模仿提供的方式显示链接到路径的局部视图home.viewmodel.js,以便我可以向后导航#home#users?将在什么Sammy.js路线定义看起来像users.viewModel.js
  2. 我是否需要做一些特殊的事情来启用浏览器后退按钮,或者只要我正确定义了我的路由它就能正常工作?
  3. 它是我还是这个模板感觉是一个半生不熟的例子?

以下代码仅用于额外的参考/上下文,但为了回答问题,可能没有必要.


一些背景

假设我创建了一个局部视图,_Users.cshtml由一个UserControllerMVC控制器而不是WebAPI控制器提供服务,并且我想通过Sammy.js路径显示该局部视图,为此我创建了一个users.viewModel.js.现在...

提供的Index.cshtml视图如下所示:

@section SPAViews {
   @Html.Partial("_Home")
}
@section Scripts{
   @Scripts.Render("~/bundles/knockout")
   @Scripts.Render("~/bundles/app")
}
Run Code Online (Sandbox Code Playgroud)

我认为这意味着应用程序"shell"页面,其中部分视图的其余部分将被加载以替换_Home部分内容 .问题是home.viewmodel.jsSammy路由初始化时没有传入将保存内容的元素的选择器,就像这样

Sammy(function () {
    this.get('#home', function () {
    // more code here
}
Run Code Online (Sandbox Code Playgroud)

而不是,例如

Sammy("#content", function () {
    this.get('#home', function () {
    // more code here
}
Run Code Online (Sandbox Code Playgroud)

我是否应该从一开始就将_Users部分放在旁边_Home,以便Index视图看起来像这样?

@section SPAViews {
   @Html.Partial("_Home")
   @Html.Partial("_Users")
}
@section Scripts{
   @Scripts.Render("~/bundles/knockout")
   @Scripts.Render("~/bundles/app")
}
Run Code Online (Sandbox Code Playgroud)

当然,这将同时显示两个视图,这不是我们想要的.

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
});
Run Code Online (Sandbox Code Playgroud)

我已尝试使用该Sammy.js swap方法,但由于我的_Users视图是部分的,Sammy并且未设置为对特定元素执行操作,因此整个页面被替换...并且浏览器的后退按钮似乎不起作用.

对于大量的文本感到抱歉,如果这是一个非常微不足道的问题.令我困扰的是,即使在完成文档之后,我似乎无法自己解决这个问题.

Ste*_*fan 8

我自己磕磕绊绊地设法应用自己的小"黑客"来解决这个问题.

将"旧"模板与新模板进行比较时,我注意到Sammy.js模板中嵌入了更多模板.虽然这是一件好事,但原始的开箱即用的敲门with装订显示你的观点已被打破.

要应用此修复程序,首先需要了解敲除with绑定.在默认home视图中有

<!-- ko with: home-->
Run Code Online (Sandbox Code Playgroud)

home view只有在成员home出现时才能确保可见性的声明.在这种情况下,全名将是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];
});
Run Code Online (Sandbox Code Playgroud)

如您所见,它始终从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);
});
Run Code Online (Sandbox Code Playgroud)

如果当前视图不是目标视图,则返回null viewItem.这对于淘汰赛的with结合至关重要.

对这两个模板的进一步检查显示了更好的集成sammy.js.它的一个关键部分在于viewmodels(home.viewmodel.js):

Sammy(function () {
   this.get('#home', function () {
    });
    this.get('/', function () { this.app.runRoute('get', '#home') });
});
Run Code Online (Sandbox Code Playgroud)

由于sammy.js处理导航,前面提到的viewItem,封装的app.view()未设置.对于淘汰赛绑定而言,这也是至关重要的.

所以,我提出的修复方法如下:

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];
});
Run Code Online (Sandbox Code Playgroud)

并在每个自定义视图模型中:

home.viewmodel.js

Sammy(function () {
    this.get('#home', function () {
         app.view(self);  //this line is added
    });
    this.get('/', function () { this.app.runRoute('get', '#home') });
});
Run Code Online (Sandbox Code Playgroud)

免责声明: 由于我刚刚启动并运行,我没有时间分析任何不必要的副作用.此外,改变默认模板的核心并不是很令人满意,因此欢迎更好的解决方案.


Sha*_*son 4

当然,这会同时显示两个视图,这不是我们想要的。

实际上,在许多情况下,这正是您想要的(或者更确切地说,您希望它们的存在并控制它们的可见性。)除了视图模型上的可见性属性和一些 JS 帮助器方法(或类)来显示/隐藏您的视图(通过视图模型引用,通常也与特定的 url 相关联。)

_Home.cshtml

<!-- ko with: $root.home -->
<div data-bind="visible: isVisible">
    <!-- view markup/etc here -->
</div>
<!-- /ko -->
Run Code Online (Sandbox Code Playgroud)

伪: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) {/**/};
}
Run Code Online (Sandbox Code Playgroud)

这些将处理与History API路由/深度链接的集成,以及剔除以显示/隐藏 DOM 元素(通过 IsVisible 绑定)。addViewModel当然,上面的“registerView”将替换默认的脚手架。在我看来,所有这些都是垃圾。

多年来我一直在 MVC 框架之上开发 SPA。MVC5 SPA 模板很有趣,但它也有问题。正确的深度链接、视图模型初始化和视图管理是更明显的问题,但是只要花一点力气,您就可以轻松地编写您需要的代码。

我也发现该SPAViews部分无用,并且更喜欢使用 RenderBody 进行部分交付,这需要对_Layout.cshtml. 毕竟,对于足够大的 SPA,无论如何,您最终都会在单个页面/视图中交付几乎所有主要视图(在 SPA 中很少看到 Ajax 部分,即使是大型 SPA。)并且唯一提供的价值部分SPAViews是放置在 _Layout 中,有效地复制 RenderBody() 的功能(因为 SPA 的主体始终是不可见视图的集合。)