Module:Infobox: Difference between revisions

From koreapedia
No edit summary
No edit summary
Line 1: Line 1:
-- Module:Infobox
-- Module:Infobox
-- Central Lua infobox builder: hides empty rows automatically and supports wrapper templates.
-- Dependency-free, Lua-powered infobox builder.
-- - Outputs HTML with CSS classes: .infobox, .infobox-title, .infobox-image, .infobox-caption, .infobox-label, .infobox-data
-- - Auto-hides empty rows
-- - Safe in previews (guards getParent())
-- - Optional 'order' control in wrappers: | order = Label A|Label B|Label C


local p = {}
local p = {}
-- ========= helpers =========
local function trim(s)
if type(s) ~= "string" then return s end
return mw.text.trim(s)
end


local function isNonEmpty(v)
local function isNonEmpty(v)
Line 13: Line 24:
if not isNonEmpty(filename) then return nil end
if not isNonEmpty(filename) then return nil end
size = size or "280px"
size = size or "280px"
return string.format("[[File:%s|%s|center]]", mw.text.trim(filename), size)
return string.format("[[File:%s|%s|center]]", trim(filename), size)
end
end


-- Builds the table with automatic row hiding
local function splitPipeList(s)
function p.build(frame)
-- Split a pipe-separated string into an array; ignores empty segments
local args = {}
local t = {}
for k, v in pairs(frame:getParent() and frame:getParent().args or {}) do
if not isNonEmpty(s) then return t end
args[k] = v
for seg in tostring(s):gmatch("([^|]+)") do
end
seg = mw.text.trim(seg)
for k, v in pairs(frame.args or {}) do
if seg ~= "" then table.insert(t, seg) end
args[k] = v
end
end
return t
end
-- ========= core table builder =========


local function buildTable(args)
local root = mw.html.create("table"):addClass("infobox")
local root = mw.html.create("table"):addClass("infobox")


Line 55: Line 70:
end
end


-- Auto-detect label/data pairs dynamically
-- Numbered label/data pairs
for i = 1, 50 do
local maxRows = tonumber(args.maxrows) or 50
local label = args["label" .. i]
for i = 1, maxRows do
local data  = args["data"  .. i]
local label = trim(args["label" .. i] or "")
local data  = trim(args["data"  .. i] or "")
if isNonEmpty(label) or isNonEmpty(data) then
if isNonEmpty(label) or isNonEmpty(data) then
local tr = root:tag("tr")
local tr = root:tag("tr")
tr:tag("th"):addClass("infobox-label"):wikitext(label or "")
tr:tag("th"):addClass("infobox-label"):wikitext(label or "")
tr:tag("td"):addClass("infobox-data") :wikitext(data or "")
tr:tag("td"):addClass("infobox-data") :wikitext(data or "")
end
end
end
end
Line 69: Line 85:
end
end


-- Helper: given a mapping table, automatically hides empty values
-- Public: direct builder; merges parent + frame args
function p.build(frame)
local args = {}
-- parent args
if type(frame.getParent) == "function" then
local parent = frame:getParent()
if parent and parent.args then
for k, v in pairs(parent.args) do args[k] = v end
end
end
-- direct args
if frame and frame.args then
for k, v in pairs(frame.args) do args[k] = v end
end
return buildTable(args)
end
 
-- Public: smart wrapper builder
-- Wrappers pass a mapping of "Label = paramName" in frame.args,
-- and end-users supply values via those paramNames.
-- Optional: wrappers can set | order = Label A|Label B|Label C to control row order.
function p.smart(frame)
function p.smart(frame)
-- Defensive: ensure we always have parent args even if getParent() is nil
-- Safe guards for parent
local parent = nil
local parent = nil
if type(frame.getParent) == "function" then
if type(frame.getParent) == "function" then
Line 77: Line 113:
end
end


local args = parent and parent.args or {}
local user = (parent and parent.args) or {}     -- end-user provided values
local map  = frame.args or {}
local map  = frame.args or {}                   -- wrapper's label->param map (plus optional 'order')


-- Build numbered pairs only for non-empty values
local newArgs = {}
local newArgs = {}
local i = 0


-- Preserve order if wrapper provided it
local orderList = splitPipeList(map.order)
local added = 0
local function addRow(label, key)
local value = user[key]
if isNonEmpty(value) then
added = added + 1
newArgs["label" .. added] = label
newArgs["data"  .. added] = value
end
end
-- 1) Ordered labels first (if any)
for _, label in ipairs(orderList) do
local key = map[label]
if isNonEmpty(key) then
addRow(label, key)
end
end
-- 2) Any remaining labels not covered by 'order'
for label, key in pairs(map) do
for label, key in pairs(map) do
local value = args[key]
-- skip control keys
if isNonEmpty(value) then
if label ~= "order" and type(label) == "string" then
i = i + 1
-- If it wasn't already added via order
newArgs["label" .. i] = label
local already = false
newArgs["data"  .. i] = value
for _, olab in ipairs(orderList) do
if olab == label then already = true; break end
end
if not already and isNonEmpty(key) then
addRow(label, key)
end
end
end
end
end


newArgs.title       = args.title or args.name or mw.title.getCurrentTitle().text
-- Pass through common header fields
newArgs.image       = args.image
newArgs.title     = user.title or user.name or mw.title.getCurrentTitle().text
newArgs.image_size = args.image_size
newArgs.image     = user.image
newArgs.caption     = args.caption
newArgs.image_size = user.image_size
newArgs.caption   = user.caption
newArgs.maxrows    = user.maxrows or 50


-- Build via the core function
local fakeFrame = { args = newArgs }
local fakeFrame = { args = newArgs }
return p.build(fakeFrame)
return buildTable(fakeFrame)
end
end


return p
return p

Revision as of 17:54, 8 October 2025

Documentation for this module may be created at Module:Infobox/doc

-- Module:Infobox
-- Dependency-free, Lua-powered infobox builder.
-- - Outputs HTML with CSS classes: .infobox, .infobox-title, .infobox-image, .infobox-caption, .infobox-label, .infobox-data
-- - Auto-hides empty rows
-- - Safe in previews (guards getParent())
-- - Optional 'order' control in wrappers: | order = Label A|Label B|Label C

local p = {}

-- ========= helpers =========

local function trim(s)
	if type(s) ~= "string" then return s end
	return mw.text.trim(s)
end

local function isNonEmpty(v)
	if v == nil then return false end
	if type(v) ~= "string" then return true end
	return mw.text.trim(v) ~= ""
end

local function renderImage(filename, size)
	if not isNonEmpty(filename) then return nil end
	size = size or "280px"
	return string.format("[[File:%s|%s|center]]", trim(filename), size)
end

local function splitPipeList(s)
	-- Split a pipe-separated string into an array; ignores empty segments
	local t = {}
	if not isNonEmpty(s) then return t end
	for seg in tostring(s):gmatch("([^|]+)") do
		seg = mw.text.trim(seg)
		if seg ~= "" then table.insert(t, seg) end
	end
	return t
end

-- ========= core table builder =========

local function buildTable(args)
	local root = mw.html.create("table"):addClass("infobox")

	-- Title
	local title = args.title or args.name or mw.title.getCurrentTitle().text
	root:tag("tr")
		:tag("th")
			:attr("colspan", "2")
			:addClass("infobox-title")
			:wikitext(title)

	-- Optional image
	local image = renderImage(args.image, args.image_size)
	if image then
		root:tag("tr")
			:tag("td")
				:attr("colspan", "2")
				:addClass("infobox-image")
				:wikitext(image)
	end

	-- Optional caption
	if isNonEmpty(args.caption) then
		root:tag("tr")
			:tag("td")
				:attr("colspan", "2")
				:addClass("infobox-caption")
				:wikitext(args.caption)
	end

	-- Numbered label/data pairs
	local maxRows = tonumber(args.maxrows) or 50
	for i = 1, maxRows do
		local label = trim(args["label" .. i] or "")
		local data  = trim(args["data"  .. i] or "")
		if isNonEmpty(label) or isNonEmpty(data) then
			local tr = root:tag("tr")
			tr:tag("th"):addClass("infobox-label"):wikitext(label or "")
			tr:tag("td"):addClass("infobox-data") :wikitext(data  or "")
		end
	end

	return tostring(root)
end

-- Public: direct builder; merges parent + frame args
function p.build(frame)
	local args = {}
	-- parent args
	if type(frame.getParent) == "function" then
		local parent = frame:getParent()
		if parent and parent.args then
			for k, v in pairs(parent.args) do args[k] = v end
		end
	end
	-- direct args
	if frame and frame.args then
		for k, v in pairs(frame.args) do args[k] = v end
	end
	return buildTable(args)
end

-- Public: smart wrapper builder
-- Wrappers pass a mapping of "Label = paramName" in frame.args,
-- and end-users supply values via those paramNames.
-- Optional: wrappers can set | order = Label A|Label B|Label C to control row order.
function p.smart(frame)
	-- Safe guards for parent
	local parent = nil
	if type(frame.getParent) == "function" then
		parent = frame:getParent()
	end

	local user = (parent and parent.args) or {}     -- end-user provided values
	local map  = frame.args or {}                   -- wrapper's label->param map (plus optional 'order')

	-- Build numbered pairs only for non-empty values
	local newArgs = {}

	-- Preserve order if wrapper provided it
	local orderList = splitPipeList(map.order)
	local added = 0

	local function addRow(label, key)
		local value = user[key]
		if isNonEmpty(value) then
			added = added + 1
			newArgs["label" .. added] = label
			newArgs["data"  .. added] = value
		end
	end

	-- 1) Ordered labels first (if any)
	for _, label in ipairs(orderList) do
		local key = map[label]
		if isNonEmpty(key) then
			addRow(label, key)
		end
	end

	-- 2) Any remaining labels not covered by 'order'
	for label, key in pairs(map) do
		-- skip control keys
		if label ~= "order" and type(label) == "string" then
			-- If it wasn't already added via order
			local already = false
			for _, olab in ipairs(orderList) do
				if olab == label then already = true; break end
			end
			if not already and isNonEmpty(key) then
				addRow(label, key)
			end
		end
	end

	-- Pass through common header fields
	newArgs.title      = user.title or user.name or mw.title.getCurrentTitle().text
	newArgs.image      = user.image
	newArgs.image_size = user.image_size
	newArgs.caption    = user.caption
	newArgs.maxrows    = user.maxrows or 50

	-- Build via the core function
	local fakeFrame = { args = newArgs }
	return buildTable(fakeFrame)
end

return p