How can I nest a loop from a single array in knockoutjs? - javascript

For simplicity's sake, I have a simple array. What I'm looking to do is to loop through the array's questions for the first category, and then loop through the array's questions for the second category, and so on.
The "Answer" will be text input fields or radio buttons or whatever.
How would I "nest" the two loops in knockout.js? I can't separate it into two arrays, because then I lose the relationship between the two loops.
//knockout.js viewModel
function SurveyViewModel() {
var self = this;
self.EvaluationElement = ko.observableArray([
{category: "Category1", question: "Question #1"},
{category: "Category1", question: "Question #2"},
{category: "Category2", question: "Question #3"},
{category: "Category2", question: "Question #4"},
{category: "Category3", question: "Question #5"},
{category: "Category3", question: "Question #6"},
{category: "Category4", question: "Question #7"},
And I want to build a view (HTML) that is essentially a table nested as follows:
<table>
<tr>
<th colspan="3">Category 1</th>
</tr>
<tr>
<td>Question #1</td>
<td>Answer</td>
<td></td>
</tr>
<tr>
<td>Question #2</td>
<td>Answer</td>
<td></td>
</tr>
<tr>
<td colspan="3">Category 2</td>
</tr>
<tr>
<td>Question #3</td>
<td>Answer</td>
<td></td>
</tr>
<tr>
<td>Question #4</td>
<td>Answer</td>
<td></td>
</tr>
<tr>
<td>Question #5</td>
<td>Answer</td>
<td></td>
</tr>
</table>
I'm trying to do something like this... there are obvious errors, but hopefully it conveys my thinking.
<br>
<table class="table table-hover">
<tbody data-bind="foreach: EvaluationElement/Category">
<tr>
<td colspan="2" data-bind="text: category></td>
</tr>
<data-bind="foreach: EvaluationElement">
<tr>
<td data-bind="text: question"></td>
<td>Answer</td>
<td></td>
</tr>
<// close loop>
</tbody>
</table>
<br>
<button data-bind="click: submitSurvey">Submit</button>

You should restructure your data in your view model to keep your view nice and simple. If your view requires table bodies per category, that's what the viewmodel data should reflect.
To restructure the list of questions in to a list of categories, you can use Knockout's pureComputed.
We create a function that does the data transformation from [ { category, question } ] to [ { category, questions: [ { category, question ] } ].
This very much resembles a groupBy. There are many ways to write a grouping function; search for "group by property in javascript" if you want to learn more about the different approaches.
With the new structure, the view very much resembles your desired format:
function SurveyViewModel() {
var self = this;
self.EvaluationElement = ko.observableArray([{
category: "Category1",
question: "Question #1"
},
{
category: "Category1",
question: "Question #2"
},
{
category: "Category2",
question: "Question #3"
},
{
category: "Category2",
question: "Question #4"
},
{
category: "Category3",
question: "Question #5"
},
{
category: "Category3",
question: "Question #6"
},
{
category: "Category4",
question: "Question #7"
}
]);
self.categories = ko.pureComputed(function() {
// Assumes questions sorted by category
return self.EvaluationElement().reduce(
function(cats, q, i, qs) {
const prev = qs[i - 1];
if (!prev || prev.category !== q.category) {
cats.push({
category: q.category,
questions: [ q ]
});
} else {
cats[cats.length - 1].questions.push(q);
};
return cats;
}, []);
});
};
ko.applyBindings(new SurveyViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<tbody data-bind="foreach: categories">
<tr>
<td colspan="2" data-bind="text: category"></td>
</tr>
<!-- ko foreach: questions -->
<tr>
<td data-bind="text: question"></td>
<td>Answer</td>
<td></td>
</tr>
<!-- /ko -->
</tbody>
</table>

Related

How can I create a Vue table component with column slots?

I am currently working with a relatively large Vue (Vue 2) project that uses a lot of tables, and I want to create a reusable table component where each column is a child component / slot. Something like this:
<Table :data="data">
<TableColumn field="id" label="ID" />
<TableColumn field="name" label="Name" />
<TableColumn field="date_created" label="Created" />
</Table>
const data = [
{ id: 1, name: 'Foo', date_created: '01.01.2021' },
{ id: 2, name: 'Bar', date_created: '01.01.2021' }
];
Which in turn should output this:
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Created</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Foo</td>
<td>01.01.2021</td>
</tr>
<tr>
<td>2</td>
<td>Bar</td>
<td>01.01.2021</td>
</tr>
</tbody>
</table>
We've previously used Buefy, but the vendor size becomes unnecessarily large, as we only use a fraction of the components' functionality - so I want to create a lightweight alternative.
With this data you only need 2 Props, labels and data.
<!-- Component -->
<table>
<thead>
<tr>
<td v-for="(label, labelIndex) in labels" :key="labelIndex">
{{ label.text }}
</td>
</tr>
</thead>
<tbody>
<tr v-for="(item, itemIndex) in data" :key="itemIndex">
<td v-for="(label, labelIndex) in labels" :key="labelIndex">
{{ item[label.field] }}
</td>
</tr>
</tbody>
</table>
// Data and labels
const labels = [
{ text: ID, field: id },
{ text: Name, field: name },
{ text: Created, field: date_created },
]
const data = [
{ id: 1, name: 'Foo', date_created: '01.01.2021' },
{ id: 2, name: 'Bar', date_created: '01.01.2021' }
];
<table-component
:labels="labels"
:data="data"
>
</table-component>
If you need something more complex you can use nested components combined with a named slots for the header or footer of the table (or other options like search).

add multiple rows in one column using angular ng-repeat

I want to generate table contents using json object. I am using ng-repeat to insert multiple rows in a table. But I have to generate table like the following.
--------------
ID | subjects
--------------
| S1
1 | S2
| S3
--------------
| S4
2 | S5
| S6
--------------
my angular code is:
<tr ng-repeat = "user in users">
<td>{{user.id}}</td>
<td>{{user.subject}}</td>
</tr>
my json object is :
user:[
{id:1 ,
subjects:[
{id:1 , name:"eng"}
{id:2 , name:"phy"}
]
},
{id:2 ,
subjects:[
{id:1 , name:"eng"}
{id:3 , name:"math"}
]
}
]
I want to generate html table like this
<table>
<tr>
<td >ID</td>
<td>Sub</td>
</tr>
<tr>
<td rowspan="3">1</td>
<td>S1</td>
</tr>
<tr>
<td>S2</td>
</tr>
<tr>
<td>S3</td>
</tr>
how to insert multiple rows in one column using angular
Use ng-repeat-start and ng-repeat-end. For example:
<tr ng-repeat-start="user in users">
<td rowspan="{{user.subjects.length+1}}">{{user.id}}</td>
</tr>
<tr ng-repeat-end ng-repeat="subject in user.subjects">
<td>S{{subject.id}}</td>
</tr>
Here is a full example:
var app = angular.module('MyApp', []);
app.controller('MyController', function ($scope) {
var users = [{
id: 1,
subjects: [{
id: 1,
name: "eng"
}, {
id: 2,
name: "phy"
}]
}, {
id: 2,
subjects: [{
id: 1,
name: "eng"
}, {
id: 3,
name: "math"
}, {
id: 4,
name: "hist"
},
{
id: 5,
name: "geog"
}]
}];
$scope.users = users;
});
table { border-collapse: collapse; }
td { border: 1px solid Black; }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<table ng-app="MyApp" ng-controller="MyController">
<thead>
<tr>
<td>ID</td>
<td>Subjects</td>
</tr>
</thead>
<tbody>
<tr ng-repeat-start="user in users">
<td rowspan="{{user.subjects.length+1}}">{{user.id}}</td>
</tr>
<tr ng-repeat-end ng-repeat="subject in user.subjects">
<td>S{{subject.id}}</td>
</tr>
</tbody>
</table>
Also, working fiddle here: http://jsfiddle.net/donal/r51d5fw5/17/
An alternative version, using nested ng-repeat, can be implemented using a div to display the nested subject information:
<tr ng-repeat="user in users">
<td>{{user.id}}</td>
<td>
<div ng-repeat="subject in user.subjects">S{{subject.id}}</div>
</td>
</tr>
rowspan will do everything you need. Follow this link:
w3schools This is one of the best resources for web development.
Using div u can also do
<div ng-repeat = "user in users">
<div> {{user.id}} </div>
<div ng-repeat = "user1 in users.subjects">
{{user1.id}} : {{user1.name}}
<div>
</div>
Donal's answer is good.
But I edited to show beautiful table:
<table class="table table-bordered table-hover table-height m-b-xs">
<thead>
<tr>
<td>ID</td>
<td>Subjects</td>
<td>name</td>
</tr>
</thead>
<tbody>
<tr ng-repeat-start="user in users">
<td rowspan="{{user.subjects.length}}">key {{user.id}}</td>
<td>S{{ user.subjects[0].id }}</td>
<td>{{ user.subjects[0].name }}</td>
</tr>
<tr ng-repeat-end ng-repeat="subject in user.subjects.slice(1)">
<td>S{{subject.id}}</td>
<td>{{subject.name}}</td>
</tr>
</tbody>

Generate html table using angular

I want to generate html table using angular.I have a json object and i used ng-repeat to add multiple rows. But my table structure is different.
I want to generate table structure like this:
<table>
<tr>
<td >ID</td>
<td>Sub</td>
</tr>
<tr>
<td rowspan="3">1</td>
<td>S1</td>
</tr>
<tr>
<td>S2</td>
</tr>
<tr>
<td>S3</td>
</tr>
</table>
--------------
ID | subjects
--------------
| S1
--------
1 | S2
--------
| S3
--------------
| S4
--------
2 | S5
--------
| S6
--------------
user:[
{id:1 ,
subjects:[
{id:1 , name:"eng"}
{id:2 , name:"phy"}
]
},
{ id:2 ,
subjects:[
{id:1 , name:"eng"}
{id:3 , name:"math"}
]
}
]
my angular code is:
<tr ng-repeat = "user in users">
<td>{{user.id}}</td>
<td>{{user.subject}}</td>
</tr>
I want to know how to generate table structure like above
You need to use ng-repeat-start and ng-repeat-end. For example:
var app = angular.module('MyApp', []);
app.controller('MyController', function ($scope) {
var users = [{
id: 1,
subjects: [{
id: 1,
name: "eng"
}, {
id: 2,
name: "phy"
}]
}, {
id: 2,
subjects: [{
id: 1,
name: "eng"
}, {
id: 3,
name: "math"
}]
}];
$scope.users = users;
});
table { border-collapse: collapse; }
td { border: 1px solid Black; }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<table ng-app="MyApp" ng-controller="MyController">
<thead>
<tr>
<td>ID</td>
<td>Subjects</td>
</tr>
</thead>
<tbody>
<tr ng-repeat-start="user in users">
<td rowspan="{{user.subjects.length+1}}">{{user.id}}</td>
</tr>
<tr ng-repeat-end ng-repeat="subject in user.subjects">
<td>S{{subject.id}}</td>
</tr>
</tbody>
</table>
Also, working fiddle here: http://jsfiddle.net/donal/r51d5fw5/17/
Similar to the nested structure of your object, you can nest your ng-repeats like this...
<tr ng-repeat = "user in users">
<td>{{user.id}}</td>
<td ng-repeat="subject in user.subjects">{{subject.name}}</td>
</tr>
I split your JSON and push it into two $scope variable, like the below:
angular.forEach($scope.user, function(user) {
$scope.userDetails.push({
userId: user.id,
});
angular.forEach(user.subjects, function(subject) {
$scope.subjectDetails.push({
userId: user.id,
subjectName: subject.name
});
});
});
In the HTML, I am using filter to filter by user's id.
<table>
<thead>
<tr>
<th style="width: 50px">ID</th>
<th style="width: 75px">Subjects</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in userDetails">
<td><span ng-bind="user.userId"></span>
</td>
<td>
<table>
<tr ng-repeat="sub in subjectDetails | filter: {userId: user.userId}">
<td>
<div ng-bind="sub.subjectName"></div>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
This is also one of the way to achieve this.
Sample Plunker
Here, I'm assuming that you want each user in users (in your JSON object) in one table, so you can do something as follows:
<table ng-repeat="user in users">
<tr>
<td>{{user.id}}</td>
<td>Subjects</td>
</tr>
<tr ng-repeat="subject in user.subjects">
<td>{{subject.id}}</td>
<td>{{subject.name}}</td>
</tr>
</table>
Adjust the html accordingly based on your needs, but this is the basic idea :)
Hope this helps!
add users object in $scope.
$scope.users=[
{id:1 ,
subjects:[
{id:1 , name:"eng"}
{id:2 , name:"phy"}
]
},
{ id:2 ,
subjects:[
{id:1 , name:"eng"}
{id:3 , name:"math"}
]
}
]
Here is the html.
<table border="1">
<tr>
<th> ID </th>
<th> subjects </th>
</tr>
<tr ng-repeat="user in users">
<td>{{user.id}}</td>
<td><table>
<tr ng-repeat="subject in user.subjects">
<td>{{subject.name}}</td>
</tr>
</table></td>
</tr>
</table>
Here is the plunker

angular ng-repeat inside of ng-repeat not working in table

I have the following
<tbody ng-repeat="history in orderHistory">
<tr>
<td>{{history.reference_code}}</td>
<div ng-repeat="items in history.orderedItems">
<td>{{items.product_description}}</td>
<td>{{items.quantity}}</td>
</div>
<td>
</tr>
</tbody>
it seems that the second ng-repeat is not working and {{items.quantity}} or items . anything does not end up showing.
any ideas?
When i just test it out like so it works
<div ng-repeat="history in orderHistory">
<div ng-repeat="items in history.orderedItems">
{{items.product_description}}
</div>
</div>
but i really need it inside the table
I tried the following:
<tbody>
<tr ng-repeat="history in orderHistory">
<td>{{history.reference_code}}</td>
<div ng-repeat="items in history.orderedItems">
<td>{{items.product_description}}</td>
<td>{{items.quantity}}</td>
</div>
<td>
</tr>
</tbody>
and still does not work
UPDATED Answer
http://plnkr.co/edit/x0ZxWSy6JN3fo961NbQX?p=preview
The following should get you going.
<table ng-controller="myCtrl">
<tbody>
<tr ng-repeat="history in orderHistory">
<td>{{history.reference_code}}</td>
<td ng-repeat-start="items in history.orderedItems">
{{items.product_description}}<//td>
<td ng-repeat-end>{{items.quantity}}</td>
</tr>
</tbody>
</table>
OLD ANSWER -----
Kept previous answer is kept for historical reasons due to comments.
The problem is tbody - not supposed to be repeated. I had a similar problem with <p> just like what you see in here.
Here is a fiddle http://jsfiddle.net/yogeshgadge/02y1jjau/1/ where it works - tbody changed to div.
Here is one demo where tbody does NOT work http://jsfiddle.net/yogeshgadge/2tk3u7hq/4/
Nested ng-repeat
Try this - moved the ng-repeat on <tr>
<tbody>
<tr ng-repeat="history in orderHistory">
<td>{{history.reference_code}}</td>
<div ng-repeat="items in history.orderedItems">
<td>{{items.product_description}}</td>
<td>{{items.quantity}}</td>
</div>
<td>
</tr>
</tbody>
This could work properly.
<table>
<thead>
<tr>
<th></th>
<th>Claim Id</th>
<th>Job Number</th>
<th>Project</th>
<th>Created On</th>
<th>Error</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="jobData in createJobResponseData.data">
<td class="counterCell"></td>
<td>{{jobData.claimId}}</td>
<td>{{jobData.jobNumber}}</td>
<td>{{jobData.project}}</td>
<td>{{jobData.createdOn}}</td>
<td >
<div class="counterCellDiv" ng-repeat="error in jobData.errorList">
{{error.errorMessage}}
</div>
</td>
</tr>
</tbody>
$scope.createJobResponseData = {
'status': '200',
'message': 'Request processed successfully',
'data': [
{
'claimId': 'data1',
'claimLineId': '1',
'errorList': null,
'insertedIntoDb': true,
'jobNumber': 'nn001',
'project': 'pro0',
'repairStatus': '5'
},
{
'claimId': 'ASD',
'claimLineId': '1',
'errorList': [{
'errorCode': ')01',
'errorMessage': 'accidentDescription cannot be blank'
}, {
'errorCode': '(01)',
'errorMessage': 'abcd cannot be blank'
}],
'insertedIntoDb': true,
'jobNumber': '',
'project': 'asd',
'repairStatus': '5'
},
{
'claimId': 'oiqweo',
'claimLineId': '1',
'errorList': null,
'insertedIntoDb': true,
'jobNumber': 'qoweiu',
'project': 'asq',
'repairStatus': '5'
},
{
'claimId': 'SDDDASD',
'claimLineId': '1',
'errorList': null,
'insertedIntoDb': true,
'jobNumber': 'asdqio',
'project': 'kalsdjjk',
'repairStatus': '5'
}
]
}

"with" databinding on a multi-dimensional array in Knockout Js

I have an array that looks something like the following
var initData = [
new Order({
orderId: "183175",
name: "Columbus Africentric",
production: [{
pType: "Art Time",
by: "MJ"
}, {
pType: "Front Pocket",
by: "WB"
}]
}),
new Order({
orderId: "198675",
name: "Stanford High",
production: [{
pType: "Art Time",
by: "MJ"
}, {
pType: "Full Back",
by: "WB"
}]
})
]
I'm trying to do a with binding to show only extra information for on order when the item is clicked on. So I have a foreach for the orders that shows the orderId and the name in a table, and a button to click that then should show all production items for the chosen order. Something like the following
<tbody data-bind="foreach:orders">
<tr>
<td>
<label class="read" data-bind="text:orderId, visible:true" />
</td>
<td>
<label class="read" data-bind="text:name, visible:!$root.isItemEditing($data)" />
</td>
<td>
<td class="tools">
<div data-bind="if: production"><button data-bind="click: $root.toggleProductionMode">Production</button>
</div>
</td>
</tr>
<tr data-bind="visible: showProductionOrder, with: production">
<td colspan="5">
<h3>Production Summary</h3>
<table class="ko-grid">
<thead>
<tr>
<th>Type</th>
<th>By</th>
</tr>
</thead>
<tbody
<tr>
<td>
<label class="read" data-bind="text:pType, visible:!$root.isItemEditing($data)" />
</td>
<td>
<label class="read" data-bind="text:by, visible:!$root.isItemEditing($data)" />
</td>
</tr>
</tbody>
</table>
</tr>
I think I need to use a foreach to get to the production information. Can a foreach binding be used inside a with binding? Or do I even need one? If I have it bound using the "with" binding, is there a certain way to get to the multiple production items? I know this is super easy and it's probably staring me right in the face.
also, when creating the Item Model I'm doing the following, which I think might be incorrect.
function Order(data) {
self.orderId = ko.observable();
self.name = ko.observable();
self.production = ko.observableArray([
[
self.pType = ko.observable(),
self.by = ko.observable()
]
]);
}
You don't have to create a new table inside the main table. For a child collection you have to use "ko foreach: production" as a html comment and then add your tr tags afterwards to display the production items. Have a look into this JSFiddle example.
// HTML
<table>
<tr>
<th>Student ID</th>
<th>Student Name</th>
</tr>
<tbody data-bind="foreach: Students">
<tr>
<td data-bind="text: StudentID"></td>
<td data-bind="text: StudentName"></td>
</tr>
<!-- ko foreach: Courses -->
<tr>
<td style='padding-left:20px;' data-bind="text: CourseID"></td>
<td style='padding-left:20px;' data-bind="text: CourseName"></td>
</tr>
<!-- /ko -->
</tbody>
</table>
// KNOCKOUT CODE
function StudentViewModel() {
var self = this;
self.Students = [
{ StudentID: "1", StudentName: "Ali",
Courses: [ { CourseID: "100", CourseName: "Math" }, { CourseID: "102", CourseName: "Physics" } ]
},
{ StudentID: "2", StudentName: "Isa" ,
Courses: [ { CourseID: "103", CourseName: "Chemistry" }, { CourseID: "104", CourseName: "Social Studies" } ] },
{ StudentID: "3", StudentName: "Zoya" ,
Courses: [ { CourseID: "100", CourseName: "Math" }, { CourseID: "106", CourseName: "Stats" } ] },
];
}
ko.applyBindings(new StudentViewModel());

Categories

Resources