Community Central
Community Central
This page discusses a library used in the Lua programming language, through the Scribunto extension. If you are not using this extension, it will not apply to you.
This page simply contains some usage notes that simply expand on the full documentation.

Frequently, your modules will construct tables or other HTML structures that you will want to print. This is best done with the mw.html library. You can see documentation for this library here, but how return values are handled may cause some confusion.

Consider the following code, which is intentionally wrong:

-- create the table's header row
local tbl = mw.html.create('table')
:tag('tr')
	:tag('th')
		:wikitext("things that aren't as cute as kittens")

-- add further rows to the table
for k, v in ipairs({ 'bunnies', 'puppies' }) do
	tbl:tag('tr')
		:tag('td')
			:wikitext(v)
end

This does NOT work. You might think that in the loop, you're adding additional rows below the header row, but it's the th element that's been assigned to tbl, so the additional rows end up within the header cell.

Here's a comment-annotated version of the code above to demonstrate what's going on:

-- create the table's header row
local tbl = mw.html.create('table') -- `tbl` now refers to the `table`
:tag('tr') -- creating a `tr` inside the `table`; the `tr` is returned
	:tag('th') -- creating a `th` inside the `tr`; the `th` is returned
		:wikitext("things that aren't as cute as kittens") -- appending wikitext to the `th`, which is returned...
        -- ...meaning that it is the `th`, not the `table`, that `tbl` now points to!

-- add further rows to what should be the table, but actually is a table cell!
for k, v in ipairs({ 'bunnies', 'puppies' }) do
	tbl:tag('tr')
		:tag('td')
			:wikitext(v)
end

Using :done() to fix the problem[]

The following code ensures that the table element is assigned to tbl:

-- create the table's header row
local tbl = mw.html.create('table')
:tag('tr')
	:tag('th')
		:wikitext("things that aren't as cute as kittens")
	:done()
:done()
-- now the current location in tbl is directly within the <table> tag

-- add further rows to the table
for _, v in ipairs({ 'bunnies', 'puppies' }) do
	tbl:tag('tr')
		:tag('td')
			:wikitext(v)
end

Content-annotated version of the code that creates the table header:

-- create the table's header row
local tbl = mw.html.create('table') -- `tbl` now refers to the `table`
:tag('tr') -- appending a `tr` to the `table` and returning the `tr`
	:tag('th') -- appending a `th` to the `tr` and returning the `th`
		:wikitext("things that aren't as cute as kittens") -- appending wikitext to the `th` and returning the `th`
	:done() -- we're done with creating the `th` (hence the function name); returns the parent element, i. e. the `tr`
:done() -- returns the parent element, i. e. the `table`.
-- now the current location in tbl is directly within the <table> tag

-- add further rows to the table
for _, v in ipairs({ 'bunnies', 'puppies' }) do
	tbl:tag('tr')
		:tag('td')
			:wikitext(v)
end

Alternative approach[]

Note that the loop doesn't call :done(), but the generated table is correct. That is because each iteration references the tbl variable, which is not reassigned and thus always refers to the table element.

This also means that the first part of the function could be written differently:

-- create the table's header row
local tbl = mw.html.create('table') -- `tbl` refers to the `table`
tbl:tag('tr') -- append a `tr` to the `table` and return it, assigning it to nothing. the value of `tbl` is unchanged.
 	:tag('th') -- append a `th` to the `tr` and return it, assigning it to nothing. the value of `tbl` is unchanged.
		:wikitext("things that aren't as cute as kittens") -- append wikitext to the `th` and return it, assigning it to nothing. the value of `tbl` is unchanged.
        
-- Unlike in the previous version, the returned value from `:wikitext()` isn't assigned to `tbl`, so `tbl` keeps
-- pointing to the same `table`.

-- snip --

Which approach to take, using :done() or adding nodes one at a time, is a matter of preference, individual-wiki code-style conventions, and context. Do whichever version generates the easiest-to-understand code.

Storing references to sub-elements[]

Ok so we can use :done() to specify where we are within the table structure when we add new content. But what if we need to add new code in different places?

--create a header row
local tbl = mw.html.create('table')
:tag('tr')

local championProperties = { 'HP', 'Mana', 'Armor' } -- some list of champion stats
local championData = mw.loadData('Module:ChampionData') -- this table has all of the data about each champion stored in it

for _, v in ipairs(championProperties) do
	tbl:tag('th')
		:wikitext(v)
end

But now how can we add additional rows to tbl? As written, we can only add new things within the first tr that we already tagged. The answer is to reference the table rows of the table as they are created, like this:

local tbl = mw.html.create('table')
local headerRow = tbl:tag('tr') -- now new tags to tbl will still store directly within the table, and we can make cells within the row referenced by `headerRow`

for _, v in ipairs(championProperties) do
    headerRow:tag('th')
		:wikitext(v)
end

for _, championDataRow in ipairs(championData) do
	local outputTblRow = tbl:tag('tr') -- once again alias the current row
	for _, dataPoint in ipairs(championDataRow) do
		outputTblRow:tag('td')
			:wikitext(dataPoint)
	end
end

Now because headerRow and later outputTblRow are referencing a part of tbl, we can tag it and actually we are continuing to append to tbl! You can think of it as tbl being the parent object, and headerRow (or whatever you choose to call the reference) being a location in tbl.

Note: In the second ipairs loop, for _, championDataRow in ipairs(championData) do typically I would just write for _, row in ipairs(championData) but I wrote out a much longer variable name here to reduce confusion with table rows / the variable championDataRow. Also, the _ just denotes that we never use the key part of the key-value pair, but if you wanted you could write for k, row with no issue.