"Mapping" plugin for Kendo

2 posts, 0 answers
  1. Stacey
    Stacey avatar
    56 posts
    Member since:
    Aug 2013

    Posted 11 Nov 2013 Link to this post

    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.

  2. Stacey
    Stacey avatar
    56 posts
    Member since:
    Aug 2013

    Posted 12 Nov 2013 Link to this post

    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.
Back to Top