问题
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:
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 theclick
event can't be bound because arrays don't have asetupPersonTypeForm
method (yourPerson
does, not the array it's inside).You're using properties below the list that only exist on
Person
, but only using thewith: 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:
- 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.
- 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