javascript Knockout.js - 模态的延迟数据绑定?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10626780/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 10:30:17  来源:igfitidea点击:

knockout.js - deferred databinding for modal?

javascriptdialogknockout.jsmodal-dialoglate-binding

提问by Adam Levitt

I am using knockout.js to display a list of employees. I have a single hidden modal markup on the page. When the "details" button for a single employees is clicked, I want to data-bind that employee to the modal popup. I am using the ko.applyBindings(employee, element) but the problem is when the page loads, it is expecting the modal to start off as bound to something.

我正在使用knockout.js 来显示员工列表。我在页面上有一个隐藏的模态标记。当单击单个员工的“详细信息”按钮时,我想将该员工数据绑定到模式弹出窗口。我正在使用 ko.applyBindings(employee, element) 但问题是当页面加载时,它期望模式开始时绑定到某些东西。

So I'm wondering, is there a trick/strategy to do a late/deferred databinding? I looked into virtual bindings but the documentation was not helpful enough.

所以我想知道,是否有一个技巧/策略来进行延迟/延迟数据绑定?我研究了虚拟绑定,但文档不够有用。

Thanks!

谢谢!

采纳答案by Alexander

I would create another observable that wraps the employee.

我将创建另一个包装员工的 observable。

this.detailedEmployee = ko.observable({}),

var self = this;
this.showDetails = function(employee){
    self.detailedEmployee(employee);
    $("#dialog").dialog("show"); //or however your dialog works
}

Attach the click to showDetails. Then you can just call applyBindingson page load.

将点击附加到showDetails。然后你可以调用applyBindings页面加载。

回答by Romanych

I would like to propose a different way to work with modals in MVVVM. In MVVM, the ViewModel is data for the View, and the View is responsible for the UI. If we examine this proposal:

我想提出一种不同的方式来处理 MVVVM 中的模态。在MVVM中,ViewModel是View的数据,View负责UI。如果我们检查这个提议:

this.detailedEmployee = ko.observable({}),

var self = this;
this.showDetails = function(employee){
    self.detailedEmployee(employee);
    $("#dialog").dialog("show"); //or however your dialog works
}

I strongly agree with this.detailedEmployee = ko.observable({}), but I am in strong disagreement with this line: $("#dialog").dialog("show");. This code is placed in the ViewModel and shows the modal window, wherein fact it is View's responsibility, so we screw-up the MVVM approach. I would say this piece of code will solve your current task but it could cause lots of problems in future.

我非常同意this.detailedEmployee = ko.observable({}),但我非常不同意这一行:$("#dialog").dialog("show");。这段代码放在 ViewModel 中并显示模态窗口,其中实际上是 View 的责任,所以我们搞砸了 MVVM 方法。我会说这段代码将解决您当前的任务,但将来可能会导致很多问题。

  • When closing the popup, you should set detailedEmployeeto undefinedto have your main ViewModel in a consistent state.
  • When closing the popup, you might want to have validation and the possibility to discard the close operation when you want to use another modal's component in the application
  • 关闭弹出窗口时,您应该设置detailedEmployeeundefined使您的主 ViewModel 处于一致状态。
  • 关闭弹出窗口时,当您想在应用程序中使用另一个模态组件时,您可能希望进行验证并有可能放弃关闭操作

As for me, these points are very critical, so I would like to propose a different way. If we "forget" that you need to display data in popup, binding withcould solve your issue.

对于我来说,这几点非常关键,所以我想提出一个不同的方法。如果我们“忘记”您需要在弹出窗口中显示数据,则绑定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>

As you can see, your ViewModel don't know anything about howdata should be shown. It knows only about datathat should be shown. The withbinding will display content only when detailedEmployeeis defined. Next, we should find a binding similar to withbut one that will display content in the popup. Let's give it the name modal. Its code is like this:

正如你所看到的,您的视图模型不知道任何有关如何数据应显示。它只知道应该显示的数据。的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;
    }
};

As you can see, it uses the withplugin internally, and shows or hide a popup depending on value passed to binding. If it is defined - 'show'. If not - 'hide'. Its usage will be the as with:

如您所见,它在with内部使用插件,并根据传递给绑定的值显示或隐藏弹出窗口。如果已定义 - 'show'。如果不是 - '隐藏'。它的用法如下:

<div data-bind="modal: detailedEmployee">
    Data to show
</div>

The only thing you need to do is to use your favorite modals plugin. I prepared an example with the Twitter Bootstrap popup component: http://jsfiddle.net/euvNr/embedded/result/

您唯一需要做的就是使用您最喜欢的模态插件。我准备了一个带有 Twitter Bootstrap 弹出组件的示例:http: //jsfiddle.net/euvNr/embedded/result/

In this example, custom binding is a bit more powerful; you could subscribe the onBeforeClose event and cancel this event if needed. Hope this helps.

在这个例子中,自定义绑定更强大一些;如果需要,您可以订阅 onBeforeClose 事件并取消此事件。希望这可以帮助。

回答by Alexander

The JSFiddle linked to in the answer provided by @Romanych didn't seem to work anymore.

@Romanych 提供的答案中链接到的 JSFiddle 似乎不再起作用。

So, I built my own example (based upon his original fiddle)with full CRUD support and basic validation using Bootstrap 3 and the Bootstrap Modallibrary: https://jsfiddle.net/BitWiseGuy/4u5egybp/

因此,我使用 Bootstrap 3 和Bootstrap Modal库构建了我自己的示例(基于他的原始小提琴),具有完整的 CRUD 支持和基本验证:https: //jsfiddle.net/BitWiseGuy/4u5egybp/

Custom Binding Handlers

自定义绑定处理程序

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');
    }
  }
};

Example Usage

示例用法

The View

风景

<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);
      }
    }
  };
}

Initializing Knockout with the View and the ViewModel

使用 View 和 ViewModel 初始化 Knockout

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);