Button visibility based on ranking items

Button visibility based on ranking items

rogueldr6rogueldr6 Posts: 8Questions: 2Answers: 0

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

  • kthorngrenkthorngren Posts: 21,337Questions: 26Answers: 4,954

    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 execute rowCallback 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 attribute data-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

  • rogueldr6rogueldr6 Posts: 8Questions: 2Answers: 0

    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

  • kthorngrenkthorngren Posts: 21,337Questions: 26Answers: 4,954

    I see you don't like my idea :smile: 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 the data parameter to check the rank value then adjust the buttons displayed based on the value. The row parameter will contain the HTML for the row. You can use jQuery to manipulate the buttons.

    Kevin

  • kthorngrenkthorngren Posts: 21,337Questions: 26Answers: 4,954

    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:

            // See if we need to add a button
            if ( weight > buttons.length ) {
              
              for (i=buttons.length + 1; i<=weight; i++) {
            
                var buttonDef = buttonDefs[i];
                $(buttons.parent()).append('<button class="btn ' + buttonDef.classes + ' rank-btn" data-rank="' + weight + '">' + buttonDef.text + '</button>');
              }
    

    Kevin

  • rogueldr6rogueldr6 Posts: 8Questions: 2Answers: 0

    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.

  • kthorngrenkthorngren Posts: 21,337Questions: 26Answers: 4,954

    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 the columns.render function as it will compete and maybe overwrite the classes you add in your event handlers. Use defaultContent to either display the first button or an empty string.

    Remove the .draw() from this statement:

    var cell = table.cell(row, 2).draw();
    

    You are getting the API instance for that cell so you don't need to use draw() as nothing has been updated. You are calling draw() in the next statement with call().data() to updated the cell.

    Kevin

  • rogueldr6rogueldr6 Posts: 8Questions: 2Answers: 0

    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

  • kthorngrenkthorngren Posts: 21,337Questions: 26Answers: 4,954

    Glad you got it working the way you want. If you have a large table you might want to move the drawCallback code into rowCallback. This way, instead of iterating all the rows in drawCallback only the rows shown on the page will be iterated in rowCallback. 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 with rows().every() and use row().node() to set the var code variable. However if what you have works no need to change it :smile:

    Kevin

  • rogueldr6rogueldr6 Posts: 8Questions: 2Answers: 0

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

                table.rows().every(function () {
                    const currentRow = $(this.node());
                    const code = parseInt(currentRow.find('td:first').text());
    
                    rankings.forEach((rank) => {
                        const rankElement = currentRow.find(`.${rank}`);
                        if (dictionary[rank].ID === code || (rank === rankings[firstNullRankIndex] && !currentRow.hasClass('selected'))) {
                            rankElement.removeClass('d-none');
                        } else {
                            rankElement.addClass('d-none');
                        }
                    });
                });
            }
    

    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.

  • kthorngrenkthorngren Posts: 21,337Questions: 26Answers: 4,954

    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 use createdRow. But if the data can change then use rowCallback 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 your rows().every() loop.

    Kevin

Sign In or Register to comment.