KnockoutJS: Adding Observable Properties and Functions to objects in a mapping generated ObservableArray
Asked Answered
J

3

17

I'm new to KnockoutJS, and I'm stuck trying to add additional properties and methods to the generated objects in the ko.observableArray() as created by the mapping plugin.

Here's where I'm up to:

  • I have a JSON array of Users
  • I've created the ko.observableArray() with the mapping plugin
  • I've got a template that creates table row for each User, so far so good :o)

Here's what I'm trying to do:

Each User has a property called 'IsActive' - I'd like to data-bind a click event to a method on each User object that toggles this 'IsActive' property.

This question looked promising, but it seems like unnecessary duplication to me to have to declare the entire View Model in JS (unless that's the way I have to do it!) - is it possible to just extend the generated object?

I was thinking more along these lines, where there's a way to declare additional properties or methods, and have them extend the mapping generated objects, but this article is concerned with single objects rather than extending objects in a generated array.

Here's the code: http://jsfiddle.net/yZkSf/2/ (not yet working in JS fiddle - but I'll keep playing with it and update this link when I get it working).

Jodeejodhpur answered 6/12, 2011 at 14:50 Comment(0)
R
13

There are several options that you could consider.

-One is to use the create callback to control how your "user" objects get created. You can either define the observables yourself and add extra functionality or call the mapping plugin on the individual user object and then add extra functionality.

Would be something like: http://jsfiddle.net/rniemeyer/fkVaK/

-Otherwise, you can place the "toggle" function on your viewModel and then pass the "user" object to it.

A nice way with 1.3, is to use ko.dataFor along with something like jQuery's live/delegate/on event delegation functionality. Would be like: http://jsfiddle.net/rniemeyer/FkjNr/

//unobtrusive event handler
$(".toggle").live("click", function() {
    var user = ko.dataFor(this);
    if (user) {
       viewModel.toggleIsActive(user);
    }
});

If you don't want to use event delegation, then you can pass the item directly using an anonymous function like: http://jsfiddle.net/rniemeyer/GpQtN/

EDIT: as of 2.0, the current data automatically gets passed to the handler when using click/event bindings, so you can just do:

<a href="#" data-bind="click: $root.toggleIsActive"><span data-bind="text: IsActive"></span></a>
Romish answered 6/12, 2011 at 15:58 Comment(1)
Perfect! - I got close with the create: callback, but the documentation focuses on modifying a child array, and I couldn't see how to augment the base object. Thank you so muchJodeejodhpur
C
4

This is what I came up with using both your and Ryan's answers... seems to work. Please leave feedback, as I am new to Knockout and curious myself, if this is a good approach.

JS:

$(function() {
    $.get("users/getUsers", function(r){
        var vm = ko.mapping.fromJS(r, {
            users: {
                create: function(user){
                    var methods = {
                        toggleIsActive: function(u){u.IsActive(!u.IsActive());},
                        foo: function(u){console.log(u);},
                        bar: function(u){/*whatever*/},   
                    }
                    return $.extend(ko.mapping.fromJS(user.data), methods);
                }
            }
        });
        ko.applyBindings(vm);
    }, 'json');
});

DOM:

<!-- ko foreach: users -->
   <a href="#" data-bind="click: toggleIsActive"><span data-bind="text: IsActive"></span></a>
<!-- /ko -->
Cardew answered 27/6, 2012 at 6:27 Comment(0)
J
0

I've found a way to do it, but it means looping through the generated objects in the array once they've been created.. I'd prefer a way achieve the same result without the additional loop..

EDIT: Like RP Niemeyer suggests in his answer! ;o)

Anyway, one way to add properties to an existing object is to use jQuery extend() to combine the objects.

First, declare the extra properties and functions in a new object:

var userModel = {
    toggleIsActive: function() {
        console.log('toggleIsActive called: before: ' + this.IsActive());
        this.IsActive(!this.IsActive());
        // todo: save!
        console.log('toggleIsActive called: after: ' + this.IsActive());
    }
}

Then, after the ko.mapping.fromJS() call, but before the ko.applyBindings() call, loop through the objects in the generated array and extend them:

viewModel.users = ko.mapping.fromJSON(/* get JSON */);

for (var i = 0; i < viewModel.users().length; i++) {
    $.extend(viewModel.users()[i], userModel);
}

ko.applyBindings(viewModel);
Jodeejodhpur answered 6/12, 2011 at 16:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.