Org-mode can nicely export tables into html customly. However, these exported tables cannot be sorted or filtered (i.e. are not interactive). Would it be possible to make these tables sortable and filterable?

  • AFAIK, these things are all provided by CSS and Javascript which is orthogonal to what Org mode provides. You can use things like #+HTML_HEAD: to link in arbitrary CSS and JS to your exported HTML file, but you'll have to write the CSS and JS yourself (or more likely, crib from one of the many examples on the web - see e.g. How to sort a table). – NickD Nov 19 '20 at 16:38
  • Indeed the job would have to be done by CSS or JS. Some JS libraries like slickgrid.net turn data sources into interactive tables. I was wondering if someone did provide such a way of exporting org mode tables. I may be could have done it myself if I did know a bit JS. – crocefisso Nov 19 '20 at 16:57
  • Meanwhile someone finds a solution to this I thought about converting org tables into csv and then turn the csv into interactive tables with Python (like this for example : dash.plotly.com/datatable/interactivity). – crocefisso Nov 19 '20 at 17:01
  • If you want to go the usual Org mode --> HTML way, you might also need to tweak the exporter (or write a filter, or write a derived exporter) that will add the decorations needed to the table headers (e.g. the <th onclick="sortTable(0)">Name</th> stuff in the link I provided). – NickD Nov 19 '20 at 17:54
  • I'm trying to enable sorting through the js code you mentioned and #+HTML_HEAD: but it doesn't work. Could you provide me with extended explanations? – crocefisso Nov 19 '20 at 19:06

The standard HTML table is not interactive, you need JavaScript to add these interactive features.

The following uses the DataTables JS library to add search and filter. Save it to an org file then C-c C-e h o to view the HTML in browser.

#+HTML_HEAD: <link rel="stylesheet" href="https://cdn.datatables.net/1.10.22/css/jquery.dataTables.min.css">

#+NAME: several emacs packages
| Name               |       Version | Status    | Source | Summary                                        |
| origami            | 20200331.1019 | available | melpa  | Flexible text folding                          |
| number             | 20170901.1312 | available | melpa  | Working with numbers at point.                 |
| helm-lib-babel     | 20180510.1324 | available | melpa  | helm insertion of babel function references    |
| org-snooze         | 20181229.1424 | available | melpa  | Snooze your code, doc and feed                 |
| assess             | 20200211.1817 | installed | n/a    | Test support functions                         |
| mocker             |  20150917.154 | available | melpa  | mocking framework for emacs                    |
| mediawiki          | 20200718.1529 | installed | n/a    | mediawiki frontend                             |
| unicode-math-input | 20190813.1436 | available | melpa  | Insert Unicode math symbols using TeX notation |
| hgrc-mode          | 20150409.2043 | available | melpa  | major mode for editing hgrc files              |
| abs-mode           |  20201021.958 | available | melpa  | Major mode for the modeling language Abs       |

#+begin_export html
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.datatables.net/1.10.22/js/jquery.dataTables.min.js"></script>
<script> $("table").DataTable(); </script>

You need basic HTML and JS knowledge to know how to use the library.

  1. the link tag is to load CSS, and the first two script tags are to load JS.
  2. $("table") is a JS function call which selects all HTML tables, and .DataTable() is to initialize Datatable.

Customization is performed by defining options in the $().DataTable() constructor - for example, in the following code the pageLength and order options are used to show 18 rows per page and the default sorting to have the 2d column sorted in ascending order.

"pageLength": 18,
"order": [1,'asc']

Full range of options is documented here.

  • Thanks, your solution works perfectly. But what worries me is that I can't make it work when I try to store the CSS and JS files locally. So how is one supposed to modify the JS or CSS code if the files aren't editable (besides their poor readability)? – crocefisso Nov 20 '20 at 10:36
  • @crocefisso You need to change the href (for css) and src (for js) attributes to point to your file. The "min" in jquery.min.js means it is minified, remove it from the filename, you'll get the pretty version, i.e., cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.js. You're not supposed to edit them directly, you can write your own CSS and JS code to overwrite their behavior. – xuchunyang Nov 20 '20 at 11:17
  • everything works fine now thanks, no more worries :-) – crocefisso Nov 20 '20 at 11:31

Here's a proof of concept, along the lines described in the comments. There are several things that need to be done to it in order to turn it into a "real" solution, which I will describe at the end of the answer. In particular, it is NOT a complete solution: it's missing the part where Org mode produces the HTML that is needed for this functionality. Instead, this last part requires manual tweaking of the HTML - for now.

Here's the Org mode file I used:

#+HTML_HEAD: <script src="./foo.js"></script>

* foo

#+name: myTable
| name  | city          | state          |
| Alice | Boston        | Massachussetts |
| Bob   | San Francisco | California     |
| Ted   | Chicago       | Illinois       |
| Carol | New York      | New York       |

There are two things of note here that I will explain in the rest of the post:

  • the #+HTML_HEAD: line
  • the #+name: line

If you start with just the table and none of the extra stuff and you export to HTML, you will find the following HTML in the output file (with most of it elided to save space):

<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">

<col  class="org-left" />

<col  class="org-left" />

<col  class="org-left" />
<th scope="col" class="org-left">name</th>
<th scope="col" class="org-left">city</th>
<th scope="col" class="org-left">state</th>

The first problem here is that in order to manipulate the table you need to be able to get a handle on it. The Document Object Model (DOM) specifies these things and you'll need to learn a bit about it in order to understand JS code that manipulates elements (e.g. tables), but it's beyond what I can cover here. Suffice it to say that the table should have an id property which will allow a JS program to get a handle on it.

We can get an id tacked on if we name the table: that's what the #+name: line does. If you add that line to the file and export again, you will see that the <table ...> tag has changed:

<table id="org994c1a3" border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">

We get an id but it's something that Org mode produces and there is no easy way to predict what it is going to be. We need the id to be something well-known that we can use in other contexts, e.g. the name we gave to the table: myTable. Fortunately, there is an option for that: org-html-prefer-user-labels which, if set to t, produces an id with the actual value of the #+name: keyword. You can customize the variable - or you can set it in your init file:

(eval-after-load "ox-html" (setq org-html-prefer-user-labels t))

Restart your emacs and check the value with C-h v org-html-prefer-user-labels to make sure it is set.

If you now export again, the <table ...> line looks like this:

<table id="myTable" border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">

and we can get to the the table using the id myTable.

Now for the #+HTML_HEAD: line which addresses the second problem: providing a JS file containing the sorting function to be used for sorting the table by column.

The #+HTML_HEAD: keyword adds its value literally into the HTML file's header, so when the HTML file is transferred from the server to the browser, the <script src="./foo.js></script> line will be part of it. When the browser parses the file it will download the file foo.js from the same directory as the HTML file and have its JS engine compile it and remember the function(s) defined in it.

In this case, foo.js contains a single function, sortTable, cribbed from the How to sort a table link I mentioned in a comment. I just cut-and-pasted the definition of it into a local file foo.js: it's the one from the Sort Table by Clicking the Headers section of that link.

The sortTable function takes a single argument, the column number n, and sorts the table by that column, alternating ascending and descending order on every click. It needs to know which table it applies to and if you look at the function, it hardwires the name in its implementation (I changed it from their myTable2 to myTable to match the name of the table above).

Finally, here comes the manual tweaking that is required to tie the clicks on the table header with the sorting function. In the exported HTML file, find the part that looks like this:

<th scope="col" class="org-left">name</th>
<th scope="col" class="org-left">city</th>
<th scope="col" class="org-left">state</th>

and modify it to look like this:

<th onclick="sortTable(0)" scope="col" class="org-left">name</th>
<th onclick="sortTable(1)" scope="col" class="org-left">city</th>
<th onclick="sortTable(2)" scope="col" class="org-left">state</th>

Save the file and then visit it in the browser, using a file:///..../foo.html URL. Or you can copy the HTML file AND the JS file to some place on the server and use an http://.../foo.html URL. Then click on each column of the table header and watch it sort.

OK, this is a proof-of-concept, so manual tweaking of the output is allowed :-), but in general you will want that to happen automatically. I hope the exercise above is useful, so you can understand what you need to do.

There is one change that is easy to make: modify the sortTable function to take the id of the table as an argument, so that the name of the table is not hardwired into the function. Instead, you specify the table when you set the onclick handler:

<th onclick="sortTable(0, 'myTable')" scope="col" class="org-left">name</th>

That way, you can use the same function for any table.

The final piece needed is to have Org mode produce the <th onclick=...> tags without you having to manually add them. That can be done by using a filter to postprocess the HTML output, but if I had to do it, I think I would it with a derived exporter: start with the HTML exporter and then modify the org-html-table function to add the onclick property appropriately. For now, that's left as an exercise for the interested reader.

  • Thanks a lot, very illuminating. I'll try to do the exercise and post the solution if I find it. – crocefisso Nov 20 '20 at 10:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.