Search code examples
ajaxknockout.jsknockout-mapping-pluginko.observablearray

Knockout Mapping - Fill Observable Arrays keeping Items' methods


I've been facing a problem that is basically the following:

  • I have a knockout ViewModel which contains observable arrays of items with observable properties and methods.
  • I need to pull data from the server. The methods need to exist after data is taken from server. So I create a new ViewModel and then update its value from what comes from server. (THIS DOES NOT WORK, THE RESULTING ARRAY HAS NO ITEMS)
  • If I create, with mapping, a new object using var newObj = ko.mapping.fromJS(data) the resulting Array has items, but its items have no methods. It spoils my Bindings.

The fiddle of my problem: http://jsfiddle.net/claykaboom/R823a/3/ ( It works util you click in "Load Data From The Server" )

The final question is: What is the best way to have items on the final array without making the loading process too cumbersome, such as iterating through every item and filling item's properties in order to keep the previously declared methods?

Thanks,


Solution

  • I changed your code little bit. Check this version of JSFiddle.

     var jsonFromServer = '{"ModuleId":1,"Metadatas":[{"Id":1,"MinValue":null,"MaxValue":null,"FieldName":"Teste","SelectedType":"String","SelectedOptionType":null,"IsRequired":true,"Options":[]}]}';
    

    Your code doesnt work because your jsonFromServer variable does not contain methods we need at binding like you described in your question. ( -- > Metadatas )

    So we need to define a custom create function for Metadata objects at the mapping process like this :

    var mapping = {
        'Metadatas': {
            create: function(options) {
                var newMetaData = new MetadataViewModel(options.parent);
    
                newMetaData.Id(options.data.id);
                newMetaData.FieldName(options.data.FieldName);
                newMetaData.SelectedType(options.data.SelectedType);
                newMetaData.SelectedOptionType(options.data.SelectedOptionType);
                newMetaData.IsRequired(options.data.IsRequired);
                newMetaData.Options(options.data.Options);
    
                // You can get current viewModel instance via options.parent
                // console.log(options.parent);
    
                return newMetaData;
            }
        }
    }
    

    Then i changed your load function to this :

    self.LoadDataFromServer = function() {
    
        var jsonFromServer = '{"ModuleId":1,"Metadatas":[{"Id":1,"MinValue":null,"MaxValue":null,"FieldName":"Teste","SelectedType":"String","SelectedOptionType":null,"IsRequired":true,"Options":[]}]}';
    
        ko.mapping.fromJSON(jsonFromServer, mapping, self);
    }
    

    You dont have to declare a new viewModel and call ko.applyBindings again. Assigning the updated mapping to current viewModel is enough. For more information check this link. Look out for customizing object construction part.

    The final question is: What is the best way to have items on the final array without making the loading process too cumbersome, such as iterating through every item and filling item's properties in order to keep the previously declared methods?

    As far as i know there is no easy way to do this with your object implemantation. Your objects are not simple. They contains both data and functions together. So you need to define custom create function for them. But if you can able to separate this like below then you dont have to customize object construction.

    For example seperate the MetadataViewModel to two different object :

     --> Metadata  : which contains only simple data
     --> MetadataViewModel : which contains Metadata observableArray and its Metadata manipulator functions
    

    With this structure you can call ko.mapping.fromJSON(newMetaDataArray , {} , MetadataViewModelInstance.MetadataArray) without defining a custom create function at the mapping process.