【问题标题】:knockout.js - deferred databinding for modal?knockout.js - 模式的延迟数据绑定?
【发布时间】:2012-05-24 12:03:06
【问题描述】:

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

所以我想知道,是否有技巧/策略来进行延迟/延迟数据绑定?我查看了虚拟绑定,但文档没有足够的帮助。

谢谢!

【问题讨论】:

    标签: javascript dialog knockout.js modal-dialog late-binding


    【解决方案1】:

    我会创建另一个包裹员工的 observable。

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

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

    【讨论】:

    • Isaac,非常感谢您的回复,但是,我不确定我是否理解...采取以下措施:
      所以我为每个员工加载了 div 的“行”,但是在我选择之前我没有想要绑定到模态的特定的列表中的一名员工。这有意义吗?
    • 实际上,我认为您的答案绝对是正确的方向,但我并不完全清楚如何进行——在模态中,我有一个看起来像这样的字段:... 作业如下所示: function Job(data) {}... 其中 data 上有一个 CompanyName 属性。
    • 我越来越近了——难题的最后一块是让它理解绑定的 Employee 上的一个字段。如果我使用 似乎什么都没有出现。
    • 我明白了。我错过了 detailEmployee() 括号,因为它是可观察的。再次感谢您的帮助艾萨克!
    • 无法回答我自己的问题,但解决方案如下: function EmployeeViewModel() { var self = this; this.detailedEmployee = ko.observable({}); this.showModal= function(employee) { self.detailedEmployee(employee); $('#employeeDetails').modal('show'); } } 不需要特殊的 ko.applyBindings。
    【解决方案2】:

    我想提出一种在 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
    }
    

    我非常同意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 不知道应如何显示数据。它只知道应该显示的 datawith 绑定只有在定义了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 事件并在需要时取消此事件。希望这会有所帮助。

    【讨论】:

    • 我喜欢当detailEmployee 的值不是未定义时知道何时显示模式窗口的方法,但这种方法看起来非常冗长。是否有一种不那么冗长的方法来保持关注点的分离?
    • 给你一个后续问题——假设我想向“保存更改”按钮添加一个点击处理程序,该按钮向服务器执行异步发布......你会把那个点击处理程序绑定放在哪里?
    • 实际上在我的示例中,我有保存按钮的单击处理程序:data-bind="click: $parent.SaveUser"。这个方法放在主视图模型中,后端通信应该放在那里。通常在主视图模型中我有这些属性/方法:EditingItemEditItemSaveItemCancelEditItem。对我来说最大的问题是 EditingEdit 之间的混合
    • 使用 'with' 绑定的缺点是,它(重新)创建了 DOM 元素。在我的例子中,我使用 Telerik MVC 控件,将它们的实现绑定到 DOM 元素 - 在我的例子中,例如。绑定到整个控件的 Input 元素的 DatePicker。使用“with”绑定时,日期选择器的实现消失了,因为重新创建了 Input 元素。
    • 伙计们,我知道这是一个有点旧的帖子,但 jsfiddle 代码似乎都没有工作。
    【解决方案3】:

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

    所以,我构建了自己的示例(基于他的 original fiddle,它具有完整的 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'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 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 初始化 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);
    

    【讨论】:

    • 很好,但是对于您要绑定的每个值,有没有办法摆脱额外的data-bind="value: UserBeingEdited() &amp;&amp; UserBeingEdited().Name
    猜你喜欢
    • 2014-05-24
    • 2012-03-14
    • 1970-01-01
    • 1970-01-01
    • 2021-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多