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.
(Note: All code in this post was originally written by me for Leaguepedia and is licensed under CC BY-SA 3.0.)
Overview
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.
CSS
This is by far the easiest part.
|
|
Yay, hooray!
You can find this css at MediaWiki:Gadget-toggles.css.
JavaScript
Checkbox selector
I have two events. The first is for the checkboxes:
The code
|
|
Walkthrough
|
|
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 theLuaVariables
extension). - Then my column index is going to be tracked through the
name
index on each checkbox input element.
|
|
I have two selectors, one with > * >
in between table
and 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.
Widget
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
, class
, and name
. The 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 ($col
). The 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.
Lua
Finally, we need to call the widget from Lua. I wrote Module:ColumnShowHide with two public functions, printToggles
and printTableClass
.
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.)
Calling 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 Module:ColumnShowHide
:
|
|
See how I use COLUMNS
here?
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:
|
|
Conclusion
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-type
selectors, 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
VariablesLua
extension to manage that global table index and handles indexing and formatting the column names within each table for the widgets extension.