albert
albert

Reputation: 1571

Knockout bind array of complex type with ajax

I have a bootstrap nav-tab and I want to display dynamically content when I select a tab. Ajax returns an array of Results. Each result has Price,Logo,Companyname and an array of Covers. Each cover has Price,MaxCover,Optional and Description. Rest of html code is here link but now I want to return a more complex type.

<script type='text/javascript'>

var cover = new
    {
        Price: ko.observable(),
        MaxCover: ko.observable(),
        Optional: ko.observable(),
        Description: ko.observable(),
    }
var result = new
    {
        Price: ko.observable(),
        InsLogo: ko.observable(),
        CompanyName: ko.observable(),
        Covers: ko.observableArray()
    };

var tab = function (Id, name, selected) {
    this.Id = Id;
    this.name = name;
    this.Results = ko.observableArray();
    this.isSelected = ko.computed(function () {
        return this === selected();
    }, this);
}

var ViewModel = function () {
    var self = this;
    self.selectedTab = ko.observable();
    self.tabs = ko.observableArray([
        new tab(0, 'Tab1', self.selectedTab),
        new tab(1, 'Tab2', self.selectedTab),
        new tab(2, 'Tab3', self.selectedTab)
    ]);
    self.selectedTab(self.tabs()[0]);

    self.selectedTab.subscribe(function () {
        $.ajax({
            url: '@Url.Action("GetSection")',
            data: { Id: self.selectedTab().Id },
            type: 'GET',
            success: function (data) {
                self.selectedTab().Results(data.Results); //Here I want to fill the results!!!!!!
            }
        });

    });

}
ko.applyBindings(new ViewModel());

Upvotes: 0

Views: 101

Answers (1)

Tomalak
Tomalak

Reputation: 338278

Your approach is okay with a few small glitches. Some suggestions to improve it:

  • Make your viewmodels so that they initialize themselves from a parameters object.
  • Don't introduce dependencies between viewmodels when you don't have have to. I'm thinking of the isSelected property here, this can be taken care of in the view. For example, when inside a foreach: tabs: data-bind="css: {selected: $data === $parent.selectedTab()}"
  • You have a timing issue: Subscribe to selectedTab first, then initialize it with self.selectedTab(self.tabs()[0]);. It should be obvious why. (Generally it's useful to split viewmodel creation into a "setup" and an "init" phase.)
  • Only send an Ajax request for tab details when tab details are still empty.
  • Subscribtions receive the new value of the observable as an argument, use it.
  • Observables are functions:
    • If you want to store an Ajax response in them you can use them as a callback directly.
    • In the same way you can use them as an event handler:
      data-bind="click: $parent.selectedTab".
  • JS convention is to use PascalCase for constructor names (like in viewmodels) and camelCase for all other identifiers.

With all that, we get:

function Tab(data) {
    this.Id = data.Id;
    this.name = data.name;
    this.Results = ko.observableArray();
}
function ViewModel(data) {
    var self = this;

    // setup
    self.selectedTab = ko.observable();
    self.selectedTab.subscribe(function (selectedTab) {
        if (selectedTab.Results()) return;
        $.get('@Url.Action("GetSection")', {Id: selectedTab.Id}).done(selectedTab.Results);
    });

    // init
    self.tabs = ko.observableArray(ko.utils.arrayMap(data.tabs, function (tabData) {
        return new Tab(tabData);
    }));
    self.selectedTab(self.tabs()[0]);
}
ko.applyBindings(new ViewModel({
    tabs: [
        {Id: 0, name: 'Tab1'},
        {Id: 1, name: 'Tab2'},
        {Id: 2, name: 'Tab3'}
    ]
}));

To convert plain data structures that come from the server (like your array results and covers) into a structure of viewmodels, observables and observable arrays I recommend looking into the mapping plugin.

Upvotes: 3

Related Questions