Module:Tabs: Difference between revisions

From Zelda Dungeon Wiki
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 = getRequiredArg( args, 'id', 'TabContainer.new' ),
     id = args.id or Guid(),
     content = args[1],
     contents = {},
    tabsTop = args.top,
    tabsLeft = args.left,
     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:wikitext( self.content )
 
   container:tag( 'div' )
   container:tag( 'div' )
     :css( 'clear', 'both' )
     :css( 'clear', 'both' )
Line 44: Line 84:
   return tostring( container )
   return tostring( container )
end
end
local TabSet = {}
TabSet.__index = TabSet


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',
     tabs = getRequiredArg( args, 1, 'TabSet.new' )
     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( self.tabs )
 
  for _, tab in ipairs( self.tabs ) do
     tabSet:wikitext( tab:render() )
  end


   return tostring( tabSet )
   return tostring( tabSet )
end
end
local Tab = {}
Tab.__index = Tab


function Tab.new( args )
function Tab.new( args )
   return setmetatable( {
   return setmetatable( {
     selection = getRequiredArg( args, 1, 'Tab.new' ),
     selection = getRequiredArg( args, 'selection', 'Tab.new' ),
     label = args[2] or args[1],
     label = args.label or args.selection,
     args = args
     args = args
   }, Tab )
   }, Tab )
Line 91: Line 135:
   return tostring( tab )
   return tostring( tab )
end
end
local TabContent = {}
TabContent.__index = TabContent


function TabContent.new( args )
function TabContent.new( args )
   return setmetatable( {
   return setmetatable( {
     contentId = getRequiredArg( args, 1, 'TabContent.new' ),
     contentId = getRequiredArg( args, 'contentId', 'TabContent.new' ),
     content = args[2] or args[1],
     content = args.content or args.contentId,
     args = args
     args = args
   }, TabContent )
   }, TabContent )
Line 122: Line 163:
local p = {}
local p = {}


p.TabContainer = TabContainer
p.Tabs = TabContainer
p.TabSet = TabSet
p.Tab = Tab
p.TabContent = TabContent


function p.tabs( frame )
function p.tabs( frame )
   local tabs = TabContainer.new( frame.args )
   local args = Args.parse( frame.args )
   return tabs:render()
   local tabs = TabContainer.new( args:values() )
end


function p.tabset( frame )
  if args.left then
  local tabset = TabSet.new( frame.args )
    local left = tabs:leftTabs( args.left:values() )
  return tabset:render()
    for _, leftArgs in ipairs( args.left ) do
end
      tabArgs = leftArgs:values()
      tabArgs.selection = leftArgs:val()
      left:tab( tabArgs )
    end
  end


function p.tab( frame )
  if args.top then
   local tab = Tab.new( frame.args )
    local topArgs = args.top:values()
  return tab:render()
    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


function p.content( frame )
   return tabs:render()
  local content = TabContent.new( frame.args )
   return content:render()
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