I have a table bound to knockoutjs using foreach.
<table>
<thead>
<tr>
<th>ID</th>
<th>BALANCE</th>
<th>GENDER</th>
<th>AGE</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: data -->
<tr>
<!-- ko foreach: Object.keys($data) -->
<td>
<label data-bind="text: $parent[$data]" />
</td>
<!-- /ko -->
</tr>
<!-- /ko -->
</tbody>
</table>
Table rows iterate an observableArray (about 2000 items).
After table is rendered, I need to edit a row but table do not render the row changed.
How can I do this whithout clear observableArray and reload it again?
Here JSFIDDLE
Thank you
You need to make your data array properties observable so knockout would observe the changes. I'd suggest you to use knockout mapping to do the job of creating observables for you, like this:
var foo = new MyVM();
var mapping = {
create: function(options) {
return ko.mapping.fromJS(options.data);
}
};
ko.mapping.fromJS(myJS, mapping, foo.data);
But you need to change your markup so it won't just iterate through object properties, but explicitly specify which property should be used:
<tbody>
<!-- ko foreach: data -->
<tr>
<td>
<label data-bind="text: _id" />
</td>
<td>
<label data-bind="text: balance" />
</td>
<td>
<label data-bind="text: gender" />
</td>
<td>
<label data-bind="text: age" />
</td>
</tr>
<!-- /ko -->
</tbody>
Here is working demo. Of course you can make observables yourself, then just rewrite your code, so every property of each item in data array would be an observable.
Edit:
Well, since we don't know the actual properties, I'd suggest the following code to make observables:
var foo = new MyVM();
for (var i=0, n = myJSON.length; i < n; i++) {
for (var prop in myJSON[i])
if (myJSON[i].hasOwnProperty(prop))
myJSON[i][prop] = ko.observable(myJSON[i][prop]);
}
foo.data(myJSON);
And in your view model function:
self.changeRow = function (){
if(typeof self.data() != "undefined"){
self.data()[0]["gender"]("male");
}
};
See updated demo.
Related
I'm trying to create a table on an html page using knockout. The table headers and columns can vary.
I'm using an Array containing other Arrays and looping through.
Here is an example:
Array is not observable
var Headers = ["Name", "Course", "DateofBirth"];
var Columns = ["John", "Math", "23/05/1979"]
self.Person.push(Headers);
self.Person.push(Columns);
Each Person got the above Arrays
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<!-- ko if: value().Persons.length > 0 -->
<!-- ko foreach: value().Persons-->
<div>
<table>
<thead>
<tr>
<!-- ko foreach: Headers -->
<th data-bind="text: $data">
</th>
<!--/ko-->
</tr>
</thead>
<tbody>
<!-- ko if: Columns.length <= 3 -->
<tr>
<!-- ko foreach: Columns -->
<td data-bind="text: $data"></td>
<!--/ko-->
</tr>
<!--/ko-->
<!-- ko ifnot: Columns.length > 3 -->
<tr data-bind="foreach: $data">
<td data-bind="text: $data"></td>
</tr>
<!--/ko-->
</tbody>
</table>
</div>
<!--/ko-->
<!--/ko-->
So Persons is the main Array and within it there is Headers and Columns. The reason why I'm checking Columns size is to add more <td> if there is more than three columns, so I end up with having $data corresponding to each header.
If Columns is less than or equal to three, then create one <td> with the data. Otherwise, create multiple <td> with the data.
I'm getting an error in Console saying Cannot read property 'length' of null
I'm handling the position of Columns items under each header through a javascript which splits the Columns into equal chunks based on the Headers size. So each item in Columns will be, in this example, a size of three.
Not sure if I'm missing something. Any ideas or suggestions?
Why TimePicker working well outside knockout list, but does not work in him. How to launch it in knockout list?
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<link href="~/Scripts/timepicker/bootstrap-datepicker.css" rel="stylesheet" />
<link href="~/Scripts/timepicker/jquery.timepicker.css" rel="stylesheet" />
<link href="~/Scripts/timepicker/site.css" rel="stylesheet" />
<script src="~/Scripts/jquery-1.8.2.js"></script>
<script src="~/Scripts/timepicker/bootstrap-datepicker.js"></script>
<script src="~/Scripts/timepicker/jquery.timepicker.min.js"></script>
<script src="~/Scripts/timepicker/site.js"></script>
<script src="~/Scripts/knockout-2.2.0.js"></script>
<div class="demo">
<h2>Basic Example</h2>
<p><input id="basicExample" type="text" class="time" /></p>
</div>
<table>
<thead>
<tr>
<th>Passenger name</th>
<th>Time</th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: name"></td>
<td><input id="basicExample" type="text" class="time" data-bind="value: time"/></td>
</tr>
</tbody>
</table>
<script>
$('#basicExample').timepicker();
function MyViewModel() {
var self = this;
self.items = ko.observableArray();
self.items = [{ name: 'Jhon', time: '12:00' }, { name: 'Nick', time: '11:00' }];
}
ko.applyBindings(new MyViewModel());
</script>
When you use a foreach binding, Knockout is creating DOM elements for each of the items in your list. They are not there when you do you timepicker initialization.
(Also, you can't use the same ID twice in an HTML document. Your call will only find the first one.)
For any widget that needs to be initialized, you should have a custom binding handler. It might be as simple as this:
ko.bindingHandlers.timePicker = {
init: function (el) {
$(el).timepicker();
}
}
Then you would use that to bind it.
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: name"></td>
<td><input type="text" class="time" data-bind="timepicker: true, value: time"/></td>
</tr>
</tbody>
Probably there needs to be more done in the binding handler than that. Someone else wrote an example fiddle with their own timepicker binding handler.
The main problem you are facing here is that you are trying to define the JqueryUI TimePicker before you have defined your viewmodel and apply the bindings.
That means basically that your DOM nodes do not exist at this point.
To avoid that I would recommend you using the "afterRender(nodes, elements)" option in knockout foreach:
http://knockoutjs.com/documentation/foreach-binding.html
This way your DOM nodes will be there and so your inputs can be "transformed" into TimePickers
BTW, remove the "id" inside the KO foreach part, it´s useless (KO will generate another unique UI in each node)
Hope this helps
Hi guys I'm new to java Script knockout framework, I would like to display contents of an array using knockout, in reality I want to retrieve these contents from a database using ajax but I decided to start with something simpler, which is like this:
$(document).ready(function() {
function requestViewModel() {
this.branchName = ko.observable();
this.allItems = ko.observableArray({items:[{orderItemId:1,
description:"Chocolate",
unitCost:8.50,
quantity:10,
total:84.0},
{orderItemId:2,
description:"Milk",
unitCost:5.0,
quantity:10,
total:50.0},
{orderItemId:3,
description:"Sugar",
unitCost:10.0,
quantity:20,
total:200.0}]});
};
ko.applyBindings(new requestViewModel());
});
......and here is my HTML
Branch Name: <input type="text" name = "branchName">
<br><br><br><br>
<table>
<thead>
<tr><th>Item id</th><th>Description</th><th>Unit Cost</th><th>Quantity</th><th>Total</th></tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: orderItemId"></td>
<td data-bind="text: description"></td>
<td data-bind="text: unitCost"></td>
<td data-bind="text: quantity"></td>
<td data-bind="text: total"></td>
</tr>
</tbody>
</table>
Please help where you can coz I get an error which is:
"Error: The argument passed when initializing an observable array must be an array, or null, or undefined."
The error says it all. You need to pass in an array instead of an object like so:
this.allItems = ko.observableArray([{orderItemId:1,
description:"Chocolate",
unitCost:8.50,
quantity:10,
total:84.0},
{orderItemId:2,
description:"Milk",
unitCost:5.0,
quantity:10,
total:50.0},
{orderItemId:3,
description:"Sugar",
unitCost:10.0,
quantity:20,
total:200.0}]);
Note the removal of {items: and }.
I have a view model in which I iterate rows and columns to dynamically generate an HTML table. The first row of the rows[] array will be a simple string array containing the column headers; proceeding rows will contain an object that holds the column data, as well as metadata about that row (i.e. the sobjectid of the row).
How can I access the sobjectid in the html/view? I've played around with Knockout's $data and $parent binding context variables in the foreach: rows iteration and foreach: columns iteration with no success.
JS Fiddle
var viewModel = {
id: 'Account1',
heading: 'Account',
rows: ko.observableArray()
};
ko.applyBindings(viewModel);
// Ajax data populates viewModel structure...
// Header row
viewModel.rows.push(['Name', 'Title', 'Position']);
for (var i = 0; i < 3; i++) {
viewModel.rows.push({
sobjectId: i,
sobjectType: 'Account',
columns: ['Matt' + i, 'Sr.', 'Software Engineer']
});
}
<table data-bind="foreach: rows">
<!-- ko if: $index() === 0 -->
<thead>
<tr data-bind="foreach: $data">
<th data-bind="text: $data"></th>
</tr>
</thead>
<!-- /ko -->
<!-- ko ifnot: $index() === 0 -->
<tbody>
<tr data-bind="foreach: columns">
<td data-bind="text: $data"></td>
</tr>
</tbody>
<!-- /ko -->
</table>
Inside the foreach: columns you can just access your property with $parent.sobjectId
Demo JSFiddle.
Or if you move your foreach inside the tr and use the comment syntax then you can just write data-bind="text: sobjectId":
<tr>
<th>Subject Id</th>
<!-- ko foreach: $data -->
<th data-bind="text: $data"></th>
<!-- /ko -->
</tr>
And
<tr>
<td data-bind="text: sobjectId"></td>
<!-- ko foreach: columns -->
<td data-bind="text: $data"></td>
<!-- /ko -->
</tr>
Demo JSFiddle.
I am in the process of making one of my previous questions fully dynamic in that the model is built from server data, and the view loops through the viewmodel via the knockout ko foreach functionality.
The problems I am facing are:
The radio options don't stay with the value set, i.e. I click on the Operating System, and then select a Database option, and then the Operating System setting disappears.
The dependent options (in this case database and clustering) do not have their initial selection selected when the dependent option changes (i.e. when OS changes, DB should go back to the first option, none).
My fiddle is here and i think the problem is either related to the code below:
computedOptions.subscribe(function () {
var section = this;
console.log("my object: %o", section);
section.selection(section.options()[0].sku);
},section);
Or my view bindings:
<!-- ko foreach: selectedOptions -->
<h3><span data-bind="text: description"></span></h3>
<table class="table table-striped table-condensed">
<thead>
<tr>
<th colspan="2" style="text-align: left;">Description</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: options -->
<tr>
<td><input type="radio" name="$parent.name" data-bind="checkedValue: $data, checked: $parent.selection" /></td>
<td style="text-align: left;"><span data-bind="text: name"></span></td>
<td style="text-align: left;"><span data-bind="text: price"></span></td>
</tr>
<!-- /ko -->
</tbody>
</table>
<!-- /ko -->
I am not sure which and would appreciate a fresh eyes as my brain hurts from the jsfiddle session.
You have two problems:
You are not correctly binding your radio button's names: name="$parent.name" is not a knockout binding expression and it just assigns the string "$parent.name" to all of your radio buttons. What you need is to use the attr binding:
<input type="radio" data-bind="checkedValue: $data,
checked: $parent.selection,
attr: { name: $parent.name }" />
The initial selection is not working because you are using the checkedValue: $dataoption this means that your checked should contain the whole object and not just one property (sku) so you need to change your computedOptions.subscribe to:
computedOptions.subscribe(function () {
var section = this;
section.selection(section.options()[0]);
},section);
Demo JSFiddle.