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.
m (preprocess didn't help, as expected. another theory, adding a newline)
(I knew the logic was bad but not that bad. breaking it into two ifs for clarity)
 
(22 intermediate revisions by the same user not shown)
Line 47: Line 47:
function TabContainer:addTabLeftWithContent( args )
function TabContainer:addTabLeftWithContent( args )
   -- normalize args so they can just be passed to everything. definitely a bad idea but...
   -- normalize args so they can just be passed to everything. definitely a bad idea but...
   args.selection = getRequiredArg( args, 'contentId', 'TabContainer:addTabLeft' )
   args.selection = getRequiredArg( args, 'contentId', 'TabContainer:addTabLeftWithContent' )
   self:leftTabs( args ):addTab( args )
   self:leftTabs( args ):addTab( args )
   self:addContent( args )
   self:addContent( args )
Line 54: Line 54:
function TabContainer:addTabTopWithContent( args )
function TabContainer:addTabTopWithContent( args )
   -- normalize args so they can just be passed to everything. definitely a bad idea but...
   -- normalize args so they can just be passed to everything. definitely a bad idea but...
   args.selection = getRequiredArg( args, 'contentId', 'TabContainer:addTabTop' )
   args.selection = getRequiredArg( args, 'contentId', 'TabContainer:addTabTopWithContent' )
   self:topTabs( args ):addTab( args )
   self:topTabs( args ):addTab( args )
   self:addContent( args )
   self:addContent( args )
Line 74: Line 74:
     container:addClass( 'zdw-tabcontainer--hastabsleft' )
     container:addClass( 'zdw-tabcontainer--hastabsleft' )
     container:css( 'margin-left', tostring( width ) .. 'px' )
     container:css( 'margin-left', tostring( width ) .. 'px' )
     container:tag( 'div' )
     local left = container:tag( 'div' )
       :addClass( 'zdw-tabcontainer__tabset--left' )
       :addClass( 'zdw-tabcontainer__tabset--left' )
       :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:render() )
       :wikitext( self.tabsLeft:render() )
    if self.args.height then left:css( 'height', self.args.height .. 'px' ) end
   end
   end


   if self.tabsTop then
   if self.tabsTop then
     container:addClass( 'zdw-tabcontainer--hastabstop' )
     container:addClass( 'zdw-tabcontainer--hastabstop' )
     container:tag( 'div' )
     local top = container:tag( 'div' )
       :addClass( 'zdw-tabcontainer__tabset--top' )
       :addClass( 'zdw-tabcontainer__tabset--top' )
       :wikitext( self.tabsTop:render() )
       :wikitext( self.tabsTop:render() )
    if self.args.width then top:css( 'width', self.args.width .. 'px' ) end
   end
   end


   for _, c in ipairs( self.contents ) do
   for _, c in ipairs( self.contents ) do
    if type( c.selection ) == 'string'
        and (self.tabsLeft and self.tabsLeft.defaultSelection == c.selection:gsub( "%s", "" )
        or self.tabsTop and self.tabsTop.defaultSelection == c.selection:gsub( "%s", "" )) then
      c.default = true
    elseif type( c.selection ) == 'table'
        and self.tabsLeft and self.tabsLeft.defaultSelection == c.selection.left:gsub( "%s", "" )
        and self.tabsTop and self.tabsTop.defaultSelection == c.selection.top:gsub( "%s", "" ) then
      c.default = true
    end
     container:wikitext( c:render() )
     container:wikitext( c:render() )
   end
   end
Line 102: Line 113:
   return setmetatable( {
   return setmetatable( {
     target = args.target,
     target = args.target,
     selector = args.selector and (tonumber(args.selector) or error('invalid arg: selector must be a number')) or 0,
     selector = args.selector,
     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,
     defaultTab = args.default and (tonumber(args.default) or error('invalid arg: default must be a number')) or 1,
Line 111: Line 122:
function TabSet:addTab( args )
function TabSet:addTab( args )
   local index = #self.tabs + 1
   local index = #self.tabs + 1
   if index == self.defaultTab then args.default = true end
   if index == self.defaultTab then
    self.defaultSelection = args.selection:gsub( "%s", "" )
    args.default = true
  end
   self.tabs[index] = Tab.new( args )
   self.tabs[index] = Tab.new( args )
end
end
Line 118: Line 132:
   local tabSet = mw.html.create( 'ul' )
   local tabSet = mw.html.create( 'ul' )
     :addClass( 'zdw-tabset' )
     :addClass( 'zdw-tabset' )
    :attr( 'data-tab-selector', self.selector )
     :attr( 'data-tab-type', self.activation )
     :attr( 'data-tab-type', self.activation )
   if self.target then tabSet:attr( 'data-tab-target', self.target ) end
   if self.target then tabSet:attr( 'data-tab-target', self.target ) end
  if self.selector then tabSet:attr( 'data-tab-selector', self.selector ) end


   for _, tab in ipairs( self.tabs ) do
   for _, tab in ipairs( self.tabs ) do
Line 132: Line 146:
   args = args or {}
   args = args or {}
   return setmetatable( {
   return setmetatable( {
     selection = getRequiredArg( args, 'selection', 'Tab.new' ),
     selection = getRequiredArg( args, 'selection', 'Tab.new' ):gsub( "%s", "" ),
     label = args.label or args.selection,
     label = args.label or args.selection,
     args = args
     args = args
Line 141: Line 155:
   local tab = mw.html.create( 'li' )
   local tab = mw.html.create( 'li' )
     :addClass( 'zdw-tab' )
     :addClass( 'zdw-tab' )
     :attr( 'data-tab-selection', self.selection:gsub( "%s", "" ) )
     :attr( 'data-tab-selection', self.selection )
     :wikitext( self.label )
     :wikitext( self.label )


   if self.args.default then
   if self.args.default then
    tab:addClass( 'active' )
     tab:attr( 'data-tab-default', 'true' )
     tab:attr( 'data-tab-default', 'true' )
   end
   end
Line 154: Line 169:
   args = args or {}
   args = args or {}
   return setmetatable( {
   return setmetatable( {
     contentId = getRequiredArg( args, 'contentId', 'TabContent.new' ),
     selection = getRequiredArg( args, 'selection', 'TabContent.new' ),
     content = args.content or args.contentId,
     content = args.content or args.selection,
     args = args
     args = args
   }, TabContent )
   }, TabContent )
Line 163: Line 178:
   local content = mw.html.create( 'div' )
   local content = mw.html.create( 'div' )
     :addClass( 'zdw-tabcontent' )
     :addClass( 'zdw-tabcontent' )
     :attr( 'data-tab-content', self.contentId )
     :wikitext( '\n' .. self.content ) -- newline is needed for tables, lists, etc.
     :wikitext( '\n' .. self.content )
 
  if type( self.selection ) == 'table' then
    for k, v in pairs( self.selection ) do
      content:attr( 'data-tab-content-' .. k, v:gsub( "%s", "" ) )
     end
  else
    content:attr( 'data-tab-content', self.selection:gsub( "%s", "" ) )
  end


   if self.contentId == 'default' then
   if self.default then
     content:addClass( 'default' )
     content:addClass( 'default' )
   end
   end
Line 184: Line 206:
   local args = Args.fromFrame( frame )
   local args = Args.fromFrame( frame )
   local tabs = TabContainer.new( args )
   local tabs = TabContainer.new( args )
  -- set selectors
  if args.left then args.left.selector = args.top and not args.combine and 'left' or nil end
  if args.top then args.top.selector = args.left and not args.combine and 'top' or nil end
  -- fix default tabs if combined
  -- l\t  nil    0      #
  -- nil  nil\0  nil\0  0\#
  -- 0    0\nil  0\0    0\#
  -- #    #\0    #\0    #\0
  if args.left and args.top and args.combine then
    if args.left.default and args.left.default ~= '0' then args.top.default = '0' end
    if not args.left.default then
      if args.top.default and args.top.default ~= '0' then
        args.left.default = 0
      else
        args.top.default = 0
      end
    end
  end


   if args.left then
   if args.left then
     local left = tabs:leftTabs( args.left )
     local left = tabs:leftTabs( args.left )
     for _, leftArgs in ipairs( args.left ) do
     for _, tabArgs in ipairs( args.left ) do
       leftArgs = Args.getTable( leftArgs )
       tabArgs = Args.getTable( tabArgs )
       leftArgs.selection = Args.getValue( leftArgs )
       tabArgs.selection = Args.getValue( tabArgs )
       left:addTab( leftArgs )
       left:addTab( tabArgs )
     end
     end
   end
   end


   if args.top then
   if args.top then
    local topArgs = args.top
     local top = tabs:topTabs( args.top )
    if args.left and not topArgs.selector then topArgs.selector = 1 end -- default to 2D behavior if both sets are present
     for _, tabArgs in ipairs( args.top ) do
     local top = tabs:topTabs( topArgs )
       tabArgs = Args.getTable( tabArgs )
     for _, topArgs in ipairs( args.top ) do
       tabArgs.selection = Args.getValue( tabArgs )
       topArgs = Args.getTable( topArgs )
       top:addTab( tabArgs )
       topArgs.selection = Args.getValue( topArgs )
       top:addTab( topArgs )
     end
     end
   end
   end
    
    
   for contentId, contentArgs in pairs( args.content or {} ) do -- order doesn't matter here since only one is displayed at a time
   -- Special case where content is transcluded from subpages rather than provided directly
    contentArgs = Args.getTable( contentArgs )
  -- For now, assumes 2D mode (both left and top tabs)
    contentArgs.contentId = contentId
  if Args.getValue( args.content or {} ) == 'transclude' then
    contentArgs.content = Args.getValue( contentArgs )
    for _, leftArgs in ipairs( args.left ) do
    tabs:addContent( contentArgs )
      local leftSelection = Args.getValue( leftArgs )
      for _, topArgs in ipairs( args.top ) do
        local topSelection = Args.getValue( topArgs )
        local template = mw.title.getCurrentTitle().text .. '/' .. leftSelection .. '/' .. topSelection
 
        -- add edit link, aligned to transcluded table's header
        local editlink = mw.html.create( 'span' )
          :addClass( 'edit plainlinks' )
          :css( 'position', 'absolute' ) -- css to align with table header
          :css( 'top', '12px' )
          :css( 'right', '15px' )
          :wikitext( '[' .. tostring( mw.uri.fullUrl( template, { action = 'edit' } ) ) .. ' [edit]]' )
 
        -- add the transcluded content
        tabs:addContent{
          selection = { left = leftSelection, top = topSelection },
          content = mw.getCurrentFrame():expandTemplate{ title = ':' .. template } .. tostring( editlink )
        }
      end
    end
  else -- content is provided by the caller
    for contentSelection, contentArgs in pairs( args.content or {} ) do -- order doesn't matter here since only one is displayed at a time
      local leftSelection, topSelection = string.match( contentSelection, '(.+) (.+)' )
      contentArgs = Args.getTable( contentArgs )
      contentArgs.selection = leftSelection and { left = leftSelection, top = topSelection } or contentSelection
      contentArgs.content = Args.getValue( contentArgs )
      tabs:addContent( contentArgs )
    end
   end
   end



Latest revision as of 02:55, July 26, 2020

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

local Args = require( 'Module:Args' )

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 )
  args = args or {}
  return setmetatable( {
    contents = {},
    args = args
  }, TabContainer )
end

function TabContainer:leftTabs( args )
  if not self.tabsLeft then
    args.target = args.target or self.args.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.args.id
    args.activation = args.activation or self.args.activation
    self.tabsTop = TabSet.new( args )
  end

  return self.tabsTop
end

function TabContainer:addTabLeftWithContent( args )
  -- normalize args so they can just be passed to everything. definitely a bad idea but...
  args.selection = getRequiredArg( args, 'contentId', 'TabContainer:addTabLeftWithContent' )
  self:leftTabs( args ):addTab( args )
  self:addContent( args )
end

function TabContainer:addTabTopWithContent( args )
  -- normalize args so they can just be passed to everything. definitely a bad idea but...
  args.selection = getRequiredArg( args, 'contentId', 'TabContainer:addTabTopWithContent' )
  self:topTabs( args ):addTab( args )
  self:addContent( args )
end

function TabContainer:addContent( args )
  self.contents[#self.contents + 1] = TabContent.new( args )
end

function TabContainer:render()
  local container = mw.html.create( 'div' )
    :addClass( 'zdw-tabcontainer zdw-box' )
  if self.args.id then container:attr( 'id', self.args.id ) 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.tabsLeft then
    local width = tonumber( self.args.left and self.args.left.width ) or 60
    container:addClass( 'zdw-tabcontainer--hastabsleft' )
    container:css( 'margin-left', tostring( width ) .. 'px' )
    local left = container:tag( 'div' )
      :addClass( 'zdw-tabcontainer__tabset--left' )
      :css( 'width', tostring( width ) .. 'px' )
      :css( 'margin-left', '-' .. tostring( width + 10 ) .. 'px' )
      :wikitext( self.tabsLeft:render() )
    if self.args.height then left:css( 'height', self.args.height .. 'px' ) end
  end

  if self.tabsTop then
    container:addClass( 'zdw-tabcontainer--hastabstop' )
    local top = container:tag( 'div' )
      :addClass( 'zdw-tabcontainer__tabset--top' )
      :wikitext( self.tabsTop:render() )
    if self.args.width then top:css( 'width', self.args.width .. 'px' ) end
  end

  for _, c in ipairs( self.contents ) do
    if type( c.selection ) == 'string'
        and (self.tabsLeft and self.tabsLeft.defaultSelection == c.selection:gsub( "%s", "" )
        or self.tabsTop and self.tabsTop.defaultSelection == c.selection:gsub( "%s", "" )) then
      c.default = true
    elseif type( c.selection ) == 'table'
        and self.tabsLeft and self.tabsLeft.defaultSelection == c.selection.left:gsub( "%s", "" )
        and self.tabsTop and self.tabsTop.defaultSelection == c.selection.top:gsub( "%s", "" ) then
      c.default = true
    end
    container:wikitext( c:render() )
  end

  container:tag( 'div' )
    :css( 'clear', 'both' )

  return tostring( container )
end

function TabSet.new( args )
  args = args or {}
  return setmetatable( {
    target = args.target,
    selector = args.selector,
    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:addTab( args )
  local index = #self.tabs + 1
  if index == self.defaultTab then
    self.defaultSelection = args.selection:gsub( "%s", "" )
    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-type', self.activation )
  if self.target then tabSet:attr( 'data-tab-target', self.target ) end
  if self.selector then tabSet:attr( 'data-tab-selector', self.selector ) end

  for _, tab in ipairs( self.tabs ) do
    tabSet:wikitext( tab:render() )
  end

  return tostring( tabSet )
end

function Tab.new( args )
  args = args or {}
  return setmetatable( {
    selection = getRequiredArg( args, 'selection', 'Tab.new' ):gsub( "%s", "" ),
    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 )
    :wikitext( self.label )

  if self.args.default then
    tab:addClass( 'active' )
    tab:attr( 'data-tab-default', 'true' )
  end

  return tostring( tab )
end

function TabContent.new( args )
  args = args or {}
  return setmetatable( {
    selection = getRequiredArg( args, 'selection', 'TabContent.new' ),
    content = args.content or args.selection,
    args = args
  }, TabContent )
end

function TabContent:render()
  local content = mw.html.create( 'div' )
    :addClass( 'zdw-tabcontent' )
    :wikitext( '\n' .. self.content ) -- newline is needed for tables, lists, etc.

  if type( self.selection ) == 'table' then
    for k, v in pairs( self.selection ) do
      content:attr( 'data-tab-content-' .. k, v:gsub( "%s", "" ) )
    end
  else
    content:attr( 'data-tab-content', self.selection:gsub( "%s", "" ) )
  end

  if self.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.fromFrame( frame )
  local tabs = TabContainer.new( args )

  -- set selectors
  if args.left then args.left.selector = args.top and not args.combine and 'left' or nil end
  if args.top then args.top.selector = args.left and not args.combine and 'top' or nil end

  -- fix default tabs if combined
  -- l\t  nil    0      #
  -- nil  nil\0  nil\0  0\#
  -- 0    0\nil  0\0    0\#
  -- #    #\0    #\0    #\0
  if args.left and args.top and args.combine then
    if args.left.default and args.left.default ~= '0' then args.top.default = '0' end
    if not args.left.default then
      if args.top.default and args.top.default ~= '0' then
        args.left.default = 0
      else
        args.top.default = 0
      end
    end
  end

  if args.left then
    local left = tabs:leftTabs( args.left )
    for _, tabArgs in ipairs( args.left ) do
      tabArgs = Args.getTable( tabArgs )
      tabArgs.selection = Args.getValue( tabArgs )
      left:addTab( tabArgs )
    end
  end

  if args.top then
    local top = tabs:topTabs( args.top )
    for _, tabArgs in ipairs( args.top ) do
      tabArgs = Args.getTable( tabArgs )
      tabArgs.selection = Args.getValue( tabArgs )
      top:addTab( tabArgs )
    end
  end
  
  -- Special case where content is transcluded from subpages rather than provided directly
  -- For now, assumes 2D mode (both left and top tabs)
  if Args.getValue( args.content or {} ) == 'transclude' then
    for _, leftArgs in ipairs( args.left ) do
      local leftSelection = Args.getValue( leftArgs )
      for _, topArgs in ipairs( args.top ) do
        local topSelection = Args.getValue( topArgs )
        local template = mw.title.getCurrentTitle().text .. '/' .. leftSelection .. '/' .. topSelection

        -- add edit link, aligned to transcluded table's header
        local editlink = mw.html.create( 'span' )
          :addClass( 'edit plainlinks' )
          :css( 'position', 'absolute' ) -- css to align with table header
          :css( 'top', '12px' )
          :css( 'right', '15px' )
          :wikitext( '[' .. tostring( mw.uri.fullUrl( template, { action = 'edit' } ) ) .. ' [edit]]' )

        -- add the transcluded content
        tabs:addContent{
          selection = { left = leftSelection, top = topSelection },
          content = mw.getCurrentFrame():expandTemplate{ title = ':' .. template } .. tostring( editlink )
        }
      end
    end
  else -- content is provided by the caller
    for contentSelection, contentArgs in pairs( args.content or {} ) do -- order doesn't matter here since only one is displayed at a time
      local leftSelection, topSelection = string.match( contentSelection, '(.+) (.+)' )
      contentArgs = Args.getTable( contentArgs )
      contentArgs.selection = leftSelection and { left = leftSelection, top = topSelection } or contentSelection
      contentArgs.content = Args.getValue( contentArgs )
      tabs:addContent( contentArgs )
    end
  end

  return tabs:render()
end

return p