Module:Infobox

From Brighter Shores Wiki
Revision as of 02:55, 25 March 2024 by Gau Cho (talk | contribs) (Barebones functional infobox module, WIP)
Jump to navigation Jump to search
Module documentation
This documentation is transcluded from Module:Infobox/doc. [edit] [history] [purge]
Module:Infobox requires Module:Edit button.

Creating a template step-by-step

Import Module:Infobox and Module:Param Parse

local Infobox = require('Module:Infobox')
local parse = require('Module:Param Parse')

Unpack the frame arguments from the Template

local p = {}

function p.main(frame)
	local args = frame:getParent().args
	...

Setup the Module config settings

	local config = {
		infobox_name = 'Scenery',
		class = {Infobox.smw_param('episode')}, -- Add css class with episode name to colorize Infobox
	}

Map your arguments to parsing functions

Use the params in Module:Param Parse to validate and format the data - feel free to create your own new params in Module:Param Parse.

	local params = {
    	parse.name,
        parse.image,
		{name = 'description', func = parse.has_content, smw_property = 'Description'}, -- Custom param
        parse.episode,
 		...
	}

Define an Infobox object

	local infobox = Infobox.new(config, params, args)

Create your table

	infobox
		:add_row{
			{tag='th', content=Infobox.param('name'), class='infobox-header', colspan='20'},
		}
		:add_row{
			{tag='td', content=Infobox.param('image'), class='infobox-image', colspan='20'},
		}
		:pad("20")
		:add_row{
			{tag='td', content='Info', class='infobox-subheader', colspan='20'},
		}
		:pad("20")
		:add_row{
			{tag='th', content='Description', colspan="6"},
			{tag='td', content=Infobox.param('description'), colspan="14"},
		}
		:add_row{
			{tag='th', content='[[Episode]]', colspan="6"},
			{tag='td', content=Infobox.param('episode'), colspan="14"},
		}
		...


You're done!

	return infobox
end

return p

Functions

Special params

You don't need to do anything about these special parameters, but they may be used as parameters within the Template:

param explanation
version1, version2, version3 Button label and SMW name for each switch version of the infobox
default_version The default version to display when the page is loaded
version If there is only a single version, you can use version to set the SMW name (default SMW name is "DEFAULT")

Referring to params

Each parameter can have a different value for each version. In addition, there are 3 different representations of each value. Therefore, a parameter must be accessed via one of the 3 helper functions:

helper function example explanation
Infobox.raw_param(name) "1000" Raw value as passed by the Template
Infobox.param(name) "1,000" Value formatted for display in the Infobox
Infobox.smw_param(name) 1000 Value formatted to be saved as an SMW property

Infobox.new(config, params, args)

Creates a new infobox. Automatically parses the arguments, creates SMW subobjects and adds categories.

	local infobox = Infobox.new(config, params, args)

config

There are only 3 parameters for config

	local config = {
    	infobox_name = 'Scenery', -- mandatory unique identifier for css
        class = {'CustomClass', Infobox.smw_param('episode')} -- optional, defaults to {}. Adds css classes to infobox table: {'infobox-CustomClass', 'infobox-[default version parameter's value]'}
    	max_buttons = 6, -- optional, defaults to 6, max number of switch buttons before using a dropdown list instead
	}

params

A list of parameters to be processed by the Infobox module

	local params = {
		{ name = <param>, func = <func>, ... },
		...
	}


key value
name parameter name as used in the Template
func A function in Module:Param Parse to validate and process the Template argument. You can also use a local function, but this is not recommended.

If func is a function, will call func(Infobox.raw_param(name)):

	{name = <param>, func = <func>, ... },

If func is a table, it takes the following parameters:

	{name = <param>, func = { name = <func>, params = <func_params>}, ... },
name function in Module:Param Parse
params a list of parameters to pass the the function, in the form of constants, or Infobox.raw_param(name), Infobox.param(name), Infobox.smw_param(name)
empty (optional) text to display in the infobox if func returns nil; defaults to "? (edit)"
category_never (optional) category to add if func returns nil for all versions
category_partial (optional) category to add if func returns nil for some versions, but a value for other versions
category_incomplete (optional) category to add if func returns nil for at least 1 version (i.e. category_never and category_partial combined)
category_complete (optional) category to add if func returns a value for all versions
smw_property (optional) if this string is defined, the parameter will be saved into SMW
smw_func (optional) function to validate and process the Template argument to save into SMW. func is used by default if smw_func is not defined

args

Arguments passed via the Template

	local args = frame:getParent().args

infobox:is_param_defined(param)

Used to conditionally display a line in the infobox

	infobox:add_row{
		{tag='th', content=Infobox.param('name'), class='infobox.subheader', colspan='2'},
	}
	if infobox:is_param_defined(Infobox.param('owner')) > 0 then
		infobox:add_row{
			{tag='td', content='[[Owner]]'},
			{tag='td', content=Infobox.param('owner')},
		}
	end
param a parameter referenced via Infobox.raw_param(name), Infobox.param(name) or Infobox.smw_param(name)
Returns 0 if param is never defined

1 if param is defined for a fraction of the versions

2 if param is defined for all versions

Infobox:add_row(...)

Adds a row to the infobox table. Parameter should be a table of cells:

	infobox:add_row{
		{tag='td', content='[[Cell1]]', ...},
		{tag='td', content=Infobox.param('param'), ...},
		{tag='td', content='[[Cell3]]', ...},
        ...
        addClass = 'row-class'
	}

Each cell should have a set of key-values, of which only the tag and content are mandatory:

key value
tag 'td' or 'th'
content a string or Infobox.param(name), Infobox.raw_param(name), Infobox.smw_param(name)
attr (optional) a table of attributes to add to the cell

mw.html:attr({ arg1 = '1', ... })

css (optional) a table of css to add to the cell

mw.html:css({ arg1 = '1', ... })

class (optional) a string with a class to add, or a table of classes to add. infobox-header, infobox-subheader and infobox-image are commonly used.

mw.html:addClass(...)

rowspan (optional) Set the cell rowspan

mw.html:attr('rowspan',arg)

rowspan (optional) Set the cell colspan

mw.html:attr('colspan',arg)

title (optional) Set the cell title

mw.html:attr('title',arg)


Infobox:pad(colspan, class)

Adds a blank row of padding spanning the given number of columns. Will always add the class infobox-padding, but can optionally add another class:

	infobox:pad("10", class=<class>)


Infobox:addClass(class)

Adds a class to the entire table

	infobox:addClass(class)


Infobox:dump()

Logs all the values into the Debug console for debugging purposes. You can also dump all the values in an Infobox template by setting a template parameter "__dump = Yes".


--[=[
-- For documentation, see [[Module:Infobox/doc]]
--]=]

-- <nowiki>
-- Edit button for unknown params
local editbutton = require('Module:Edit button')
local edit = editbutton("'''?''' (edit)")

local Infobox = {}
Infobox.__index = Infobox

--[[
	Infobox class
	-- infobox_name: the name of the infobox
	-- args : parameters from frame to pass through
    -- Special args: version, default_version
	-- Sets a meta table and creates a <div> tag wrapper
	-- other fields are initialized in other functions
--]]
function Infobox.new(infobox_name, args)
	local obj = setmetatable({
				args_raw = args, -- parameters (uncleaned)
				args_parsed = {}, -- parsed parameters
				args_smw = {}, -- parameters parsed for smw
				infobox_name = nil, -- template name
				param_names = {}, -- ordered param list
				params = {}, -- param definitions
                default_version = 1, -- default version to populate the infobox
				versions = nil, -- number of versions
				version_names = {}, -- title of each version (for selection and SMW)
                rtable = nil, -- infobox table to return at the end
                switch_datatable = nil, -- datatable for javascript for switch infoboxes
				errors = {}, -- list of errors
				},
			Infobox)
	obj.infobox_name = mw.ustring.gsub(infobox_name, '%s', '_')
	obj:parse_versions()
    obj:create()
	return obj
end


--[[
	Function for defining parameters
	-- name : parameter name
	-- func : function to define param, defaults to looking at blanks
	DO NOT DEFINE VERSION HERE
	USE :maxVersion()
	Can be used any number of times for efficient definition
--]]

function Infobox:define_params(...)
	for _, v in ipairs(...) do
		-- For every parameter, store its corresponding function to self.params
		if v.name then
			local param = {}
			-- Copy the function
			if type(v.func) == 'function' or type(v.func) == 'table' then
				param.func = v.func
			end
			-- If smw_property is defined, then use smw_func, or default to func if it is not defined
			if v.smw_property then
				param.smw_property = v.smw_property
				if type(v.smw_func) == 'function' or type(v.smw_func) == 'table' then
					param.smw_func = v.smw_func
				else
					param.smw_func = param.func
				end
			end
			self.params[v.name] = param
			table.insert(self.param_names, v.name)
		end
	end
    self:parse_params()
	return self
end

--[[
	Counts the number of versions in the infobox, and populates version_names
--]]
function Infobox:parse_versions()
	-- Count the versions
	local i = 1
	while self.args_raw['version'..i] do
		table.insert(self.version_names, self.args_raw['version'..i])
		i = i + 1
	end
	self.versions = i - 1
	-- Handle the no version case - might have a custom version_name
	if self.versions == 0 then
		table.insert(self.version_names, self.args_raw['version'] or 'DEFAULT')
	end
    if self.versions == 1 then
        table.insert(self.errors, 'There should be multiple versions or no versions. If defining a custom version name for a single entry, use "version=Name" instead of "version1=Name".')
        self.versions = 0
    end
    if self.args_raw['default_version'] then
        self.default_version = tonumber(self.args_raw['default_version'])
    end
end


--[[
	Fetches a param value. If the value is nil, will return the default value instead
    -- arg: a table generated from Infobox:param(), Infobox:raw_param() or Infobox:smw_param(), or else
    -- version: '' for default, or else a number
--]]
function Infobox:get_param(arg, version)
    if version == 0 then
        version = ''
    end
    if type(arg) == 'table' then
	    local value = self[arg.property][arg.param_name..version]
	    if value == nil then -- Try to get default value if it exists
	        value = self[arg.property][arg.param_name]
	    end
	    return value
    end
    -- Other (int, string)
    return arg
end

--[[
	Calculates the parsed value of a param
    -- param_name : string, name of the param
    -- version : 0/'' for default, or else a number
    -- smw : boolean, whether to use the smw function or default function
--]]
function Infobox:parse_param(param_name, version, smw)
    if version == 0 then
        version = ''
    end
    -- use func or smw_func depending on smw argument
    local param = self.params[param_name]
    local func = smw and param.smw_func or param.func
    -- call functions by passing the param_name
    if type(func) == 'function' then
        return func(self:get_param(self.raw_param(param_name), version))
    -- call tables by grabbing the function and reading the param arguments
    elseif type(func) == 'table' then
        local func_name = func.name
        local func_params = func.params
        local func_fetched_params = {}
        for _, func_param in ipairs(func_params) do
            table.insert(func_fetched_params, self:get_param(func_param, version))
        end
        return func_name(unpack(func_fetched_params))
    else
        table.insert(self.errors, 'Invalid param definition for '..param_name)
    end
end


function Infobox:parse_params()
	-- Calculate the param value for all params and all versions
	for _, param_name in ipairs(self.param_names) do
        for version=0,self.versions do
            if version == 0 then
                version = '' -- default version
            end
            -- Only get the parsed value if the raw value is defined
            if self.args_raw[param_name..version] then
                self.args_parsed[param_name..version] = self:parse_param(param_name, version, false)
                if self.params[param_name].smw_property then
                    self.args_smw[param_name..version] = self:parse_param(param_name, version, true)
                end
            end
        end
    end
end


function Infobox.param(param_name)
	param = {
		property = 'args_parsed',
		param_name = param_name,
	}
	return param
end

function Infobox.raw_param(param_name)
	param = {
		property = 'args_raw',
		param_name = param_name,
	}
	return param
end

function Infobox.smw_param(param_name)
	param = {
		property = 'args_smw',
		param_name = param_name,
	}
	return param
end

-----------
-- Table --
-----------
function Infobox:create()
	-- Create infobox table
	self.rtable = mw.html.create('table')
                    :addClass('plainlinks')
                    :addClass('infobox')
                    :addClass('infobox-'..self.infobox_name)
	-- Add necessary class if switch infobox
	if self.versions > 1 then
		self.rtable:addClass('infobox-switch')
        self.switch_datatable = mw.html.create('div')
                                    :addClass('infobox-switch-resources')
                                    :addClass('infobox-resources-'..self.infobox_name)
                                    :addClass('hidden')
        self.switch_datatable:tag('span'):wikitext('Versions: '..self.versions)
        self.switch_datatable:tag('span'):wikitext('Default version: '..self.default_version)
	end
    return self
end

--[[
	Add parameters functions
	All parameters should be tables
	The first parameter defines the type of cell to create
		-- tag = 'th' or 'td'
	The second parameter defines what is inside the tag
		-- content = string or Infobox.param
	Additional named parameters can be used to add any styling or attributes
		-- attr : mw.html:attr({ arg1 = '1', ... })
		-- css : mw.html:css({ arg1 = '1', ...)
		-- class : mw.html:addClass('arg')
		---- class also supports a table of values, even though mw.html:addClass() does not
		-- rowspan : mw.html:attr('rowspan',arg)
		-- colspan : mw.html:attr('colspan',arg)
		-- title : mw.html:attr('title',arg)
	Example:
		ipsobox:addRow( { 'th' , 'Header', title = 'Title' },
				{ 'argh', 'arg1', class = 'parameter' } })
	produces:
		<tr><th title="Title">Header</th><th class="parameter">args.arg1</th></tr>

	adding it to the infobox table of ipsobox

	Cells defined as 'argh' and 'argd' will automatically have data-attr-param="" added, and defined as the passed argument if the infobox in creation is defined as a switch infobox

	The row itself may be modified with metadata using the named index "addClass'
		-- addClass : mw.html:addClass('arg')
		-- this function currently only supports a single string
--]]

function Infobox:add_switch_data(content)
    -- Only check for params if there are multiple versions
    if self.versions <= 1 then
        return false
    end
    if type(content) ~= 'table' then
        return false
    end
    -- Only if the data varies between the different versions
    local first_value = self:get_param(cell_params.content, 1)
    local all_same = true
    for version=2, self.versions do
        if first_value ~= self:get_param(cell_params.content, version) then
            all_same = false
            break
        end
    end
    if all_same then
        return false
    end
    -- Let's build the datatable
    -- Prepend raw__ or smw$<__ if not a parsed argument
    local name = content.param_name
    if content.property == 'args_raw' then
        name = 'raw__' + name
    end
    if content.property == 'args_smw' then
        name = 'smw__' + name
    end
    data_param = self.switch_datatable:tag('span'):attr('data-attr-param', name)
    -- Add each version to the datatable
    for version=1, self.versions do
        text = self:get_param(content, version)
        if text == nil then
            text = edit
        end
        data_param:tag('span'):attr('data-attr-index', version):wikitext(text)
    end
    -- return the 'data-attr-param' name
    return name
end

function Infobox:add_row(...)
	-- New row to add
	local args = ...
	local _row = self.rtable:tag('tr')
	-- For each member of tags
	for _, v in ipairs(args) do
		local _cell = _row:tag(v.tag)

		-- mw.html:attr() and mw.html:css() both accept table input
		-- colspan, rowspan, title will be quick ways to access attr
		-- these functions also do all the necessary work
		if v.attr then
			_cell:attr(v.attr)
		end
		if v.colspan then
			_cell:attr('colspan',v.colspan)
		end
		if v.rowspan then
			_cell:attr('rowspan',v.rowspan)
		end
		if v.title then
			_cell:attr('title',v.title)
		end
		if v.css then
			_cell:css(v.css)
		end

		-- if class is a string, it can be added directly
		-- if a table, add every value
		-- mw.html:addClass() doesn't function with tables
		-- so iterate over the class names here and add them individually
		if v.class then
			if type(v.class) == 'string' then
				_cell:addClass(v.class)
			elseif type(v.class) == 'table' then
				for _, w in ipairs(v.class) do
					_cell:addClass(w)
				end
			end
		end

        local content = Infobox:get_param(v.content, self.default_version)
        if content == nil then
            content = edit
        end
        _cell:wikitext(content)

        -- Add the switch data if multiple values exist
        data_attr_param = self:add_switch_data(v.content)
        if data_attr_param then
            _cell:attr('data-attr-param', data_attr_param)
        end
	end

	-- allow classes to be defined on the whole row
	if args.addClass then
        _row:addClass(args.addClass)
	end

	return self
end

-- Override tostring
function Infobox:tostring()
    error_text = ''
    if #self.errors > 0 then
        for _, v in ipairs(self.errors) do
            error_text = error_text..'<span class="mw-message-box-error">'..v..'</span>'
        end
    end
	return tostring(self.rtable) .. tostring(self.switch_datatable) .. tostring(error_text)
end



function Infobox:dump()
	mw.log(mw.dumpObject(self))
	mw.log(tostring(self))
end

return Infobox