In my controller I have a function that recieves an object from Java controller. My AngularJS variable is simple:
var self = this;
self.item = {};
And my function where I get the object:
function getItem() {
MyService.getItem(REST_SERVICE_URI)
.then(
function (d) {
self.item = d;
},
function (errResponse) {
console.error('Error while getting item');
}
);
}
Object that's received has rather complicated structure. It has id, name and list of child objects, who have also id and name fields. How do I get into this object's fields and list in the AngularJS controller? I tried loop though list using fucntion below to even count duplicate values but it didn't work. I tried even to include one more loop into it with outputing result in console, no effect. It only returns zeros.
var i = "SOME TEST NAME VALUE TO CHECK";
function getCount(i) {
var iCount = iCount || 0;
for (var el in self.item) {
console.log("let me see what are you: " + el);
if (el == i) {
iCount++;
}
}
return iCount;
}
The object I recieve is ok, I can see it content in Chrome using F12 - Network - Response or Preview.
added later:
On my page I test it like this
<tr class="my_item" ng-repeat="p in ctrl.item.children">
<span>getCount {{p.name}}: {{ctrl.getCount(p.name)}}</span>
</tr>
It displays p.name in the span btw. Java object structure is
public class Item {
private int id;
private String name;
List<Child> children = new ArrayList<>();
}
Child class is simple
public class Child {
private int id;
private String name;
}
As per your question, the content is complex and has recursive properties inside child content.
So you need to iterate on content recursively, inside one forEach loop.
See this example working Demo:
var myApp = angular.module('myApp', []);
myApp.controller('ExampleController', function() {
var vm = this;
vm.count = 0;
vm.searchTxt = "";
vm.getCount = function() {
vm.count = 0; //clear count before search
recursive(vm.content);
}
function recursive(dataArray) { //recursive function
dataArray.forEach(function(data) {
if (vm.searchTxt == data.name) { //match name property
vm.count = vm.count + 1;
}
if (data.child.length > 0) {
recursive(data.child); // call recursive function
}
});
}
vm.content = [{ //example content
id: 1,
name: 'one',
child: [{
id: 1.1,
name: 'new one',
child: [{
id: 1,
name: 'one',
child: []
}]
}]
}, {
id: 2,
name: 'two',
child: [{
id: 1.1,
name: 'new two',
child: []
}]
}]
});
<script src="https://code.angularjs.org/1.5.2/angular.js"></script>
<div ng-app="myApp" ng-controller="ExampleController as vm">
<input ng-model="vm.searchTxt" placeholder="ender search.." />
<br>
<button ng-click="vm.getCount()">Search</button>
<br>
<span>Match 'Name' count : {{vm.count}}</span>
</div>
Related
Is there any way to get a key-value from an object's parent object? In the example below, I want to combine urlParent with section:
const linkItems = [
{
id: 1,
name: 'Home Page',
urlParent: '/home',
subItems: [
{
subId: 1,
name: 'Project 1',
section: '#project1',
get url() {
//output /home#project1
}
}
]
}
];
console.log(linkItems[0].subItems[0].url) // /home#project1;
You cannot reference an Object parent that way (Object(value) ← Array ← Object), and not even from an Object's parent Object.
What you can do instead is:
Create two Classes, one for the parent and one for the child.
When adding a child to the parent, just make a "linked list", by referencing the parent's this to the created child item parent property
class Child {
constructor(data) {
Object.assign(this, data);
}
get url() {
return this.parent.urlParent + this.section
}
}
class Parent {
constructor(data) {
Object.assign(this, data);
this.subItems = [];
}
addChild(item) {
this.subItems.push(new Child({...item, parent: this}));
}
}
// Example:
const parent = new Parent({id:1, name:"Home", urlParent:"/home"});
parent.addChild({subId:1, name:"Project 1", section:"#project1"});
console.log(parent.subItems[0].url) // /home#project1;
But hey! Nodes and trees
Your original idea and the above use too much complexity.
What I'd suggest is to treat all parent, child, whatever, as Page Nodes.
class Page {
constructor(data) {
Object.assign(this, data);
this.children = {};
}
addChild(page) {
page.parent = this; // Linked to parent!
this.children[page.id] = page;
}
get url() {
// Generate full URI by recursing the parents tree
return this.parent ? `${this.parent.url}/${this.slug}` : this.slug;
}
}
// Example:
// 1. Create pages:
const pageRoot = new Page({id:1, name:"Home page", slug:""});
const pageProj = new Page({id:3, name:"All projects", slug:"projects"});
const pageWebs = new Page({id:4, name:"Websites", slug:"websites"});
const pageStOv = new Page({id:6, name:"Stack Overflow", slug:"so"});
const pageSpec = new Page({id:9, name:"Stack Overflow Specs", slug:"specs"});
// 2. Create Tree:
pageRoot.addChild(pageProj);
pageProj.addChild(pageWebs);
pageWebs.addChild(pageStOv);
pageStOv.addChild(pageSpec);
// 3. Test
console.log(pageRoot.url); // "";
console.log(pageProj.url); // "/projects";
console.log(pageSpec.url); // "/projects/websites/so/specs";
console.log(pageRoot);
const linkItems = [
{
id: 1,
name: 'Home Page',
urlParent: '/home',
get subItems(){
console.log(this.name);
return ([
(() => {
console.log(this);
return {
subId: 1,
name: 'Project 1',
section: '#project1',
urlParentFromOuterScope: () => {
return this.urlParent;
},
sectionString(){
return this.section;
},
url(){
console.log('url', this);
return this.urlParentFromOuterScope() + this.sectionString();
}
}
})()
])
}
}
];
const subItems = linkItems[0].subitems;
console.log(linkItems[0].subItems[0].url());
Please feel free to remove the unnecessary 'console.log's after you understand the approach.
I took the liberty of adding a few methods.
This is a tricky one and has to do with the scope of this in array functions.
P.S.: I guess this can be simplified.
Problem
I have a combo box, basically a select element that is filled with an array of complex objects by ng-options. When I update any object of the collection on second-level, this change is not applied to the combo box.
This is also documented on the AngularJS web site:
Note that $watchCollection does a shallow comparison of the properties of the object (or the items in the collection if the model is an array). This means that changing a property deeper than the first level inside the object/collection will not trigger a re-rendering.
Angular view
<div ng-app="testApp">
<div ng-controller="Ctrl">
<select ng-model="selectedOption"
ng-options="(selectedOption.id + ' - ' + selectedOption.name) for selectedOption in myCollection track by selectedOption.id">
</select>
<button ng-click="changeFirstLevel()">Change first level</button>
<button ng-click="changeSecondLevel()">Change second level</button>
<p>Collection: {{ myCollection }}</p>
<p>Selected: {{ selectedOption }}</p>
</div>
</div>
Angular controller
var testApp = angular.module('testApp', []);
testApp.controller('Ctrl', ['$scope', function ($scope) {
$scope.myCollection = [
{
id: '1',
name: 'name1',
nested: {
value: 'nested1'
}
}
];
$scope.changeFirstLevel = function() {
var newElem = {
id: '1',
name: 'newName1',
nested: {
value: 'newNested1'
}
};
$scope.myCollection[0] = newElem;
};
$scope.changeSecondLevel = function() {
var newElem = {
id: '1',
name: 'name1',
nested: {
value: 'newNested1'
}
};
$scope.myCollection[0] = newElem;
};
}]);
You can also run it live in this JSFiddle.
Question
I do understand that AngularJS does not watch complex objects within ng-options for performance reasons. But is there any workaround for this, i.e. can I manually trigger re-rendering? Some posts mention $timeout or $scope.apply as a solution, but I could utilize neither.
A quick hack I've used before is to put your select inside an ng-if, set the ng-if to false, and then set it back to true after a $timeout of 0. This will cause angular to rerender the control.
Alternatively, you might try rendering the options yourself using an ng-repeat. Not sure if that would work.
Yes, it's a bit ugly and needs an ugly work-around.
The $timeout solution works by giving AngularJS a change to recognise that the shallow properties have changed in the current digest cycle if you set that collection to [].
At the next opportunity, via the $timeout, you set it back to what it was and AngularJS recognises that the shallow properties have changed to something new and updates its ngOptions accordingly.
The other thing I added in the demo is to store the currently selected ID before updating the collection. It can then be used to re-select that option when the $timeout code restores the (updated) collection.
Demo: http://jsfiddle.net/4639yxpf/
var testApp = angular.module('testApp', []);
testApp.controller('Ctrl', ['$scope', '$timeout', function($scope, $timeout) {
$scope.myCollection = [{
id: '1',
name: 'name1',
nested: {
value: 'nested1'
}
}];
$scope.changeFirstLevel = function() {
var newElem = {
id: '1',
name: 'newName1',
nested: {
value: 'newNested1'
}
};
$scope.myCollection[0] = newElem;
};
$scope.changeSecondLevel = function() {
// Stores value for currently selected index.
var currentlySelected = -1;
// get the currently selected index - provided something is selected.
if ($scope.selectedOption) {
$scope.myCollection.some(function(obj, i) {
return obj.id === $scope.selectedOption.id ? currentlySelected = i : false;
});
}
var newElem = {
id: '1',
name: 'name1',
nested: {
value: 'newNested1'
}
};
$scope.myCollection[0] = newElem;
var temp = $scope.myCollection; // store reference to updated collection
$scope.myCollection = []; // change the collection in this digest cycle so ngOptions can detect the change
$timeout(function() {
$scope.myCollection = temp;
// re-select the old selection if it was present
if (currentlySelected !== -1) $scope.selectedOption = $scope.myCollection[currentlySelected];
}, 0);
};
}]);
Explanation of why changeFirstLevel works
You are using (selectedOption.id + ' - ' + selectedOption.name) expression to render select options labels. This means an {{selectedOption.id + ' - ' + selectedOption.name}} expression is working for select elements label. When you call changeFirstLevel func the name of selectedOption is changing from name1 to newName1. Because of that html is rerendering indirectly.
Solution 1
If the performance is not a problem for you you can simply delete the track by expression and the problem will be solved. But if you want performance and rerender at the same time both will be a bit low.
Solution 2
This directive is deep watching the changes and apply it to model.
var testApp = angular.module('testApp', []);
testApp.directive('collectionTracker', function(){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
var oldCollection = [], newCollection = [], ngOptionCollection;
scope.$watch(
function(){ return ngModel.$modelValue },
function(newValue, oldValue){
if( newValue != oldValue )
{
for( var i = 0; i < ngOptionCollection.length; i++ )
{
//console.log(i,newValue,ngOptionCollection[i]);
if( angular.equals(ngOptionCollection[i] , newValue ) )
{
newCollection = scope[attrs.collectionTracker];
setCollectionModel(i);
ngModel.$setUntouched();
break;
}
}
}
}, true);
scope.$watch(attrs.collectionTracker, function( newValue, oldValue )
{
if( newValue != oldValue )
{
newCollection = newValue;
oldCollection = oldValue;
setCollectionModel();
}
}, true)
scope.$watch(attrs.collectionTracker, function( newValue, oldValue ){
if( newValue != oldValue || ngOptionCollection == undefined )
{
//console.log(newValue);
ngOptionCollection = angular.copy(newValue);
}
});
function setCollectionModel( index )
{
var oldIndex = -1;
if( index == undefined )
{
for( var i = 0; i < oldCollection.length; i++ )
{
if( angular.equals(oldCollection[i] , ngModel.$modelValue) )
{
oldIndex = i;
break;
}
}
}
else
oldIndex = index;
//console.log(oldIndex);
ngModel.$setViewValue(newCollection[oldIndex]);
}
}}
});
testApp.controller('Ctrl', ['$scope', function ($scope) {
$scope.myCollection = [
{
id: '1',
name: 'name1',
nested: {
value: 'nested1'
}
},
{
id: '2',
name: 'name2',
nested: {
value: 'nested2'
}
},
{
id: '3',
name: 'name3',
nested: {
value: 'nested3'
}
}
];
$scope.changeFirstLevel = function() {
var newElem = {
id: '1',
name: 'name1',
nested: {
value: 'newNested1'
}
};
$scope.myCollection[0] = newElem;
};
$scope.changeSecondLevel = function() {
var newElem = {
id: '1',
name: 'name1',
nested: {
value: 'newNested2'
}
};
$scope.myCollection[0] = newElem;
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular.min.js"></script>
<div ng-app="testApp">
<div ng-controller="Ctrl">
<p>Select item 1, then change first level. -> Change is applied.</p>
<p>Reload page.</p>
<p>Select item 1, then change second level. -> Change is not applied.</p>
<select ng-model="selectedOption"
collection-tracker="myCollection"
ng-options="(selectedOption.id + ' - ' + selectedOption.name) for selectedOption in myCollection track by selectedOption.id">
</select>
<button ng-click="changeFirstLevel()">Change first level</button>
<button ng-click="changeSecondLevel()">Change second level</button>
<p>Collection: {{ myCollection }}</p>
<p>Selected: {{ selectedOption }}</p>
</div>
</div>
Why don't you just simply track collection by that nested property ?
<select ng-model="selectedOption"
ng-options="(selectedOption.id + ' - ' + selectedOption.name) for selectedOption in myCollection track by selectedOption.nested.value">
Update
Since you don't know which property to track you can simply track all properties passing a function on track by expression.
ng-options="(selectedOption.id + ' - ' + selectedOption.name) for selectedOption in myCollection track by $scope.optionsTracker(selectedOption)"
And on Controller:
$scope.optionsTracker = (item) => {
if (!item) return;
const firstLevelProperties = Object.keys(item).filter(p => !(typeof item[p] === 'object'));
const secondLevelProperties = Object.keys(item).filter(p => (typeof item[p] === 'object'));
let propertiesToTrack = '';
//Similarilly you can cache any level property...
propertiesToTrack = firstLevelProperties.reduce((prev, curr) => {
return prev + item[curr];
}, '');
propertiesToTrack += secondLevelProperties.reduce((prev, curr) => {
const childrenProperties = Object.keys(item[curr]);
return prev + childrenProperties.reduce((p, c) => p + item[curr][c], '');
}, '')
return propertiesToTrack;
}
I think that any solution here will be either overkill (new directive) or a bit of a hack ($timeout).
The framework does not automatically do it for a reason, which we already know is performance. Telling angular to refresh would be generally frowned upon, imo.
So, for me, I think the least intrusive change would be to add a ng-change method and set it manually instead of relying on the ng-model change. You'll still need the ng-model there but it would be a dummy object from now on. Your collection would be assigned on the return (.then ) of the response , and let alone after that.
So, on controller:
$scope.change = function(obj) {
$scope.selectedOption = obj;
}
And each button click method assign to the object directly:
$scope.selectedOption = newElem;
instead of
$scope.myCollection[0] = newElem;
On view:
<select ng-model="obj"
ng-options="(selectedOption.id + ' - ' + selectedOption.name) for selectedOption in myCollection track by selectedOption.id"
ng-change="change(obj)">
</select>
Hope it helps.
I have a javascript object which is structured like the following:
var object = {
item1: {
name: 'item1',
details: {
name: 'detail1',
warn_lvl: 1
},
check: function(res) {
console.log('Warn lvl of item1 = '+//access var?)
}
}
}
In my console.log() from my check() function, I would like to print the warn_lvl of item1, but I can't figure out how to access it.
I tried several things with this, but nothing which work.
What is the proper way to access this var?
Edit
To be more precise (sorry, my mistake), I call the check function from another Javascript file, like this (only relevant parts):
var fetchMetrics = function (config, metrics) {
Object.keys(metrics).forEach(function(section) {
var metric = metrics[section];
doRequests(metric, section);
};
var doRequests = function(metric, section) {
$.ajax({
dataType: "json",
url: metric.url,
data: metric.params,
var result;
if (!query_result.length) {
result = state_retriever.RESULT_UNKNOWN
} else {
result = metric.check(query_result);
}
};
If you're calling check via object.item1.check("res arg here"), then you can use this.details.warn_lvl:
var object = {
item1: {
name: 'item1',
details: {
name: 'detail1',
warn_lvl: 1
},
check: function(res) {
console.log('Warn lvl of item1 = ' + this.details.warn_lvl)
}
}
};
object.item1.check();
But you've said you're calling check a different way. You can reliably access it via object.item1.details.warn_lvl regardless of how you call check:
var object = {
item1: {
name: 'item1',
details: {
name: 'detail1',
warn_lvl: 1
},
check: function(res) {
console.log('Warn lvl of item1 = ' + object.item1.details.warn_lvl);
}
}
};
var c = object.item1.check;
c();
I'm trying to populate a drop down box rendered by Mithril's view from methods being called outside of its module (not sure if this terminology is correct, but outside of the property which contains the view, model and controller).
This Chrome extension adds a new field to an existing page and depending on what the user select, the drop down box should refresh to items pertaining to the selected item. I can get up to the stage of getting the new list of items, but i cannot get the drop down list to redraw with the new objects.
The following shows the module which gets inserted inside an existing page:
var ItemsList = {
model: function () {
this.list = function (id) {
var d = m.deferred()
// Calls Chrome extension bg page for retrieval of items.
chromeExt.getItems(pId, function (items) {
// Set default values initially when the controller is called.
if (items.length === 0) {
items = [
{name: 'None', value: 'none'}
]
}
d.resolve(items || [])
})
return d.promise
}
},
controller: function () {
this.model = new ItemsList.model()
this.index = m.prop(0)
this.onchange = function (e) {
console.info('ctrl:onchange', e.target)
}
// Initialise the drop down list array list.
this.dropDownItemsList = m.prop([]);
// This sets the default value of the drop down list to nothing by calling the function in the model,
// until the user selects an item which should populate the drop down list with some values.
this.getItems = function(pId) {
this.model.list(pId).then(function (data) {
this.dropDownItemsList(data)
m.redraw()
}.bind(this))
}
this.getItems(0);
},
view: function (ctrl) {
var SELECT_ID = 'record_select'
return vm.Type() ? m('div', [
m('.form__item', [
m('.label', [
m('label', {
htmlFor: SELECT_ID
}, 'ID')
]),
m('.field', [
m('select#' + SELECT_ID, {
onchange: ctrl.onchange.bind(ctrl)
},
ctrl.dropDownItemsList().map(function (it, i) {
return m('option', {
value: it.value,
checked: ctrl.model.index === i
}, it.name)
})
),
])
]),
]) : null
}
}
And it is mounted using
m.mount("element name here", ItemsList);
The code which checks to see if the item has changed is using a mutation observer, and whenever it detects changes to a certain field, it will call a method to get the new values. I can see that the return value has my new items.
I have tried various different methods on trying to update the drop down list, first by trying to set the "this.list" with the new items list i've got, or trying to create a returnable method on the controller which i can call when the mutation observer fires.
After getting the new items, how can i make the drop down list show the new items which has been retrieved?
I have read guides which shows functions in the controller or model being run - but only if they've been defined to use them already in the view (i.e. have an onclick method on the view which calls the method) but so far i cannot figure out how to update or call methods from outside of the module.
Is there a way to achieve the above or a different method i should approach this?
After some more research into how Mithril works, seems like that it's not possible to call any functions defined within the component.
Due to this, i have moved the model outside of the component (so now it only has the controller and the view defined) and bound the view to use the model outside of the component.
Now calling a function which updates the model (which is now accessible from elsewhere in the code) and redrawing shows the correct values that i need.
If I understand correctly, you need to have two variables to store your lists, one to store the old list and one to store the updated list so you can always map the updated one and go to your old one if you need.
Here is a simple implementation of a drop down list with some methods to update and search. You can update the list on the fly using the methods.
mithDropDown
jsFiddle
var MythDropDown = function(list) {
if (Array.isArray(list))
this.list = list;
else
list = [];
if (!(this instanceof MythDropDown))
return new MythDropDown(list);
var self = this;
this.selected = {
name: list[0],
index: 0
};
this.list = list;
};
MythDropDown.prototype.view = function(ctrl) {
var self = this;
return m('select', {
config: function(selectElement, isinit) {
if (isinit)
return;
self.selectElement = selectElement;
self.update(self.list);
},
onchange: function(e) {
self.selected.name = e.target.value;
self.selected.index = e.target.selectedIndex;
}
},
this.list.map(function(name, i) {
return m('option', name);
}));
};
MythDropDown.prototype.getSelected = function() {
return (this.selected);
};
MythDropDown.prototype.update = function(newList) {
this.list = newList;
this.selectElement.selectedIndex = 0;
this.selected.name = newList[0];
this.selected.index = 0;
};
MythDropDown.prototype.sort = function() {
this.list.sort();
this.update(this.list);
};
MythDropDown.prototype.delete = function() {
this.list.splice(this.selected.index, 1);
this.update(this.list);
};
var list = ['test option 1', 'test option 2'];
var myList = new MythDropDown(list);
var main = {
view: function() {
return m('.content',
m('button', {
onclick: function() {
var L1 = ['Banana', 'Apple', 'Orange', 'Kiwi'];
myList.update(L1);
}
},
'Fruits'),
m('button', {
onclick: function() {
var L1 = ['Yellow', 'Black', 'Orange', 'Brown', 'Red'];
myList.update(L1);
}
},
'Colors'),
m('button', {
onclick: function() {
myList.sort();
}
},
'Sort'),
m('button', {
onclick: function() {
myList.delete();
}
},
'Remove Selected'),
m('', m.component(myList),
m('', 'Selected Item: ' + myList.selected.name, 'Selected Index: ' + myList.selected.index)
)
);
}
};
m.mount(document.body, main);
<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/0.2.3/mithril.min.js"></script>
So I'm trying to load JSON from a server via AJAX call. I'm able to map it fine into an array that is being binded in HTML. These objects are saved into an array that is being used as the value for a select tag. If I output to the console what's in the array, all the objects show up fine. But these objects don't show as if they have been preselected in the select box.
What I'm trying to do is have the previous data that a user saved from an old session and continue where they left off without having to redo everything again. So I'm loading all old data and putting it back where it used to be by preselecting the option for them.
Here is the JS I currently have:
function BracketsViewModel() {
self.AfcTeams = ko.observableArray([]);
// Normally pulled from server via AJAX with more teams. Hardcoded for simplicity
self.AfcTeams.push(new TeamModel({
Tricode: "CIN",
DisplayName: "Bengals"
}));
self.AfcTeams.push(new TeamModel({
Tricode: "BUF",
DisplayName: "Bills"
}));
self.AfcTeams.push(new TeamModel({
Tricode: "DEN",
DisplayName: "Broncos"
}));
self.AfcTeams.push(new TeamModel({
Tricode: "CLE",
DisplayName: "Browns"
}));
self.AfcTeams.push(new TeamModel({
Tricode: "SD",
DisplayName: "Chargers"
}));
// Temporary array that holds Team object
self.AfcSelectedWildCards = [];
for (var i = 0; i <= 5; i++) {
self.AfcSelectedWildCards.push(ko.observable());
}
// Holds selected teams that go to next round
self.AfcDivisionals = ko.computed(function () {
var tmp = [];
ko.utils.arrayForEach(self.AfcSelectedWildCards, function (team) {
if (team()) {
tmp.push(team());
}
});
return tmp;
});
// Other properties not shown for simplicity
// This will be loaded from server via AJAX call
var bracketsObject = {
AfcTeams: [{
Tri: "CIN",
Name: "Bengals",
Rank: "1"
}, {
Tri: "HOU",
Name: "Texans",
Rank: "2"
}, {
Tri: "NE",
Name: "Patriots",
Rank: "3"
}, {
Tri: "NYJ",
Name: "Jets",
Rank: "5"
}, {
Tri: "DEN",
Name: "Broncos",
Rank: "4"
}, {
Tri: "KC",
Name: "Chiefs",
Rank: "6"
}]
};
var afcteams = $.map(bracketsObject.AfcTeams, function (team) {
return new AltTeamModel(team);
});
// Saving objects to array that is being binded in HTML
for (var i = 0; i <= 5; i++) {
self.AfcSelectedWildCards[i] = ko.observable(afcteams[i]);
}
}
function TeamModel(data) {
if (data) {
this.Tri = data.Tricode;
this.Name = data.DisplayName;
} else {
this.Tri = "";
this.Name = "";
}
this.Rank = ko.observable(0);
}
function AltTeamModel(data) {
this.Tri = data.Tri;
this.Name = data.Name;
this.Rank = ko.observable(data.Rank);
}
ko.applyBindings(new BracketsViewModel());
Here is the Fiddle
I appreciate any help I can get.
The first issue is that you are referencing self, but never declaring it. You need to add var self = this; at the top of BracketsViewModel.
The next problem is that AfcTeams is an observable array of TeamModels, but AfcSelectedWildCards is an array of AltTeamModel. They need to be the same view model for options and value to match up.
One way around this is to set optionsValue and value both to 'Tri' as follows:
<select class="form-control"
data-bind="options: AfcTeams,
optionsText: 'Name',
optionsCaption: '-- Team --',
optionsValue: 'Tri',
value: AfcSelectedWildCards[0]().Tri"></select>
Here is a fiddle with these two fixes: http://jsfiddle.net/qpolarbear/8kkamzy7/
Only the Bengals and Broncos are selected because they are the only matching teams between AfcTeams and AfcSelectedWildCards.
So after taking a break from this project, I finally figured out how to bind the objects loaded via AJAX. The problem was that Knockout was binding the document before the AJAX calls were finished so, for whatever reason, the binding wasn't reflecting these changes. What I decided to do was time out the document from applying the binding and load everything from the server first. I then passed in all the objects into the View Model and apply the binding on a half second delay. Everything works great now. Here is the code:
function TeamModel(data, isPreData) {
if (isPreData) {
this.Tri = data.Tri;
this.Name = data.Name;
this.Rank = ko.observable(data.Rank);
} else {
if (data) {
this.Tri = data.Tricode;
this.Name = data.DisplayName;
} else {
this.Tri = "";
this.Name = "";
}
this.Rank = ko.observable(0);
}
}
var afcteams;
$.getJSON('/Brackets/GetBrackets', { id: someId}, function (bracketsObject) {
if (bracketsObject) {
afcteams = $.map(bracketsObject.AfcTeams, function (team) {
return new TeamModel(team, true);
});
}
}).fail(function () {
alert("There was an error getting data from the server.");
});
var teams;
$.getJSON('/Brackets/GetAFCTeams', function (data) {
teams = $.map(data, function (team) {
return new TeamModel(team, false);
});
});
function SetBindings(afcteams, teams) {
ko.applyBindings(new BracketsViewModel(afcteams, teams));
}
setTimeout(function() {
SetBindings(afcteams, teams);
}, 500);