The situation that I'm trying to resolve is how to map a child array of json data to my view model. The business logic is that for a given position, there is a collection of projects, with each project having a collection of experiences. If I use the knockout mapper function, I'm able to loop through and populate my experience collection, but my viewModel has computed functions, subscribed events, etc and I'm not sure how to wire up my incoming inner array to that viewModel.
The models (in an abbreviated format)
// for creating project objects
my.Project = function (selectedProject) {
var self = this;
self.project = ko.observable();
self.company = ko.observable();
self.experiences = ko.observableArray([]);
self.hourlyRate = ko.observable();
self.hours = ko.observable();
self.hoursPerWeek = ko.observable();
self.hoursTypeId = ko.observable();
self.id = ko.observable();
self.maxDate = ko.observable();
self.minDate = ko.observable();
self.minDescriptionLength = ko.observable();
self.memberId = ko.observable();
self.name = ko.observable();
self.startDate = ko.observable();
// non-persistable properties
self.chartVals = ko.observableArray([]);
self.isSelected = ko.computed(function () {
if (typeof selectedProject === 'undefined') {
return false;
} else {
return selectedProject === self;
}
});
self.newExperience = ko.observable(new my.Experience());
self.selectProject = function (p) {
console.log(p.name());
$("#selectedProjectName").text(p.name());
};
self.enableDetails = function () {
my.vm.proficencyTip = ko.observable();
console.log("enableDetails pow");
return true;
},
self.disableDetails = function () {
console.log('disableDetails mouse-off');
return false;
};
self.isSelectedProficiency = ko.observable(false);
self.selectedProficiency = function (p) {
console.log('Proficiency value is ' + p.name);
};
self.updateProject = function () {
my.postProjectData(self);
};
self.hoursTypeId.subscribe(function () {
var endDate = new Date();
var startDate = new Date();
if (self.endDate() != "") {
endDate = $.datepicker.parseDate("mm/dd/yy", self.endDate());
}
if (self.startDate() != undefined) {
startDate = $.datepicker.parseDate("mm/dd/yy", self.startDate());
}
var days = (endDate - startDate) / 1000 / 60 / 60 / 24;
var weeks = days / 7;
if (self.hoursTypeId() == 2) {
if (self.hoursPerWeek() > 0) {
self.totalHours((weeks * self.hoursPerWeek()).toFixed(0));
} else {
}
}
if (self.hoursTypeId() == 1) {
if (self.totalHours() > 0) {
self.hoursPerWeek((self.totalHours() / weeks).toFixed(0));
}
}
});
};
// for creating Position Models
my.Experience = function (selectedExperience) {
var self = this;
self.id = ko.observable();
self.projectId = ko.observable();
self.positionId = ko.observable();
self.memberId = ko.observable();
self.frequencyId = ko.observable();
self.skillName = ko.observable();
self.skillId = ko.observable();
self.proficiencyId = ko.observable();
self.frequencyId = ko.observable();
self.proficiency = ko.observable();
self.frequency = ko.observable();
self.description = ko.observable();
self.skill = {
name:ko.observable()
};
self.proficiency.subscribe(function () {
self.proficiencyId = self.proficiency();
console.log('proficiency subscribed: ' + self.proficiency());
my.setCounterHint(self.frequency(), self.proficiency(), self.description());
var tip = "Don't just list those skills your strongest in. It's just as important to add new skills you are aquiring!";
var result = $.grep(my.ajaxData.member.Proficiencies, function (e) { return e.Id == self.proficiency(); });
if (result.length == 0) {
// not found
} else if (result.length == 1) {
// access the foo property using result[0].foo
tip = result[0].Name + ':\nAutonomy: ' + result[0].Autonomy + '\nContext: ' + result[0].nContext + '\nKnowledge: ' + result[0].Knowledge + '\nWorkmanship: ' + result[0].Workmanship;
} else {
// multiple items found
}
$(".proficiencyTip").attr('title', tip).attr('alt', tip);
$(".proficiencyQuestionMark").fadeIn('slow');
});
self.frequency.subscribe(function () {
self.frequencyId = self.frequency();
console.log('frequency subscribed: ' + self.frequency());
self.frequencyId = self.frequency();
$("#newExperienceFrequency").val(self.frequencyId);
my.setCounterHint(self.frequency(), self.proficiency(), self.description());
var tip;
$(".frequencyTip").attr('title', tip).attr('alt', tip);
$(".frequencyQuestionMark").fadeIn('slow');
});
self.minDesc = ko.observable(my.getMinDescriptionLen(self.frequency(), self.proficiency()));
self.mouseoverDescription = function () {
$(".tip").hide();
$(".descriptionQuestionMark").fadeIn('slow');
},
self.mouseoffDescription = function () {
$(".descriptionQuestionMark").delay(3000).fadeOut("slow");
};
});
};
The function where I'm loading the data.
loadProjectsForPosition = function (position) {
// if we have no projects, add one
if (position.projects.length == 0) {
position.projects.push(new my.Project()
.company(position.company())
.companyName(position.companyName())
.endDate(position.endDate())
.experiences([])
.hourlyRate(position.hourlyRate())
.hours(position.hours())
.maxDate(position.maxDate())
.minDate(position.minDate())
.memberId(position.memberId())
.description("Summarize the project and its objectives here.")
.name('New')
.positionId(position.positionId())
.startDate(position.startDate())
.totalHours(position.totalHours())
);
$.each(my.ajaxData.member.Projects, function (i, p) {
if (p.PositionId == position.id()) {
position.projects.push(new my.Project(position.projects[0])
.chartVals(p.ChartVals)
.company(p.Company)
.companyName(p.CompanyName)
.creditMinutes(p.CreditMinutes)
.description(p.Description)
.endDate(p.EndDate)
**.experiences(p.Experiences)**
.hourlyRate(p.HourlyRate)
.hours(p.Hours)
.hoursPerWeek(p.HoursPerWeek)
.hoursTypeId(p.HoursTypeId)
.id(p.Id)
.maxDate(p.MaxDate)
.minDate(p.MinDate)
.minDescriptionLength(p.MinDescriptionLength)
.memberId(p.MemberId)
.name(p.Name)
.positionId(p.PositionId)
.startDate(p.StartDate)
.totalCompensation(p.TotalCompensation)
.totalHours(p.TotalHours)
.weightedHours(p.WeightedHours)
.isSelectedProficiency(false)
);
position.selectedPosition = true;
}
});
};
}
The grandchild has children and grandchildren as well, so as much as I'm searching for an answser to this issue, I'm looking for what the pattern is in knockout mapping for dealing with 'turtles all the way down'.
As always, thanks for taking a look, sharing your experience and offering your well reasoned opinion.