Tabulator: reformat cell on 'cellEdited' event - javascript

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();
}

Related

DataTable rows.every function Issue

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.

How to change row color in datatables?

I am using datatables and currently stuck in changing a row to another color if value = INACTIVE, already tried many things but it has really weird error, my codes are :
"createdRow": function (row, data, dataIndex) {
if (data[9] = "INACTIVE") {
$(row).addClass("yellow");
} else {
$(row).addClass("white");
}
}
This code change all color row, but i want only change value INACTIVE
Thanks for the help!
You have a typo in your code.
In your if statement use == instead of =.
"createdRow": function (row, data, dataIndex) {
if (data[9] == "INACTIVE") {
$(row).addClass("yellow");
} else {
$(row).addClass("white");
}
}
In the condition, you are assigning the value "INACTIVE" to the data[9] instead of comparing the value. Subsequently, the condition only checks whether data[9] has some value, which is true, and class .yellow is always added.
So the condition should be like this if (data[9] == "INACTIVE") or rather if (data[9] === "INACTIVE") to perform check without type conversion.
In your if statement you are using a single '=' which is used for assignment. You should use double '=' to compare if the value is the same and triple '=' to compare if the value and the data types are the same.
You are also only checking index 9 of data. In your function you seem to also be passing in the index, you should instead change your code to something like this.
if ( data[ dataIndex ] === "INACTIVE" )

Compare two arrays and using ng-style to mark equal entries in the arrays

I have a list which shows a query of words from a db, from there i can click on one word and it gets pushed to another list which i can save than. With this i can create different wordlists. What i want to do is to give the words another color if i have already pushed them on my new list.
To do so i use a function in my controller to compare the two lists with and angular.foreach. If wordFromQuery._id === wordOnNewList._id i gave the words another background color with ng-style.
Here is my code:
View
ng-repeat="word in searchWords" ng-click="addWordToSet(word)" ng-class="isInside(word)" ng-style="{ background: choosenWords.value == 'exist' ? 'lightgreen' : 'white' }"
I iterate over the words query (searchWords) and with addWordtoSet(word) i push them to my other array (this works great). isInside(word) will do the angular.foreach to compare the two arrays and the ng-style should provide different styles, according to the if-statement from the isInside function.
Controller
$scope.isInside = function (word) {
angular.forEach($scope.currentWordlist, function (item) {
if (item._id === word._id) {
$scope.choosenWords = {value: 'exist'};
} else {
$scope.choosenWords = {value: 'notOnList'};
}
});
};
The angular.forEach compares the words from both arrays. currentWordList is the array in which i push with addWordToSet
What happens is that one word on the searchword array gets the green color (and its set of by +1, so if the word in arraypos. 0 would be right the arraypos. 1 gets the green color).
I suspect that i did it all wrong with the ng-class element, but i didnt found another good opportunity to get the word._id another way. Did i do something obviously wrong here?
I would appreciate tips or hints. Thanks!
UPDATE
It works quite fine with the addWordToSet function:
$scope.addWordToSet = function (word) {
var exists = false;
angular.forEach($scope.currentWordlist, function (item) {
if (item._id === word._id) {
exists = true;
}
});
if (exists === false) {
$scope.currentWordlist.push(word);
}
};
The only thing i need i think is not doing this on click but instantly without clicking anything. is my ng-class="isInside(word)" the right choice for that?
You can assign a color to a variable inside the same function and use it in the view.
$scope.isInside = function (word) {
angular.forEach($scope.currentWordlist, function (item) {
if (item._id === word._id) {
$scope.choosenWords = {value: 'exist'};
$scope.color = 'lightgreen'
} else {
$scope.choosenWords = {value: 'notOnList'};
$scope.color = 'white'
}
});
};
ng-style="{'background-color':color}"
View:
ng-repeat="word in searchWords" ng-click="addWordToSet(word)" ng-class="isInside(word)" ng-style="{'background-color':color}" }"
Try
$scope.choosenWords.value = 'exist';
Also initialize choosenWords at the start of the controller.
If this doesn't work check the order of priority of execution of the ng modules.
Is the controller initialized through a partial?
I sat together with a friend and we came up with a working version of this problem, so here is the solution in case someone has a similar problem and hand.
In the Controller we used the following function:
$scope.isSelected = function (word) {
var found = false;
angular.forEach($scope.currentWordlist, function (item) {
if (item._id === word._id) {
found = true;
}
});
return found;
};
It uses the foreach to compare the arrays and if there are ids that are a match the found bool returns true.
In the View we used the following:
ng-class="isSelected(word) ? 'marked' : 'unmarked'"
which uses the marked or unmarked css class for, in my case, coloring the matched words in green (marked). All other words are getting the background color white.
here is the CSS:
.marked {
background: $lightgreen;
}
.unmarked {
background: $nicewhite;
}
In my case i use scss and colorvariables, but you can, of course use all other colors like red; or #fff. The result of this are two arrays that are views. The first one is a searchquery from a DB which shows all words. The second is a new array in which you can push words by clicking on one of the words. If you do so the word gets pushed AND it gets a green background. Thats it, i hope this is good information.

DataTables custom detection plug-in and sorting order

i'm trying to implement a function on Datatables that has to look up the table data, do a regex and then, if it returns true, then, when i click on the header to sort data, it will sort it by the last 5 digits, ignoring the letters that comes up in the beginning of the string.
i have the following code
$.fn.dataTable.ext.oSort['custom'] = function (settings, col) {
return this.api().column(col, {order: 'index'}).nodes().map(function (td, i) {
var string= $(td).html();
return $.trim(string.substr(string.length - 4));
});
}
$.fn.dataTable.ext.type.detect.push(
function (value) {
var test = (/PT\d/).test(value);
return test ? 'custom' : null;
}
);
this is for a custom data that has lots of trash in the beggining, like country code and stuff, but the data order is only by the last 5 digits.
i've been searching all over i'm having a hard time to understand and debug. Debuguing the detect works, if 1 put an alert, it gives-me true when it hits the colum with the values i want, but the custom sorting doesn't work, can anybody help?
hope i'm clear about it
thanks
actualy i solved it myself.
the problem was that DataTables needs to make the entire column return true, so, if the regex fails in any value in the same column it fails.
$.fn.dataTable.ext.type.detect.unshift(
function (d) {
var pt = (/^PT\d/).test(d);
var es= (/^ES\d/).test(d);
var en= (/^EN\d/).test(d);
if (pt || es|| en) {
return 'custom'
} else {
return false;
}
}
);
$.fn.dataTable.ext.type.order['custom-pre'] = function (string) {
return $.trim(string.substr(string.length - 4));
};
so this is my last code used and it works just fine.
i'm posting it so anybody with the same problem can have a clue to solve some future problem :)

Bring selected rows to the top from the Jqgrid

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

Categories