将 jquery ui 对话框与 knockoutjs 集成

Posted

技术标签:

【中文标题】将 jquery ui 对话框与 knockoutjs 集成【英文标题】:integrating jquery ui dialog with knockoutjs 【发布时间】:2012-01-26 12:49:36 【问题描述】:

我正在尝试为 jquery ui 对话框创建 knockoutjs 绑定,但无法打开对话框。对话框元素已正确创建,但似乎有 display: none 调用 dialog('open') 不会删除。此外,对dialog('isOpen') 的调用返回对话框对象而不是布尔值。

我正在使用最新的 knockoutjs 和 jquery 1.4.4 和 jquery ui 1.8.7。我也用 jQuery 1.7.1 尝试过,结果相同。这是我的 html

<h1 class="header" data-bind="text: label"></h1>

<div id="dialog" data-bind="dialog: autoOpen: false, title: 'Dialog test'">foo dialog</div>

<div>
    <button id="openbutton" data-bind="dialogcmd: id: 'dialog'" >Open</button>
    <button id="openbutton" data-bind="dialogcmd: id: 'dialog', cmd: 'close'" >Close</button>
</div>

这是javascript

var jQueryWidget = function(element, valueAccessor, name, constructor) 
    var options = ko.utils.unwrapObservable(valueAccessor());
    var $element = $(element);
    var $widget = $element.data(name) || constructor($element, options);
    $element.data(name, $widget);

;

ko.bindingHandlers.dialog = 
        init: function(element, valueAccessor, allBindingsAccessor, viewModel) 
            jQueryWidget(element, valueAccessor, 'dialog', function($element, options) 
                console.log("Creating dialog on "  + $element);
                return $element.dialog(options);
            );
                
;

ko.bindingHandlers.dialogcmd = 
        init: function(element, valueAccessor, allBindingsAccessor, viewModel)           
            $(element).button().click(function() 
                var options = ko.utils.unwrapObservable(valueAccessor());
                var $dialog = $('#' + options.id).data('dialog');
                var isOpen = $dialog.dialog('isOpen');
                console.log("Before command dialog is open: " + isOpen);
                $dialog.dialog(options.cmd || 'open');
                return false;
            );
                
;

var viewModel = 
    label: ko.observable('dialog test')
;

ko.applyBindings(viewModel);

我已经设置了一个JSFiddle 来重现该问题。

我想知道这是否与 knockoutjs 和事件处理有关。我尝试从点击处理程序返回true,但这似乎没有影响任何事情。

【问题讨论】:

【参考方案1】:

看起来将小部件写入 .data("dialog") 然后尝试对其进行操作会导致问题。这是一个示例,其中没有使用.data,而是根据元素调用打开/关闭:http://jsfiddle.net/rniemeyer/durKS/

另外,我喜欢以稍微不同的方式使用对话框。我喜欢使用 observable 来控制对话框是打开还是关闭。因此,您将在对话框本身上使用单个绑定。 init 将初始化对话框,而update 将检查一个可观察对象以查看它是否应该调用打开或关闭。现在,打开/关闭按钮只需要切换一个 boolean observable,而不用担心 id 或定位实际对话框。

ko.bindingHandlers.dialog = 
        init: function(element, valueAccessor, allBindingsAccessor) 
            var options = ko.utils.unwrapObservable(valueAccessor()) || ;
            //do in a setTimeout, so the applyBindings doesn't bind twice from element being copied and moved to bottom
            setTimeout(function()  
                options.close = function() 
                    allBindingsAccessor().dialogVisible(false);                        
                ;

                $(element).dialog(options);          
            , 0);

            //handle disposal (not strictly necessary in this scenario)
             ko.utils.domNodeDisposal.addDisposeCallback(element, function() 
                 $(element).dialog("destroy");
             );   
        ,
        update: function(element, valueAccessor, allBindingsAccessor) 
            var shouldBeOpen = ko.utils.unwrapObservable(allBindingsAccessor().dialogVisible),
                $el = $(element),
                dialog = $el.data("uiDialog") || $el.data("dialog");

            //don't call open/close before initilization
            if (dialog) 
                $el.dialog(shouldBeOpen ? "open" : "close");
              
        
;

类似:

<div id="dialog" data-bind="dialog: autoOpen: false, title: 'Dialog test' , dialogVisible: isOpen">foo dialog</div>

这是一个示例: http://jsfiddle.net/rniemeyer/SnPdE/

【讨论】:

谢谢!我想知道如何处理 init 在这里被调用两次的事实。我宁愿不要两次创建对话框。 有趣。我以前从未注意到这一点。 .dialog() 包裹并将元素移动到底部,applyBindings 再次点击它。通过在setTimeout 中调用.dialog() 很容易处理。我更新了上面的小提琴和代码。 我不太明白为什么超时没有被调用两次。但它确实有效! 问题是applyBindings 从上到下遍历元素,当.dialog() 运行时,它实际上将元素移动(并包裹)到页面底部。然后applyBindings又遇到了,又运行了init。 setTimeout 允许 applyBindings 在调用.dialog() 之前完成其通过页面。处理回调可以不设置超时,因为它只会在 Knockout 清理元素时调用(例如作为模板的一部分)。 jquery ui 1.11.3 - 新变化:$(element).data('ui-dialog')。这种对话检测方式破的太频繁了,貌似是非官方的对话检测……【参考方案2】:

在此处添加此内容,因为这是大多数人在搜索 jQuery UI Dialog 和 Knockout JS 问题时会发现的内容。

避免上述答案中解释的“双重绑定”问题的另一种选择。对我来说, setTimeout() 导致其他绑定失败,需要已经初始化对话框。对我有用的简单解决方案是对接受的答案进行以下更改:

    使用自定义对话框绑定将 class='dialog' 添加到任何元素。

    在页面加载后调用此函数,但在调用 ko.applyBindings() 之前调用:

    $('.dialog').dialog(autoOpen: false);

去掉自定义绑定的init里面的setTimeout,直接调用代码就行了。

第 2 步确保所有 jQuery UI 对话框在任何 KO 绑定之前都已初始化。这样 jQuery UI 已经移动了 DOM 元素,因此您不必担心它们在 applyBindings 中间移动。初始化代码仍然按原样工作(除了删除 setTimeout),因为 dialog() 函数只会在已经初始化的情况下更新现有对话框。

我需要这个的一个例子是由于我使用自定义绑定来更新对话框的标题:

ko.bindingHandlers.jqDialogTitle = 
    update: function(element, valueAccessor) 
        var value = ko.utils.unwrapObservable(valueAccessor());
        $(element).dialog('option', 'title', value);
    
;

我为此使用单独的绑定而不是主对话框绑定的更新函数,因为我只想更新标题,而不是其他属性,例如高度和宽度(不希望对话框调整大小只是因为我更改标题)。我想我也可以使用更新并删除高度/宽度,但现在我可以同时执行这两项操作,而不用担心 setTimeout 是否完成。

【讨论】:

【参考方案3】:

我对 RP Niemeyer 的回答做了一点改动,以允许对话框的选项是可观察的

http://jsfiddle.net/YmQTW/1/

使用 ko.toJS 获取 observables 值来初始化小部件

setTimeout(function()  
    options.close = function() 
        allBindingsAccessor().dialogVisible(false);                        
    ;

    $(element).dialog(ko.toJS(options));          
, 0);

并在更新时检查 observables

//don't call dialog methods before initilization
if (dialog) 
    $el.dialog(shouldBeOpen ? "open" : "close");

    for (var key in options) 
        if (ko.isObservable(options[key])) 
            $el.dialog("option", key, options[key]());
        
    

【讨论】:

【参考方案4】:

现在this library 拥有 KnockoutJS 的所有 JQueryUI 绑定,当然还包括对话框小部件。

【讨论】:

这就是我一直在寻找的答案!谢谢。【参考方案5】:

这是出色的 RP Niemeyer 绑定处理程序的变体,可用于不同的场景。

要允许实体的版本,您可以创建带有版本控件的&lt;div&gt;,并使用with 绑定,这取决于专门为版本制作的可观察对象。

例如,要允许编辑person,您可以像editedPerson那样创建和观察,并创建一个带有编辑控件的div,绑定如下:

data-bind="with: editedPerson"

当您将一个人添加到可观察对象时:

vm.editedPerson(personToEdit);

绑定使div 可见。完成编辑后,可以将 observable 设置为 null,就像这样

vm.editedPerson(null);

div 将关闭。

我的 RP Niemeyer 的 bindingHandler 变体允许在 jQuery UI 对话框中自动显示这个 div。要使用它,您只需保留原始的 with 绑定,并指定 jQuery UI 对话框选项,如下所示:

data-bind="with: editedPerson, withDialog: /* jQuery UI dialog options*/"

您可以获取我的绑定处理程序的代码,并在此处查看它的实际效果:

http://jsfiddle.net/jbustos/dBLeg/

您可以轻松地修改此代码以使对话框具有不同的默认值,甚至可以通过将处理程序封装在 js 模块中并添加公共配置函数来修改这些默认值来配置这些默认值。 (您可以将此函数添加到绑定处理程序,它会继续工作)。

// Variation on Niemeyer's http://jsfiddle.net/rniemeyer/SnPdE/

/*
This binding works in a simple way:
1) bind an observable using "with" binding
2) set the dialog options for the ui dialog using "withDialog" binding (as you'd do with an standard jquery UI dialog) Note that you can specify a "close" function in the options of the dialog an it will be invoked when the dialog closes.

Once this is done:
- when the observable is set to null, the dialog closes
- when the observable is set to something not null, the dialog opens
- when the dialog is cancelled (closed with the upper right icon), the binded observable is closed

Please, note that you can define the defaults for your binder. I recommend setting here the modal state, and the autoOpen to false.

*/

ko.bindingHandlers.withDialog = 
        init: function(element, valueAccessor, allBindingsAccessor) 
            var defaults = 
                modal: false,
                autoOpen: false,
            ;
            var options = ko.utils.unwrapObservable(valueAccessor());
            //do in a setTimeout, so the applyBindings doesn't bind twice from element being copied and moved to bottom
            $.extend(defaults, options)
            setTimeout(function()  
                var oldClose = options.close;
                defaults.close = function() 
                    if (options.close) options.close();
                    allBindingsAccessor().with(null);                        
                ;
                
                $(element).dialog(defaults);          
            , 0);
            
            //handle disposal (not strictly necessary in this scenario)
             ko.utils.domNodeDisposal.addDisposeCallback(element, function() 
                 $(element).dialog("destroy");
             );   
        ,
        update: function(element, valueAccessor, allBindingsAccessor) 
            var shouldBeOpen = ko.utils.unwrapObservable(allBindingsAccessor().with),
                $el = $(element),
                dialog = $el.data("uiDialog") || $el.data("dialog");
            
            //don't call open/close before initilization
            if (dialog) 
                $el.dialog(shouldBeOpen ? "open" : "close");
              
        
;
    
var person = function() 
    this.name = ko.observable(),
    this.age = ko.observable()


var viewModel = function() 
    label= ko.observable('dialog test');
    editedPerson= ko.observable(null);
    clearPerson= function() 
       editedPerson(null);
    ;
    newPerson= function() 
        editedPerson(new person());
    ;
    savePerson= function() 
        alert('Person saved!');
        clearPerson();
    ;
    return 
        label: label,
        editedPerson: editedPerson,
        clearPerson: clearPerson,
        newPerson: newPerson,
        savePerson: savePerson,
    ;



var vm = viewModel();

ko.applyBindings(vm);
.header 
    font-size: 16px;
    font-family: sans-serif;
    font-weight: bold;
    margin-bottom: 20px;
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.1/knockout-min.js"></script>
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/base/jquery-ui.css" rel="stylesheet"/>
<h1 class="header" data-bind="text: label"></h1>

<div id="dialog" data-bind="with: editedPerson, withDialog: autoOpen: false, title: 'Dialog test', close: function()  alert('closing'); ">
    Person editor<br/>
    Name:<br/><input type="text" data-bind="value: $data.name"/><br/>
    Age:<br/><input type="text" data-bind="value: $data.age"/><br/>
    <button data-bind="click: $parent.savePerson">Ok</button>
    <button data-bind="click: $parent.clearPerson">Cancel</button>
</div>

<div>
    <button data-bind="click: clearPerson">Clear person</button>
    <button data-bind="click: newPerson">New person</button>
</div>

<hr/>

<div data-bind="text: ko.toJSON($root)"></div>

【讨论】:

以上是关于将 jquery ui 对话框与 knockoutjs 集成的主要内容,如果未能解决你的问题,请参考以下文章

将 jquery ui 对话框与 knockoutjs 集成

Django/Jquery UI 对话框 - 如何将 Django 与 JqueryUI 对话框集成?

将 Knockout.js 与最新的 jQuery 文件和 ASP.NET MVC 一起使用

我可以将 jQuery UI 1.12.1 与 jQuery 3.x 一起使用吗?

Jquery UI 对话框不能与 JQuery Mobile 一起工作

如何设置与不同按钮相关的jQuery UI对话框定位?