Knockout.js – liaison de données différée pour modal?

J'utilise knockout.js pour afficher une liste d'employés. J'ai une seule balise modale cachée sur la page. Lorsque le bouton "Détails" pour un seul employé est cliqué, je souhaite lier cet employé à la popup modal. J'utilise ko.applyBindings (employé, élément), mais le problème est quand la page se charge, il s'attend à ce que le mode modal commence comme étant lié à quelque chose.

Je me demande donc s'il existe une stratégie / stratégie pour effectuer une liaison de données retardée / différée? J'ai cherché des liaisons virtuelles, mais la documentation n'était pas assez utile.

Merci!

Je créerais un autre observable qui enveloppe l'employé.

this.detailedEmployee = ko.observable({}), var self = this; this.showDetails = function(employee){ self.detailedEmployee(employee); $("#dialog").dialog("show"); //or however your dialog works } 

Joignez le clic pour showDetails . Ensuite, vous pouvez appeler applyBindings en charge de la page.

J'aimerais proposer une autre façon de travailler avec des modals dans MVVVM. Dans MVVM, ViewModel est une donnée pour la vue, et la vue est responsable de l'interface utilisateur. Si nous examinons cette proposition:

 this.detailedEmployee = ko.observable({}), var self = this; this.showDetails = function(employee){ self.detailedEmployee(employee); $("#dialog").dialog("show"); //or however your dialog works } 

Je suis tout à fait d'accord avec this.detailedEmployee = ko.observable({}) , mais je suis fortement en désaccord avec cette ligne: $("#dialog").dialog("show"); . Ce code est placé dans le ViewModel et affiche la fenêtre modal, dans laquelle il fait partie de la responsabilité de View, afin d'endommager l'approche MVVM. Je dirais que ce code permettra de résoudre votre tâche actuelle, mais cela pourrait causer beaucoup de problèmes à l'avenir.

  • Lorsque vous fermez la fenêtre contextuelle, vous devez spécifier detailedEmployee sur undefined pour que votre ViewModel principal soit cohérent.
  • Lorsque vous fermez la fenêtre contextuelle, vous voudrez peut-être avoir la validation et la possibilité de supprimer l'opération de fermeture lorsque vous souhaitez utiliser le composant d'un autre modèle dans l'application

Pour ce qui est de moi, ces points sont très critiques, alors j'aimerais proposer une manière différente. Si nous "oublions" que vous devez afficher les données en pop-up, la liaison with pourrait résoudre votre problème.

 this.detailedEmployee = ko.observable(undefined); var self = this; this.showDetails = function(employee){ self.detailedEmployee(employee); } <div data-bind="with: detailedEmployee"> Data to show </div> 

Comme vous pouvez le voir, votre ViewModel ne sait rien de la façon dont les données doivent être affichées. Il ne connaît que les données qui doivent être affichées. Le lien with liaison affiche le contenu uniquement lorsque le detailedEmployee est défini. Ensuite, nous devrions trouver une liaison similaire à celle qui affichera le contenu dans la fenêtre contextuelle. Donnez-lui le nom modal . Son code est comme ceci:

 ko.bindingHandlers['modal'] = { init: function(element) { $(element).modal('init'); return ko.bindingHandlers['with'].init.apply(this, arguments); }, update: function(element, valueAccessor) { var value = ko.utils.unwrapObservable(valueAccessor()); var returnValue = ko.bindingHandlers['with'].update.apply(this, arguments); if (value) { $(element).modal('show'); } else { $(element).modal('hide'); } return returnValue; } }; 

Comme vous pouvez le voir, il utilise le plugin en interne, et montre ou cache un popup en fonction de la valeur passée à la liaison. Si elle est définie – 'show'. Sinon – "cacher". Son utilisation sera la suivante:

 <div data-bind="modal: detailedEmployee"> Data to show </div> 

La seule chose que vous devez faire, c'est d'utiliser votre plugin de mode préféré. J'ai préparé un exemple avec le composant Twitter Bootstrap Twitter: http://jsfiddle.net/euvNr/embedded/result/

Dans cet exemple, la liaison personnalisée est un peu plus puissante; Vous pouvez vous abonner à l'événement onBeforeClose et annuler cet événement si nécessaire. J'espère que cela t'aides.

Le JSFiddle lié à dans la réponse fournie par @Romanych ne semblait plus fonctionner.

Donc, j'ai construit mon propre exemple (basé sur son violon d'origine ) avec un support CRUD complet et une validation de base à l'aide de Bootstrap 3 et de la bibliothèque Bootstrap Modal : https://jsfiddle.net/BitWiseGuy/4u5egybp/

Gestionnaires de reliure personnalisés

 ko.bindingHandlers['modal'] = { init: function(element, valueAccessor, allBindingsAccessor) { var allBindings = allBindingsAccessor(); var $element = $(element); $element.addClass('hide modal'); if (allBindings.modalOptions && allBindings.modalOptions.beforeClose) { $element.on('hide', function() { var value = ko.utils.unwrapObservable(valueAccessor()); return allBindings.modalOptions.beforeClose(value); }); } }, update: function(element, valueAccessor) { var value = ko.utils.unwrapObservable(valueAccessor()); if (value) { $(element).removeClass('hide').modal('show'); } else { $(element).modal('hide'); } } }; 

Exemple d'utilisation

La vue

 <div data-bind="modal: UserBeingEdited" class="fade" role="dialog" tabindex="-1"> <form data-bind="submit: $root.SaveUser"> <div class="modal-header"> <a class="close" data-dismiss="modal">×</a> <h3>User Details</h3> </div> <div class="modal-body"> <div class="form-group"> <label for="NameInput">Name</label> <input type="text" class="form-control" id="NameInput" placeholder="User's name" data-bind="value: UserBeingEdited() && UserBeingEdited().Name, valueUpdate: 'afterkeydown'"> </div> <div class="form-group"> <label for="AgeInput">Age</label> <input type="text" class="form-control" id="AgeInput" placeholder="User's age" data-bind="value: UserBeingEdited() && UserBeingEdited().Age, valueUpdate: 'afterkeydown'"> </div> <!-- ko if: ValidationErrors() && ValidationErrors().length > 0 --> <div class="alert alert-danger" style="margin: 20px 0 0"> Please correct the following errors: <ul data-bind="foreach: { data: ValidationErrors, as: 'errorMessage' }"> <li data-bind="text: errorMessage"></li> </ul> </div> <!-- /ko --> </div> <div class="modal-footer"> <button type="button" data-dismiss="modal" class="btn btn-default">Cancel</button> <button type="submit" class="btn btn-primary">Save Changes</button> </div> </form> </div> 

Le modèle ViewModel

 /* ViewModel for the individual records in our collection. */ var User = function(name, age) { var self = this; self.Name = ko.observable(ko.utils.unwrapObservable(name)); self.Age = ko.observable(ko.utils.unwrapObservable(age)); } /* The page's main ViewModel. */ var ViewModel = function() { var self = this; self.Users = ko.observableArray(); self.ValidationErrors = ko.observableArray([]); // Logic to ensure that user being edited is in a valid state self.ValidateUser = function(user) { if (!user) { return false; } var currentUser = ko.utils.unwrapObservable(user); var currentName = ko.utils.unwrapObservable(currentUser.Name); var currentAge = ko.utils.unwrapObservable(currentUser.Age); self.ValidationErrors.removeAll(); // Clear out any previous errors if (!currentName) self.ValidationErrors.push("The user's name is required."); if (!currentAge) { self.ValidationErrors.push("Please enter the user's age."); } else { // Just some arbitrary checks here... if (Number(currentAge) == currentAge && currentAge % 1 === 0) { // is a whole number if (currentAge < 2) { self.ValidationErrors.push("The user's age must be 2 or greater."); } else if (currentAge > 99) { self.ValidationErrors.push("The user's age must be 99 or less."); } } else { self.ValidationErrors.push("Please enter a valid whole number for the user's age."); } } return self.ValidationErrors().length <= 0; }; // The instance of the user currently being edited. self.UserBeingEdited = ko.observable(); // Used to keep a reference back to the original user record being edited self.OriginalUserInstance = ko.observable(); self.AddNewUser = function() { // Load up a new user instance to be edited self.UserBeingEdited(new User()); self.OriginalUserInstance(undefined); }; self.EditUser = function(user) { // Keep a copy of the original instance so we don't modify it's values in the editor self.OriginalUserInstance(user); // Copy the user data into a new instance for editing self.UserBeingEdited(new User(user.Name, user.Age)); }; // Save the changes back to the original instance in the collection. self.SaveUser = function() { var updatedUser = ko.utils.unwrapObservable(self.UserBeingEdited); if (!self.ValidateUser(updatedUser)) { // Don't allow users to save users that aren't valid return false; } var userName = ko.utils.unwrapObservable(updatedUser.Name); var userAge = ko.utils.unwrapObservable(updatedUser.Age); if (self.OriginalUserInstance() === undefined) { // Adding a new user self.Users.push(new User(userName, userAge)); } else { // Updating an existing user self.OriginalUserInstance().Name(userName); self.OriginalUserInstance().Age(userAge); } // Clear out any reference to a user being edited self.UserBeingEdited(undefined); self.OriginalUserInstance(undefined); } // Remove the selected user from the collection self.DeleteUser = function(user) { if (!user) { return falase; } var userName = ko.utils.unwrapObservable(ko.utils.unwrapObservable(user).Name); // We could use another modal here to display a prettier dialog, but for the // sake of simplicity, we're just using the browser's built-in functionality. if (confirm('Are you sure that you want to delete ' + userName + '?')) { // Find the index of the current user and remove them from the array var index = self.Users.indexOf(user); if (index > -1) { self.Users.splice(index, 1); } } }; } 

Initialisation Knockout avec la vue et le ViewModel

 var viewModel = new ViewModel(); // Populate the ViewModel with some dummy data for (var i = 1; i <= 10; i++) { var letter = String.fromCharCode(i + 64); var userName = 'User ' + letter; var userAge = i * 2; viewModel.Users.push(new User(userName, userAge)); } // Let Knockout do its magic! ko.applyBindings(viewModel);