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

Bindings not preserved through 'set' function

1 Answer 64 Views
MVVM
This is a migrated thread and some comments may be shown as answers.
Stacey
Top achievements
Rank 1
Stacey asked on 22 Nov 2013, 07:07 PM
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?

1 Answer, 1 is accepted

Sort by
0
Alexander Valchev
Telerik team
answered on 26 Nov 2013, 02:59 PM
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!
Tags
MVVM
Asked by
Stacey
Top achievements
Rank 1
Answers by
Alexander Valchev
Telerik team
Share this question
or