如何在嵌套视图模型的 Knockout JS 中使用 "with" 绑定?

How to use "with" binding in Knockout JS with nested View Models?

我正在尝试编写一个模板来为多人填写表格。我不想进入 "foreach" 绑定或模板。我只是想使用一个按钮将所有数据保存在简单的表单中,其中包含 5 个输入,该按钮会将人添加到 observableArray 并为下一个人清除表单。

我已经在下面粘贴了我的全部代码:

<html>
<head>
    <title>Test Knockouts</title>
</head>
<body>
    <div id="rootDiv">
        <ul data-bind="with: Person">
            <li data-bind="event: { click: $data.setupPersonTypeForm.bind($data, 'Member') }">
                <a href="#" aria-controls="home">
                    <span class="radio-label">Member</span>
                </a>
            </li>
            <li data-bind="event: { click: $data.setupPersonTypeForm.bind($data, 'Guest') }">
                <a href="#" aria-controls="messages">
                    <span class="radio-label">Guest</span>
                </a>
            </li>
            <li data-bind="event: { click: $data.setupPersonTypeForm.bind($data, 'Employee') }">
                <a href="#" aria-controls="profile">
                    <span class="radio-label">Employee</span>
                </a>
            </li>
        </ul>
        <div>
            Person Name:
            <input type="text" data-bind="text: PersonName" /><br />
            Person Age:
            <input type="text" data-bind="text: PersonAge" /><br />
            Person Type:
            <input type="text" data-bind="text: PersonType" /><br />
            Person Country:
            <input type="text" data-bind="text: PersonCountry" /><br />
            Person NetWorth:
            <input type="text" data-bind="text: PersonNetWorth" />
        </div>
    </div>

    <script src="jquery-2.1.4.js"></script>
    <script src="knockout-3.4.0.js"></script>

    <script type="text/javascript">

        $(document).ready(function (e) {

            var element = document.getElementById("rootDiv");
            ko.applyBindings(rootVM, element);

            var RootVM = function (data) {
                var self = this;

                self.Id = ko.observable();
                self.Name = ko.observable();
                self.Location = ko.observable();
                self.TypeId = ko.observable();
                self.Age = ko.observable();

                self.Person = ko.observableArray([new PersonVM()]);
            }
            var rootVM = new RootVM();


            var PersonVM = function (data) {
                var self = this;

                self.Id = ko.observable();
                self.PersonName = ko.observable();
                self.PersonType = ko.observable();
                self.PersonAge = ko.observable();
                self.PersonCountry = ko.observable();
                self.PersonNetWorth = ko.observable();

                self.PersonTypeIdPlaceholder = ko.observable();
                self.ShowEmployeeTitleField = ko.observable(false);

                self.setupPersonTypeForm = function (personType) {

                    self.ShowEmployeeTitleField(false);

                    switch (personType) {

                        case "Member":
                            self.PersonTypeIdPlaceholder("Member ID");
                            break;
                        case "Guest":
                            self.PersonTypeIdPlaceholder("Guest ID");
                            break;
                        case "Employee":
                            self.PersonTypeIdPlaceholder("Employee ID");
                            self.ShowEmployeeTitleField(true);
                            break;
                    }
                }

                self.setupPersonTypeForm('Member');

                self.Property = ko.observableArray([new PropertyVM()]);
            }

            var PropertyVM = function (data) {
                var self = this;

                self.Id = ko.observable();
                self.PropertyPlace = ko.observable();
                self.PropertySize = ko.observable();
                self.PropertyName = ko.observable();
                self.PropertyAge = ko.observable();
            }
        });
    </script>
</body>
</html>

我收到以下错误:

knockout-3.4.0.js:72 Uncaught ReferenceError: Unable to process binding "with: function (){return Person }" Message: Person is not defined

我哪里错了?

如有任何帮助,我们将不胜感激!

从这一行开始:

ko.applyBindings(rootVM, element);

...rootVMundefined,您尚未为其分配任何值。您想将其移动到 after your

var rootVM = new RootVM();

...这又需要 之后

var PersonVM = function (data) { ... };

...因为它使用 PersonVM.

只有声明被提升,而不是赋值。当你有:

(function() {
    console.log(foo);

    var foo = 42;
})();

...您看到的是 undefined,而不是 42,因为该代码被解释为:

(function() {
    var foo = undefined;

    console.log(foo);

    foo = 42;
})();

这在您的代码中一直在发生。

至少还有几个其他问题:

  1. 您正在使用 Person 就好像它是一个单一的事物,但您将其定义为一组事物。解决上述问题后,您会遇到无法绑定 click 事件的问题,因为数组没有 setupPersonTypeForm 方法(您的 Person 有,而不是数组里面).

  2. 您正在使用仅存在于 Person 的列表下方的属性,但仅使用列表中的 with: Person

如果您的目标是拥有一个人员列表,并且能够使用表单 add/edit 列表中的人员, 展示了如何做一些几乎相同的事情,只是 "projects" 而不是 "people".

这是为上下文复制的答案中的简单示例:

function Project(title, priority) {
  this.title = ko.observable(title || "");
  this.priority = ko.observable(priority || "Medium");
}


function ProjectViewModel() {
  var self = this;
  this.priorityOptions = ko.observableArray(["High", "Medium", "Low"]);
  this.projects = ko.observableArray();
  this.editing = ko.observable(new Project());
  this.addProject = function() {
    this.projects.push(this.editing());
    this.editing(new Project());
  };
}

ko.applyBindings(new ProjectViewModel(), document.body);
<div>
  <div>
    <label>
      Title:
      <input type="text" data-bind="value: editing().title, valueUpdate: 'input'">
    </label>
  </div>
  <div>
    <label>
      Priority:
      <select data-bind='options: priorityOptions, value: editing().priority'></select>
    </label>
  </div>
  <div>
    <button type="button" data-bind="click: addProject, enable: editing().title">Add Project</button>
  </div>
  <hr>
  <div>Projects:</div>
  <div data-bind="foreach: projects">
    <div>
      <span data-bind="text: title"></span>
      (<span data-bind="text: priority"></span>)
    </div>
  </div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>


在您提出的评论中:

  1. If we had some sub-category for each Project, and i would like to add several of the sub-category items within each specific project. In which viewmodel do you place the addSubCategory function. How do you keep track of each grandchild item?

你只是重复相同的结构,嵌套。例如,我们可以很容易地在 "project" 中添加 "tasks" 列表。

  1. Why can't we use the "with" binding? Is there a limitation in knockout for that?

你喜欢就可以。我没有在原始示例中使用,因为我在一个项目中只有两个字段,但是您可以将 data-bind="with: editing" 放在上面以将它们包装在 with 中。

这是一个使用 with 绑定的任务示例(我已将上面的 editing 更改为 editProject,因为现在我们还有 editTask):

function Task(label, difficulty) {
  this.label = ko.observable(label);
  this.difficulty = ko.observable(difficulty || "Normal");
}

function Project(title, priority) {
  this.title = ko.observable(title || "");
  this.priority = ko.observable(priority || "Medium");
  this.tasks = ko.observableArray();
  this.editTask = ko.observable(new Task());
  this.addTask = function() {
    this.tasks.push(this.editTask());
    this.editTask(new Task());
  };
}


function ProjectViewModel() {
  var self = this;
  this.priorityOptions = ko.observableArray(["High", "Medium", "Low"]);
  this.difficultyOptions = ko.observableArray(["Hard", "Normal", "Easy"]);
  this.projects = ko.observableArray();
  this.editProject = ko.observable(new Project());
  this.addProject = function() {
    this.projects.push(this.editProject());
    this.editProject(new Project());
  };
}

ko.applyBindings(new ProjectViewModel(), document.body);
ul {
  margin-top: 0;
  margin-bottom: 0;
}
<div>
  <!-- Added a new div with a 'with' binding -->
  <div data-bind="with: editProject">
    <div>
      <label>
        Title:
        <input type="text" data-bind="value: title, valueUpdate: 'input'">
      </label>
    </div>
    <div>
      <label>
        Priority:
        <select data-bind='options: $parent.priorityOptions, value: priority'></select>
      </label>
    </div>
    <div style="padding-left: 3em">
      Tasks for this project:
      <div style="padding-left: 2em">
        New task:
        <div data-bind="with: editTask">
          <div>
            <label>
              Difficulty:
              <select data-bind='options: $root.difficultyOptions, value: difficulty'></select>
            </label>
          </div>
          <div>
            <label>
              Label:
              <input type="text" data-bind="value: label, valueUpdate: 'input'">
            </label>
          </div>
        </div>
        <button type="button" data-bind="click: addTask, enable: editTask().label">Add Task</button>
      </div>
      <div data-bind="foreach: tasks">
        <div>
          [<span data-bind="text: difficulty"></span>]
          <span data-bind="text: label"></span>
        </div>
      </div>
    </div>
  </div>
  <div>
    <button type="button" data-bind="click: addProject, enable: editProject().title">Add Project</button>
  </div>
  <hr>
  <div>Projects:</div>
  <div data-bind="foreach: projects">
    <div>
      <span data-bind="text: title"></span>
      (<span data-bind="text: priority"></span>)
    </div>
    <ul data-bind="foreach: tasks">
      <li>[<span data-bind="text: difficulty"></span>]: <span data-bind="text: label"></span></li>
    </ul>
  </div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>