web-dev-qa-db-ja.com

jquery uiダイアログとknockoutjsの統合

Jquery uiダイアログのknockoutjsバインディングを作成しようとしていますが、ダイアログを開くことができません。ダイアログ要素は正しく作成されますが、dialog('open')を呼び出しても削除されない_display: none_があるようです。また、dialog('isOpen')の呼び出しは、ブール値ではなくダイアログオブジェクトを返します。

Jquery ui 1.8.7で最新のknockoutjsとjquery 1.4.4を使用しています。 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を返そうとしましたが、それは何にも影響しないようです。

37

ウィジェットに.data( "dialog")に書き込み、それを操作しようとすると問題が発生するようです。以下は、.dataは使用されず、要素に基づいてopen/closeが呼び出されます。 http://jsfiddle.net/rniemeyer/durKS/

あるいは、ダイアログを少し異なる方法で操作したいです。オブザーバブルを使用して、ダイアログを開くか閉じるかを制御するのが好きです。そのため、ダイアログ自体で単一のバインディングを使用します。 initはダイアログを初期化し、updateはobservableをチェックして、openまたはcloseを呼び出す必要があるかどうかを確認します。これで、開く/閉じるボタンは、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/

64
RP Niemeyer

これは、優れたRP Niemeyerバインディングハンドラのバリエーションであり、異なるシナリオに役立ちます。

エンティティの編集を許可するには、<div>エディションコントロールを使用し、withバインディングを使用します。これは、エディションの目的で作成されたオブザーバブルに依存します。

たとえば、personのエディションを許可するには、editedPersonのようなobservableを作成し、次のようなバインディングでエディションコントロールを使用してdivを作成できます。

data-bind="with: editedPerson"

観察可能なlkeに人を追加すると、次のようになります。

vm.editedPerson(personToEdit);

バインディングはdivを可視にします。エディションを終了したら、observableをnullに設定できます。

vm.editedPerson(null);

divが閉じます。

私のRP NiemeyerのbindingHandlerのバリエーションでは、このdivをjQuery UIダイアログ内に自動的に表示できます。これを使用するには、元の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
JotaBe

これは、jQuery UIダイアログとKnockout JSで問題を検索するときにほとんどの人が見つけるものであるため、ここに追加します。

上記の回答で説明した「二重結合」の問題を回避するためのもう1つのオプションです。私にとっては、setTimeout()により、ダイアログをすでに初期化する必要がある他のバインディングが失敗していました。私のために働いた簡単な解決策は、受け入れられた答えに以下の変更を加えることでした:

  1. カスタムダイアログバインディングを使用して、任意の要素にclass = 'dialog'を追加します。

  2. ページの読み込み後、ko.applyBindings()を呼び出す前にこれを呼び出します。

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

カスタムバインディングのsetTimeout内のinitを削除し、コードを直接呼び出します。

ステップ2では、KOバインディングの前にjQuery UIダイアログが初期化されていることを確認します。このように、jQuery UIはすでにDOM要素を移動しているため、applyBindingsの途中でそれらが移動することを心配する必要はありません。すでに初期化されている場合、dialog()関数は既存のダイアログを更新するだけなので、initコードは(setTimeoutを削除する以外に)そのまま機能します。

これが必要な理由の例は、ダイアログのタイトルを更新するために使用するカスタムバインディングが原因です。

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

メインダイアログバインディングの更新機能ではなく、これに別のバインディングを使用します。これは、タイトルのみを更新し、高さや幅などの他のプロパティは更新しないためです(タイトルを変更しただけでダイアログのサイズを変更しないでください) )。 updateを使用して高さ/幅を削除することもできたと思いますが、setTimeoutが完了しているかどうかを心配することなく、両方または両方を実行できるようになりました。

4
eselk

現在、 このライブラリ があります。これには、もちろんダイアログウィジェットを含むKnockoutJSのすべてのJQueryUIバインディングがあります。

3
juuxstar