Module:NavboxBuilder
Jump to navigation
Jump to search
Documentation for this module may be created at Module:NavboxBuilder/doc
-- <nowiki>
local NBB = {}
-- Constants
local EXPANDED, COLLAPSED = 1, 2
-- Easy access to all arguments
local arguments = {}
-- Invokable - Navbox creation
function NBB.create(frame)
if frame then arguments = getArguments(frame) end
if type(NBB.hlist) == 'boolean' then NBB.hlist = NBB.hlist and 'hlist' or '' end
if type(NBB.vlist) == 'boolean' then NBB.vlist = NBB.vlist and 'vlist' or '' end
local main = prepMain()
local sections = prepSections(main)
local navbox = ''
if #sections.list > 0 then navbox = makeNavbox(main, sections) end
return navbox
end
-- Invokable - Return parameter documentation
function NBB.documentation(frame)
local docs
if not pcall(function () -- Try specified lang code
local lang = mw.ustring.lower(mw.text.trim(tostring(frame.args[1])))
docs = require('Dev:NavboxBuilder/doc/' .. lang)
end) then pcall(function () -- Then wiki lang
local lang = mw.getContentLanguage():getCode()
docs = require('Dev:NavboxBuilder/doc/' .. lang)
end) end
if not docs then -- Then English
docs = require('Dev:NavboxBuilder/doc/en') -- Then default
end
for k,v in pairs(NBB.params) do
v = mw.ustring.gsub(v, '#', tostring(NBB.n), 1)
v = mw.ustring.gsub(v, '#', tostring(NBB.m), 1)
docs = mw.ustring.gsub(docs, '%%'..k..'%%', v)
end
docs = mw.ustring.gsub(docs, '%%n%%', NBB.n)
docs = mw.ustring.gsub(docs, '%%m%%', NBB.m)
return docs
end
-- Customizing default parameters
function NBB.changeParameters(params)
if type(params) == 'table' then
for k,v in pairs(params) do
if NBB.params[k] then
if k:sub(1, 6) == 'value_' then v = mw.ustring.lower(mw.text.trim(tostring(v) or '')) end
NBB.params[k] = v
end
end
end
return NBB
end
-- Default parameter names
NBB.params = {
-- Settings
links = 'Links',
state = 'State',
-- Fields
title = 'Title',
above = 'Above',
below = 'Below',
limage = 'Left image',
rimage = 'Right image',
-- Sections
header_n = 'Header #',
layout_n = 'Layout #',
state_n = 'State #',
header_state = 'Header state',
-- Table layout
limage_n = 'Left image #',
rimage_n = 'Right image #',
-- Horizontal layout
perrow_n = 'Per row #',
span_n = 'Span #',
-- Groups
group_n = 'Group #',
group_n_m = 'Group #.#',
list_n = 'List #',
list_n_m = 'List #.#',
-- CSS
navbox_class = 'Navbox class',
navbox_style = 'Navbox style',
title_class = 'Title class',
title_style = 'Title style',
base_class = 'Base class',
base_style = 'Base style',
above_class = 'Above class',
above_style = 'Above style',
below_class = 'Below class',
below_style = 'Below style',
image_class = 'Image class',
image_style = 'Image style',
limage_class = 'Left image class',
limage_style = 'Left image style',
rimage_class = 'Right image class',
rimage_style = 'Right image style',
header_class = 'Header class',
header_style = 'Header style',
header_n_class = 'Header # class',
header_n_style = 'Header # style',
limage_n_class = 'Left image # class',
limage_n_style = 'Left image # style',
rimage_n_class = 'Right image # class',
rimage_n_style = 'Right image # style',
group_class = 'Group class',
group_style = 'Group style',
subgroup_class = 'Subgroup class',
subgroup_style = 'Subgroup style',
group_n_class = 'Group # class',
group_n_style = 'Group # style',
group_n_m_class = 'Group #.# class',
group_n_m_style = 'Group #.# style',
list_class = 'List class',
list_style = 'List style',
list_n_class = 'List # class',
list_n_style = 'List # style',
list_n_m_class = 'List #.# class',
list_n_m_style = 'List #.# style',
-- Values
value_expanded = 'expanded',
value_collapsed = 'collapsed',
value_table_layout = 'table',
value_horizontal_layout = 'horizontal',
}
-- How n's and m's are displayed in documentations
NBB.n = '<span class="accent">n</span>'
NBB.m = '<span class="accent">m</span>'
-- Class names for horizontal and vertical lists
NBB.hlist = 'hlist'
NBB.vlist = 'vlist'
-- Cleanup of arguments from frame and its parent
function getArguments(frame, useFrame, useParent)
local args = {
keys = {},
frame = {},
parent = {},
}
if frame and frame.args then
local keys = {}
if useFrame or useFrame == nil then
for k,v in pairs(frame.args) do
if type(k) == 'string' then k = mw.text.trim(k) end
if type(v) == 'string' then v = mw.text.trim(v) end
keys[k] = true
args.frame[k] = v
end
end
if frame.getParent and (useParent or useParent == nil) then
local parent = frame:getParent()
for k,v in pairs(parent.args) do
if type(k) == 'string' then k = mw.text.trim(k) end
if type(v) == 'string' then v = mw.text.trim(v) end
keys[k] = true
args.parent[k] = v
end
end
for k,v in pairs(keys) do table.insert(args.keys, k) end
end
return args
end
-- Returns value of a customized parameter
function getValue(key, n, m)
local param = NBB.params[key]
n, m = tonumber(n), tonumber(m)
if n then param = mw.ustring.gsub(param, '#', tostring(n), 1) end
if m then param = mw.ustring.gsub(param, '#', tostring(m), 1) end
local val = arguments.parent[param] or arguments.frame[param]
if val and val ~= '' then return val, arguments.frame[param], arguments.parent[param] end
return nil
end
-- Checks the input for customized values
function checkValue(val, check)
val = mw.ustring.lower(mw.text.trim(tostring(val) or ''))
if type(check) == 'table' then
for k,v in pairs(check) do
if NBB.params['value_' .. k] then
local test = NBB.params['value_' .. k]
if val == test then return v end
end
end
else
check = tostring(check)
if NBB.params['value_' .. check] then
local test = NBB.params['value_' .. check]
if val == test then return true end
end
end
return nil
end
-- Returns a list of values for parameters with variables
function getList(...)
local lists = {}
local patterns = {}
for i,v in ipairs(arg) do
if v then
lists[i] = {}
patterns[i] = '^'..mw.ustring.gsub(NBB.params[v], '#', '([0-9]+)')..'$'
end
end
for i,k in ipairs(arguments.keys) do
local v = arguments.parent[k] or arguments.frame[k]
if v and v ~= '' then
for i,pattern in ipairs(patterns) do
local n, m = mw.ustring.match(k, pattern)
if m then
if not lists[i][tonumber(n)] then lists[i][tonumber(n)] = {} end
lists[i][tonumber(n)][tonumber(m)] = v
break
elseif n then
lists[i][tonumber(n)] = v
break
end
end
end
end
return unpack(lists)
end
-- Determine which section the row belongs to
function getSectionNo(test, list)
for i=2,#list do
if test < list[i] then return list[i-1] end
end
return list[#list] or nil
end
-- Reads parameters and prepares an object with settings for the navbox
function prepMain()
local main = {}
main.title = getValue('title')
if main.title then
main.links = getValue('links')
main.state = checkValue(getValue('state'), {['expanded'] = EXPANDED, ['collapsed'] = COLLAPSED})
end
main.above = getValue('above')
main.below = getValue('below')
main.limage = getValue('limage')
main.rimage = getValue('rimage')
return main
end
-- Reads parameters and prepares objects with necessary settings for each section
function prepSections(main)
local sections = {[0] = { rows = {} }}
local headers, layouts, lists, sublists = getList('header_n', 'layout_n', 'list_n', 'list_n_m')
for k,v in pairs(headers) do
sections[k] = { header = v, rows = {} }
end
for k,v in pairs(layouts) do
if not sections[k] then
sections[k] = { layout = v, rows = {} }
end
end
local numbers = getKeys(sections, true)
for k,v in pairs(lists) do
local sec = getSectionNo(k, numbers)
sections[sec].rows[k] = { list = v, group = getValue('group_n', k) }
end
for k,list in pairs(sublists) do
local sec = getSectionNo(k, numbers)
local obj = { rows = {}, group = getValue('group_n', k) }
for l,v in pairs(list) do
obj.rows[l] = { list = v, group = getValue('group_n_m', k, l) }
end
sections[sec].rows[k] = obj
end
for _,v in ipairs(numbers) do
local rows = getKeys(sections[v].rows)
if #rows > 0 then
sections[v].state = checkValue(getValue('state_n', v), {['expanded'] = EXPANDED, ['collapsed'] = COLLAPSED})
or checkValue(getValue('header_state'), {['expanded'] = EXPANDED, ['collapsed'] = COLLAPSED})
sections[v].layout = getValue('layout_n', v) or 'table'
else
sections[v] = nil
end
end
sections.list = getKeys(sections, true)
return sections
end
-- Returns a list of keys in a table
function getKeys(tab, sorted)
local keys = {}
if not tab then return keys end
for k,v in pairs(tab) do
table.insert(keys, k)
end
if sorted then table.sort(keys) end
return keys
end
-- Applies styles and classes from parameters to the element
function applyCSS(elem, ...)
if not elem then return nil end
local classes, styles = {}, {}
for i,v in ipairs(arg) do
local c, cP, cF, s, sP, sF
if type(v) == 'table' then
local key = v[1]
v[1] = key .. '_class'
c, cF, cP = getValue(unpack(v))
v[1] = key .. '_style'
s, sF, sP = getValue(unpack(v))
else
v = tostring(v)
c, cF, cP = getValue(v .. '_class')
s, sF, sP = getValue(v .. '_style')
end
if c then
table.insert(classes, cF)
table.insert(classes, cP)
end
if s then
table.insert(styles, sF)
table.insert(styles, sP)
end
end
if #classes > 0 then
classes = mw.ustring.gsub(table.concat(classes, ' '), ' +', ' ')
elem:addClass(classes)
end
if #styles > 0 then
styles = mw.ustring.gsub(table.concat(styles, ';'), ';;+', ';')
styles = mw.ustring.gsub(styles, ';$', '')
elem:cssText(styles)
end
return elem
end
-- Makes an element collapsible
function collapsible(elem, header, state)
if state then
elem:addClass('mw-collapsible')
if state == COLLAPSED then elem:addClass('mw-collapsed') end
if header then header:addClass('mw-collapsible-toggle') end
return true
end
return false
end
-- Creates a navbox with prepared settings
function makeNavbox(main, sections)
-- Structure
local box, title, wrapper, tab, row, links, above, below, limage, rimage, content
box = mw.html.create('div')
if main.title then
title = box:tag('div')
if main.links then links = title:tag('div'):wikitext(main.links .. ' ') end
title:tag('span'):addClass('navbox-title-text'):wikitext(main.title)
end
wrapper = box:tag('div')
tab = wrapper:tag('table')
if main.above then
above = tab:tag('tr'):tag('td'):newline():wikitext(main.above)
above:done():newline()
end
row = tab:tag('tr')
if main.limage then
limage = row:tag('td'):newline():wikitext(main.limage)
limage:done():newline()
end
content = row:tag('td')
for i,v in ipairs(sections.list) do content:node(makeSection(sections[v], v)) end
if #content.nodes == 0 then return nil end
if main.rimage then
rimage = row:tag('td'):newline():wikitext(main.rimage)
rimage:done():newline()
end
if main.below then
below = tab:tag('tr'):tag('td'):newline():wikitext(main.below)
below:done():newline()
end
-- Appearance
box:addClass('navbox')
applyCSS(box, 'navbox')
wrapper:addClass('navbox-table-wrapper')
tab:addClass('navbox-table')
content:addClass('navbox-content')
if title then
title:addClass('navbox-title')
applyCSS(title, 'title')
if collapsible(box, title, main.state) then wrapper:addClass('mw-collapsible-content') end
end
if links then links:addClass('navbox-template-links') end
if above then
above:addClass('navbox-above navbox-base navbox-padding')
applyCSS(above, 'base', 'above')
end
if below then
below:addClass('navbox-below navbox-base navbox-padding')
applyCSS(below, 'base', 'below')
end
if limage then
limage:addClass('navbox-image')
applyCSS(limage, 'image', 'limage')
end
if rimage then
rimage:addClass('navbox-image')
applyCSS(rimage, 'image', 'rimage')
end
if limage or rimage then
local cols = 1 + (limage and 1 or 0) + (rimage and 1 or 0)
if cols > 1 then
if above then above:attr('colspan', cols) end
if below then below:attr('colspan', cols) end
end
end
return box
end
-- Creates a single section
function makeSection(section, no)
-- Structure
local sec, header, wrapper
sec = mw.html.create('div')
if section.header then header = sec:tag('div'):wikitext(section.header) end
wrapper = sec:tag('div')
-- Appearance
sec:addClass('navbox-section')
wrapper:addClass('navbox-section-wrapper')
if header then
header:addClass('navbox-header navbox-base')
if collapsible(sec, header, section.state) then wrapper:addClass('mw-collapsible-content') end
applyCSS(header, 'base', 'header', {'header_n', no})
end
-- Layout
local layout
for k,v in pairs(NBB.formats) do
if checkValue(section.layout, k .. '_layout') then
layout = k
break
else
if k == section.layout then
layout = k
break
end
end
end
layout = layout or 'table'
section.layout = layout
local res = NBB.formats[layout](section, no, wrapper)
if res == nil then return nil end
wrapper:node(res)
wrapper:addClass('navbox-' .. layout .. '-layout')
return sec
end
NBB.formats = {}
NBB.formats.table = function(section, no)
local lists, deep, count, keys = {}, false, 0, getKeys(section.rows, true)
if #keys == 0 then return nil end
-- Structure
local tab, limage, rimage
section.limage = getValue('limage_n', no)
section.rimage = getValue('rimage_n', no)
tab = mw.html.create('table')
for i1,v1 in ipairs(keys) do
count = count + 1
local row1 = section.rows[v1]
-- Structure
local tr, group, list
tr = tab:tag('tr'):addClass('navbox-'..(count % 2 == 0 and 'even' or 'odd'))
if i1 == 1 and section.limage then
limage = tr:tag('td'):newline():wikitext(section.limage)
limage:done():newline()
end
if row1.group then group = tr:tag('th'):wikitext(row1.group) end
local keys = getKeys(row1.rows, true)
if #keys > 0 then
for i2,v2 in ipairs(keys) do
local row2 = row1.rows[v2]
-- Structure
local tr, group, list = tr
if i2 > 1 then
count = count + 1
tr = tab:tag('tr'):addClass('navbox-'..(count % 2 == 0 and 'even' or 'odd'))
end
if row2.group then
group = tr:tag('th'):wikitext(row2.group)
deep = true
end
list = tr:tag('td'):newline():wikitext(row2.list)
list:done():newline()
-- Appearance
list:addClass('navbox-list navbox-padding'):addClass(NBB.hlist or '')
applyCSS(list, 'list', {'list_n_m', v1, v2})
if not row2.group then list:attr('colspan', 2) end
if not row1.group then table.insert(lists, list) end
if group then
group:addClass('navbox-subgroup navbox-base navbox-padding')
applyCSS(group, 'base', 'subgroup', {'group_n_m', v1, v2})
end
end
else
-- Structure
list = tr:tag('td'):newline():wikitext(row1.list)
list:done():newline()
table.insert(lists, list)
-- Appearance
list:addClass('navbox-list navbox-padding'):addClass(NBB.hlist or '')
if not row1.group then list:attr('colspan', 2):addClass('navbox-nogroup') end
applyCSS(list, 'list', {'list_n', v1})
end
if i1 == 1 and section.rimage then
rimage = tr:tag('td'):newline():wikitext(section.rimage)
rimage:done():newline()
end
-- Appearance
if group then
group:addClass('navbox-group navbox-base navbox-padding')
if #keys > 1 then group:attr('rowspan', #keys) end
applyCSS(group, 'base', 'group', {'group_n', v1})
end
end
-- Appearance
tab:addClass('navbox-table')
for i,v in ipairs(lists) do
local span = v:getAttr('colspan') or 1
if deep then span = span + 1 end
if span == 1 then span = nil end
v:attr('colspan', span)
end
if limage then
limage:addClass('navbox-image')
applyCSS(limage, 'image', {'limage_n', no})
if count > 1 then limage:attr('rowspan', count) end
end
if rimage then
rimage:addClass('navbox-image')
applyCSS(rimage, 'image', {'rimage_n', no})
if count > 1 then rimage:attr('rowspan', count) end
end
return tab
end
NBB.formats.horizontal = function(section, no, wrapper)
local lists, deep, count, keys = {}, false, 0, getKeys(section.rows, true)
if #keys == 0 then return nil end
-- Structure
local res, wrap
wrap = tonumber(getValue('perrow_n', no))
if wrap then
wrap = math.max(1, wrap)
-- if wrap > 0 then
-- wrapper:addClass('navbox-static'):css('--wrap', wrap)
-- end
end
res = mw.html.create('')
for i1,v1 in ipairs(keys) do
local row1 = section.rows[v1]
-- Structure
local keys = getKeys(row1.rows, true)
if #keys > 0 then
for i2,v2 in ipairs(keys) do
count = count + 1
local row2 = row1.rows[v2]
-- Structure
local col, group, list
col = res:tag('div'):addClass('navbox-'..(count % 2 == 0 and 'even' or 'odd'))
if row2.group then group = col:tag('div'):wikitext(row2.group) end
if wrap then -- delete in case of css vars
col:css('flex-basis', 100/wrap .. '%')
end
list = col:tag('div'):newline():wikitext(row2.list)
list:done():newline()
table.insert(lists, list)
-- Appearance
col:addClass('navbox-col')
list:addClass('navbox-list navbox-padding'):addClass(NBB.vlist or '')
applyCSS(list, 'list', {'list_n', v1})
if group then
group:addClass('navbox-group navbox-base navbox-padding')
applyCSS(group, 'base', 'group', {'group_n', v1})
end
end
else
count = count + 1
-- Structure
local col, group, list
col = res:tag('div'):addClass('navbox-'..(count % 2 == 0 and 'even' or 'odd'))
if row1.group then group = col:tag('div'):wikitext(row1.group) end
if wrap then
local span = math.max(0, math.min(wrap, tonumber(getValue('span_n', v1)) or 1))
col:css('flex-basis', span/wrap*100 .. '%') -- delete in case of css vars
-- if span ~= 1 then
-- col:css('--span', span)
-- end
else
col._span = tonumber(getValue('span_n', v1))
end
list = col:tag('div'):newline():wikitext(row1.list)
list:done():newline()
table.insert(lists, list)
-- Appearance
col:addClass('navbox-col')
list:addClass('navbox-list navbox-padding'):addClass(NBB.vlist or '')
applyCSS(list, 'list', {'list_n', v1})
if group then
group:addClass('navbox-group navbox-base navbox-padding')
applyCSS(group, 'base', 'group', {'group_n', v1})
end
end
end
if not wrap then
wrap = #lists
for i,v in ipairs(lists) do
local col = v.parent
col:css('flex-basis', math.max(0, math.min(wrap, col._span or 1))/wrap*100 .. '%')
end
end
return res
end
return NBB