How to render a table with some fixed and some dynamic columns - javascript

I want to get a table with these columns:
[Name]
[Club]
[Dynamic1]
[Dynamic2]
[Dynamic3]
etc etc.
I tried this:
<table>
<tbody data-bind="template: { name: 'rowTmpl', foreach: runners }">
</tbody>
</table>
<script id="rowTmpl" type="text/html">
<tr data-bind="template: { name: 'colTmpl', foreach: radios }" >
<td data-bind="text: name"></td>
<td data-bind="text: club"></td>
</tr>
</script>
<script id="colTmpl" type="text/html">
<td>aa</td>
</script>
#section Scripts{
<script src="/Scripts/knockout-1.3.0beta.js" type="text/javascript"></script>
<script type="text/javascript">
var vm = {
id: 1,
name: 'H21',
radios: ['2km', '4km', 'mål'],
runners: ko.observableArray([
{ name: 'Mikael Eliasson', club: 'Göteborg-Majorna OK', radios: ko.observableArray([{}, {}, {}]) },
{ name: 'Ola Martner', club: 'Göteborg-Majorna OK', radios: ko.observableArray([{}, {}, {}]) }
])
};
ko.applyBindings(vm);
</script>
}
My problem is that the tds inside colTmpl is not databoud, it's empty and placed after the third column with the text 'aa'. See this fiddle.

If you are using 1.3 beta (your fiddle is referencing the latest build), then you can do this:
<table>
<tbody data-bind="foreach: runners">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: club"></td>
<!-- ko foreach: radios-->
<td>aa</td>
<!-- /ko -->
</tr>
</tbody>
</table>
Sample here: http://jsfiddle.net/rniemeyer/bd7DT/
If you need to do this prior to 1.3 with jQuery templates, then you would want to pass the first item in your array into the template via templateOptions and do an {{if}} to check if you are on the first radio and render the two cells. Another option in jQuery templates is to use {{each}} around your dynamic cells rather than the foreach option of the template binding on the parent. You would lose some efficiency, if your columns are frequently changing dynamically. I can provide a sample for these two options, if necessary.

It is because of the fact that the content of <tr data-bind="template: { name: 'colTmpl', foreach: radios }" > is getting replaced by the template you specify.
If you instead do:
<script id="rowTmpl" type="text/html">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: club"></td>
<td data-bind="template: { name: 'colTmpl', foreach: radios }" ></td>
</tr>
</script>
<script id="colTmpl" type="text/html">
<span> . aa . </span>
</script>
It will render.

Related

knockout.js can I pass an an array of data to a template?

Working with Knockout templates, all of the examples seem to pass in single values to the template instead of array data. I have a template which will create some basic HTML, and then render a table using the data passed in to the template. Here is how it looks:
<script type="text/html" id="my-template">
<p>Here is the data</p>
[MORE HTML DATA HERE]
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Surname</th>
</tr>
</thead>
<tbody data-bind="foreach: [WHAT DO I PUT HERE???]">
<tr>
<td data-bind="text: Id"></td>
<td data-bind="text: FirstName"></td>
<td data-bind="text: Surname"></td>
</tr>
</tbody>
</table>
</script>
<div data-bind="template: { name: 'my-template', data: PersonArray1 }"></div>
<div data-bind="template: { name: 'my-template', data: PersonArray2 }"></div>
<div data-bind="template: { name: 'my-template', data: PersonArray3 }"></div>
The templates do support a foreach binding, but I don't think I can use that as I don't want the template HTML heading data marked [MORE HTML DATA HERE] repeated for every item in the array.
I want the foreach binding within the template as in my rough example above. Is there a way I can make this work? I think the answer is in the placeholder i have marked with [WHAT DO I PUT HERE???] above, but I don't know if there is a variable which holds top-level template data.
What you are looking for is the $data parameter which will give you each entry from the supplied array:
var myViewModel = {
PersonArray1 : [
{ Id: 1, FirstName: "Alice", Surname: "Bloggs" },
{ Id: 2, FirstName: "Bob", Surname: "Bloggs" },
{ Id: 3, FirstName: "Claire", Surname: "Bloggs" }
]
};
ko.applyBindings(myViewModel)
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script type="text/html" id="my-template">
<p>Here is the data</p>
[MORE HTML DATA HERE]
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Surname</th>
</tr>
</thead>
<tbody data-bind="foreach: $data">
<tr>
<td data-bind="text: Id"></td>
<td data-bind="text: FirstName"></td>
<td data-bind="text: Surname"></td>
</tr>
</tbody>
</table>
</script>
<div data-bind="template: { name: 'my-template', data: PersonArray1 }"></div>
I found the answer from this link, and the solution was posted by the creator himself.
https://github.com/knockout/knockout/issues/246
The solution is supplied in this fiddle:
http://jsfiddle.net/b9WWF/
The part of interest is where you call your template, instead of supplying an object directly to the data attribute, you can supply data in named values as follows:
<div data-bind="template: { name: 'my-template', data: { people: PersonArray1 } }"></div>
<div data-bind="template: { name: 'my-template', data: { people: PersonArray2, displayAdmins: false } }"></div>
You then access the array in your template as follows (I'm using my code above as an example):
<tbody data-bind="foreach: people">

JQuery timepicker in knockout list

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

How to access sibling properties from an array in a knockout model iteration

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.

How to display only selected records in the result table

There are two tables using the same source. These tables are using source binding of kendo templates. At present the source for both these tables are employees. Both these tables are displaying the same data.
Now, we need to modify it to show only check-box selected records in the result table. Also, when user clicks on the delete button on the result table, the check-box should be un-selected in the section table.
What modification do we need to do to make it work in MVVM?
Head
<head>
<title>MVVM Test</title>
<script type="text/javascript" src="lib/kendo/js/jquery.min.js"></script>
<script type="text/javascript" src="lib/kendo/js/kendo.web.min.js"></script>
<!----Kendo Templates-->
<script id="row-template" type="text/x-kendo-template">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
<td><button type="button" data-bind="click: deleteEmployee">Delete</button></td>
</tr>
</script>
<script id="selection-table-template" type="text/x-kendo-template">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
<td>
<input type="checkbox" name="selection" value="a">
</td>
</tr>
</script>
<!--MVVM Wiring using Kendo Binding-->
<script type="text/javascript">
$(document).ready(function () {
kendo.bind($("body"), viewModel);
});
</script>
</head>
MVVM
<script type="text/javascript">
var viewModel = kendo.observable({
// model definition
employees: [
{ name: "Lijo", age: "28" },
{ name: "Binu", age: "33" },
{ name: "Kiran", age: "29" }
],
personName: "",
personAge: "",
//Note: Business functions does not access any DOM elements using jquery.
//They are referring only the objects in the view model.
//business functions (uses "this" keyword - e.g. this.get("employees"))
addEmployee: function () {
this.get("employees").push({
name: this.get("personName"),
age: this.get("personAge")
});
this.set("personName", "");
this.set("personAge", "");
},
deleteEmployee: function (e) {
//person object is created using "e"
var person = e.data;
var employees = this.get("employees");
var index = employees.indexOf(person);
employees.splice(index, 1);
}
});
</script>
Body
<body>
<table id="selectionTable">
<thead>
<tr>
<th>
Name
</th>
<th>
Age
</th>
</tr>
</thead>
<tbody data-template="selection-table-template" data-bind="source: employees">
</tbody>
</table>
<br />
<hr />
<table id="resultTable">
<thead>
<tr>
<th>
Name
</th>
<th>
Age
</th>
</tr>
</thead>
<!--The data-template attribute tells Kendo UI that the employees objects should be formatted using a Kendo UI template. -->
<tbody data-template="row-template" data-bind="source: employees">
</tbody>
</table>
</body>
REFERENCES
set method - ObservableObject - Kedo API Reference
set method - kendo Model - Kedo API Reference
Filtering source in a Kendo Template
Kendo-UI grid Set Value in grid with Javascript
First things first.
If you remove the object from the viewModel when you delete it, it will be removed from your source table as well. You would need two arrays to handle this if you wanted it to be the way you describe. But based on the first part of your question, I thought I would post a solution.
HTML
<script id="row-template" type="text/x-kendo-template">
<tr data-bind="visible: isChecked">
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
<td>
<button type="button" data-bind="click: deleteEmployee">Delete</button>
</td>
</tr>
</script>
<script id="selection-table-template" type="text/x-kendo-template">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: age"></td>
<td>
<input type="checkbox" name="selection" data-bind="checked: isChecked"/>
</td>
</tr>
</script>
<table id="selectionTable">
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody data-template="selection-table-template" data-bind="source: employees"/>
</table>
<br />
<hr />
<table id="resultTable">
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody data-template="row-template" data-bind="source: employees"/>
</table>
JAVASCRIPT
var viewModel = kendo.observable({
employees: [
{ name: "Lijo", age: "28", isChecked: true },
{ name: "Binu", age: "33", isChecked: true },
{ name: "Kiran", age: "29", isChecked: true }
],
personName: "",
personAge: "",
addEmployee: function () {
this.get("employees").push({
name: this.get("personName"),
age: this.get("personAge")
});
this.set("personName", "");
this.set("personAge", "");
},
deleteEmployee: function (e) {
var person = e.data;
var employees = this.get("employees");
var index = employees.indexOf(person);
var employee = employees[index];
//set
employee.set('isChecked', false);
}
});
$(document).ready(function () {
kendo.bind($("body"), viewModel);
});
JSFiddle
Fiddle
Summary
Use data-bind="visible: isChecked" in "row-template" to show only selected records in the bottom table.
Make template for checkbox as
<input type="checkbox" name="selection" data-bind="checked: isChecked"/>
In the delete function, use following
employee.set('isChecked', false);
Use a dictionary to store [employee, boolean] to store employee and the checkbox state, and bind the dictionary to the view
Check this

Knockout js template, filter first element of observable array

Hi I'm trying to make the first element of my observableArray hidden, the following doesn't appear to be working, any ideas?
data-bind="ifnot: $root.typedData[0]===$data"
http://jsfiddle.net/Lx8jR/
<table border="1" style="width:90%">
<tr>
<td data-bind="text: typedData()[0].name"></td>
<td data-bind="text: typedData()[0].type"></td>
</tr>
<tr>
<td>
<table data-bind="foreach: typedData()">
<tr>
<td data-bind="text: name"></td>
</tr>
</table>
</td>
<td>
<table data-bind="foreach: typedData()">
<tr data-bind="ifnot: $root.typedData[0]===$data">
<td data-bind="text: type">
</td>
<td data-bind="text: $index">
</td>
</tr>
</table>
</td>
</tr>
</table>
var ViewModel = function() {
var self = this;
this.typedData = ko.observableArray([
{ name: "Bungle", type: "Bear" },
{ name: "George", type: "Hippo" },
{ name: "Zippy", type: "Unknown" }
]).indexed();
}
Looks like you missed a () on that line.
ifnot: $root.typedData[0]===$data
becomes
ifnot: $root.typedData()[0]===$data
http://jsfiddle.net/Lx8jR/1/
A simple mistake I've made a few dozen times.
If you get into the habit of using ko.utils.unwrapObservable this becomes less of an issue. If you use that function on a non-observable, it still succeeds.
... ko.utils.unwrapObservable($root.typedData)[0]
And for reference, there's an article on KnockMeOut which suggests a few other standards that help simplify our templates and bindings.
typedData is an observableArray, so in your comparison you would want to do (add ()):
$root.typedData()[0] === $data

Categories

Resources