Module:Navbox
Jump to navigation
Jump to search
Documentation for this module may be created at Module:Navbox/doc
--------------------------------------------------------------------------------
-- Module:Navbox
-- Navigation box system for linking related articles.
-- Wikipedia-compatible with enhanced features.
--
-- INSTALLATION:
-- 1. Create page: Module:Navbox
-- 2. Paste this entire file
-- 3. Create TemplateStyles page: Module:Navbox/styles.css
--
-- USAGE:
-- {{#invoke:Navbox|navbox
-- |name = Navbox Name
-- |title = [[Topic]] Navigation
-- |group1 = Category | list1 = [[Link1]] · [[Link2]]
-- }}
--
-- @author Remilia Wiki
-- @license MIT
--------------------------------------------------------------------------------
local p = {}
--------------------------------------------------------------------------------
-- CONFIGURATION
--------------------------------------------------------------------------------
local MAX_GROUPS = 20 -- Maximum number of groups supported
--------------------------------------------------------------------------------
-- UTILITY FUNCTIONS
--------------------------------------------------------------------------------
local function trim(s)
if not s then return nil end
return tostring(s):match("^%s*(.-)%s*$")
end
local function isEmpty(s)
if s == nil then return true end
if type(s) == 'string' then
return trim(s) == ''
end
return false
end
local function hasValue(s)
return not isEmpty(s)
end
local function getArg(args, name, default)
local val = args[name]
if isEmpty(val) then return default end
return trim(val)
end
local function getFrameArgs(frame)
local args = {}
local parentArgs = frame:getParent() and frame:getParent().args or {}
local directArgs = frame.args or {}
for k, v in pairs(directArgs) do args[k] = v end
for k, v in pairs(parentArgs) do args[k] = v end
return args
end
-- Count the number of content rows (for image rowspan)
local function countRows(args)
local count = 0
-- above row
if hasValue(getArg(args, 'above')) then count = count + 1 end
-- group/list rows
for i = 1, MAX_GROUPS do
local group = getArg(args, 'group' .. i)
local list = getArg(args, 'list' .. i)
if hasValue(list) or hasValue(group) then
count = count + 1
end
end
-- below row
if hasValue(getArg(args, 'below')) then count = count + 1 end
return count
end
--------------------------------------------------------------------------------
-- NAVBOX
--------------------------------------------------------------------------------
function p.navbox(frame)
local args = getFrameArgs(frame)
local name = getArg(args, 'name')
local title = getArg(args, 'title')
local state = getArg(args, 'state', 'autocollapse')
local above = getArg(args, 'above')
local below = getArg(args, 'below')
local image = getArg(args, 'image')
local imageleft = getArg(args, 'imageleft')
local border = getArg(args, 'border')
local bodyclass = getArg(args, 'bodyclass')
local titleclass = getArg(args, 'titleclass')
local listclass = getArg(args, 'listclass')
local groupclass = getArg(args, 'groupclass')
local groupwidth = getArg(args, 'groupwidth')
local evenodd = getArg(args, 'evenodd')
if isEmpty(title) then
return '<span class="error">Error: Navbox title required</span>'
end
-- Calculate number of columns based on images
local baseColspan = 2
if hasValue(image) then baseColspan = baseColspan + 1 end
if hasValue(imageleft) then baseColspan = baseColspan + 1 end
-- Count content rows for image rowspan
local contentRows = countRows(args)
-- Build the navbox table
local tbl = mw.html.create('table')
:addClass('navbox')
-- Handle child/nested navbox
if border == 'child' or border == 'none' then
tbl:addClass('navbox-child')
end
if hasValue(bodyclass) then
tbl:addClass(bodyclass)
end
-- Handle collapse state
if state == 'collapsed' then
tbl:addClass('mw-collapsed')
tbl:addClass('mw-collapsible')
elseif state == 'expanded' then
tbl:addClass('navbox-expanded')
else
tbl:addClass('mw-collapsible')
end
tbl:attr('cellspacing', '0')
-- Title row
local titleRow = tbl:tag('tr')
local titleCell = titleRow:tag('th')
:addClass('navbox-title')
:attr('colspan', tostring(baseColspan))
if hasValue(titleclass) then
titleCell:addClass(titleclass)
end
-- V-T-E links
if hasValue(name) and border ~= 'child' and border ~= 'none' then
local vte = titleCell:tag('div')
:addClass('navbox-vte')
-- Build edit URL
local editUrl = tostring(mw.uri.fullUrl('Template:' .. name, { action = 'edit' }))
vte:wikitext('[')
vte:tag('abbr')
:attr('title', 'View this template')
:wikitext('[[Template:' .. name .. '|v]]')
vte:wikitext(' · ')
vte:tag('abbr')
:attr('title', 'Discuss this template')
:wikitext('[[Template talk:' .. name .. '|t]]')
vte:wikitext(' · ')
vte:tag('abbr')
:attr('title', 'Edit this template')
:wikitext('[' .. editUrl .. ' e]')
vte:wikitext(']')
end
-- Title text
titleCell:tag('span')
:addClass('navbox-title-text')
:wikitext(title)
-- Track row number for evenodd
local rowNum = 0
local imageRowsAdded = false
-- Helper to add image cells
local function addImageCells(row, isFirstContentRow)
if isFirstContentRow then
if hasValue(imageleft) then
row:tag('td')
:addClass('navbox-image')
:attr('rowspan', tostring(contentRows))
:wikitext(imageleft)
end
end
end
local function addRightImageCell(row, isFirstContentRow)
if isFirstContentRow and hasValue(image) then
row:tag('td')
:addClass('navbox-image')
:attr('rowspan', tostring(contentRows))
:wikitext(image)
end
end
-- Get row class based on evenodd setting
local function getRowClass(num)
if evenodd == 'swap' then
return (num % 2 == 0) and 'navbox-odd' or 'navbox-even'
elseif evenodd == 'even' then
return 'navbox-even'
elseif evenodd == 'odd' then
return 'navbox-odd'
else
return (num % 2 == 0) and 'navbox-even' or 'navbox-odd'
end
end
-- Above row
if hasValue(above) then
rowNum = rowNum + 1
local aboveRow = tbl:tag('tr')
addImageCells(aboveRow, not imageRowsAdded)
local aboveColspan = 2
local aboveCell = aboveRow:tag('td')
:addClass('navbox-above')
:attr('colspan', tostring(aboveColspan))
:wikitext(above)
addRightImageCell(aboveRow, not imageRowsAdded)
imageRowsAdded = true
end
-- Group rows
for i = 1, MAX_GROUPS do
local group = getArg(args, 'group' .. i)
local list = getArg(args, 'list' .. i)
if hasValue(list) then
rowNum = rowNum + 1
local row = tbl:tag('tr')
addImageCells(row, not imageRowsAdded)
if hasValue(group) then
local groupCell = row:tag('th')
:addClass('navbox-group')
if hasValue(groupclass) then
groupCell:addClass(groupclass)
end
if hasValue(groupwidth) then
groupCell:cssText('width: ' .. groupwidth)
end
groupCell:wikitext(group)
local listCell = row:tag('td')
:addClass('navbox-list')
:addClass(getRowClass(rowNum))
if hasValue(listclass) then
listCell:addClass(listclass)
end
listCell:wikitext(list)
else
-- List without group header spans full width
local listCell = row:tag('td')
:addClass('navbox-list')
:addClass('navbox-list-full')
:addClass(getRowClass(rowNum))
:attr('colspan', '2')
if hasValue(listclass) then
listCell:addClass(listclass)
end
listCell:wikitext(list)
end
addRightImageCell(row, not imageRowsAdded)
imageRowsAdded = true
elseif hasValue(group) then
-- Group without list (subheader row)
rowNum = rowNum + 1
local row = tbl:tag('tr')
addImageCells(row, not imageRowsAdded)
row:tag('th')
:addClass('navbox-subheader')
:attr('colspan', '2')
:wikitext(group)
addRightImageCell(row, not imageRowsAdded)
imageRowsAdded = true
end
end
-- Below row
if hasValue(below) then
rowNum = rowNum + 1
local belowRow = tbl:tag('tr')
addImageCells(belowRow, not imageRowsAdded)
belowRow:tag('td')
:addClass('navbox-below')
:attr('colspan', '2')
:wikitext(below)
addRightImageCell(belowRow, not imageRowsAdded)
end
return tostring(tbl)
end
--------------------------------------------------------------------------------
-- NAVBAR (V-T-E links only)
--------------------------------------------------------------------------------
function p.navbar(frame)
local args = getFrameArgs(frame)
local name = getArg(args, 1) or getArg(args, 'name')
if isEmpty(name) then
return '<span class="error">Error: Template name required</span>'
end
local mini = getArg(args, 'mini') == 'yes' or getArg(args, 'mini') == '1'
local span = mw.html.create('span')
:addClass('navbar')
if mini then
span:addClass('navbar-mini')
span:wikitext('[')
span:tag('abbr')
:attr('title', 'View this template')
:wikitext('[[Template:' .. name .. '|v]]')
span:wikitext(' · ')
span:tag('abbr')
:attr('title', 'Discuss this template')
:wikitext('[[Template talk:' .. name .. '|t]]')
span:wikitext(' · ')
span:tag('abbr')
:attr('title', 'Edit this template')
:wikitext('[{{fullurl:Template:' .. name .. '|action=edit}} e]')
span:wikitext(']')
else
span:wikitext('[[Template:' .. name .. '|view]] · ')
span:wikitext('[[Template talk:' .. name .. '|talk]] · ')
span:wikitext('[{{fullurl:Template:' .. name .. '|action=edit}} edit]')
end
return tostring(span)
end
--------------------------------------------------------------------------------
-- HLIST (Horizontal list)
--------------------------------------------------------------------------------
function p.hlist(frame)
local args = getFrameArgs(frame)
local separator = getArg(args, 'separator', ' · ')
local items = {}
for i = 1, 20 do
local item = getArg(args, i)
if hasValue(item) then
table.insert(items, item)
end
end
if #items == 0 then
return ''
end
local span = mw.html.create('span')
:addClass('hlist')
:wikitext(table.concat(items, separator))
return tostring(span)
end
--------------------------------------------------------------------------------
-- FLATLIST (Comma-separated list)
--------------------------------------------------------------------------------
function p.flatlist(frame)
local args = getFrameArgs(frame)
local separator = getArg(args, 'separator', ', ')
local items = {}
for i = 1, 20 do
local item = getArg(args, i)
if hasValue(item) then
table.insert(items, item)
end
end
if #items == 0 then
return ''
end
local span = mw.html.create('span')
:addClass('flatlist')
:wikitext(table.concat(items, separator))
return tostring(span)
end
return p