Подтвердить что ты не робот

Интеграция диалога jquery ui с knockoutjs

Я пытаюсь создать привязки knockoutjs для диалогов jquery ui и не могу открыть диалоговое окно. Элемент диалога создается правильно, но, похоже, имеет 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 из обработчика кликов, но это не повлияло ни на что.

4b9b3361

Ответ 1

Похоже на запись в виджет в .data( "dialog" ), а затем попытка работать с ним вызывает проблему. Вот пример, где .data не используется, и open/close вызывается на основе элемента: http://jsfiddle.net/rniemeyer/durKS/

В качестве альтернативы, мне нравится работать с диалоговым окном несколько иначе. Мне нравится управлять открытием или закрытием диалога с помощью наблюдаемого. Таким образом, вы должны использовать единственное связывание в самом диалоге. 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/

Ответ 2

Я немного изменил ответ RP Niemeyer, чтобы позволить диалогам быть наблюдаемыми

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

Получить значения наблюдаемых с помощью ko.toJS для инициализации виджета

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

Ответ 3

Добавление этого здесь, потому что это то, что большинство людей находит при поиске по проблемам с jQuery UI Dialog и Knockout JS.

Еще один вариант, чтобы избежать проблемы с двойной связью, описанной в приведенном выше ответе. Для меня setTimeout() вызывал другие привязки, которые требуют инициализации диалога. Простым решением, которое сработало для меня, было внесение следующих изменений в принятый ответ:

  • Добавить class= 'диалоговое окно' к любым элементам, используя настраиваемую привязку диалогового окна.

  • Вызов этой загрузки после загрузки, но перед вызовом ko.applyBindings():

    Диалоговое окно

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

Удалите setTimeout внутри init пользовательской привязки и просто вызовите код напрямую.

Шаг 2 гарантирует, что любые jQuery UI Dialogs были инициализированы до любых привязок KO. Таким образом, пользовательский интерфейс jQuery уже переместил элементы DOM, так что вам не нужно беспокоиться о том, что они перемещаются в середине applyBindings. Код init по-прежнему работает как есть (кроме удаления setTimeout), потому что функция dialog() будет просто обновлять существующее диалоговое окно, если оно уже инициализировано.

Примером того, почему мне это нужно, является использование пользовательского связывания, которое я использую для обновления названия диалогового окна:

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

Я использую отдельную привязку для этого вместо функции обновления для привязки основного диалога, потому что я хочу только обновить заголовок, а не другие свойства, такие как высота и ширина (не хочу, чтобы диалог изменялся только потому, что я изменить название). Я полагаю, что я мог бы также использовать обновление и просто удалить высоту/ширину, но теперь я могу сделать и то и другое, и не беспокоиться о завершении setTimeout или нет.

Ответ 4

Это вариация большого обработчика привязки RP Niemeyer, который полезен для другого сценария.

Чтобы разрешить выпуск сущности, вы можете создать элемент <div> с элементами управления изданием и использовать привязку with, которая зависит от наблюдаемого, сделанного специально для издания.

Например, чтобы разрешить выпуск person, вы можете создать и наблюдать, например, editedPerson, и создать элемент управления div с редакцией с привязкой вроде этого:

data-bind="with: editedPerson"

Когда вы добавляете человека к наблюдаемому lke, поэтому:

vm.editedPerson(personToEdit);

привязка делает видимым div. Когда вы закончите издание, вы можете установить для наблюдаемого значение 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 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>

Ответ 5

Там теперь эта библиотека, которая имеет все привязки JQueryUI для KnockoutJS, включая, конечно, виджет диалогового окна.