Item selection MVC view with KnockoutJS

2019-08-09 08:01发布

问题:

I am trying to implement a generic ASP.net MVC view. The UI should display a list of available and selected items loading data (basically list of string) from server. User can make changes into the list i.e. can select new items from available item list and also can remove items from selected list.

I wanted to do it using KnockoutJS as to take advantage of binding.

I manage to complete it upto the point everything is working except showing selected item as checked when the view is initialized in available list. E.g. As Shown Here

I tried various options (using template (closest to what I want to achieve), Checked attr, possible options), the issue is if I manage to display item checked some other functionality breaks. Tried defining a template but could not get it to work in my case.

HTML:

<div class='moverBoxOuter'>
<div id='contactsList'>
    <span data-bind="visible: availableItems().length > 0">Available countries: </span>
    <ul data-bind="foreach: availableItems, visible: availableItems().length > 0">
        <li>
            <input type="checkbox" data-bind="checkedValue: $data, checked: $root.selectedItems" />
            <span data-bind="text: title"></span>
        </li> 
    </ul>

    <span data-bind="visible: selectedItems().length > 0">Selected countries: </span>

    <ul data-bind="foreach: selectedItems, visible: selectedItems().length > 0">
        <li>
            <span data-bind="text: title"></span>
            <a href="#" data-bind="click: $parent.removeItem">Delete</a>
        </li> 
    </ul>
</div>

JS:

    var initialData = [
    {
        availableItems: [
          { title: "US", isSelected: true },
          { title: "Canada", isSelected: false },
          { title: "India", isSelected: false }]
    },
    {
        selectedItems: [
          { "title": "US" },
          { "title": "Canada" }
        ]
    }
];

function Item(titleText, isSelected) {
    this.title = ko.observable(titleText);
    this.isSelected = ko.observable(isSelected);
}

var SelectableItemViewModel = function (items) {
    // Data
    var self = this;

    self.availableItems = ko.observableArray(ko.utils.arrayMap(items[0].availableItems, function (item) {
        return new Item(item.title, item.isSelected);
    }));

    self.selectedItems = ko.observableArray(ko.utils.arrayMap(items[1].selectedItems, function (item) {
        return new Item(item.title, item.isSelected);
    }));

    // Operations
    self.selectItem = function (item) {
        self.selectedItems.push(item);
        item.isSelected(!item.isSelected());
    };

    self.removeItem = function (removedItem) {
        self.selectedItems.remove(removedItem);
        $.each(self.availableItems, function (item) {
            if (item.title === removedItem.title) {
                item.isSelected = false;
            }
        });
    };
}

var vm = new SelectableItemViewModel(initialData);

$(document).ready(function () {
    ko.applyBindings(vm);
});

Could you please help, see jsfiddle below:

http://jsfiddle.net/sbirthare/KR4a6/6/

**Update: Follow up question below **

Its followup question:

I need to add a combobox on same UI e.g. for US state. The available items are counties, based on user selection in state combo I need to filter out counties. I am getting data from server using AJAX and its all successful BUT the displayed list is not refreshing. I was expecting having binding setup correctly, if we change the observable array in viewmodel, the UI should change. I tried forcing change to availableItems but it just display all items. Please see if you can spot the problem in below code where I am updating ViewModel observable array.

function multiselect_change() {

            console.log("event: openmultiselect_change");

            var selectedState = $("#stateDropdownSelect").val();

            var propertyName = $("#PropertyName").val();
            var searchId = @Model.SearchId;
            var items;

            var model = { propertyName: propertyName, searchId: searchId, stateName: selectedState };
            $.ajax({
                url: '@Url.Action("GetFilterValues", "Search")',
                contentType: 'application/json; charset=utf-8',
                type: 'POST',
                dataType: 'html',
                data: JSON.stringify(model)
            })
                .success(function(result) {
                    debugger;

                    items = JSON.parse(result);

                    vm.availableItems(items.AvailableItems);

                    //vm.availableItems.valueHasMutated();

                    //var item = document.getElementById('availableItemId');
                    //ko.cleanNode(item);
                    //ko.applyBindings(vm, item);

                    vm.filter(selectedState);
                })
                .error(function(xhr, status) {
                    alert(status);
                });        
    }

回答1:

As user3426870 mentioned, you need to change the value you passed to the checked binding to boolean.

<input type="checkbox" data-bind="checkedValue: $data, checked: isSelected" />

Also, I don't think you need to have selectedItems in the initial data.

Instead in the viewModel, you can do something like:

self.selectedItems = ko.computed(function() {
            return ko.utils.arrayFilter(self.availableItems(), function (item) {
                return item.isSelected();
            });
        });


回答2:

It's because you give an array to the binding checked while it's supposed to be a value comparable to true or false (like undefind or an empty string). I would use a function checking if the $data is in your array and returning a boolean to your binding.

Something like that!