Module:Sandbox/ChaoticShadow/InfoboxBuilder

Documentation for this module may be created at Module:Sandbox/ChaoticShadow/InfoboxBuilder/doc

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 = {
			text = '#000',
			bg   = 'transparent'
		},
		params = {
			{ name = 'bg color' },
			{ name = 'text color' }
		},
		paramnames = { 'bg color', 'text color' },
		args = {},
		final_args = {},
		proto_infobox = {},
		infobox = mw.html.create('table'):addClass('infobox'),
		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 width of the infobox
-- @param arg string
--            Width of the infobox, should be a valid value for the CSS "width" 
--            property, not nil or empty
-- @return self
--         The current object
function InfoboxBuilder:setWidth(arg)
	if arg == nil or arg == '' then
		error("Width must not be nil or empty")
	end
	
	self.infobox:css('width', 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.text = 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.bg = arg
	
	return self
end

--- Sets both the text and background color of the header
-- @param arg { text, bg }
--        text string
--             Same as setHeaderTextColor
--        bg   string
--             Same as setHeaderBackgroundColor
-- @return self
--         The current object
function InfoboxBuilder:setHeaderColors(arg)
	if arg == nil then
		error("Header colors must not be nil")
	end
	
	self:setHeaderTextColor(arg.text)
	self:setHeaderBackgroundColor(arg.bg)
	
	return self
end

--- Sets both the text and background color of the header
-- @param param string
--              Parameter name that helps map the colors
-- @param color_table { text, bg }
--        text string
--             Same as setHeaderTextColor
--        bg string
--             Same as setHeaderBackgroundColor
-- @return self
--         The current object
function InfoboxBuilder:setHeaderColorsByParam(param, color_table)
	if param == nil then
		error("Parameter name must not be nil")
	elseif color_table == nil then
		error("Header color table must not be nil")
	end
	
	local raw_param_value = self.args[param]
	local colors = color_table[raw_param_value]
	
	if colors == nil then
		return self
	end
	
	self:setHeaderTextColor(colors.text)
	self:setHeaderBackgroundColor(colors.bg)
	
	return self
end

--- Sets the infobox params
-- @param ... {{ name, func, default, should_hide }, ...}
--        name    string
--                The name of the parameter, not nil, cannot be duplicate
--        func    function, table or string
--                A function that accepts the parameter as an argument and
--                returns a string, OR
--                A table that has the parameter as a key, OR
--                An empty string
--        default string or nil
--                The default value if no argument is given
-- @return self
--         The current object
function InfoboxBuilder:setParams(...)
	for i, v in ipairs(...) do
		if v.name == nil and v.name == "" then
			error("name must not be nil or empty")
		end
		if self.paramnames[v.name] then
			error("name cannot be duplicate")
		end
		
		self.params[v.name] = {
			['type'] = type(v.func),
			func = v.func,
			default = v.default
		}
		table.insert(self.paramnames, v.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 k,v in pairs(args) do
		if v ~= '' then
			self.args[k] = v
		end
	end
	
	if self.args['bg color'] then
		self:setHeaderBackgroundColor(self.args['bg color'])
	end
	if self.args['text color'] then
		self:setHeaderTextColor(self.args['text color'])
	end
	
	return self
end

--- Gets the content associated with a parameter
-- @param param string
--              The param name, not nil or empty
-- @return content string
--                 A string containing the content
function InfoboxBuilder:getContent(param)
	if param == nil or param == "" then
		error("Param must not be nil or empty")
	end
	
	if self.final_args[param] then
		return self.final_args[param]
	end
	
	local content = nil
	local current_param = self.params[param]
	
	if current_param == nil then
		error(string.format("No such param: %s", param))
	end
	
	local raw_param_value = self.args[param] or current_param.default
	
	if raw_param_value == nil then
		return raw_param_value
	end
	
	if current_param['type'] == 'function' then
		content = current_param.func(raw_param_value)
	elseif current_param['type'] == 'table' then
		content = current_param.func[raw_param_value]
	else
		content = raw_param_value
	end
	
	self.final_args[param] = content
	
	return content
end

--- Adds a header
-- @param arg { content, attr, colspan, rowspan, css }
--        content string or nil The wikitext to be rendered
--        attr    {...} or nil  The attributes of the cell in table form
--        colspan number or nil The colspan of the cell
--        roswpan number or nil The rowspan of the cell
--        css     {...} or nil  The css of the cell in table form
-- @return self
--         The current object
function InfoboxBuilder:addHeader(arg)
	self:addSpacer()
	
	local _cell = self.infobox:tag('tr'):tag('th'):attr('colspan', 30)
	_cell:css({
		['text-align']       = 'center',
		['background-color'] = self.headerColors.bg,
		['color']            = self.headerColors.text
	})
	
	if arg.attr then
		_cell:attr(arg.attr)
	end
	if arg.colspan then
		_cell:attr('colspan', arg.colspan)
	end
	if arg.rowspan then
		_cell:attr('rowspan', arg.rowspan)
	end
	if arg.css then
		_cell:css(arg.css)
	end
	
	if arg.tag == 'th' then
		_cell:wikitext(arg.content)
	elseif arg.tag == 'argth' then
		_cell:wikitext(self:getContent(arg.content))
	end
	
	self:addSpacer()
	
	return self
end

--- Adds an image, or switchable images
-- @param ... { { tag, content, title }, ... }
--        tag     "artd" or "td" Whether or not an it is based off a parameter
--        content string         The content or the parameter name
--        title   string or nil  The title, if using switchable images
-- @return self
--         The current object
function InfoboxBuilder:addImage(...)
	local argt = ...
	
	local _cell = self.infobox:tag('tr'):tag('td'):css('text-align', 'center')
	local content = '?'
	
	local actual_args = {}
	for _,v in ipairs(argt) do
		local c = v.content
		if v.tag == 'argtd' then
			c = self:getContent(c)
		end
		
		if c ~= nil then
			table.insert(actual_args, { title = v.title, content = c })
		end
	end
	
	if #actual_args == 0 then
		return self
	elseif #actual_args < 2 then
		content = actual_args[1].content
	else
		local t = {}
		for _,v in ipairs(actual_args) do
			table.insert(t, v.title .. '=' .. v.content)
		end
		
		content = mw.getCurrentFrame():callParserFunction({
					name = '#tag',
					args = { 'tabber', table.concat(t, '|-|') }
				})
	end
	
	_cell:attr('colspan', 30)
	_cell:wikitext(content)
	
	return self
end

--- Adds a row, with columns up to 30 columns spanned
-- @param should_hide boolean
--        The row will be hidden if all varying columns are nil
-- @param cols    { { tag, content, hide, attr, colspan, rowspan, css }, ... }
--        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        Content to be used as wikitext or a parameter
--                              name
--        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
-- @return self
--         The current object
function InfoboxBuilder:addRow(should_hide, cols)
	local actual_values = {}
	
	for i,v in ipairs(cols) do
		if v.tag == 'argth' or v.tag == 'argtd' then
			table.insert(actual_values, self:getContent(v.content))
		end
	end
	
	if should_hide and #actual_values == 0 then
		return self
	end
	
	local _row = self.infobox:tag('tr')
	for i,v in ipairs(cols) do
		local _cell = _row:tag(tagmap[v.tag] or 'td')
						:attr('colspan', 30 / #cols)
		
		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.css then
			_cell:css(v.css)
		end
		
		if v.tag == 'th' or v.tag == 'td' then
			_cell:wikitext(v.content)
		elseif v.tag == 'argth' or v.tag == 'argtd' then
			_cell:wikitext(self:getContent(v.content))
		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)
			:css('width', 'calc(100% / 30)')
	end
end

--- Adds links to the bottom of the infobox
function InfoboxBuilder:addLinks()
	if not self.finished then
		self.finished = true
		
		local links = {
			"[[Template:%s|<span style=\"color: %s;\">View</span>]]",
			"[[Template talk:%s|<span style=\"color: %s;\">Talk</span>]]"
		}
		self:addHeader{
			tag = 'th',
			content = 
				string.format(links[1], self.name, self.headerColors.text)
				.. " &bull; " ..
				string.format(links[2], self.name, self.headerColors.text)
		}
	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 tostring(self.infobox)
end

return InfoboxBuilder