Create New Row Every 2 Records Using Knockout Foreach

Create new row every 2 records using knockout foreach

Knockout binding only happen on elements, and virtual elements must also adhere to the element hierarchy. If we take your example and use indentation to show the element relation, it looks like this:

<!--ko foreach:UDGroupboxes-->
<!--ko if:$index()%2==0-->
<div class="row-fluid">
<!--/ko-->
<div class="panel form-horizontal span6">
<div class="panel-heading"><span data-bind="text:$data.Caption"></span></div>
</div>
<!--ko if:$index()%2!=0-->
</div>
<!--/ko-->
<!--/ko-->

The closing and opening virtual tags within the div are ignored by Knockout. So the above just has the effect of outputting every other item.

Here is a good answer for doing grouping of array items in Knockout: https://stackoverflow.com/a/10577599/1287183

How to perform foreach loop in knockout.js for a multiple row and column list

You need to group the users based on their indexes. The end goal is to create an array of arrays where the inner arrays contain the data for each column. You can create a computed property like this:

var viewModel = function() {
var self = this;

self.users = [
{ name: "name 1"},
{ name: "name 2"},
{ name: "name 3"},
{ name: "name 4"},
{ name: "name 5"},
{ name: "name 6"},
{ name: "name 7"},
];

// this array looks: [[user1,user2,user3], [user4,user5,user6]]
self.groupedUsers = ko.computed(function() {
var rows = [];

self.users.forEach(function(user, i) {
// whenever i = 0, 3, 6 etc, we need to initialize the inner array
if (i % 3 == 0)
rows[i/3] = [];

rows[Math.floor(i/3)][i % 3] = user;
});

return rows;
});
};

ko.applyBindings(new viewModel());

And in your html:

<table>
<tbody data-bind="foreach: groupedUsers">
<tr data-bind="foreach: $data">
<td data-bind="text: name">
</td>
</tr>
</tbody>
</table>

$data here is the inner array in context. You can read about binding context here.

Here's a fiddle for testing


CSS options:

I'm not an expert, but the key here is whether you have a container for each row. If you didn't have a separator between each row, you could achieve this using pure css. You could use float: left on the user containers and it would take the number of users per line based on the screen size and container width.

The same line of argument can be made if you're using a framework like bootstrap or foundation which have grid classes to help out. You would contain all the columns within a row class and the grid would automatically adjust based on the screen size and custom col-md and col-sm classes you set based on your device-level requirement.

Dynamic Add New Row Using knockout js

The error says

The argument passed when initializing an observable array must be an array, or null, or undefined.

and you are doing this:

self.Qualifications = ko.observableArray(Qualification);

This passes a function to the observable array. This cannot work. You probably wanted to make single new qualification as the default value of qualifications.

self.Qualifications = ko.observableArray([new Qualification()]);

However, I would initialize the list as empty and let the user add something only when there is something to add. This saves screen space.

The following is an improved version of your attempt:

  • Moved the "Add" button out of the table. This makes much more sense - it will be available when the list is empty and it will not be duplicated with every row.
  • Added an if binding to hide the entire qualifications table when there is nothing to show, and an ifnot binding to show a info paragraph when the list is empty.
  • Cleaned up the qualification list and removed duplication from your code.
  • It now stores the entire qualification object in the viewmodel, instead of just the ID. This is done by not using the optionsValue binding and saves you from having to duplicate the qualification details into each Qualification object. This also removes the need for an "Update" button.
  • Missing function deleteQualification added.
  • No inline "onkeypress" event handler in the HTML. It's not 1995 anymore, don't write inline event handlers. Knockout has the event binding for that.
  • Better object and property names. Only constructors are supposed to start with a capital letter.

function checkKeyIsDigit(vm, event) {  return event.charCode >= 48 && event.charCode <= 57 || event.charCode === 46;}
function Qualification(data) { var self = this; self.qual = ko.observable(); self.marks = ko.observable();}
function EmployeeQualification() { var self = this; self.qualificationList = ko.observableArray([ {id: '0', name: 'Master'}, {id: '1', name: 'Bachelor'}, {id: '2', name: 'CA'}, {id: '3', name: 'School Leaving'} ]); self.qualifications = ko.observableArray(); self.addQualification = function() { self.qualifications.push(new Qualification()); }; self.deleteQualification = function(qual) { self.qualifications.remove(qual); }; self.saveQualification = function() { console.log(self.qualifications()); };}
var vm = new EmployeeQualification();ko.applyBindings(vm);
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"><script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h3>Employee Qualification</h3><hr>
<div class="col-md-12"> <p data-bind="ifnot: qualifications().length">No qualifications</p> <table class="table-bordered" data-bind="if: qualifications().length"> <thead> <tr> <th class="text-center">Qualification</th> <th class="text-center">Marks</th> <th class="text-center">Action</th> </tr> </thead> <tbody data-bind="foreach: qualifications"> <tr> <td> <select class="form-control" data-bind=" value: qual, options: $parent.qualificationList, optionsText: 'name', optionsCaption: '--Choose--' "></select> </td> <td> <input type="text" placeholder='Marks' data-bind=" value: marks, event: {keypress: checkKeyIsDigit} " class="form-control"> </td> <td> <button class="btn btn-default" data-bind="click: $parent.deleteQualification">Delete</button> </td> </tr> </tbody> </table> <hr> <div class="col-md-6"> <button class="btn btn-default" data-bind="click: addQualification">Add Qualification</button> <button class="btn btn-default" data-bind="click: saveQualification">Submit</button> </div></div><hr><pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

Knockout js foreach binding large data into row by row template

I'd create a computed that simply returns an array of 4-element subarrays from the original array, and then use two nested foreach loops; the outer one would probably be a containerless binding looping over the computed array, with the inner one being similar to what you're doing now, looping over each 4-array.

Something like:

vm.byFour=ko.computed(function() {
var source=ko.unwrap(vm.degreeCodes);
var result=[];
for (var i=0; i<source.length; i+=4) {
result.push(source.slice(i, i+4);
}
return result;
});

...

<!-- ko: foreach: {data: byFour, as: degreeCodes} -->
<div class="row" data-bind="foreach: degreeCodes">
<label class="text-muted" data-bind="text: DegreeName">></label>
<div>
<!-- /ko -->

knockout foreach add row number to grid

From what I can tell, your rows are being created based on currentFormula.tones within <!-- ko foreach:$parent.tones--> and columns based on tones.levels. So you should create a div that binds its text to $index() + 1 only if the first level (column). To do this, you will need to move your loop for creating columns up above the div that will represent the column.

Something like this:

<div id="formula" data-bind="with: currentFormula">
<!-- ko foreach:$parent.tones-->
<!-- ko foreach:$parents[1].levels -->
<!-- If this is the first level/column, then create a new column to show rownum -->
<div class="col-sm-2" data-bind="if: $index() == 0, text: 'Row ' + $parent.$index() + 1"></div>
<!-- Normal column creation -->
<div class="col-sm-2" >
<a href="#" class="thumbnail img-responsive" data-bind="click: $root.hasCurrent() ? $root.currentFormula().setEnding.bind($index(), $parentContext.$index()) : $root.currentFormula().setStarting.bind($index(), $parentContext.$index())">
<img data-bind="attr: { src: '/Content/images/colors/' + $index() + $parentContext.$index() + '.png' }" alt="" />
</a>
</div>
<!-- /ko-->
<!-- /ko-->
</div>

Show limited rows with knockoutjs foreach control in a table

Your foreach should be bound to a computed that returns the slice of rows you want to display. You'll need an observable to store how many rows that is, and a function to increase the number.

function obj(nr, name) {  return {    Nr: nr,    Name: name  };}
vm = { howmany: ko.observable(3), showMore: function() { vm.howmany(vm.howmany() + 3); }, result: [ obj(1, 'one'), obj(2, 'two'), obj(3, 'three'), obj(4, 'four'), obj(5, 'five'), obj(6, 'six'), obj(7, 'seven'), obj(8, 'eight'), obj(9, 'nine'), obj(10, 'one') ], sliced: ko.pureComputed(function () { return vm.result.slice(0, vm.howmany()); })};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script><table>  <thead>    <tr>      <th>Id</th>      <th>Name</th>    </tr>  </thead>  <tbody data-bind="foreach: sliced">    <tr>      <td data-bind="text: Nr"></td>      <td data-bind="text: Name"></td>      <td data-bind="text: Date.now()"></td>    </tr>  </tbody></table><button data-bind="click: showMore">Show More</button>

How to get Knockout to group foreach

So I'm not entirely sure what you are after but you could you group manually like this.

http://jsfiddle.net/madcapnmckay/hFPgT/1/

<div data-bind="foreach: grouped" >
<div data-bind="foreach: $data" class="row">
<div class="column" data-bind="text: text"></div>
</div>
</div>

this.grouped = ko.computed(function () {
var rows = [], current = [];
rows.push(current);
for (var i = 0; i < this.items.length; i += 1) {
current.push(this.items[i]);
if (((i + 1) % 4) === 0) {
current = [];
rows.push(current);
}
}
return rows;
}, this);

Hope this helps.

Inject table row in knockout foreach binding depending on previous & current row data

For the record nemesv provided the answer in the comments. See http://jsfiddle.net/f1mv1mn8/

<div data-bind="foreach: salesOrders">
<!-- ko if: $index() == 0 || $parent.salesOrders()[$index() - 1].name != $parent.salesOrders()[$index()].name -->
<strong data-bind="text: name"></strong>
<!-- /ko -->

<li data-bind="text: item"></li>
</div>


Related Topics



Leave a reply



Submit