knockout.js - отложенная привязка данных для модальных?
Я использую knockout.js для отображения списка сотрудников. У меня есть единственная скрытая модальная разметка на странице. Когда нажата кнопка "Сведения" для одного сотрудника, я хочу привязать данные этого сотрудника к модальному всплывающему окну. Я использую ko.applyBindings(employee, element), но проблема в том, что когда страница загружается, она ожидает, что модальное начало будет связано с чем-то.
Так что мне интересно, есть ли уловка / стратегия для позднего / отложенного связывания данных? Я изучил виртуальные привязки, но документация оказалась недостаточно полезной.
Спасибо!
3 ответа
Я хотел бы создать еще одну наблюдаемую, которая охватывает работника.
this.detailedEmployee = ko.observable({}),
var self = this;
this.showDetails = function(employee){
self.detailedEmployee(employee);
$("#dialog").dialog("show"); //or however your dialog works
}
Прикрепите щелчок к showDetails
, Тогда вы можете просто позвонить applyBindings
на странице загрузки.
Я хотел бы предложить другой способ работы с модалами в 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 и показывает модальное окно, в котором ответственность лежит на View, поэтому мы испортили подход 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 и отменить это событие, если это необходимо. Надеюсь это поможет.
JSFiddle, на который ссылается ответ @Romanych, похоже, больше не работает.
Итак, я создал свой собственный пример (на основе его оригинальной скрипки) с полной поддержкой CRUD и базовой проверкой с использованием Bootstrap 3 и модальной библиотеки Bootstrap: 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'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>
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);
}
}
};
}
Инициализация нокаута с помощью 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);