The situation: You’re displaying a Cargo query about possible magical companions in a fantasy RPG in a wikitable, and you have a column called PowerLevel
with 5 values:
- Minor
- Common
- Major
- Extreme
- Godly
You want this wikitable to be sortable, but if you sort by the PowerLevel column, it orders like:
- Common
- Extreme
- Godly
- Major
- Minor
How can you fix this?
The current state
If you want to follow along, you can create this example on your own wiki. First, make Template:MagicalCompanion
with this code:
<noinclude>{{#cargo_declare:_table=MagicalCompanions
|Name=String
|PowerLevel=String
|FindLocation=String
|HP=Integer
|Attack=Integer
|Shield=Integer
|Spell=String
}}</noinclude><includeonly>{{#cargo_store:_table=MagicalCompanions
|Name={{{name|}}}
|PowerLevel={{{power_level|}}}
|FindLocation={{{location|}}}
|HP={{{hp|}}}
|Attack={{{attack|}}}
|Shield={{{shield|}}}
|Spell={{{spell|}}}
}}</includeonly>
At any page you like, write this:1
{{MagicalCompanion|name=Kitten|power_level=Minor|location=Alley|hp=25|attack=3|shield=0|spell=Throw Yarn}}
{{MagicalCompanion|name=Cat|power_level=Common|location=House|hp=50|attack=7|shield=2|spell=Spill Water}}
{{MagicalCompanion|name=Linx|power_level=Major|location=Steppe|hp=150|attack=15|shield=10|spell=Stalk}}
{{MagicalCompanion|name=Tiger|power_level=Extreme|location=Forest|hp=200|attack=45|shield=8|spell=Roar}}
{{MagicalCompanion|name=Bastet|power_level=Godly|location=Egypt|hp=500|attack=40|shield=30|spell=Greater Protect}}
And then we’ll query it like this:
{|class="wikitable sortable"
! Name !! Power Level !! Found at !! HP !! Attack !! Shield !! Spell
{{#cargo_query:table=MagicalCompanions
|fields=Name,PowerLevel,FindLocation,HP,Attack,Shield,Spell
|format=template
|template=MagicalCompanionDisplayRow
|named args=yes
}}
|}
Finally, here’s Template:MagicalCompanionDisplayRow
(the first empty line is required):
|-
| {{{Name|}}} || {{{PowerLevel|}}} || {{{FindLocation|}}} || {{{HP|}}} || {{{Attack|}}} || {{{Shield|}}} || {{{Spell|}}}
Go ahead and play with the table you get. You can fix the order by sorting by HP, but let’s assume that’s not always the case, and anyway, that’s pretty user-unfriendly even if it did always work.
data-sort-value
The first piece to fix this is MediaWiki’s data-sort-value
attribute.2 Let’s code an example table by hand to demonstrate:
First, here’s a not-working example:
{| class="wikitable sortable"
! Name !! Power Level
|-
| Kitten || Minor
|-
| Cat || Common
|-
| Linx || Major
|-
| Tiger || Extreme
|-
| Bastet || Godly
|}
Now, let’s fix it with data-sort-value
:
{| class="wikitable sortable"
! Name !! Power Level
|-
| Kitten || data-sort-value="1" | Minor
|-
| Cat || data-sort-value="2" | Common
|-
| Linx || data-sort-value="3" | Major
|-
| Tiger || data-sort-value="4" | Extreme
|-
| Bastet || data-sort-value="5" | Godly
|}
Hooray, it works!
A wrong solution: add a sort number to this table
First, let’s solve this the wrong way (but potentially the least-complicated way). To the Cargo declaration at Template:MagicalCompanion
, we’ll add |PowerLevelValue=Integer
and to the Cargo store we’ll add |PowerLevelValue={{{power_level_value}}}
(remember to recreate the table).
Next, change Template:MagicalCompanionDisplayRow
to this:
|-
| {{{Name|}}} || data-sort-value="{{{PowerLevelValue}}}" | {{{PowerLevel|}}} || {{{FindLocation|}}} || {{{HP|}}} || {{{Attack|}}} || {{{Shield|}}} || {{{Spell|}}}
Then add the correct power_level_value
to each row of data, like so:
{{MagicalCompanion|name=Kitten|power_level=Minor|power_level_value=1|location=Alley|hp=25|attack=3|shield=0|spell=Throw Yarn}}
{{MagicalCompanion|name=Cat|power_level=Common|power_level_value=2|location=House|hp=50|attack=7|shield=2|spell=Spill Water}}
{{MagicalCompanion|name=Linx|power_level=Major|power_level_value=3|location=Steppe|hp=150|attack=15|shield=10|spell=Stalk}}
{{MagicalCompanion|name=Tiger|power_level=Extreme|power_level_value=4|location=Forest|hp=200|attack=45|shield=8|spell=Roar}}
{{MagicalCompanion|name=Bastet|power_level=Godly|power_level_value=5|location=Egypt|hp=500|attack=40|shield=30|spell=Greater Protect}}
And finally update the fields
in your query to include the new value:
|fields=Name,PowerLevel,PowerLevelValue,FindLocation,HP,Attack,Shield,Spell
And now it should work!
Why this solution is wrong
So if it works, what’s wrong? Well, we don’t want to have to store this power_level_value
along with the power_level
. There’s a few reasons:
power_level_value
is not an actual in-game entity. It doesn’t mean anything to players; it’s our own construct just used for sorting data. So our editors might get confused about what to put there or what the right value is.- What if the game gets patched? Maybe we get a new level called
Demigodly
that goes in betweenExtreme
andGodly
. Then we have to either change every instance of apower_level
ofGodly
or we have to use something like4.5
for this new value. Both of these options are pretty bad, especially when you consider that users have to keep track of which number means which value, so they’re likely to make mistakes. - It’s also just, kinda annoying to have to do this. We can compute the order directly from the value, so why should we have to enter both things?
A slightly better solution: switch statement
Okay, let’s first get rid of all of the power_level_value
s from our storing template. Instead of making the user enter this, what if we compute it in the storing template?
Let’s edit the cargo_store
in Template:MagicalCompanion
as follows:
{{#cargo_store:_table=MagicalCompanions
|Name={{{name|}}}
|PowerLevel={{{power_level|}}}
|PowerLevelValue={{#switch:{{{power_level}}}
|Minor=1
|Common=2
|Major=3
|Extreme=4
|Godly=5
}}
|FindLocation={{{location|}}}
|HP={{{hp|}}}
|Attack={{{attack|}}}
|Shield={{{shield|}}}
|Spell={{{spell|}}}
}}
No need to recreate any table this time, but go ahead and save the template, save the storing data, purge cache on the query. Everything should still work! If you want to be sure the cache is refreshed, try reordering Common
and Major
in the switch statement (so |Common=3|Major=2
) and verify it sorts in this wrong order instead.
Why this solution is still imperfect
Turns out, I haven’t told you the full scope of how this game works. There are also Weapon
s, and each weapon has a power level too! So, I’ll need a copy of this same switch statement at Template:Weapon
. Oh, and also there are shrines you can visit, and each one of these has a power level too.
And then I patch the game again to add a new a new power level. Now there’s three places you have to change the PowerLevelValue
at. That’s more than one, ugh.
Also, then I patch the game again and add a classification system to the bosses that you fight. But someone else is doing the development now, and they don’t realize they need to store PowerLevelValue
in the Bosses
table, so everything breaks.
In general, this is a “separation of concerns” issue. The logic for storing MagicalCompanions data shouldn’t need to know anything about the order in which power levels display; that’s a PowerLevel concern.
But you can do it if you want
Keep reading, but if you get confused by the next section and really think you can’t manage it, this solution is workable. Go ahead and use it if you need to.
The correct solution: A new table
Let’s make a new table called PowerLevels
. Put it at Template:PowerLevel
and give it this code:
<noinclude>{{#cargo_declare:_table=PowerLevels
|Name=String
|SortOrder=Integer
}}</noinclude><includeonly>{{#cargo_store:_table=PowerLevels
|Name={{{name|}}}
|SortOrder={{{order|}}}
}}</includeonly>
Remember to create the table.
Now store some data:
{{PowerLevel|name=Minor|order=1}}
{{PowerLevel|name=Common|order=2}}
{{PowerLevel|name=Major|order=3}}
{{PowerLevel|name=Extreme|order=4}}
{{PowerLevel|name=Godly|order=5}}
And finally, let’s edit our query to use the new data:
{|class="wikitable sortable"
! Name !! Power Level !! Found at !! HP !! Attack !! Shield !! Spell
{{#cargo_query:tables=MagicalCompanions=MC,PowerLevels=PL
|join on=MC.PowerLevel=PL.Name
|fields=MC.Name,MC.PowerLevel,PL.SortOrder=PowerLevelValue,MC.FindLocation,MC.HP,MC.Attack,MC.Shield,MC.Spell
|format=template
|template=MagicalCompanionDisplayRow
|named args=yes
}}
|}
Check it out - everything should still work! Again, feel free to modify the orders in your PowerLevel
data to double check.
You can also remove the PowerLevelValue
field from MagicalCompanions
and recreate that table.
Where should I store this data?
There’s one question remaining, which is where to store the PowerLevels
table data?
- If power levels are a major part of the game, you should probably have a page explaining them, maybe called
Power levels
. You can store the data here, even if the storing templates don’t display anything. But, remember to comment the newlines so you don’t get a bunch of vertical whitespace in the middle of your article. - The other option, which I like but which adds some complexity to your setup, is to make a namespace called
Data
. This namespace can contain anything you want to have in Cargo but that doesn’t make sense to be user-facing. Then, createData:Power levels
orData:PowerLevels
or whatever you like, and store it there.
In the second case, I’d probably make the template display something; I like to do something like this:
{{PowerLevel/Start}}
{{PowerLevel|name=Minor|order=1}}
{{PowerLevel|name=Common|order=2}}
{{PowerLevel|name=Major|order=3}}
{{PowerLevel|name=Extreme|order=4}}
{{PowerLevel|name=Godly|order=5}}
{{PowerLevel/End}}
Template:PowerLevel/Start
will look like this:
{| class="wikitable"
! Name !! SortOrder
and Template:PowerLevel/End
will be:
|}
And at Template:PowerLevel
we’ll update to this:
<noinclude>{{#cargo_declare:_table=PowerLevels
|Name=String
|SortOrder=Integer
}}</noinclude><includeonly>|-
| {{{name|}}} || {{{order|}}}<!--
-->{{#cargo_store:_table=PowerLevels
|Name={{{name|}}}
|SortOrder={{{order|}}}
}}</includeonly>
And it’ll look something like this:
This is a very common pattern that I employ, and I highly recommend getting comfortable with it if you’re going to be using a lot of Cargo.
Conclusion
If you want to sort a Cargo query by a value, you should make a new table containing the sort information for your query. Join this new table to your data and use data-sort-value
to sort as needed!
In general, this idea of creating new tables any time you have an “entity” is called normalization, and it’s great to normalize your data as much as possible. It makes your schema and possible queries a lot more flexible, and helps you store less data overall. Check out database normalization on Wikipedia if you want to read more about normalizations and “normal forms.”
-
Yes, I did do a bit of worldbuilding research for this ridiculous example. In my fake game design, you block
Shield
amount of damage from each incoming attack, which dealsAttack
damage. A linx is a tank, a tiger is a support-carry (“Roar” will buff allies), and Bastet is a healer. I have almost zero game design experience & have no idea if these numbers make sense. ↩︎ -
We don’t need it here, but you can also put the related attribute
data-sort-type
on a table header cell to tell MW how to sort that column (useful for example if you have data that’s a number prefixing some text & you want to sort by number). ↩︎