Alright i have working code that removes a selected row(s) via a checkbox being checked. However i am running into the issue of enforcing that only one of the radio buttons can be checked at any given moment. My first approach is to tie a click event to the each radio button and if it gets clicked, it loops through the observable array and marks all "false." Then it simply flips the flag to true for the item that fired the event. I know this isn't the best way but my lack luster knowledge of knockout is forcing me down this path..even though this method doesn't work atm. Can anyone shed light on what i am doing wrong or how to properly wire this up?
The html for the table
<table class="accountGroups information" id="tblAccountGroups">
<tr>
<td width="125px;" style="font-weight: bold;">StandardAccountNo</td>
<td width="125px;" style="font-weight: bold; text-align: center;">Primary</td>
<td style="font-weight: bold;">Effective Date</td>
<td style="font-weight: bold;">End Date</td>
<td style="font-weight: bold;">Remove</td>
</tr>
<!-- ko foreach: NewAccountGroupDetails-->
<tr id="Model.NewAccountGroupDetails[0].AccountGroupName" class="acctgrp-row">
<td>
<div>
<input style="width: 100%;" data-bind="value: StandardAccountNo, attr: {name: 'NewAccountGroupDetails[' + $index() + '].StandardAccountNo'}" />
</div>
</td>
<td>
<div style="text-align:center;">
<input style="width:100%;" type="radio" data-bind="value: IsPrimary, attr: {name: 'NewAccountGroupDetails[' + $index() + '].IsPrimary'}, click: $parent.markIsPrimary" />
</div>
</td>
<td>
<div>
<input style="width:125px;" class="datepicker" data-bind="value: EffectiveDate, attr: {name: 'NewAccountGroupDetails[' + $index() + '].EffectiveDate'}" readonly="readonly" />
</div>
</td>
<td>
<div>
<input style="width:125px;" class="datepicker" data-bind="value: EndDate, attr: {name: 'NewAccountGroupDetails[' + $index() + '].EndDate'}" readonly="readonly" />
</div>
</td>
<td>
<div style="text-align:center;">
<input type="checkbox" data-bind="checked: markedForDeletion, attr: {name: 'NewAccountGroupDetails[' + $index() + '].MarkedForDeletion'}" />
</div>
</td>
</tr>
<!-- /ko -->
</table>
The JS below powers the page
////VIEW MODEL FOR KNOCKOUT////
var Detail = function () {
this.StandardAccountNo = ko.observable('');
this.IsPrimary = ko.observable(false);
this.EffectiveDate = ko.observable(formattedDate(new Date()));
this.EndDate = ko.observable(formattedDate(new Date()));
this.markedForDeletion = ko.observable(false);
};
var ViewModel = function () {
var rawList = '#Html.Raw(new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(Model.NewAccountGroupDetails))';
this.NewAccountGroupDetails = ko.observableArray(convertJSONToKoObservableObject($.parseJSON(rawList)));
this.NewAccountGroupDetails.push(new Detail());
this.deleteMarkedItems = function () {
this.NewAccountGroupDetails.remove(function (item) {
return item.markedForDeletion();
});
};
this.markIsPrimary = function () {
for (i = 0; this.NewAccountGroupDetails().length > 0; i++) {
this.NewAccountGroupDetails[i].IsPrimary(false);
}
return item.IsPrimary(true);
};
this.addNew = function () {
this.NewAccountGroupDetails.push(new Detail());
$('.datepicker').each(function (i, obj) {
$(obj).datepicker({ changeYear: true, changeMonth: true });
});
}
};
ko.applyBindings(new ViewModel());
function convertJSONToKoObservableObject(json) {
var ret = [];
$.each(json, function (i, obj) {
var newOBJ = {};
for (prop in obj) {
newOBJ[prop] = ko.observable(obj[prop]);
}
ret.push(newOBJ);
});
return ret;
}
Once i have the page working the way i want it to, i'll look into syntax improvements such as ko mapping library for the array.
In your view model, construct the remove button like this:
viewModel.remove = function (row) {
console.log(row);
viewModel.NewAccountGroupDetails.remove(row);
};
Now, the current context is passed as the first argument to any callback in knockout. Therefore, if you add a button with data-bind="click: $parent.remove", it will call the viewModel.remove function with the row context.
<tr ...>
...
<td>
<button data-bind="click: $parent.remove">Remove</button>
</td>
</tr>
I'd need some extra information, but let me show you an example, and give you a few advices:
First, the advices:
in order to convert your regular object in an object with observable properties an arrays you can use the Knockout Mapping plugin.
you can omit the step of parsing the JSON. You can simply assigng the JSON to a var, like this: var JSON=*your serialized JSON*; (Don't forget the semicolon at the end.
instead of including so many code in the data-bind, like this: NewAccountGroupDetails['+ $index() + '].EndDate, do this calculation on the viewmodel itself, an use a computed named, for example EndDateName
your viewmodel should include a selectedRow observable. When the user selects the row, put the row there, and you can use a computed observable that determines if a row is the selected row or not.
take into account that you can bind events that invoke functions in your code, and this events carry the data associated to the DOM object that originated the event. I.e. if the users clicks a row associated to a account group detail, you'll receive it in the event.
Example for 2:
// Instead of:
var viewModelJson = '[{"name": "Pepe"},{"name":"Juan"}]';
var viewModel = $.parseJSON(viewModelJson);
// Do this directly:
var people = [{"name": "Pepe"},{"name":"Juan"}];
As 4 and 5 are not clear at once, this is a simple sample of what you want to achieve.
<ul data-bind="foreach: people">
<li data-bind="text: name, click: $root.select,
css: {red: $data == $root.selectedPerson()}" >
</li>
</ul>
NOTE that the css class red is applied when the condition true. And the condition is that the value bound to the current row is the same as the value in the selectedPerson observable.
And this is the corresponding JavaScript (remember to reference knockout mapping!!)
var people = [{"name": "Pepe"},{"name":"Juan"}];
var PeopleModel = function(people) {
var self = this;
self.selectedPerson = ko.observable(); // This will hold the selected person
self.people = ko.mapping.fromJS(people); // Note ko.mapping!!
self.select = function(person) { // event receives the current data as 1st param
self.selectedPerson(person);
}
self.delete = function(person) {
// find de index of person and remove 1 item from that index
self.people.splice(self.people.indexOf(person),1);
}
return self;
};
var peopleModel = new PeopleModel(people);
ko.applyBindings(peopleModel);
You can run the jsfiddle here.
If you change the click binding to invoke $root.delete instead of $root.select, you'll see the person dissapear from the list when clicking it. Of course, you can add an extra element to do so.
NOTE: you can read the docs on click binding on knockout js site.
And a last advice: it's much better to use Web API, or a method returning a JsonResult to recover the data directly from the server, and keep the js on a separate file.
UPDATE
A little bit mode code.
You can add this HTML:
<input type="button" data-bind="click: removeSelected" value="removeSelected"/>
And this method in the view model:
self.removeSelected = function() {
if (self.selectedPerson()) {
self.delete(self.selectedPerson());
}
};
If you do so, when clicking the button, if there is a selected item, it will be removed from the list.
UPDATE: Another, more comple example
Here you have a more complete example, in this fiddle, that includes the code below:
CSS:
body {
font-family: Arial;
}
.container {
margin: 10px 0;
border: solid 1px #ABF;
}
.container > div {
padding: 4px;
border: solid 1px #ABF;
position: relative;
}
.selected {
border: solid 1px #00A;
color: #00A;
background-color: #BCF;
}
HTML:
<div data-bind="foreach: people" class="container">
<div data-bind="click: $root.select,
css: {selected: $data == $root.selectedPerson()}" >
<!-- ko text: name --><!-- /ko -->
<input type="button" value="Remove"
style="right:3px;top:2px; position:absolute;"
data-bind="click:$root.delete"/>
</div>
</div>
<div data-bind="visible: selectedPerson()" >
<input type="button" data-bind="click: removeSelected" value="Remove Selected"/>
<input type="button" data-bind="click: unSelect" value="Deselect"/>
</div>
<div data-bind="visible: selectedPerson()" class="container">
<div>
Selected: <!-- ko text: selectedPerson().name --><!-- /ko -->
</div>
</div>
JavaScript:
var people = [{"name": "Pepe"},{"name":"Juan"},{"name":"Luis"},{"name":"Adolfo"}];
var PeopleModel = function(people) {
var self = this;
self.selectedPerson = ko.observable(); // This will hold the selected person
self.people = ko.mapping.fromJS(people); // Note ko.mapping!!
self.select = function(person) { // The event receives the current data as parameter
self.selectedPerson(person);
};
self.delete = function(person) {
// find de index of person and remove (splice) it from the observable array
self.people.splice(self.people.indexOf(person),1);
self.selectedPerson(null);
}
self.removeSelected = function() {
if (self.selectedPerson()) {
self.delete(self.selectedPerson());
}
};
self.unSelect = function() {
self.selectedPerson(null);
}
return self;
};
var peopleModel = new PeopleModel(people);
ko.applyBindings(peopleModel);
Try to temporarily save the selected row when you select it
function AccountGroupViewModel() {
var viewModel = this;
viewModel.selectedRow = null;
// ...
viewModel.selectRow = function (data) {
// ...
viewModel.selectedRow = data;
}
viewModel.remove = function () {
// ...
if (viewModel.selectedRow != null) {
this.NewAccountGroupDetails.remove(viewModel.selectedRow);
}
}
}
Related
I'm trying to use the select 2 Multi-select boxes with Knockout JS.
The box displays a list of countries and the user can select multiple countries.
The box is displaying multiple countries correctly as expected, however the observable array is only showing the first entry.
My intention is to get all the selected countries and not the first one.
At first i thought i cant use a select 2 multi select with knockout, however if if i add two for example (MT,NL) the observable shows MT, however if i remove MT it updates to NL (so i dont think thats the issue)
Logic below:
// Class to represent a row in the dpos grid
function DpoItem(address, preferredLanguage, countries)
{
var self = this;
self.address = address;
self.preferredLanguage = preferredLanguage;
self.countries = ko.observableArray(countries);
}
// Class to represent a language
function LanguageItem(code, name)
{
var self = this;
self.code = code;
self.name = name;
}
// Class to represent a country
function CountryItem(code, name)
{
var self = this;
self.code = code;
self.name = name;
}
// Overall viewmodel for this screen, along with initial state
function DposViewModel()
{
var self = this;
// Populate countries
var countriesObject = JSON.parse(countriesJSON);
var countries = [];
for (var cKey in countriesObject)
{
countries.push(new CountryItem(cKey, countriesObject[cKey]));
}
self.countriesList = ko.observableArray(countries);
// Populate languages
var languagesObject = JSON.parse(languagesJSON);
var languages = [];
for (var lKey in languagesObject)
{
languages.push(new LanguageItem(lKey, languagesObject[lKey]));
}
self.languagesList = ko.observableArray(languages);
// parse JSON DTOs and put them in the viewmodel
var dposObject = JSON.parse('[{"countries":[],"type":"dpo","address":"dpo #avis.com","preferredLanguage":"en - GB"},{"countries":["GB", "MT"],"type":"dpo","address":"dpo #avis.co.uk","preferredLanguage":"en - GB"},{"countries":["MT"],"type":"dpo","address":"dpo #avis.com.mt","preferredLanguage":"mt - MT"}]');
var dpos = [];
dposObject.forEach(dpo =>
{
dpos.push(new DpoItem(dpo.address, dpo.preferredLanguage, dpo.countries));
});
self.dpos = ko.observableArray(dpos);
self.addDpo = function ()
{
self.dpos.push(new DpoItem("", "", ""));
};
self.removeDpo = function ()
{
self.dpos.remove(this);
};
self.checkDpos = function ()
{
for (i = 0; i < self.dpos().length; i++)
{
var dpo = self.dpos()[i];
var dpoCountries = dpo.countries();
}
};
}
ko.applyBindings(new DposViewModel());
$(document).ready(function ()
{
$('.js-example-basic-multiple').select2();
});
UI below:
<div id="table" class="table-editable">
<table class="table">
<thead>
<tr>
<th>Email</th>
<th>Preferred Language</th>
<th>Countries</th>
<th><span id="table-add" class="table-add glyphicon glyphicon-plus" data-bind="click: addDpo"></span></th>
</tr>
</thead>
<tbody data-bind="foreach: dpos">
<tr>
<td contenteditable="true" data-bind="text: $data.address"></td>
<td>
<select class="js-example-basic-single" data-bind="options: $parent.languagesList, optionsText: 'name', optionsValue: 'code', value: $data.preferredLanguage"></select>
</td>
<td>
<select class="js-example-basic-multiple" multiple="multiple" data-bind="options: $parent.countriesList, optionsText: 'name', optionsValue: 'code', value: $data.countries"></select>
</td>
<td>
<span class="table-remove glyphicon glyphicon-remove" data-bind="click: $parent.removeDpo"></span>
</td>
</tr>
</tbody>
</table>
</div>
<button data-bind="click: checkDpos">Click me</button>
<div>
<h1>Summary</h1>
<div data-bind="foreach: dpos">
<p>Address: <strong data-bind="text: address"></strong></p>
<p>Preferred Language: <strong data-bind="text: preferredLanguage"></strong></p>
<p>Countries: <strong data-bind="text: countries"></strong></p>
</div>
</div>
Any ideas why this is happening?
I have a table that has a checkbox. It has a select ALL javascript function and a select one by one function. My html file looks like this for the delete button:
<button data-toggle="modal" data-target="#rejectModal" contenteditable="false" id="delbutton" ng-model="delbutton" ng-disabled="countChecked() == 0">Delete</span></button>
Select All Checkbox:
<th class="">
<input type="checkbox" name="select" id="checkAll" ng-model="selectAllRawSCP"/>
</th>
table details:
<tr ng-repeat=" item in rawSCAP | orderBy:sort">
<td>
<input type="checkbox" name="select" value="checked" ng-model="item.checked"/>
</td>
I used the code I saw in one of the answers here in stack overflow to disable the delete button if there is no checkbox checked. It looks like this:
$scope.countChecked = function(){
var count = 0;
angular.forEach($scope.rawSCAP, function(value){
if (value.checked) count++;
});
return count;
}
But using this, my page returns an error which isTypeError: Cannot read property 'checked' of null
And also I need to enable delete button even if I used select all
The error is pretty clear, it cannot access the property checked of the rawSCAP itens. You have to guarantee rawSCAP itens checked property is not null before you try to use it in ng-model.
You could try to initialize it before with some value you want. Check this on how to do it.
There are some conditions you need to follow to use the <<ng-model>>.checked, which is a plain array can't be used for checkbox, it must be an array of objects. Also please change your function to below.
$scope.countChecked = function() {
var count = 0;
angular.forEach($scope.rawSCAP, function(value) {
value.checked = value.checked || 0;
if (value.checked) count++;
});
return count;
}
The main line which does the work is value.checked = value.checked || 0; Here if the value.checked is undefined, then the 0 will get assigned to value.checked, hence you won't get the error!
var app = angular.module('myApp', []);
app.controller('MyController', function MyController($scope) {
$scope.rawSCAP = [{
item: 1
}, {
item: 2
}, {
item: 3
}, {
item: 4
}, {
item: 5
}, {
item: 6
}, {
item: 7
}];
$scope.countChecked = function() {
var count = 0;
angular.forEach($scope.rawSCAP, function(value) {
value.checked = value.checked || 0;
if (value.checked) count++;
});
return count;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller='MyController' ng-app="myApp">
<button data-toggle="modal" data-target="#rejectModal" contenteditable="false" id="delbutton" ng-model="delbutton" ng-disabled="countChecked() == 0">Delete</button>
<table>
<th class="">
<input type="checkbox" name="select" id="checkAll" ng-model="selectAllRawSCP" />
</th>
<tr ng-repeat=" item in rawSCAP | orderBy:sort">
<td>
<input type="checkbox" name="select" value="checked" ng-model="item.checked" />{{item.item}}
</td>
</tr>
</table>
</div>
thank you for looking into this.
I have the following example built: http://jsfiddle.net/zm381qjx/5/
This is a menu list builder. When you add a menu, an edit form pops up. Using protectedObservable so that i can either commit or reset (as per code). One functionality, which i am having problems with, is there is radio button list (for TypeId), and depending on the value (10 = Url, 20 = Category, 30 = Page), you set the respective properties (10 = Url, 20 = CategoryId, 30 = PageId).
Flicking through the radio buttons, if Url is selected, another textbox should show (based on urlVisible) so user can enter the Url. I have added a span with text: TypeId.temp so i can see the temporary value. This is very irregular. Try to flick through several times.
Any help will be greatly appreciated.
My HTML
<a class="btn btn-primary" data-bind="click: addMenu">Add Menu</a>
<ul data-bind="foreach: Menus">
<li></li>
</ul>
<div class="panel panel-default" data-bind="slideIn: editMenuItem, with: editMenuItem">
<div class="panel-body">
<div class="form-group">
<label for="MenuName">Name: </label>
<input type="text" id="MenuName" data-bind="value: Name" class="form-control" />
</div>
<label class="radio-inline">
<input type="radio" name="MenuTypeId" value="10" data-bind="checked: TypeId" /> Url
</label>
<label class="radio-inline">
<input type="radio" name="MenuTypeId" value="20" data-bind="checked: TypeId" /> Category
</label>
<label class="radio-inline">
<input type="radio" name="MenuTypeId" value="30" data-bind="checked: TypeId" /> Page
</label>
<div class="form-group" data-bind="visible: urlVisible">
<label for="MenuUrl">Url: </label>
<input type="text" id="MenuUrl" data-bind="value: Url" class="form-control" />
</div>
<br />
<p>TypeId.temp = <span data-bind="text: TypeId.temp"></span></p>
<br /><br />
<input type="button" class="btn btn-success" value="Update" data-bind="click: commit" /> or
Cancel
</div>
</div>
My JS:
var vm = null;
//wrapper for an observable that protects value until committed
ko.protectedObservable = function (initialValue) {
//private variables
var _temp = ko.observable(initialValue);
var _actual = ko.observable(initialValue);
var result = ko.dependentObservable({
read: function () {
return _actual();
},
write: function (newValue) {
_temp(newValue);
}
});
//commit the temporary value to our observable, if it is different
result.commit = function () {
var temp = _temp();
if (temp !== _actual()) {
_actual(temp);
}
};
//notify subscribers to update their value with the original
result.reset = function () {
_actual.valueHasMutated();
_temp(_actual());
};
result.temp = _temp;
return result;
};
ko.bindingHandlers.slideIn = {
init: function (element) {
$(element).hide();
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
$(element).stop().hide().slideDown('fast');
} else {
$(element).stop().slideUp('fast');
}
}
};
var Menu = function (Id, Name, TypeId, CategoryId, PageId, Url) {
var self = this;
/* Core Properties */
self.Id = ko.observable(Id);
self.Name = ko.protectedObservable(Name);
self.TypeId = ko.protectedObservable(TypeId);
self.CategoryId = ko.protectedObservable(CategoryId);
self.PageId = ko.protectedObservable(PageId);
self.Url = ko.protectedObservable(Url);
/* Virtual Properties */
self.urlVisible = ko.computed(function () {
return self.TypeId.temp() == "10";
}, self);
/* Virtual Functions */
self.editMenu = function (data) {
if(vm.editMenuItem()) {
vm.editMenuItem(null);
}
vm.editMenuItem(data);
};
/* Core Functions */
self.commit = function () {
if (self.Name.temp() == '' || self.Name.temp() == null) {
alert('Please enter a name.'); return;
}
self.Name.commit();
self.TypeId.commit();
self.CategoryId.commit();
self.PageId.commit();
self.Url.commit();
vm.editMenuItem(null);
};
self.reset = function () {
self.Name.reset();
self.TypeId.reset();
self.CategoryId.reset();
self.PageId.reset();
self.Url.reset();
vm.editMenuItem(null);
};
};
var ViewModel = function() {
var self = this;
/* Core Properties */
self.Menus = ko.observableArray([]);
/* Virtual Properties */
self.editMenuItem = ko.observable(null);
self.addMenu = function(){
var menu = new Menu(0, "New Menu", "10", 0, 0, "");
self.Menus.push(menu);
self.editMenuItem(menu);
};
};
$(function () {
vm = new ViewModel();
ko.applyBindings(vm);
});
If you change your radio button binding to
<input type="radio" name="MenuTypeId" value="10" data-bind="checked: TypeId.temp" />
The temp id will be changed accordingly and radio button behaviour is consistent, but not with TypeId as value.
also the protectedObservable binding the radio button value is not playing nice
When you manually click the radio the TypeId value is never changed (as you are not committing the value) and I guess that as the radio button value never changes from 10 , it is not recognizing the subsequent manual clicks on Url radio button.
I updated the value using a button and it is changing accordingly; but then it will not move the value from that TypeId on subsequent radio button clicks
And the problem is still appearing for protectedObservable binding but not with a simple observable.
Code which explores this idea further: http://jsfiddle.net/zm381qjx/101/
When I try to access the values of the newly created elements, I get the function body as the variable values.
In the Fiddle,
Click "Add Name"
Enter First Name as "Kate" in the newly added row.
Click "Show All Names" button.
In the popup, function body gets displayed instead of "Kate". I am able to use the first element using name.FirstName and the rest of the elements using name.FirstName().
Is there any consistent way to get the value of FirstNamein this scenario (across the loop iterations)?
My HTML code is:
<table>
<tbody data-bind="foreach: names">
<tr>
<td>
<label>First Name:</label>
</td>
<td>
<input data-bind="value: FirstName" type="text">
</td>
<td>
<label>Last Name:</label>
</td>
<td>
<input data-bind="value: LastName" type="text">
</td>
</tr>
</tbody>
</table>
<button data-bind="click: addName">Add Name</button>
<button data-bind="click: showAllNames">Show All Names</button>
Javascript Code:
var namesArray = [{
"FirstName": "Tom",
"LastName": "Langdon"
}];
var ViewModel = function () {
var self = this;
self.names = ko.observableArray(namesArray);
self.CreateBlankName = function () {
return {
FirstName: ko.observable(""),
LastName: ko.observable("")
};
};
self.addName = function () {
names.push(CreateBlankName());
};
self.showAllNames = function () {
var namestring = "";
ko.utils.arrayForEach(self.names(), function (name) {
namestring += name.FirstName + "\n";
});
alert(namestring);
};
};
ko.applyBindings(ViewModel);
The fisrtname and lastName fields should be observables because you can changed through the UI.
So you need to turn these into ko.observables as follow :
var namesArray = [{
"FirstName": ko.observable("Tom"),
"LastName": ko.observable("Langdon")
}];
Now items will only have observables. That's why you need to change the showAllNames function :
ko.utils.arrayForEach(self.names(), function (name) {
namestring += name.FirstName() + "\n";
});
See fiddle
You can also automatically convert raw js object into observables object by using ko.mapping.fromJS function
Looking for a good example of how to set up child models in knockoutjs. This includes binding to child events such as property updates which I haven't been able to get working yet.
Also, it would be better to bind to a single child in this case instead of an array but I don't know how to set it up in the html without the foreach template.
http://jsfiddle.net/mathewvance/mfYNq/
Thanks.
<div class="editor-row">
<label>Price</label>
<input name="Price" data-bind="value: price"/>
</div>
<div class="editor-row">
<label>Child</label>
<div data-bind="foreach: childObjects">
<div><input type="checkbox" data-bind="checked: yearRound" /> Year Round</div>
<div><input type="checkbox" data-bind="checked: fromNow" /> From Now</div>
<div>
<input data-bind="value: startDate" class="date-picker"/> to
<input data-bind="value: endDate" class="date-picker"/>
</div>
</div>
</div>
var ChildModel= function (yearRound, fromNow, startDate, endDate) {
var self = this;
this.yearRound = ko.observable(yearRound);
this.fromNow = ko.observable(fromNow);
this.startDate = ko.observable(startDate);
this.endDate = ko.observable(endDate);
this.yearRound.subscribe = function (val) {
alert('message from child model property subscribe\n\nwhy does this only happen once?');
//if(val){
// self.startDate('undefined');
// self.endDate('undefined');
//}
};
}
var ParentModel = function () {
var self = this;
this.price = ko.observable(1.99);
this.childObjects = ko.observableArray([ new ChildModel(true, false) ]);
};
var viewModel = new ParentModel ();
ko.applyBindings(viewModel);
Try it with the following:
this.yearRound.subscribe(function (val) {
alert('value change');
});
If you want to have the subscriber also being called while loading the page do something like this:
var ChildModel= function (yearRound, fromNow, startDate, endDate) {
var self = this;
this.yearRound = ko.observable();
this.fromNow = ko.observable(fromNow);
this.startDate = ko.observable(startDate);
this.endDate = ko.observable(endDate);
this.yearRound.subscribe(function (val) {
alert('value change');
});
this.yearRound(yearRound);
}
http://jsfiddle.net/azQxx/1/ - this works for me with Chrome 16 and Firefox 10
Every time the checked button changes its value the callback fires.
The observableArray is fine in my opinion if you may have more than one child model associated to the parent.