整合jquery ui对话框与knockoutjs

我正在尝试为jQuery UI对话框创buildknockoutjs绑定,并且无法打开对话框。 对话元素创build正确,但似乎有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 ,但这似乎没有影响任何东西。

它看起来像写入小部件.data(“对话框”),然后尝试操作它是一个问题。 下面是一个示例,其中.data不被使用,打开/closures是基于元素调用的: http : //jsfiddle.net/rniemeyer/durKS/

或者,我喜欢以稍微不同的方式与对话框一起工作。 我喜欢通过使用observable来控制对话框是打开还是closures。 所以,你会在对话框本身上使用一个绑定。 init会初始化对话框,而update会检查一个observable,看是否应该打开或closures。 现在,打开/closuresbutton只需要切换一个布尔值可观察性,而不用担心ID或find实际的对话框。

 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/

在这里添加这个,因为这是大多数人在使用jQuery UI对话框和Knockout JS进行search时发现的。

只是另一个select,以避免上述答案中解释“双重约束力”的问题。 对我来说,setTimeout()导致其他绑定失败,需要对话框已被初始化。 为我工作的简单解决scheme是对接受的答案进行以下更改:

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

  2. 调用这个页面加载后,但在调用ko.applyBindings()之前:

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

在自定义绑定的init中删除setTimeout ,直接调用代码。

第2步确保任何jQuery UI对话框在任何KO绑定之前已经被初始化。 这样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完成或不。

我对RP Niemeyer的答案做了一点改动,让对话的选项成为可观察的

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

用ko.toJS获取可观察值来初始化小部件

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

现在有了这个库 ,它拥有KnockoutJS的所有JQueryUI绑定,当然还包括对话框部件。

这是一个伟大的RP尼迈耶绑定处理程序的变体,这是一个不同的场景有用。

为了允许编辑一个实体,你可以创build一个带有编辑控件的<div> ,并使用一个绑定,这个绑定依赖于这个编辑的目的。

例如,为了允许编辑一个person ,你可以像editedPerson那样创build和观察,并用一个绑定来创build一个带有编辑控件的div。

 data-bind="with: editedPerson" 

当你将一个人添加到可观察的lke中时:

 vm.editedPerson(personToEdit); 

该绑定使div可见。 当你完成这个版本时,你可以将observable设置为null,像这样

 vm.editedPerson(null); 

div将closures。

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

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

你可以得到我的绑定处理程序的代码,并在这里看到它的行动:

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

你可以很容易的修改这段代码,使其具有不同的对话框默认值,甚至可以通过将处理程序封装在一个js模块中来configuration这些默认值,并添加一个公共configuration函数来修改它。 (您可以将此函数添加到绑定处理程序,它将继续工作)。

 // 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="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>