I have developed a site with MVC4, jQuery and Twitter Boostrap. Now I decided to add knockout to some pages in order to make the workflow more fluent. On this particular page the intention is to have a list of items to the left that can be reordered with jQuery UI sortable items. When clicked the details of each item will turn up in an area to the right for editing. Depending on type the the editing will be very different so I decided to have separate DIV tags that are made visible depending on type.
The previous working version loads the page items through the model. My idea was to add an observable array to the knockout viewmodel and then just load it on page load. The model is defined on the server so my intention was to not define all properties once again on the client viewmodel. Further manipulation and editing would just use the same collection and upload new/changed/deleted values to the server through AJAX calls.
I have read the knockout demos and searched stackoverflow for similar usecases. I think I followed the suggestions, but obviously something is missing. The model is loaded all right. The items turn up and are displayed with different colors depending on type. They are sortable and on resort the changes are sent back to server and persisted all right.
The missing part is the display and editing of the currently selected item. I added another property named currentItem to the viewmodel. In the function $("body").on("click", ".sortableItem" I can verify that the currentItem is loaded and the Heading property is correct.
But visibility and content of the DIV divEditType1 is not affected. Have I misunderstood the binding concept? Have I misunderstood the loading from server JSON array? Any help is most appreciated.
This is the server page model:
public class PresentationItemsModel
{
public Guid PresentationId { get; set; }
public string DisplayName { get; set; }
public List<PresentationItemModel> PresentationItems { get; set; }
}
This is the repeated item model:
public class PresentationItemModel
{
public Guid PresentationItemId { get; set; }
public string PresentationItemType { get; set; }
public int OrderNumber { get; set; }
public string Heading { get; set; }
public string Content { get; set; }
....
}
This is the client page (irrelevant parts removed):
@model MyCustomWeb.Models.PresentationItemsModel
<header>
<h2>@Model.DisplayName</h2>
</header>
<div style="float: left;">
<fieldset>
<legend>Current list</legend>
<ul id="sortable" data-bind="foreach: presentationItems">
<li class="sortableItem btn btn-block" data-bind="css: 'sortable' + PresentationItemType + 'Item'">
<div style="font-weight: 500;">
<span data-bind="text: Heading"></span>
<span class="ui-icon ui-icon-arrowthick-2-n-s" style="display: inline-block; color: gray; float: right; opacity: 0.5; height: 14px; margin: 2px 4px 0 0"></span>
</div>
</li>
</ul>
</fieldset>
</div>
<div id="divEditType1" data-bind="visible: currentItem.PresentationItemType == 'Type1'" style="float: left; margin-left: 50px;">
<fieldset>
<legend>Edit Type 1</legend>
<div class="control-group">
<input type="text" data-bind="value: currentItem.Heading" placeholder = "Enter Heading" />
</div>
<div class="control-group">
<button id="btnSave" type="submit" class="btn btn-primary">Save</button>
<button id="btnDelete" type="submit" class="btn btn-danger">Delete</button>
</div>
</fieldset>
</div>
<div id="divEditType2" data-bind="visible: currentItem.PresentationItemType = 'Type2'" style="float: left; margin-left: 50px;">
</div>
<div id="divEditType3" data-bind="visible: currentItem.PresentationItemType = 'Type3'" style="float: left; margin-left: 50px;">
</div>
@section Scripts {
<script>
$(document).ready(function () {
// Enable item sorting
$("#sortable").sortable(
{
cursor: "move",
placeholder: "sortableSeparator",
update: function () {
// Create array of all changed items
var changeItems = [];
var currentOrderNumber = 1;
$(".sortableItem").each(function () {
var id = $(this).attr('data-id');
var previousOrderNumber = $(this).attr('data-orderNumber');
if (currentOrderNumber != previousOrderNumber)
changeItems.push({ "id": id, "orderNumber": currentOrderNumber });
currentOrderNumber += 1;
});
// Send ajax action
$.ajax({
type: "PUT",
url: "/api/PresentationApi/ChangeOrder/" + "@Model.PresentationId",
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(changeItems)
});
},
}
);
// Enable change depending on item click
// Note: Must be done this way beacuse of the dynamic binding!
$("body").on("click", ".sortableItem", function () {
viewModel.currentItem = ko.dataFor(this);
alert(viewModel.currentItem.Heading); // Works fine!
//alert(JSON.stringify(viewModel.currentItem)); // Also looks good!
});
// Knockout view model
function ViewModel() {
var self = this;
self.presentationItems = ko.observableArray([]);
self.currentItem = ko.observable();
}
var viewModel = new ViewModel();
viewModel.presentationItems(@Html.Raw(Json.Encode(Model.PresentationItems)));
ko.applyBindings(viewModel);
});
</script>
}
Edit: An attempt to create a Fiddle demo can be found here: http://jsfiddle.net/XXNz9/