интеграция диалогового окна jquery ui с knockoutjs - PullRequest
37 голосов
/ 23 декабря 2011

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

Ответы [ 5 ]

64 голосов
/ 23 декабря 2011

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

В качестве альтернативы, мне нравится работать с диалоговым окном немного по-другому.Мне нравится контролировать, является ли диалог открытым или закрытым, используя наблюдаемый.Таким образом, вы бы использовали одну привязку к самому диалогу.init будет инициализировать диалоговое окно, а update будет проверять наблюдаемое, чтобы увидеть, следует ли ему открывать или закрывать.Теперь кнопкам открытия / закрытия нужно просто переключать наблюдаемую логическую переменную, а не беспокоиться об идентификаторах или расположении фактического диалога.: http://jsfiddle.net/rniemeyer/SnPdE/

5 голосов
/ 09 мая 2013

Я внес небольшое изменение в ответ Р.П. Нимейера, чтобы позволить параметрам диалога быть наблюдаемыми

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]());
        }
    }
}
4 голосов
/ 14 марта 2014

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

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

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

data-bind="with: editedPerson"

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

vm.editedPerson(personToEdit);

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

vm.editedPerson(null);

и div закроется.

Мой вариант привязки RP Niemeyer позволяет автоматически показывать это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 голосов
/ 19 февраля 2013

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

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

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

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

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

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

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

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

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

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

3 голосов
/ 17 сентября 2013

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

...