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

  1. --
  2. -- This module implements {{Navbox}}
  3. --
  4.  
  5. local p = {}
  6.  
  7. local navbar = require('Module:Navbar')._navbar
  8. local getArgs -- lazily initialized
  9.  
  10. local args
  11. local tableRowAdded = false
  12. local border
  13. local listnums = {}
  14.  
  15. local function trim(s)
  16. return (mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1"))
  17. end
  18.  
  19. local function addNewline(s)
  20. if s:match('^[*:;#]') or s:match('^{|') then
  21. return '\n' .. s ..'\n'
  22. else
  23. return s
  24. end
  25. end
  26.  
  27. local function addTableRow(tbl)
  28. -- If any other rows have already been added, then we add a 2px gutter row.
  29. if tableRowAdded then
  30. tbl
  31. :tag('tr')
  32. :css('height', '2px')
  33. :tag('td')
  34. :attr('colspan',2)
  35. end
  36.  
  37. tableRowAdded = true
  38.  
  39. return tbl:tag('tr')
  40. end
  41.  
  42. local function renderNavBar(titleCell)
  43. -- Depending on the presence of the navbar and/or show/hide link, we may need to add a spacer div on the left
  44. -- or right to keep the title centered.
  45. local spacerSide = nil
  46.  
  47. if args.navbar == 'off' then
  48. -- No navbar, and client wants no spacer, i.e. wants the title to be shifted to the left. If there's
  49. -- also no show/hide link, then we need a spacer on the right to achieve the left shift.
  50. if args.state == 'plain' then spacerSide = 'right' end
  51. elseif args.navbar == 'plain' or (not args.name and mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '') == 'Template:Navbox') then
  52. -- No navbar. Need a spacer on the left to balance out the width of the show/hide link.
  53. if args.state ~= 'plain' then spacerSide = 'left' end
  54. else
  55. -- Will render navbar (or error message). If there's no show/hide link, need a spacer on the right
  56. -- to balance out the width of the navbar.
  57. if args.state == 'plain' then spacerSide = 'right' end
  58.  
  59. titleCell:wikitext(navbar{
  60. args.name,
  61. mini = 1,
  62. fontstyle = (args.basestyle or '') .. ';' .. (args.titlestyle or '') .. ';background:none transparent;border:none;'
  63. })
  64. end
  65.  
  66. -- Render the spacer div.
  67. if spacerSide then
  68. titleCell
  69. :tag('span')
  70. :css('float', spacerSide)
  71. :css('width', '6em')
  72. :wikitext(' ')
  73. end
  74. end
  75.  
  76. --
  77. -- Title row
  78. --
  79. local function renderTitleRow(tbl)
  80. if not args.title then return end
  81.  
  82. local titleRow = addTableRow(tbl)
  83.  
  84. if args.titlegroup then
  85. titleRow
  86. :tag('th')
  87. :attr('scope', 'row')
  88. :addClass('navbox-group')
  89. :addClass(args.titlegroupclass)
  90. :cssText(args.basestyle)
  91. :cssText(args.groupstyle)
  92. :cssText(args.titlegroupstyle)
  93. :wikitext(args.titlegroup)
  94. end
  95.  
  96. local titleCell = titleRow:tag('th'):attr('scope', 'col')
  97.  
  98. if args.titlegroup then
  99. titleCell
  100. :css('border-left', '2px solid #fdfdfd')
  101. :css('width', '100%')
  102. end
  103.  
  104. local titleColspan = 2
  105. if args.imageleft then titleColspan = titleColspan + 1 end
  106. if args.image then titleColspan = titleColspan + 1 end
  107. if args.titlegroup then titleColspan = titleColspan - 1 end
  108.  
  109. titleCell
  110. :cssText(args.basestyle)
  111. :cssText(args.titlestyle)
  112. :addClass('navbox-title')
  113. :attr('colspan', titleColspan)
  114.  
  115. renderNavBar(titleCell)
  116.  
  117. titleCell
  118. :tag('div')
  119. :addClass(args.titleclass)
  120. :css('font-size', '114%')
  121. :wikitext(addNewline(args.title))
  122. end
  123.  
  124. --
  125. -- Above/Below rows
  126. --
  127.  
  128. local function getAboveBelowColspan()
  129. local ret = 2
  130. if args.imageleft then ret = ret + 1 end
  131. if args.image then ret = ret + 1 end
  132. return ret
  133. end
  134.  
  135. local function renderAboveRow(tbl)
  136. if not args.above then return end
  137.  
  138. addTableRow(tbl)
  139. :tag('td')
  140. :addClass('navbox-abovebelow')
  141. :addClass(args.aboveclass)
  142. :cssText(args.basestyle)
  143. :cssText(args.abovestyle)
  144. :attr('colspan', getAboveBelowColspan())
  145. :tag('div')
  146. :wikitext(addNewline(args.above))
  147. end
  148.  
  149. local function renderBelowRow(tbl)
  150. if not args.below then return end
  151.  
  152. addTableRow(tbl)
  153. :tag('td')
  154. :addClass('navbox-abovebelow')
  155. :addClass(args.belowclass)
  156. :cssText(args.basestyle)
  157. :cssText(args.belowstyle)
  158. :attr('colspan', getAboveBelowColspan())
  159. :tag('div')
  160. :wikitext(addNewline(args.below))
  161. end
  162.  
  163. --
  164. -- List rows
  165. --
  166. local function renderListRow(tbl, listnum)
  167. local row = addTableRow(tbl)
  168.  
  169. if listnum == 1 and args.imageleft then
  170. row
  171. :tag('td')
  172. :addClass('navbox-image')
  173. :addClass(args.imageclass)
  174. :css('width', '0%')
  175. :css('padding', '0px 2px 0px 0px')
  176. :cssText(args.imageleftstyle)
  177. :attr('rowspan', 2 * #listnums - 1)
  178. :tag('div')
  179. :wikitext(addNewline(args.imageleft))
  180. end
  181.  
  182. if args['group' .. listnum] then
  183. local groupCell = row:tag('th')
  184.  
  185. groupCell
  186. :attr('scope', 'row')
  187. :addClass('navbox-group')
  188. :addClass(args.groupclass)
  189. :cssText(args.basestyle)
  190.  
  191. if args.groupwidth then
  192. groupCell:css('width', args.groupwidth)
  193. end
  194.  
  195. groupCell
  196. :cssText(args.groupstyle)
  197. :cssText(args['group' .. listnum .. 'style'])
  198. :wikitext(args['group' .. listnum])
  199. end
  200.  
  201. local listCell = row:tag('td')
  202.  
  203. if args['group' .. listnum] then
  204. listCell
  205. :css('text-align', 'left')
  206. :css('border-left-width', '2px')
  207. :css('border-left-style', 'solid')
  208. else
  209. listCell:attr('colspan', 2)
  210. end
  211.  
  212. if not args.groupwidth then
  213. listCell:css('width', '100%')
  214. end
  215.  
  216. local isOdd = (listnum % 2) == 1
  217. local rowstyle = args.evenstyle
  218. if isOdd then rowstyle = args.oddstyle end
  219.  
  220. local evenOdd
  221. if args.evenodd == 'swap' then
  222. if isOdd then evenOdd = 'even' else evenOdd = 'odd' end
  223. else
  224. if isOdd then evenOdd = args.evenodd or 'odd' else evenOdd = args.evenodd or 'even' end
  225. end
  226.  
  227. listCell
  228. :css('padding', '0px')
  229. :cssText(args.liststyle)
  230. :cssText(rowstyle)
  231. :cssText(args['list' .. listnum .. 'style'])
  232. :addClass('navbox-list')
  233. :addClass('navbox-' .. evenOdd)
  234. :addClass(args.listclass)
  235. :tag('div')
  236. :css('padding', (listnum == 1 and args.list1padding) or args.listpadding or '0em 0.25em')
  237. :wikitext(addNewline(args['list' .. listnum]))
  238.  
  239. if listnum == 1 and args.image then
  240. row
  241. :tag('td')
  242. :addClass('navbox-image')
  243. :addClass(args.imageclass)
  244. :css('width', '0%')
  245. :css('padding', '0px 0px 0px 2px')
  246. :cssText(args.imagestyle)
  247. :attr('rowspan', 2 * #listnums - 1)
  248. :tag('div')
  249. :wikitext(addNewline(args.image))
  250. end
  251. end
  252.  
  253.  
  254. --
  255. -- Tracking categories
  256. --
  257.  
  258. local function needsHorizontalLists()
  259. if border == 'child' or border == 'subgroup' or args.tracking == 'no' then return false end
  260.  
  261. local listClasses = {'plainlist', 'hlist', 'hlist hnum', 'hlist hwrap', 'hlist vcard', 'vcard hlist', 'hlist vevent'}
  262. for i, cls in ipairs(listClasses) do
  263. if args.listclass == cls or args.bodyclass == cls then
  264. return false
  265. end
  266. end
  267.  
  268. return true
  269. end
  270.  
  271. local function hasBackgroundColors()
  272. return mw.ustring.match(args.titlestyle or '','background') or mw.ustring.match(args.groupstyle or '','background') or mw.ustring.match(args.basestyle or '','background')
  273. end
  274.  
  275. local function isIllegible()
  276. local styleratio = require('Module:Color contrast')._styleratio
  277.  
  278. for key, style in pairs(args) do
  279. if tostring(key):match("style$") then
  280. if styleratio{mw.text.unstripNoWiki(style)} < 4.5 then
  281. return true
  282. end
  283. end
  284. end
  285. return false
  286. end
  287.  
  288. local function getTrackingCategories()
  289. local cats = {}
  290. if needsHorizontalLists() then table.insert(cats, 'Navigational boxes without horizontal lists') end
  291. if hasBackgroundColors() then table.insert(cats, 'Navboxes using background colours') end
  292. if isIllegible() then table.insert(cats, 'Potentially illegible navboxes') end
  293. return cats
  294. end
  295.  
  296. local function renderTrackingCategories(builder)
  297. local title = mw.title.getCurrentTitle()
  298. if title.namespace ~= 10 then return end -- not in template space
  299. local subpage = title.subpageText
  300. if subpage == 'doc' or subpage == 'sandbox' or subpage == 'testcases' then return end
  301.  
  302. for i, cat in ipairs(getTrackingCategories()) do
  303. builder:wikitext('[[Category:' .. cat .. ']]')
  304. end
  305. end
  306.  
  307. --
  308. -- Main navbox tables
  309. --
  310. local function renderMainTable()
  311. local tbl = mw.html.create('table')
  312. :addClass('nowraplinks')
  313. :addClass(args.bodyclass)
  314.  
  315. if args.title and (args.state ~= 'plain' and args.state ~= 'off') then
  316. tbl
  317. :addClass('collapsible')
  318. :addClass(args.state or 'autocollapse')
  319. end
  320.  
  321. tbl:css('border-spacing', 0)
  322. if border == 'subgroup' or border == 'child' or border == 'none' then
  323. tbl
  324. :addClass('navbox-subgroup')
  325. :cssText(args.bodystyle)
  326. :cssText(args.style)
  327. else -- regular navobx - bodystyle and style will be applied to the wrapper table
  328. tbl
  329. :addClass('navbox-inner')
  330. :css('background', 'transparent')
  331. :css('color', 'inherit')
  332. end
  333. tbl:cssText(args.innerstyle)
  334.  
  335. renderTitleRow(tbl)
  336. renderAboveRow(tbl)
  337. for i, listnum in ipairs(listnums) do
  338. renderListRow(tbl, listnum)
  339. end
  340. renderBelowRow(tbl)
  341.  
  342. return tbl
  343. end
  344.  
  345. function p._navbox(navboxArgs)
  346. args = navboxArgs
  347.  
  348. for k, v in pairs(args) do
  349. local listnum = ('' .. k):match('^list(%d+)$')
  350. if listnum then table.insert(listnums, tonumber(listnum)) end
  351. end
  352. table.sort(listnums)
  353.  
  354. border = trim(args.border or args[1] or '')
  355.  
  356. -- render the main body of the navbox
  357. local tbl = renderMainTable()
  358.  
  359. -- render the appropriate wrapper around the navbox, depending on the border param
  360. local res = mw.html.create()
  361. if border == 'none' then
  362. res:node(tbl)
  363. elseif border == 'subgroup' or border == 'child' then
  364. -- We assume that this navbox is being rendered in a list cell of a parent navbox, and is
  365. -- therefore inside a div with padding:0em 0.25em. We start with a </div> to avoid the
  366. -- padding being applied, and at the end add a <div> to balance out the parent's </div>
  367. res
  368. :wikitext('</div>') -- XXX: hack due to lack of unclosed support in mw.html.
  369. :node(tbl)
  370. :wikitext('<div>') -- XXX: hack due to lack of unclosed support in mw.html.
  371. else
  372. res
  373. :tag('table')
  374. :addClass('navbox')
  375. :css('border-spacing', 0)
  376. :cssText(args.bodystyle)
  377. :cssText(args.style)
  378. :tag('tr')
  379. :tag('td')
  380. :css('padding', '2px')
  381. :node(tbl)
  382. end
  383.  
  384. renderTrackingCategories(res)
  385.  
  386. return tostring(res)
  387. end
  388.  
  389. function p.navbox(frame)
  390. if not getArgs then
  391. getArgs = require('Module:Arguments').getArgs
  392. end
  393. args = getArgs(frame, {wrappers = 'Template:Navbox'})
  394.  
  395. -- Read the arguments in the order they'll be output in, to make references number in the right order.
  396. local _
  397. _ = args.title
  398. _ = args.above
  399. for i = 1, 20 do
  400. _ = args["group" .. tostring(i)]
  401. _ = args["list" .. tostring(i)]
  402. end
  403. _ = args.below
  404.  
  405. return p._navbox(args)
  406. end
  407.  
  408. return p