Search code examples
javascriptknockout.jsknockout-mapping-plugin

Knockout click handler not working for table created using ko.computed


I am trying to bind a table to a drop down menu. Here are my requirements:

  • The drop down contains a list of MENUs.

  • A MENU can have many associated MODULES which are displayed in the table for the selected menu item.

  • A MODULE does not have to be associated to a MENU item, in which case it is consider SHARED between the selected menu items.

This is an example of the screen I have:

INSERT SCREEN SHOT HERE

In the example, you can see the CommonShellModule is shared, which means that CommonShellModule will be listed for all menus.

This is what I have so far:

http://jsfiddle.net/fag62z1x/

ViewModel:

var ModuleIndexViewModel = function (data) {
    var self = this;
    ko.mapping.fromJS(data, {}, self);

    self.selectedMenu = ko.observable();

    self.selectedMenuModules = ko.computed(function () {

        if (self.selectedMenu()) {

            //display modules for the selected menu or where they are not associated to a menu
            var modules = ko.utils.arrayFilter(self.Modules(), function (module) {
                return self.isModuleAssociatedToCurrentMenuSelection(module) || self.isSharedModule(module);
            });

            //return a sorted list of modules
            return modules.sort(function (left, right) {
                return left.Name() > right.Name() ? 1 : -1;
            }); 
        }
        return null;
    });

    self.isModuleAssociatedToCurrentMenuSelection = function (module) {
        if (self.selectedMenu()) {
            return module.MenuId() == self.selectedMenu().Id()
        }
        return false;
    }

    self.isSharedModule = function (module) {
        return module.MenuId() == null;
    }

    self.handleSharedCheckboxClick = function (module) {

        if (self.isSharedModule(module)) {
            module.MenuId(null);
        }
        else {
            module.MenuId(self.selectedMenu().Id());
        }

        return true; //allow the default click event action
    }
}

Models:

The models that are being mapped from (these models are serialized and passed in to this view model):

public class IndexViewModel
{
    public IEnumerable<MenuViewModel> Menus { get; set; }
    public IEnumerable<ModuleViewModel> Modules { get; set; }
}

public class MenuViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class ModuleViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int? MenuId { get; set; }
}

View:

...

<div class="form-group top-buffer">
    <label class="control-label" for="menu">Menu</label>
    <select id="menu" class="form-control" data-bind="options: Menus, optionsText: 'Name', value: selectedMenu"></select>
</div>

<div class="row top-buffer">
    <div class="col-lg-12">

        <table class="table table-striped">
            <thead>
                <tr>
                    <th class="col-lg-6">Module Name</th>
                    <th class="col-lg-3">Shared</th>
                    <th class="col-lg-3"></th>
                </tr>
            </thead>
            <tbody data-bind="foreach: selectedMenuModules">
                <tr>
                    <td data-bind="text: Name"></td>
                    <td><input type="checkbox" data-bind="checked: $parent.isSharedModule($data), click: $parent.handleSharedCheckboxClick" /></td>
                    <td><a href="#">Edit</a> | <a href="#">Details</a> | <a href="#">Delete</a></td>
                </tr>
            </tbody>
        </table>

    </div>
</div>

...

The problem is that javascript handleSharedCheckboxClick is not working and I have no idea why. It is being executed each time a shared checkbox is clicked but when I update the module value in this function, the knockout computed is not being recalculated and so the table holds the previous values.

I would appreciate any suggestions as to what I may do to resolve this.


Solution

  • Edit: My bad, played around a bit with the findle and that module value is being observed, looks like the problem is here:

    self.handleSharedCheckboxClick = function (module) {
    
        if (self.isSharedModule(module)) {
            module.MenuId(null);
        }
    

    If the module is shared the value is already null so the value is not being changed, modifying that code adding a ! like this:

    self.handleSharedCheckboxClick = function (module) {
    
        if (!self.isSharedModule(module)) {
            module.MenuId(null);
        }
    

    make it work as you wanted, shared status saved between menu item value updates (click the checked checkbox and the line is no more shared but binded to the current menu item, click it again and it goes back to shared)

    jsfindle: http://jsfiddle.net/yj1s9qod/