In April last year, I saw a google doc with screenshots of our tables on Leaguepedia cut and pasted in MSPaint or the like so that some of the columns were removed, to make the table artificially narrower. “Ahhh,” I thought, “If only we had a way of hiding some columns on the wiki so that you could just screenshot what you wanted!” And then I realized I could in fact build that. So I did.
I’m writing this blog post about eight months after having built this feature because I think it’s a pretty cool feature that a lot of wikis could adapt, but I may have forgotten a couple details. Well, one detail. I really don’t remember the specifics of how the (truly unintuitive) widget syntax works. So I’m going to casually gloss over that and leave it as an exercise to the reader. Enjoy, reader!
You can play with this example at Faker’s match history page.
The high-level overview is that I’m going to make use of the
nth-of-type selector to pick which cells to show-hide when a user selects or un-selects a checkbox.
All of the boxes start out selected, and all of the columns start out shown. If you then un-select the 5th checkbox, I apply a class called
column-show-hide-hidden to each cell in the 5th column. If you re-select the 5th checkbox, I remove this class. The class has one rule, which is
display:none!important;. This is one of the few cases when
!important; is actually appropriate.
(Why not an inline style? Because maybe some cells had an inline
display:none; for another reason unrelated to this toggle, for example an entire row could be
display:none;, and I don’t want to mess with that. It’s cleaner to use classes.)
Shortly after I created the MVP, I realized I also wanted a button to reset the state, so I added a “Show all” button as well.
And that’s really all there is to it from a conceptual point of view; everything else is implementation details.
This is by far the easiest part.
You can find this css at MediaWiki:Gadget-toggles.css.
I have two events. The first is for the checkboxes:
In case an event causes ResourceLoader to refire, I first turn off all event handlers on my selector.
As for the logic of the event handler itself, I have two indices:
- The first is a table index, since I might have more than one table on the page that’s capable of having its columns shown or hidden via these checkboxes. So I’m going to put a parent div around this entire thing with an attribute called
data-table-index, and track this through a global variable on the page (using the
- Then my column index is going to be tracked through the
nameindex on each checkbox input element.
I have two selectors, one with
> * > in between
tr and one without that, just because MediaWiki likes to do that.
I’m skipping the show-hide on anything with a class of
colspan-cell; I add that class in my
HtmlUtil module any time I construct a cell with a colspan. (This is one of the reasons it’s so important and useful to have utility module wrappers for literally every circumstance; you can rely on certain conventions like this.)
Then I concatenate the two selectors into a list, and my event just toggles the class
column-show-hide-hidden any time I click one of these checkboxes.
Show all selector
The “show all” button selector is very similar:
Note that I’m always removing the class instead of toggling it (removing the
display:none; shows the element), and I don’t have any
:nth-of-type in my selector, so it applies to all cells.
Now let’s build the HTML for our form input. Going into this project, I didn’t know any of the widget php syntax. Eight months later, I have once again forgotten it all. Here are some useful documentation pages:
- Extension:Widgets on mediawiki.org
- smarty.net, the PHP templating engine used by widgets
- Escape modifiers
In the first line, I’m making our container
div, with the right class and the
data-table-index attribute provided by the parameter
table. I’m also escaping the parameter so that you can’t pass in arbitrary code.
I then create the show-all button, which is just static HTML, a button with the right class.
After that, I need to loop over all of my columns and print the checkboxes with the proper
id must be globally unique in the entire page, not just this one instance of the widget, so the table index is included as part of the
id in addition to the column index.
I read the docs and did a lot of trial and error to get this to work. As I mentioned in the introduction, I don’t really remember exactly how the loop syntax works anymore.
As an aside: note that nowhere in “important” fields am I using the display name of the column (
name attribute is given by the column numerical index, and it’s just the label’s display text that uses the column name. This fact is important because it’s possible in some cases for two column labels to have the same name. For example, in the tournament player statistics table,
G is reused to mean both “Games” and “Gold” (with title attributes explaining the two meanings). Of course, the very existence of the title attributes (and also spaces!) also makes using words inconvenient here, as I’d have to do a bunch of escaping etc.
The takeaway is: any time you’re creating “hidden” attributes, ids, etc, go with automatically-incrementing numerical keys instead of things named by users if you can.
Finally, we need to call the widget from Lua. I wrote Module:ColumnShowHide with two public functions,
The important thing that enables me to “just do this” is that I always, always, always have an array containing all of my column names. This fact may seem like not a big deal, but it’s a big deal. Why? Because I can reuse this array:
- to make my actual list of headings and
- to send it to the widget in order to print all of these nice column-toggle labels.
(Actually (2) happens before (1), but the order of intentionality is (1) then (2).)
This way, the two sets of things will always match.
(Incidentally, it’s also mandatory to use the
mw.html library (or a custom alternative that works with objects) instead of just working with raw HTML strings if you want to do something like this in a remotely-sane manner.)
Module:ColumnShowHide from another module (in this example,
MatchHistoryPlayer, the module I showed in the introduction) looks like this:
And here’s the code for
See how I use
And then in
Module:MatchHistoryPlayer, I used the following function to print my actual column labels:
(I have a function in
Module:HtmlUtil that I’d normally call directly to print headings, but with a settings file my headings table isn’t formatted exactly right for that function to get the right classes.)
Anyway, most of
Module:ColumnShowHide is constructing the arguments in the exact format that the widget wants.
The i18n file has one line:
Thanks to some guarantees about the HTML structure of my pages that I can rely on from my consistent use of utility wrappers in Lua, I’m able to have an extremely simple interface to create column show-hide buttons at the top of any data table I create; just two functions have to be called from the module I’m writing, and everything else happens magically behind the scenes.
The components that go into this are:
- A css class - never manipulate inline styles!
- Two event handlers that work using
nth-of-typeselectors, and check to make sure they’re within the correct parent element on the page via a global index in case the page holds two or more applicable tables.
- A widget, so that form elements are available.
- And a Lua module, to create the interface from code to the widget. This makes use of a singleton state via the
VariablesLuaextension to manage that global table index and handles indexing and formatting the column names within each table for the widgets extension.