Note: Throughout this article the only content page I discuss is Dragon
. If you see a reference to a different page name, it’s a template, although sometimes I have omitted the text Template:
in the interest of readability. I also assume you have the #or
parser function of Extension:ParserPower. See Dealing with defaults for alternatives if you don’t.
The situation
Say you have Template:Infobox Enemy
(as in a gaming wiki with information about enemies you fight in the game). You want to include the enemy’s name, which usually will be the same as the page name but occasionally might be hardcoded in the case of a disambiguation page or something. So you write {{#or:{{{name|}}}|{{PAGENAME}}<!-- -->}}
near the top of the infobox.
But you actually want to access this name several times over the course of the template:
- When you display the infobox’s title
- When you store to the Cargo table
Enemies
later on - And also when you store to Cargo in the
EnemyDrops
table, which can have multiple rows for each Enemy.
The third one looks something like this when you call the infobox from the page Dragon
:
{{Infobox Enemy
|name=Elder Dragon
|drops={{Infobox Enemy/Drops|item=Dragon Tooth|rarity=5|chance=2}}<!--
-->{{Infobox Enemy/Drops|item=Dragon Tooth|rarity=4|chance=20}}<!--
-->{{Infobox Enemy/Drops|item=Dragon Tooth|rarity=3|chance=78}}
}}
And Template:Infobox Enemy/Drops
looks something like this:
<li>{{{rarity}}}★ [[{{{item}}}]] ({{{chance}}}%)</li><!--
-->{{#if:{{NAMESPACE}}||{{#cargo_store:_table=EnemyDrops
|Enemy=<!-- how do we figure this out? -->
|Item={{{item}}}
|Rarity={{{rarity}}}
|Chance={{{chance}}}
}}<!-- end if -->}}
It’s retrieved in Template:Infobox Enemy
like this:
{{#if:{{{drops|}}}|<ul>{{{drops}}}</ul><!-- end if -->}}
What we want
We’d love to do something like this at the beginning of Template:Infobox Enemy
where we display our title:
{{#vardefineecho:Name|{{#or:{{{name|}}}|{{PAGENAME}}<!-- end or -->}}<!-- end vde -->}}
And then later on at Template:Infobox Enemy/Drops
we would write:
|Enemy={{#var:Name}}
Why we can’t
But this doesn’t work; notice that the calls to Infobox Enemy/Drops
are inside the call to Infobox Enemy
. When we evaluate this page, the parsing order looks like this:
- Evaluate each of the args of
Infobox Enemy/Drops
- Send these pre-computed args to
Infobox Enemy/Drops
- Evaluate the logic of
Infobox Enemy
Since there’s a #var:
call in Infobox Enemy/Drops
with a #vardefine:
call in Infobox Enemy
, that means that the #var:
evaluates before the #vardefine:
, and our code doesn’t work.
Oh no! How can we fix this?
Solution 1: Force the vardefine to evaluate earlier
We can introduce a new template, called SetName
with this logic:
{{#vardefineecho:Name|{{#or:{{{1|}}}|{{PAGENAME}}<!-- end or -->}}<!-- end vde -->}}
Then, at Dragon
we can write this:
{{Infobox Enemy
|name={{SetName|Elder Dragon}}
|drops={{Infobox Enemy/Drops|item=Dragon Tooth|rarity=5|chance=2}}<!--
-->{{Infobox Enemy/Drops|item=Dragon Tooth|rarity=4|chance=20}}<!--
-->{{Infobox Enemy/Drops|item=Dragon Tooth|rarity=3|chance=78}}
}}
Now, the order of operations will be:
- Evaluate the call to
Template:SetName
(here is where the#vardefine
happens) - Evaluate all of the calls to
Template:Infobox Enemy/Drops
(here is where the#var
happens) - Send all these params to
Template:Infobox Enemy
- Evaluate
Template:Infobox Enemy
(there is another#var
call here when you store to theEnemies
table)
And it works. Great!
Solution 2: Force the vardefine to evaluate earlier (v2)
Alternatively, we could make Template:SetName
do just that: set the name, and nothing else. In this case, our logic at Template:SetName
would be this:
{{#vardefine:Name|{{#or:{{{1|}}}|{{PAGENAME}}<!-- end or -->}}<!-- end vardefine -->}}
Notice that we are using #vardefine
and not #vardefineecho
.
Now, at Dragon
we can write this (notice there is no name parameter):
{{SetName|Elder Dragon}}{{Infobox Enemy
|drops={{Infobox Enemy/Drops|item=Dragon Tooth|rarity=5|chance=2}}<!--
-->{{Infobox Enemy/Drops|item=Dragon Tooth|rarity=4|chance=20}}<!--
-->{{Infobox Enemy/Drops|item=Dragon Tooth|rarity=3|chance=78}}
}}
Then at Template:Infobox Enemy
instead of printing {{{name|}}}
as the title, we print {{#var:Name}}
as the title.
The logic goes in this order:
- Evaluate
SetName
(here is where the#vardefine
happens) - Move on to evaluating
Infobox Enemy
, starting with evaluating its arguments - During this evaluation, evaluate
Infobox Enemy/Drops
(here is where the#var
happens) - Evaluate
Infobox Enemy
(there is another#var
call here)
This also works.
Solution 3: Use Lua for this parameter
Alternatively, we could use Lua, using a multi-instance subtemplates pattern.
So at Template:Infobox Enemy/Drops
, we would put something like this:
{{{item|}}};;;{{{rarity|}}};;;{{{chance|}}}:::
Then at Template:Infobox Enemy
, instead of displaying {{#if:{{{drops|}}}|<ul>{{{drops|}}}</ul>}}
we would write {{#invoke:InfoboxEnemyDrops|main|{{{drops|}}}<!-- end invoke -->}}
.
Here’s the Lua code (note, I am using the legacy version of a #cargo_store
here; in the most recent Cargo version there is now a native Lua cargo_store
method):
|
|
This Lua code is a very minimal example; were I to implement this in a live wiki, the functions splitArgsArray
and splitArgs
would be part of Module:ArgsUtil
, I’d have a Cargo wrapper instead of calling frame:callParserFunction
directly in this code, and I’d be using a generic merge
function to extract arg
from a merged frame.args
(args to the invoke) & frame:getParent().args
(args to the template calling the invoke). The line p.h = h
aids testing in the Scribunto console.
Note also that local ARGS_ORDER = { 'Item', 'Rarity', 'Chance' }
at the top of the page must contain the names of the fields as expected by the Cargo table EnemyDrops
. The order must be the same as the order in which we provide the parameters at Template:Infobox Enemy/Drops
.
At the page Dragon
when we create our infobox on a content page, we can now write:
{{Infobox Enemy
|name=Dragon
|drop={{Infobox Enemy/Drops|item=Dragon Tooth|rarity=5|chance=2}}<!--
-->{{Infobox Enemy/Drops|item=Dragon Tooth|rarity=4|chance=20}}<!--
-->{{Infobox Enemy/Drops|item=Dragon Tooth|rarity=3|chance=78}}
}}
Template:Infobox Enemy/Drops
is as defined above, and Template:Infobox Enemy
displays {{#invoke:InfoboxEnemyDrops|main|{{{drops|}}}<!-- end invoke -->}}
. Assuming you have properly declared the Cargo table EnemyDrops
, this code will work.
In this setup, here is the order in which things happen:
- We call to
Infobox Enemy
, evaluating the args first - Args evaluation starts
Infobox Enemy/Drops
is parsed, outputting{{{item|}}};;;{{{rarity|}}};;;{{{chance|}}}:::
and not using any variables- Evaluation of the source at
Infobox Enemy
starts - The
#vardefine
happens when we print the title at the top of the infobox ({{#vardefineecho:Name|{{#or:{{{Name|}}}|{{PAGENAME}}<!-- end or -->}}<!-- end vde -->}}
) - Continuing execution of the code at
Infobox Enemy
, we evaluate{{#invoke:InfoboxEnemyDrops|main}}
with the plaintext that we generated in step 3 - Lua execution of
Module:InfoboxEnemyDrops
begins - During this execution, we
frame:callParserFunction('#var')
to retrieve the value ofName
- Note that using the extension VariablesLua is equivalent to this
callParserFunction
- Note that using the extension VariablesLua is equivalent to this
- With the value of
#var:Name
safely retrieved, we now store to Cargo.
Solution 4: Use Lua for everything
If you are comfortable with Lua, you can create your entire infobox in Lua. The way you do this may vary wildly from wiki to wiki, so I won’t discuss this other than to note that it’s possible.
Solution 5: Like the Lua method, but with wikitext
This method is a little bit for funsies, I do not recommend it. It’s similar in philosophy to the Lua example, but it’s worse. It depends on Template:Counter
being defined like this:
{{#if:{{{get|}}}
|{{#var:counter-{{{1|}}}<!-- end var -->}}
|{{#vardefineecho:counter-{{{1|}}}|{{#expr:{{#var:counter-{{{1|}}}|0<!-- end var -->}}+1<!-- end expr -->}}<!-- end vde -->}}
<!-- end if -->}}
This template allows us to write something like this:
# {{counter|enemydrops}}
# {{counter|enemydrops}}
# {{counter|enemyabilities}}
# {{counter|enemydrops}}
# {{counter|enemydrops|get=yes}}
Which will output a list like this:
- 1
- 2
- 1
- 3
- 3
In other words, we get the ability to make multiple counters that start at 1 and continue upwards.
The other important concept is that if we write {{#var:Item{{counter|enemydrops|get=yes}}<!-- end var -->}}
, and {{counter|enemydrops|get=yes}}
is currently 3
, we’ll output the value of {{#var:Item3}}
.
So, let’s make Template:Infobox Enemy/Drops
with this code:
{{#vardefine:Item{{Counter|ed}}|{{{item|}}}<!-- end vardefine -->}}<!--
-->{{#vardefine:Rarity{{Counter|ed|get=yes}}|{{{rarity|}}}<!-- end vardefine -->}}<!--
-->{{#vardefine:Chance{{Counter|ed|get=yes}}|{{{chance|}}}<!-- end vardefine -->}}
At our page Dragons
, we will still write:
|drops={{Infobox Enemy/Drops|item=Dragon Tooth|rarity=5|chance=2}}<!--
-->{{Infobox Enemy/Drops|item=Dragon Tooth|rarity=4|chance=20}}<!--
-->{{Infobox Enemy/Drops|item=Dragon Tooth|rarity=3|chance=78}}
The result of this will now be that nothing is output, but the following variables are defined:
#var:Item1
is Dragon Tooth#var:Rarity1
is 5#var:Chance1
is 2#var:Item2
is Dragon Tooth#var:Rarity2
is 4#var:Chance2
is 20#var:Item3
is Dragon Tooth#var:Rarity3
is 3#var:Chance3
is 78
Now, at our infobox, when we display {{{drops|}}}
, we’ll also display the contents of a template designed to evaluate the variables we just defined. So we’ll write {{{drops|}}}{{DisplayEnemyInfoboxDrops}}
.
Let’s now write Template:DisplayEnemyInfoboxDrops
. We’ll need to use the Loops extension.
<ul>{{#while:
| {{#var:Item{{Counter|ed2}}<!-- end var -->}}<!--
as soon as this evaluates to an empty string, exit the loop.
in our example that will happen when `counter|ed2` gets to 4, since `#var:Item4` is not defined.
-->
| <li>{{#var:Rarity{{Counter|ed2|get=yes}}<!-- end var -->}}★ <!--
-->[[{{#var:Item{{Counter|ed2|get=yes}}<!-- end var -->}}]] <!--
-->({{#var:Chance{{Counter|ed2|get=yes}}<!-- end var -->}}%)</li>{{#if:{{NAMESPACE}}||
{{#cargo_store:
_table=EnemyDrops
|Name={{#var:Name}}
|Rarity={{#var:Rarity{{Counter|ed2|get=yes}}<!-- end var -->}}
|Item={{#var:Item{{Counter|ed2|get=yes}}<!-- end var -->}}
|Chance={{#var:Chance{{Counter|ed2|get=yes}}<!-- end var -->}}
}}
}}
}}</ul>
Another equivalent way to write this is:
<ul>{{#while:
| {{#var:Item{{Counter|ed2}}<!-- end var -->}}<!--
as soon as this evaluates to an empty string, exit the loop.
in our example that will happen when `counter|ed2` gets to 4, since `#var:Item4` is not defined.
-->
| <li>{{#vardefineecho:Rarity|{{#var:Rarity{{Counter|ed2|get=yes}}<!-- end var -->}}<!-- end vde -->}}★ <!--
-->[[{{#vardefineecho:Item|{{#var:Item{{Counter|ed2|get=yes}}<!-- end var -->}}<!-- end vde -->}}]] <!--
-->({{#vardefineecho:Chance|{{#var:Chance{{Counter|ed2|get=yes}}<!-- end var -->}}<!-- end vde -->}}%)</li>{{#if:{{NAMESPACE}}||
{{#cargo_store:
_table=EnemyDrops
|Name={{#var:Name}}
|Rarity={{#var:Rarity}}
|Item={{#var:Item}}
|Chance={{#var:Chance}}
}}
}}
}}</ul>
With this method, here is the order of evaluation:
- We start evaluating each of the arguments sent to
Infobox Enemy
- When we evaluate
|drops=
, we save a bunch of variables, indexed by the countered
which is incremented by 1 for each instance ofInfobox Enemy/Drops
- We start evaluating the code at
Infobox Enemy
- At the top of the template, we display the title & save a value for
#var:Name
- We get to the call to
DisplayEnemyInfoboxDrops
- We begin a loop
- We start a new counter named
ed2
which will start out at 1 and increase for each iteration of the loop - At each iteration, we print the
<li>
item and store the appropriate Cargo, recalling back to#var:Name
for theName
field of the table
One small thing to note is that we never actually need to print anything with the parameter {{{drops|}}}
. If there’s a possibility that an enemy has no drops, and we don’t want to output anything from this parameter, we might want to make Template:Infobox Enemy/Drops
output something, say a .
after all the #vardefine
s. And then at Infobox Enemy
, instead of saying {{{drops}}}{{DisplayEnemyInfoboxDrops}}
, we could write:
{{#if:{{{drops|}}}|{{DisplayEnemyInfoboxDrops}}<!-- end if -->}}
Then we won’t get an empty <ul></ul>
printed in the event that there are no drops.
Solution 6: A similar template option, but with arraymaptemplate
Instead of using a loop, this time we’ll use the parser function arraymaptemplate
, provided by Extension:Page Forms. If you only need to store 1 or 2 params alongside Name
in your Cargo table, this solution is pretty reasonable, but if you have a lot of parameters there, it gets out of hand pretty fast.
We’re going to use the same Template:Infobox Enemy/Drops
as we did for the Lua example, namely:
{{{item|}}};;;{{{rarity|}}};;;{{{chance|}}}:::
And at Dragon
, when we build the infobox, we will still write:
|drops={{Infobox Enemy/Drops|item=Dragon Tooth|rarity=5|chance=2}}<!--
-->{{Infobox Enemy/Drops|item=Dragon Tooth|rarity=4|chance=20}}<!--
-->{{Infobox Enemy/Drops|item=Dragon Tooth|rarity=3|chance=78}}
In our infobox, when we display this information, we’ll now write:
{{#if:{{{drops|}}}|
<ul>{{#arraymaptemplate:{{{drops}}}|DisplayEnemyInfoboxDrops|:::|}}</ul>
}}
And at Template:DisplayEnemyInfoboxDrops
, we’ll write this:
{{#vardefine:item|{{#explode:{{{1}}}|;;;|0}}<!-- end vardefine -->}}<!--
-->{{#vardefine:rarity|{{#explode:{{{1}}}|;;;|1}}<!-- end vardefine -->}}<!--
-->{{#vardefine:chance|{{#explode:{{{1}}}|;;;|2}}<!-- end vardefine -->}}<!--
--><li>{{#var:rarity}}★ [[{{#var:item}}]] ({{#var:chance}}%)</li>{{#if:{{NAMESPACE}}||
{{#cargo_store:
_table=EnemyDrops
|Enemy={{#var:Name}}
|Item={{#var:item}}
|Rarity={{#var:rarity}}
|Chance={{#var:chance}}
}}
<!-- end if -->}}
Solution 7: Abusing frame:preprocess
The final solution I will present is arguably the easiest to implement, and it’s also arguably the worst. It’s an example of being very clever to your own detriment because you make extremely confusing code that no one will be able to maintain in the future.
Remember that in the beginning, what we wanted to write at Template:Enemy Infobox/Drops
was this:
<li>{{{rarity}}}★ [[{{{item}}}]] ({{{chance}}}%)</li><!--
-->{{#if:{{NAMESPACE}}||{{#cargo_store:_table=EnemyDrops
|Enemy=<!-- how do we figure this out? -->
|Item={{{item}}}
|Rarity={{{rarity}}}
|Chance={{{chance}}}
}}<!-- end if -->}}
Let’s change that a bit; we will instead write:
<li>{{{rarity}}}★ [[{{{item}}}]] ({{{chance}}}%)</li><!--
-->{{#if:{{NAMESPACE}}||{{((}}#cargo_store:_table=EnemyDrops
{{!}}Enemy={{((}}#var:Name{{))}}
{{!}}Item={{{item}}}
{{!}}Rarity={{{rarity}}}
{{!}}Chance={{{chance}}}
{{))}}<!-- end if -->}}
At Template:((
we write {{
and at Template:))
we write }}
.
If we just evaluate this straight-up, we’ll get something slightly disgusting that is including as literal text:
{{#cargo_store:_table=EnemyDrops |Enemy={{#var:Name}} |Item=Dragon Tooth |Rarity=5 |Chance=2 }}
This is most definitely not what we want, but let’s write a very quick Lua module which we’ll call Module:Preprocess
:
local p = {}
function p.main(frame)
return frame:preprocess(frame.args[1] or frame:getParent().args[1])
end
return p
And then instead of printing {{{drops|}}}
in Enemy Infobox
, we print:
{{#if:{{{drops|}}}|{{#invoke:Preprocess|main|{{{drops}}}<!-- end invoke -->}}<!-- end if -->}}
And voila, that plaintext cargo_store
that we generated above is preprocessed by Lua and everything “just works”!
Invalid solution: var_final
You might be wondering why I didn’t suggest to write this at Template:Enemy Infobox/Drops
<li>{{{rarity}}}★ [[{{{item}}}]] ({{{chance}}}%)</li><!--
-->{{#if:{{NAMESPACE}}||{{#cargo_store:_table=EnemyDrops
|Enemy={{#var_final:Name}} <!-- use the var_final parser function here -->
|Item={{{item}}}
|Rarity={{{rarity}}}
|Chance={{{chance}}}
}}<!-- end if -->}}
Well, if we only wanted to display #var:Name
, this could actually work. But let’s try a really minimal example with a #cargo_store
:
{{#vardefine:Name|dragon}}{{#cargo_store:_table=EnemyDrops|Enemy={{#var_final:Name}}}}
Here’s what happens:
The #cargo_store
will evaluate before the Variables extension inserts the final value of #var_final:Name
, and everything breaks.
Which method should you use?
The answer depends a lot on your comfort level with writing wiki code (and with lua code), and on how much burden of knowledge you want to give your editors.
My true recommendation is to do everything in Lua; if you’re comfortable with Lua you should do that. If not, then probably options 1 and 2 are the best. But if you have deep concerns that your editors won’t understand what’s going on with that extra SetName
template, I might opt for either the arraymaptemplate
option or the frame:preprocess
version, whichever one makes more sense to you. If they both make equal sense, arraymaptemplate
is probably a bit safer.
Happy coding, and try not to abuse the MediaWiki parser too much!