Module:Infobox: Difference between revisions

Clean up, annotated. Finalized, hopefully not many bugs
(Fix tabs)
(Clean up, annotated. Finalized, hopefully not many bugs)
Line 6:
-- Edit button for unknown params
local editbutton = require('Module:Edit button')
local edit = editbutton("'''?''' (edit)")
 
local pagename = mw.title.getCurrentTitle().fullText
 
local Infobox = {}
Infobox.__index = Infobox
 
 
--[[
Infobox class
-- config: table containing configuration parameters
-- infobox_name: the name of the infobox
-- argsparams : parametersdefinitions fromfor frameeach toused pass throughvalue
-- args : values passed from the Template
-- Special args: version, default_version
---- the following values are treated specially: default_version, version, version1, version2...
-- Sets a meta table and creates a <div> tag wrapper
-- other fields are initialized in other functions
--]]
function Infobox.new(config, params, args)
Line 32 ⟶ 29:
default_version = 1, -- default version to populate the infobox
versions = nil, -- number of versions
version_names = {'INFOBOX_ERROR'}, -- title of each version (for selection and SMW); version_name[0] should not be used (base index should be 1)
rtable = nil, -- infobox table to return at the end
switch_datatable = nil, -- datatable for javascript for switch infoboxes
categories = {}, -- set of categories
errors = {}, -- list of errors
__smw_debug = {}, -- debug data for smw subobjects
},
Infobox)
-- Step 1, setup config vars and count the versions
obj:config(config)
obj:parse_versions()
-- Step 2, process the params
obj:create()
obj:define_params(params)
obj:parse_params()
-- Table
obj:table_header()
obj:buttons()
-- Misc
obj:store_smw()
obj:parse_categories()
return obj
end
 
 
--[[
function Infobox:config(...)
Refers to a param after being processed by the validating func
for k, v in pairs(...) do
Used in add_row(), define_params() and is_param_defined()
if k == 'infobox_name' then
--]]
self.infobox_name = mw.ustring.gsub(v, '%s', '_')
function Infobox.param(param_name)
elseif k == 'max_buttons' then
param = {
self.max_buttons = tonumber(v)
property = 'args_parsed',
param_name = param_name,
}
return param
end
 
 
--[[
Refers to a param in raw form as passed from the Template
Used in add_row(), define_params() and is_param_defined()
--]]
function Infobox.raw_param(param_name)
param = {
property = 'args_raw',
param_name = param_name,
}
return param
end
 
 
--[[
Refers to a param after being processed by the validating smw_func
Used in add_row(), define_params() and is_param_defined()
--]]
function Infobox.smw_param(param_name)
param = {
property = 'args_smw',
param_name = param_name,
}
return param
end
 
 
--[[
Checks to see if a param is defined.
-- param: Infobox.param(name), Infobox.raw_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
]]
function Infobox:is_param_defined(param)
local undefined = 0
local defined = 0
for version = 1, self.versions do
local value = self:get_param(param, version)
if value ~= nil then
defined = 1
else
undefined = 1
end
end
return 1 + defined - undefined
if self.infobox_name == nil then
end
table.insert(self.errors, 'infobox_name needs to be defined in Infobox.new()\'s config!')
 
 
--[[
Adds a row to the infobox table
Parameter should be a table of cells, where each cell is a table with the following arguments:
-- tag : 'td' or 'th'
-- content : a string or Infobox.param(name), Infobox.raw_param(name), Infobox.smw_param(name)
-- attr (optional) : mw.html:attr({ arg1 = '1', ... })
-- css (optional) : mw.html:css({ arg1 = '1', ...)
-- class (optional) : mw.html:addClass('arg')
---- class also supports a table of values, even though mw.html:addClass() does not
---- common classes: infobox-subheader
-- rowspan (optional) : mw.html:attr('rowspan',arg)
-- colspan (optional) : mw.html:attr('colspan',arg)
-- title (optional) : mw.html:attr('title',arg)
 
The row itself may be assigned a single class by setting the value of the key addClass
-- addClass : mw.html:addClass('arg')
-- this function currently only supports a single string
--]]
function Infobox:add_row(...)
local args = ...
local _row = self.rtable:tag('tr')
-- For each cell
for _, v in ipairs(args) do
local _cell = _row:tag(v.tag)
-- Optional parameters
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 v.class then
if type(v.class) == 'string' then
_cell:addClass(v.class)
-- mw.html:addClass() doesn't function with tables, add in a loop
elseif type(v.class) == 'table' then
for _, w in ipairs(v.class) do
_cell:addClass(w)
end
end
end
-- Populate the cell contents
local content = self:get_param(v.content, self.default_version)
if content == nil then
content = self.params[v.content.param_name].empty
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
 
 
--[[
Adds a blank row of padding spanning the given number of columns
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:pad(colspan, class)
local tr = self:tag('tr'):tag('td'):attr('colspan', colspan or 1):addClass('infobox-padding')
if class then
tr:addClass(class)
end
return self
end
 
 
function Infobox:define_params(...)
--[[
for _, v in ipairs(...) do
Adds a class to the table as a whole
-- For every parameter, store its corresponding function to self.params
--]]
if v.name then
function Infobox:addClass(arg)
local param = {}
self.rtable:addClass(arg)
-- Copy the function
return self
if type(v.func) == 'function' or type(v.func) == 'table' then
end
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
Setup config values
param.smw_property = v.smw_property
-- config: table containing configuration parameters
if type(v.smw_func) == 'function' or type(v.smw_func) == 'table' then
---- infobox_name = mandatory unique identifier for this infobox, used for css
param.smw_func = v.smw_func
---- max_buttuons = max number of switch buttons before using a dropdown list instead
else
--]]
param.smw_func = param.func
function Infobox:config(config)
end
for k, v in pairs(config) do
end
if k == 'infobox_name' then
self.params[v.name] = param
self.infobox_name = mw.ustring.gsub(v, '%s', '_')
table.insert(self.param_names, v.name)
elseif k == 'max_buttons' then
self.max_buttons = tonumber(v)
end
end
if self.infobox_name == nil then
self:parse_params()
table.insert(self.errors, 'infobox_name needs to be defined in Infobox.new()\'s config!')
--self:parse_categories()
end
return self
end
 
 
--[[
Counts the number of versions in the infobox, and populates version_names
Populates Infobox.version_names
Sets Infobox.default_version
--]]
function Infobox:parse_versions()
-- Count the versions and setup self.version_names
local i = 1
while self.args_raw['version'..i] do
Line 107 ⟶ 239:
end
self.versions = i - 1
-- Handle the no version case - mightcheck havefor a custom version_name
if self.versions == 0 then
table.insert(self.version_names, self.args_raw['version'] or 'DEFAULT')
end
-- Should either have 0 or 2+ versions
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
-- Check for a default_version
if self.args_raw['default_version'] then
self.default_version = tonumber(self.args_raw['default_version'])
Line 123 ⟶ 257:
end
 
-- Value is not defined if "action=edit" exists in string, otherwise
-- value is defined if not nil and non-empty string
function Infobox.is_value_defined(value)
if value == nil then
return false
end
if type(value) ~= 'string' then
return true
end
if value:find('action=edit') then
return false
end
if value:find('%S') then
return true
end
return false
end
 
--[[
Define all used parameters (except for default_version/version)
Checks to see if a param is defined.
-- name : parameter name as used in the Template
Returns 0 if param is never defined. Returns 1 if param is defined for some versions. Returns 2 if param is defined for all versions.
-- func : function to validate and process the Template argument
-- param: a table generated from Infobox:param(), Infobox:raw_param() or Infobox:smw_param()
-- If func is a function, will call func(Infobox.raw_param(name))
]]
-- If func is a table, contains the following key-value pairs:
function Infobox:is_param_defined(param)
---- name : function
local undefined = 0
---- params : list of arguments to pass to the function (use Infobox.raw_param(),
local defined = 0
---- Infobox.param() and Infobox.smw_param() to use arguments)
for version = 1, self.versions do
-- empty (optional) : text to display in the infobox if func returns nil, defaults to "? (edit)"
local value = self:get_param(param, version)
-- category_never : category to add if func returns nil for all versions
if self.is_value_defined(value) then
-- category_partial : category to add if func returns nil for some versions, but a value for other versions
defined = 1
-- category_incomplete : category to add if func returns nil for at least 1 version (i.e. category_never and category_partial combined)
else
-- category_complete : category to add if func returns a value for all versions
undefined = 1
-- 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
--]]
function Infobox:define_params(...)
-- For every parameter, store its corresponding function to self.params
for _, v in ipairs(...) do
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
-- If empty is not defined, default message is "? (edit)"
param.empty = v.empty or editbutton("'''?''' (edit)")
-- Get the category names
param.category_never = v.category_never
param.category_partial = v.category_partial
param.category_incomplete = v.category_incomplete
param.category_complete = v.category_complete
-- Store the param
self.params[v.name] = param
table.insert(self.param_names, v.name)
end
end
return 1 + defined - undefinedself
end
 
 
--[[
Fetches a param value. If the value is nil, will return the default value instead
-- arg: a non-table generatedvalue from(string, number, etc), or Infobox:.param(name), Infobox:.raw_param(name) or, Infobox:.smw_param(name), or else
-- version: 0/'' for default, or else a number
Returns arg if a constant
param value (or default value) if Infobox.param(name), Infobox.raw_param(name), Infobox.smw_param(name)
--]]
function Infobox:get_param(arg, version)
Line 169 ⟶ 321:
version = ''
end
-- Handle Infobox.param(), Infobox.raw_param(), Infobox.smw_param()
if type(arg) == 'table' then
local value = self[arg.property][arg.param_name..version]
if-- value ==If nil, thengrab --default Tryvalue to(which getcould defaultbe value ifnil itas existswell)
if value == nil then
value = self[arg.property][arg.param_name]
end
return value
end
-- Everything else passes through unmodified (string, number, etc)
-- Other (int, string)
return arg
end
 
 
--[[
Calculates the parsed value of a param
-- param_name : string,param_name nameas ofa the paramstring
-- version : 0/'' for default, or else a number
-- smw : boolean, whether towill use thesmw_func smwif functiontrue, or defaultfunc is functionfalse
--]]
function Infobox:parse_param(param_name, version, smw)
Line 211 ⟶ 366:
 
 
--[[
Parses all the param values (args_parsed and args_smw)
--]]
function Infobox:parse_params()
-- Calculate the param value for all params and all versions
Line 216 ⟶ 374:
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_rawargs_parsed[param_name..version] then= self:parse_param(param_name, version, false)
-- Only get the smw value if smw_property is defined
self.args_parsed[param_name..version] = self:parse_param(param_name, version, false)
-- Only get the smw value if self.params[param_name].smw_property is definedthen
if self.paramsargs_smw[param_name].smw_property.version] = self:parse_param(param_name, version, thentrue)
self.args_smw[param_name..version] = self:parse_param(param_name, version, true)
end
end
end
Line 231 ⟶ 387:
 
 
--[[
function Infobox:store_smw()
Creates the table header
-- Abort if not in mainspace
--]]
if mw.title.getCurrentTitle().namespace ~= 0 then
function Infobox:table_header()
return false
-- Create infobox table
self.rtable = mw.html.create('table')
:addClass('plainlinks')
:addClass('infobox')
:addClass('infobox-'..self.infobox_name)
-- Create the switch datatable if multiple versions
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
-- Store a subobject for each version
for version=1, self.versions do
-- Reminder - subobject name cannot have a . in the first 5 characters
local subobject_name = 'Infobox.'..pagename..'#'..self.version_names[version]
local subobject = {}
-- Store each param that has smw_property defined and has a defined value
for _, param_name in ipairs(self.param_names) do
local property = self.params[param_name].smw_property
if property then
value = self:get_param(self.smw_param(param_name), version)
if self.is_value_defined(value) then
subobject[property] = value
end
end
end
local result = mw.smw.subobject(subobject, subobject_name)
if result ~= true then
table.insert(self.errors, 'SMW error: '..result.error)
end
end
return true
end
 
function Infobox.param(param_name)
param = {
property = 'args_parsed',
param_name = param_name,
}
return param
end
 
--[[
function Infobox.raw_param(param_name)
If multiple versions exist, creates a set of buttons for switching
param = {
-- self.max_buttons : if the number of versions exceeds this value, a dropdown will be created instead
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:buttons()
if self.versions < 2 then
return
end
-- Button caption
local buttons = self.rtable:tag('caption')
:tag('div')
:addClass('infobox-buttons')
:attr('data-default-version', self.default_version)
-- Dropdown list instead of buttons if too many versions
if self.versions > self.max_buttons then
buttons:addClass('infobox-buttons-select')
end
-- Create all the buttons
for version=1, self.versions do
buttons:tag('span')
Line 305 ⟶ 435:
:wikitext(self.version_names[version])
end
end
 
 
function Infobox:create()
--[[
-- Create infobox table
For each version, stores a subobject for all params that have smw_property defined
self.rtable = mw.html.create('table')
--]]
:addClass('plainlinks')
function Infobox:store_smw()
:addClass('infobox')
for version=1, self.versions do
:addClass('infobox-'..self.infobox_name)
-- Generate a subobject name
-- Add necessary class if switch infobox
-- Reminder - subobject name cannot have a . in the first 5 characters
if self.versions > 1 then
local subobject_name = 'Infobox.'..mw.title.getCurrentTitle().fullText..'#'..self.version_names[version]
self.rtable:addClass('infobox-switch')
-- Store each param that has smw_property defined and is not nil
self.switch_datatable = mw.html.create('div')
local subobject = {}
:addClass('infobox-switch-resources')
for _, param_name in ipairs(self.param_names) do
:addClass('infobox-resources-'..self.infobox_name)
local property = self.params[param_name].smw_property
:addClass('hidden')
if property then
self.switch_datatable:tag('span'):wikitext('Versions: '..self.versions)
value = self:get_param(self.smw_param(param_name), version)
self.switch_datatable:tag('span'):wikitext('Default version: '..self.default_version)
if value ~= nil then
self:buttons()
subobject[property] = value
end
end
end
-- Keep a copy for debugging
self.__smw_debug[subobject_name] = subobject
-- Save subobjects if not in mainspace
if mw.title.getCurrentTitle():inNamespace(0) then
local result = mw.smw.subobject(subobject, subobject_name)
if result ~= true then
table.insert(self.errors, 'SMW error: '..result.error)
end
end
end
return self
end
 
 
--[[
Automatically add categories for each param as defined in define_params()
--]]
function Infobox:parse_categories()
for _, param_name in ipairs(self.param_names) do
local param = self.params[param_name]
local defined = self:is_param_defined(Infobox.param(param_name))
if defined == 0 and param.category_never then
self.categories[param.category_never] = 1
end
if defined == 1 and param.category_partial then
self.categories[param.category_partial] = 1
end
if defined < 2 and param.category_incomplete then
self.categories[param.category_incomplete] = 1
end
if defined == 2 and param.category_complete then
self.categories[param.category_complete] = 1
end
end
end
 
 
--[[
When a parameter is added into the table, add the alternative versions' data into the switch datatable
-- content : a non-table value (string, number, etc), or Infobox.param(name), Infobox.raw_param(name), Infobox.smw_param(name)
Returns the name of the parameter in the tag 'data-attr-param'
--]]
function Infobox:add_switch_data(content)
-- Only check for params if there are multiple versions
if self.versions <= 1 then
return false
end
-- Only needed for non-constant params
if type(content) ~= 'table' then
return false
end
-- Only needed if the dataparam varieschanges betweenvalue thebetween 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
Line 350 ⟶ 519:
end
-- Let's build the datatable
-- datum name: Prepend raw__ or smw$__smw__ if not a parsed argument
local name = content.param_name
if content.property == 'args_raw' then
Line 363 ⟶ 532:
text = self:get_param(content, version)
if text == nil then
text = editself.params[content.param_name].empty
end
data_param:tag('span'):attr('data-attr-index', version):wikitext(text)
Line 370 ⟶ 539:
return name
end
 
 
--[[
Override tostring
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') (common class: infobox-subheader)
---- 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_row__tostring(...)
-- NewCreate rowerror to addmessages
local argserror_text = ...''
localfor _row_, =v in ipairs(self.rtable:tag('tr'errors) do
error_text = error_text..'<span class="mw-message-box-error">'..v..'</span>'
-- 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
-- Create categories
 
local category_text = ''
-- allow classes to be defined on the whole row
for key, _ in pairs(self.categories) do
if args.addClass then
category_text = category_text..'[[Category:'..key..']]'
_row:addClass(args.addClass)
end
return tostring(self.rtable) .. tostring(self.switch_datatable) .. error_text .. category_text
 
return self
end
 
 
--[[
Debug function
-- adds a blank row of padding spanning the given number of columns
--]]
function Infobox:pad(colspan, class)
local tr = self:tag('tr')
:tag('td'):attr('colspan', colspan or 1):addClass('infobox-padding')
if class then
tr:addClass(class)
end
return self
end
 
-- addClass
function Infobox:addClass(arg)
self.rtable:addClass(arg)
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()
-- Temporarily detach the metatable so we don't override tostring anymore, allowing us to use dumpObject
setmetatable(self, nil)
mw.log(mw.dumpObject(self))
setmetatable(self, Infobox)
mw.log(tostring(self))
end
 
 
return Infobox