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);
Другие вопросы по тегам