我第一次使用 knockoutjs 是为了获得一个任务列表,然后能够单击一个以在弹出表单中显示它。我遇到的问题是 data-bind="click: $parent.edit",它调用 ToDoListViewModel/edit。
当我第一次开始编写列表代码时,它会将当前行的 todo 对象作为第一个参数传递给编辑函数。在为我的添加/编辑弹出表单添加第二个视图模型后,它不再以这种方式运行。调用 $parent.edit 仍然调用编辑函数,但是现在它传入一个空对象 { }。
似乎我遇到了某种冲突的绑定问题,有什么想法吗?
这是待办事项列表html:
<table class="table table-striped table-hover" data-bind="visible: isVisible">
<thead>
<tr>
<th>To-Do</th>
<th>Estimate</th>
<th>Deadline</th>
@if (Model == null) { <th>Property</th> }
<th>Tenant</th>
<th style="display: none;">Assigned To</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: todos">
<tr data-bind="visible: isVisible()">
<td><a class="pointer" title="Edit To-Do" data-bind="click: $parent.edit, text: Task"></a></td>
<td data-bind="text: FormattedAmount"></td>
<td data-bind="text: FormattedDueDate"></td>
<td data-bind="visible: $parent.showPropertyColumn, text: PropertyName"></td>
<td data-bind="text: TenantName"></td>
<td>
<div class="btn-group pull-right">
<a class="btn btn-primary pointer default-click-action" data-bind="click: $parent.markComplete">Mark Complete</a>
<button class="btn btn-primary dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a class="pointer" data-bind="click: $parent.edit">Edit To-Do</a></li>
<li><a class="pointer" data-bind="click: $parent.remove">Delete To-Do</a></li>
</ul>
</div>
</td>
</tr>
</tbody>
</table>
这是弹出的html:
@model Housters.Schemas.Models.Property.Listing
<div id="popup" class="modal fade" style="display: none;" data-bind="with: form">
@using (Html.BeginForm("SaveTodo", "Properties", FormMethod.Post, new { Id = "form", @class = "form-horizontal" }))
{
<div class="modal-header">
<a class="close" data-dismiss="modal">×</a>
<h3>To-Do Information</h3>
</div>
<div class="modal-body">
<input id="id" name="id" type="hidden" data-bind="value: todo().Id" />
<input id="originalListingId" type="hidden" value="@(Model != null ? Model.IdString : "")" />
<div class="control-group @(Model != null ? " hide" : "")">
<label class="control-label" for="listingId">Property</label>
<div class="controls">
@Html.DropDownList("listingId", ViewBag.Listings as SelectList, "None",
new Dictionary<string, Object> { { "data-bind", "value: todo().ListingId" } })
</div>
</div>
<div class="control-group">
<label class="control-label" for="tenantId">Tenant</label>
<div class="controls">
<select id="tenantId" name="tenantId" class="span11" data-bind="value: todo().TenantId">
<option value="">None</option>
</select>
<p class="help-block">Is this task related to a certain tenant?</p>
</div>
</div>
<div class="control-group">
<label class="control-label" for="amount">Estimate to Complete</label>
<div class="controls">
<div class="input-prepend"><span class="add-on">$</span><input type="text" id="amount" name="todo.Amount" maxlength="10"
class="span11 number" data-bind="value: todo().Amount" /></div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="dueDate">Due Date</label>
<div class="controls">
<input type="text" id="dueDate" name="todo.DueDate" maxlength="10"
class="span11 date" data-bind="value: todo().DueDate" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="task">Task<span class="required-asterisk">*</span></label>
<div class="controls">
<textarea id="task" name="todo.Task" rows="4" class="span11 required" data-bind="value: todo().Task"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<a class="btn pointer" data-dismiss="modal">Close</a>
<a id="mark-complete-popup" class="btn btn-primary" data-dismiss="modal" data-bind="visible: (todo().Id && !todo().IsComplete), click: markComplete">Mark Complete</a>
<button type="button" class="btn btn-primary" data-bind="click: save">Save</button>
</div>
}
</div>
最后,这里是定义视图模型的 javascript:
function ToDoList () {
if(!(this instanceof arguments.callee))
return new arguments.callee();
var parent = this;
this.showCompleted = ko.observable(false);
this.tenantFilter = new PropertyTenantFilter();
this.viewModel = {
list: new ToDoListViewModel(),
form: new ToDoFormViewModel()
};
this.init = function () {
//get all tenants.
utils.block($("#grid-content"), "Loading");
this.tenantFilter.init(function () {
//initialize view model.
ko.applyBindings(this.viewModel);
//setup controls & events.
$("#dueDate").datepicker();
$("#listingId").change(this.tenantFilter.getByListing.bind(this.tenantFilter)).change();
} .bind(this));
};
function ToDoListViewModel() {
//init.
var self = this;
self.todos = ko.observableArray([]);
//computed.
self.showPropertyColumn = ko.computed(function () {
return $("#originalListingId").val().length == 0;
});
self.isVisible = ko.computed(function () {
return _.find(self.todos(), function (todo) { return todo.isVisible(); }) != null;
});
//operations.
self.add = function () {
//set form field values.
parent.viewModel.form.fill(new schemas.ToDo({}, parent));
//show popup.
$("#popup").modal("show");
};
self.edit = function (todo) {
console.debug("edit: " + JSON.stringify(todo));
//set form field values.
parent.viewModel.form.fill(todo);
//update tenants dropdown for selected listing.
parent.tenantFilter.getByListing();
//show popup.
$("#popup").modal("show");
};
self.markComplete = function (todo) {
parent.markComplete(todo);
};
self.remove = function (todo) {
var result = confirm("Are you sure that you want to delete this To-Do?");
if (result) {
//save changes.
utils.ajax(basePath + "properties/deletetodo",
{ id: todo.Id },
function (success) {
//refresh results.
self.todos.remove(todo);
//show result.
utils.showSuccess('The To-Do has been deleted successfully');
}
);
}
};
self.toggleShowCompleted = function () {
parent.showCompleted(!parent.showCompleted());
$("#showCompletedTodos").text(parent.showCompleted() ? "Show Active" : "Show Completed");
};
self.update = function (todo) {
var existingToDo = _.find(self.todos(), function (item) { return item.Id() == todo.Id(); });
existingToDo = todo;
};
//load todos from server.
utils.ajax(basePath + "properties/gettodos",
{ id: $("#originalListingId").val(), showCompleted: parent.showCompleted() },
function (results) {
var mappedTodos = $.map(results, function (item) { return new schemas.ToDo(item, parent); });
self.todos(mappedTodos);
$("#grid-content").unblock();
}
);
}
function ToDoFormViewModel() {
//init.
var self = this;
self.todo = ko.observable({});
utils.setupPopupForm(self.saved);
//operations.
self.fill = function (todo, isEdit) {
//set form field values.
self.todo = todo;
if (todo.Id()) {
//update tenants dropdown for selected listing.
parent.tenantFilter.getByListing();
}
//show popup.
$("#popup").modal("show");
};
self.save = function (todo) {
self.todo = todo;
$("#form").submit();
};
self.saved = function (result) {
var todo = new schemas.ToDo(result.todo, parent);
if (result.isInsert)
parent.viewModel.list.todos().push(todo);
else
parent.viewModel.list.update(todo);
utils.showSuccess("Your To-Do has been saved successfully.");
};
self.markComplete = function (todo) {
parent.markComplete(todo);
};
}
this.markComplete = function (todo) {
var result = confirm("Are you sure that you want to mark this To-Do complete?");
if (result) {
//save changes.
utils.ajax(basePath + "properties/marktodocomplete", {
id: todo.Id()
},
function () {
todo.IsComplete(true);
//show success.
utils.showSuccess('Your To-Do has been marked completed');
} .bind(this)
);
}
}
this.init();
}