Module:Navbox

From Feed The Beast Wiki
Jump to: navigation, search

Exported functions[edit source]

navbox[edit source]

Emit a navbox. Has three named parameters:

Parameter Function
1 Mandatory Subpage of Module:Navbox to load the definition from.
name Mandatory Page to link to in the v.d.e bit of the navbox header.
fromParent If present, read the previous two parameters from the page that invoked the one the #invoke is on. ie, use this in your wrapper template
forceUntranslated Debugging only If present, load the untranslated version of the navbox, instead of a translation.

Navbox definitions[edit source]

The module must have one member, navbox, which must be a function with the following signature:

function(navbox, highlightline, group, list, line, ni, nid, l)

The function returns a navbox definition built by calling the functions passed to it.

The top of the tree is a navbox{}
    title: Mandatory wikitext to display as the navbox's title
    Can contain a mix of group{}, list{}, and highlightline{}

group{} are the collapsible groups.
    title: Mandatory wikitext to title the group with.
    name: Untranslated name to refer to it in things like the opengroups template parameter.
    Can contain list{} and highlightline{}

list{}
    title: Optional title to display at the left.
    Can contain list{}/line{}, in which case the lists will be formatted tabley/subgroupy,
    or string/ni{}/l{}, which will be formatted as a hlist. Note that the *first*
    positional argument determines if it's a table or not.
    
highlightline{}
    Contents are concatenated (with space between) and displayed in a highlighted
    line like wp:Template:Navbox before= and after=
    
line{}
    Like list{} except it formats as a straight run of text rather than hlist. Can only contain
    string. ni{} or l{}

ni{}
    Invokes {{NI}} (or a facsimile, anyway)

nid{}
    Invokes {{NID}} (or a facsimile, anyway)
    
l{}
    Invokes the guts of {{L}}

text{}
    You should never need to use this directly. It's what strings are wrapped in.

--[[
All of the items in the definition tree for the navbox have an expand() method that
causes them to produce their markup:
expand(state, output)
where output is a function that adds its argument to the output, and state is state variables.
output's job also includes calling expand() on any tables!

The top of the tree is a navbox{}
    title: Mandatory wikitext to display as the navbox's title
    Can contain a mix of group{}, list{}, and highlightline{}

group{} are the collapsible groups.
    title: Mandatory wikitext to title the group with.
    name: Untranslated name to refer to it in things like the opengroups template parameter.
    Can contain list{} and highlightline{}

list{}
    title: Optional title to display at the left.
    Can contain list{}/line{}, in which case the lists will be formatted tabley/subgroupy,
    or string/ni{}/l{}, which will be formatted as a hlist. Note that the *first*
    positional argument determines if it's a table or not.
    
highlightline{}
    Contents are concatenated (with space between) and displayed in a highlighted
    line like wp:Template:Navbox before= and after=
    
line{}
    Like list{} except it formats as a straight run of text rather than hlist. Can only contain
    string. ni{} or l{}

ni{}
    Invokes {{NI}} (or a facsimile, anyway)

nid{}
    Invokes {{NID}} (or a facsimile, anyway)
    
l{}
    Invokes the guts of {{L}}

text{}
    You should never need to use this directly. It's what strings are wrapped in.

Strings will be presented literally in the output. Functions are allowed: in the positional
arguments to any of the above functions, any function will be evaluated in the same way as 
navbox{}, and then be replaced by the positional elements of the table it returns. Same deal
in the named arguments except the return is used directly. In the interests of non-silly
output, strings are joined with a space.

line{ "a", function() return { "1", "2" } end, "b" } is equivalent to,
line{ "a", "1", "2", "b" }

list{ title = function() return "foo" end, "1", "2" } is equivalent to,
list{ title = "foo", "1", "2" }

The exported function, navbox(), has three template parameters: {
    [1] = Name of the navbox definition to use
    name = template name for generating the v.d.e links
}
]]

local ipairs, pairs, rawget, type = ipairs, pairs, rawget, type

local util = require("Module:Utility_functions")
local language = require("Module:Language")

local element = {
    addDataToOutput = function(self, context, output, options)
        local oldmod = context.mod
        context.mod = self.data.mod or oldmod or "UNDEFINED"
        local oldmodname = context.modname
        context.modname = self.data.modname or oldmodname or "Undefined"
        for i, item in ipairs(self.data) do
            output((options.surround or {''})[1])
            self:expandItem(item, context, output, options.spacer)
            output((options.surround or {'',''})[2])
            if (i < #(self.data)) and options.separator then
                output(options.separator)
            end
        end
        context.mod = oldmod
    end,
    
    expandItem = function(self, item, context, output, spacer)
        if type(item) == "table" then
            item:expand(context, output)
        else
            output(tostring(item or ''))
        end
        output(spacer or '')
    end,
    
    expandOrMessage = function(self, item, context, output, spacer, message)
        if item then
            self:expandItem(item, context, output, spacer)
        else
            output(message)
        end
    end
}

-- The navbox as a whole.
element.navbox = {
    expand = function(self, context, output)
        output('<table class="navbox" cellspacing="0"><tr><td style="padding: 2px;">')
        output('<table class="nowraplinks')
        output(' mw-collapsible ')
        if context.state == "plain" or context.state == "off" then
            output('mw-collapsed')
        end
        output('" cellspacing="0" style="width:100%; background: transparent; color: inherit;"')
        output(' data-expandtext="show" data-collapsetext="hide">')
        output('<tr><th colspan="2" class="navbox-title"><span style="float:left;width:6em;text-align:left;">')
        output(mw.getCurrentFrame():expandTemplate{title="Navbar", args={mini="1", context.name}})
        output('</span><span style="font-size: 110%">')
        self:expandOrMessage(self.data.title, context, output, ' ', '!!! Missing title !!!')
        output('</span>')
        output('</th></tr>')
        output('<tr style="height: 2px;"><td></td></tr>')
        
        self:addDataToOutput(context, output, {separator = '<tr style="height: 2px;"><td></td></tr>'})
        
        output('</table>')
        output('</td></tr></table>')
    end
}

-- The collapsible groups that are the first division of the navbox.
element.group = {
    expand = function(self, context, output)
        context.odd = true
        
        output('<tr><td colspan="2" class="navbox-list" style="width:100%; padding:0px;">')
        output('<div style="padding:0em 0.25em"> </div>')
        output('<table class="nowraplinks mw-collapsible')
        -- These two semicolons stops something from wrapping tables in gratuitous divs
        output(' navbox-subgroup" cellspacing="0" style="width:100%;"')
        output(' data-expandtext="show" data-collapsetext="hide">')
        output('<tr><th class="navbox-title" colspan="2">')
        output('<span style="float:left;width:6em;">&nbsp;</span><span style="font-size: 100%;">')
        self:expandOrMessage(self.data.title, context, output, ' ', '!!! Missing title !!!')
        output('</span></th></tr>')
        output('<tr style="height: 2px;"><td></td></tr>')
        
        self:addDataToOutput(context, output, {spacer = '', separator='<tr style="height: 2px;"><td></td></tr>'})
        
        output('</table>')
        output('</td></tr>')
    end
}

-- A group-level thingy that displays text in a contrasting background.
element.highlightline = {
    expand = function(self, context, output)
        output('<tr><td colspan="2" class="navbox-abovebelow">')
        self:addDataToOutput(context, output, {spacer = ' '})
        output('</td></tr>')
    end
}

-- The remaining subdivisions of the navbox; with the title at the left, optionally.
element.list = {
    expand = function(self, context, output)
        if #(self.data) == 0 then
            return
        end

        local hasChildLists = (type(self.data[1]) == "table") and ((self.data[1].elementType == "list") or (self.data[1].elementType == "line"))
        
        output('<tr>')
        if self.data.title then
            output('<td class="navbox-group" style="padding-left: 0em; padding-right:0em;"><div style="padding:0em 0.75em;">')
            self:expandItem(self.data.title, context, output)
            output('</div></td>')
            output('<td style="text-align:left;border-left-width:2px;border-left-style:solid;width:100%;padding:0px;"')
        else
            output('<td colspan="2" style="')
            if hasChildLists then
                output('text-align:left;')
            end
            output('border-left-width:2px;border-left-style:solid;width:100%;padding:0px;"')
        end
        
        if context.odd then
            output(' class="navbox-list navbox-odd">')
        else
            output(' class="navbox-list navbox-even">')
        end
        
        
        if hasChildLists then
            output('<table class="nowraplinks navbox-subgroup" cellspacing="0" style="width: 100%;">')
            self:addDataToOutput(context, output, {separator = '<tr style="height: 2px;"><td></td></tr>'})
            output('</table>')
        else
            context.odd = not context.odd
            output('<ul class="hlist" style="padding:0em 0.25em; margin: 0;">')
            self:addDataToOutput(context, output, {surround = {' <li class="nowrap"> ',' </li> '}})
            output('</ul>')
        end
        
        output('</td></tr>')
    end
}

-- Like list, except it doesn't format as a list.
element.line = {
    expand = function(self, context, output)
        output('<tr>')
        if self.data.title then
            output('<th>' .. tostring(self.data.title) .. '</th>')
            output('<td>')
        else
            output('<td colspan="2">')
        end
        
        self:addDataToOutput(context, output, {spacer = ' '})
        
        output('</td></tr>')
    end
}

-- Invocation of {{NI}}, or at least the equivalent thereof.
element.ni = {
    expand = function(self, context, output)
        if self.data.icon then
            output("[[File:" .. self.data.icon .. "|16px|link=]]")
        else
            local iconargs = {
            	"",
                item = (self.data.gicon or self.data[1]),
                size=16,
                mod=(self.data.mod or context.mod)
            }
            output(mw.getCurrentFrame():callParserFunction("#icon", iconargs))
        end
        output("&nbsp;")
        local linkargs = { self.data[2] or self.data[1], (self.data[3] or self.data[2]) or mw.getCurrentFrame():callParserFunction("#iconloc", {self.data[1], self.data.mod or context.mod, "name", mw.title.getCurrentTitle().subpageText:lower()})}
        output(language.link(linkargs))
    end
}

-- Invocation of {{NID}}, or at least the equivalent thereof.
element.nid = {
    expand = function(self, context, output)
       if self.data.icon then
            output("[[File:" .. self.data.icon .. "|16px|link=]]")
        else
            local iconargs = {
            	"",
                item = (self.data.gicon or self.data[1]),
                size = 16,
                mod = (self.data.mod or context.mod)
            }
            output(mw.getCurrentFrame():callParserFunction("#icon", iconargs))
        end
        output("&nbsp;")
        local linkargs = { self.data[1] .. " (" .. context.modname .. ")", (self.data[2]) or mw.getCurrentFrame():callParserFunction("#iconloc", {self.data[1], self.data.mod or context.mod, "name", mw.title.getCurrentTitle().subpageText:lower()})}
        output(language.link(linkargs))
    end
}

-- Invocation of {{L}}, or an equivalent.
element.l = {
    expand = function(self, context, output)
        output(language.link(self.data))
    end
}

-- literal string
element.text = {
    expand = function(self, context, output)
        output(self.data)
    end
}

-- This environment is interposed in the one available to the function in definition.navbox.
local definitionEnvironment = {}

-- Note that this doesn't handle arguments!
--[[local function invokeInDefinition(func)
    local origEnv = getfenv(func)
    local newEnv = {}
    setmetatable(newenv, { __index = function(tab, key)
        return definitionEnvironment[key] or origEnv[key]
    end})
    setfenv(func, newEnv)
    local result = func()
    setfenv(func, origEnv)
    return result
end]]

-- stub because getfenv doesn't exist.
local function invokeInDefinition(func)
    return func()
end

local function createElement(name, metatable, args)
    local newthing = { elementType = name, data = {} }
    setmetatable(newthing, element[name])

    for k, v in pairs(args) do
        if type(k) == "number" then
	        if type(v) == "function" then
	            for j, w in ipairs(invokeInDefinition(v)) do
	                table.insert(newthing.data, w)
	            end
	        else
	            table.insert(newthing.data, v)
	        end
        else
	        if type(v) == "function" then
	            newthing.data[k] = invokeInDefinition(v)
	        else
	            newthing.data[k] = v
	        end
        end
    end

    return newthing
end

for name, metatable in pairs(element) do
    if type(metatable) == "table" then
        metatable.__index = function(tab, key)
            return (rawget(tab, key) or rawget(metatable, key)) or rawget(element, key)
        end
        definitionEnvironment[name] = function(args)
            return createElement(name, metatable, args)
        end
    end
end

local p = {}

local function tryLoadModule(name)
    return pcall(function()
        return require(name)
    end)
end

local function getParameter(frame, name)
    if frame.args.fromParent then
        return frame.args[name] or frame:getParent().args[name]
    else
        return frame.args[name]
    end
end

function p.navbox(frame)
    local out = ""
    local output = function(text)
        out = out .. tostring(text or '')
    end

    local success = false
    local module = {}
    local moduleName = "Module:Navbox/" .. frame.args[1]
    if frame.args.forceUntranslated then
        success, module = tryLoadModule(moduleName)
        if not success then
            return "!!! Could not load [[" .. moduleName .. "]] : " .. module .. " !!!"
        end
    else
        success, module = tryLoadModule(moduleName .. util.pageSuffix())
        if not success then
            output("!!! Could not load [[" .. moduleName  .. util.pageSuffix() .. "]], loading untranslated fallback. " .. module .. " !!!")
            success, module = tryLoadModule(moduleName)
            if not success then
                return out .. "<br />!!! Could not load [[" .. moduleName .. "]] : " .. module .. " !!!"
            end
        end
    end
    
    local navboxModule = module
    local navboxData = navboxModule.navbox(
        definitionEnvironment.navbox,
        definitionEnvironment.highlightline,
        definitionEnvironment.group,
        definitionEnvironment.list,
        definitionEnvironment.line,
        definitionEnvironment.ni,
        definitionEnvironment.nid,
        definitionEnvironment.l)
    
    local context = {}
    context.name = frame.args.name
    context.state = getParameter(frame, "state") or "autocollapse"
    context.opengroups = {}
    -- opengroups is a set, not a list.
    for str in string.gmatch(getParameter(frame, "opengroups") or '', "%S+") do
        context.opengroups[str] = true
    end

    output('<div style="display:none">')
    output(util.table_print(frame.args))
    output('</div>')
    
    navboxData:expand(context, output)
    return out
end

return p