Module:Horizontal infobox
Appearance
Documentation for this module may be created at Module:Horizontal infobox/doc
-- ================================================================================
-- Module:Horizontal infobox
-- ================================================================================
-- Renders a compact horizontal infobox for use inline within article sections.
-- Designed as a supplementary box (e.g. life history, diet summary) that sits
-- in the body of a species page alongside prose, below or above section headers.
--
-- Usage from wikitext (via a wrapper template):
-- {{#invoke:Horizontal infobox|section|...}}
--
-- Or called directly from another Lua module:
-- local hib = require("Module:Horizontal infobox")
-- local html = hib.build({ label = "Life history", species = "Melanoplus differentialis",
-- theme = "species", cells = { {label="Egg hatch", value="April – May"}, ... } })
-- ================================================================================
local getArgs = require('Module:Arguments').getArgs
local hib = {}
-- ================================================================================
-- Internal: resolve theme class suffix
-- ================================================================================
-- Accepts "species", "organization", "resource", "geography", "experimental" (case-insensitive).
-- Falls back to "species" for anything unrecognised.
local function resolve_theme(raw)
local t = (raw or "species"):lower():match("^%s*(.-)%s*$")
local valid = { species=true, organization=true, resource=true, geography=true, experimental=true }
return valid[t] and t or "species"
end
-- ================================================================================
-- Internal: build a single cell <div>
-- ================================================================================
local function make_cell(label, value)
if not label or label == "" or not value or value == "" then
return nil
end
local cell = mw.html.create('div')
:addClass('ib-h-cell')
cell:tag('div')
:addClass('ib-h-label')
:wikitext(label)
:done()
cell:tag('div')
:addClass('ib-h-value')
:wikitext(value)
:done()
return cell
end
-- ================================================================================
-- Internal: how many columns a box of `n` cells should lay out in, so the last row is never a lone
-- orphan when a nicer split exists (5 -> 3+2, not 4+1). Styled by .ib-h-cols-N in Common.css, which
-- falls back to a fluid layout on narrow screens.
-- ================================================================================
local COLUMNS_BY_COUNT = { 1, 2, 3, 4, 3, 3, 4, 4, 3 }
local function column_count(n)
return COLUMNS_BY_COUNT[n] or 4
end
-- ================================================================================
-- hib.build(opts)
-- ================================================================================
-- The core builder. Call this from other Lua modules.
--
-- opts (table):
-- label (string) Section label shown in the pill badge. Required.
-- species (string) Species name shown in italics in the header. Optional.
-- subtitle (string) Plain (non-italic) header text, for boxes not about a taxon. Optional;
-- shown only when species is blank.
-- theme (string) "species" | "organization" | "resource" | "geography" | "experimental"
-- cells (table) Array of { label=string, value=string } pairs. Required.
--
-- Returns: HTML string ready for mw.text output or tostring().
-- ================================================================================
function hib.build(opts)
opts = opts or {}
local badge_label = opts.label or "Summary"
local species = opts.species or nil
local subtitle = opts.subtitle or nil
local theme = resolve_theme(opts.theme)
local cells = opts.cells or {}
-- Nothing to render if no cells provided
if #cells == 0 then
return ""
end
-- ── Outer container ──────────────────────────────────────────────
local root = mw.html.create('div')
:addClass('ib-h')
-- ── Header row ───────────────────────────────────────────────────
local header = root:tag('div')
:addClass('ib-h-header')
-- Pill badge
header:tag('span')
:addClass('ib-h-badge')
:addClass('ib-h-badge-' .. theme)
:wikitext(badge_label)
:done()
-- Species name (italic, muted)
if species and species ~= "" then
header:tag('span')
:addClass('ib-h-species')
:wikitext("<i>" .. species .. "</i>")
:done()
elseif subtitle and subtitle ~= "" then
header:tag('span')
:addClass('ib-h-species')
:addClass('ib-h-subtitle')
:wikitext(subtitle)
:done()
end
header:done()
-- ── Cell grid ────────────────────────────────────────────────────
local built = {}
for _, cell_data in ipairs(cells) do
local cell = make_cell(cell_data.label, cell_data.value)
if cell then
table.insert(built, cell)
end
end
local grid = root:tag('div')
:addClass('ib-h-cells')
:addClass('ib-h-cols-' .. column_count(#built))
for _, cell in ipairs(built) do
grid:node(cell)
end
grid:done()
return tostring(root)
end
-- ================================================================================
-- hib.section(frame)
-- ================================================================================
-- Entry point for {{#invoke:Horizontal infobox|section}}.
-- Template authors pass named args; this function assembles opts and calls build().
--
-- Template args:
-- |label= Section name for the pill badge (e.g. "Life history")
-- |species= Species name (e.g. "Melanoplus differentialis")
-- |subtitle= Plain header text instead of a species name (ignored if |species= is set)
-- |theme= species / organization / resource / geography / experimental
-- |cell1_label= Label for cell 1
-- |cell1_value= Value for cell 1
-- |cell2_label= Label for cell 2
-- |cell2_value= Value for cell 2
-- ...up to cell20 (expand below if more are needed)
-- ================================================================================
function hib.section(frame)
local args = getArgs(frame)
-- Assemble cells from numbered arg pairs
local cells = {}
for i = 1, 20 do
local lbl = args["cell" .. i .. "_label"]
local val = args["cell" .. i .. "_value"]
if lbl and lbl ~= "" and val and val ~= "" then
table.insert(cells, { label = lbl, value = val })
end
end
local opts = {
label = args.label or "Summary",
species = args.species or nil,
subtitle = args.subtitle or nil,
theme = args.theme or "species",
cells = cells,
}
return hib.build(opts)
end
-- ================================================================================
-- hib.life_history(frame)
-- ================================================================================
-- A higher-level entry point that accepts semantically named life-history args
-- and maps them to cells automatically. Intended to be called from a dedicated
-- {{Life history infobox}} template, or from Module:Species infobox directly.
--
-- Recognized args / cargo fields (all optional — omitted if blank):
-- otu_id Used to query Cargo if record not passed directly.
-- egg_hatch "April – May"
-- nymph_instars "5 – 6"
-- adult_months "July – October"
-- overwintering_stage "Egg"
-- voltinism "Univoltine"
-- generation_time "1 year"
-- mating_system "Polygynous"
-- habitat "Grasslands, cropland edges"
--
-- Can also be called from Lua with pre-fetched values:
-- hib.life_history_from_record(record, species_name)
-- ================================================================================
-- Field definitions: { arg_key, display_label }
local LIFE_HISTORY_FIELDS = {
{ "egg_hatch", "Egg hatch" },
{ "nymph_instars", "Instars" },
{ "adult_months", "Adult flight" },
{ "overwintering_stage", "Overwinters as" },
{ "voltinism", "Voltinism" },
{ "generation_time", "Generation time" },
{ "mating_system", "Mating system" },
{ "habitat", "Habitat" },
}
function hib.life_history(frame)
local args = getArgs(frame)
local cells = {}
for _, pair in ipairs(LIFE_HISTORY_FIELDS) do
local key = pair[1]
local label = pair[2]
local val = args[key]
if val and val ~= "" then
table.insert(cells, { label = label, value = val })
end
end
return hib.build({
label = "Life history",
species = args.species or nil,
theme = "species",
cells = cells,
})
end
-- ================================================================================
-- hib.life_history_from_record(record, species_name)
-- ================================================================================
-- Called directly from Module:Species infobox (or similar) with a pre-fetched
-- Cargo/JSON record table. Maps standard field names to display cells.
-- Returns an HTML string, or "" if the record has no relevant data.
-- ================================================================================
function hib.life_history_from_record(record, species_name)
if not record then return "" end
local cells = {}
for _, pair in ipairs(LIFE_HISTORY_FIELDS) do
local key = pair[1]
local label = pair[2]
-- Support both snake_case and Title_case field names in the record
local val = record[key] or record[key:gsub("^%l", string.upper)]
if val and tostring(val) ~= "" then
table.insert(cells, { label = label, value = tostring(val) })
end
end
return hib.build({
label = "Life history",
species = species_name or nil,
theme = "species",
cells = cells,
})
end
return hib