Knockout JS: Creating dynamic table columns - javascript

Using Knockout JS:
I have a requirement as.
I have a table with 2 static columns where each has a text-box. I also have a add row button outside the table and one remove row button along each row.
When the user clicks add row it adds a row to the table and I can see two columns with textbox in each. User can add more rows if required by clicking add row and click remove row to remove rows.
This all has been setup and works fine as :
https://jsfiddle.net/aman1981/xmd1xobm/14/
My issue is there is also an Get Columns button. When the user clicks this button I fetch a list of columns and want to add these columns with headers b/w the already existing columns of the table.Each of these columns also need to have a textbox as well.
For ex I would the list of columns as:
var columnsList = 'name, address, zip';
I am not sure how to add columns dynamically. Would appreciate inputs.
Here is the setup of my table:
<table class="table table-bordered">
<thead class="mbhead">
<tr class="mbrow">
<th>SIID</th>
<th>Comment</th>
</tr>
</thead>
<tbody data-bind="foreach: data">
<tr>
<td>
<input type="text" data-bind="text: SIID" class="form-control textbox" />
</td>
<td>
<input type="text" data-bind="text: Comment" class="form-control textbox" />
</td>
<td>
<input type="button" value="Remove Row" data-bind="click: $parent.removeRow" class="btn btn-danger" />
</td>
</tr>
</tbody>
<div class="col-xs-12 col-sm-6">
<input type="button" value="Add Row" class="btn btn-primary" data-bind="click: addRow" />
<input type="button" value="Get Columns" class="btn btn-primary" data-bind="click: addColumns" />
</div>
Anyone?

Nice for you i have a dynamic table component from my previous project:
var observable = ko.observable;
var pureComputed = ko.pureComputed;
var observableArray = ko.observableArray;
var unwrap = ko.unwrap;
var data = observableArray([{
a: 123,
b: 234,
c: 345
},{
a: 1231,
b: 2341,
c: 3451
},{
a: 1232,
b: 2342,
c: 3425
},{
a: 1233,
b: 2343,
c: 3453
}]);
var columns = observableArray([{
field: "a",
displayName: "A"
},{
field: "b",
displayName: "B (i can even change the title)"
}])
function viewModel(params) {
var paramColumns = params.columns;
var paramData = params.data;
var paramFieldKey = params.fieldKey || "field";
var paramDisplayNameKey = params.displayNameKey || "displayName";
var koColumnHeaders = pureComputed(function () {
var columns = paramColumns();
var columnHeaders = [];
var fieldKey = unwrap(paramFieldKey);
var displayNameKey = unwrap(paramDisplayNameKey);
for (var i in columns) {
var column = columns[i];
columnHeaders.push({
field: column[fieldKey],
displayName: column[displayNameKey]
})
}
return columnHeaders;
})
var koRows = pureComputed(function () {
var data = paramData();
var columns = paramColumns();
var fieldKey = unwrap(paramFieldKey);
var rows = [];
for (var i in data) {
var datum = data[i];
var cells = []
var row = {
entity: data,
cells: cells
};
for (var j in columns) {
var column = columns[j];
cells.push(datum[column[fieldKey]] || "");
}
rows.push(row);
}
return rows;
});
return {
rows: koRows,
columns: koColumnHeaders,
}
}
ko.applyBindings(new viewModel({
data: data,
columns: columns
}))
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table class="table table-bordered" border="1">
<thead>
<tr data-bind="foreach: columns">
<th data-bind="text: displayName">
</th>
</tr>
</thead>
<tbody data-bind="foreach: rows">
<tr>
<!-- ko foreach: cells -->
<td class="" data-bind="text: $data"></td>
<!-- /ko -->
</tr>
</tbody>
</table>

I was able to resolve this using the post at:
Knockout JS: Get Textbox data from Table under for-each binding
Here is my code:
(function() {
var ViewModel = function() {
var self = this;
self.valuesData = ko.observableArray();
self.columns = ko.computed(function() {
if (self.valuesData().length === 0)
return [];
return ValuesData.columns;
});
self.addRow = function() {
self.valuesData.push(new ValuesData());
};
self.Save = function() {
alert('Data:')
};
self.removeRow = function(data) {
self.valuesData.remove(data);
};
}
// Dynamic values.
var ValuesData = function(siid, comment) {
var self = this;
// Add observables dynamically for all relevant columns.
for (var i = 0; i < ValuesData.columns.length; i++) {
var column = ValuesData.columns[i];
self[column.Property] = ko.observable(column.InitialValue)
}
};
// Basic column definition.
ValuesData.columns = [{
Caption: 'SIID',
Property: 'SIID',
InitialValue: undefined
},
{
Caption: 'Column 1',
Property: 'Col1',
InitialValue: undefined
},
{
Caption: 'Column 2',
Property: 'Col2',
InitialValue: 'banana'
},
{
Caption: 'Comment',
Property: 'Comment',
InitialValue: undefined
}
]
vm = new ViewModel()
ko.applyBindings(vm);
// add initial row.
vm.addRow();
})();
Here is the html part:
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-
min.js"></script>
<br><br>
<table>
<thead>
<!-- NEW: use ko foreach with 'as' to have an alias for accessing $data. -->
<tr data-bind="foreach: { data: columns, as: 'column'}">
<th> <span data-bind="text: column.Caption"></span>
</th>
</tr>
</thead>
<tbody data-bind="foreach: { data: valuesData, as: 'rowData'}">
<tr data-bind="foreach: { data: $parent.columns, as: 'column' }">
<!-- NEW: bind to the corresponding property/observable in ValuesData -->
<td><input type="text" class="form-control textbox" data-bind="textInput:
rowData[column.Property]" /> </td>
</tr>
<tr>
<td>
<input type="button" value="Remove Row" data-bind="click:
$parent.removeRow" class="btn btn-danger" />
</td>
</tr>
</tbody>

Related

Bind different property than what is shown in the html component

Consider I have a list of objects like -
var configs = [{
id: "1",
name: "config1"
},
{
id: "2",
name: "config2"
}
];
I use the following to search through the config list and bind the selected config to another variable called changed_config.
<table style="width:900px;">
<tr>
<th style="width:500px; margin:50px">Config Name</th>
<th style="width:150px;">Config Value</th>
</tr>
<tr ng-repeat="changed_config in changed_configs">
<td>
<input type="text" class="form-control" ng-model="changed_config.id" list="names">
<datalist id="names">
<option ng-repeat="option in configs | filter:search | limitTo:30" ng-value="option.name"> {{option.name}}
</option>
</datalist>
<td>
<input type="text" class="form-control" ng-model="changed_config.value">
</td>
</tr>
</table>
<div class="row">
<button type="button" id="add-reward-grp-btn" ng-click="addConfig()"
class="btn btn-primary">Add Config
</button>
</div>
Controller Code(Not complete code, just relevant snippets):
var app = angular.module("drs_scheduler_app", []);
app.controller("CreateSchedulerJob", function ($scope, $http) {
$scope.changed_configs = [];
$scope.configs = [{
id: "1",
name: "config1"
},
{
id: "2",
name: "config2"
}
];
$scope.addConfig = function () {
var config = {
"id": "",
"value": ""
};
$scope.changed_configs.push(config);
console.log(config);
console.log(JSON.stringify($scope.changed_configs));
}
}
Currently the code displays and binds the name of the selected config to the changed_config variable. But I need the id to be bound to the changed_config variable and the name to be displayed in the html.
If I change the <option> to use id, then id will be displayed.
How do I bind one property to a variable but show another in the html??
Here is the solution you needed,
Procedure:
When an option is selected from datalist I m checking for that
change
That change is observed through input on which datalist is added
On that input change i,e when option is selected I m assigning that
id to id key of respective changed_config
That is inturn displayed in second textbox
It works for dynamic
// Code goes here
function cntryController($scope) {
$scope.LoadSessionData=function(val)
{
console.log(val);
};
$scope.changed_configs = [];
$scope.configs = [{
id: "1",
name: "config1"
},
{
id: "2",
name: "config2"
}
];
$scope.addConfig = function () {
var config = {
"id": "",
"value": ""
};
$scope.changed_configs.push(config);
console.log(config);
console.log(JSON.stringify($scope.changed_configs));
}
$scope.test = function(data, index){
console.log(data)
var newArray = $scope.configs.filter(function (config) {
return config.name == data;
});
console.log(newArray)
if(newArray.length){
var new_changed_config = $scope.changed_configs[index];
new_changed_config.id = newArray[0].id;
}
}
}
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<div ng-app="" ng-controller="cntryController">
<table cellspacing="10" cellpadding="10">
<tr>
<th>Config Name</th>
<th>Config Value</th>
</tr>
<tr ng-repeat="changed_config in changed_configs">
<td>
<input type="text" class="form-control" ng-model="changed_config.name" list="names" ng-change="test(changed_config.name, $index)">
<datalist id="names">
<option ng-repeat="option in configs | filter:search | limitTo:30" value={{option.name}}>{{option.name}}
</option>
</datalist>
</td>
<td>
<input type="text" class="form-control" ng-model="changed_config.id"/>
</td>
</tr>
</table>
<div class="row">
<button type="button" id="add-reward-grp-btn" ng-click="addConfig()" class="btn btn-primary">Add Config</button>
</div>
</div>
</html>
Please run the above snippet
Here is a working DEMO

View data binding in nested dynamic observableArray

I need to put observableArray with dynamic size inside observableArray and bind data to View.
View is render values from newData, but not update it in newData, when i edit it in View.
ko.applyBindings(new (function () {
var self = this;
self.lengthNewData = ko.observable(2);
self.newData = ko.observableArray();
self.AddDataStrings = function () {
let newString = ko.observableArray();
for(let i0 = 0; i0 < self.lengthNewData(); i0++)
newString.push(i0);
self.newData.push(newString);
}
self.SendData = function () {
alert(ko.toJSON(self.newData));
}
})());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<span data-bind="visible: !newData().length">Number of rows: <input data-bind="value: lengthNewData"/></span>
<input type="button" value="Add" data-bind="click: $root.AddDataStrings"/>
<input type="button" value="Send" data-bind="visible: newData().length, click: $root.SendData"/><br>
<table>
<tbody data-bind="foreach: newData">
<tr data-bind="foreach: $data">
<td><input data-bind="value: $data"/></td>
</tr>
</tbody>
</table>
https://jsfiddle.net/tvxyyzkp/6/
Push Add, edit it and push Send. Data was not modified!
Why and What need to change?
There are two immediate mistakes in your code.
You do not push observables into your newString. You should do:
newString.push(ko.observable(i0));
You bind your inputs to $data. $data contains the unwrapped data value, i.e. it's not observable. Bind to $rawData in such a setup.
This works:
ko.applyBindings(new(function() {
var self = this;
self.lengthNewData = ko.observable(2);
self.newData = ko.observableArray();
self.AddDataStrings = function() {
let newString = ko.observableArray();
for (let i0 = 0; i0 < self.lengthNewData(); i0++)
newString.push(ko.observable(i0));
self.newData.push(newString);
}
self.SendData = function() {
alert(ko.toJSON(self.newData));
}
})());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<span data-bind="visible: !newData().length">Number of rows: <input data-bind="value: lengthNewData"/></span>
<input type="button" value="Add" data-bind="click: AddDataStrings" />
<input type="button" value="Send" data-bind="visible: newData().length, click: SendData" /><br>
<table>
<tbody data-bind="foreach: newData">
<tr data-bind="foreach: $data">
<td><input data-bind="value: $rawData" /></td>
</tr>
</tbody>
</table>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

Can't call controller function from view

I want to call the controller function from view.
This my view:
<body>
<div ng-app="appTable">
<div ng-controller="Allocation">
<button ng-click="add()"> Add </button>
<button ng-click="remove()">remove</button>
<table>
<th>
<td>Date</td>
<td>Project</td>
<td>Release</td>
<td>Feature</td>
<td>Module name</td>
<td>Hours spent</td>
<td>Comment</td>
</th>
<tr ng-repeat="data in dataList">
<td><input type="checkbox" ng-model="data.isDelete"/></td>
<td>
<input type="text"
datepicker
ng-model="data.date" />
</td>
<td><input type="text" ng-model="data.dept"/></td>
<td>
<select ng-model="data.release" ng-options="x for x in range">
</select>
</td>
<td>
<select ng-model="data.feature" ng-options="x for x in feature">
</select>
</td>
<td>
<input type = "text" ng-model = "data.modulename">
</td>
<td>
<select ng-model="data.hours" ng-options="x for x in hours">
</select>
</td>
<td>
<input type = "text" ng-model = "data.comment">
</td>
</tr>
</table>
<button ng-click="Submit()">Submit</button>
<table>
<tr ng-repeat="data in displayList">
<div ng-controller="Allocation as vm">
<div>{{vm.postdata(data.date)}}</div>
</div>
<p>Output Message : {{msg}}</p>
<p>StatusCode: {{statusval}}</p>
<p>Status: {{statustext}} </p>
<p>Response Headers: {{headers}} </p>
<td>
<p>{{data.date}}</p>
</td>
<td>
<p>{{data.dept}}</p>
</td>
<td>
<p>{{data.release}}</p>
</td>
<td>
<p>{{data.feature}} </p>
</td>
<td>
<p>{{data.modulename}}</p>
</td>
<td>
<p>{{data.hours}}</p>
</td>
<td>
<p>{{data.comment}}</p>
</td>
</td>
</tr>
</table>
</div>
</div>
</body>
This is script.
<script>
var app = angular.module("appTable", []);
app.controller("Allocation", function($scope) {
$scope.hours = ["1", "2", "3"];
$scope.range = ["1", "2", "3"];
$scope.feature = ["UT", "FSDS", "Coding/Devlopment", "QA"];
$scope.dataList = [{
date: '17/07/2016',
dept: 'OneCell',
release: '1',
feature: "UT",
modulename: "Redundancy",
hours: "1",
comment: "Add extra information"
}];
$scope.add = function() {
var data = {};
var size = $scope.dataList.length;
if (!size) {
$scope.dataList = [{
date: '17/07/2016',
dept: 'OneCell',
release: '1',
feature: "UT",
modulename: "Redundancy",
hours: "1",
comment: "Add extra information"
}];
} else {
size = size - 1;
data.date = $scope.dataList[size].date;
data.dept = $scope.dataList[size].dept;
data.release = $scope.dataList[size].release;
data.feature = $scope.dataList[size].feature;
data.modulename = $scope.dataList[size].modulename;
data.hours = $scope.dataList[size].hours;
data.comment = $scope.dataList[size].comment;
$scope.dataList.push(data);
}
};
$scope.Submit = function() {
$scope.test = "Submit is pressed...";
$scope.displayList = [];
angular.forEach($scope.dataList, function(v) {
if (!v.isDelete) {
$scope.displayList.push(v);
}
});
$scope.dataList.splice(0);
};
$scope.remove = function() {
var newDataList = [];
angular.forEach($scope.dataList, function(v) {
if (!v.isDelete) {
newDataList.push(v);
}
});
$scope.dataList = newDataList;
};
$scope.postdata = function(date) {
var data = {
date: date,
};
$http.post('/add_status/', data).then(function(response) {
if (response.data)
$scope.msg = "Post Data Submitted Successfully!";
}, function(response) {
$scope.msg = "Service not Exists";
$scope.statusval = response.status;
$scope.statustext = response.statusText;
$scope.headers = response.headers();
});
};
});
app.directive("datepicker", function() {
function link(scope, element, attrs, controller) {
// CALL THE "datepicker()" METHOD USING THE "element" OBJECT.
element.datepicker({
onSelect: function(dt) {
scope.$apply(function() {
// UPDATE THE VIEW VALUE WITH THE SELECTED DATE.
controller.$setViewValue(dt);
});
},
dateFormat: "dd/mm/yy" // SET THE FORMAT.
});
}
return {
require: 'ngModel',
link: link
};
});
</script>
As you can see in the view I have called the postdata function of the controller.This function internally uses msg variable.But view is not printing value of this variable.I am very new to Aj. Please help.
You need to make following changes:
Remove ng-controller="Allocation as vm", as you already defined controller above., and you not using this syntax in controller.
after you removed as vm, then no need to vm. calls.
you need to inject $http in your controller., to make API call
See this fiddle demo, I logged dummy text in console for each `submit click.
and to make single call on submit, See this Fiddle
You should return a value in the function or add a scope variable to be shown in the controller's div, because right now you are not showing anything.
<div ng-controller="Allocation as vm">
<div>{{vm.postdata(data.date)}}</div>
<p>Output Message : {{msg}}</p>
<p>StatusCode: {{statusval}}</p>
<p>Status: {{statustext}} </p>
<p>Response Headers: {{headers}} </p>
</div>
Anyway, you should not run procedural function in this way. Bind it to an event, ng-click or something.
And do not create other than td or th child elements in tr.
This is the proper html table structure:
<table>
<tr>
<th>Month</th>
<th>Savings</th>
</tr>
<tr>
<td>January</td>
<td>$100</td>
</tr>
</table>

KnockoutJS Data disappears when I add a new item to array

View the issue on jsfiddle: http://jsfiddle.net/6bFsY/3/
When you click "Add Users" and then click "Add Users" again all of the data in the extensions drop down field disappears. This happened after I added an email column.
The email field gets pre-populated with whatever is selected in the extension dropdown (email is part of its object).
Also, the extensions drop down is unique per line, part of the script tells it to remove it from the array if it exists on a previous line.
JS
window.usrViewModel = new function () {
var self = this;
window.viewModel = self;
self.list = ko.observableArray();
self.pageSize = ko.observable(10);
self.pageIndex = ko.observable(0);
self.selectedItem = ko.observable();
self.extData = ko.observableArray();
self.validAccess = [{
'name': 'No Access',
'id': 'none'
}, {
'name': 'System Settings',
'id': 'pbx'
}, {
'name': 'Accounting',
'id': 'billing'
}, {
'name': 'Full Administrator',
'id': 'full'
}];
self.availableExtData = ko.computed(function () {
var inUse = [];
if (!self.selectedItem()) return inUse;
ko.utils.arrayForEach(self.list(), function (item) {
if (inUse.indexOf(item.usrExtVal().extension) == -1 && self.selectedItem() != item) inUse.push(item.usrExtVal().extension);
self.selectedItem().usrEmail(self.selectedItem().usrExtVal().email);
});
return ko.utils.arrayFilter(self.extData(), function (item) {
return inUse.indexOf(item.extension) == -1;
});
});
self.edit = function (item) {
if (self.selectedItem()) self.save();
self.selectedItem(item);
};
self.cancel = function () {
self.selectedItem(null);
};
self.add = function () {
if (self.selectedItem()) self.save();
var newItem = new Users();
self.selectedItem(newItem);
self.list.push(newItem);
self.moveToPage(self.maxPageIndex());
};
self.remove = function (item) {
if (confirm('Are you sure you wish to delete this item?')) {
self.list.remove(item);
if (self.pageIndex() > self.maxPageIndex()) {
self.moveToPage(self.maxPageIndex());
}
}
$('.error').hide();
};
self.save = function () {
self.selectedItem(null);
};
self.templateToUse = function (item) {
return self.selectedItem() === item ? 'editUsrs' : 'usrItems';
};
self.pagedList = ko.dependentObservable(function () {
var size = self.pageSize();
var start = self.pageIndex() * size;
return self.list.slice(start, start + size);
});
self.maxPageIndex = ko.dependentObservable(function () {
return Math.ceil(self.list().length / self.pageSize()) - 1;
});
self.previousPage = function () {
if (self.pageIndex() > 0) {
self.pageIndex(self.pageIndex() - 1);
}
};
self.nextPage = function () {
if (self.pageIndex() < self.maxPageIndex()) {
self.pageIndex(self.pageIndex() + 1);
}
};
self.allPages = ko.dependentObservable(function () {
var pages = [];
for (i = 0; i <= self.maxPageIndex(); i++) {
pages.push({
pageNumber: (i + 1)
});
}
return pages;
});
self.moveToPage = function (index) {
self.pageIndex(index);
};
};
ko.applyBindings(usrViewModel, document.getElementById('usrForm'));
function Users(fname, lname, email, phone, access, usrExtVal, usrEmail) {
this.fname = ko.observable(fname);
this.lname = ko.observable(lname);
this.email = ko.observable(email);
this.phone = ko.observable(phone);
this.access = ko.observable(access);
this.usrExtVal = ko.observable(usrExtVal);
this.usrEmail = ko.observable(usrEmail);
}
var ajaxResultExt = [{
'extension': '123',
'name': 'Stephen',
'email': 'test#test.com'
}, {
'extension': '123',
'name': 'Stephen',
'email': 'stephen#test.com'
}];
usrViewModel.extData(ajaxResultExt);
HTML
<fieldset title="Users">
<legend>2</legend>
<div>
<div class="cbp-content">
<form id="usrForm">
<h2>Users</h2>
<table class="table table-striped table-bordered" data-bind='visible: pagedList().length > 0'>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Phone Number</th>
<th>Access</th>
<th>Extension</th>
<th>Email</th>
<th style="width: 100px; text-align:right;" />
</tr>
</thead>
<tbody data-bind=" template:{name:templateToUse, foreach: pagedList }"></tbody>
</table>
<!-- ko if: 2 > pagedList().length -->
<p class="pull-right"><a class="btn btn-primary" data-bind="click: $root.add" href="#" title="edit"><i class="icon-plus"></i> Add Users</a></p>
<!-- /ko -->
<div class="supOneUsr" style="display:none;"><i class="icon-warning-sign"></i> <span style="color:red;">Please supply at least 1 User with Administrator Rights</span></div>
<div class="pagination pull-left" data-bind='visible: pagedList().length > 0'>
<ul><li data-bind="css: { disabled: pageIndex() === 0 }">Previous</li></ul>
<ul data-bind="foreach: allPages">
<li data-bind="css: { active: $data.pageNumber === ($root.pageIndex() + 1) }"></li>
</ul>
<ul><li data-bind="css: { disabled: pageIndex() === maxPageIndex() }">Next</li></ul>
</div>
<br clear="all" />
<script id="usrItems" type="text/html">
<tr>
<td data-bind="text: fname"></td>
<td data-bind="text: lname"></td>
<td data-bind="text: phone"></td>
<td data-bind="text: access.asObject && access.asObject() && access.asObject().name"></td>
<td data-bind="text: usrExtVal().extension"></td>
<td data-bind="text: usrEmail"></td>
<td class="buttons">
<a class="btn" data-bind="click: $root.edit" href="#" title="edit"><i class="icon-edit"></i></a>
<a class="btn" data-bind="click: $root.remove" href="#" title="remove"><i class="icon-remove"></i></a>
</td>
</tr>
</script>
<script id="editUsrs" type="text/html">
<tr>
<td><input data-errorposition="b" class="required" name="fname" data-bind="value: fname" /></td>
<td><input data-errorposition="b" class="required" name="lname" data-bind="value: lname" /></td>
<td><input data-errorposition="b" class="required" name="phone" data-bind="value: phone" /></td>
<td><select class="accessSelect" data-bind="options: $root.validAccess, optionsText: 'name', optionsValue: 'id', value: access, valueAsObject: 'asObject'"></select></td>
<td><select id="extData" data-bind="options: $root.availableExtData, optionsText: 'extension', value: usrExtVal"></select></td>
<td><input id="extEmail" data-errorposition="b" class="required" name="email" data-bind="value: usrEmail" /></td>
<td class="buttons">
<a class="btn btn-success" data-bind="click: $root.save" href="#" title="save"><i class="icon-ok"></i></a>
<a class="btn" data-bind="click: $root.remove" href="#" title="remove"><i class="icon-remove"></i></a>
</td>
</tr>
</script>
</form>
</div>
</div>
</fieldset>
There are a few problems in your code.
availableExtData tries to access subproperties of usrExtVal, which is sometimes undefined. That access causes an error, which prevents further execution of the computed. So you need to first check if usrExtVal is set.
You have two entries in ajaxResultExt, but they both have the same extension. So once you've selected 123 for the first item, there aren't any left for the second, because both 123 values will be removed. So your extensions need to be unique.
You're updating usrEmail within a loop in availableExtData, which doesn't make any sense. It should be in a separate ko.computed.
Here is your example with these fixes: http://jsfiddle.net/mbest/6bFsY/5/

knockoutjs observablearray make individual item observable

i was looking # 2'nd tutorial "Working with lists and colection" on Knockoutjs.com: http://learn.knockoutjs.com/#/?tutorial=collections
Here's HTML:
Your seat reservations ()
<table>
<thead><tr>
<th>Passenger name</th><th>Meal</th><th>Surcharge</th><th></th>
</tr></thead>
<!-- Todo: Generate table body -->
<tbody data-bind="foreach: seats">
<tr>
<td> <input data-bind="value: name()"/></td>
<td> <select data-bind="options: $root.availableMeals, value: meal, optionsText: 'mealName'"></select></td>
<td data-bind="text: formatterPrice"></td>
<!--<td> Remove</td>-->
<td> <button data-bind="click: $root.removeSeat">Remove</button></td>
</tr>
</tbody>
</table>
<button data-bind="click: addSeat, enable: seats().length < 5">Reserve another seat</button>
<h3 data-bind="visible: totalSurcharge() > 0">
Total surcharge: $<span data-bind="text: totalSurcharge().toFixed(2)"></span>
</h3>
<span data-bind="text: seats()[0].name()" ></span> <br/>
<span data-bind="text: seats()[0].meal().mealName" />
Here's ViewModel:
// Class to represent a row in the seat reservations grid
function SeatReservation(name, initialMeal) {
var self = this;
self.name = ko.observable(name);
self.meal = ko.observable(initialMeal);
self.formatterPrice = ko.computed(function() {
var price = self.meal().price;
return price ? "$" + price.toFixed(2) : "None";
});
}
// Overall viewmodel for this screen, along with initial state
function ReservationsViewModel() {
var self = this;
// Non-editable catalog data - would come from the server
self.availableMeals = [
{ mealName: "Standard (sandwich)", price: 0 },
{ mealName: "Premium (lobster)", price: 34.958989 },
{ mealName: "Ultimate (whole zebra)", price: 290.00000 }
];
// Editable data
self.seats = ko.observableArray([
new SeatReservation("Steve", self.availableMeals[0]),
new SeatReservation("Bert", self.availableMeals[1])
]);
self.addSeat = function() {
self.seats.push(new SeatReservation("Steve", self.availableMeals[2]))
};
self.removeSeat = function(seat) {
self.seats.remove(seat);
}
self.totalSurcharge = ko.computed(function() {
var total = 0;
for(var i = 0; i < self.seats().length; i++) {
total += self.seats()[i].meal().price;
}
// alert(total);
return total;
});
}
ko.applyBindings(new ReservationsViewModel());
When i try to change "name" in textbox, my observableArray (seats) doesn't seem to change. Still my span # bottom shows "Steve" if i try to change first row's textbox.
I've made name() as observable same as meal, and if i change meal (dropdown), my observableArray (seats) seems working.
Please help!
at
<td> <input data-bind="value: name()"/></td>
use <td> <input data-bind="value: name"/></td>
and at <span data-bind="text: seats()[0].name()" ></span> <br/> you want <span data-bind="text: seats()[0].name" ></span> <br/>

Categories