AngularJS ng-repeat column aware table - javascript

I have the following model: Item : {data: String, schedule: DateTime, category: String}
I need to display a report of this data in the following way:
<table>
<tr>
<th>Time Range</th>
<th>Category 1</th>
<th>Category 2</th>
<th>No Category</th>
</tr>
<tr>
<td>6:00 - 2:30</td>
<td>3</td>
<td>5</td>
<td>7</td>
</tr>
</table>
So I will need to compile and filter the list of Items into the time ranges and display the totals based on categories. How can I accomplish this with angular and tell which column I am in (and therefore choose the right value to display) as the categories are dynamic.
EDIT: The table will display a summary of all Items. So you'll have take all items and compile it into the form Time Range, # In Category 1, # In Category 2, # In No Category. The only thing that will be set by me is the time ranges.
I plan to store this data in a hash map where the keys are the category names, but I need to know which column I am in as the categories are dynamic. The categories come from the database (a user putting these items in).

Basically you need to do two things. Group list by category and display grouped list. See JsFiddle: https://jsfiddle.net/vittore/mmvxbcjx/4/
In order to group list you can use array.reduce. Convenient data structure for that would be hash of hashes, ie
var hash = {
'6:30': {
category1: 5
}
}
(Say you are grouping datetime based on time with an hour step.)
In order to get structure like that with reduce you will do:
var myList = [{},....];
$scope.grouped = list.reduce(function(a, d) {
var hours = d.schedule.getHours();
if (!(hours in a)) a[hours] = {}
var timeSlot = a[hours]
timeSlot[d.category || 'no category' ] =
1 + ( timeSlot[d.category || 'no category'] | 0)
return a;
}, {})
After you've got desired structure you need to do nested ng-repeat with angular:
<tr>
<th>time slot</th>
<th ng-repeat='c in categories'>{{ c }} </th>
<th>no category</th>
</tr>
<tr ng-repeat='(k,v) in grouped'>
<th>{{ k }} : 00</th>
<td ng-repeat='c in categories'>{{ v[c] }} </td>
<td>{{ v['no category'] }}</td>
</tr>

Related

ng-zorro antd, nz-table dynamic table columns and rows

I have a table where columns and row cells are dynamically set,
in the table header th content should be dynamic and also for table body tr maybe contain HTML that contains another component tag.
is there any way to handle that, I have created a component called table and this table has #Input and #Output to be reusable for different usage.
in the ng-zorro documentation, there is no way to use the table data source technique so I can use render functions like react and.
You can create two inputs, one for the columns and one for the rows.
To make the columns dynamically you have to send to the column input an array with column objects. There you can set everything that you want. I usually use the tittle and the column function like that:
listOfColumn = [
{
title: 'Code',
compare: (a: User, b: User) => a.code.localeCompare(b.code)
},
{
title: 'Customer',
compare: (a: User, b: User) => a.name.localeCompare(b.name)
}
]
The html code to use it is the following:
<thead>
<tr>
<th *ngFor="let column of listOfColumn" [nzSortFn]="column.compare">{{ column.title }}</th>
</tr>
</thead>
And for the data, just send it to other input and set the array as data input for the table and make a loop to display the content:
<nz-table
#basicTable
[nzData]="data">
<thead>
<tr>
<th *ngFor="let column of listOfColumn" [nzSortFn]="column.compare">{{ column.title }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of basicTable.data">
<td>{{ user.code }}</td>
<td>{{ user.name }}</td>
</tbody>
</nz-table>
I hope I answer your question :D

How to add a new row in datatable and re order the indexes of each row?

I am adding a new row in the datatable and its working correctly, I am ordering the table by a field (which is timestamp) which means the newly created row should have index 0 and the rows should be 1,2,3...
however the problem is that the new row is giving -1 when I check its index. So, how can I actually re-order the table when I add a new row so that the newly created row should have index 0?
var myTable = $('#myTable').DataTable();
var rowNode = myTable
.row.add([ btnHtml1, btnHtml ])
.draw();
when I check the index after it has been added:
rowindex = tr.index(); //this gives -1 for the newly created row
Can anyone suggest why is it giving the wrong index?
UPDATE:
function myFunction(this_) {
var tr = $(this_).closest("tr");
rowindex = tr.index(); //this gives the displayed index of the row, NOT the real index
}
I call it from the datatable row like this:
<td>
<button type="button" id="edit_button" onclick='myFunction(this)' name="edit_button">Edit</button>
</td>
I am not able to recreate your problem.
But I am not sure where the tr variable is defined - so, that may be causing an issue.
For a simple test, here is some data:
<table id="example" class="display dataTable cell-border" style="width:100%">
<thead>
<tr>
<th>Name</th>
<th>Row Index</th>
</tr>
</thead>
<tbody>
<tr>
<td>Tiger Nixon</td>
<td>0</td>
</tr>
<tr>
<td>Garrett Winters</td>
<td>1</td>
</tr>
<tr>
<td>Ashton Cox</td>
<td>2</td>
</tr>
<tr>
<td>Cedric Kelly</td>
<td>3</td>
</tr>
<tr>
<td>Airi Satou</td>
<td>4</td>
</tr>
<tr>
<td>Haley Kennedy</td>
<td>5</td>
</tr>
</tbody>
</table>
And here is a test for the data:
<script type="text/javascript">
$(document).ready(function() {
var table = $('#example').DataTable();
var rowNode = table.row.add( ['Quinn Flynn', '?'] ).draw();
console.log(rowNode.index());
});
</script>
This prints 6 to the browser console - the new row has an index of 6, as expected.
Another way to verify this is to loop through all the rows, showing the index of each one:
<script type="text/javascript">
$(document).ready(function() {
var table = $('#example').DataTable();
var rowNode = table.row.add( ['Quinn Flynn', '?'] ).draw();
table.rows().every( function () {
console.log(this.index());
});
});
</script>
This generates the following output in the browser console:
Here we can see that the row for "Quinn Flynn" has the expected index: 6.
Update
Some additional notes based on comments in the answer:
The row index assigned to a row does not change, once it has been assigned, until the row is deleted, or if the data is replaced and refreshed.
Index values are assigned based on the order of the data provided to DataTables - so, for example, in my case, the first row in my HTML table is for "Tiger Nixon" - so that is assigned row index 0. The same applies to data provided by a JSON object.
The row index is independent of the display order of the row (due to sorting and/or filtering).
When you add a new row to DataTables, it is added to the end of the existing rows inside DataTables - and is indexed accordingly. So, my new row is assigned index 6.
It sounds like you want to take the first row as displayed in the table regardless of what its index number is.
There is a shortcut you can use to get that:
console.log(table.row().data());
In my example, this returns an array:
[ "Airi Satou", "4" ]
It works because it uses row() - not rows() and therefore defaults to fetching only one row (the first row!) from the displayed table.
Be aware that if you provide your data as objects, you may not get an array like my example - you may get an object - for example, something like this:
{ "firstName": "Airi Satou", "index": "4" }

vuetify simple table. Convert nested object propertied to <td> columns

I am using vuetify 2.1 and a simple nested table. with the following data structure in my data model:
groups:[
{
style:"X",
colours:"colours",
sizes:"standard",
marketplaces:[
{
markeplace:"UK",
pricelists:["A","B","C"]
},
{
markeplace:"EU",
pricelists:["D","E","F"]
},
{
markeplace:"ROW",
pricelists:["G","H","I"]
},
]
},
{
style:"X",
colours:"Black/White",
sizes:"standard",
marketplaces:[
{
markeplace:"UK",
pricelists:["X","Y","Z"]
},
{
markeplace:"EU",
pricelists:["P","Q","R"]
},
{
markeplace:"ROW",
pricelists:["S","T","U"]
},
]
}
]
What I want to achieve is < td > records for:
style
colour
size
UK.pricelists[0]
UK.pricelists[1]
UK.pricelists[2]
EU.pricelists[0]
EU.pricelists[1]
EU.pricelists[2]
ROW.pricelists[0]
ROW.pricelists[1]
ROW.pricelists[2]
<v-simple-table
dense
calculate-widths
fixed-header
height="90vh"
>
<template v-slot:default>
<thead>
<tr>
<th>Style</th>
<th>Colour Group</th>
<th>Size Group</th>
<th>UK 1</th>
<th>UK 2</th>
<th>UK 3</th>
<th>EU 1</th>
<th>EU 2</th>
<th>EU 3</th>
<th>ROW 1</th>
<th>ROW 2</th>
<th>ROW 3</th>
</tr>
</thead>
<tbody>
<tr v-for="group in groups" >
<td>{{group.style}}</td>
<td>{{group.colour}}</td>
<td>{{group.size}}</td>
<!-- this is where I am struggling... I need the next 9 td records to iterate through two levels of arrays. -->
<td v-for="mkt in group.marketplaces">{{mkt.pricelists[0]}}<td>
</tr>
</tbody>
</template>
</v-simple-table>
for reference I have complete control over the API and the shape of the data object so feel free to suggest an alternative document structure. Can you native iterate over multiple levels in vuetify simple table - perhaps using array.foreach().
Is there a vue equivalent of react-fragment which acts as outer nesting element but does not actually render anything. The challenge is that this is within a table row and I need a collection around only some of the cells in the row.
do I move the logic to a method which remaps the pricelists for the passed in group. In my situation, all groups will have the same marketplaces in the same order and each marketplace will have the same number of price lists so I don't have any issues with sorting or padding the array.
In the absence of any other suggestions, I have create a method to remap the data into a single array:
methods: {
remapPricelists(style,colours,sizes){
/* This should find a single match */
let group = this.groups.filter(g=>{
return g.style == style
&& g.colours == colours
&& g.sizes == sizes
});
let pl =[];
group[0].pricelists.map(plst =>{
pl = pl.concat(plst.pricelists);
});
return pl;
}
}
DISCLAIMER: I have edited the above code from my live data which has a slightly different format (more outer groups and differently named fields) so E&OE. In production, I will likely abstract the group fetch to a separate method as I am going to need it in lots of places and will likely strip the outer array to just leave the group object so that I can access the inner data without having to specify the group array-index.

NodeJS: How can I scrape two different tables, that are visually part of the same table, into one JSON Object?

Here's an example of the table of data I'm scraping:
The elements in red are in the <th> tags while the elements in green are in a <td> tag, the <tr> tag can be displayed according to how they're grouped (i.e. '1' is in it's own <tr>; HTML snippet:
EDIT: I forgot to add the surrounding div
<div class="table-cont">
<table class="tg-1">
<thead>
<tr>
<th class="tg-phtq">ID</td>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0pky">1</td>
<td class="tg-0pky">2</td>
<td class="tg-0pky">3</td>
</tr>
</tbody>
</table>
<table class="tg-2">
<thead>
<tr>
<th class="tg-phtq">Sample1</td>
<th class="tg-phtq">Sample2</td>
<...the rest of the table code matches the pattern...>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0pky">Swimm</td>
<td class="tg-dvpl">1:30</td>
<...>
</tr>
</tbody>
<...the rest of the table code...>
</table>
</div>
As you can see, in the HTML they're actually two different tables while they're displayed in the above example as only one. I want to generate a JSON object where the keys and values include the data from the two tables as if they were one, and output a single JSON Object.
How I'm scraping it right now is a bit of modified javascript code I found on a tutorial:
EDIT: In the below, I've been trying to find a way to select all relevant <th> tags from both tables and insert them into the same array as the rest of the <th> tag array and do the same for <tr> in the table body; I'm fairly sure for the th I can just insert the element separately before the rest but only because there's a single one - I've been having problems figuring out how to do that for both arrays and make sure all the items in the two arrays map correctly to each other
EDIT 2: Possible solution? I tried using XPath Selectors and I can use them in devTools to select everything I want, but page.evaluate doesn't accept them and page.$x('XPath') returns JSHandle#node since I'm trying to make an array, but I don't know where to go from there
let scrapeMemberTable = async (page) => {
await page.evaluate(() => {
let ths = Array.from(document.querySelectorAll('div.table-cont > table.tg-2 > thead > tr > th'));
let trs = Array.from(document.querySelectorAll('div.table-cont > table.tg-2 > tbody > tr'));
// the above two lines of code are the main problem area- I haven't been
//able to select all the head/body elements I want in just those two lines of code
// just removig the table id "tg-2" seems to deselect the whole thing
const headers = ths.map(th => th.textContent);
let results = [];
trs.forEach(tr => {
let r = {};
let tds = Array.from(tr.querySelectorAll('td')).map(td => td.textContent);
headers.forEach((k,i) => r[k] = tds[i]);
results.push(r);
});
return results; //results is OBJ in JSON format
}
}
...
results = results.concat( //merge into one array OBJ
await scrapeMemberTable(page)
);
...
Intended Result:
[
{
"ID": "1", <-- this is the goal
"Sample1": "Swimm",
"Sample2": "1:30",
"Sample3": "2:05",
"Sample4": "1:15",
"Sample5": "1:41"
}
]
Actual Result:
[
{
"Sample1": "Swimm",
"Sample2": "1:30",
"Sample3": "2:05",
"Sample4": "1:15",
"Sample5": "1:41"
}
]

How to calculate sum of rows in ng-repeat?

i have a slight different requirement,request you to please read through before marking as duplicate.
Given the example:
<table ng:init="stuff={items:[{description:'gadget', cost:99,date:'jan3'},{description:'thing', cost:101,date:'july6'},{description:'thing', cost:101,date:'jan3'} ]}">
<tr>
<th>Description</th>
<th>Cost</th>
</tr>
<tr ng:repeat="item in stuff.items|filter"> /*only filtered item grouped by date*/
<td>{{item.description}}</td>
<td ng-bind='item.cost'>{{item.cost}}</td>
</tr>
<tr>
<td></td>
<td>{{total}}</td> /*cost of items grouped by date jan3*/
</tr>
</table>
How do i calculate total cost of group by items?Is there any data-attribute in angular where I can add the cost for a grouped item,then again re-initialize it for the next grouped items?
Angular 1.3 added the ability to create an alias to your ng-repeat, which is very useful when used in conjunction with a filter.
variable in expression as alias_expression – You can also provide an optional alias expression which will then store the intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message when a filter is active on the repeater, but the filtered result set is empty.
For example: item in items | filter:x as results will store the fragment of the repeated items as results, but only after the items have been processed through the filter.
So, you can use this as alias_expression to perform a calculation on the filtered subset of your list. i.e.:
<tr ng-repeat="item in stuff.items|filter as filteredStuff">
{{filteredStuff.length}}
{{calculateTotal(filteredStuff)}}
</tr>
in controller:
$scope.calculateTotal = function(filteredArray){
var total = 0;
angular.forEach(filteredArray, function(item){
total += item.cost;
});
return total;
};
You could create your own custom filter, that will accept the array will return you the total cost of your all items.
Markup
<tr ng:repeat="item in filteredData = (stuff.items|filter)">
<td>{{item.description}}</td>
<td ng-bind='item.cost'>{{item.cost}}</td>
</tr>
<tr>
<td></td>
<td>{{filteredData| total}}</td> /*cost of items grouped by date jan3*/
</tr>
Code
app.filter('total', function(){
return function(array){
var total = 0;
angular.forEach(array, function(value, index){
if(!isNaN(value.cost))
total = total + parseFloat(value.cost);
})
return total;
}
})

Categories