How to map to an Array coming from server object using Knockout Mapping plugin and have some computed?

473 views Asked by At

reading this exchange in stackoverflow titled "How to map to an Array coming from server object using Knockout Mapping plugin in templates?" (sorry stackoverflow is putting me limitations on the number of links of the post) i tried to play making use of the answer (the jsFiddle: http://jsfiddle.net/ueGAA/1)

so the exercise was to make the todo of the knockoutjs Tutorial at learn.knockoutjs.com named "Loading and saving data" but using knockout mapping.

the problem is with the kind of of viewmodel declaration of the answer, which I like, here transposed to the todo:

var viewModel = 
{
    tasks : ko.mapping.fromJS(data),
    newTaskText: ko.observable(),
    incompleteTasks: ko.computed(function() {     
           return ko.utils.arrayFilter(this.tasks(), function(task) { return !task.isDone() });
    }),
    // Operations
    addTask:  function() {
        alert('Entering add task, count:' + this.tasks().length);
        this.tasks.push(new Task({ title: this.newTaskText() }));
        this.newTaskText("");
    },
    removeTask: function(task) { this.tasks.remove(task) }

}

The point is here: inside the declaration of ko.computed(), this is referencing window. Normal indeed. The correct behavior can be obtained if I declare the ko.computed() after the vewmodel variable.

this way:

viewModel.incompleteTasks=ko.computed(function() {
    return ko.utils.arrayFilter(viewModel.tasks(), function(task) { return !task.isDone() });
});

I don't like it because it references statically the object viewModel in the anonymous function.
the question is: how to declare in an elegant way the incompleteTasks directly in the viewmodel declaration? the jsFiddle is here http://jsfiddle.net/Yqg8e/

thanks

1

There are 1 answers

1
John Earles On BEST ANSWER

Switch from using an object literal to a constructor function for your ViewModel.

function ViewModel() {
    var self = this;

    self.tasks = ko.mapping.fromJS(data);
    self.newTaskText = ko.observable();
    self.incompleteTasks = ko.computed(function() {       
        return ko.utils.arrayFilter(self.tasks(), function(task) { 
            return !task.isDone()
        });
    });
    self.addTask = function() {
       alert('Entering add task, count:' + self.tasks().length);
       self.tasks.push(new Task({ title: self.newTaskText() }));
       self.newTaskText("");
    };
    self.removeTask = function(task) { self.tasks.remove(task) }
}

ko.applyBindings(new ViewModel());​

Note also the use of var self = this; which allows access to the this context even within inner anonymous functions.

This technique is described in the Knockout docs, in the section on Computed Observables (skip down to the section titled Managing 'this').

Here's an updated fiddle: http://jsfiddle.net/Yqg8e/1/