Module:AutosortTable

From OODA WIKI

Usage

This module helps create data tables in an automatically sorted order. As of this writing it is used primarily for the generation of the huge, dynamic tables at List of OODA WIKIs. For the OODA WIKI editions table, the module is invoked directly to create the framework for the table, followed by content for the rows within the module's invocation. For the Details of OODA WIKI editions table, the module is invoked directly to create the framework for the table while other templates (e.g., {{WP7}}, {{WP7a}}) generate content for the individual rows, also within the module's invocation. The templates {{WP7}}, {{WP7a}}, {{WP7b}} and {{WP7c}} are intended to be called as arguments within the module's invocation, as they do not produce regular wikitable (or any standard table) code themselves.

The module's arguments are:

{{#invoke:AutosortTable|create |class= |style= |separator= |order= |numeric= |descending= |hidden= |rowheader= |caption= |header= |footer= |colstyle= }}

Module arguments
Argument Example Notes
function create The argument create is the only function of this module, and is required.
class class = wikitable Class for the entire table. Table can be made user-sortable by including the class "sortable", but does not need to be.
style style = width: 50%; CSS for the entire table
separator separator = -- Separator string used to separate cells in the data definition. The pipe (|) is an invalid separator for this module.
order order = 3, 2 Order for auto-sorting preference, takes a comma-separated list of column numbers. In the example here, the table will be sorted by column 3 first, then by column 2.
numeric numeric = 2 Columns which use numeric sorting when auto-sorted. Takes comma-separated list of column numbers.
descending descending = 3 Columns for which the auto-sort order should be descending (otherwise, ascending is used). Takes comma-separated list of column numbers. Here, only the third column will be auto-sorted in descending order (e.g., Zebra, Walrus, Muskrat, Emu, etc., or 12, 9, 6, 4).
hidden hidden = 2 Columns which are not to be displayed (even though they may be used for row-sorting purposes). Takes comma-separated list of column numbers. Here, the second column will not be shown.
rowheader rowheader = 1 Cell(s) in each non-header row to be emitted as row header, per MOS:ACCESS. Usually 1, and only 1, but accepts comma-separated list of column numbers. Causes !scope="row" to be used in the HTML for the cells specified.
caption caption = Notable people by age Caption to be used for the table, per MOS:ACCESS
header header = -- Name -- Age These are the column headings. In this example there are two columns with headings, the first is "Name", the second, "Age". Note the separators (--) which match the separator value above.
footer footer =-- Country -- Population -- Density Table footer, typically a duplication of header (see header argument above). Here, the first three columns have the footer labels shown.
colstyle colstyle = -- text-align:left; -- text-align:right; -- -- -- Adds the specified CSS styling to entire columns. Here, the first column will be left-aligned, the second column will be right-aligned, and the next three columns (with no CSS specified) will use the default styling. Note the separators (--) which match the separator value above.

Styling tricks

While there appear to be no ways to style an individual cell when using this module, styling particular rows, columns, or the entire table can be done using CSS.

Styling the table

The style argument allows the entire table to use a default styling. For example, |style=width:70%; text-align:center; would constrain the table width to 70% of the available window, and center the text (not including column and row headers) by default. When using multiple properties, the semicolon is necessary to separate them. Do not use the quotation marks ordinarily required in CSS or wikitable markup (as in "|style=width:70%; text-align:center;"), as it causes the CSS to be disregarded. A reminder: use CSS to constrain tables sparingly, as reduced font-size or unexpected table widths may cause accessibility problems or irritate readers.

Styling a row

An individual row can be styled separately by including CSS prior to the first data cell. Consider this example table:
<code>
{{#invoke: AutosortTable| create | class = wikitable plainrowheaders sortable | separator = -- | order = 2 | numeric =2  | descending = 2 | caption=Old friends | rowheader = 1
| header =  -- Name -- Age -- Diet                  <!-- Table header -->
| -- Maria -- 36 -- Vegan                           <!-- Row 1 -->
| -- Peter -- 35 -- Vegetarian                      <!-- Row 2 -->
| -- Julia -- 35 -- Meat                            <!-- Row 3 -->
| -- James -- 50 -- Vegan                           <!-- Row 4 -->
| background-color: #FFDDDD -- Henry -- 45 -- Meat  <!-- Row 5, with CSS -->
| -- Ireni -- 47 -- Fish, no meat                   <!-- Row 6 -->
| colstyle = -- text-align:left; -- text-align:right; -- -- --
}}
</code>
Old friends
NameAgeDiet
James50Vegan
Ireni47Fish, no meat
Henry45Meat
Maria36Vegan
Peter35Vegetarian
Julia35Meat

The row for "Henry" gets a pink color (#FFDDDD), while the row header appropriately retains the gray formatting wikitables use for headers.

Styling a column

An individual column can be styled separately by using CSS in the colstyle argument. In the Old friends example just above, the alignment has been set to text-align:left; and text-align:right;, respectively. Consider this similar table:

<code>
{{#invoke: AutosortTable| create | class = wikitable plainrowheaders sortable | separator = -- | order = 2 | numeric =2  | descending = 2 | caption=Old friends again | rowheader = 1
| header =  -- Name -- Age -- Diet  <!-- Table header -->
| -- Maria -- 36 -- Vegan           <!-- Row 1 -->
| -- Peter -- 35 -- Vegetarian      <!-- Row 2 -->
| -- Julia -- 35 -- Meat            <!-- Row 3 -->
| -- James -- 50 -- Vegan           <!-- Row 4 -->
| -- Henry -- 45 -- Meat            <!-- Row 5 -->
| -- Ireni -- 47 -- Fish, no meat   <!-- Row 6 -->
| colstyle = -- -- font-weight:bold; color:brown -- background-color:yellow
}}
</code>
Old friends again
NameAgeDiet
James50Vegan
Ireni47Fish, no meat
Henry45Meat
Maria36Vegan
Peter35Vegetarian
Julia35Meat

The "Age" column entries are now in bolded brown and no longer left-aligned as in the previous example. The "Diet" column here has the specified yellow background.

Sample tables

"OODA WIKI editions" example

Here is an abbreviated version of the OODA WIKI editions table at List of OODA WIKIs. It is a manually sortable wikitable which uses the class "plainrowheaders" (no bold, not centered) for row headers (specified here as only column 1). It is auto-sorted by the fifth column ("Active user base"), which is a numeric field and should be auto-sorted in descending order (highest at the top). The content of each row in the table is entered as a separate argument (starting with a pipe [|] symbol) and includes text, wikilinks, and image file links. Please view the wikicode to see the details.

OODA WIKI editions
OODA WIKI nameLanguageScriptWP codeActive user
base (10n)
Logo
English OODA WIKIEnglishLatnenExpression error: Unexpected < operator.
OODA WIKI
in italiano
ItalianLatnitExpression error: Unexpected < operator.
Kuanyama
OODA WIKI
(closed)
KuanyamaLatnkj (closed)Expression error: Unexpected < operator.

"Details of OODA WIKI editions" example

Here is an abbreviated version of the Details of OODA WIKI editions table at List of OODA WIKIs. This table uses templates (using this module) to produce the individual rows for the table which this module will generate.

Like the above example, this is a manually sortable wikitable which uses the class "plainrowheaders" (no bold, not centered) for row headers (specified here as only column 1). However, this table is auto-sorted by the second column (based on number of articles) which is hidden (although the same values are used again — and shown — in column 5 as "Articles"), and which is a numeric field and should be used for auto-sorting in descending order (highest at the top). The content of each row in the table is generated by one of the WPn templates and the call to each template is entered as a separate argument (starting with a pipe [|] symbol).

This table has some special alignment requirements, as it contains several columns containing large numbers, so the colstyle argument is used extensively. Please view the wikicode to see the details.

Lua error at line 80: attempt to perform arithmetic on local 'firstSep' (a nil value).



--[[
AutosortTable: Creates a table which is automatically sorted
 
Usage: (Remove the hidden comments before use)
 
{{#invoke: AutosortTable|create
 
| class = wikitable                          <!-- Class for the entire table -->
| style = width: 50%;                        <!-- CSS for the entire table -->
| separator = --                             <!-- Separator string used to separate cells; pipe (|) invalid -->
| order = 2, 1                               <!-- Order for sorting preference, takes a comma-separated list of column numbers -->
| numeric = 2                                <!-- Columns which use numeric sorting. Takes comma-separated list of column numbers -->
| descending = 1                             <!-- Columns for which sort order should be descending. Takes comma-separated list of col numbers -->
| hidden = 2                                 <!-- Columns which are not to be displayed. Takes comma-separated list of col numbers -->
| rowheader = 1                              <!-- Cell(s) in each non-header row to be emitted as row header, per WP:ACCESS#Data tables. Usually just 1, but accepts comma-separated list of col numbers -->
| caption = Notable people by age            <!-- Table caption per WP:ACCESS -->
| header =  -- Name -- Age                   <!-- Table header -->
| footer =                                   <!-- Table footer, typically a totals row or duplication of header -->
| -- Bob -- 20                               <!-- Row 1 -->
| -- Peter -- 35                             <!-- Row 2 -->
| -- John -- 35                              <!-- Row 3 -->
| -- James -- 50                             <!-- Row 4 -->
| background-color: #FFDDDD -- Henry -- 45   <!-- Row 5, with CSS -->
| colstyle = -- text-align:left; -- text-align:right; -- -- --
											 <!-- CSS to be used on content of respective columns, here 1st & 2nd -->

}}
]]
 
local _module = {}
 
_module.create = function(frame)
 
    local args = frame.args
 
    -- Named parameters
 
    local class = args.class
    local style = args.style
    local sep = args.separator
    local order = args.order
    local desc = args.descending or ""
    local nsort = args.numeric or ""
    local hidden = args.hidden or ""
    local header = args.header
    local footer = args.footer
    local colstyle = args.colstyle
    local rowheader = args.rowheader or ""
    local caption = args.caption
 
    -- Frequently-used functions
 
    local strIndexOf = mw.ustring.find
    local strSplit = mw.text.split
    local strSub = mw.ustring.sub
    local strTrim = mw.text.trim
 
    local seplen = #sep
    local nsortLookup, descLookup, hiddenLookup, rowHeading = {}, {}, {}, {}
 
    -- Create the table
 
    local html = mw.html.create()
    local htable = html:tag('table')
    if class then htable:attr('class', class) end
    if style then htable:attr('style', style) end
    if caption then
    	local hcaption = htable:tag('caption')
    	hcaption:wikitext(caption)
    end

    -- Parses a row string. The 'key' parameter is used to assign a unique key to the result so that equal rows do not cause sort errors.
    local parse = function(s, key)
        local css
        local firstSep = strIndexOf(s, sep, 1, true)
        if firstSep == 1 then  -- no CSS
            css = nil
            s = strSub(s, seplen + 1, -1)
        else   -- CSS before first separator
            css = strSub(s, 1, firstSep - 1)
            s = strSub(s, firstSep + seplen, -1)
        end
        return {key = key, css = css, data = strSplit(s, sep, true)}
    end
 
    --[[
        Writes a row to the table.
        css: CSS to apply to the row
        data: The data (cells) of the row
        _type: Can be 'header', 'footer' or nil.
    ]]
    local writeHtml = function(css, data, _type)
        local row = htable:tag('tr')
        if css then row:attr('style', strTrim(css)) end
 
        for i, v in ipairs(data) do
            if not hiddenLookup[i] then
                local cell
                if _type == 'header' then
                    -- Header: use the 'th' tag with scope="col"
                    cell = row:tag('th')
                    cell:attr('scope', 'col')
                elseif _type == 'footer' then
                    -- Footer: Mark as 'sortbottom' so that it does not sort when the table is made user-sortable
                    -- with the 'wikitable sortable' class
                    cell = row:tag('td')
                    cell:attr('class', 'sortbottom')
                else
                	if rowHeading[i] then
                		-- Cell is a row heading
                    	cell = row:tag('th')
                    	cell:attr('scope', 'row')
                    else
                    	-- Ordinary cell
                    	cell = row:tag('td')
                    end
                    local cellCss = colstyle and colstyle[i]
                    if cellCss then cell:attr('style', strTrim(cellCss)) end   -- Apply the column styling, if necessary
                end
                cell:wikitext(strTrim(v))
            end
        end
        return row
    end
 
    -- Parse the column styles
    if colstyle then colstyle = parse(colstyle, -1).data end
 
    -- Write the header first
    if header then
        local headerData = parse(header)
        writeHtml(headerData.css, headerData.data, 'header')
    end
 
    -- Parse the data
    local data = {}
    for i, v in ipairs(frame.args) do data[i] = parse(v, i) end
 
    order = strSplit(order, '%s*,%s*')
    nsort = strSplit(nsort, '%s*,%s*')
    desc = strSplit(desc, '%s*,%s*')
    hidden = strSplit(hidden, '%s*,%s*')
    rowheader = strSplit(rowheader, '%s*,%s*')

    for i, v in ipairs(order) do order[i] = tonumber(v) end
    for i, v in ipairs(nsort) do nsortLookup[tonumber(v) or -1] = true end
    for i, v in ipairs(desc) do descLookup[tonumber(v) or -1] = true end
    for i, v in ipairs(hidden) do hiddenLookup[tonumber(v) or -1] = true end
    for i, v in ipairs(rowheader) do rowHeading[tonumber(v) or -1] = true end

    --Sorting comparator function.
    local sortFunc = function(a, b)
        local ad, bd = a.data, b.data
        for i = 1, #order do
            local index = order[i]
            local ai, bi = ad[index], bd[index]
            if nsortLookup[index] then
                -- Numeric sort. Find the first occurrence of a number and use it. Decimal points are allowed. Scientific notation not supported.
                ai = tonumber( (ai:find('.', 1, true) and ai:match('[+-]?%d*%.%d+') or ai:match('[+-]?%d+')) or 0 )
                bi = tonumber( (bi:find('.', 1, true) and bi:match('[+-]?%d*%.%d+') or bi:match('[+-]?%d+')) or 0 )
            end
            if ai ~= bi then
                if descLookup[index] then return ai > bi else return ai < bi end
            end
        end
        return a.key < b.key
    end
    table.sort(data, sortFunc)
 
    -- Write the sorted data to the HTML output
    for i, v in ipairs(data) do writeHtml(v.css, v.data, nil) end
 
    -- Write the footer
    if footer then
        local footerData = parse(footer)
        writeHtml(footerData.css, footerData.data, 'footer')
    end

    return tostring(html)
end

return _module