Create a table in Svelte from a key list object like {col1: [cell, cell], col2: [cell, cell, cell]} - html-table

I need a table which takes as prop an object like the one bellow (data) and creates a table.
I found some js libraries that can help with creating tables from json but I would prefer something more simple and in "svelte way".
I'm stuck at generating rows.
<script>
// export let data
let data = {
ignore1: 85,
ignore2: "2020-10-31",
ignore3: "some data",
ignore4: "another data",
ignore5: "../../assets/img/avatar.jpg",
col1: ["cell1", "cell1", "cell1", "cell1"],
col2: ["cell2", "cell2", "cell2", "cell2", "cell2", "cell2"],
col3: ["cell3", "cell3", "cell3"]
}
let columns = []
let values = []
for (const [col, val] of Object.entries(data)) {
if (Array.isArray(val)) {
columns.push(col)
values.push(val)
}
}
console.log(columns)
console.log(values)
// From the list of values get the max len of a list
let rows_len = Math.max(0, ...values.map(item => item.length))
// Normalize lists to have the same length as the biggest one in the nested lists
let rows = values.map(li => {
if (li.length != rows_len) {
let fill_arr = Array.from({length: rows_len - li.length}).map(el => "")
li.push(...fill_arr)
}
return li
})
console.log(rows)
let rows_range = Array.from({length: rows_len}).map(el => "")
</script>
<table class="w-full">
<thead class="capitalize border-b-2">
<tr>
{#each columns as col}
<td>{col}</td>
{/each}
</tr>
</thead>
<tbody>
{#each rows[0] as row, i}
<td>{row}</td>
{/each}
</tbody>
</table>

You need a nested loop in your template. One for lines, one for cells on each line:
<tbody>
{#each rows as row}
<tr>
{#each row as cell}
<td>{cell}</td>
{/each}
</tr>
{/each}
</tbody>

Combined with the answer from rixo I needed to create a new list which took each index at a time from all 3 lists and created another list. In the template iterated over html_rows instead of rows.
let cols_range = Array.from({length: columns.length}).map(el => "")
let rows_range = Array.from({length: rows_len}).map(el => "")
// console.log(`${rows_range.length}rows, ${cols_range.length}cols`)
let html_rows = []
for (let ir = 0; ir < rows_range.length; ir++) {
let html_row = []
for (let ic = 0; ic < cols_range.length; ic++) {
console.log(ic, ir, rows[ic][ir])
html_row.push(rows[ic][ir])
}
html_rows.push(html_row)
}
Repl: https://svelte.dev/repl/d417680037274d82b3f0fe2cd807d4a2?version=3.29.4

Related

For(?) looping multiple fetch values

I'm working on a purely for fun fantasy addition for my league.
I have a list of all the player IDs I need and I have the API link for these values.
var Players = [8475167, 8477956, 8473419, 8470638, 8471698, 8480012, 8476880, 8473563, 8478444, 8477500, 8476468, 8475744, 8476871];
and I have the basic fetch function of getting the data here
fetch('https://statsapi.web.nhl.com/api/v1/people/8476468?hydrate=stats(splits=statsSingleSeasonPlayoffs)')
.then(response => response.json())
.then(function (data) {
console.log(data);
response = data.people;
var names = response.map(function (i) {
let millerGoals = i.stats[0].splits[0].stat.goals;
let millerApples = i.stats[0].splits[0].stat.assists;
document.getElementById("millerGoals").innerHTML = millerGoals;
document.getElementById("millerAssists").innerHTML = millerApples;
})
}
)
This code above works and gets all the data I need for ONE player. Is there a way to do this with a for loop? ( I tried and I'm a little embarassed to post my results)
EDIT -
<table>
<tr class="red">
<td>Player Name</td>
<td>Team</td>
<td>Goals</td>
<td>Assists</td>
</tr>
<tr>
<td>Hedman, Victor</td>
<td>Tampa Bay</td>
<td><span id="HedmanGoals"></span></td>
<td><span id="HedmanAssists"></span></td>
</tr>
<tr class="grey">
<td>Pastrnak, David</td>
<td>Boston</td>
<td><span id="PastrnakGoals"></span></td>
<td><span id="PastrnakAssists"></span></td>
</tr>
..../ More of the same for the rest of my players ..../
Script for the above 2 - same thing for the rest of the players
addEventListener("load", () => {
fetch('https://statsapi.web.nhl.com/api/v1/people/8475167?hydrate=stats(splits=statsSingleSeasonPlayoffs)')
.then(response => response.json())
.then(function (data) {
console.log(data);
response = data.people;
response.map(function (i) {
let HedmanGoals = i.stats[0].splits[0].stat.goals;
let HedmanApples = i.stats[0].splits[0].stat.assists;
document.getElementById("HedmanGoals").innerHTML = HedmanGoals;
document.getElementById("HedmanAssists").innerHTML = HedmanApples;
})
}
)
fetch('https://statsapi.web.nhl.com/api/v1/people/8477956?hydrate=stats(splits=statsSingleSeasonPlayoffs)')
.then(response => response.json())
.then(function (data) {
console.log(data);
response = data.people;
response.map(function (i) {
let PastrnakGoals = i.stats[0].splits[0].stat.goals;
let PastrnakApples = i.stats[0].splits[0].stat.assists;
document.getElementById("PastrnakGoals").innerHTML = PastrnakGoals;
document.getElementById("PastrnakAssists").innerHTML = PastrnakApples;
})
}
)

Copy table to clipboard in vue.js

I am trying to copy div element to clipboard in vuejs. I have gone through to search related solution and applied. But not working. I ant to copy full table to clipboard. Thanks in advance
<button v-on:click = "copyToClipboard(select_txt)">Click To Copy</button>
<table class="table table-sm" id="select_txt">
<tr>
<td>Name</td>
<td> abcd </td>
</tr>
<tr>
<td>Phone</td>
<td>124545</td>
</tr>
</table>
Methods
methods:{
copyToClipboard(containerid){
var range = document.createRange();
range.selectNode(containerid);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
document.execCommand("copy");
window.getSelection().removeAllRanges();
alert("data copied");
}
},
You are doing something wrong in selecting node.
copyToClipboard(containerid){
var range = document.createRange();
let containerNode = document.getElementById(containerid); //// this part
range.selectNode(containerNode); //// this part
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
document.execCommand("copy");
window.getSelection().removeAllRanges();
alert("data copied");
}
To copy html code
copyToClipboard(containerid) {
let containerNode = document.getElementById(containerid);
var textArea = document.createElement("textarea");
textArea.style.position = "fixed";
textArea.style.top = 0;
textArea.style.left = 0;
// Ensure it has a small width and height. Setting to 1px / 1em
// doesn't work as this gives a negative w/h on some browsers.
textArea.style.width = "2em";
textArea.style.height = "2em";
// We don't need padding, reducing the size if it does flash render.
textArea.style.padding = 0;
// Clean up any borders.
textArea.style.border = "none";
textArea.style.outline = "none";
textArea.style.boxShadow = "none";
// Avoid flash of white box if rendered for any reason.
textArea.style.background = "transparent";
textArea.value = containerNode.outerHTML;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
document.execCommand("copy");
window.getSelection().removeAllRanges();
document.body.removeChild(textArea);
alert("data copied");
}

How to assign cell value depending on row and column header in vue js table

For my table, below is the code to display its components:
<tbody style="border-bottom:#dbb100 1px" v-for="(item,itm_index) in itemList" :key="itm_index.id" >
<tr class="left-align">
<td>{{item.name}}</td>
<td v-for="(day, day_index) in days" :key="day_index.id" v-if="getTotalWork(itm_index, day_index)==true">{{newValue}}</td>
</tr>
</tbody>
And this is my method that supposedly updates the value on it's corresponding rows and columns.
getTotalWork(item_index, day_index) {
let item = this.itemList[item_index];
let day = this.days[day_index];
let getDate = this.template.date;
item = item.name;
if(this.costList.length > 0){
if(day < 10) day = '0'+day;
var searchItem = this.costList.find(emp => emp.name === item);
if(searchItem.name == item && searchItem.date ==this.monthYear+'-'+day){
this.newValue = searchItem.total_work;
}else{
this.newValue = 0;
}
}
return true;
},
My problem now is that, instead of updating it's corresponding columns and rows only, it updates all of it with one value which is the value of an item. Below is the sample output:
My question is, how can I just update the corresponding cell only based on the item_index and day_index passed value. Passed value of this parameters are row and column headers. Expected output should not be 499 only, there should be like other digits.
Any idea how can I attain this? Been stucked here for almost a day and haven't found any luck in my searches.
EDIT
I don't know how to make a fiddle but the output is like this when I console.log(this.costList):
1:
date: "2018-07-01"
name:"John Doe"
total_work:240
2:
date: "2018-07-02"
name:"John Doe"
total_work:242
3:
date: "2018-07-03"
name:"John Doe"
total_work:243
You should make getTotalWork to return value and place it in output:
<tbody style="border-bottom:#dbb100 1px" v-for="(item,itm_index) in itemList" :key="itm_index.id" >
<tr class="left-align">
<td>{{item.name}}</td>
<td v-for="(day, day_index) in days" :key="day_index.id">{{getTotalWork(itm_index, day_index)}}</td>
</tr>
</tbody>
So your function will be something like this:
getTotalWork(item_index, day_index) {
let item = this.itemList[item_index];
let day = this.days[day_index];
let getDate = this.template.date;
item = item.name;
//MAX SINEV's answer
if(this.costList.length > 0){
if(day < 10) day = '0'+day;
var searchItem = this.costList.find(emp => emp.name === item);
if(searchItem.name == item && searchItem.date ==this.monthYear+'-'+day){
return searchItem.total_work;
}else{
return 0;
}
}
return 0;
}
OP's REVISION:
if(this.costList.length > 0){
if(day < 10){
day = '0'+day;
}
//this.monthYear contains year and date in yyyy-mm format
var searchItem = this.costList.find(emp => emp.name === item.name && emp.date == this.monthYear+'-'+day);
if(typeof searchItem != 'undefined'){
return searchItem.total_work;
}
}
return 0;

Is there any way to specify the column name in the custom sort function of an Aurelia Table?

This might be a very specific way to use aurelia-table, but I would like to know if there is any way to access the key you define in the aut-sort parameters in the custom.bind function.
Instead of populating the table using a static array for the data I'm loading in a JSON array and dynamically creating the table based on what's in the JSON so I can't use aut-sort by pointing to the array list value.
If I could only access a key value based on the column I click to sort then I would be able to leverage the custom sort to my needs.
I have already tried defining a global string and attaching an update to that string using a change.delegate on the Aurelia Table headers, but the change.delegate fires after the custom.bind sort and the custom.bind function is scoped to the Aurelia Table-only anyway.
<table aurelia-table="data.bind: data; display-data.bind: $resultsData; current-page.bind: currentPage; page-size.bind: pageSize; total-items.bind: totalItems; api.bind: tableApi;">
<thead>
<tr>
<th repeat.for="[k,v] of responseFields" aut-sort="custom.bind: someSort">${v.displayName}</th>
</tr>
</thead>
<tbody>
<tr repeat.for="result of $resultsData">
<td repeat.for="[k,v] of responseFields">
${result[k]}
</td>
</tr>
</tbody>
</table>
The Map just allows me to set the headers dynamically.
And here is the sort function:
someSort(a, b, sortOrder) {
let sortColumnName = this.sortColumnName;
let code1 = this.data[this.data.indexOf(a)][sortColumnName];
let code2 = this.data[this.data.indexOf(b)][sortColumnName];
if (code1 === code2) {
return 0;
}
if (code1 > code2) {
return 1 * sortOrder;
}
return -1 * sortOrder;
}
I would like to be able to define a sortColumnName but I currently cannot unless I hard-code it, but I need it to be dynamic.
there is a way to custom order:
import {HttpClient} from "aurelia-fetch-client";
export class Example {
users = [];
bind(){
let client = new HttpClient();
return client.fetch('data.json')
.then(response => response.json())
.then(users => this.users = users);
}
nameLength(row) {
return row.name.length;
}
dateSort(a, b, sortOrder) {
let date1 = new Date(a.registered);
let date2 = new Date(b.registered);
if (date1 === date2) {
return 0;
}
if (date1 > date2) {
return 1 * sortOrder;
}
return -1 * sortOrder;
}
}
.a ut-sort:before{
font-family: FontAwesome;
padding-right: 0.5em;
width: 1.28571429em;
display: inline-block;
text-align: center;
}
.aut-sortable:before{
content: "\f0dc";
}
.aut-asc:before{
content: "\f160";
}
.aut-desc:before{
content: "\f161";
}
<template>
<table class="table table-striped" aurelia-table="data.bind: users; display-data.bind: $displayData">
<thead>
<tr>
<th aut-sort="key.bind: nameLength">Name</th>
<th aut-sort="key: age; default: desc">Age</th>
<th>E-mail</th>
<th aut-sort="custom.bind: dateSort">Registered</th>
<th aut-sort="key: isActive">Active</th>
</tr>
</thead>
<tbody>
<tr repeat.for="user of $displayData">
<td>${user.name}</td>
<td>${user.age}</td>
<td>${user.email}</td>
<td>${user.registered}</td>
<td>${user.isActive}</td>
</tr>
</tbody>
</table>
</template>
I was able to get this working by using a click.capture that calls a function with the index (click.delegate didn't work since that was fired after the custom.bind) of the header.
<tr class="table-header">
<th repeat.for="item of tableData.columns" click.capture="sort($index)" aut-sort="custom.bind: sortList">
${item}
<div class="arrow"></div>
</th>
sort() just sets a private variable
private sortIndex = 0;
public sort(index) {
this.sortIndex = index;
}
and my custom sort looks like this and uses sortIndex
public sortList = (a, b, order) => {
//here I use the index of the header to get the right data from the row
const firstCell = a.cells[this.sortIndex].text;
const secondCell = b.cells[this.sortIndex].text;
if (order === 1) {
if (firstCell < secondCell) {
return -1;
}
if (firstCell > secondCell) {
return 1;
}
}
if (order === -1) {
if (firstCell < secondCell) {
return 1;
}
if (firstCell > secondCell) {
return -1;
}
}
return 0;
}

Datatable colspan sorting

Datatable jquery plugin gives error when colspan or rowspan is introduced.
Is there any other way of getting through it.
Just do it manually with some jQuery:
$(function(){
var tableRows = $('#myDatatable tbody tr');
$.each(tableRows, function (index, value) {
var cells = $(value).find('td');
$(cells[1]).remove(); // for example I want to remove cell 1
$(cells[0]).attr('colspan','2'); // for example I want to set colspan = 2 for cell 0
});
});
Datatables does not support colspan. You can add a detail row to the table that can be any format, like on click expand the row.
Datatables Colspan
Row Detail
Datatable does not support colspan but we always do trick using CSS and javascript. Here is my trick to use call span with datatable, But you have to lose search and sort functionality :(.
Say you have a table something like below:
|Header1|Header2|Header3|
|Data 11|Data 12|Data 13|
|Col SPAN For 1st. Row |
|Data 21|Data 22|Data 23|
|Col SPAN For 2nd Row |
|Data 31|Data 32|Data 33|
|Col SPAN For 3rd. Row |
<table>
<thead>
<tr> <th> Header1</th> <th> Header2</th> <th> Header3</th> </tr>
</thead>
<tbody>
<tr><td>Data 11</td><td>Data 12</td><td>Data 13</td></tr>
<tr><td >Col SPAN For 1st. Row</td></td></td></tr>
<tr><td>Data 21</td><td>Data 22</td><td>Data 23</td></tr>
<tr><td colspan=3>Col SPAN For 2st. Row</td></td></td></tr>
</tbody>
To fix the datatable error and hide all unnecessary rows
$(document).ready(function() {
\$('#example').dataTable( {
"bSort" : false,
"bFilter": false,
"fnRowCallback": function( nRow, aData, iDisplayIndex ) {
if((iDisplayIndex + 1)%2 == 1)
{
\$('td:eq(0)', nRow).attr('colspan',3);
for(var i =1; i<=2;i++){
\$('td:eq('+i+')', nRow).attr('colspan',0);
\$('td:eq('+i+')', nRow).hide();
}
}
return nRow;
}
} );
} );
If you are not fetching data from server side then you can use this code.
jQuery.fn.dataTableExt.oApi.fnFakeRowspan = function ( oSettings, iColumn, bCaseSensitive ) {
for(var i in oSettings.aoData){
var pageSize = oSettings._iDisplayLength;
var oData = oSettings.aoData[i];
var cellsToRemove = [];
for (var iColumn = 0; iColumn < oData.nTr.childNodes.length; iColumn++) {
var cell = oData.nTr.childNodes[iColumn];
var rowspan = $(cell).data('rowspan');
var hide = $(cell).data('hide');
if (hide) {
cellsToRemove.push(cell);
} else if (rowspan > 1) {
cell.rowSpan = rowspan;
}
}
for(var j in cellsToRemove){
var cell = cellsToRemove[j];
oData.nTr.removeChild(cell);
}
}
oSettings.aoDrawCallback.push({ "sName": "fnFakeRowspan" });
return this;
};
In HTML do not use rowspan attribute.
Rather use :
<table id = "myTable">
<tr>
<td data-rowspan = "3">
Content with rowspan = 3
</td>
</tr>
<tr>
<td data-hide = "true">
Content with rowspan = 3
</td>
</tr>
<tr>
<td data-hide = "true">
Content with rowspan = 3
</td>
</tr>
</table>
And call it on your datatable after initialisation.
$('#myTable').dataTable().fnFakeRowspan();
$(document).ready(function() {
$('#example').dataTable( {
"bSort" : false,
"bFilter": false,
"fnRowCallback": function( nRow, aData, iDisplayIndex ) {
if((iDisplayIndex + 1)%2 == 1)
{
$('td:eq(0)', nRow).attr('colspan',3);
for(var i =1; i<=2;i++)
{
$('td:eq('+i+')', nRow).attr('colspan',0);
}
}
return nRow;
}
} );
} );