Module:Tabs: Difference between revisions
Jump to navigation
Jump to search
Want an adless experience? Log in or Create an account.
(for tabcontent, read default from the id arg since it doesn't make sense to have an id in that case) |
(refactored with a single entrypoint accepting flattened-tree args) |
||
Line 1: | Line 1: | ||
local Args = require( 'Module:Args' ) | |||
local Guid = require( 'Module:Guid' ) | |||
function getRequiredArg( args, argName, fnName ) | function getRequiredArg( args, argName, fnName ) | ||
return assert( args[argName], 'missing required arg for ' .. fnName .. ': ' .. argName ) | return assert( args[argName], 'missing required arg for ' .. fnName .. ': ' .. argName ) | ||
Line 5: | Line 8: | ||
local TabContainer = {} | local TabContainer = {} | ||
TabContainer.__index = TabContainer | TabContainer.__index = TabContainer | ||
local TabSet = {} | |||
TabSet.__index = TabSet | |||
local Tab = {} | |||
Tab.__index = Tab | |||
local TabContent = {} | |||
TabContent.__index = TabContent | |||
function TabContainer.new( args ) | function TabContainer.new( args ) | ||
return setmetatable( { | return setmetatable( { | ||
id = | id = args.id or Guid(), | ||
contents = {}, | |||
args = args | args = args | ||
}, TabContainer ) | }, TabContainer ) | ||
end | |||
function TabContainer:leftTabs( args ) | |||
if not self.tabsLeft then | |||
args.target = args.target or self.id | |||
args.activation = args.activation or self.args.activation | |||
self.tabsLeft = TabSet.new( args ) | |||
end | |||
return self.tabsLeft | |||
end | |||
function TabContainer:topTabs( args ) | |||
if not self.tabsTop then | |||
args.target = args.target or self.id | |||
args.activation = args.activation or self.args.activation | |||
self.tabsTop = TabSet.new( args ) | |||
end | |||
return self.tabsTop | |||
end | |||
function TabContainer:content( args ) | |||
self.contents[#self.contents + 1] = TabContent.new( args ) | |||
end | end | ||
Line 22: | Line 56: | ||
if self.args.width then container:css( 'width', self.args.width .. 'px' ) end | if self.args.width then container:css( 'width', self.args.width .. 'px' ) end | ||
if self.args.height then container:css( 'height', self.args.height .. 'px' ) end | if self.args.height then container:css( 'height', self.args.height .. 'px' ) end | ||
if self.tabsLeft then | if self.tabsLeft then | ||
local width = tonumber( self.args.vtabwidth ) or 60 | local width = tonumber( self.args.vtabwidth ) or 60 | ||
Line 30: | Line 65: | ||
:css( 'width', tostring( width ) .. 'px' ) | :css( 'width', tostring( width ) .. 'px' ) | ||
:css( 'margin-left', '-' .. tostring( width + 10 ) .. 'px' ) | :css( 'margin-left', '-' .. tostring( width + 10 ) .. 'px' ) | ||
:wikitext( self.tabsLeft ) | :wikitext( self.tabsLeft:render() ) | ||
end | end | ||
if self.tabsTop then | if self.tabsTop then | ||
container:addClass( 'zdw-tabcontainer--hastabstop' ) | container:addClass( 'zdw-tabcontainer--hastabstop' ) | ||
container:tag( 'div' ) | container:tag( 'div' ) | ||
:addClass( 'zdw-tabcontainer__tabset--top' ) | :addClass( 'zdw-tabcontainer__tabset--top' ) | ||
:wikitext( self.tabsTop ) | :wikitext( self.tabsTop:render() ) | ||
end | |||
for _, c in ipairs( self.contents ) do | |||
container:wikitext( c:render() ) | |||
end | end | ||
container:tag( 'div' ) | container:tag( 'div' ) | ||
:css( 'clear', 'both' ) | :css( 'clear', 'both' ) | ||
Line 44: | Line 84: | ||
return tostring( container ) | return tostring( container ) | ||
end | end | ||
function TabSet.new( args ) | function TabSet.new( args ) | ||
Line 53: | Line 90: | ||
selector = args.selector and (tonumber(args.selector) or error('invalid arg: selector must be a number')) or 0, | selector = args.selector and (tonumber(args.selector) or error('invalid arg: selector must be a number')) or 0, | ||
activation = args.activation or 'click', | activation = args.activation or 'click', | ||
defaultTab = args.default and (tonumber(args.default) or error('invalid arg: default must be a number')) or 1, | |||
tabs = {} | |||
}, TabSet ) | }, TabSet ) | ||
end | |||
function TabSet:tab( args ) | |||
local index = #self.tabs + 1 | |||
if index == self.defaultTab then args.default = true end | |||
self.tabs[index] = Tab.new( args ) | |||
end | end | ||
Line 63: | Line 107: | ||
:attr( 'data-tab-selector', self.selector ) | :attr( 'data-tab-selector', self.selector ) | ||
:attr( 'data-tab-type', self.activation ) | :attr( 'data-tab-type', self.activation ) | ||
:wikitext( | |||
for _, tab in ipairs( self.tabs ) do | |||
tabSet:wikitext( tab:render() ) | |||
end | |||
return tostring( tabSet ) | return tostring( tabSet ) | ||
end | end | ||
function Tab.new( args ) | function Tab.new( args ) | ||
return setmetatable( { | return setmetatable( { | ||
selection = getRequiredArg( args, | selection = getRequiredArg( args, 'selection', 'Tab.new' ), | ||
label = args | label = args.label or args.selection, | ||
args = args | args = args | ||
}, Tab ) | }, Tab ) | ||
Line 91: | Line 135: | ||
return tostring( tab ) | return tostring( tab ) | ||
end | end | ||
function TabContent.new( args ) | function TabContent.new( args ) | ||
return setmetatable( { | return setmetatable( { | ||
contentId = getRequiredArg( args, | contentId = getRequiredArg( args, 'contentId', 'TabContent.new' ), | ||
content = args | content = args.content or args.contentId, | ||
args = args | args = args | ||
}, TabContent ) | }, TabContent ) | ||
Line 122: | Line 163: | ||
local p = {} | local p = {} | ||
p. | p.Tabs = TabContainer | ||
function p.tabs( frame ) | function p.tabs( frame ) | ||
local | local args = Args.parse( frame.args ) | ||
local tabs = TabContainer.new( args:values() ) | |||
if args.left then | |||
local left = tabs:leftTabs( args.left:values() ) | |||
for _, leftArgs in ipairs( args.left ) do | |||
end | tabArgs = leftArgs:values() | ||
tabArgs.selection = leftArgs:val() | |||
left:tab( tabArgs ) | |||
end | |||
end | |||
if args.top then | |||
local | local topArgs = args.top:values() | ||
if args.left and not topArgs.selector then topArgs.selector = 1 end -- default to 2D behavior if both sets are present | |||
end | local top = tabs:topTabs( topArgs ) | ||
for _, topArgs in ipairs( args.top ) do | |||
tabArgs = topArgs:values() | |||
tabArgs.selection = topArgs:val() | |||
top:tab( tabArgs ) | |||
end | |||
end | |||
for k, v in pairs( args.content or {} ) do -- order doesn't matter here since only one is displayed at a time | |||
local contentArgs = v:values() | |||
contentArgs.contentId = k | |||
contentArgs.content = v:val() | |||
tabs:content( contentArgs ) | |||
end | |||
return tabs:render() | |||
return | |||
end | end | ||
return p | return p |
Revision as of 23:47, June 23, 2020
Documentation for this module may be created at Module:Tabs/doc
local Args = require( 'Module:Args' ) local Guid = require( 'Module:Guid' ) function getRequiredArg( args, argName, fnName ) return assert( args[argName], 'missing required arg for ' .. fnName .. ': ' .. argName ) end local TabContainer = {} TabContainer.__index = TabContainer local TabSet = {} TabSet.__index = TabSet local Tab = {} Tab.__index = Tab local TabContent = {} TabContent.__index = TabContent function TabContainer.new( args ) return setmetatable( { id = args.id or Guid(), contents = {}, args = args }, TabContainer ) end function TabContainer:leftTabs( args ) if not self.tabsLeft then args.target = args.target or self.id args.activation = args.activation or self.args.activation self.tabsLeft = TabSet.new( args ) end return self.tabsLeft end function TabContainer:topTabs( args ) if not self.tabsTop then args.target = args.target or self.id args.activation = args.activation or self.args.activation self.tabsTop = TabSet.new( args ) end return self.tabsTop end function TabContainer:content( args ) self.contents[#self.contents + 1] = TabContent.new( args ) end function TabContainer:render() local container = mw.html.create( 'div' ) :attr( 'id', self.id ) :addClass( 'zdw-tabcontainer zdw-box' ) if self.args.width then container:css( 'width', self.args.width .. 'px' ) end if self.args.height then container:css( 'height', self.args.height .. 'px' ) end if self.tabsLeft then local width = tonumber( self.args.vtabwidth ) or 60 container:addClass( 'zdw-tabcontainer--hastabsleft' ) container:css( 'margin-left', tostring( width ) .. 'px' ) container:tag( 'div' ) :addClass( 'zdw-tabcontainer__tabset--left' ) :css( 'width', tostring( width ) .. 'px' ) :css( 'margin-left', '-' .. tostring( width + 10 ) .. 'px' ) :wikitext( self.tabsLeft:render() ) end if self.tabsTop then container:addClass( 'zdw-tabcontainer--hastabstop' ) container:tag( 'div' ) :addClass( 'zdw-tabcontainer__tabset--top' ) :wikitext( self.tabsTop:render() ) end for _, c in ipairs( self.contents ) do container:wikitext( c:render() ) end container:tag( 'div' ) :css( 'clear', 'both' ) return tostring( container ) end function TabSet.new( args ) return setmetatable( { target = getRequiredArg( args, 'target', 'TabSet.new' ), selector = args.selector and (tonumber(args.selector) or error('invalid arg: selector must be a number')) or 0, activation = args.activation or 'click', defaultTab = args.default and (tonumber(args.default) or error('invalid arg: default must be a number')) or 1, tabs = {} }, TabSet ) end function TabSet:tab( args ) local index = #self.tabs + 1 if index == self.defaultTab then args.default = true end self.tabs[index] = Tab.new( args ) end function TabSet:render() local tabSet = mw.html.create( 'ul' ) :addClass( 'zdw-tabset' ) :attr( 'data-tab-target', self.target ) :attr( 'data-tab-selector', self.selector ) :attr( 'data-tab-type', self.activation ) for _, tab in ipairs( self.tabs ) do tabSet:wikitext( tab:render() ) end return tostring( tabSet ) end function Tab.new( args ) return setmetatable( { selection = getRequiredArg( args, 'selection', 'Tab.new' ), label = args.label or args.selection, args = args }, Tab ) end function Tab:render() local tab = mw.html.create( 'li' ) :addClass( 'zdw-tab' ) :attr( 'data-tab-selection', self.selection:gsub( "%s", "" ) ) :wikitext( self.label ) if self.args.default then tab:attr( 'data-tab-default', 'true' ) end return tostring( tab ) end function TabContent.new( args ) return setmetatable( { contentId = getRequiredArg( args, 'contentId', 'TabContent.new' ), content = args.content or args.contentId, args = args }, TabContent ) end function TabContent:render() local content = mw.html.create( 'div' ) :addClass( 'zdw-tabcontent' ) :attr( 'data-tab-content', self.contentId ) :wikitext( self.content ) if self.contentId == 'default' then content:addClass( 'default' ) end if self.args.width then content:css( 'width', self.args.width .. 'px' ) end return tostring( content ) end local p = {} p.Tabs = TabContainer function p.tabs( frame ) local args = Args.parse( frame.args ) local tabs = TabContainer.new( args:values() ) if args.left then local left = tabs:leftTabs( args.left:values() ) for _, leftArgs in ipairs( args.left ) do tabArgs = leftArgs:values() tabArgs.selection = leftArgs:val() left:tab( tabArgs ) end end if args.top then local topArgs = args.top:values() if args.left and not topArgs.selector then topArgs.selector = 1 end -- default to 2D behavior if both sets are present local top = tabs:topTabs( topArgs ) for _, topArgs in ipairs( args.top ) do tabArgs = topArgs:values() tabArgs.selection = topArgs:val() top:tab( tabArgs ) end end for k, v in pairs( args.content or {} ) do -- order doesn't matter here since only one is displayed at a time local contentArgs = v:values() contentArgs.contentId = k contentArgs.content = v:val() tabs:content( contentArgs ) end return tabs:render() end return p