I am creating an attendance tracker with the jQuery DataTables plugin! I have gotten really far with the functionality/capability and have just been stuck for weeks trying to figure out how to do this last portion of what I want it to do.
I will have a static/workable test case attached below. So the issue that I cannot figure out is how to style the parent rows based off of the child row cell values. The columns Sunday-Friday are colored based off of a hidden value called SundayStatus, MondayStatus, TuesdayStatus, and so on. There are two values that could cause it to turn green (TW & P), two values that could cause it to turn yellow (NR & O), and two values to cause it to turn red (PTO & H). In my rows.every(function ( rowIdx, tableLoop, rowLoop ) { function I need to find a way to manipulate the data and add classes to the parent rows based off the attendance values from each individual day.
P.S.(I created my own plugin $.fn.dataTable.ext.search.push(function to search through all of the data in the table and only show items where the dates Sunday-Friday are dates that are in the current week.
UPDATE 5/10 Andrew was on the right track with the update to his answer, I made one small change to today format, and changed var result = Object.keys(data).find(key => data[key].substring(0,10) === today); to var result = Object.keys(data).find(key => typeof data[key] === 'string' && data[key].startsWith(today));. I then created a conditional in my dynamic code, to read through the result from the reverse-lookup and depending on what the result is, to color the row a certain color.
Here is my JSFiddle of the Static Example that was previously in a snippet within the post: https://jsfiddle.net/BeerusDev/y8t0xoze/19/
In this update, my last and final issue that I am dealing with that I did not foresee, is that everything seems to be working fine, but it appends the status class from the first item that is posted to the DataTable and doesn't take into account for the other items. I have hit a mental block trying to figure out a way around this issue, but here is my rows.every function from my dynamic application which is inside of my startRender function
var statusClass = '';
rows.every(function ( rowIdx, tableLoop, rowLoop ) {
var data = this.data();
var node = this.node();
var today = moment().format("YYYY-MM-DD"); // "05/10/2021"
console.log(today);
//console.log(JSON.stringify(data));
var result = Object.keys(data).find(key => typeof data[key] === 'string' && data[key].startsWith(today)); // "Monday"
console.log(result);
var todayStatus = result ? data[result + 'Status'] : 'n/a';
console.log(todayStatus);
if(todayStatus === "P" || todayStatus === "TW") {
statusClass = 'green';
}
if(todayStatus === "NR" || todayStatus === "O") {
statusClass = 'yellow';
}
if (todayStatus === "PTO" || todayStatus === "H") {
statusClass = 'red';
}
});
//Add category name to the <tr>.
return $('<tr/>').addClass(statusClass)
.append('<td colspan="8">' + group + ' (' + rows.count() + ')</td>')
.attr('data-name', all)
.toggleClass('collapsed', collapsed);
This looks very close, to me!
Here are some changes I recommend:
After the end of your closing </table> tag, there is an extra <body> tag. That looks incorrect - it should be removed. I don't think this causes any errors - but it is worth fixing.
In your rows.every() function, the data variable is a plain array - for example:
[ "IT", "Name 1", "Locations Here", "05/02/2021", "05/03/2021", "P", … ]
Therefore you cannot use data.MondayStatus - because that will be undefined. Instead use something like data[5] to get the 6th item in the array (P).
If you want to change the background color of a row for a location (e.g. "IT" or "OM"), you can use a selector like this:
$("tr[data-name='IT'] td").addClass("green");
This works because you have already added a custom attribute called data-name to the relevant <td> tag. The selector finds the <td> tag which is the child of the <tr> tag using that custom attribute.
However, the problem here is: You are trying to assign the class to a table node before the DataTable has finished being built.
To address this you can move all of that row looping logic to an initComplete function:
initComplete: function(settings, json) {
this.api().rows().every(function ( rowIdx, tableLoop, rowLoop ) {
var data = this.data();
var node = this.node().previousSibling; // to handle your row grouping
if (node !== null) {
if (data[5] === "P") {
var selectorVar = "[data-name='" + data[0] + "'] td";
$( selectorVar ).addClass("green");
}
}
});
}
Instead of if (data[5] === "P"), you can expand this logic to handle different values and also different class names (not just "green"), for whatever the overall logic is that you need. My logic is just a small demo to show the color change.
Update to handle "today"
To show the approach, let's assume the following record:
var data = {
"Department": "IT",
"Name": "Name 1",
"Locations": "Locations Here",
"Sunday": "2021-05-09",
"Monday": "2021-05-10",
"MondayStatus": "P",
"Tuesday": "2021-05-11",
"TuesdayStatus": "Q",
"Wednesday": "2021-05-12",
"WednesdayStatus": "R",
"Thursday": "2021-05-13",
"ThursdayStatus": "S",
"Friday": "2021-05-14",
"FridayStatus": "T"
};
This data variable is what I think you are handling in the rows.every function. So, it's the equivalent of var data = this.data();. I may have got some of the keys wrong (uppercase/lowercase) - but you can adjust the test data if that is the case.
Now, I get today's date, formatted to match the same format as the dates in the data object:
var today = moment().format("YYYY-MM-DD"); // "2021-05-10"
I use this value to find the equivalent value in the data variable, and I return the key name for that entry:
var result = Object.keys(data).find(key => data[key].substring(0,10) === today); // "Monday"
This is basically a reverse-lookup from what you would normally do. Instead of starting with a key, we start with a value and end with a key - in this case, the key is the string "Friday".
Now we take this string and append "Status" to it.
This gives us an actual key string: "FridayStatus".
Now we use that key to find the status for today (if it exists at all in the data object):
var todayStatus = result ? data[result + 'Status'] : 'n/a'; // "P"
If the date does not exist, then you will end up with a status of "n/a".
Overall, this gives us a quick way to get today's status, without having to perform lots of if/else logic.
Once you have today's status you can use it in a smaller if/else to choose the required color you want to apply to the row.
Related
I am new at using Tabulator.js but I am willing to learn and use it deeply. I was indeed looking for a good tableGenerator library for a main project. So far, it seems to be pretty nice.
I am currently using a CND link for version 5.0.7.
Now here is my problem: I try to format cell with a background-color depending on the cell value (the cell name is status and the value can be either true or false). It works at the creation of the table. But it doesn't work if I change the cell value afterwards.
I created a method called statusFormatter:
statusFormatter: (cell) => {
if (cell.getValue() == true) {
cell.getElement().style.backgroundColor = "#A6A6DF";
}
return cell.getValue();
},
I call this method on the cellEdited event:
mainTable.on("cellEdited", function (cell) {
clientTabulator.statusFormatter(cell);
});
I suppose there is something wrong with the return in the method. But I don't know what to return when it is the style that I need.
Hope someone can help...
Change your statusFormatter function as
statusFormatter: function (cell, formatterParams, onRendered) {
let position = cell.getRow().getPosition();
// switch row even and odd color
let backgroundColor = cell.getValue() ? "#A6A6DF" : position % 2 ? "#efefef" : "#fff";
cell.getElement().style.backgroundColor = backgroundColor;
return cell.getValue();
}
I am trying to remove the specific product a user is clicking on.
So far, when you are trying to remove a product from the cart it removes based on the ID the product has. Though, I need it to check size, color, and ID. I don't want it to remove both "Product A, size M" and "Product A, size L" if the user is only clicking on size M.
So for example:
If I try to remove the "size M" one, it should only remove the "size M" one, not the "size L" as well (as both of them have the same ID).
Here is my code where the deletion of a product is:
const removeItemHandler = (e) => {
let id = e.currentTarget.getAttribute("itemid");
let size = e.currentTarget.getAttribute("size");
let color = e.currentTarget.getAttribute("color");
const clonedCartItems = JSON.parse(JSON.stringify(cartItems));
// Here is where I am trying to filter out the product. So it will only take the one the user is
clicking on
let newCart = clonedCartItems.filter(
(elem) => elem.id !== id && elem.size !== size
);
setCartItems(newCart);
};
Thanks!
If using React JS, then question like this has already been asked,
trick is to pass the index, while calling removeItemHandler
Removing element from array in component state
e.currentTarget.getAttribute("itemid"); this code is dependent on the data,
Today your logic dependent on size and color, tomorrow it might be something else
Scalable code : You can assign the UUID (probably v4) while adding the stuffs to the cart and then setting it over attribute (or passing over callback with above approach), then taking the id and finding it.
let id = e.currentTarget.getAttribute("UUID");
with one line this can be generic.
sample code
codesandbox.io/s/delete-items-from-cart-g9qm5?file=/src/App.js
let newCart = clonedCartItems.filter(
(elem) => {
if(elem.id == id) {
return elem.size !== size;
}else
return elem.id !== id
}
);
I have data of this form (simplified, but assume 20 columns between Admin and Mining):
Date,Series,Admin,Mining,CPI
1990,Ordinary Time Earnings,20,30,96
1991,Ordinary Time Earnings,22,33,100
1990,Total Earnings,25,38,96
1991,Total Earnings,29,43,100
Which I separate out into two series like this:
d3.csv("avgearnings_v1_1.csv", function(error, data) {
if (error) throw error;
OrdinaryTimeEarnings = data
.filter(function(d) {
if(d.Series == 'Ordinary Time Earnings')
return d;
});
TotalEarnings = data
.filter(function(d) {
if(d.Series == "Total Earnings")
return d;
});
And can get that to display on a graph without any issue. What I want to do next is create two more series:
OrdinaryTimeEarningsReal = OrdinaryTimeEarnings;
TotalEarningsReal = TotalEarnings;
And then recalculate those new series. Basically:
For any column that is not Date/Series/CPI
Take the CPI value for that year
Individually divide each of the Mining-Admin columns by the CPI and multiply by 100.
So: New Value = ([Old Value]/[CPI])*100
My code is terrible but I can get the correct values using this:
OrdinaryTimeEarningsReal
.forEach(function (z,i) {
var CPI = z["CPI"];
d3.map(z, function(b) {return b;})
.forEach(function (c) {
if(c !== "Date" && c !== "Series" && c !== "CPI" )
OrdinaryTimeEarningsReal[i][c] = ((z[c])/(CPI))*100;
});
});
But, when I do this it is somehow also updating the original OrdinaryTimeEarnings series, such that they equal each other and the original data in OrdinaryTimeEarnings is lost.
I'm not sure whether it's the fact I'm using the bare object (while iterating within it, eek!) or that the code above is actually changing the values in the original data object (and all 4 of the series I've created after are just references to it).
Either way, I can't work it out! I've tried a fair few different syntax forms but can't work it out. Help would be greatly appreciated to achieve this.
If you indeed use this code to "duplicate" your arrays:
OrdinaryTimeEarningsReal = OrdinaryTimeEarnings;
TotalEarningsReal = TotalEarnings;
then you mentioned it right, when you said that they reference the same object. In JavaScript, arrays are mutable, and using the code above you just created 2 new variables with a reference to the existing array in the memory.
In order to deep clone your array of objects, use this method:
OrdinaryTimeEarningsReal = JSON.parse(JSON.stringify(OrdinaryTimeEarnings));
TotalEarningsReal = JSON.parse(JSON.stringify(TotalEarnings));
This will create duplicates of the array and assign them to the new variables, so that when you'll edit them, the initial arrays will remain unaffected.
Now, regarding your code, it's a bit too complex. If I understood correctly what are you trying to achieve, you could simplify it as follows:
OrdinaryTimeEarningsReal
.forEach(function (z,i) {
for (var c in z) {
if (z.hasOwnProperty(c) && c !== "Date" && c !== "Series" && c !== "CPI" )
z[c] = z[c] / z.CPI * 100;
});
});
Good luck!
If I understand correctly :
data.forEach(function(d) {
for (var key in d) {
if (key !== 'Date' && key !== 'Series' && key !== 'CPI') {
d['new' + key] = (d[key] / d.CPI) * 100;
}
}
})
console.log(data)
I have added new onto the new attributes so the new admin value is newAdmin
Implemented fiddle : https://jsfiddle.net/thatOneGuy/9ywLytjf/
I have an array of objects that presents as follows:
0: Object
ConsolidatedItem_catalogId: "080808"
ConsolidatedItem_catalogItem: "undefined"
ConsolidatedItem_cost: "0"
ConsolidatedItem_description: "Test Catalog Item"
ConsolidatedItem_imageFile: "27617647008728.jpg"
ConsolidatedItem_itemNumber: "1234"
ConsolidatedItem_quantity: "1"
ConsolidatedItem_source: "CAT"
ConsolidatedItem_status: "02"
ConsolidatedItem_umCode: "EA"
1: Object
ConsolidatedItem_catalogId: ""
ConsolidatedItem_catalogItem: "undefined"
ConsolidatedItem_cost: "0"
ConsolidatedItem_description: "ALARM,SHUTDOWN SYSTEM,AXIOM,XP3, 0-1500 PSIG, HIGH AND LOW PRES Testing"
ConsolidatedItem_imageFile: ""
ConsolidatedItem_itemNumber: "10008"
ConsolidatedItem_quantity: "1"
ConsolidatedItem_source: "INV"
ConsolidatedItem_status: "02"
ConsolidatedItem_umCode: "EA"
I'm trying to update and remove an object if it's added again, or update the object. Preferably update the object with the new value. My code is as follows:
var result = $.grep(finalObject, function(e) {
return e.ConsolidatedItem_itemNumber == o.ConsolidatedItem_itemNumber;
});
console.log(result);
if (result.length == 0) {
finalObject.push(o);
shoppingCounter = finalObject.length;
$('#numberShoppedItems').text(shoppingCounter);
console.log(finalObject);
} else if (result.length == 1) {
finalObject.filter(function(x){
result = x;
console.log(result);
return x == result.ConsolidatedItem_itemNumber;
});
} else {
alert('Multiples Found');
}
}
I've tried multiple ways of getting the exact object and manipulating the data, however they've all failed. I would prefer to update the object, say if CatalogItem_itemNumber held the same value, if the CatalogItem_quantity was different - add the CatalogItem_quantity values together and update the array of objects.
I don't need an exact answer, a nudge in the right direction would do wonders though. I've looked at several of the related questions over the past couple of hours but none of them seem to address the issue. If you know of a question that has an answer, feel free to just link that as well. I may have missed it.
No Underscore.js please
When you find the matching record, you may update it by using $.extend
$.extend(result[0], o)
This will update the object in finalObject array in-place.
Alternatively, if you want to use the filter, you will need to insert the new object in the array.
finalObject = finalObject.filter(function(x) {
return x !== result[0];
});
finalObject.push(o)
Here we are allowing all the records that are not not equal to result to be returned in the resultant array that is received in finalObject. In next line, we are adding the new record.
Solved in the following manner:
1.) Verify object is not empty.
2.) Use .some() on object to iterate through it.
3.) Check if the finalObject, which is now e, has a match for the key in my temporary object I assemble, o.
4.) Update the values that need updating and return true;
Note: Originally I was going to remove the object by its index and replace it with a new object. This too can work by using .splice() and getting the index of the current object in that array you're in.
Here is the updating version:
if (o.ConsolidatedItem_quantity != '') {
var result = $.grep(finalObject, function(e) {
return e.ConsolidatedItem_itemNumber == o.ConsolidatedItem_itemNumber;
});
if (result.length == 0) {...}
else {
finalObject.some(function (e) {
if(e.ConsolidatedItem_itemNumber == o.ConsolidatedItem_itemNumber){
var a;
a = +e.ConsolidatedItem_quantity + +o.ConsolidatedItem_quantity;
e.ConsolidatedItem_quantity = a.toString();
document.getElementById(o.ConsolidatedItem_itemNumber).value=a;
return true;
};
});
}
}
I am using jqgrid in 'multiselect' mode and without pagination. When the user selects individual records by using mouse click, is there any way that I can bring those selected records to the top of the grid?
Thanks in advance for your help.
After small discussion with you in comments I could reformulate your question so: "how one can implement sorting by multiselect column?"
The question find is very interesting so I invested some time and could suggest a solution in case of jqGrid which hold local data (datatype which is not 'xml' or 'json' or which has 'loadonce: true' option).
First of all the working demo which demonstrate my suggestion you can find here:
The implementation consist from two parts:
Making selection as part of local data. As the bonus of the selection will be hold during paging of local data. This feature is interesting independent on the sorting by multiselect column.
The implementation of sorting by multiselect column.
To implement of holding selection I suggest to extend local data parameter, which hold local data with the new boolean property cb (exactly the same name like the name of the multiselect column). Below you find the implementation:
multiselect: true,
onSelectRow: function (id) {
var p = this.p, item = p.data[p._index[id]];
if (typeof (item.cb) === "undefined") {
item.cb = true;
} else {
item.cb = !item.cb;
}
},
loadComplete: function () {
var p = this.p, data = p.data, item, $this = $(this), index = p._index, rowid;
for (rowid in index) {
if (index.hasOwnProperty(rowid)) {
item = data[index[rowid]];
if (typeof (item.cb) === "boolean" && item.cb) {
$this.jqGrid('setSelection', rowid, false);
}
}
}
}
To make 'cb' column (multiselect column) sortable I suggest to do following:
var $grid = $("#list");
// ... create the grid
$("#cb_" + $grid[0].id).hide();
$("#jqgh_" + $grid[0].id + "_cb").addClass("ui-jqgrid-sortable");
cbColModel = $grid.jqGrid('getColProp', 'cb');
cbColModel.sortable = true;
cbColModel.sorttype = function (value, item) {
return typeof (item.cb) === "boolean" && item.cb ? 1 : 0;
};
UPDATED: The demo contain a little improved code based on the same idea.
If you have the IDs of the row(s) you can do a special sort on server side by using following command for e.g. MySQL:
Select a,b,c
FROM t
ORDER BY FIND_IN_SET(yourColumnName, "5,10,44,29") DESC
or
ORDER BY FIELD(yourColumnName, "5") DESC