javascriptasp.net-mvc-3knockout.jsupshot

How do I access descendants from a different branch in the same viewModel?


I am currently working on a website spa application and attempting to use knockout.js. it's on an mvc platform with and a dbdatacontroller api using upshot for data and javascript view models. I have a complex view model and am running into difficulty, mainly due to being new to knockout. My biggest problem seems to be in accessing the observables. The database is organized thusly:

    function AdvanceSearch(data) {
var self = this;
self.AdvanceSearchID = ko.observable(data.AdvanceSearchID);
self.FieldTypeEnum = ko.observable(data.FieldTypeEnum);
self.AnswerType = ko.observable(data.AnswerType);
self.UserValues = ko.observableArray(ko.utils.arrayMap(data.UserValues, function (item) {
    return new UserValue(item);
}));
upshot.addEntityProperties(self, "AdvanceSearch:#A.Lib.Repository");
};

function UserValue(data) {
var self = this;

self.UserProfileID = ko.observable(data.UserProfileID);
self.LoginID = ko.observable(data.LoginID);
self.AdvanceSearchID = ko.observable(data.AdvanceSearchID);
self.FieldValueCount = ko.observable(data.FieldValueCount);
self.FieldValueText = ko.observable(data.FieldValueText);
    upshot.addEntityProperties(self, "UserValue:#A.Lib.Repository");
});
};


function AnswerType(data) {
var self = this;
self.AnswerTypeID = ko.observable(data.AnswerTypeID);
self.AnswerTypeText = ko.observable(data.AnswerTypeText);
self.Answers = ko.observableArray(ko.utils.arrayMap(data.Answers, function (item) {
    return new Answer(item);
}));
self.AnswerSliders = ko.observableArray(ko.utils.arrayMap(data.AnswerSliders, function (item) {
    return new AnswerSlider(item);
}));
upshot.addEntityProperties(self, "AnswerType:#A.Lib.Repository");
}

function Answer(data) {
var self = this;
self.AnswerTypeID = ko.observable(data.AnswerTypeID);
self.AnswerEnum = ko.observable(data.AnswerEnum);
self.AnswerText = ko.observable(data.AnswerText);
upshot.addEntityProperties(self, "Answer:#A.Lib.Repository");
}

function AnswerSlider(data) {
var self = this;
self.SliderID = ko.observable(data.SliderID);
self.AnswerTypeID = ko.observable(data.AnswerTypeID);
self.SliderType = ko.observable(data.SliderType);
self.Seed = ko.observable(data.Seed);
upshot.addEntityProperties(self, "AnswerSlider:#A.Lib.Repository");
}

And my view model is just this:

function ASViewModel() {
// Private
var self = this;
var dataSourceOptions = {
    providerParameters: {
        url: "/api/Dating",
        operationName: "GetDatingProfile"
    },
    entityType: "AdvanceSearch:#A.Lib.Repository",
    bufferChanges: false,
    mapping: AdvanceSearch
};

// Public Properties
self.dataSource = new upshot.RemoteDataSource(dataSourceOptions)
                            .refresh();
self.AdvanceSearchs = self.dataSource.getEntities();
}

So my markup is something like

    <ol data-bind="foreach: AdvanceSearch">
   <!-- ko if: FieldTypeEnum()===5 -->
       <select data-bind="options: AnswerType().Answers, optionsText: 'AnswerText', optionsValue: 'AnswerEnum', optionsCaption: 'Not Specified', value: UserValues().FieldValueText"></select>
   <!-- /ko -->
    <!-- ko if: FieldTypeEnum()===11 -->
        <input type="text" class="multilinetext" data-bind="attr: { id: 'value_'+AdvanceSearchID()}, value: UserValues().FieldValueText" />
    <!-- /ko -->

So basically, no matter what I do, I cannot seem to access the values of the items. Anywhere I have accessed the one branch, ie. AdvanceSearch().AnswerType().Answers, trying to get $parents[1].UserValues[].FieldValueText seems always to be undefined. Like I said, I'm new to knockout, so I'm probably just missing something. Or should I be using multiple viewmodels or something similar? (And if so, how would I do that?) Thanks.


Solution

  • Based on your comments, I think I now know what you're trying to do. I am going to abstract it so that it is easier to follow as your ViewModel and Objects are very complex and not easy to understand without explanation.

    If you have object types, Book and Author with constructors:

    function Book(data) {
        this.ISBN = ko.observable( data.ISBN );
        this.Title = ko.observable( data.Title );
        this.AuthorID = ko.observable( data.AuthorID );
        /* other properties */
    }
    
    function Author(data) {
        this.AuthorID = ko.observable( data.AuthorID );
        this.Name = ko.observable( data.Name );
        /* other properties */
    }
    

    To select a Book from a select box and have a input display bound to the Authors name, you will need the viewmodel:

    var vm = {};
    vm.books = ko.observableArray( /* initial data */ );
    vm.authors = ko.observableArray( /* initial data */ );
    vm.selectedBook = ko.observable(null);
    vm.selectedBooksAuthor = ko.computed( function() {
        var authors = vm.authors(),
            selectedBook = vm.selectedBook();
        if( !selectedBook ) return null; // cannot get .AuthorID() if selectedBook == null
        var id = selectedBook.AuthorID();
        for( var a in authors ) {
            if( authors[a].AuthorID() == id )
                return authors[a];
        }
    })
    

    And your HTML:

    <select data-bind="options: books,
                       optionsText: 'Title',
                       optionsCaption: 'Select a book...',
                       value: selectedBook"></select>
    <!-- ko with: selectedBooksAuthor -->
    <input type="text" data-bind="value: Name" />
    <!-- /ko -->
    

    Please note, this can get pretty inefficient over large sets of data -- look into using a better search algorithm (rather than the 'linear' used here) for your computed observable i.e. selectedBooksAuthor

    EDIT:

    Please see this fiddle for a working demonstration.