Search code examples
javascriptjqueryknockout.jsmaterialize

Knockout.js + MaterializeCSS pushed selected values on Multiple select do not work


I am trying to build a form where I select multiple people for a task and I need to be able update task later. So I am trying to push the previously selected assignees in the observableArray before reinitializing the select box as recommended on the MaterializeCSS doc. However, the options are not shown as selected in the select box.

First, I use a Prototype for my assignees.

function Assignee (data) {
    var self = this;
    self.id = ko.observable(data.id);
    self.name = ko.observable(data.name);
}

The same goes for the task.

function Task (data) {
    var self = this;
    self.id = ko.observable(data.id);
    self.title = ko.observable(data.name);
    self.description = ko.observable(data.description);
    self.startDate = ko.observable(data.start);
    self.endDate = ko.observable(data.end);
    self.assignees = ko.observableArray($.map(data.assignees, function (item) {return new Assignee(item)}));
}

In my view model I have an observableArray for all the selectable items and an other for the selected ones. Both employees and assignees observableArray are arrays of Assignee.

function TaskViewModel (task) {
    var self = this;
    self.id = ko.observable(null);
    self.title = ko.observable(null);
    self.description = ko.observable(null);
    self.task = ko.observable(task);
    self.employees = ko.observableArray([]);
    self.assignees = ko.observableArray([]);

    Request(
    '/users/select/assignees',
    'GET',
    {},
    function (data) {
        self.employees($.map(data, function (item) {return new Assignee(item)}));
        if (task != undefined) {
            task.assignees().forEach(function(item, index, array) {
                self.assignees().push(item);
            });
            $('select#task_edit_assignees').material_select();
        }
    });
};

Then my HTML goes like this:

<div class="input-field col s12">
    <select id="task_edit_assignees" data-bind="options: employees, selectedOptions: assignees, optionsText: 'name', optionsCaption: 'Choisissez un ou plusieurs employés'" multiple></select>
    <label for="task_edit_assignees">Assigner à</label>
</div>

Solution

  • There's a couple of issues in your code. The first one is easy: self.assignees().push(item); is pushing the item to the underlying array and not the observable because of the extra parenthesis that are unwrapping the observable before the item is pushed. This prevents knockout from knowing that the array changed and no updates are triggered.

    Secondly though the entire concept is somewhat flawed because your employees array and your assignees array contain entirely different objects. Even though the values within each object might match it is a separate "instance" of the object and will not be equal.

    This will always return false: new Task({id: 1}) == new Task({id: 1})

    What you could do instead is plan on storing only the ids in your selected array (TaskViewModel.assignees). Since you're already using optionsValue: 'id' the binding doesn't need to change. Just modify your callback function to look like this:

    self.employees($.map(data, function (item) {return new Assignee(item)}));
        if (task != undefined) {
            task.assignees().forEach(function(item, index, array) {
                self.assignees.push(item.id());
            });
            $('select#task_edit_assignees').material_select();
        }
    }
    

    example jsFiddle