Tabulator Dash Component

Folks I found myself needing functionality from Tabulator (fantastic data table project) http://tabulator.info/

  • Grouping of rows under a header
  • Incase-sensitive searching
  • Custom filters

I found Tabulator had a react port, and went ahead and made a dash version, providing callbacks for updating data / columns, and handling row clicks.

It meets my current needs, and i’m learning as I’m going but it might be of use to others.
And willing to take input / requests if there’s a desire to add more tabulator functionality.

Details : GitHub - preftech/dash-tabulator: Tabulator component for Dash Plotly
Pypi: pip install dash_tabulator

5 Likes

Very sweet @pjaol and thanks for sharing :tada:

I love your note in the readme:

This readme is probably longer than the code, due to the work of those individuals!

Yes! That’s the power of React and the Dash plugin system! :raised_hands:

Here are some screenshots for those that are just passing through. Definitely some nice features that aren’t covered yet in dash_table.DataTable

thanks @chriddyp, took a bit of yelling at the screen and going what do you mean i can’t concatenate a string that. But got it working.

I think Tabulator would definitely complement the fantastic work in Dash / Plotly.
I’m also taking my company down the path of contributing back to the open source world with this being our first of hopefully many projects.
There’s definitely ways to improve this code, and I would encourage anyone interested to take a look and make suggestions.

This has worked beautifully for me so far.

“Page Click” callbacks would be wonderful so I can implement server-side pagination on large datasets.

@crachel it looks like it’s possible with remote pagination http://tabulator.info/docs/4.4/page#remote
And it seems react-tabulator can do it
https://github.com/ngduc/react-tabulator/blob/master/src/ReactTabulatorExample.tsx#L93-L131

What I might need help in understanding is how to tie dash to that
ajaxURL and just use ajaxResponse in the component to format the data

Any ideas?

Hi @pjaol

Thanks so much for making Tabulator available as Dash component. I really like the header filters.

I’ve been able to make lots of other Tabulator features work in my app too – as long as they are predefined. But I haven’t been able to figure out how to make things work that require javascript. For example, I see how to add conditional formatting in the Tabulator documentation, but how would I do this in Dash? I tried a bunch of different things, but I don’t know enough js yet. (I also tried yelling at the screen, since you said that worked for you, but that didn’t work either :laughing: )

# Setup some columns 
# This is the same as if you were using tabulator directly in js 
columns = [
                {"formatter":"printIcon",  "width": 150, "hozAlign":"center"},
                { "title": "Name", "field": "name", "width": 150, "headerFilter":True},


         # If I wanted to use this conditional formatting for the "Name" columns, how would I do it?

                #{"title":"Name", field:"name", "width":150, "formatter":function(cell, formatterParams){
                #    var value = cell.getValue();
                #    if(value.indexOf("o") > 0){
                #        return "<span style='color:red; font-weight:bold;'>" + value + "</span>";
                #    }else{
                #        return value;
                #    }
                #}},


                { "title": "Age", "field": "age", "hozAlign": "left", "formatter": "progress" },
                { "title": "Favourite Color", "field": "col", "headerFilter":True },
                { "title": "Date Of Birth", "field": "dob", "hozAlign": "center" },
                { "title": "Rating", "field": "rating", "hozAlign": "center", "formatter": "star" },
                { "title": "Passed?", "field": "passed", "hozAlign": "center", "formatter": "tickCross" }
              ]

I would also like to add a button to clear the filters, and get the filters to use in other callbacks. I see it’s available: http://tabulator.info/docs/4.7/filter

table.clearFilters(true)
var headerFilters = table.getHeaderFilters();

Is it possible to make this work with Dash? If so, could you provide an example?

Thanks again for sharing this component – I appreciate your contribution!

1 Like

@pjaol this looks fantastic! Is it possible to add multi-row selection using checkboxes? If so, how would I modify the sample usage app to support this?

Apologies I’ve been a little tied up with work, I’ll take a look this week at it.
I suspect it’s just a case of extending the react component to include a button to do so
Similar to the download button

I can probably include that if you don’t want to extend it, whats the best way to setup the css for folks to style the buttons?

@cufflink
There is a checkbox option for row-formatters http://tabulator.info/examples/4.6#selectable-tick
Where you have to provide a formatter in the columns array

column=[
               {formatter:"rowSelection"... cellClick:function(e,cell){....} }, 
               {'title':"......."},
               {'title':"......."},

However you should probably look at how to extend the component in order to provide your own function to handling the select method.
There may be an option to have a global method you can import in

external_scripts = ['https://oss.sheetjs.com/sheetjs/xlsx.full.min.js', '/js/mycustom.js']
app = dash.Dash(__name__, external_scripts=external_scripts, external_stylesheets=external_stylesheets)

Thanks for your response. I suspected that the component would need to be extended, but I haven’t learned how to do that yet.(I’ve tried but got stuck on some errors I couldn’t fix)

For my purposes, it’s not necessary to incorporate a button - it’s easy enough to use a dash component for that. If I could get access to the filter data in a callback - just like the data, columns, and rowClicked parameter, I could make it work.

Have you tried yelling louder at the screen again? (The 2nd / 3rd time sometimes works for me)
I’ll take a look, I think having an external global set of functions might make it easier for JS callbacks

For python filtering I think it’s easier to add reusable features in the code - but will look to see if there’s an easy way for folks to extend or customize and contribute back.
Honestly the hardest part is figuring out the project setup, how callbacks work, and where the code is.

Ann Marie for your use case you are looking at 2 parts?

  1. Filter data python side based on header filters
  2. Ability to clear header filters client side

Haha - there has been plenty of yelling going on here and some bad language too :blush:

I’ve gone through all the documentation on how to do custom components in Dash, and if I could get my environment set up right, I’m sure I’d be able to extend Tabulator. I’ve spent a lot of time with the Tabulator docs too, so I think I understand how things work there. I’m sure I’ll get it figured out eventually and then I’d be delighted to contribute. I’d also like to learn more about the external global methods.

Tabulator does a great job with the filtering, so I don’t need to do that client side. In my application I have about 10 columns with filters, and it would be a nice UI feature to be able to reset the filters without the user having to delete each one.

But the most important thing for me is to be able to update the filter in a callback. When I update columns the filter disappears, but it’s not really gone and it still filters the data according to what was previously in the filter. Very weird. A user would have to remember what column had the filter and hit delete in what looks like an empty field. If I can re-populate the filter when I update the columns, it would work. In the meantime, I’m back to a dash table.

Hi pjaol

Many thanks for making tabulator available in dash. I wonder if you could help me with setting the background color of a cell such that the entire cell is colored but the text is still visible? I have failed so far.

What I have tried so far:
Options 1: Use plain html:

columns = [
                { "title": "Name", "field": "name", "width": 150, "headerFilter":True},
                { "title": "Age", "field": "age", "hozAlign": "left", "formatter": "progress" },
                { "title": "Favourite Color", "field": "col", "headerFilter":True, "formatter":"html"},
                { "title": "Date Of Birth", "field": "dob", "hozAlign": "center" },
                { "title": "Rating", "field": "rating", "hozAlign": "center", "formatter": "star" },
                { "title": "Passed?", "field": "passed", "hozAlign": "center", "formatter": "tickCross" }
              ]

data = [
                {"id":1, "name":"Oli Bob", "age":"12", "col":"<style>body{margin:0px} p{background-color: rgb(255,0,0); margin: 0;border: 0}</style> <body><p>Red text </p></body>", "dob":"",},
                {"id":2, "name":"Mary May", "age":"1", "col":"blue", "dob":"14/05/1982"},
                {"id":3, "name":"Christine Lobowski", "age":"42", "col":"green", "dob":"22/05/1982"},
                {"id":4, "name":"Brendon Philips", "age":"125", "col":"orange", "dob":"01/08/1980"},
                {"id":5, "name":"Margret Marmajuke", "age":"16", "col":"yellow", "dob":"31/01/1999"},
                {"id":6, "name":"Fred Savage", "age":"16", "col":"yellow", "rating":"1", "dob":"31/01/1999"},
                {"id":6, "name":"Brie Larson", "age":"30", "col":"blue", "rating":"1", "dob":"31/01/1999"},
              ]

However, this does not color the entire cell. Fiddling around with margin did not help either:
image

Option 2: The color formater. Here the problem is that the text is not visible
, and I cannot set the font color:

columns = [

                { "title": "Name", "field": "name", "width": 150, "headerFilter":True},

                { "title": "Age", "field": "age", "hozAlign": "left", "formatter": "progress" },

                { "title": "Favourite Color", "field": "col", "headerFilter":True, "formatter":"color"},

                { "title": "Date Of Birth", "field": "dob", "hozAlign": "center" },

                { "title": "Rating", "field": "rating", "hozAlign": "center", "formatter": "star" },

                { "title": "Passed?", "field": "passed", "hozAlign": "center", "formatter": "tickCross" }

              ]


data = [

                {"id":1, "name":"Oli Bob", "age":"12", "col":"red", "dob":"",},

                {"id":2, "name":"Mary May", "age":"1", "col":"blue", "dob":"14/05/1982"},

                {"id":3, "name":"Christine Lobowski", "age":"42", "col":"green", "dob":"22/05/1982"},

                {"id":4, "name":"Brendon Philips", "age":"125", "col":"orange", "dob":"01/08/1980"},

                {"id":5, "name":"Margret Marmajuke", "age":"16", "col":"yellow", "dob":"31/01/1999"},

                {"id":6, "name":"Fred Savage", "age":"16", "col":"yellow", "rating":"1", "dob":"31/01/1999"},

                {"id":6, "name":"Brie Larson", "age":"30", "col":"blue", "rating":"1", "dob":"31/01/1999"},

              ]

image

Option 3: Write a customized formatter in JS:

columns = [
                { "title": "Name", "field": "name", "width": 150, "headerFilter":True},
                { "title": "Age", "field": "age", "hozAlign": "left", "formatter": "progress" },
                { "title": "Favourite Color", "field": "col", "headerFilter":True, "formatter":"function(cell){var col = cell.getValue();cell.getElement().style.backgroundColor = '#A6A6DF';return col}"},
                { "title": "Date Of Birth", "field": "dob", "hozAlign": "center" },
                { "title": "Rating", "field": "rating", "hozAlign": "center", "formatter": "star" },
                { "title": "Passed?", "field": "passed", "hozAlign": "center", "formatter": "tickCross" }
              ]


data = [
                {"id":1, "name":"Oli Bob", "age":"12", "col":"red", "dob":"",},
                {"id":2, "name":"Mary May", "age":"1", "col":"blue", "dob":"14/05/1982"},
                {"id":3, "name":"Christine Lobowski", "age":"42", "col":"green", "dob":"22/05/1982"},
                {"id":4, "name":"Brendon Philips", "age":"125", "col":"orange", "dob":"01/08/1980"},
                {"id":5, "name":"Margret Marmajuke", "age":"16", "col":"yellow", "dob":"31/01/1999"},
                {"id":6, "name":"Fred Savage", "age":"16", "col":"yellow", "rating":"1", "dob":"31/01/1999"},
                {"id":6, "name":"Brie Larson", "age":"30", "col":"blue", "rating":"1", "dob":"31/01/1999"},
              ]

–>Does not work. I guess I cannot pass a JS function at all, or my syntax is wrong.

Could you please let me know if there is a way to solve this?

Many thanks

HI @fair

It’s true you can’t pass a JS function call, but you could try adding this to the css in the assets folder:

.tabulator-row .tabulator-cell {
padding:0;
}

Hi AnnMarieW

Many thanks for your answer. Please apologize my ignorance, but where could I find the assets folder? I only found C:\python\Lib\site-packages\dash_tabulator but there is no CSS file in it.

In order to include custom CSS in your Dash app, create a folder called assets in the same directory as your app (not the dash_tabulator files). Then create a file with the CSS and save it in the assets folder. You can name the file anything you like as long as it ends with .css Then Dash will automatically use this css in your app.

You can see a better explanation and some examples here: https://dash.plotly.com/external-resources

If you haven’t done this before, I recommend that you copy and paste the examples from the tutorial and give it a try to make sure it works for you. It’s actually pretty slick!

This is amazing.

what about the cellEdited callback from tabulator? is it available?

basically if you make a column/cell editable with an editor option in the column definition, this callback is triggered every time an edit is identified and it is of course very important to deal with a database in order to have a physical save of the changes.

another important missing feature (or maybe I’ve just missed it) is the selected cell callback, right now it seems you can only use selected row, while having the possibility to get both coordinates of a cell would definitely help in building interactive actions (e.g. clicking on a cell, a modal is opened).

I strongly believe it’s fairly easy to implement some of these very important functions and with some work this may become a solid alternative to datatables (that seemed a bit bugged and limited to me).

@MrGianlu
I’ve just added cellEdited and dataChanged https://github.com/preftech/dash-tabulator/issues/3

Tabulator has a lot of circular DOM references for browser based usage, but I’ve done what I can to get useful data back in the callback.
Let me know if that works

Hi @AnnMarieW

I think I’m closer to what you want.
I’ve added
Input(‘input’, ‘dataFiltering’) => returns the filters the user has typed in
Input(‘input’, ‘dataFiltered’) => returns the rows filtered and filters the user has typed in

I’ve also added a clear filters button.

I discovered tabulator treats “Header Filters” where user interaction occurs differently to regular filters which are invisible and just filter a table in the background.
So there is a method of table.setHeaderFilter(field, value) that makes it appear.

I was able to make that work with a componetDidUpdate change, but I found that it set it for every single character change in your filters.
So as you’re typing color filter yellow, it would filter the rows and immediate reset to what you initialized the table as.

What it means is that it’s possible to set a filter on the table, but I’d need a couple of use cases to help understand what state needs to be set to not make it a living nightmare.

This is so cool. I’ll give this a try and let you know if I find some limitations, the code looks ok!
Thanks for the nice work, I believe that if you and hopefully other contributors will broaden the functionalities this will become far more popular.

A cool feature missing in dash-table that would eventually move people here around is the possibility to add buttons within cells, this is very common for user interactions and would add a lot of value to this component. Do you need me to add this “issue” in git maybe?