User:John F. Lewis/Adopt/AdvancedTemplates
Advanced Templates
[edit]Note: You should complete User:John F. Lewis/Adopt/Templates before taking this lesson.
In the previous template lesson, you saw how you could use template parameters to add custom information into a template message. In this lesson, we'll look at how to use other templates to make even more happen, depending on what you enter as a parameter. These other templates are called "ParserFunctions" and are built into the MediaWiki software that Wikipedia is based on. Because of that, you can't edit these templates by going to their template page (because there isn't one), and they also are called in a unique way.
Before we look at these, let's have a quick review of how parameters work, because many of these ParserFunctions are going to depend on them, and you using them correctly. You create a parameter by using three curly brackets around a name (or number), like so: {{{foo}}}. In that case, calling the template "example" would require you to use a "foo" parameter, like this: {{example|foo=bar}}. That will cause the word "bar" to appear wherever you have the code "{{{foo}}}" in the template. If you forget the parameter, though, (just using {{example}}), bad things happen. Instead of a useful word, you get a big ugly {{{foo}}} in the middle of your message. We can avoid this by giving "foo" a default value with a pipe character: {{{foo|Hey, dummy, you forgot to set "foo".}}} Now, instead of an ugly parameter, we get a helpful message that tells us exactly what went wrong and how to fix it. If we'd rather our templates not insult us when we mess up, we can make the default value simply not appear at all: {{{foo|}}}. This works just as well, and in fact is exactly what we want to do for some of the ParserFunctions we're now going to look at.
#if:
The most basic function we have available is {{#if:}}. #if: probably looks fairly strange to you - since when do we start templates with a # sign? And what's with the colon? That is what tells us, and MediaWiki, that we're calling a ParserFunction instead of a normal template. Here's how #if: works:
{{#if: <text that either is or is not blank> | <what you want to appear if it isn't blank> | <what you want to appear if it is> }}
Huh?
#if: works a little differently than most "if... then..." structures work. #if: is set up like this: "If this space has something in it, I print this. If it's blank, I print that." How does this help us? Well, remember how we could set our parameters to have a blank default value? Imagine what would happen if I wrote this code:
{{#if: {{{1|}}} | Hello! | Goodbye! }}
Now, when I call the template that uses this code, I will do one of two things. I will either enter a parameter or I won't. If I don't, this code will display "Goodbye!" because there is nothing displayed between #if: and the first option; we set our parameter 1 to be blank by default, so there is nothing but blank space for #if: to look at. However, if I do enter a parameter, regardless of what it is, that code will display "Hello!". This is because when #if: looks at what you gave it, there's something between it and the first option. It doesn't care what that something is, it just cares that something exists. But now, here's why we had that short review on parameters:
{{#if: {{{1}}} | Hello! | Goodbye! }}
The difference between these two sets of code is minor, but causes the whole thing to bork up. This time, there is no pipe in our parameter, so there is no default value. As a result, when we don't set the parameter in the template, #if: still sees {{{1}}} right after its colon. So, regardless of what we do, we're always going to get "Hello!" as a result of this function.
#ifeq:
#ifeq: is a bit more useful. #ifeq: stands for "If equal" - instead of just checking to see if something exists, #ifeq: checks to see if that something is equal to something you specifically told it to look for. Here's how it works:
{{#ifeq: <text you input> | <text you want to compare against> | <what you want to appear if it they match> | <what you want to appear if they don't> }} {{#ifeq: {{{1}}} | foo | Hello! | Goodbye! }}
In the sample above, I want to see if the user typed "foo" as a parameter to my template. If they did, #ifeq: will see that and print out "Hello!". If they enter anything else, though, or in this case, nothing at all, #ifeq: will compare whatever they enter to "foo", see that they don't match, and print "Goodbye!" instead. ( bar =/= foo; {{{1}}} =/= foo ) This code is a bit more "secure" - if you want the template to do something if the user enters "yes" as a parameter, #if: is not what you want to use. If you use #if:, it'll do whatever you told it to do even if the user enters "no". By using #ifeq:, the function will only do this thing if they enter "yes", exactly like that. It won't work even if they enter "YES", because uppercase letters and lowercase letters aren't the same.
But what if you don't want to risk confusing the user? What if you do want "YES" to work? It's pretty pointless to make an #ifeq: for every single different capitalization of "yes". There's two options available to you. One is to use another ParserFunction, which we'll get to shortly, which acts like a super #ifeq:, checking for multiple different parameter values at once. Another, much easier way, is to tell the parameter to use all uppercase or lowercase letters. How? Magic. Observe:
Code | What comes out |
---|---|
{{lc:foo BAR}} | foo bar |
{{uc:foo BAR}} | FOO BAR |
{{lcfirst:BAR foo}} | bAR foo |
{{ucfirst:foo BAR}} | Foo BAR |
You can use these codes (which are examples of some Magic words) on just about anything - including your parameters. Obviously, it won't have much of an effect on {{{1}}}, but when your user types in "YES" when your #ifeq: is expecting to find "yes", adding the code {{lc: {{{1}}} }} will solve all of your problems.
#switch:
This is my favorite ParserFunction (yes, I'm a geek, and you're not one to talk; you ARE reading this, after all). This is the "super #ifeq:" I mentioned earlier. #switch: allows you to check a single line of text for a practically unlimited number of possible results (That is, the limit is set so high there's no way you're going to design a practical template that uses that many options). It works like this:
{{#switch: <text you input> | <possible value 1> = <what is displayed for possible value 1> | <possible value 2> = <what is displayed for possible value 2> | <possible value 3> | <possible value 4> = <what is displayed for possible values 3 AND 4> | #default = <what appears if the value you input doesn't match any possible value> }}
What this template does is this: It takes the value you enter (which is probably a parameter, which is probably forced to be either lower or upper case for the same reason it would be in #ifeq:) and moves down the list, comparing it to each possible value in turn. As soon as it matches something, it stops, and looks for the next equals sign. It then prints whatever you have between that equals sign and the next pipe. Let's look at an example, based on the above format:
{{#switch: {{lc: {{{1}}} }} | foo = bar | ice = cream | french | burnt = toast }}
If I enter "foo", #switch: replies with "bar". Likewise, "ice" gets "cream" as a response, and "burnt" gets "toast". But "french" also gets "toast". This is because "french" doesn't have anything set specifically for it - there's no equals sign after "french". Because of this, #switch: is going to keep looking for the next equals sign, which is after "burnt". This makes sense for me, because I want that to happen. "burnt toast" and "french toast" both make sense. However, I do have to be careful about what order I put things in; this code may look similar, but will cause "french" to come out with a different result:
{{#switch: {{lc: {{{1}}} }} | foo = bar | french | ice = cream | burnt = toast }}
Now, entering "french" will return "cream", because "ice = cream" is the next value in line for #switch: to find. For both of these, anything not listed in the ParserFunction will not return anything - nothing will be printed, because there is no default value. For #switch: to print something out regardless of what I type in, I would need to specify "#default = <something>" at the very end of the template. There's really no technical reason why #default has to be at the end, but it just makes it easier for other users.
#time:
Time is an interesting thing in how it is calculated and how it brings some order to our lives. Because of that, it's important we have a bit of code that allows us to display time however we would like. #time: is just that code, allowing you to enter your own custom time and change it however you wish. It's a very useful code, that you'll see used in many places thoughout Wikipedia - for example, proposed deletion templates "expire" after five days, and those templates use a #time: function to control that.
Time, of course, is rather complicated, and #time: itself is complicated to mirror that. Because there are many different ways to display the time, there are many different things you can tell #time: to do. Before we cover that, though, let's look at how #time: works:
{{#time: <how you want the time displayed> | <what time you want displayed> }} OR {{#time: <how you want the current time displayed> }} (to display the time at which the page was viewed)
As you can see, there are two ways to set this code up. You can display the current time, or a custom time that you specify. This custom time can be simply a change in timezone, a certain about of time before or after the current time, or a fixed time that you set. Here are some examples below of how that works. You can ignore the formatting code for just the moment, we'll cover that shortly. Just focus on what I have entered in the time slot on the right hand side.
What I'm doing | Code | Result |
---|---|---|
Printing the current time in UTC | {{#time: H:i:s }} | 08:00:17 |
Changing the time zone to U.S. Eastern Standard Time, during Daylight saving time) (Note that this is in fact backwards: EST is actually UTC-4 during DST.) |
{{#time: H:i:s | +4 }} | 04:00:17 |
Changing the time to 30 minutes ago | {{#time: H:i:s | -30 minutes }} | 07:30:17 |
Fixing the time to the time I saved this version of the lesson | {{#time: H:i:s | {{subst:CURRENTTIMESTAMP}} }} | 15:51:32 |
Fixing the time to 30 minutes before I saved this version of the lesson, in EST during DST (Combining everything you've seen so far) |
{{#time: H:i:s | {{subst:CURRENTTIMESTAMP}} +4 -30 minutes }} | 11:21:32 |
With me so far? You can do almost anything you want with the time that way, but there are some limitations to the template. For example, I was trying to set up a stopwatch here, that would display how many months, days, hours, and minutes had gone by since I saved the code onto the page. This is the code I tried to use: {{#time: n "months," j "days," G "hours, and" i "minutes" | -{{subst:CURRENTTIMESTAMP}} }}. There's nothing wrong with the format, and the time looks as though it might work, but instead I got this: Error: Invalid time.. Obviously not what I wanted. The problem was that I didn't specify any units for it to subtract, and the number {{CURRENTTIMESTAMP}} spits out is way to big to be considered a time zone. #time: is very finicky about what it will accept as a time - it has to be something it can easily recognize and use, or it's not going to bother. Here are some examples:
Code | Result | Notes |
---|---|---|
{{#time: H:i:s | {{CURRENTTIME}} }} | 08:00:00 | {{CURRENTTIME}} converts into a usable value for #time:, specifically the current hour and minute of the day: 08:00 |
{{#time: j M Y | April 9 2008 }} | 9 Apr 2008 | #time: knows how to recognize names of months, as long as they're spelled correctly, so it knows to read that as Month-day-year. |
{{#time: j M Y | 04/09/2008 }} | 9 Apr 2008 | This sort of format is recognizable to me as the same date, but is ambiguous - see below. |
{{#time: j M Y | 09/04/2008 }} | 4 Sep 2008 | This date could also represent April 9th, and some people might expect #time: to display that. Keep in mind that #time: will always read in the order Month-Day-Year if things get confusing. |
{{#time: j M Y | December 7 1941 }} | 7 Dec 1941 | We would expect time to be able to display this properly, but it won't. Computers as we knew them didn't exist in 1941, and this is reflected in how we program things. As far as #time: is concerned, all life began on January 1st, 1970, and you're not going to convince it otherwise. |
{{#time: j M Y | October 14 1066 }} | 14 Oct 1066 | If you try too hard to convince it, #time: will get mad at you and throw a temper tantrum. Don't try it. |
Now that you roughly know how to tell #time: what time to show, let's look at how to get it to show it (following my grammar lesson on antecedants; moving on...). You'll have noticed from above that I've been sticking what seem to be random letters in the format side of #time:. #time: appears to be written for the sole purpose of being confusing as hell, because few of the codes for the format make any sort of sense whatsoever. No, simply typing "day" won't work - if you're lucky, #time: will simply print "day" out and it won't look horrible, but it's also possible you'll get another big red error message. So what does it take? Let's figure it out:
Code | What it shows | Example | Code | What it shows | Example | |
---|---|---|---|---|---|---|
Days and weeks | Years | |||||
W | The number of the week (see ISO week date) | 48 | Y | The year, using four digits | 2024 | |
d | The day of the month, with a leading zero if less than 10 | 25 | y | The year, using two digits | 24 | |
z | The day of the year (January 1 = 0) | 329 | Hours | |||
D | The abbreviation for day of the week | Mon | a | Returns am or pm for use in a 12-hour time format | am | |
l | The full day of the week (lowercase letter L) | Monday | A | Same as above, but uppercase | AM | |
N | The number of the day of the week, ISO style (Monday = 1, Sunday = 7) | 1 | g | The hour of the day, in 12-hour format. | 8 | |
w | The number of the day of the week, US style (Sunday = 0, Saturday = 6) | 1 | h | The hour of the day, in 12-hour format, with a leading zero. | 08 | |
Months | G | The hour of the day, in 24-hour format. | 8 | |||
n | The month number | 11 | H | The hour of the day, in 24-hour format, with a leading zero. | 08 | |
m | The month number with a leading zero (January = 01) | 11 | Minutes and seconds | |||
M | The month's abbreviation | Nov | i | The minute of the hour, with a leading zero. | 00 | |
F | The full month name | November | s | The second of the minute, with a leading zero. | 17 | |
U | The total number of seconds that have passed since January 1, 1970, 00:00:00 UTC. This is used by computers to represent time. | 1732521617 |
Anything that doesn't appear in this list will generally be treated as what it actually is. So, you can wikilink dates by enclosing the format code in square brackets: {{#time: [[F d]] }} produces November 25. If, however, you want to type a letter that is in this list, you'll need to enclose it in quotes: {{#time: U represents a time }} comes out to:
- 1732521617 Mon, 25 Nov 2024 08:00:17 +0000UTCpMon, 25 Nov 2024 08:00:17 +0000UTC17UTC113017 am 300011UTC
To get the template to display as you intend it to, you'll need to use {{#time: U "represents a time" }} (1732521617 represents a time) or something similar.
Things get easier from here out, don't worry!
#expr:
This is the last ParserFunction we'll cover, although there are more: this is the last of the more commonly used ones, however. #expr: stands for "expression", referring to the mathematical sort. #expr: is your calculator, allowing you to play with parameters and variables to spit out something that may or may not be useful. It also can be used for logical statements, where 0 is considered false and anything else is considered true. Here's what you can do with it:
Operator | What it does | Sample code | Result |
---|---|---|---|
Mathematical stuff | |||
+ | Adds stuff | {{#expr: 1 + 1 }} | 2 |
{{#expr: 3 + -2 }} | 1 | ||
- | Subtracts stuff | {{#expr: 1 - 1 }} | 0 |
{{#expr: 3 - -2 }} | 5 | ||
* | Multiplies stuff | {{#expr: 3 * 2 }} | 6 |
/ | Divides stuff | {{#expr: 3 / 2 }} | 1.5 |
mod | Finds the remainder after dividing whole numbers. Cuts off any decimal values. |
{{#expr: 3 mod 2 }} | 1 |
{{#expr: 3 mod 2.8 }} | 1 | ||
round | Rounds to the number of decimal places indicated to the right of "round" |
{{#expr: 3.1415926 round 2 }} | 3.14 |
{{#expr: 32345 round -3 }} | 32000 | ||
Logical stuff | |||
not | Changes non-zero to zero, and zero to one. (Makes it not true/false) |
{{#expr: not 30 }} | 0 |
{{#expr: not 0 }} | 1 | ||
and | Only true if both sides are true (non-zero) | {{#expr: 0 and 30 }} | 0 |
{{#expr: 1 and 30 }} | 1 | ||
or | True if one or both sides are true | {{#expr: 0 or 30 }} | 1 |
{{#expr: 0 or 0 }} | 0 | ||
!= | Exclusive or - only true if one side is true | {{#expr: 1 != 0 }} | 1 |
{{#expr: 1 != 1 }} | 0 | ||
= | Equals | {{#expr: 3 = 3 }} | 1 |
<> | Not equal | {{#expr: 3 <> 2 }} | 1 |
> | Greater than | {{#expr: 3 > 2 }} | 1 |
< | Less than | {{#expr: 3 < 2 }} | 0 |
>= | Greater than or equal | {{#expr: 3 >= 2 }} | 1 |
<= | Less than or equal | {{#expr: 3 <= 2 }} | 0 |
You can combine mathematical stuff and logical stuff in the same #expr:, as well as add parentheses to group operations - for example, {{#expr: (30 + 2) / 16 > 3}} will produce 0 ((30 + 2) / 16 = 32 / 16 = 2, which is less than 3, so false or 0). The function follows a specific order of operations, with all things going from left to right:
- Stuff in parentheses ()
- Positive and negative signs (+1, -1, not)
- Multiplication and division (*, /, mod)
- Addition and subtraction (+, -)
- Rounding with round
- Comparisons (=, <>, <, <=, >, >=, !=)
- and
- or
Make sure to be careful about this; just as in school, failing to pay attention to order of operations can easily cause your equation to come out to something you didn't expect.
Obviously there are some things you can't do with #expr: - it doesn't like letters, so using exponential formats such as 6.67E-11, or mathematical constants like e won't work. Also, it's limited by the usual laws of mathematics (for example, you can't divide by zero, etc.)
Other ParserFunctions
There are a total of 5 other ParserFunctions I haven't covered. I'll list these below, but won't go into detail about them because they are rarely used outside of meta-level templates, such as {{db-meta}}. Each of these is either fairly basic (along with what you already know) or can be easily represented by using one of the functions already covered. If you have an interest in these templates, they, along with the ones mentioned above, are covered in full detail at m:Help:ParserFunctions (Note: this page is on MetaWiki).
- #ifexist: Similar to #if:, this checks to see if a page exists at the given title, and returns one of two possible lines of text.
- #ifexpr: Combines #ifeq: and #expr:, checks a mathematical or logical expression to see if it results in zero, and returns one of two possible lines of text.
- #rel2abs: Converts a relative link to a direct link - for example, /Subpage is a relative link which can be converted to the direct link, User:John F. Lewis/Adopt/AdvancedTemplates/Subpage.
- #titleparts: Returns a portion of a given page title as specified by the user.
- #iferror: Checks ParserFunctions that could return an error message, including #time:, #expr:, #ifexpr:, and #rel2abs:, and returns a specified line of text if an error is the result.
Templates and Tables
You've noticed that all of these functions use pipes, just like regular templates do. You've probably also noticed that most templates use tables to keep their formats in a readable order, and that these tables also use pipes. So, how does MediaWiki know when a pipe is a template pipe or a table pipe? Well, it doesn't.
Say you're setting up a template, that displays a table with an optional third row, triggered by the parameter {{{row}}}. Here's the code you try:
{| class="wikitable" |- !This is !a template. |- |This is |a row. {{#if: {{{row|}}} | |- |Here's an |extra row. }} |}
So, let's see what happens when we test this out. We made {{{row}}} be blank by default, so we should see a table that has only two rows.
|
|
Bleah. That's not quite what we wanted. Things came out this way because when we condense the #if: code to a single line, this is what we get: {{#if: {{{row|}}} | |- |Here's an |extra row. }}. #if: doesn't know that it's in a table. #if: just sees that for some reason it's being given four different bits of text to choose from. However, it only cares about the first two: the blank section between the first two pipes, and the dash between the second two. Oops. So how can we tell #if: it's in a table, and needs to ignore some of those pipes? We trick it.
The templates {{!}} {{!!}} and {{!-}} are all designed for this purpose. Since we can't put an actual pipe in there and have it work, we fake it with another template. What happens is that #if: sees the template as a template, that is, like this: {{!}}
. Since that's not a pipe, it assumes that's part of the text you want it to print out. But when it prints that text out into the table, it strips the template down into what it actually is, in this case, a pipe |. Now it's the table's turn. It goes through and sees the code left behind by #if:. Since there's a pipe there now, it deals with it accordingly, and spits out the table as we wanted to see it. So, this is how our code above should be written:
{| class="wikitable" |- !This is !a template. |- |This is |a row. {{#if: {{{row|}}} | {{!}}- <!-- That makes a new row --> {{!}} <!-- That makes a new cell --> Here's an {{!}} <!-- That makes a new cell --> extra row. }} |}
And so we get:
|
|
That's it! Remember, {{!-}} produces |-, {{!}} produces |, and {{!!}} produces ||.
That's everything! Try testing out these templates in a sandbox, and seeing what all they can do. Once you're confident with what they do, feel free to add this userbox to your user page - you'll have earned it!
Code | Result | |
---|---|---|
{{Template:User t|3|c}} | Usage • Category:User template coder-3 • Category:Wikipedians who program conditional templates |