javascript - knockout.js-databinding diferido para modal?
dialog modal-dialog (3)
Estoy usando knockout.js para mostrar una lista de empleados. Tengo un único marcado modal oculto en la página. Cuando se hace clic en el botón "detalles" para un solo empleado, quiero vincular los datos de ese empleado a la ventana emergente modal. Estoy usando ko.applyBindings (empleado, elemento) pero el problema es que cuando la página se carga, se espera que el modal comience por algo.
Entonces me pregunto, ¿hay un truco / estrategia para hacer un enlace de datos tarde / diferido? Busqué enlaces virtuales, pero la documentación no fue lo suficientemente útil.
¡Gracias!
Crearía otro observable que envuelva al empleado.
this.detailedEmployee = ko.observable({}),
var self = this;
this.showDetails = function(employee){
self.detailedEmployee(employee);
$("#dialog").dialog("show"); //or however your dialog works
}
Adjunte el clic para showDetails
. Entonces puedes simplemente llamar a applyBindings
en la carga de la página.
El JSFiddle vinculado a en la respuesta proporcionada por @Romanych ya no parecía funcionar.
Entonces, construí mi propio ejemplo (basado en su violín original ) con soporte CRUD completo y validación básica usando Bootstrap 3 y la biblioteca Bootstrap Modal : https://jsfiddle.net/BitWiseGuy/4u5egybp/
Controladores de enlace personalizados
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'');
}
}
};
Ejemplo de uso
La vista
<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>
The 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);
}
}
};
}
Inicializando Knockout con la Vista y el 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);
Me gustaría proponer una forma diferente de trabajar con modales en MVVVM. En MVVM, ViewModel es información para la Vista, y View es responsable de la UI. Si examinamos esta propuesta:
this.detailedEmployee = ko.observable({}),
var self = this;
this.showDetails = function(employee){
self.detailedEmployee(employee);
$("#dialog").dialog("show"); //or however your dialog works
}
Estoy totalmente de acuerdo con this.detailedEmployee = ko.observable({})
, pero estoy en this.detailedEmployee = ko.observable({})
desacuerdo con esta línea: $("#dialog").dialog("show");
. Este código se coloca en ViewModel y muestra la ventana modal, en la que es responsabilidad de View, por lo que nos equivocamos con el enfoque de MVVM. Diría que este código resolverá su tarea actual, pero podría causar muchos problemas en el futuro.
- Al cerrar la ventana emergente, debe establecer DetailEmployee en
undefined
para tener su ViewModel principal en un estado coherente. - Al cerrar la ventana emergente, es posible que desee tener validación y la posibilidad de descartar la operación de cierre cuando desee utilizar el componente de otro modal en la aplicación
En cuanto a mí, estos puntos son muy críticos, por lo que me gustaría proponer una forma diferente. Si "olvidamos" que necesita mostrar los datos en una ventana emergente, el enlace podría resolver su problema.
this.detailedEmployee = ko.observable(undefined);
var self = this;
this.showDetails = function(employee){
self.detailedEmployee(employee);
}
<div data-bind="with: detailedEmployee">
Data to show
</div>
Como puede ver, su ViewModel no sabe nada sobre cómo deben mostrarse los datos. Solo conoce los datos que deben mostrarse. El enlace with
mostrará el contenido solo cuando se define el detalleEmployee. A continuación, deberíamos encontrar un enlace similar a pero que muestre contenido en el menú emergente. Vamos a darle el nombre modal
. Su código es así:
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;
}
};
Como puede ver, usa el plugin with
interno, y muestra u oculta una ventana emergente dependiendo del valor pasado al enlace. Si está definido, ''mostrar''. Si no, ''ocultar''. Su uso será el mismo que con:
<div data-bind="modal: detailedEmployee">
Data to show
</div>
Lo único que debes hacer es usar tu complemento de modales favorito. Preparé un ejemplo con el componente emergente Twitter Bootstrap: http://jsfiddle.net/euvNr/embedded/result/
En este ejemplo, el enlace personalizado es un poco más poderoso; puede suscribirse al evento onBeforeClose y cancelar este evento si es necesario. Espero que esto ayude.