了解VS2013 MVC 5 SPA模板

时间:2014-09-22 09:49:20

标签: knockout.js visual-studio-2013 asp.net-mvc-5 single-page-application sammy.js

我已经开始使用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 {1}}? Sammy.js中的users.viewModel.js路由定义会是什么样的?
  2. 我是否需要做一些特别的事情来启用浏览器后退按钮,或者只要我正确定义路线就能正常工作?
  3. 我或者这个模板是一个半生不熟的例子吗?
  4. 以下代码仅用于额外的参考/上下文,但为了回答问题,可能没有必要。


    某些上下文

    假设我创建了一个部分视图_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未设置为针对特定广告采取行动元素整个页面被替换...浏览器的后退按钮似乎无法正常工作。

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

3 个答案:

答案 0 :(得分:8)

我自己磕磕绊绊地设法应用了自己的小黑客'解决这个问题。

比较年龄较大的'使用新模板的模板,我注意到Sammy.js更多地嵌入在模板中。虽然这是一件好事,但原始开箱即用的淘汰赛with绑定以显示您的观点已被打破。

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

<!-- ko with: home-->

语句,只有在成员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];
});

如您所见,它始终从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正在处理导航,因此未设置封装在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];
});

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

home.viewmodel.js

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

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

答案 1 :(得分:3)

  

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

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

_Home.cshtml

<!-- ko with: $root.home -->
<div data-bind="visible: isVisible">
    <!-- view markup/etc here -->
</div>
<!-- /ko -->

Pseudo: 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绑定)。以上&#39; registerView&#39;当然,它会从默认的脚手架中取代addViewModel。所有这些,IMO,都是垃圾。

我已经在MVC框架之上开发了多年的SPA。 MVC5 SPA模板很受欢迎,但它有问题。正确的深层链接,视图模型初始化和视图管理是更明显的问题,但有一点肘部油脂,您可以轻松编写您需要的代码。

我还发现SPAViews部分无用,并且更喜欢使用RenderBody进行部分投放,这需要对_Layout.cshtml进行一些修改。毕竟,对于一个足够大的SPA,你最终会在一个页面/视图中提供几乎所有的主要视图(很少看到SPA中的Ajax部分,甚至是大型的。)而且是唯一的。值SPAViews部分提供了_Layout中的位置,有效地复制了RenderBody()的功能(因为SPA的主体总是将是不可见视图的集合。)

答案 2 :(得分:1)

是的,它确实令人困惑,而且似乎没有太多的文档。我怀疑做事方式的数量是如此之大,以至于他们不得不将其烘焙一半。 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以某种方式重新排列页面)。除了上面的简单方法,还不确定应该做得最好。