Working with Knockout / Mapper and Dictionary objects

Skip to first unread message

Chris Marisic

unread,
Apr 25, 2011, 5:25:57 PM4/25/11
to knock...@googlegroups.com
I have an object that contains a dictionary and I'm trying to figure out how to set this up with Knockout and have been making very little progress.

var jsonViewModelText = { "User": { "IsDisabled": false, "Id": "2343243", "IsActive": true, "LockedOutUntil": null, "UnlockedUntil": null, "Claims": { "key0": "value0", "key1": "value1", "key2": "value2" }, "ExternalId": "afdsfdsa", "FirstName": "Chris", "LastName": "Marisic", "OracleId": "FML", "UserName": "chrism@.com", "GlobalAdmin": true }, "Flat1": null, "Flat2": "bob" };

var jsonViewModel = ko.mapping.fromJS(jsonViewModelText);
ko.applyBindings(jsonViewModel);

When I inspect jsonViewModel.User.Claims I see that Claims gets transformed into what looks like a first class object that I would need to invoke something like jsonViewModel.User.Claims.key0() to read the value from it. Being that this is a dictionary I implicitly have no knowledge of what the key names would be at runtime. So I'm not really sure how I could write a template for this.

When I was originally working with knockout and testing some manual objects I was creating I was able to get this template to work

<script id="claimsTemplate" type="text/html">
    {{each(index, claim) User.Claims}}   
    <div>
        <label >
            Key:</label>
        <input type="text" value= "${ claim.key }" />
    </div>
    <div>
        <label >
            Value:</label>
        <input type="text" value= "${ claim.value }" />
    </div>
    {{/each}}                                                        
</script>

But that worked when I was creating a true array by hand with array items being objects with key and value properties. I'm not sure how I would work with what Knockout Mapper creates from my json, also I wonder about the implications of posting that model back to ASP.NET MVC3, I have no idea what the model binder will see there.


rpn

unread,
Apr 26, 2011, 12:08:41 AM4/26/11
to knock...@googlegroups.com
Hello-
If you want a simple way to loop through your properties and display them, then maybe something like this would be sufficient: http://jsfiddle.net/rniemeyer/s9nbx/  It just uses a dependentObservable to put the "keys" into an array, so that you can use it in an each loop.

However, it looks like you have input fields.  If what you really want to be able to do is edit your dictionary (the keys and values), then I think that it gets a bit more complicated.  We really want to turn the dictionary object into an array containing objects with key/value properites, so we can edit either of them.  Then, when we convert it to JSON, we probably want it back in the original format (not an array, but an object properties for each key).  

Here is a sample of doing that: http://jsfiddle.net/rniemeyer/AHNw4/
-it shows using the "create" hooks in the options of the mapping plugin to transform a dictionary into suitable format for editing
-it implements the "toJSON" function of the Dictionary object to handle tranforming it from an array back into the original format.

So, we would receive your data like:

 { "key0": "value0", "key1": "value1", "key2": "value2" }

Convert it to the equivalent of: 

 ko.observableArray([
   { key: ko.observable("key0"), value: ko.observable("value0") },
   { key: ko.observable("key1"), value: ko.observable("value1") },
   { key: ko.observable("key2"), value: ko.observable("value2") }
 ])

Then, make any edits or add/remove items.  Finally, when we call ko.toJSON on our viewModel or part of our viewModel, when we try to stringify the Dictionary object it will run our toJSON function and we will end up with the original format.

Let me know if any of this didn't make sense.  Also, if anyone sees an easier path for editing a dictionary, then feel free to chime in.








Chris Marisic

unread,
Apr 26, 2011, 8:57:18 AM4/26/11
to knock...@googlegroups.com
rpn first let me say thank you for this amazingly detailed answer, I definitely see where you were going with everything and why you would override the toJSON method as you did on the dictionary object. At this point I'm trying to reverse this back to my deep object model and it seems to me that I'm having trouble figuring out what exactly I need do in the mapping object.

When I put what I believe is the correct code I get a "key is not defined" error being thrown by jQuery tmpl.

http://jsfiddle.net/EULML/ is the full setup but the key parts I updated were

var dataFromServer = {
    User: {
        Claims: {

            key0: "value0",
            key1: "value1",
            key2: "value2",
            key3: "value3"
        }
    }
};

var mapping = {
    User: {
        Claims: {
            create: function(options) {
                return new Dictionary(options.data);
            }
        }
    }
};

rpn

unread,
Apr 26, 2011, 9:22:10 AM4/26/11
to knock...@googlegroups.com
I think that you have everything right, except in the mapping options just do:

var mapping {

    Claims{
        createfunction(options{
            return new Dictionary(options.data);
        }
    }
};


The mapping plugin currently doesn't handle the nesting in the options that you added and looks for the field names at the top level.  If you just have one "Claims" or if all "Claims" are handled the same way, then this is fine.  If you were to have User > Claims and SomethingElse > Claims that should be handled differently, then you would have to implement a create at the level of User and SomethingElse, if that makes sense.

Chris Marisic

unread,
Apr 26, 2011, 10:28:10 AM4/26/11
to knock...@googlegroups.com
That did the trick! I'm glad you explained why the mapping shouldn't have had the wrapping User : { } since it was probably one of my list of things to try for guess and check. I would have been thoroughly perplexed at why it would have worked then.

With more understanding of the mapping construct I agree that's a pretty reasonable limitation to not support hierarchical objects, yet. For my case that's absolutely fine since a User has a single claim bag.

I also appreciate you wiring up the access to the add/delete events as that eliminated me having to piece what exactly i wanted for those together.

Dave Merrill

unread,
Sep 3, 2011, 7:18:16 AM9/3/11
to knock...@googlegroups.com
I know this is a bit old, but I have this exact scenario, and a question. (And big thanks, RPN, very clear and relevant explanation, as usual.)

Besides the array needed to represent this data for binding and rendering, I also need the Dictionary version of it, ideally live and bound to the array and the form. In other words, I need an observable version of the JSON representation that's dependent on either the form or array, for use by other parts of the app.

Alternatively, before the operations that need it, I could regenerate it from the array data, but if there's a way to make it live and directly dependent, that'd be cleaner and more in the KO spirit.

Thanks in advance for any thoughts,

Dave

rpn

unread,
Sep 3, 2011, 5:28:58 PM9/3/11
to knock...@googlegroups.com
Hi Dave-
I think that you would be looking for something like this: http://jsfiddle.net/rniemeyer/4YpQK/

The idea is that we produce the array of key/value pairs and then build an object representation as a dependentObservable.  The value of the keys is the same observable that is in the array.  So, in the example you can edit or display the value of "key0" in either spot (of course, you can also remove the "key0" key and break it too).

Hope that I understood your question.  Let me know if you had something different in mind.


Dave Merrill

unread,
Sep 4, 2011, 8:04:10 AM9/4/11
to knock...@googlegroups.com
That's exactly it Ryan, thanks.

I think the dependency tracking model isn't deeply enough ingrained in
me yet (:-). I didn't get that it would track through an active
function like itemsAsObject, as long as the returned object was a
dependantObservable.

Great stuff. Very impressed with KO so far, there seems to be an
elegant way to accomplish much of what I need to do.

Dave

Reply all
Reply to author
Forward
0 new messages