Module:NavboxMobile: Difference between revisions

From Ikwipedia
No edit summary
No edit summary
Line 1: Line 1:
-- Module:NavboxMobile
-- This module implements {{navbox}} for MobileFrontend
-- This module implements {{navbox}} for MobileFrontend


Line 6: Line 7:
local cfg = mw.loadData('Module:NavboxMobile/configuration')
local cfg = mw.loadData('Module:NavboxMobile/configuration')
local navbar = require('Module:Navbar')._navbar
local navbar = require('Module:Navbar')._navbar
local getArgs -- lazily initialized
local getArgs -- Lazily initialized
local inArray = require('Module:TableTools').inArray
local inArray = require('Module:TableTools').inArray
local format = string.format
local format = string.format
Line 15: Line 16:
local listnums = {}
local listnums = {}


-- Function to inject styles from styles.css
local function addNavboxMobileStyles()
local function addNavboxMobileStyles()
     local frame = mw.getCurrentFrame()
     return mw.html.create('style')
    return frame:extensionTag{
        :attr('data-mw-deduplicate', 'TemplateStyles-navboxmobile') -- Ensures styles are not duplicated
        name = 'templatestyles',
         :wikitext(mw.loadData('Module:NavboxMobile/styles.css'))
         args = { src = 'Module:NavboxMobile/styles.css' }
        :done()
    }
end
end


-- Helper function to trim whitespace
local function trim(s)
local function trim(s)
     return (mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1"))
     return (mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1"))
end
end


-- Helper function to add newlines based on content
local function addNewline(s)
local function addNewline(s)
     if s:match('^[*:;#]') or s:match('^{|') then
     if s:match('^[*:;#]') or s:match('^{|') then
Line 35: Line 38:
end
end


-- Function to add a new table row with optional gutter
local function addTableRow(tbl)
local function addTableRow(tbl)
    -- If any other rows have already been added, then we add a 2px gutter row.
     if tableRowAdded then
     if tableRowAdded then
         tbl
         tbl
Line 42: Line 45:
                 :css('height', '2px')
                 :css('height', '2px')
                 :tag('td')
                 :tag('td')
                     :attr('colspan',2)
                     :attr('colspan', 2)
        -- Note: You can add additional gutter styling here if needed
     end
     end
     tableRowAdded = true
     tableRowAdded = true
     return tbl:tag('tr')
     return tbl:tag('tr')
end
end


-- Function to render the navigation bar
local function renderNavBar(titleCell)
local function renderNavBar(titleCell)
    -- Depending on the presence of the navbar and/or show/hide link, we may need to add a spacer div on the left
    -- or right to keep the title centered.
     local spacerSide = nil
     local spacerSide = nil


     if args.navbar == 'off' then
     if args.navbar == 'off' then
        -- No navbar, and client wants no spacer, i.e. wants the title to be shifted to the left. If there's
        -- also no show/hide link, then we need a spacer on the right to achieve the left shift.
         if args.state == 'plain' then spacerSide = 'right' end
         if args.state == 'plain' then spacerSide = 'right' end
     elseif args.navbar == 'plain' or (not args.name and mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '') == 'Template:navbox') then
     elseif args.navbar == 'plain' or (not args.name and mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '') == 'Template:navbox') then
        -- No navbar. Need a spacer on the left to balance out the width of the show/hide link.
         if args.state ~= 'plain' then spacerSide = 'left' end
         if args.state ~= 'plain' then spacerSide = 'left' end
     else
     else
        -- Will render navbar (or error message). If there's no show/hide link, need a spacer on the right
        -- to balance out the width of the navbar.
         if args.state == 'plain' then spacerSide = 'right' end
         if args.state == 'plain' then spacerSide = 'right' end


Line 74: Line 70:
     end
     end


    -- Render the spacer div.
     if spacerSide then
     if spacerSide then
         titleCell
         titleCell
Line 113: Line 108:


     local titleColspan = 2
     local titleColspan = 2
     --[[if args.imageleft then titleColspan = titleColspan + 1 end
     -- Adjust colspan based on image presence if needed
    if args.image then titleColspan = titleColspan + 1 end]]
     if args.titlegroup then titleColspan = titleColspan - 1 end
     if args.titlegroup then titleColspan = titleColspan - 1 end


Line 137: Line 131:


local function getAboveBelowColspan()
local function getAboveBelowColspan()
     local ret = 2
     return 2
    --[[if args.imageleft then ret = ret + 1 end
    if args.image then ret = ret + 1 end]]
    return ret
end
end


Line 196: Line 187:
     listCell:cssText(args.liststyle)
     listCell:cssText(args.liststyle)
     listCell:addClass('navboxMobile-list')
     listCell:addClass('navboxMobile-list')
   
 
     -- Check for nested lists (e.g., child1_groupX)
     -- Check for nested lists (e.g., child1_groupX)
     local nestedGroup = args['child' .. listnum .. '_group1']
     local nestedGroup = args['child' .. listnum .. '_group1']
Line 235: Line 226:
     end
     end
end
end


--
--
Line 245: Line 235:


     local listClasses = {'plainlist', 'hlist', 'hlist hnum', 'hlist hwrap', 'hlist vcard', 'vcard hlist', 'hlist vevent'}
     local listClasses = {'plainlist', 'hlist', 'hlist hnum', 'hlist hwrap', 'hlist vcard', 'vcard hlist', 'hlist vevent'}
     for i, cls in ipairs(listClasses) do
     for _, cls in ipairs(listClasses) do
         if args.listclass == cls or args.bodyclass == cls then
         if args.listclass == cls or args.bodyclass == cls then
             return false
             return false
Line 255: Line 245:


local function hasBackgroundColors()
local function hasBackgroundColors()
     return mw.ustring.match(args.titlestyle or '','background') or mw.ustring.match(args.groupstyle or '','background') or mw.ustring.match(args.basestyle or '','background')
     return mw.ustring.match(args.titlestyle or '', 'background')  
        or mw.ustring.match(args.groupstyle or '', 'background')  
        or mw.ustring.match(args.basestyle or '', 'background')
end
end


Line 281: Line 273:
local function renderTrackingCategories(builder)
local function renderTrackingCategories(builder)
     local title = mw.title.getCurrentTitle()
     local title = mw.title.getCurrentTitle()
     if title.namespace ~= 10 then return end -- not in template space
     if title.namespace ~= 10 then return end -- Not in template space
     local subpage = title.subpageText
     local subpage = title.subpageText
     if subpage == 'doc' or subpage == 'sandbox' or subpage == 'testcases' then return end
     if subpage == 'doc' or subpage == 'sandbox' or subpage == 'testcases' then return end


     for i, cat in ipairs(getTrackingCategories()) do
     for _, cat in ipairs(getTrackingCategories()) do
         builder:wikitext('[[Category:' .. cat .. ']]')
         builder:wikitext('[[Category:' .. cat .. ']]')
     end
     end
Line 298: Line 290:
         :addClass(args.bodyclass)
         :addClass(args.bodyclass)


    -- Uncomment and implement collapsibility if needed
     --[[if args.title and (args.state ~= 'plain' and args.state ~= 'off') then
     --[[if args.title and (args.state ~= 'plain' and args.state ~= 'off') then
         tbl
         tbl
Line 310: Line 303:
             :cssText(args.bodystyle)
             :cssText(args.bodystyle)
             :cssText(args.style)
             :cssText(args.style)
     else -- regular navbox - bodystyle and style will be applied to the wrapper table
     else -- Regular navbox - bodystyle and style will be applied to the wrapper table
         tbl
         tbl
             :addClass('navboxMobile-inner')
             :addClass('navboxMobile-inner')
Line 320: Line 313:
     renderTitleRow(tbl)
     renderTitleRow(tbl)
     renderAboveRow(tbl)
     renderAboveRow(tbl)
     for i, listnum in ipairs(listnums) do
     for _, listnum in ipairs(listnums) do
         renderListRow(tbl, listnum)
         renderListRow(tbl, listnum)
     end
     end
Line 328: Line 321:
end
end


--
--  Main function to render NavboxMobile
--
function p._navboxMobile(navboxMobileArgs)
function p._navboxMobile(navboxMobileArgs)
     args = navboxMobileArgs
     args = navboxMobileArgs
Line 343: Line 339:
     border = trim(args.border or args[1] or '')
     border = trim(args.border or args[1] or '')


     -- render the main body of the navboxMobile
     -- Render the main body of the navboxMobile
     local tbl = renderMainTable()
     local tbl = renderMainTable()


Line 357: Line 353:
         local navWrapper = mw.html.create('div')
         local navWrapper = mw.html.create('div')
             :attr('role', 'navigation')
             :attr('role', 'navigation')
             :attr('aria-label', cfg.aria_label)
             :attr('aria-label', cfg.aria_label) -- Ensure cfg.aria_label is defined appropriately
             :node(tbl)
             :node(tbl)
         res:node(navWrapper)
         res:node(navWrapper)
     elseif border == 'subgroup' or border == 'child' then
     elseif border == 'subgroup' or border == 'child' then
         -- Assume this navboxMobile is inside a parent navboxMobile's list cell
         -- Assume this navboxMobile is being rendered in a list cell of a parent navboxMobile
         -- Insert closing and opening divs to manage padding
         -- Insert closing and opening divs to manage padding
         res
         res
Line 375: Line 371:
             :cssText(args.bodystyle)
             :cssText(args.bodystyle)
             :cssText(args.style)
             :cssText(args.style)
             :tag('tr')
             :node(tbl)
                :tag('td')
                    :css('padding', '2px')
                    :node(tbl)
         res:node(navWrapper)
         res:node(navWrapper)
     end
     end
Line 388: Line 381:
end
end


--
--  Entry point function for the template
--
function p.navboxMobile(frame)
function p.navboxMobile(frame)
     if not getArgs then
     if not getArgs then
Line 395: Line 391:


     -- Read the arguments in the order they'll be output in, to make references number in the right order.
     -- Read the arguments in the order they'll be output in, to make references number in the right order.
    -- This ensures that list numbers are processed sequentially.
     local _
     local _
     _ = args.title
     _ = args.title

Revision as of 01:34, 24 November 2024

This module implements the {{Navbox}} template for mobile devices when Extension:MobileFrontend is installed. This mobile version differs from the desktop version as follows:

  • The table does not collapse.
  • The list cells, e.g. group1, list1, each display across the width of the screen
  • Images are not displayed, as it increases the complexity of the layout while reducing the area needed for content.

Original from wikipedia::Module:User:Lady G2016/NavboxMobile/doc

Overview

This approach works by utilizing CSS class rules. See: Hiding content on desktop devices

An onlymobile class in MediaWiki:Common.css will not display classes intended for mobile devices.

A nomobile class in MediaWiki:Mobile.css will not display classes intended for desktop devices.

Use these classes together inside the same template. The template will be rendered twice, but only display once depending on the view (desktop or mobile).

Module description

The module was copied from Module:Navbox and modified as follows:

  • All instances of navbox- were replaced with navboxMobile- to provide unique CSS identifiers that are independent of navbox.
  • All statements which processed images were removed. The statements remain as comments to show what was changed.

In function renderListRow(), the cell pair of (groupCell, listCell) appearing as a single row is broken into separate rows by adding local row = addTableRow(tbl) just before listCell.

Adding this row and removing the images are the only functional changes made to the existing navbox template. Except for removal of the images, the parameters used in the current navbox are supported.

Implementation

MediaWiki:Common.css

In MediaWiki:Common.css, add:

/* Only for mobile devices */
.onlymobile {
display:none;
}

MediaWiki:Mobile.css

In MediaWiki:Mobile.css, add:

/* Only for desktop devices */
.nomobile {
display:none;
}

MobileFrontend processes MediaWiki:Mobile.css. To provide an independent class that is not affected by the navbox rules, copy all of the navbox and hlist CSS styles from MediaWiki:Common.css to MediaWiki:Mobile.css.

Replace all navbox- with navboxMobile-. For example, navbox-title becomes navboxMobile-title. This will match the CSS properties in Module:NavboxMobile.

An example page can be found at User:Lady G2016/MediaWiki:Mobile.css.

Template:Navbox

Modify Template:Navbox to call both the desktop and mobile Lua modules as follows:

<div class="nomobile">{{#invoke:Navbox|navbox}}</div><div class="onlymobile">{{#invoke:NavboxMobile|navboxMobile}}</div><noinclude>
{{Documentation}}
</noinclude>

This will render both the desktop (navbox) and mobile (navboxMobile) versions. When displaying from a desktop view, MediaWiki:Common.css will not display the mobile template. When displaying from a mobile, only the mobile template will be displayed.

Constraints and limitations

This template was developed with MediaWiki 1.27.4 (Long Term Support) and MobileFrontEnd 1.0.0 (29 June 2016). No testing was performed with other versions.

The navbox class is explicitly disabled in /extensions/MobileFrontend/resources/skins.minerva.content.styles/hacks.less. Attempts to override navbox using $wgMFRemovableClasses do not display as intended when the navbox class is enabled.


-- Module:NavboxMobile
-- This module implements {{navbox}} for MobileFrontend

local p = {}

require('strict')
local cfg = mw.loadData('Module:NavboxMobile/configuration')
local navbar = require('Module:Navbar')._navbar
local getArgs -- Lazily initialized
local inArray = require('Module:TableTools').inArray
local format = string.format

local args
local tableRowAdded = false
local border
local listnums = {}

-- Function to inject styles from styles.css
local function addNavboxMobileStyles()
    return mw.html.create('style')
        :attr('data-mw-deduplicate', 'TemplateStyles-navboxmobile') -- Ensures styles are not duplicated
        :wikitext(mw.loadData('Module:NavboxMobile/styles.css'))
        :done()
end

-- Helper function to trim whitespace
local function trim(s)
    return (mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1"))
end

-- Helper function to add newlines based on content
local function addNewline(s)
    if s:match('^[*:;#]') or s:match('^{|') then
        return '\n' .. s ..'\n'
    else
        return s
    end
end

-- Function to add a new table row with optional gutter
local function addTableRow(tbl)
    if tableRowAdded then
        tbl
            :tag('tr')
                :css('height', '2px')
                :tag('td')
                    :attr('colspan', 2)
        -- Note: You can add additional gutter styling here if needed
    end
    tableRowAdded = true
    return tbl:tag('tr')
end

-- Function to render the navigation bar
local function renderNavBar(titleCell)
    local spacerSide = nil

    if args.navbar == 'off' then
        if args.state == 'plain' then spacerSide = 'right' end
    elseif args.navbar == 'plain' or (not args.name and mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '') == 'Template:navbox') then
        if args.state ~= 'plain' then spacerSide = 'left' end
    else
        if args.state == 'plain' then spacerSide = 'right' end

        titleCell:wikitext(navbar{
            args.name,
            mini = 1,
            fontstyle = (args.basestyle or '') .. ';' .. (args.titlestyle or '') ..  ';background:none transparent;border:none;'
        })
    end

    if spacerSide then
        titleCell
            :tag('span')
                :css('float', spacerSide)
                :css('width', '6em')
                :wikitext('&nbsp;')
    end
end

--
--   Title row
--
local function renderTitleRow(tbl)
    if not args.title then return end

    local titleRow = addTableRow(tbl)

    if args.titlegroup then
        titleRow
            :tag('th')
                :attr('scope', 'row')
                :addClass('navboxMobile-group')
                :addClass(args.titlegroupclass)
                :cssText(args.basestyle)
                :cssText(args.groupstyle)
                :cssText(args.titlegroupstyle)
                :wikitext(args.titlegroup)
    end

    local titleCell = titleRow:tag('th'):attr('scope', 'col')

    if args.titlegroup then
        titleCell
            :css('border-left', '2px solid #fdfdfd')
            :css('width', '100%')
    end

    local titleColspan = 2
    -- Adjust colspan based on image presence if needed
    if args.titlegroup then titleColspan = titleColspan - 1 end

    titleCell
        :cssText(args.basestyle)
        :cssText(args.titlestyle)
        :addClass('navboxMobile-title')
        :attr('colspan', titleColspan)

    renderNavBar(titleCell)

    titleCell
         :tag('div')
             :addClass(args.titleclass)
             :css('font-size', '114%')
             :wikitext(addNewline(args.title))
end

--
--   Above/Below rows
--

local function getAboveBelowColspan()
    return 2
end

local function renderAboveRow(tbl)
    if not args.above then return end

    addTableRow(tbl)
        :tag('td')
            :addClass('navboxMobile-abovebelow')
            :addClass(args.aboveclass)
            :cssText(args.basestyle)
            :cssText(args.abovestyle)
            :attr('colspan', getAboveBelowColspan())
            :tag('div')
                :wikitext(addNewline(args.above))
end

local function renderBelowRow(tbl)
    if not args.below then return end

    addTableRow(tbl)
        :tag('td')
            :addClass('navboxMobile-abovebelow')
            :addClass(args.belowclass)
            :cssText(args.basestyle)
            :cssText(args.belowstyle)
            :attr('colspan', getAboveBelowColspan())
            :tag('div')
                :wikitext(addNewline(args.below))
end

--
--   List rows
--
local function renderListRow(tbl, listnum)
    local row = addTableRow(tbl)

    -- Process group
    if args['group' .. listnum] then
        local groupCell = row:tag('th')
        groupCell
            :attr('scope', 'row')
            :addClass('navboxMobile-group')
            :cssText(args.basestyle)
            :cssText(args.groupstyle)
            :cssText(args['group' .. listnum .. 'style'])
            :wikitext(args['group' .. listnum])
    end

    -- Add table row for the list
    row = addTableRow(tbl)
    local listCell = row:tag('td')
    listCell:attr('colspan', 2)
    listCell:css('width', '100%')
    listCell:cssText(args.liststyle)
    listCell:addClass('navboxMobile-list')

    -- Check for nested lists (e.g., child1_groupX)
    local nestedGroup = args['child' .. listnum .. '_group1']
    local nestedList = args['child' .. listnum .. '_list1']

    if nestedGroup or nestedList then
        local nestedTbl = mw.html.create('table')
            :addClass('navboxMobile-subgroup')
            :css('width', '100%')

        for i = 1, 10 do -- Assume a max of 10 nested groups/lists
            local nestedGroup = args['child' .. listnum .. '_group' .. i]
            local nestedList = args['child' .. listnum .. '_list' .. i]

            if not nestedGroup and not nestedList then break end

            local nestedRow = addTableRow(nestedTbl)

            if nestedGroup then
                nestedRow:tag('th')
                    :addClass('navboxMobile-group')
                    :cssText(args.basestyle)
                    :cssText(args['child' .. listnum .. '_group' .. i .. 'style'])
                    :wikitext(nestedGroup)
            end

            if nestedList then
                nestedRow:tag('td')
                    :addClass('navboxMobile-list')
                    :cssText(args['child' .. listnum .. '_list' .. i .. 'style'])
                    :wikitext(addNewline(nestedList))
            end
        end

        listCell:node(nestedTbl)
    else
        listCell:wikitext(addNewline(args['list' .. listnum]))
    end
end

--
--   Tracking categories
--

local function needsHorizontalLists()
    if border == 'child' or border == 'subgroup'  or args.tracking == 'no' then return false end

    local listClasses = {'plainlist', 'hlist', 'hlist hnum', 'hlist hwrap', 'hlist vcard', 'vcard hlist', 'hlist vevent'}
    for _, cls in ipairs(listClasses) do
        if args.listclass == cls or args.bodyclass == cls then
            return false
        end
    end

    return true
end

local function hasBackgroundColors()
    return mw.ustring.match(args.titlestyle or '', 'background') 
        or mw.ustring.match(args.groupstyle or '', 'background') 
        or mw.ustring.match(args.basestyle or '', 'background')
end

local function isIllegible()
    local styleratio = require('Module:Color contrast')._styleratio

    for key, style in pairs(args) do
        if tostring(key):match("style$") then
            if styleratio{mw.text.unstripNoWiki(style)} < 4.5 then
                return true 
            end
        end
    end
    return false
end

local function getTrackingCategories()
    local cats = {}
    if needsHorizontalLists() then table.insert(cats, 'Navigational boxes without horizontal lists') end
    if hasBackgroundColors() then table.insert(cats, 'navboxMobiles using background colours') end
    if isIllegible() then table.insert(cats, 'Potentially illegible navboxMobiles') end
    return cats
end

local function renderTrackingCategories(builder)
    local title = mw.title.getCurrentTitle()
    if title.namespace ~= 10 then return end -- Not in template space
    local subpage = title.subpageText
    if subpage == 'doc' or subpage == 'sandbox' or subpage == 'testcases' then return end

    for _, cat in ipairs(getTrackingCategories()) do
        builder:wikitext('[[Category:' .. cat .. ']]')
    end
end

--
--   Main navboxMobile tables
--
local function renderMainTable()
    local tbl = mw.html.create('table')
        :addClass('nowraplinks')
        :addClass(args.bodyclass)

    -- Uncomment and implement collapsibility if needed
    --[[if args.title and (args.state ~= 'plain' and args.state ~= 'off') then
        tbl
            :addClass('collapsible')
            :addClass(args.state or 'autocollapse')
    end]]

    tbl:css('border-spacing', 0)
    if border == 'subgroup' or border == 'child' or border == 'none' then
        tbl
            :addClass('navboxMobile-subgroup')
            :cssText(args.bodystyle)
            :cssText(args.style)
    else -- Regular navbox - bodystyle and style will be applied to the wrapper table
        tbl
            :addClass('navboxMobile-inner')
            :css('background', 'transparent')
            :css('color', 'inherit')
    end
    tbl:cssText(args.innerstyle)

    renderTitleRow(tbl)
    renderAboveRow(tbl)
    for _, listnum in ipairs(listnums) do
        renderListRow(tbl, listnum)
    end
    renderBelowRow(tbl)

    return tbl
end

--
--   Main function to render NavboxMobile
--
function p._navboxMobile(navboxMobileArgs)
    args = navboxMobileArgs

    -- Reset state variables to prevent state leakage between invocations
    tableRowAdded = false
    listnums = {}

    for k, v in pairs(args) do
        local listnum = ('' .. k):match('^list(%d+)$')
        if listnum then table.insert(listnums, tonumber(listnum)) end
    end
    table.sort(listnums)

    border = trim(args.border or args[1] or '')

    -- Render the main body of the navboxMobile
    local tbl = renderMainTable()

    -- Create the final HTML with styles
    local res = mw.html.create()

    -- Inject styles
    res:node(addNavboxMobileStyles())

    -- Handle wrapping based on border parameter
    if border == 'none' then
        -- Wrap the table within a navigation div with ARIA attributes
        local navWrapper = mw.html.create('div')
            :attr('role', 'navigation')
            :attr('aria-label', cfg.aria_label) -- Ensure cfg.aria_label is defined appropriately
            :node(tbl)
        res:node(navWrapper)
    elseif border == 'subgroup' or border == 'child' then
        -- Assume this navboxMobile is being rendered in a list cell of a parent navboxMobile
        -- Insert closing and opening divs to manage padding
        res
            :wikitext('</div>') -- Close parent div
            :node(tbl)
            :wikitext('<div>') -- Reopen parent div
    else
        -- Wrap the table within a navigation div with additional classes and styles
        local navWrapper = mw.html.create('div')
            :attr('role', 'navigation')
            :addClass('navboxMobile') -- Add appropriate classes
            :css('border-spacing', 0)
            :cssText(args.bodystyle)
            :cssText(args.style)
            :node(tbl)
        res:node(navWrapper)
    end

    -- Render tracking categories
    renderTrackingCategories(res)

    return tostring(res)
end

--
--   Entry point function for the template
--
function p.navboxMobile(frame)
    if not getArgs then
        getArgs = require('Module:Arguments').getArgs
    end
    args = getArgs(frame, {wrappers = 'Template:Navbox'})

    -- Read the arguments in the order they'll be output in, to make references number in the right order.
    -- This ensures that list numbers are processed sequentially.
    local _
    _ = args.title
    _ = args.above
    for i = 1, 20 do
        _ = args["group" .. tostring(i)]
        _ = args["list" .. tostring(i)]
    end
    _ = args.below

    return p._navboxMobile(args)
end

return p