Module:InfoboxBuilder

--- --                      InfoboxBuilder v1.1.0                       -- --- -- Builds an infobox using Lua -- -- Dependencies: -- * Extensions -- ** TemplateStyles -- ** Tabber/TabberNeue -- * Modules -- ** Navbar -- * Styles -- ** Module:InfoboxBuilder/styles.css

local Navbar = require('Module:Navbar')._navbar

local InfoboxBuilder = {} InfoboxBuilder.__index = InfoboxBuilder InfoboxBuilder.__tostring = InfoboxBuilder.tostring

local tagmap = { th = 'th', td = 'td', argth = 'th', argtd = 'td' }

--- Create the infobox -- @return obj metatable --            A metatable describing the infobox function InfoboxBuilder.new local obj = setmetatable({		name = '',		headerColors = {			['color']           = nil,			['background-color'] = nil		},		params = {},		paramNames = { 'bg color', 'text color' },		rawArgs = {},		procArgs = {},		categoryMap = {},		infobox = mw.html.create('table')			:addClass('infobox')			:css('width', '330px'),		finished = false	}, InfoboxBuilder)

return obj end

--- Set the infobox name, for use with bottom links -- @param arg string --           Name of the template, not nil or empty -- @return self --        The current object function InfoboxBuilder:setName(arg) if arg == nil or arg == '' then error("Template name must not be nil or empty") end self.name = arg return self end

--- Set the text color of the header -- @param arg string --           Text color of the header, should be a valid value for the CSS --           "color" property, not nil or empty -- @return self --        The current object function InfoboxBuilder:setHeaderTextColor(arg) if arg == nil or arg == '' then error("Header text color must not be nil or empty") end self.headerColors['color'] = arg return self end

--- Set the background color of the header -- @param arg string --           Background color of the header, should be a valid value for the --           CSS "background-color" property, not nil or empty -- @return self --        The current object function InfoboxBuilder:setHeaderBackgroundColor(arg) if arg == nil or arg == '' then error("Header background color must not be nil or empty") end self.headerColors['background-color'] = arg return self end

--- Sets the infobox params -- @param ... {{ name, fn, default }, ...} --       name    string --               The name of the parameter, not nil, cannot be duplicate --       fn      A function or table that processes the raw arguments --               - A function that accepts the parameter as an argument and --                 returns a string or list, OR --                - A table that has the parameter as a key --       default string or nil --               The default value if no argument is given -- @return self --        The current object function InfoboxBuilder:setParams(...) for _, p in ipairs(...) do		if p.name == nil or p.name == "" then error("param name must not be nil or empty") end if self.paramNames[p.name] then error("param name cannot be duplicate") end self.params[p.name] = { fn = p.fn, default = p.default }		table.insert(self.paramNames, p.name) end return self end

--- Sets the infobox arguments -- @param args Frame --            A frame object, passed in when invoked -- @return self --        The current object function InfoboxBuilder:setArgs(args) for name, value in pairs(args) do		if value ~= '' then self.rawArgs[name] = value end end if self.rawArgs['bg color'] then self:setHeaderBackgroundColor(self.rawArgs['bg color']) end if self.rawArgs['text color'] then self:setHeaderTextColor(self.rawArgs['text color']) end return self end

--- Gets the raw argument values passed -- @return args table --             A table containing the args function InfoboxBuilder:getRawArgs return self.rawArgs end

--- Gets the argument values after being processed -- @return args table --             A table containing the args function InfoboxBuilder:getProcessedArgs return self.procArgs end

--- Gets the argument values of the table, for either raw or processed -- @param which string --             A string that determines which argument values to return -- @retun args table --            A table containing the args function InfoboxBuilder:getArgs(which) if which == 'raw' then return self:getRawArgs elseif which == 'processed' then return self:getProcessedArgs end return {} end

--- Sets the category map for each param -- @param catMap -- @return self --        The current object function InfoboxBuilder:setCategoryMap(catMap) if catMap and type(catMap) == 'table' then self.categoryMap = catMap end return self end

--- Returns categories based on processed args -- @return categories string --                   A string containing with all the categories function InfoboxBuilder:getCategories local categories = {} for name, value in pairs(self.procArgs) do		local catMap = self.categoryMap[name] local valueType = type(value) if catMap then if valueType == "string" or 			valueType == "boolean" or			valueType == "number" then if catMap[value] then table.insert(						categories,						string.format('', catMap[value])					) end elseif valueType == "table" then for _, val in ipairs(value) do					if catMap[val] then table.insert(							categories,							string.format('', catMap[val])						) end end end end end return table.concat(categories, '') end

--- Processes raw args into a format usable by auto categorizer and display functions -- @return self --        The current object function InfoboxBuilder:processArgs for name, param in pairs(self.params) do		local value = self.rawArgs[name] or param.default local fn = param.fn		local fnType = type(fn) if value then if fnType == 'function' then value = fn(value) elseif fnType == 'table' then value = fn[value] end end self.procArgs[name] = value end return self end

--- Gets the content associated with a parameter or parameters -- @param paramNames string or {...} --                  The param name(s), not nil or empty -- @param fn        function --                  A function that turns an array of processed args into a --                   string. The order of parameters is identical to paramNames. --                  Used to transform args into their final display format. -- @return string content --               A string containing the content function InfoboxBuilder:getContent(paramNames, fn) if paramNames == nil then error("Params must not be nil") else if type(paramNames) == "table" then if #paramNames == 0 then error("There must be more than one param used") end elseif type(paramNames) == "string" then paramNames = { paramNames } end end local contents = {} for i, paramName in ipairs(paramNames) do		if paramName == "" then error("Param name must not be empty") elseif self.params[paramName] == nil then error(string.format("No such param with name: %s", paramName)) end -- Must assign by index to preserve nil contents[i] = self.procArgs[paramName] end local content = nil if type(fn) == 'function' then content = fn(unpack(contents)) elseif #paramNames == 1 then content = contents[1] end return content end

--- Determines if the row should be shown based of a list of param names -- @param paramNames { paramName, ... } --       paramName  string --                  The param name -- @return shouldShow boolean function InfoboxBuilder:shouldShow(paramNames) if paramNames and #paramNames > 0 then local actualValues = {} for _, name in ipairs(paramNames) do	       table.insert(actualValues, self:getContent(name)) end if #actualValues == 0 then return false end end return true end

--- Adds a header -- @param arg { tag, content, attr, colspan, rowspan, css } --       tag     "argth" or "th" --               Whether or not an it is based off a parameter --       content string or {...} or nil --               The wikitext to be rendered if th, and the parameter(s) if --                argth --       fn      function --               A function that turns processed arg(s) into their final --               display format. --       attr    {...} or nil --               The attributes of the cell in table form --       colspan number or nil --               The colspan of the cell --       rowspan number or nil --               The rowspan of the cell --       css     {...} or nil --               The css of the cell in table form -- @param options { hideIfEmpty, subheader } --       hideIfEmpty { paramName, ... } --       paramName string --                 The parameter name that will be used to check if the --                 corresponding content is nil --       subheader boolean --                 Whether the header is a subheader or not -- @return self --        The current object function InfoboxBuilder:addHeader(arg, options) local isSubheader = false if options then if not self:shouldShow(options.hideIfEmpty) then return self end if options.subheader then isSubheader = true end end local ibCell = mw.html.create('tr') :tag('th') :attr('colspan', 30) :css(self.headerColors) if not isSubheader then ibCell:addClass('infobox-header') else ibCell:addClass('infobox-subheader') end if arg.attr then ibCell:attr(arg.attr) end if arg.colspan then ibCell:attr('colspan', arg.colspan) end if arg.rowspan then ibCell:attr('rowspan', arg.rowspan) end if arg.css then ibCell:css(arg.css) end if arg.tag == 'th' then ibCell:wikitext(arg.content) elseif arg.tag == 'argth' then ibCell:wikitext(self:getContent(arg.content, arg.fn)) end self:addSpacer self.infobox:node(ibCell) self:addSpacer return self end

--- Adds an image, or switchable images -- @param cols { { tag, content, fn, tabName }, ... } --       tag     "argtd" or "td" --               Whether or not an it is based off a parameter --       content string or {...} --               The wikitext content if td or parameter name(s) if argtd --       fn      function --               A function that turns processed arg(s) into their final --               display format. --       tabName string or nil --               The tab name, if using switchable images --      required boolean or nil --               Whether or not the tab will show regardless if empty or not -- @param options { hideIfEmpty } --       hideIfEmpty { paramName, ... } --       paramName string --                 The parameter name that will be used to check if the --                 corresponding content is nil -- @return self --        The current object function InfoboxBuilder:addImage(cols, options) if options then if not self:shouldShow(options.hideIfEmpty) then return self end end local ibCell = mw.html.create('tr') :tag('td') :addClass('infobox-image') :attr('colspan', 30) local content = nil local actualArgs = {} for _, col in ipairs(cols) do		content = col.content if col.tag == 'argtd' then content = self:getContent(content, col.fn) end if content == nil and col.required == true then table.insert(				actualArgs,				{					tabName = col.tabName,					content = string.format( ' [%s?action=edit ? (edit)] ', tostring(mw.uri.fullUrl(mw.title.getCurrentTitle.text)) )				}			)		end if content ~= nil then table.insert(actualArgs, { tabName = col.tabName, content = content }) end end if #actualArgs == 0 then ibCell:addClass('plainlinks')

content = string.format(			"[%s?action=edit ? (edit)]",			tostring(mw.uri.fullUrl(mw.title.getCurrentTitle.text))		) elseif #actualArgs == 1 then content = actualArgs[1].content else local tabs = {} for _, actArg in ipairs(actualArgs) do table.insert(tabs, actArg.tabName .. '=' .. actArg.content) end content = mw.getCurrentFrame :callParserFunction({				name = '#tag',				args = { 'tabber', table.concat(tabs, '|-|') }			}) end ibCell:wikitext(content or '?') self.infobox:node(ibCell) return self end

--- Adds a row, with columns up to 30 columns spanned -- @param cols   { { tag, content, fn, attr, colspan, rowspan, css, class, cssFuncs, classFuncs }, ... } --       tag        "th", "td", "argth", "argtd" --                  A string containing one of the above, "th" or "td" uses --                  content as the wikitext, "argth" or "argtd" uses content as --                   the parameter name to produce the suitable content --       content    string or {...} --                  Content to be used as wikitext or a parameter name for fn --        fn         function or nil --                  A function that turns processed arg(s) into their final --                  display format. --       attr       {...} or nil --                  The attributes of the cell in table form --       colspan    number or nil --                  The colspan of the cell --       rowspan    number or nil --                  The rowspan of the cell --       css        {...} or nil --                  The css of the cell in table form --       class      {...} or nil --                  The classes of the cell in table form or the class of the --                  cell in string form --       cssFns     { fn, ... } or nil --       fn         function --                  A function that returns css in table form --       classFns   { fn, ... } or nil --       fn         function --                  A function that returns css classes as a table or returns --                  a css class as a table -- @param options { hideIfEmpty, defaultCss } --       hideIfEmpty { paramName, ... } --       paramName   string --                   The parameter name that will be used to check if the --                   corresponding content is nil --       defaultCss  {...} or nil --                   The css of all cells in table form -- @return self --        The current object function InfoboxBuilder:addRow(cols, options) local defaultCss = nil if options then if not self:shouldShow(options.hideIfEmpty) then return self end if options.defaultCss then defaultCss = options.defaultCss end end local ibRow = self.infobox:tag('tr') local colspans = {} if #cols == 2 then colspans = { 12, 18 } end for i, col in ipairs(cols) do		local ibCell = ibRow:tag(tagmap[col.tag] or 'td') :attr('colspan', colspans[i] or (30 / #cols)) if col.attr then ibCell:attr(col.attr) end if col.colspan then ibCell:attr('colspan', col.colspan) end if col.rowspan then ibCell:attr('rowspan', col.rowspan) end if defaultCss then ibCell:css(defaultCss) end if col.css then ibCell:css(col.css) end if col.class then if type(col.class) == 'table' then for _, c in ipairs(coll.class) do					ibCell:addClass(c) end else ibCell:addClass(c) end end if col.tag == 'th' or col.tag == 'td' then ibCell:wikitext(col.content) elseif col.tag == 'argth' or col.tag == 'argtd' then local content = self:getContent(col.content, col.fn) if content ~= nil then if col.cssFns and #col.cssFns > 0 then for _, fn in ipairs(col.cssFns) do						local cellCss = fn(self.procArgs[col.content]) if cellCss then ibCell:css(cellCss) end end end if col.classFns and #col.classFns > 0 then for _, fn in ipairs(col.classFns) do						local cellClasses = fn(self.procArgs[col.content]) if cellClasses then if type(cellClasses) == 'table' then for _, class in ipairs(cellClasses) do									ibCell:addClass(class) end else ibCell:addClass(cellClasses) end end end end ibCell:wikitext(content) else ibCell:addClass('plainlinks') :wikitext(						string.format( "[%s?action=edit ? (edit)]", tostring(mw.uri.fullUrl(mw.title.getCurrentTitle.text)) )					)			end end end return self end

--- Creates the 30-col layout function InfoboxBuilder:addSpacer local spacer = self.infobox:tag('tr') for i=1,30,1 do		spacer:tag('td') :attr('colspan', 1) :addClass('spacer-cell') end end

--- Adds links to the bottom of the infobox function InfoboxBuilder:addLinks if not self.finished then self.finished = true self:addRow end end

--- Generates the infobox -- @return string --        The html of the infobox function InfoboxBuilder:tostring if not self.finished then self:addLinks end self.finished = true return mw.getCurrentFrame:extensionTag{ name = 'templatestyles', args = { src = 'Module:InfoboxBuilder/styles.css' } } ..		tostring(self.infobox) end

return InfoboxBuilder