Button visibility based on ranking items
Button visibility based on ranking items
Link to test case: https://live.datatables.net/domeweja/2/edit
Debugger code (debug.datatables.net): None
Error messages shown: None
Description of problem: I am trying to set up a mechanism to rank items in a datatable by clicking on the corresponding button. I have a version that works but the code is ridiculously convoluted and is not easily maintained or scaleable (i.e. expanding the ranking from 3 to n). I can't access that version of the code right now and I was trying to re-engineer it in a more streamlined manner anyway. The intended behavior is that on the initial load, only 1st should be visible, after clicking 1st, 2nd should be available, then lastly 3rd. If 1st is deactivated, it should reset and unselect two and three. Same if the Clear button is selected.
If the data has rankings already selected, on load, it should update the appropriate buttons.
Any advice or references would be appreciated.
Replies
Not sure if I fully understand what you are trying to do but maybe this will give you some ideas:
https://live.datatables.net/lehamimo/1/edit
It uses
cell().data()
to update the weight column with the new rank.draw()
is called to executerowCallback
which will add or remove buttons as required.columns.defaultContent
is used to display the initial 1st button.I created a
buttonDefs
variable that contains html attributes for each button based on the HTML5 data attributedata-rank
.This code should allow for defining any amount of buttons without needing to update the code. You just need to update
buttonDefs
with all the buttons.Kevin
Thanks for the feedback. That helped me a little. I reworked my example based on your code and have progressed to the next challenge. The buttons work essentially correctly at the moment. I was trying to have only 1 series visible at a time, but I'm not going to keep chasing that.
My current is issue is setting the correct button visibility on load. I set 3 of the items to have rankings. I think the answer is in the table initialization or a row callback, but I can't seem to get it right.
Here is the updated example: https://live.datatables.net/lehamimo/2/edit
I see you don't like my idea I guess I don't understand your requirements. If I click
1st
in row one buttons 2 and 3 are removed. But also the first button is removed from the other rows. Is this what you are looking for?I would look at using
rowCallback
. Use thedata
parameter to check the rank value then adjust the buttons displayed based on the value. Therow
parameter will contain the HTML for the row. You can use jQuery to manipulate the buttons.Kevin
Just for fun I updated my example to display the number of buttons based on the rank on initial load:
https://live.datatables.net/lehamimo/3/edit
Added this loop to
rowCallback
:Kevin
The behavior I have in mine is the correct way. Each item can only have 1 ranking and once that ranking is used, no other item should be able to select it. Unless that ranking is un-selected or the whole table is reset.
Looking at your code again, I don't think I understand some of the behavior. The rank of the button is what the weight should be, i.e. if i click 1st, the weight should be 1.
You are handling the buttons differently than in my example. I haven't fully digested what your code is doing either - I think you are adding a class to hide the buttons. However I suggest using
rowCallback
to keep the button display updated based on the rank/weight.If you opt for
rowCallback
then I would remove thecolumns.render
function as it will compete and maybe overwrite the classes you add in your event handlers. UsedefaultContent
to either display the first button or an empty string.Remove the
.draw()
from this statement:You are getting the API instance for that cell so you don't need to use
draw()
as nothing has been updated. You are callingdraw()
in the next statement withcall().data()
to updated the cell.Kevin
I spent about a day and a half with ChatGPT on this and had great success that I wanted to share and see if the community had any further refinements. I was amazed by ChatGPT, it actually added capability beyond what I was originally looking for it to accomplish. Recap on the challenge: Create a table that allows the user to rank the items. A rank can only be used once and the ranks must be applied in order. In addition to the rankings, it calculates a normalized weight.
The comments and explanations were all written by ChatGPT.
https://live.datatables.net/hebayabe/1/edit?html,js,output
Glad you got it working the way you want. If you have a large table you might want to move the
drawCallback
code intorowCallback
. This way, instead of iterating all the rows indrawCallback
only the rows shown on the page will be iterated inrowCallback
. No need to change it if you aren't seeing performance issues.If you wanted to use Datatables API's you could replace the for loop in
drawCallback
withrows().every()
and userow().node()
to set thevar code
variable. However if what you have works no need to change itKevin
just to follow up, i implemented your second suggestion:
drawCallback: function () {
const table = $(tableName).DataTable();
const rankings = Object.keys(dictionary);
const firstNullRankIndex = rankings.findIndex((rank) => dictionary[rank].ID === null);
Out of curiosity, i asked ChatGPT about moving drawCallBack code into rowCallback and it actually said don't do that. I thought that was amusing. Here is the response I got:
In DataTables, rowCallback and drawCallback serve different purposes:
rowCallback is called once for every row when it is being created. It gives you a chance to manipulate the row's element before it is added to the table.
drawCallback is called every time the table is redrawn. It is not only called after initial draw, but also every time the table is paginated or the data is sorted or filtered.
In your drawCallback, you are looping over each row in the table and adjusting classes based on some conditions. This can't be done in rowCallback because rowCallback is called as each row is created, and at that time the other rows don't exist yet, so you can't make decisions based on their state.
You might be able to optimize the drawCallback function itself, but there isn't an obvious way to combine it with rowCallback.
That is not entirely correct. What is being described is
createdRow
.rowCallback
is also used for manipulating the row but it is called for each draw. So if you need to add classes or whatever once and the data doesn't change usecreatedRow
. But if the data can change then userowCallback
as it is called each table draw.Since you aren't manipulating the row data only the row it is more efficient to iterate only the rows being displayed. You could do the same by using
selector-modifier
of{page: 'current'}
in yourrows().every()
loop.Kevin