Question

I have an item list displayed with knockout binding. Simplified it looks something like this:

<ul data-bind="foreach: currentItems ">
    <li>
        <div>
            <span data-bind="text: Heading"></span>
        </div>
   </li>
</ul>

In a select list I have potential items that can be added to the first list. When a value is double-clicked an ajax call is initiated with the selectedItem.

<select id="lstSongs" data-bind="options: possibleItems, optionsText: 'Heading', value: selectedItem, dblclick: $root.addItem"></select>

Both arrays use the same model structure defined on server.

The viewmodel looks like this:

function MyViewModel() {
    var self = this;
    self.currentItems = ko.observableArray([]);
    self.possibleItems = ko.observableArray([]);
    self.selectedItem = ko.observable();

    self.addItem = function(item) {
        $.ajax({
            type: "POST",
            url: "/api/MyController/AddItem",
            contentType: "application/json",
            dataType: "json",
            data: ko.toJSON(item),
            success: function(itemId) {

                // Attempt1
                alert(itemId);
                item.ItemId(itemId);
                alert("hello1");
                alert(item.ItemId);
                currentItems.push(item);

                // Attempt2
                alert(itemId);
                item.ItemId = itemId;
                alert("hello2");
                alert(item.ItemId);
                currentItems.push(item);

                // Attempt3
                alert(itemId);
                selectedItem.ItemId = itemId;
                alert("hello3");
                alert(selectedItem.ItemId);
                currentItems.push(selectedItem);
            }
        });
    };
}

var viewModel = new MyViewModel();
var data = @Html.Raw(new JavaScriptSerializer().Serialize(Model));
ko.mapping.fromJS(data, {}, viewModel);
ko.applyBindings(viewModel);

The list with currentItems is loaded from the MVC initial model with ko.mapping. The possibleItems is loaded with values from a dynamic ajax call. But both are loaded as expected so I think those bindings are OK.

The doubleclick is triggered as expected. The call is detected at server and an appropriate itemId is returned. On successful return that value should be set on the selectedItem. Then the selectedItem should be added to the currentItems array and turn up in the first list.

I have elaborated with several attempts but without success. I tried them one at the time with the others commented.

Attempt1: The itemId is returned but the first hello is not reached. Why? Is that not the way to set observable properties?

Attempt2: The value is set, hello2 displayed and itemId reported to be set. But the push call does not make the item turn up in the currentItem list.

Attempt3: The same approach but this time the property is set on the the viewModel selectedItem. This time the hello3 is not reached.

Was it helpful?

Solution 3

Bradley Trager got me on the right track with his first suggestion. To maybe help someone else I just add the working solution here:

var addedItem = ko.mapping.fromJSON(ko.toJSON(item));
addedItem.ItemId(itemId);
self.currentItems.push(addedItem);

OTHER TIPS

Your first attempt isn't working because item is probably not being passed as an observable but a value.

Your second attempt should work fine, but you need to use the self variable:

self.currentItems.push(item);

Your third attempt isn't working for the same reason as attempt one.

You have to create an instance of your added item and assign it to you selectedItem and currentItems array accordingly

success: function() {    
    var addedItem = new Item {ItemId:item.ItemId,Heading:item.Heading };
    self.selectedItem(addedItem);
    self.currentItems.push(addedItem);
    alert("Successfully added");   
}

I think I can reproduce your issue. Seems to me your problem is that you're databinding to the (double) click of the select, where you would like to trigger the event on options instead. You can see this if you don't alert(itemId) but instead console.log(item): it's the root viewmodel that's being passed to your function.

Two solutions come to mind:

  • Use a custom binding handler that finds the actual clicked element (upon skimming similar to this approach); or
  • Drop the select and use a list with individually templated items ("options") that each have a dblclick binding.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top