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

放肆的年华 提交于 2019-12-08 12:35:33

问题


I am trying to write a template to fill a form for multiple persons. I do not want to go in for "foreach" binding or template. I am just trying to save all the data in the simple form with 5 inputs using a button that will add the person to the observableArray and clear the form for the next person.

I have pasted my entire code below:

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

I am getting the following error:

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

Where am i going wrong here?

Any help would be appreciated!.


回答1:


As of this line:

ko.applyBindings(rootVM, element);

...rootVM is undefined, you haven't assigned it any value yet. You want to move that to after your

var rootVM = new RootVM();

...which in turn needs to be after the

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

...since it uses PersonVM.

Only declarations are hoisted, not assignments. When you have:

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

    var foo = 42;
})();

...you see undefined, not 42, because that code is interpreted as:

(function() {
    var foo = undefined;

    console.log(foo);

    foo = 42;
})();

That's happening all through your code.

There are at least a couple of other issues as well:

  1. You're using Person as though it were a single thing, but you're defining it as an array of things. Once you fix the issue above, you have the issue that the click event can't be bound because arrays don't have a setupPersonTypeForm method (your Person does, not the array it's inside).

  2. You're using properties below the list that only exist on Person, but only using the with: Person on the list.

If your goal is to have a list of people, and to be able to add/edit people on that list with a form, my answer over here shows how to do something nearly identical, just with "projects" rather than "people".

Here's the simple example in that answer duplicated for context:

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>

In a comment you've asked:

  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?

You just repeat the same structure, nested. For instance, we can readily add a list of "tasks" in our "project"s.

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

You can if you like. I didn't in the original example because I only had two fields on a project, but you could put data-bind="with: editing" in the above to wrap those in a with instead.

Here's an example with tasks and using with bindings (I've changed editing from the above to editProject since now we also have 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>


来源:https://stackoverflow.com/questions/36481401/how-to-use-with-binding-in-knockout-js-with-nested-view-models

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!