Bindings not preserved through 'set' function

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

    Posted 22 Nov 2013 Link to this post

    I need to be able to 'map' some certain data incoming to an existing javascript object that called upon the appropriate code of my MVVM framework, so I took my hand at trying it myself and I thought I had done a pretty good job, but I am running into a problem.

    Edit

    I have also created a jsBin to show this behavior exists even without my mapping. This is something that is occuring in the `set` function of kendo, and I want to find a way to stop it

    jsBin


    Basically, I draw a "mapping" like this, using Kendo's model system.
    var Model = kendo.data.Model.define({
       Id: "Id",
       fields: {
           Id: {
               type: "string",
           },
           Name: {
               type: "string",
           },
           Mutations: [],
           Tags: []
       },
       mapping: {
           Tags: {
               children: function (data) {
                   return $.extend(data, {
                       onRemove: function (e) {
                           // execute code
                       }
                   });
               }
           },
           Mutations: {
               children: function (data) {
                   return $.extend(data, {
                       Label: null,
                       onRemove: function (e) {
                           // execute code
                       },
                       onEdit: function (e) {
                           // execute code
                       },
                       onSave: function (e) {
                           // execute code
                       }
                   });
               }
           }
       }
    });

    I know that on the outside, this looks a bit unnecessary, but it works well for me and fits my thinking process. I'm open to suggestions for other ways to do it, but ...

    Anyway, the purpose of this is to "map" the `onRemove` functions to the `Tags` array when it comes in from the server, and to map the `onRemove, onEdit, onSave` functions to each child in the `Mutations` array. I can do this 'after' they are loaded, but I wanted to try this approach to learn more about javascript.

    So then, this is my mapping code.
    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]);
            }
        }
     
    }

    This will call the following code, which I felt pretty proud of myself for writing, considering this is my first time trying to do anything like this in javascript.
    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;
    }

    Now for a while, I thought this was awesome. It was working how I wanted, as demonstrated here;
    // -------------------------------------------------------------
    // create a kendo ui grid to show the existing prototypes
    // -------------------------------------------------------------
    widgets.grid = $('#grid').kendoGrid({
        dataSource: {
            transport: {
                read: {
                    url: "/administrator/data/prototypes",
                    dataType: "json",
                    type: 'GET'
                }
            },
            schema: {
                total: "total",
                data: "data"
            },
            page: 0,
            pageSize: 15,
            take: 15,
            serverPaging: true,
            serverFiltering: true,
            type: "aspnetmvc-ajax"
        },
        pageable: {
            refresh: true,
            pageSizes: true
        },
        selectable: "row",
        columns: [
            {
                field: "Id",
                width: 25,
                title: "Identity"
            },
            {
                field: "Name",
                width: 40,
                title: "Name"
            }
        ],
        change: function (e) {
            // get the selected row from the grid
            var selected = this.select();
            // get the data from the selected row
            var data = this.dataItem(selected);
            // update the model
            viewModel.fromJSON(data.toJSON(), viewModel.mapping);
        },
    }).data("kendoGrid");
    So this code goes to my controller and gets a JSON list of all of the relevant items from the database (stored in `RavenDB` as JSON). This returns flat items, which is exactly how I want them in the database (since the functions can't be serialized, obviously). But when I get them into my UI, I want those functions. So in the `change` function, I take the data and pass it through my `fromJSON` function, which accepts the mapping defined in the view model.

    This works, it works very well I think. But then when I try to go further and have other bindings on the view model.. .like this..
    viewModel.Mutations.bind("change", function (e) {
       // do something else
    });
    This is really normal kendo code. I attach the `change` event to a function so that it runs whenever that `ObservableArray` changes. This works fine in my other pages where I don't use mapping, but on this one, once the mapping runs, it seems to become 'unbound'. I have found that if I put the `bind` code _after_ the mapping is done, it works.

    So I have to assume that what is happening is that the code that does the binding is somehow hidden inside of the `ObservableArray`, and that it gets deleted when it runs the mapping.

    Can anyone help me with this?
  2. Alexander Valchev
    Admin
    Alexander Valchev avatar
    2895 posts

    Posted 26 Nov 2013 Link to this post

    Hello Stacey,

    Thank you for providing a jsBin sample.

    The behaviour which it demonstrates is actually expected. The change event is bound to the observable array:
    viewModel.Mutations.bind("change", function (e) {
        console.log('changed');
    });

    When, in the onUpdate function, you set the ViewModel field to another ObservableArray the change event handler does not fire because the previous ObservableArray which was assigned to the Mutations field is not changed. Instead, the Mutations field now holds a reference to a new ObservableArray instance.
    // now, just use the 'set' function
    // to completely replace the array
    viewModel.set("Mutations", [{
        Id: "items/1",
        Value: "test"
    }]);

    Any subsequent modifications of the new ObservableArray that is assigned to the Mutations field will not trigger the change event handler attached to the old ObservableArray which was previously referenced by the Mutations field.

    If you plan to completely replace the ObservableArrays you should hook up to the change event of their parent. In your case that is the root ViewModel.
    viewModel.bind("change", function (e) {
        console.log('changed');
    });

    In this way the change event will trigger five times when the users presses onUpdate button.
    I hope this information will help.

    Regards,
    Alexander Valchev
    Telerik
    Join us on our journey to create the world's most complete HTML 5 UI Framework - download Kendo UI now!
Back to Top