将jquery ui对话与knockoutjs集成

时间:2011-12-23 01:20:17

标签: jquery-ui jquery-ui-dialog knockout.js

我正在尝试为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,但这似乎不会影响任何内容。

5 个答案:

答案 0 :(得分:64)

看起来像是将小部件写入.data(&#34;对话框&#34;),然后尝试对其进行操作会导致问题。以下是未使用.data并根据元素调用open / close的示例:http://jsfiddle.net/rniemeyer/durKS/

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

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/

答案 1 :(得分:5)

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

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

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

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

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

并检查更新时的可观察量

//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]());
        }
    }
}

答案 2 :(得分:4)

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

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

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

  2. 在页面加载后调用此方法,但在调用ko.applyBindings()之前调用:

    $('。dialog')。dialog({autoOpen:false});

  3. 删除自定义绑定的setTimeout内的init,然后直接调用代码。

    步骤2确保在任何KO绑定之前已初始化任何jQuery UI对话框。这样jQuery UI已经移动了DOM元素,因此您不必担心它们在applyBindings中移动。 init代码仍可按原样运行(除了删除setTimeout),因为dialog()函数只会在已初始化时更新现有对话框。

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

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

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

答案 3 :(得分:4)

这是伟大的RP Niemeyer绑定处理程序的变体,对于不同的场景非常有用。

要允许实体的版本,您可以使用版本控件创建<div>,并使用with绑定,这取决于为该版本而生成的可观察对象。

例如,要允许person的版本,您可以像editedPerson一样创建和观察,并使用版本控件创建一个div,其绑定如下:

data-bind="with: editedPerson"

当你将一个人添加到observable lke中时:

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>

答案 4 :(得分:3)

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