Hide datatable rows by using buttons that are inside the datatable
Hide datatable rows by using buttons that are inside the datatable
I have a data set that's composed of active records and inactive records. Some "names" are active or inactive, and some active "names" have inactive records; simple example below:
name | active
ABC | 1
ABC | 0
DEF | 0
DEF | 0
GHI | 0
GHI | 1
JKL | 1
I have a html table with multiple buttons in the left most column (depending on the type of record... whether it's active or inactive) and the dataset in all the other columns generated w/ python flask templating. Datatables ingests this html and displays the table as desired.
buttons | name | active
b1 b2 b3 | ABC | 1
b3 | ABC | 0
button 1 (b1) is meant to hide/unhide the inactive records associated with the "name"/group. It has an onclick attribute that passes the "name" [and anything else we need/want for this... I'm passing in table ID as I have multiple tables but only 1 table per page so the id may not be needed] to javascript.
Question: What should I write in javascript so that each active record's b1 hides/unhides the desired inactive records? (Ideally, the inactive records are always below the active ones when b1 is clicked, so some sorting by "name" may be needed before records are shown again).
I've looked into parent/child rows (but don't want to rebuild the table) and am hoping for something that doesn't involve AJAX (because this table is a form and cells are inputs/etc. and it seems that AJAX would over complicate it... did I mention I'm new to javascript & DataTables api?). Any advice would be most appreciative.
Code
I started with something like this. (I thought if I hid/unhid rows, put in a sort, and then figured out how to navigate to the appropriate page, since my datatable is paginated, that I could somewhat achieve the desired functinality... but I didn't make it pass the hiding functionality):
function inactiveView(name, tableID) {
var clickedButton = event.target
var table = $('#' + tableID).DataTable();
var inactiveNameRows = table.rows().eq( 0 ).filter( function (rowIdx) {
return table.cell( rowIdx, 2 ).data() === 0 && table.cell( rowIdx, 1).data() === name? true : false;
} );
\\ insert amazing code to hide [or unhide] rows
I discovered that .hide() seems to be for only child rows. So I tweaked the above code and attempted to add in a custom filter that selected on the desired columns. A StackOverflow example:
https://stackoverflow.com/questions/30086341/datatable-hide-and-show-rows-based-on-a-button-click-event
$("#hide").click(function() {
$.fn.dataTable.ext.search.push(
function(settings, data, dataIndex) {
return $(table.row(dataIndex).node()).attr('data-user') == 5;
}
);
table.draw();
However, $.fn.dataTable.ext.search.push() [etc] didn't seem to do anything for me when combined with my code. Perhaps I'm not calling the correct api instance???
Libraries
https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js
https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js
https://cdn.datatables.net/plug-ins/1.10.21/sorting/natural.js
This question has accepted answers - jump to:
Answers
The
filter()
API isn't going to affect the rows displayed. It is meant to get the rows that match the filter to use in some in the Javascript. Meaning theinactiveNameRows
variable should have all the roes that match the filter condition to use in Javascript, like maybe selecting them or removing them from the table.Using jQuery
.hide()
would hide the rows but Datatables wouldn't know about it and would provide undesired results when doing other Datatables functions.Your click event might work but is problematic. See this example of it working:
http://live.datatables.net/guganonu/2/edit
Note in the example it pushes the search plugin, draws, then pops the plugin. If you are going to push each time the click event runs then you need to pop it otherwise you will keep pushing the same plugin onto the array. Also notice that this filters the table but if I sort or cause another
draw()
the filter is removed.Depending on what you really want it is best to push all the plugins and use flags to determine how they behave. See this example:
http://live.datatables.net/wavelaxe/1/edit
I can sort, etc and keep the filters the way I want them.
HTH,
Kevin
Thanks, @kthorngren, I think this does help! Here's your code w/ the buttons inside the table
http://live.datatables.net/wahodezu/1/edit?html,css,js,output
Thanks,
D.
Here are a couple examples of buttons in the rows.
This uses
columns.defaultContent
:http://live.datatables.net/xijecupo/1/edit
This uses
columns.render
:http://live.datatables.net/xijecupo/1/edit
The first one is simpler if you don't need/want each button to have its own 'id'.
Kevin
@kthorngren - amazing... thank you!!!
If you have trouble working this out maybe you can build a simple example from one of the above examples or create your own. This way we can see exactly what you want and can provide more precise answers.
Kevin
[blank]
Hi, Kevin @kthorngren .... umm, yeah, I did had trouble working this out the way I wanted hahaha. I started just writing pseudo code & comments so I could at least convey what I'm after in an ideal scenario. Do you mind taking a look?
http://live.datatables.net/nosehoyu/1/edit?html,js,output
Thanks for the example and details, helps a lot. I think this is what you are looking for:
http://live.datatables.net/nosehoyu/3/edit
At a high level I removed the use of the global flag and placed a flag in each row based on the click of the row button. The flag column is visible now so you can see how it behaves but you can use
columns.visible
to hide it.I created a function that first finds all the rows matching the Name of the row clicked. It then sets the flag to 1 to show the inactive rows or 0 to hide. You will want to read these docs:
rows()
androw-selector
. I used therow-selector
as a function to filter the rows to those the match the Name.rows().every()
to loop through all the rows found with the above.row().data()
used this (this.data()
) to both get the row data then set it after making the updates.The second part is to order the table. Used
columns.orderData
to influence the Name column sort by the Active column. The problem with this is they both need to sort in the same order, eitherasc
ordesc
, but this doesn't work with active being1
and inactive being0
. So I created a simple Ordering Plugin to handle this. Effectively it keeps the active order asdesc
no matter which order the Name column is in.Lots to take in. Please post any questions. Hopefully the comments will help explain.
Kevin
Also I should mention that I changed your click event selectors to this:
$('#example tbody').on('click', "input[value='+']", function() {
In order for them to work you need to use Delegated Events. Otherwise the
-
event will never be invoked. It will always be the+
event. Plus it solves the problem in this FAQ.Kevin
Wow, thank you @kthorngren !! I'm going to keep going thru this to understand better - but I was wondering, is your version of the code working? When I click the buttons the red/green/etc. changes but it doesn't show/hide rows.
http://live.datatables.net/nosehoyu/5/edit?html,js,output
(same as your code, I just took my comments out)
For some reason it didn't auto save properly - or it could be use error
Here is the working version:
http://live.datatables.net/hoxiwoma/1/edit
Kevin
@kthorngren legendary! Thank you SO much.
If I understand correctly, you coded it so that regardless of a user sorting on the Name column, the active record will always be on top. I realize I may not have explained it as well, but what I was asking (in a perfect world) that this would be the case regardless of the column the user sorts on. i.e. the sorting would only be on active records... the point being that the inactive records would always be next to the respective active record (vs potentially on different pages after a user sorts).
This is not a deal breaker (what you've given me is already great) but thought I'd ask.
i.e. if a user sorted on Start Date in this example, the desired result would have the active/inactive records still being together (unlike screenshot attached):
http://live.datatables.net/hoxiwoma/2/edit?html,js,output
There is
orderFixed
but not sure if that will solve the problem.Another option might be to use Chid Row details. Splitting the active and inactive into two datasets with the active being in the Datatable rows and the inactive in the Child detail rows would remove the inactive from being involved in Datatables functions like searching and sorting.
I would put the inactive in an object with the key being the Name. Then when displaying the child row just get the inactive rows by the key of Name. Can show you if this is of interest.
Kevin
Thanks, Kevin @kthorngren - I think this is great as-is. I found out that giving the user the ability to sort isn't too important (the user has the search functionality), so I'll just take out the default column sorting options all together (but still make the datatable initialize in the order I need). VERY much appreciate your help. Impressed by the quick responses!
Hi, @kthorngren - Based on your great help, I implemented this code example below. What I didn't mention is that there are submit buttons in my datatable (which is a form too), but now this bit of info is especially relevant. Here's a revised code example:
http://live.datatables.net/yipujuvu/6/edit?html,js,output
The code works (hides/unhides rows) which is great, but the issue I'm having now is not all the form buttons work- specifically, the reactivate button in the inactive rows doesn't work in my datatable, even when the inactive row is visible/unhidden. Based on tinkering around, it seems solely to do with the customer filtering extension. When I remove the extension from the code, the reactivate button works (submits a POST request to underlying database).
Any advice on getting all the buttons to submit the desired POST request? I'm hoping there's a slick solution. If not, would pop()-ing this extension every time work?
I guess I'm confused by the example. Admittedly I don't use forms so that may be the cause of my confusion but I don't see any on the page. So, I'm not sure how to help with the buttons nor what you are trying to post. I've always used click events, like this example:
http://live.datatables.net/xijecupo/1/edit
But that doesn't sound like what you want.
You had this comment:
The
columns.type
option is for setting the type for searching and sorting. What are you expecting it to do?If you pop the search plugin then it won't run if there is a table draw, like searching, sorting, paging.
I'm not sure why the hidden rows don't work when they are made visible. Would need to see the issue to offer suggestions.
Kevin
Hi, Kevin @kthorngren - Below is a much more complete code example, followed by a more detailed background info. I've expounded upon the example as I've learned more... plus my question(s) require it as well : )
Background/Recap:
Imagine you have a variety of records, some being inactive and others being active. However, you'd like to present them to the user in a more user-friendly way vs just showing them an entire table to page through. So, you decide to roll the data up, in this case, by "name" with the most recent one first and have "+/-" buttons on applicable rows to unhide/hide detail. You don't want to put these "+/-" buttons on EVERY row as not all names have multiple records... however, both active and inactive records can have multiple records (I use Flask templates to generate this html as this is inside a Python Flask app.)
Also, you give the user the ability to update information in the table by allowing certain cells to be editable. You also wrap each row of the table with
<
form> tags and put other <submit> type buttons (well... your Flask template does this, but you get the idea! As you know, the generated html from the Flask template is then fed to the browser & Datatables api). For active records, you include a button to "modify" and one to "disable". For inactive records you include a button to "reactivate". By pressing these buttons, the specific row values in your table are submitted to a function (not shown) via a POST request which then commits the updates to a database in the underlying app (not shown).
Here's the code example!
http://live.datatables.net/hupihepa/1/edit?html,js,output
I've reflected the above code example's functionality into a page of my app and it's working like the code example, but I've also ran into a couple of issues:
Questions
1) The form submit buttons ("modify, etc.) for records that are always visible work. Yay! However, oddly enough, the buttons for records that aren't always shown don't work... even when the records are visible. While I don't have view functions and a database attached to this example, I think you can see this behavior. Simply press a button of a "lead record" (i.e. the most recent record for a name) and you will get an error message (because it's trying to send data, I suppose). However, unhide any record and try the same button... no error... weird! However, if you delete the custom filter (lines 3 - 7), these non-lead record buttons work. In fact, if you change the custom filter to always show non-lead records and not other records, then the non-lead records work. So it definitely seems to be the customer filter. The buttons are firing the POST request even though there are
<
form> tags wrapped : o
2) Ok, this is probably an easier question. Notice how when you navigate to the 2nd page and click the "+" button to unhide a record, the Datatable updates and puts you back on the first page... which isn't ideal if a user is working with, say, 65 pages of data. They unhide a button on p.25, get kicked back to p. 1 and then have to navigate back to p.25 to see the unhidden record. Totally get why Datatables is doing this but...
I hope I explained it better. Thanks for the help!
Thanks, @kthorngren - I wrote up (what I thought) was a great, detailed explanation & background with my code example and my couple of questions, submitted it, and then lost it in trying to edit it. hahahaha sob sob sooobbbbb It took forever. Anyway, at least I still have the code example here:
http://live.datatables.net/sopurugi/1/edit?html,js,output
long-winded explanation aside, my questions are:
1) Is there a way to have the user return to the page of the row whose "+/-" button they pressed vs the first page when the datatable updates? (ex. if you navigate to page 2 in the example, press the "+/-" button, you are kicked back to the first page... totally get it, but not exactly ideal for my user... they may unhide a row on p.40 of a datatable and then have to navigate back to p.40 to see the unhidden row. Fyi, the row order shouldn't change based on clicking a "+/-" button in my datatable... the data gets sorted before going to a Flask template which then kicks out the html to the browser & datatable api.
2) Many of the cells in my Flask app's datatable are editable, and each row has a
<
form> tag wrapped around it (like in this code example... though the cells here aren't editable, you still get the idea : ). There are "submit" type buttons (named modify, reactivate, etc.) which submit the data to a function (not shown) via POST request and update the underlying database (not shown). In the code example, you can tell that it's trying to doing something/send data/etc. by pressing one of the buttons of the rows that are _always _visible. You'll get an error. This is actually good. It's doing something. However, unhide one of the records and press a "reactivate" button and nothing happens. Somehow the form/POST request is not doing anything. Seems very weird to me. If you delete lines 3-7 (the custom filter) everything works as expected. If you change the custom filter to now show the rows that were once hidden all the time, their buttons work and other rows don't. As you pointed out, I can't pop() the extension or else the pagination won't work. Any advice on how to fix?
Thanks for your help!!!
The
draw()
API can take parameters that affect this behavior. Youfalse
, ietable.draw(false);
, to stay on the same page. Here is the updated example:http://live.datatables.net/sopurugi/2/edit
It is a strange problem. I updated the example with a button that activates the search plugin. Probably need @allan or @colin to help understand. Something different happens when the rows are removed using the search plugin versus normal table filtering or paging. Try these steps:
Reactivate
button of the 4th row,Ashton Cox
- the form is submitted.zip
for example.Reactivate
button of the 4th row,Ashton Cox
- the form is submitted.Reactivate
button of the 4th row,Ashton Cox
- the form is submitted.+
next to Ashton Cox to show the 2nd Ashton Cox row.Reactivate
button of the 2ndAshton Cox
row - the form is not submitted.Kevin
Thanks, Kevin @kthorngren - I'll wait and see if @allan or @colin have any suggestions. (I take it those steps 1-9 are for them?)
Here is my code example with the draw(false)... works beautifully!
http://live.datatables.net/sopurugi/3/edit?html,js,output
Thanks for your help!
D.
Yes, so they can recreate the issue.
Kevin
Thanks for the steps, Kevin.
This seems to work here. It looks like the problem is when you write the entire row of data back - not entirely sure why - but if you just write back the cell for the column 9, it looks like it's doing the trick to me.
Suspect writing back the row might be removing some of the HTML, but couldn't see what.
Colin
Its always good to get someone else to look
Kevin
EXCELLENT, thank you @colin @kthorngren !!
Here's the updated version of my code example- it works!
http://live.datatables.net/sopurugi/6/edit?html,js,output
Nice work!
Kevin
Good stuff! and yep, the more the merrier
Colin
@kthorngren - I was able to implement this in my app and it worked beautifully. Thanks again for your help. Though, I did have one big item come up on one of the three tables I implemented this for.
The table in question has ~15K records. Recall that the backend sends data for a single table to a Flask template and then the generated html goes to Datatables. The issue is that the data takes 5 min to load up in the Flask template and more time afterwards to be rendered by Datatables... while throwing unresponsive messages along the way. If the flask template wasn't generating buttons (using an if statement) and adding form tags, etc. it would spit the data out within 5 seconds.
Questions:
-Do you anticipate sending the data in JSON directly to the datatable (i.e. no Flask templates) and having Datatables generate everything to be much much MUCH faster?
-If not, then do you see server-side processing as being a viable solution? (I've read some of the docs on this but still learning.) If so, would the "+/-" buttons be an AJAX call to the backend as well? Am I correct in assuming that I would have to store which [non-lead] row id's were unhidden in the app (because Datatables no longer has any idea)... like a session variable in Flask?
Just trying to get an idea of what's ahead if I need to go this route.
Thanks!
D.
Just saw this: https://datatables.net/examples/ajax/defer_render.html
If I do AJAX instead of using Flask templates, maybe this is the answer vs. server-side processing? (Server-side processing seems more work as I'd have to figure out how the "+/-" buttons would change and which rows would be hidden/unhidden).
I don't believe there are any speed improvement options for a DOM sourced table.
This FAQ provides the speed improvement options you have. Doing this you may be able to simply use
deferRender
to speed up the table. You should be able to generate the HTML in Flask if you wish and return it as part of the JSON response. Or you can move the logic tocolumns.render
which maybe faster withdeferRender
.I would only look at Server Side Processing if
deferRender
doesn't help enough. You Flask script would need to perform sorting, searching and paging operations instead of at the client. Not sure if anyone has published a Flask SSP script but I believe there are some Django SSP scripts on GIthub. Or you can look here for a basic example in PHP.Not sure if I totally understand but the Lead and multiple record flags can be return in the original JSON. I assume the buttons or built based on these and maybe other values you can return in the JSON which you can access using
columns.render
.Here is an example of buttons generated using
columns.render
:http://live.datatables.net/qemodapi/1/edit
Kevin
Thanks, Kevin @kthorngren The FAQ link is quite helpful.
Above, I meant that when the user clicks on a "+" button, they expect to see hidden rows to now show and the button to change colors and values (i.e. become a red "-"). Currently Datatables handles all of this. It has all the data, it uses a custom filter, it "knows" which rows to show (by changing the html) and how to format the buttons, like our example:
http://live.datatables.net/sopurugi/6/edit?html,js,output
But I think all this changes with server-side processing. When you press a "+" button, Datatables makes a request to the backend for more info (some of the same data plus the hidden rows) and probably passes along the row id of the button that's pressed in the data attribute so the server can find the hidden rows. Server finds the same-ish data but includes the additional-once-hidden rows. Datatables takes the new data and changes the color of the "+" button corresponding to the rows that are normally hidden but aren't any more to a "-" button. However, the user goes to another page. Then comes back to the same page. Datatables doesn't remember which rows are unhidden or which button is a red "-". The server has to store this (but you're not going to write to the database for this) so perhaps a session variable or something that keeps a list of record id's that are typically hidden but should be visible in the current user's session. Am I thinking about this correctly? (If so, you can see why I'm trying to avoid server-side processing... sounds like a lot of work!)
I imagine you simply change the Python Flask view function to send data in AJAX format vs. serving up a template. (i.e. no separate script required per se) Am I thinking about this correctly?
Thanks!
D.