This is a migrated thread and some comments may be shown as answers.

"Mapping" plugin for Kendo

1 Answer 65 Views
MVVM
This is a migrated thread and some comments may be shown as answers.
Stacey
Top achievements
Rank 1
Stacey asked on 12 Nov 2013, 02:49 AM
One thing I have missed a lot with Kendo, that I was accustomed to with KnockoutJS was the KnockoutJS Mapping Plugin. This was an extremely useful feature to me. I have very much wanted its functionality in kendo, so I took some time and just wrote a small few lines of code to do some of what I used to with knockout.

I don't know if this will ever see the light of day, but I would love to see this kind of thing in the future of kendo.

First, you just declare a mapping. To make this easier, I simply added it to the declaration of a model in kendo.data.Model.define. This particular mapping simply uses a function to merge all children under an array called Tags when it is created, so that they each have a function.

var Model = kendo.data.Model.define({
    id: "Id",
    fields: {
        Id: {
            type: "string",
        },
        Name: {
            type: "string",
        },
        Tags: []
    },
    mapping: {
        Tags: {
            children: function (data) {
                return $.extend(data, {
                    onRemove: function (e) {
                        console.log(e);
                    }
                });
            }
        }
    }
});
Next, you need an actual function to perform the mapping. I did this with this code.
kendo.mapping = function (source, mapping) {
    var name,
        value;
 
    // if the user provides us a mapping, we can use that to help
    // build the objects appropriately
    for (name in source) {
        if (source.hasOwnProperty(name)) {
            // get the value for this property or item
            value = source[name];
 
            // try to determine if this is an array, or just a
            // normal object. That will greatly dictate our choice of behavior
            if (value instanceof Array) {
 
                // if this is an array, then we will try to check for a
                // key in the mapping schema
                if (mapping[name].children) {
 
                    // if we discover a mapping key for this array, we
                    // will use it to reconstruct the array data
                    for (var i = 0; i < value.length; i++) {
                        source[name][i] = mapping[name].children(value[i]);
                    }
                }
            } else {
                // attempt to match any non array type keys
                if (mapping[name]) {
                    source[name] = mapping[name](value);
                }
            }
        }
    }
 
    return source;
}
This will go through the mapping data given, and some JSON data given, and do the appropriate work to call upon the mapping keys and
children functions.

Now, I have a function for merging raw JSON data with a kendo view model.
kendo.data.ObservableObject.prototype.fromJSON = function (source, mapping) {
    var name,
        value,
        observable = this;
 
    // if there is mapping given, then pass it through that first
    if (mapping) {
        source = kendo.mapping(source, mapping);
    }
 
    for (name in source) {
        if (observable.hasOwnProperty(name)) {
            observable.set(name, source[name]);
        }
    }
}
So then, I can just call this in my view model and javascript like this.
var viewModel = new Model({
    Id: null,
    Name: null,
    Tags: []
});
 
var dataSource = new kendo.data.dataSource({
    transport: {
        read: {
            url: '/data/items/',
            dataType: "json",
            type: 'GET'
        }
    },
    schema: {
        total: "total",
        data: "data"
    }
});
So then, the controller returns data that looks like this.
Id: "items/1",
Name: "Some Game Item",
Tags: [
    {
        Id: "tags/1",
        Name: "Sword"
    },
    {
        Id: "tags/2",
        Name: "Weapon"
    }
]
And the mapping just needs one call, to merge it with the view model, and to map the function onto the tags array. Where data is the data returned from the dataSource.
viewModel.fromJSON(data.toJSON(), viewModel.mapping);
I think you will find this is extremely useful for building complex models that meet more realistic business needs.

1 Answer, 1 is accepted

Sort by
0
Stacey
Top achievements
Rank 1
answered on 12 Nov 2013, 08:12 PM
I also forgot to add, this can be used to map non-array fields too. You simply declare the function as the actual object you want to map, like this.

mapping: {
    Tags: {
        children: function (data) {
            return $.extend(data, {
                onRemove: function (e) {
                    console.log(e);
                }
            });
        }
    },
    Name: function(data) {
        return data + " Modified";
    }
}
So then, if you want to map the items in an array, you use children - as a reserved keyword. If you want to map the actual object itself, you just make it a function that takes one parameter, and returns the result! 

Using this you can easily mix the kendo models in extremely meaningful, object oriented ways. And it is very lightweight.

It is nowhere near as robust as the knockout mapping plugin (It doesn't handle actual updates), but because kendo does the observable property on the entire model, and not on the individual property, we may not need all of that, since just using the set function accomplishes our updates!

I will keep working on this and once I do more quality testing, I will post it as a full plugin.
Tags
MVVM
Asked by
Stacey
Top achievements
Rank 1
Answers by
Stacey
Top achievements
Rank 1
Share this question
or