理解VS2013 MVC 5 SPA模板

14

我开始使用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之间来回切换?在users.viewModel.js中,Sammy.js路由定义会是什么样子?
  2. 我需要做任何特殊的事情才能启用浏览器的后退按钮,或者只要我正确定义了我的路由,它就会自动工作?
  3. 是我还是这个模板感觉像是一个半成品的示例?

以下代码仅供额外参考/上下文,但可能不是回答问题所必需的。


一些背景信息

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

提供的Index.cshtml视图如下:

@section SPAViews {
   @Html.Partial("_Home")
}
@section Scripts{
   @Scripts.Render("~/bundles/knockout")
   @Scripts.Render("~/bundles/app")
}

我猜这是指应用程序的“外壳”页面,其他部分视图将加载以替换 _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.jsswap方法,但由于我的_Users视图是一个部分视图,而且Sammy没有设置针对特定元素的操作,所以整个页面被替换了...而且浏览器的后退按钮似乎不起作用。

很抱歉文本量如此之大,如果这是一个非常琐碎的问题,也请谅解。我很困扰自己似乎无法自己解决它,即使已经查阅了文档。


你好Sergi,我有和你一样的问题,但是下面的答案并没有真正回答它们。你最终解决了这个问题吗? - Ezi
嗨@Ezi,不是很好。在很短的时间内,我决定放弃这种方法,并使用Durandal编写应用程序...目前,我只使用AureliaASP.NET 5结合使用 :) - Sergi Papaseit
谢谢你的信息,我猜我不得不改变方法,因为我无法弄清楚那个问题。微软决定将该模板内置到系统中,这真是有点奇怪。 - Ezi
3个回答

8

我自己也遇到了这个问题,但是我成功地应用了自己的小‘技巧’来解决它。

与‘老’模板相比较,我注意到新模板中 Sammy.js 更加嵌入在模板中。虽然这是件好事,但是原始的knockout with 绑定来展示你的视图已经不起作用了。

为了应用修复程序,首先需要了解knockouts 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。这对于knockout的with绑定非常重要。

进一步检查两个模板,显示与sammy.js更好的集成。其中关键部分在视图模型(home.viewmodel.js)中:

Sammy(function () {
   this.get('#home', function () {
    });
    this.get('/', function () { this.app.runRoute('get', '#home') });
});

由于 sammy.js 处理导航,前面提到的封装在 app.view() 中的 viewItem 没有被设置。这对于 knockout 绑定再次至关重要。

因此,我的建议修复方法如下:

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') });
});

声明:由于我刚刚开始使用这个程序,所以我没有时间分析任何不良副作用。此外,更改默认模板的核心并不感到非常令人满意,因此欢迎更好的解决方案。


3

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

实际上,在许多情况下,这正是您想要的(或者更确切地说,您想要它们存在并控制它们的可见性)。除了在视图模型上设置可见性属性以及一些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的集成以进行路由/深度链接,并使用knockout来显示/隐藏DOM元素(通过IsVisible绑定)。当然,上述“registerView”将替换默认脚手架中的addViewModel。在我看来,所有这些都是垃圾。

我已经在MVC框架上开发SPA几年了。 MVC5 SPA模板是一个不错的兴趣展示,但它存在问题。适当的深度链接,视图模型初始化和视图管理是更明显的问题,但只要稍加努力,就可以轻松编写所需内容。

我还发现SPAViews部分毫无用处,并且更喜欢使用RenderBody进行部分交付,这需要对_Layout.cshtml进行一些修改。毕竟,对于足够大的SPA,您最终会在单个页面/视图中传递几乎所有主要视图(即使是大型SPA也很少看到Ajax局部视图)。而SPAViews部分提供的唯一价值是在_Layout中的位置,有效地复制了RenderBody()的功能(因为您的SPA的正文始终是一组不可见视图)。


或者像这样:https://dev59.com/8WUq5IYBdhLWcg3wVu-D - decades
在提供的示例中,您可以看到data-bind="visible: isVisible" -- 不希望将 visible: truevisible: false 绑定,因为这可能会导致在 ko.applyBindings 执行期间出现任意的 UI 元素显示。为了控制“初始加载闪烁”,您总是希望将静态 CSS应用于最外层的父元素。样式应该静态应用,但动态地删除。您应该从showView的回调函数(如上)中删除它,确保用户在没有初始化视图的情况下不会看到任何内容。 - Shaun Wilson

1

是的,这确实令人困惑,似乎文档不太多。我猜测做事情的方式有很多种,他们不得不将其留下一半。就我而言,我通过将以下内容添加到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”视图模型绑定来设置事物(更改视图,视图可观察对象以某种方式重新排列页面)。除了上述简单方法之外,不确定最佳的实现方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接