【问题标题】:How can I apply KnockoutJS on a master page and an individual page?如何在母版页和单个页面上应用 KnockoutJS?
【发布时间】:2014-01-03 23:24:33
【问题描述】:

我被一个较旧的 WebForms 项目困住了,我想知道是否有适合我的场景的推荐方法。

目标

  • 我在使用 KnockoutJS 绑定的模式对话框中有一个反馈表。
  • 我希望通过网站页脚中的链接在所有页面上提供反馈表。
  • 我希望其他几个页面也使用淘汰赛以及他们自己的单独脚本和绑定,而不考虑模式中的反馈表单绑定。
  • 我有一些页面根本不使用淘汰赛。我希望他们不必插入代码来完成此操作。
  • 如果可能的话,我希望避免使用全局变量,而使用命名空间的 JavaScript。
  • 本质上,我希望页面上的视图模型和反馈视图模型不知道彼此的存在。

当前设置

  • 我们的页脚链接位于 Site.master 文件中,因此我放置了 Feedback.js 脚本和具有绑定的模式的 div。所以在母版页上,我打电话给ko.applyBindings(vm, referenceToFeedbackDiv),它可以很好地连接反馈表。
  • 我们的各个页面偶尔会有一个淘汰视图模型,因此他们可能会调用 ko.applyBindings(vm),因为据他们所知,他们希望将 vm 应用于整个页面。

问题

  • 这会导致剔除冲突,因为通过 Site.master 调用将一个 vm 应用于反馈表单,而其后的页面将一个 vm 应用于整个正文。

问题

我怎样才能使这两个功能——一个跨所有使用剔除页面的模式对话框和单个剔除页面——协调工作?

(当前)代码中的问题演示

请记住,问题是我希望能够拥有一个仅适用于整个客户端站点的反馈 div 的反馈 VM,并且我希望能够应用其他不需要知道任何内容的 VM关于反馈 vm。

母版页文件 (Site.Master) -- 摘录

这是在每一页上:

<div class="page">
    <div class="main">
        <asp:ContentPlaceHolder ID="MainContent" runat="server" />
    </div>
</div>
<div class="footer">
    &copy; <%=DateTime.Now.Year.ToString() %> Company, Inc. |  <a href="/">Home</a> | <a
        href="/about.aspx">About</a> | 

    <!-- begin feedback area -->

    <span id="FeedbackArea">
        <a data-bind="click: showModal">Feedback</a>

        <div id="feedback-modal" title="What's on your mind?">
            <div class="btn-group" id="feedbackButtonGroup">
                <button class="btn" data-bind="click: UpdateFeedbackType" style="padding-top: 6px;">
                    <i class="fa fa-warning fa-2x fa-align-center"></i>

                    <br />
                    <span>Problem</span>

                </button>
                <button class="btn" data-bind="click: UpdateFeedbackType" style="padding-top: 6px;">
                    <i class="fa fa-question-circle fa-2x fa-align-center"></i>

                    <br />
                    <span>Question</span>

                </button>
                <button class="btn" data-bind="click: UpdateFeedbackType" style="padding-top: 6px;">
                    <i class="fa fa-lightbulb-o fa-2x fa-align-center"></i>

                    <br />
                    <span>Suggestion</span>

                </button>
                <button class="btn" data-bind="click: UpdateFeedbackType" style="padding-top: 6px;">
                    <i class="fa fa-thumbs-o-up fa-2x fa-align-center"></i>

                    <br />
                    <span>Praise</span>

                </button>
                <button class="btn" data-bind="click: UpdateFeedbackType" style="padding-top: 6px;">
                    <i class="fa fa-info-circle fa-2x fa-align-center"></i>

                    <br />
                    <span>General</span>

                </button>
            </div>
            <br />
            <br />
            <textarea rows="5" placeholder="Enter feedback here" data-bind="value: feedbackText, valueUpdate: 'afterkeydown'"></textarea>
            <br />
            <br />
            <button>Send Feedback</button>&nbsp;&nbsp;
            <button data-bind="click: CancelFeedback">Cancel</button>
            <h3>Other Information: </h3>

            <ul>
                <li><strong>Feedback Type:</strong> <span data-bind="text: feedbackType"></span></li>
                <li><strong>Current URL:</strong> <span data-bind="text: pageUserIsOn"></span></li>
                <li><strong>Current User: </strong><%=hdnLoggedInUsername.Value %></li>
                <li><strong>Current Client: </strong>[Not yet captured]</li>
                <li><strong>Current Tab: </strong>[Not yet captured]</li>
            </ul>
        </div>
    </span>
    <!-- End feedback area --> 
</div>

Feedback.JS -- 这也包含在每个页面中

...FeedbackVM 的命名空间定义:

var FeedbackNamespace = FeedbackNamespace || {};

..命名空间本身的定义:

FeedbackNamespace = {
    ViewModel: function () {
       // etc. etc. 
    }
};

...以及 VM 变量的声明以及将其连接到 document.ready():

var FeedbackVM;
$(document).ready(function () {
    FeedbackVM = new FeedbackNamespace.ViewModel();
    ko.applyBindings(FeedbackVM, $('#FeedbackArea')[0]); 
    FeedbackVM.Start();
    log('FeedbackVM started');
});

其他没有 Knockout / JS 的页面

其他页面上可能有也可能没有任何 javascript,更不用说淘汰赛了。在这些页面上,FeedbackVM 当前运行良好。

具有自己的 Knockout ViewModel 的页面

这些页面将拥有自己的命名空间 JS 文件和自己的 document.ready() 事件,该事件创建一个名为 invoiceUploaderVM = new InvoiceUploader.ViewModel() 的虚拟机,然后调用 ko.applyBindings(invoiceUploaderVM)

这就是我们遇到麻烦的地方。

更新:一种可能的方法和一个小麻烦

在 Site.master 页面中,我将整个页脚包裹在 "stopBindings: true" div 中:

<div data-bind="stopBindings: true">
    <div class="footer" id="footerDiv">
        <!-- Feedback Viewmodel stuff in here -->
    </div>
</div>

我已将stopBindings 定义为:

ko.bindingHandlers.stopBindings = {
    init: function () {
        return { controlsDescendantBindings: true };
    }
};

我的 Feedback.js 文件作为全局 JS 文件的一部分加载到每个页面上,具有:

var FeedbackNamespace = FeedbackNamespace || {};
FeedbackNamespace = {
    // defines viewmodel, etc. etc.
};
var FeedbackVM;
$(document).ready(function () {
    FeedbackVM = new FeedbackNamespace.ViewModel();
    ko.applyBindings(FeedbackVM, $('#footerDiv')[0]); 
    FeedbackVM.Start();
    log('FeedbackVM started');
});

这种方法效果很好——只要没有绑定其他视图模型。在从我的母版页继承的页面上,我可能有类似的内容:

$(document).ready(function () {
    'use strict';
    vm = new invoiceUploader.ViewModel();
    ko.applyBindings(vm);
});

我希望这样:

  • 设置应用于 div 的反馈视图模型,停止其他视图模型
  • 设置 invoiceUploader 视图模型并将其应用于正文(然后由 stopBindings div 停止)

但是,我在加载子页面时遇到错误:

注释行以应用反馈绑定使这项工作再次正常。

我做错了什么?

【问题讨论】:

  • 您找不到“click: function() { return ToggleHelp }”的绑定的问题是由于范围界定问题。您需要更改绑定字符串,因此只需“单击:ToggleHelp”即可。或者从knockoutjs传入的回调数据中获取数据。见:knockoutjs.com/documentation/click-binding.html

标签: javascript asp.net knockout.js webforms master-pages


【解决方案1】:

我想我会将您的模态视图模型放在一个全局对象中,然后对它做任何您需要做的事情除了在共享脚本中应用绑定

window.feedbackModal = {
    foo: ko.observable("Whatever you need to do here"),
    bar: ko.observable("assuming it can be done the same on every page")
};

然后在 Site.master 中

<div class="feedback-modal" data-bind="with: feedbackModal">
  <p data-bind="text: foo"></p>
  <p data-bind="text: bar"></p>
</div>

在每个单独页面的脚本中:

function ViewModel() {
  this.individualProperty = ko.observable(true);
  this.specificAction = function() { /* do something specific to this page */ };

  this.feedbackModal = window.feedbackModal;
}

ko.applyBindings(new ViewModel());

所以 window.feedbackModal 可能是未定义的,它不会给你带来问题,但如果你 ko.applyBindings,你必须在视图模型中公开一个 feedbackModal 属性,否则应用这些绑定会出错。

当然,有更聪明的方法可以实现这个基本概念,以便最好地适应你的模式,但重要的是,正如你所知,你不能应用两次绑定,所以你需要推迟那个任务到您最具体的代码,并将您的可重用代码公开给它。

【讨论】:

  • 嗨,安德鲁,感谢您的参与!一个快速的问题:在这种情况下,当我的子页面没有任何自己的淘汰脚本时会发生什么?那么就没有什么可以应用反馈模式了,或者我必须在每个页面上添加这个脚本(我想要避免的)。我将尝试添加一些代码来说明我的意思。谢谢!
  • 如果您从不运行ko.applyBindings,您将永远不会因为您的data-bind 属性而出现错误。简而言之,您所问的情况不是问题。
【解决方案2】:

这是另一种将公共模块与页面相关模块分离的策略:

// An example of a module that runs on everypage
var modalDialog = function(){
    this.name = "dialog1";
    this.title = ko.observable("My Modal Title");
    this.content = ko.observable("My Modal content is also something");
}
// An example of a module that runs on everypage
var modalDialog2 = function(){
    this.name = "dialog2";
    this.title = ko.observable("My Modal Title 2");
    this.content = ko.observable("My Modal content is also something 2");
}
// Either generate it automatically or by hand
// to represent which modules are common
var commonModules = [modalDialog, modalDialog2];

// An example of a module only for this page
var pageModule = function(){
    this.pageFunction = function(){
        alert("Called page function");
    }
}

// Composition is the final object you will actually bind to the page
var composition = {
    pageMod: new pageModule()
}

// Let's add the common modules to the composition 
ko.utils.arrayForEach(commonModules, function(item){
    var module = new item();
    composition[module.name] = module;
});

// Bind the composition
ko.applyBindings(composition);

示例 HTML 为:

<div class="modalDialog">
    <h2 data-bind="text: dialog1.title"><h2>
    <h2 data-bind="text: dialog1.content"><h2>
</div>
<div class="modalDialog">
    <h2 data-bind="text: dialog2.title"><h2>
    <h2 data-bind="text: dialog2.content"><h2>
</div>

<div id="content">
    <h2>Welcome to page</h2>
    <div id="somePageStuff">
        <a href="#" data-bind="click: pageMod.pageFunction">Click me</a>
    </div>
</div>

Link to the jsfille for this

【讨论】:

  • 嗨 XGreen,感谢您的参与!通常我会对此很好,除了我只有一些其他页面甚至有淘汰赛,而反馈脚本及其绑定位于完全独立的文件中。鉴于此,我看不出有一种方法可以使用您的方法,而无需在每个页面上指定构图(即使是根本没有使用自己的敲除的页面)。你能详细说明一下吗?我也会尝试添加一些代码来说明我的意思。
【解决方案3】:

您可以使用一种技术进行设置,使您的绑定范围不限于页面中的特定区域。

查看:How to stop knockout.js bindings evaluating on child elements

示例: http://jsfiddle.net/anAgent/RfM2R/

HTML

<div id="Main">
    <label data-bind="text: ViewModel.Name">default</label>
    <div data-bind="stopBindings: true">
        <div id="ChildBinding">
            <label data-bind="text: AnotherViewModel.Name">default</label>
        </div>
    </div>
</div>

JavaScript

$(function () {
    ko.bindingHandlers.stopBindings = {
        init: function () {
            return {
                controlsDescendantBindings: true
            };
        }
    };
    var data = {
        ViewModel: {
            Name: "Testing"
        }
    };

    var data2 = {
        AnotherViewModel: {
            Name: "More Testing"
        }
    };

    ko.applyBindings(data, $("#Main")[0]);
    ko.applyBindings(data2, $("#MyModalHtml")[0]);

});

【讨论】:

  • 您好代理,感谢您的参与!对你的解决方案有点困惑。我不一定要停止处理绑定(这就是 stopBindings 在我的应用程序中所做的)。我希望绑定反馈模式,同时还允许其他页面应用它们的绑定,而无需知道反馈视图模型及其绑定甚至存在。对此有什么想法吗?
  • 嗨,肖恩,我想指出的是,您可以将两个视图模型绑定到您的 DOM - 允许您独立于主视图模型使用模态。 Knockoutjs 不允许“应用绑定”两次。 stopBindings 阻止第一个“applyBindings”执行任何事件 - 从而允许您将模式绑定到不同的视图模型。
  • 感谢您的澄清!我遇到了一个问题:将 stopBindinga 应用于我的模态容器 div 会导致两个视图模型都停止工作。知道我在研究时可能会做错什么吗?
  • 我已更新问题以包含我尝试应用此解决方案但遇到一些问题的整个部分。希望你能注意到我做错了什么。谢谢!
猜你喜欢
  • 2011-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-17
  • 1970-01-01
  • 2011-07-08
相关资源
最近更新 更多