Knockout.js 当我的Knockout/RequireJS小部件在同样使用Knockout的站点中使用时,它如何绑定到正确的上下文? 脚本

Knockout.js 当我的Knockout/RequireJS小部件在同样使用Knockout的站点中使用时,它如何绑定到正确的上下文? 脚本,knockout.js,requirejs,Knockout.js,Requirejs,我正在使用RequireJS和KnockoutJS构建一个小部件。小部件在实例化时调用ko.applyBindings(widgetViewModel,thisWidget)。这个小部件应该能够在一个站点中使用,而不管站点本身是否使用了Knockout 问题 当我将我的小部件放入使用Knockout的站点时,如果该站点在小部件设置后调用ko.applyBindings(siteViewModel),则会将错误的视图模型应用于该小部件。小部件将获取siteViewModel,而不是所需的widge

我正在使用RequireJS和KnockoutJS构建一个小部件。小部件在实例化时调用
ko.applyBindings(widgetViewModel,thisWidget)
。这个小部件应该能够在一个站点中使用,而不管站点本身是否使用了Knockout

问题 当我将我的小部件放入使用Knockout的站点时,如果该站点在小部件设置后调用
ko.applyBindings(siteViewModel)
,则会将错误的视图模型应用于该小部件。小部件将获取
siteViewModel
,而不是所需的
widgetViewModel

到目前为止我都试过了
  • 重新排序对
    ko.applyBindings(siteViewModel)
    的调用,使其在小部件设置之前发生。
    这可以工作,但并不理想,因为它限制了小部件的调用方式

  • 为我的小部件添加一个自定义绑定,将正确的绑定上下文(即
    widgetViewModel
    应用于我的小部件,并返回
    {ControlsDescentBindings:true};
    不幸的是,站点使用的淘汰实例与我的小部件使用的实例不同(因为需要)因此,我需要访问站点的实例,可能需要查看全局名称空间


  • 我建议以导出应用程序方法的方式编写小部件,或者将其作为ViewModel属性包含

    小部件的HTML如下所示:

    <h1 data-bind="text: title">Default</h1>
    
    <div id="ko" data-bind="with: widgetInstance">
         <h1 data-bind="text: title">Default</h1>
    </div>
    <h2 data-bind="text: thing"></h2>
    
    require(["mywidget", "jquery"], function (widget, $) {
        widget($('.widgetStyle')[0]);
    });
    
    require(["mywidget", "jquery", "knockout"], function (widget, $, ko) {
        var viewModel = { title: "My App" };
        widget($('.widgetStyle')[0]);
        ko.applyBindings(viewModel);
    });
    
    淘汰用户可以将其包含在他们的ViewModel中,并使用带有绑定的
    为其提供上下文

    var ViewModel = function(){
        var self = this;
        self.widgetInstance = new MyWidgets.widget("In KO");
        self.thing = "Other Thing";
    }
    
    ko.applyBindings(new ViewModel);
    
    他们的HTML将如下所示:

    <h1 data-bind="text: title">Default</h1>
    
    <div id="ko" data-bind="with: widgetInstance">
         <h1 data-bind="text: title">Default</h1>
    </div>
    <h2 data-bind="text: thing"></h2>
    
    require(["mywidget", "jquery"], function (widget, $) {
        widget($('.widgetStyle')[0]);
    });
    
    require(["mywidget", "jquery", "knockout"], function (widget, $, ko) {
        var viewModel = { title: "My App" };
        widget($('.widgetStyle')[0]);
        ko.applyBindings(viewModel);
    });
    
    
    违约
    
    请注意,它们仍然可以创建其他属性,这些属性与小部件上的任何属性都不冲突


    (旁白:有人,请想出一个比“应用”
    更好的名字,然后编辑我的帖子)

    为你的小部件编写一个自定义绑定怎么样,如下所示:

    define(["knockout", "jquery"], function (ko, $) {
        var widgetViewModel = {
            title: "My Widget Titel"
        };
        var widgetWrapperHtml = "<div data-bind=\"mywidget: true\"></div>";
        var widgetHtml = $("<div data-bind=\"text: $data.title\"</div>");
    
        ko.virtualElements.allowedBindings.mywidget = true;
        ko.bindingHandlers.mywidget = {
            init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
                // Create the widget context:
                var widgetBindingContext = widgetViewModel;
                // Or uncomment this line to created a child context:
                // var widgetBindingContext = bindingContext.createChildContext(widgetBindingContext);
    
                ko.virtualElements.emptyNode(element);
                ko.virtualElements.prepend(element, widgetHtml[0]); 
    
                ko.applyBindingsToDescendants(innerContext, element);
            }
        };
    
        var applyWidgetToElement = function (element) {
            $(element).html(widgetWrapperHtml);
            ko.applyBindings({}, element);
        };
    
        return applyWidgetToElement;
    })
    
    但也像这样:

    <h1 data-bind="text: title">Default</h1>
    
    <div id="ko" data-bind="with: widgetInstance">
         <h1 data-bind="text: title">Default</h1>
    </div>
    <h2 data-bind="text: thing"></h2>
    
    require(["mywidget", "jquery"], function (widget, $) {
        widget($('.widgetStyle')[0]);
    });
    
    require(["mywidget", "jquery", "knockout"], function (widget, $, ko) {
        var viewModel = { title: "My App" };
        widget($('.widgetStyle')[0]);
        ko.applyBindings(viewModel);
    });
    

    我想您已经接近第二个解决方案了。您可以做的是使用一个自定义绑定来停止站点的绑定,如本页提供的示例所示

    但是正如你所说,如果他们已经在网站上使用了knockout,那么你需要在该版本的knockout上使用这个绑定,所以你应该像这样用if(ko)来包装它

    if(ko){
        ko.bindingHandlers.allowBindings = {
            init: function(elem, valueAccessor) {
                // Let bindings proceed as normal *only if* my value is false
                var shouldAllowBindings = ko.unwrap(valueAccessor());
                return { controlsDescendantBindings: !shouldAllowBindings };
            }
        };
    }
    
    <body>
        <div>
            <span data-bind="text: SomeSiteLevelBinding"></span>
        </div>
        <div data-bind="allowBindings: false">
            <div id="someElementId">
                <span data-bind="text:SomeComponentLevelBinding"></span>
            </div>
        </div>
    </body>
    
    然后,因为您使用的是requirejs,所以您可以将您的击倒版本映射到ko以外的其他版本,如ko2或其他任何版本

    define(["knockout", "jquery"], function (ko2, $) {
    
        var myViewModel = {
            SomeComponentLevelBinding: ko2.observable()
        };
    
        ko2.applyBindings(myViewModel, document.getElementById('someElementId'));
    }
    
    那么你的html可能看起来像这样

    if(ko){
        ko.bindingHandlers.allowBindings = {
            init: function(elem, valueAccessor) {
                // Let bindings proceed as normal *only if* my value is false
                var shouldAllowBindings = ko.unwrap(valueAccessor());
                return { controlsDescendantBindings: !shouldAllowBindings };
            }
        };
    }
    
    <body>
        <div>
            <span data-bind="text: SomeSiteLevelBinding"></span>
        </div>
        <div data-bind="allowBindings: false">
            <div id="someElementId">
                <span data-bind="text:SomeComponentLevelBinding"></span>
            </div>
        </div>
    </body>
    
    
    
    因为站点已经使用了knockout,并且我们将allowBindings绑定添加到该实例中,所以我们阻止站点版本的knockout控制该div中的任何内容。然后,因为我们确实使用组件版本的knockout将绑定仅应用于组件中的div,所以在s上应该有两个版本的knockout一起玩得很好


    如果他们在页面上没有敲除,这也没关系,因为如果他们没有,我们就不会添加allowBindings绑定,而且因为我们只对其中的div应用绑定,在这种情况下,带有
    allowBindings:false
    的数据绑定属性被忽略。

    这是一个挑战。我现在的想法是将您的小部件位于一个容器中,该容器具有如我所述的绑定。然后,当您的小部件脚本加载时,将自定义绑定添加到您的KO实例中,查看window.KO是否存在,并将其添加到该实例中。唯一剩下的问题是,如果您通过require.js使用多个KO版本,或者当
    KO.applyBi时是否存在计时问题ndings
    被调用,当你的模块被加载时。如果我们添加一个
    停止绑定
    ,比如绑定到KO-core,那会很好。是的,这就是我所采取的方法,但我并不100%满意必须预测外部容器的KO实例的位置。是的,目前我想不出一个更好的解决方案缺点。例如,您可以使用不使用
    数据绑定
    的,但这可能会弄乱现有的KO实例。它可以同时支持这两个属性,因此,如果您的页面和小部件使用相同的KO实例,您仍然可以。仍然不太好,只是另一种想法。