This is for a react project I am working on.
need help with table.
I have a table for employees where each row has a different employee and I input the days for each employee and the other values gets calculated based on the number of days. so, let's say pf, esi, amount payable gets calculated based on that.
Now, there is a way to do those calculations.
for each row we could setup different variables
like for row1, employee1, we could do
days1 and the pf1, esi1, amtPayable1 would get calculated accordingly
and then I could do the same for row2.
so it would be days2 and the pf2, esi2, amtPayable2 would get calculated accordingly.
but I dont think this is the best way to do this.
what if there are a 100 rows? would I create these variables 100 times?? and the code wont be dynamic anymore.
so, what is the best way to do this?
I havent written the full code yet, because I was going to write it like this but saw the problem with this approach and therefore am asking about the correct and better way to do this.
But still, people want to see some relevant code to better understand what I mean, so here's the relevant code
This code is for 3 rows i.e. 3 employees, which means I may have to create days100, pf100, esi100, amtPayable100 if there are a 100 employees.
this made me think that it was not the best way and therfore I thought there's got to be a better way.
anyways, here's the code
let days1 = e.target.value;
let pf1 = days1*(12/100);
let esi1 = pf1*0.25;
let amtPayable1 = pf1 + es1 + 100;
let days2 = e.target.value;
let pf2 = days2*(12/100);
let esi2 = pf2*0.25;
let amtPayable2 = pf2 + es2 + 100;
let days3 = e.target.value;
let pf3 = days3*(12/100);
let esi3 = pf3*0.25;
let amtPayable3 = pf3 + es3 + 100;
after getting the values for all the variables above, I will be using it as data for a different table.
something like this:
const data = [{
key: '1',
name: 'Jerry gold',
days: days1,
amtpayable:amtPayable1,
pf:pf1,
esi:esi1,
}, {
key: '2',
name: 'Arnold Smith',
days: days2,
amtpayable:amtPayable2,
pf:pf2,
esi:esi2,
},
{
key: '3',
name: 'Baker',
days: days3,
amtpayable:amtPayable3,
pf:pf3,
esi:esi3,
}
];
so, you see there is a lot of duplication going on here and I want a way to avoid that.
#Rafael , it's not an ajax call.
everything is getting calculated based on number of days.
so, someone, say the employer, inputs the number of days each employee was present and based on that input the other values are calculated and the data is provided to a different table.
Here's the basics of how I would go about it. Use classes with properties instead of raw data objects. Create properties for the calculated fields of a person. Pass the objects to React components that know how to display the fields of the Person nicely. A Table component can create a Row for each person, for example.
class Person {
constructor(properties) {
Object.assign(this, properties);
}
get pf() {
return this.days * 12 / 100;
}
get esi() {
return this.pf * 0.25;
}
get amtPayable() {
return this.pf + this.esi + 100;
}
}
let MyRow = (props) => (
<tr>
<td>{props.item.name}</td>
<td>{props.item.days}</td>
<td>{props.item.pf}</td>
<td>{props.item.esi}</td>
<td>{props.item.amtPayable}</td>
</tr>
);
let MyTable = (props) => (
<table>
<tr>
<th>Name</th>
<th>Days</th>
<th>pf</th>
<th>esi</th>
<th>amtPayable</th>
</tr>
{props.data.map(item => <MyRow item={item} />)}
</table>
);
const data = [
new Person({
key: '1',
name: 'Jerry gold',
days: 101
}),
new Person({
key: '2',
name: 'Arnold Smith',
days: 102
}),
new Person({
key: '3',
name: 'Baker',
days: 103
})
];
// Render it
ReactDOM.render(
<MyTable data={data} />,
document.getElementById("react")
);
table {
border-collapse: collapse;
}
table, th, td {
border: 1px solid black;
padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
This question is quite broad, but after chat discussion, it sounds like you're unsure how to reduce duplication using hard-coded employee names that will eventually be retrieved from a database.
Here is an example of using an employee array that populates a table and updates the pf, esi, and amount payable according to changes per employee days:
let employees = [ 'Jerry gold', 'Arnold Smith', 'Baker' ];
let tbody = document.getElementById('list');
let tbodyHTML = '';
employees.forEach(insertRow);
tbody.innerHTML = tbodyHTML;
function insertRow(employeeName) {
tbodyHTML += `
<tr class="employee">
<th scope="row" class="name" style="text-align: left">${employeeName}</th>
<td class="pf">--</td>
<td class="esi">--</td>
<td class="payable">--</td>
<td class="days">
<input type="number" min="0" onchange="updateEmployeeData(this)">
</td>
</tr>
`;
}
function updateEmployeeData(aNumberInput) {
let days = parseInt(aNumberInput.value);
let tr = aNumberInput.parentNode.parentNode;
let pf = days * 12 / 100;
let esi = pf * 0.25;
let payable = pf + esi + 100;
const HUNDREDTHS_PLACE = 2;
let payable_td = tr.querySelector('.payable');
tr.querySelector('.pf').innerHTML = pf.toFixed(HUNDREDTHS_PLACE);
tr.querySelector('.esi').innerHTML = esi.toFixed(HUNDREDTHS_PLACE);
payable_td.innerHTML = '$' + payable.toFixed(HUNDREDTHS_PLACE);
if (payable <= 100.15) {
payable_td.dataset.range = 'low';
} else if (payable > 100.15 && payable < 100.50) {
payable_td.dataset.range = 'med';
} else if (payable > 100.50) {
payable_td.dataset.range = 'high';
} else {
delete payable_td.dataset.range;
}
}
#employeeTable tbody>tr:nth-child(odd) {
background-color: lightgrey;
}
#employeeTable th,
td {
padding: 0.5em;
text-align: center;
}
#employeeTable th {
text-transform: uppercase;
}
#employeeTable caption {
font-style: italic;
color: grey;
}
#employeeTable td.payable {
transition: background-color 1s;
}
#employeeTable td.payable[data-range="low"] {
background-color: red;
}
#employeeTable td.payable[data-range="med"] {
background-color: yellow;
}
#employeeTable td.payable[data-range="high"] {
background-color: lightgreen;
}
<table id="employeeTable">
<colgroup class="name_col"></colgroup>
<caption>employee data</caption>
<thead>
<tr>
<th scope="col">name</th>
<th scope="col">pf</th>
<th scope="col">esi</th>
<th scope="col">payable</th>
<th scope="col">days</th>
</tr>
</thead>
<tbody id="list">
</tbody>
</table>
Related
I have a table generated with data from an array (the array contains more info than what is displayed in the table). I want to click on a row to see all info from the element.
Earlier done it like this:
let rows = document.getElementsByTagName("tr");
for (let row of rows) {
row.onclick = function rowClicked(evt) {
selected = myArray[evt.target.parentElement.rowIndex];
//code (not relevant)
}
But since I added a search feature myArray[1] is not necessarily equal to row number 1 and this method doesn't work.
Is it another way to find the element in the array from clicking on a random row?
The table is generated like this:
function drawTable(data) {
let table = document.getElementById("table");
table.innerHTML = "";
let tableHead = document.createElement("thead");
let colHeads = ["Names"];
for (let header of colHeads) {
let cell = document.createElement("th")
cell.innerHTML = header;
tableHead.appendChild(cell);
}
table.appendChild(tableHead)
for (var i = 0; i < data.length; i++){
let row = document.createElement("tr");
let name = document.createElement("td");
name.innerHTML = data[i].name.first + " " + data[i].name.last;
row.appendChild(name);
table.appendChild(row);
}
}
Any help is greatly appreciated!
You need some way to map a table row to its relevant entry in myArray which isn't dependent on the position of the row in the table. Data attributes wouldn't be affected.
Create a data-index attribute on each table row. Then, when it's clicked use the value of the data-index attribute to access the relevant myArray entry.
A simple version of what you have in mind. The visible line is bound with a click event. As soon as it is triggered, it gets the ref-id from the clicked element and toggles the reference column.
const clickables = document.querySelectorAll('.clickable');
console.log(clickables)
clickables.forEach(tr => {
tr.addEventListener('click', (e) => {
const ref = e.target.parentElement.getAttribute('data-ref');
const row = document.querySelector('#' + ref);
row.classList.toggle('hide');
});
});
td {
text-align: center;
padding: 20px;
}
.hide {
display: none;
}
.clickable {
cursor: pointer;
}
.clickable:hover {
background: #ccc;
}
<table border="1">
<tr class="clickable" data-ref="a">
<td>1</td>
<td>2</td>
</tr>
<tr id="a" class="hide">
<td>a1</td>
<td>b2</td>
</tr>
<tr class="clickable" data-ref="b">
<td>3</td>
<td>4</td>
</tr>
<tr id="b" class="hide">
<td>a3</td>
<td>a4</td>
</tr>
</table>
I'm using the JSON API found at https://disease.sh/v3/covid-19/jhucsse. It stores the data as a list of JSON objects. I'm making a small website to allow users to scroll through a list, pick their country and province, and view data for that country/province. I'm using JQuery for this, here is my code so far:
const apiLink = "https://disease.sh/v3/covid-19/jhucsse"
jQuery(document).ready(function() {
jQuery("#SubmitButton").click(function() {
country_value = $('#country_picker').val() //Gets the value of the country_value select list in index.html
$.get(apiLink, function(data,status){
console.log(data[0]) // Placeholder code that prints the first JSON object in the list to the console
// Other code here
})
document.getElementById('output').innerHTML = TestHTML; // Fills the 'output' div element with the output of the program
});
});
How would I approach this problem?
You don't need jQuery. You can use the Fetch API
Get the data as JSON
Since the JSON data is an Array, use the prototype .forEach() method to extract data for each country object
Use .reduce() to construct a HTML string to be appended to the table's TBODY
Use .addEventListener() to attach an "input" Event to your #search input Element
Use the String .includes() method to check if any row has the searched value, and if a value does not matches set the hidden property to that TR element
Here's an example:
const el = (sel, el) => (el || document).querySelector(sel);
const els = (sel, el) => (el || document).querySelectorAll(sel);
const elSearch = el("#search");
const elTable = el("#table");
const elTbody = el("tbody", elTable);
let elsTr;
const apiLink = "https://disease.sh/v3/covid-19/jhucsse";
const row = (item) => `<tr>
<td>${item.country}</td>
<td>${item.province || ""}</td>
<td>${item.county || ""}</td>
<td>${item.stats.confirmed || "-"}</td>
<td>${item.stats.deaths || "-"}</td>
<td>${item.stats.recovered || "-"}</td>
</tr>`;
const build = (data) => {
elTbody.innerHTML = data.reduce((html, item) => html += row(item), "");
elsTr = els("tr", elTbody);
elSearch.addEventListener("input", search);
};
const search = () => {
const value = elSearch.value.trim().toLowerCase();
elsTr.forEach(elTr => {
const content = elTr.textContent.toLowerCase();
const match = content.includes(value);
elTr.hidden = value && !match;
});
};
fetch(apiLink).then(res => res.json()).then(build);
.sticky-thead {
position: relative;
max-height: 140px;
overflow-y: auto;
}
.sticky-thead table {
width: 100%;
border-spacing: 0;
border-collapse: separate;
}
.sticky-thead td,
.sticky-thead th {
text-align: left;
padding: 4px;
}
.sticky-thead thead th {
position: sticky;
top: 0;
background: #fff;
}
<input id="search" type="search" placeholder="Search" autocomplete="off">
<div class="sticky-thead">
<table id="table">
<thead>
<tr>
<th>Country</th>
<th>Province</th>
<th>County</th>
<th>Confirmed</th>
<th>Deaths</th>
<th>Recovered</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
If you want to populate a list, just do a for-loop? data is just an array of objects with properties.
for (let i = 0; i < data.length; i++) {
data[i].country // access the country
data[i].stats.deaths //access the deaths
}
I am having a problem when I try to update the DOM with new information coming from an API.
Every time that I click to add new users, the array displays the old, and new information. Ideally, it would update the array first and then display only the new information. I will attach a picture of what is happening. I would like to every time the user click on add new user, the DOM update with only the information of that new user.
HTML part
<table class="table is-fullwidth table is-hoverable table-info">
<thead>
<tr">
<th title="Channel Name" class="has-text-left"> Channel Name </th>
<th title="View per week" class="has-text-right"> View per week </th>
</tr>
</thead>
<tbody id="body-table">
<tr id="tr-table">
</tr>
</tbody>
</table>
script.js
const trline = document.getElementById('body-table')
let usersList = [];
async function getnewUsers(){
const res = await fetch('https://randomuser.me/api')
const data = await res.json()
// create an instance of the results
const user = data.results[0]
// create the new user
const newUser = {
name:`${user.name.first} ${user.name.last}`,
social: Math.floor(Math.random() * 10000 )
}
// update the new user to the database...
addData(newUser)
}
function addData(obj) {
usersList.push(obj)
// update the information on the screen
updateDOM()
}
function updateDOM( providedData = usersList){
providedData.forEach(item => {
const element = document.createElement('tr')
element.innerHTML = `
<td class="has-text-left cname"> ${item.name} </td>
<td class="has-text-right cview"> ${item.social} k</td>
`
trline.appendChild(element)
})
}
addUser.addEventListener('click', getnewUsers)
Result picture:
I found the problem and the solution.
I didn't reset the HTML part to clear before adding a new item. I had to fix the function updateDOM with this: trline.innerHTML = ''
After that, the function works fine.
function updateDOM( providedData = usersList){
trline.innerHTML = '' // clear everything before adding new stuff
providedData.forEach(item => {
const element = document.createElement('tr')
element.innerHTML = `
<td class="has-text-left cname"> ${item.name} </td>
<td class="has-text-right cview"> ${item.social} k</td>
`
trline.appendChild(element)
})
}
I have an array that every 12 items belongs to one set of data (i want to print that set into one <tr>). For example if my array got 24 items. I want to print first 12th items in one <tr> and remaining in second <tr> and so on. Could any one tell me how this can be done.Thanks
example array :
var myArray = ["1","first","Mixed","http://www.somesite.com/12.jpg","chicago","http://www.somesite.com/countries/1.jpg","false","http://www.somesite.com/logos/87.jpg","http://www.somesite.com/logos/87.jpg","winter","summer","http://somesite.com/list/thumb.jpg"];
This is the way i construct the array:
$.each($xml.find('dict>string'), function (i) {
var keyName = $(this).text();
myArray.push(keyName);
}
example <tr> to be printed via javascript:
<tr id="1">
<td>1</td>
<td><img src="http://somesite.com/list/thumb.jpg" height="42" width="42"></td>
<td>
chicago<br>
<br></td></tr>
What you really need to do is restructure your data to use an array of objects where each object contains all the key value pairs for a single table row.
Now building the html is trivial.
While we're at it, let's get rid of that inline javascript on the link and use an event handler instead:
var myArray = [{
id: 1,
name: 'first',
type: 'Mixed',
season: 'winter',
season2: 'summer',
logo1: 'http://www.somesite.com/12.jpg&city=chicago',
logo2: 'http://www.somesite.com/countries/1.jpg',
logo3: 'http://www.somesite.com/logos/87.jpg',
logo4: 'http://www.somesite.com/logos/87.jpg',
order: false,
thumb: 'http://somesite.com/list/thumb.jpg',
city: 'chicago'
}, {
id: 1,
name: 'last',
type: 'someType',
season: 'fall',
season2: 'spring',
logo1: 'http://www.somesite.com/12.jpg&city=chicago',
logo2: 'http://www.somesite.com/countries/1.jpg',
logo3: 'http://www.somesite.com/logos/87.jpg',
logo4: 'http://www.somesite.com/logos/87.jpg',
order: true,
thumb: 'http://somesite.com/list/thumb.jpg',
city: 'new york'
}, ];
$.each(myArray,function(i, object){
var $row = $('<tr>');
$row.append( $('<td></td>', {text:object.id}));
$row.append( $('<td></td>').append( $('<img>', {src:object.thumb, height:42, width:42}))) ;
$row.append( $('<td></td>').append( $('<a></a>', {href:'#', text:object.city, class:'doIt'}).data(object)));
$('#myTable').append($row);
});
$('#myTable').on('click','.doIt',function(e){
// use this handler to access the data on the link
// put your logic from "doIt()" in here instead
e.preventDefault();
var $this=$(this);
console.log($this.data('name'));
console.log($this.data('type'));
console.log($this.data('season'));
// etc....
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="myTable">
</table>
you can do it easily by spliting string. put your all data into a string, divide each 12 by special character like |, then divide inside strings from , and split it twice. below is a example and I used example data to understand. hope this will help to your. refere the working demo.
<html>
<head></head>
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<style>
#mytable
{
border-collapse: collapse;
}
table td{
border:1px solid black;
}
</style>
<body>
<table id='mytable'>
</table>
<script type="text/javascript">
var the_data = "1,first,mixed,http://www.first.com,chicago,http://www.somefirst.com/contry.jpg,http://www.somitemsone.com/logos/87.jpg,http://another.jpg,winter,summer,http://someitmeone/logo/one.jpg,http://oneone.com|2,22,2222,22222,22222,222222,22222222,22222222,222222222,222222222,22222222,22222222222";
var splited = the_data.split('|');
var create;
for (var i =0;i<splited.length;i++)
{
create = "<tr>";
var myarray = splited[i].split(',');
for(var j=0;j<myarray.length;j++)
{
var value = myarray[j];
create += "<td>"+value+"</td>";
}
create += "</tr>";
$("#mytable").append(create);
}
</script>
</body>
</html>
like above you can print your other data also. have a nice day
I'm working on a project where I would like to be able to display several items side by side for comparison.
I'd like to have the comparison displayed similarly to the way in which amazon does, with each item being represented by a vertical column with all of the info about the item in that column:
I'm currently using Knockout, and have an Observable array with the items I would like to compare. I would like to use the foreach binding to do the job and my problem stems from the fact that table's have all cells for a specific row contained in the same element which makes it difficult to use:
<tr>
<td>Rating</td>
<td> 4.5 (data from item 1)</td>
<td> 3.5 (data from item 2)</td>
</tr>
<tr>
<td>price</td>
<td>$2.3 (data from item 1) </td>
<td>$3.5 (data from item 2) </td>
</td>
If I wanted each item to be contained in a row this would not be a problem.
Anyone have any recommendations for how to tackle this issue since the data for different items has to exists in different (overlapping) parts of the document.
I have bad news for you. Html tables are strictly ordered in rows, and this kind of layout for your data can be a bit frustrating.
You have two options, basically:
Option A: Transpose your data
You could introduce a specific View Model for a "Feature-Comparison" and transpose your data to that. You can then do a foreach on your tbody iterating through those "featureComparisons". In code, here's what I mean:
var inputData = [
{ name: "First product", rating: 4.5, price: "$3.50", color: "Gray" },
{ name: "Another product", rating: 3.5, price: "$2.95", color: "Yellow" }
];
function FeatComparison(comparisonName, comparedValues) {
this.comparisonName = comparisonName;
this.comparedValues = comparedValues;
}
var vm = { comparisons: ko.observableArray([]) };
// You could also explicitly send a list of comparable properties from back-end.
// For this example we iterate the first product.
for (var prop in inputData[0]) {
if (inputData[0].hasOwnProperty(prop)) {
var vals = inputData.map(function(i) { return i[prop]; });
vm.comparisons.push(new FeatComparison(prop, vals));
}
}
ko.applyBindings(vm);
td { background: #eee; padding: 5px 10px; }
tr:first-child, td:first-child { font-weight: bold; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<table><tbody data-bind="foreach: comparisons">
<tr>
<td data-bind="text: comparisonName"></td>
<!-- ko foreach: comparedValues -->
<td data-bind="text: $data"></td>
<!-- /ko -->
</tr>
</tbody></table>
This option is best suited for situations where you want to make an extended comparison feature with things like comparing multiple products, saving user preferences on what features to include or not, etc.
Option B: Deal with it (for lack of a better title)
Just leave your data as is, and iterate through both objects with foreach at the same time. In code:
var inputData = [
{ name: "First product", rating: 4.5, price: "$3.50", color: "Gray" },
{ name: "Another product", rating: 3.5, price: "$2.95", color: "Yellow" }
];
function RootVm(data) {
var self = this;
this.products = ko.observableArray(data);
this.keys = ko.computed(function() {
if (self.products().length === 0) return [];
return Object.keys(self.products()[0]);
});
}
ko.applyBindings(new RootVm(inputData));
td { background: #eee; padding: 5px 10px; }
tr:first-child, td:first-child { font-weight: bold; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<table><tbody data-bind="foreach: keys">
<tr>
<td data-bind="text: $data"></td>
<!-- ko foreach: $root.products -->
<td data-bind="text: $data[$parent]"></td>
<!-- /ko -->
</tr>
</tbody></table>