This page looks best with JavaScript enabled

Ordering a Cargo field

 ·  ☕ 7 min read

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:

  1. 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.
  2. What if the game gets patched? Maybe we get a new level called Demigodly that goes in between Extreme and Godly. Then we have to either change every instance of a power_level of Godly or we have to use something like 4.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.
  3. 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_values 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 Weapons, 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, create Data:Power levels or Data: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.”


  1. 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 deals Attack 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. ↩︎

  2. 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). ↩︎

Share on

river
WRITTEN BY
River
River is a developer most at home in MediaWiki and known for building Leaguepedia. She likes cats.


What's on this Page