1

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.

Mike
  • 1,199
  • 1
  • 15
  • 24

2 Answers2

0

Your:

<ol data-bind="foreach: AdvanceSearch">

needs to be:

<ol data-bind="foreach: AdvanceSearchs">

Note the pluralisation of AdvanceSearch to AdvanceSearchs

Edit:

Also, UserValues is an Observable Array, hence you cannot access UserValues().FieldValueText.

Note sure exactly what your expected output is, but this displays an input per UserValue:

<!-- ko foreach: UserValues -->
    <input type="text" class="multilinetext" data-bind="attr: { id: 'value_'+UserProfileID()}, value: FieldValueText" />
<!-- /ko -->

NB: I've changed, the ID of the input to something else to avoid duplicate IDs -- this assumes UserProfileID is unique per UserValue

Further,

May I refer you to the documentation for Knockouts options-binding. Your data-bind options for a select element are:

options: - This is the list of options to bind the select element to

optionsText: For each item in the list, the property to display in the select element

optionsCaption: The text for the first/'dummy' item of the select element

value: this should be an observable and is bound to the current selection of the select element

The value binding is a very handy feature of Knockout. It removes the need to worry about IDs -- and thus optionsValue -- as you have access to the entire selected object.

So your select binding can look like:

<select data-bind="options: AnswerType().Answers,
                   optionsText: 'AnswerText',
                @* optionsValue: 'AnswerEnum', *@
                   optionsCaption: 'Not Specified',
                   value: selectedAnswer"></select>

Assuming you've added an observable selectedAnswer to your viewmodel.

Sethi
  • 1,378
  • 8
  • 14
  • so to access the observables inside the observable array, I always need a foreach? is there any way to switch the context inside the select so that my value option can point to an observable of another observable array? – Mike Oct 01 '12 at 15:09
  • taking the select you have, something like `value:UserValues()[someindextakenfromadvancesearchs].selectedAnswer`? And thanks for catching that typo (AdvanceSearchs). – Mike Oct 01 '12 at 15:17
  • I think you're confused over the `value:` binding. `selectedAnswer` here refers to the item selected in your `select` box - it is **not** a property of your UserValue object. I think I now know what you are trying to do, please see my other answer. – Sethi Oct 01 '12 at 17:28
0

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.

Sethi
  • 1,378
  • 8
  • 14
  • Ahh. I think I see now. So for the selected values, leave them null, and then link them through a computed observable to the other array's value. That makes sense. – Mike Oct 02 '12 at 00:04
  • ok, so 1 thing I am not sure of: Where does `id = vm.selectedBook().AuthorID();` come from? I see that `selectedBook()` is an observable, but is it bound by the select? – Mike Oct 02 '12 at 01:32
  • Yes. By setting `value: selectedBook` in the select's data-bind attribute, you are telling knockout to populate the observable `selectedBook` with the current selection – Sethi Oct 02 '12 at 09:00
  • So, I have been trying to get the example to work at [fiddle](http://jsfiddle.net/c56WQ/27/). It really doesn't like nulls, and I don't think it knows what the `selectedBook().AuthorID()` is. Should that be explicitly defined somewhere? – Mike Oct 05 '12 at 07:05
  • I [rewrote the fiddle](http://jsfiddle.net/CkNf6/) to make it a little cleaner and then figured out what the issue was and fixed [your fiddle](http://jsfiddle.net/c56WQ/28/) – Sethi Oct 08 '12 at 08:57
  • Also, note the edits I've made to my answer -- particularly the ko.computed function. Please mark my answer as correct if this covers everything you wanted – Sethi Oct 08 '12 at 09:03
  • Thanks so much for sticking with me until I got it. (Been tracing through both fiddles, and seeing how everything is a function). Off to go unwrap observables in the real code now. – Mike Oct 08 '12 at 19:09