Подтвердить что ты не робот

Knockout.js - отложенная привязка данных для модальных?

Я использую knockout.js для отображения списка сотрудников. У меня есть одна скрытая модальная разметка на странице. Когда нажата кнопка "Сведения" для одного сотрудника, я хочу привязать этот сотрудник к модальному всплывающему окну. Я использую ko.applyBindings(сотрудник, элемент), но проблема заключается в загрузке страницы, она ожидает, что модальная функция начнется как связанная с чем-то.

Итак, мне интересно, есть ли уловка/стратегия для позднего/отложенного привязки данных? Я просмотрел виртуальные привязки, но документация не была достаточно полезной.

Спасибо!

4b9b3361

Ответ 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);