Knockout.js - отложенная привязка данных для модальных?
Я использую knockout.js для отображения списка сотрудников. У меня есть одна скрытая модальная разметка на странице. Когда нажата кнопка "Сведения" для одного сотрудника, я хочу привязать этот сотрудник к модальному всплывающему окну. Я использую ko.applyBindings(сотрудник, элемент), но проблема заключается в загрузке страницы, она ожидает, что модальная функция начнется как связанная с чем-то.
Итак, мне интересно, есть ли уловка/стратегия для позднего/отложенного привязки данных? Я просмотрел виртуальные привязки, но документация не была достаточно полезной.
Спасибо!
Ответы
Ответ 1
Я бы создал еще одно наблюдаемое, которое обертывает сотрудника.
this.detailedEmployee = ko.observable({}),
var self = this;
this.showDetails = function(employee){
self.detailedEmployee(employee);
$("#dialog").dialog("show"); //or however your dialog works
}
Прикрепите клик к showDetails
. Затем вы можете просто вызвать applyBindings
при загрузке страницы.
Ответ 2
Я хотел бы предложить другой способ работы с модалами в MVVVM. В MVVM ViewModel - это данные для представления, и представление отвечает за пользовательский интерфейс. Если мы рассмотрим это предложение:
this.detailedEmployee = ko.observable({}),
var self = this;
this.showDetails = function(employee){
self.detailedEmployee(employee);
$("#dialog").dialog("show"); //or however your dialog works
}
Я сильно согласен с this.detailedEmployee = ko.observable({})
, но я сильно не согласен с этой строкой: $("#dialog").dialog("show");
. Этот код помещается в ViewModel и показывает модальное окно, в котором фактом является "Ответственность", поэтому мы используем подход MVVM. Я бы сказал, что этот фрагмент кода решит вашу текущую задачу, но в будущем это может вызвать множество проблем.
- При закрытии всплывающего окна вы должны установить
detailedEmployee
в undefined
, чтобы ваша основная ViewModel была в согласованном состоянии.
- При закрытии всплывающего окна вам может потребоваться проверка и возможность отменить операцию закрытия, если вы хотите использовать другой модальный компонент в приложении
Что касается меня, эти моменты очень важны, поэтому я хотел бы предложить другой способ. Если мы "забудем", что вам нужно отображать данные во всплывающем окне, привязка with
может решить вашу проблему.
this.detailedEmployee = ko.observable(undefined);
var self = this;
this.showDetails = function(employee){
self.detailedEmployee(employee);
}
<div data-bind="with: detailedEmployee">
Data to show
</div>
Как вы можете видеть, ваш ViewModel ничего не знает о том, как должны отображаться данные. Он знает только данные, которые должны быть показаны. Связывание with
будет отображать контент только тогда, когда будет определен detailedEmployee
. Затем мы должны найти привязку, похожую на with
, но такую, которая отобразит содержимое во всплывающем окне. Пусть дайте ему имя modal
. Его код выглядит так:
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;
}
};
Как вы можете видеть, он использует внутренний плагин with
и показывает или скрывает всплывающее окно в зависимости от значения, переданного привязке. Если он определен - "показать". Если нет - "скрыть". Его использование будет следующим:
<div data-bind="modal: detailedEmployee">
Data to show
</div>
Единственное, что вам нужно сделать, это использовать ваш любимый плагин для модалов. Я подготовил пример с компонентом всплывающего окна Twitter Bootstrap: http://jsfiddle.net/euvNr/embedded/result/
В этом примере пользовательская привязка немного более мощная; вы можете подписаться на событие onBeforeClose и отменить это событие, если это необходимо. Надеюсь, это поможет.
Ответ 3
JSFiddle, связанный с ответом, предоставленным @Romanych, больше не работает.
Итак, я построил свой собственный пример (основанный на его оригинальной скрипке) с полной поддержкой CRUD и базовой проверкой с использованием Bootstrap 3 и Библиотека Bootstrap Modal: https://jsfiddle.net/BitWiseGuy/4u5egybp/
Пользовательские обработчики привязки
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');
}
}
};
Пример использования
Вид
<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 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 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>
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 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 name is required.");
if (!currentAge) {
self.ValidationErrors.push("Please enter the user 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 age must be 2 or greater.");
} else if (currentAge > 99) {
self.ValidationErrors.push("The user age must be 99 or less.");
}
} else {
self.ValidationErrors.push("Please enter a valid whole number for the user 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 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 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);
}
}
};
}
Инициализация нокаута с помощью View и 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);