web-dev-qa-db-fra.com

intégration du dialogue jquery ui avec knockoutjs

J'essaie de créer des liaisons knockoutjs pour les boîtes de dialogue jquery ui, et je n'arrive pas à ouvrir la boîte de dialogue. L'élément de dialogue est créé correctement, mais semble avoir display: none Que l'appel de dialog('open') ne supprime pas. De plus, l'appel à dialog('isOpen') renvoie l'objet de dialogue plutôt qu'un booléen.

J'utilise les derniers knockoutjs et jquery 1.4.4 avec jquery ui 1.8.7. Je l'ai également essayé avec jQuery 1.7.1 avec les mêmes résultats. Voici mon 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>

et voici le 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);

J'ai mis en place un JSFiddle qui reproduit le problème.

Je me demande si cela a quelque chose à voir avec les knockoutjs et la gestion des événements. J'ai essayé de renvoyer true à partir du gestionnaire de clics, mais cela ne semble rien affecter.

37
Gene Golovchinsky

Cela ressemble à écrire dans le widget dans .data ("boîte de dialogue"), puis essayer de le faire fonctionner provoque un problème. Voici un exemple où .data n'est pas utilisé et l'ouverture/fermeture est appelée en fonction de l'élément: http://jsfiddle.net/rniemeyer/durKS/

Alternativement, j'aime travailler avec la boîte de dialogue d'une manière légèrement différente. J'aime contrôler si la boîte de dialogue est ouverte ou fermée en utilisant un observable. Ainsi, vous utiliseriez une seule liaison sur la boîte de dialogue elle-même. Le init initialiserait la boîte de dialogue, tandis que le update vérifierait un observable pour voir s'il doit appeler open ou close. Maintenant, les boutons d'ouverture/fermeture ont juste besoin de basculer un booléen observable plutôt que de se soucier des identifiants ou de localiser la boîte de dialogue réelle.

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");
            }  
        }
};

Utilisé comme:

<div id="dialog" data-bind="dialog: {autoOpen: false, title: 'Dialog test' }, dialogVisible: isOpen">foo dialog</div>

Voici un exemple: http://jsfiddle.net/rniemeyer/SnPdE/

64
RP Niemeyer

J'ai apporté un petit changement à la réponse de RP Niemeyer pour permettre aux options de la boîte de dialogue d'être observables

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

Obtenez les valeurs observables avec ko.toJS pour initialiser le widget

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

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

et vérifier les observables sur la mise à jour

//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]());
        }
    }
}
5
Bruno S.

Il s'agit d'une variante du grand gestionnaire de liaisons RP Niemeyer, qui est utile pour un scénario différent.

Pour autoriser l'édition d'une entité, vous pouvez créer un <div> avec des contrôles d'édition, et utilisez une liaison with, qui dépend d'un observable fait exprès pour l'édition.

Par exemple, pour autoriser l'édition d'un person, vous pouvez créer et observer comme editedPerson, et créer un div avec des contrôles d'édition, avec une liaison comme celle-ci:

data-bind="with: editedPerson"

Lorsque vous ajoutez une personne au lke observable, alors:

vm.editedPerson(personToEdit);

la liaison rend le div visible. Lorsque vous avez terminé l'édition, vous pouvez définir l'observable sur null, comme ceci

vm.editedPerson(null);

et le div se fermera.

Ma variation de RP Niemeyer bindingHandler permet d'afficher automatiquement ce div dans une boîte de dialogue jQuery UI. Pour l'utiliser, il vous suffit de conserver la liaison with d'origine et de spécifier les options de la boîte de dialogue jQuery UI comme suit:

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

Vous pouvez obtenir le code de mon gestionnaire de liaison et le voir en action ici:

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

Vous pouvez facilement modifier ce code pour avoir des valeurs par défaut différentes pour la boîte de dialogue, et même pour rendre ces valeurs par défaut configurables en enfermant le gestionnaire dans un module js et en ajoutant une fonction de configuration publique pour le modifier. (Vous pouvez ajouter cette fonction au gestionnaire de liaison et elle continuera de fonctionner).

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

Ajouter ceci ici parce que c'est ce que la plupart des gens trouvent lors de la recherche de problèmes avec jQuery UI Dialog et Knockout JS.

Juste une autre option pour éviter le problème de "double liaison" expliqué dans la réponse ci-dessus. Pour moi, setTimeout () provoquait l'échec d'autres liaisons qui nécessitent déjà que la boîte de dialogue soit initialisée. La solution simple qui a fonctionné pour moi consistait à apporter les modifications suivantes à la réponse acceptée:

  1. Ajoutez class = 'dialog' à tous les éléments à l'aide de la liaison de boîte de dialogue personnalisée.

  2. Appelez cela après le chargement de la page, mais avant d'appeler ko.applyBindings ():

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

Supprimez le setTimeout à l'intérieur du init de la liaison personnalisée et appelez simplement le code directement.

L'étape 2 vérifie que toutes les boîtes de dialogue d'interface utilisateur jQuery ont été initialisées avant toute liaison KO. De cette façon, jQuery UI a déjà déplacé les éléments DOM, de sorte que vous n'avez pas à vous soucier de leur déplacement au milieu de applyBindings. Le code init fonctionne toujours tel quel (autre que la suppression de setTimeout) car la fonction dialog () ne mettra à jour qu'une boîte de dialogue existante si elle est déjà initialisée.

Un exemple de la raison pour laquelle j'avais besoin de cela est dû à une liaison personnalisée que j'utilise pour mettre à jour le titre de la boîte de dialogue:

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

J'utilise une liaison distincte pour cela au lieu de la fonction de mise à jour pour la liaison de la boîte de dialogue principale, car je veux seulement mettre à jour le titre, pas d'autres propriétés telles que la hauteur et la largeur (je ne veux pas que la boîte de dialogue se redimensionne juste parce que je change le titre ). Je suppose que je pourrais également utiliser la mise à jour et simplement supprimer la hauteur/largeur, mais maintenant je peux faire les deux/ou ne pas me soucier de la fin de setTimeout.

4
eselk

Il y a maintenant cette bibliothèque qui a toutes les liaisons JQueryUI pour KnockoutJS, y compris, bien sûr, le widget de dialogue.

3
juuxstar