Jump to content

Module:Horizontal infobox

From HopperWiki

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
Cookies help us deliver our services. By using our services, you agree to our use of cookies.