Module:PerksView: Difference between revisions

From Against the Storm Official Wiki
(Created to replace the table templates that are way too slow)
 
m (adding finalize method to return the completed markup)
Line 372: Line 372:


---startTable
---startTable
---@param caption table
---@param caption string the desired caption
---@param viewParameters table
---@param viewParameters table a ViewParameters initialized with its constructor
---@return table
---@return table an html node, the htmlTable member variable
function PerksView.startTable(caption, viewParameters)
function PerksView.startTable(caption, viewParameters)


Line 384: Line 384:


---addRow
---addRow
---@param id table
---@param id string the ID of the perk
---@param name table
---@param name string the name of the perk
---@param rarity table
---@param rarity string the rarity of the perk expressed as a string
---@param description table
---@param description string the description of the perk
---@param isSourceAltar table
---@param isSourceAltar boolean whether the perk is acquired at the altar
---@param isSourceCornerstone table
---@param isSourceCornerstone boolean whether the perk is acquired as a cornerstone
---@param isSourceOrder table
---@param isSourceOrder boolean whether the perk is acquired from orders
---@param isSourceRelic table
---@param isSourceRelic boolean whether the perk is acquired from relics
---@param isSourceTrader table
---@param isSourceTrader boolean whether the perk is acquired from traders
---@param price table
---@param price number the purchase price at a trader
---@param viewParameters table
---@param viewParameters table a ViewParameters initialized with its constructor
---@return table
---@return table an html node, the htmlTable member variable
function PerksView.addRow(id, name, rarity, description, isSourceAltar, isSourceCornerstone, isSourceOrder, isSourceRelic, isSourceTrader, price, viewParameters)
function PerksView.addRow(id, name, rarity, description, isSourceAltar, isSourceCornerstone, isSourceOrder, isSourceRelic, isSourceTrader, price, viewParameters)


Line 414: Line 414:


     return htmlTable
     return htmlTable
end
---finalize
---@return string the complete view fully rendered
function PerksView.finalize()
    return tostring(htmlTable)
end
end



Revision as of 21:06, 23 June 2024

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

---
--- Serves the Perks searching template by capturing input and using it to control the display of data.
---
---@module PerksView
local PerksView = {}

--region Dependencies

--endregion



--region Private constants

--local ARG_ID_LIST = "id"
--local ARG_NAME_LIST = "name"
--local ARG_DESCRIPTION_LIST = "description"
--local ARG_SEARCH_ALL_LIST = "search"
--local ARG_RARITY = "rarity"
--local ARG_SOURCE = "source"
--local ARG_EXCLUDE_LIST = "exclude"
--local ARG_CAPTION = "caption"
--
--local ARG_DISPLAY_OVERRIDE = "display"
--local ARG_DISPLAY_OVERRIDE_OPTION_LIST = "list"
--local ARG_DISPLAY_OVERRIDE_OPTION_INLINE = "inline"
--local ARG_LIST_TYPE = "list_type"

local DEFAULT_CAPTION = "Perks and Cornerstones"

local CLASS_PERKS_TABLE = "wikitable sortable mw-collapsible"
local CLASS_UNSORTABLE = "unsortable"

local ATTR_SPAN_TWO_ROWS = { rowspan="2" }

local HEADER_ID = "ID"
local HEADER_NAME = "Name"
local HEADER_RARITY = "Rarity"
local HEADER_DESCRIPTION = "Description"
local HEADER_SOURCES = "Sources"
local HEADER_PRICE = "Price"

local HEADER_ABBR_ALTAR = tostring(mw.html.create("abbr"):attr({ title="Altar (Stormforged Cornerstone)" }):wikitext("Alt"))
local HEADER_ABBR_CORNERSTONE = tostring(mw.html.create("abbr"):attr({ title="Cornerstone" }):wikitext("Cor"))
local HEADER_ABBR_ORDER = tostring(mw.html.create("abbr"):attr({ title="Order"}):wikitext("Ord"))
local HEADER_ABBR_RELIC = tostring(mw.html.create("abbr"):attr({ title="Relic (Glade Event)" }):wikitext("Rel"))
local HEADER_ABBR_TRADER = tostring(mw.html.create("abbr"):attr({ title="Trader" }):wikitext("Tra"))

local RED_X = mw.getCurrentFrame():expandTemplate{ title = "c", args = { [1] = "Red", [2] = "x" } }

--endregion



--region Public sub-class

--- This subclass should never be created directly, but only via the constructFromTemplateFrame method, to ensure data integrity and minimize chance for errors.
PerksView.ViewParameters = {}

-- Indexes
local ARG_SKIP_SOURCES = "skip_sources"
local ARG_SHOW_ID = "show_id"
local ARG_SHOW_RARITY = "show_rarity"
local ARG_SHOW_DESCRIPTION = "show_description"
local ARG_SHOW_SOURCE_ALTAR = "show_source_altar"
local ARG_SHOW_SOURCE_CORNERSTONE = "show_source_cornerstone"
local ARG_SHOW_SOURCE_ORDER = "show_source_order"
local ARG_SHOW_SOURCE_RELIC = "show_source_relic"
local ARG_SHOW_SOURCE_TRADER = "show_source_trader"
local ARG_SHOW_PRICE = "show_price"
-- Flags
local ARG_SKIP_FLAG_VALUE = "skip"
local ARG_SHOW_FLAG_VALUE = "show"

function PerksView.ViewParameters.isShowingID(self)
    return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_ID]
end
function PerksView.ViewParameters.isShowingName()
    return true
end
function PerksView.ViewParameters.isShowingRarity(self)
    return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_RARITY]
end
function PerksView.ViewParameters.isShowingDescription(self)
    return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_DESCRIPTION]
end
function PerksView.ViewParameters.isShowingSources(self)
    return ARG_SKIP_FLAG_VALUE ~= self[ARG_SKIP_SOURCES]
end
function PerksView.ViewParameters.isShowingSourceAltar(self)
    return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_SOURCE_ALTAR]
end
function PerksView.ViewParameters.isShowingSourceCornerstone(self)
   return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_SOURCE_CORNERSTONE]
end
function PerksView.ViewParameters.isShowingSourceOrder(self)
    return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_SOURCE_ORDER]
end
function PerksView.ViewParameters.isShowingSourceRelic(self)
    return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_SOURCE_RELIC]
end
function PerksView.ViewParameters.isShowingSourceTrader(self)
    return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_SOURCE_TRADER]
end
function PerksView.ViewParameters.isShowingPrice(self)
    return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_PRICE]
end

local checkerSwitch = {
    [HEADER_ID] = PerksView.ViewParameters.isShowingID,
    [HEADER_NAME] = PerksView.ViewParameters.isShowingName,
    [HEADER_RARITY] = PerksView.ViewParameters.isShowingRarity,
    [HEADER_DESCRIPTION] = PerksView.ViewParameters.isShowingDescription,
    [HEADER_SOURCES] = PerksView.ViewParameters.isShowingSources,
    [HEADER_PRICE] = PerksView.ViewParameters.isShowingPrice,
    [HEADER_ABBR_ALTAR] = PerksView.ViewParameters.isShowingSourceAltar,
    [HEADER_ABBR_CORNERSTONE] = PerksView.ViewParameters.isShowingSourceCornerstone,
    [HEADER_ABBR_ORDER] = PerksView.ViewParameters.isShowingSourceOrder,
    [HEADER_ABBR_RELIC] = PerksView.ViewParameters.isShowingSourceRelic,
    [HEADER_ABBR_TRADER] = PerksView.ViewParameters.isShowingSourceTrader
}

---getCheckerMethod
---@param headerLabel string the label displayed on the table
---@return function the function to call to see whether that column should be shown
function PerksView.ViewParameters.getCheckerMethod(headerLabel)
    return checkerSwitch[headerLabel]
end

---constructViewParametersFromTemplateFrame
---@param frame table the mediawiki template's calling frame
---@return table an instance of the ViewParameters class
function PerksView.constructViewParametersFromTemplateFrame(frame)
    local newInstance = {}
    newInstance[ARG_SKIP_SOURCES] = frame.args[ARG_SKIP_SOURCES] or ARG_SKIP_FLAG_VALUE
    newInstance[ARG_SHOW_ID] = frame.args[ARG_SHOW_ID] or ARG_SHOW_FLAG_VALUE
    newInstance[ARG_SHOW_RARITY] = frame.args[ARG_SHOW_RARITY] or ARG_SHOW_FLAG_VALUE
    newInstance[ARG_SHOW_DESCRIPTION] = frame.args[ARG_SHOW_DESCRIPTION] or ARG_SHOW_FLAG_VALUE
    newInstance[ARG_SHOW_SOURCE_ALTAR] = frame.args[ARG_SHOW_SOURCE_ALTAR] or ARG_SHOW_FLAG_VALUE
    newInstance[ARG_SHOW_SOURCE_CORNERSTONE] = frame.args[ARG_SHOW_SOURCE_CORNERSTONE] or ARG_SHOW_FLAG_VALUE
    newInstance[ARG_SHOW_SOURCE_ORDER] = frame.args[ARG_SHOW_SOURCE_ORDER] or ARG_SHOW_FLAG_VALUE
    newInstance[ARG_SHOW_SOURCE_RELIC] = frame.args[ARG_SHOW_SOURCE_RELIC] or ARG_SHOW_FLAG_VALUE
    newInstance[ARG_SHOW_SOURCE_TRADER] = frame.args[ARG_SHOW_SOURCE_TRADER] or ARG_SHOW_FLAG_VALUE
    newInstance[ARG_SHOW_PRICE] = frame.args[ARG_SHOW_PRICE] or ARG_SHOW_FLAG_VALUE

    -- Attach methods to the instance
    setmetatable(newInstance, { __index = PerksView.ViewParameters })

    return newInstance
end

--endregion



--region Private member variables

local htmlTable

--endregion



--region Private methods

---createCaptionNode
---@param captionText string the desired caption
---@return table the html node of the caption tags
local function createCaptionNode(captionText)

    captionNode = mw.html.create("caption")

    if null == captionText or "" == captionText then
        captionNode:wikitext(DEFAULT_CAPTION)
    else
        captionNode:wikitext(captionText)
    end

    return captionNode
end

---openTable
---@param caption string the desired caption
---@return table the complete html node, htmlTable, the class variable
local function openTable(caption)

    htmlTable = mw.html.create("table")
    htmlTable:addClass(CLASS_PERKS_TABLE):newline()
    htmlTable:node(createCaptionNode(caption)):newline()

    return htmlTable
end

---createHeaderCell
---@param label string the label to put in the header cell
---@return table a new html table header cell
local function createHeaderCell(label)

    local cell = mw.html.create("th")
    cell:wikitext(label)

    -- Assign the methods so they can be chained
    cell.spanTwoRows = function(self)
        self:attr(ATTR_SPAN_TWO_ROWS)
        return self
    end
    cell.makeUnsortable = function(self)
        self:addClass(CLASS_UNSORTABLE)
        return self
    end

    return cell
end

---loopThroughHeaderCells
---@param row table the html table row node to use
---@param preconfiguredRowToUse table the row already setup to choose from
---@param viewParameters table the ViewParameters specified by the author
---@return table the same row, now with more stuff in it
local function addAllCellsThatShouldBeShowing(row, preconfiguredRowToUse, viewParameters)

    for _, rowItem in ipairs(preconfiguredRowToUse) do
        local isShowingIn = PerksView.ViewParameters.getCheckerMethod(rowItem.label)
        if isShowingIn(viewParameters) then
            row:node(rowItem.cell):newline()
        end
    end
end

---addHeaderRow adds header rows to the member variable htmlTable and returns what was added.
---
---@param viewParameters table a ViewParameters initialized with its constructor
---@return table, table the header row added, sometimes a second sub-header row
local function addHeaderRow(viewParameters)

    -- Configure the options; we'll figure out which to create in a moment. Separating the setup from the logic makes this significantly easier to read and understand. These cannot be key-value pairs, like [label] = cell, because we have to use numerical indexes to preserve their order.
    local headersInOrderWithoutSources = {
        { label = HEADER_ID, cell = createHeaderCell(HEADER_ID) },
        { label = HEADER_NAME, cell = createHeaderCell(HEADER_NAME) },
        { label = HEADER_RARITY, cell = createHeaderCell(HEADER_RARITY) },
        { label = HEADER_DESCRIPTION, cell = createHeaderCell(HEADER_DESCRIPTION):makeUnsortable() },
        { label = HEADER_PRICE, cell = createHeaderCell(HEADER_PRICE) }
    }
    local headersInOrderWithSources = {
        { label = HEADER_ID, cell = createHeaderCell(HEADER_ID):spanTwoRows() },
        { label = HEADER_NAME, cell = createHeaderCell(HEADER_NAME):spanTwoRows() },
        { label = HEADER_RARITY, cell = createHeaderCell(HEADER_RARITY):spanTwoRows() },
        { label = HEADER_DESCRIPTION, cell = createHeaderCell(HEADER_DESCRIPTION):spanTwoRows():makeUnsortable() },
        { label = HEADER_SOURCES, cell = createHeaderCell(HEADER_SOURCES) },
        { label = HEADER_PRICE, cell = createHeaderCell(HEADER_PRICE):spanTwoRows() }
    }
    local subheadersInOrderUnderSources = {
        { label = HEADER_ABBR_ALTAR, cell = createHeaderCell(HEADER_ABBR_ALTAR) },
        { label = HEADER_ABBR_CORNERSTONE, cell = createHeaderCell(HEADER_ABBR_CORNERSTONE) },
        { label = HEADER_ABBR_ORDER, cell = createHeaderCell(HEADER_ABBR_ORDER) },
        { label = HEADER_ABBR_RELIC, cell = createHeaderCell(HEADER_ABBR_RELIC) },
        { label = HEADER_ABBR_TRADER, cell = createHeaderCell(HEADER_ABBR_TRADER) }
    }

    -- If there are subheaders, populate two rows then add them to the main htmlTable
    if viewParameters:isShowingSources() then

        local headerRow = mw.html.create("tr")
        headerRow:newline()
        addAllCellsThatShouldBeShowing(headerRow, headersInOrderWithSources, viewParameters)

        local subheaderRow = mw.html.create("tr")
        subheaderRow:newline()
        addAllCellsThatShouldBeShowing(subheaderRow, subheadersInOrderUnderSources, viewParameters)

        htmlTable:node(headerRow):newline()
        htmlTable:node(subheaderRow):newline()

        return headerRow, subheaderRow

    else
        -- No subheaders, just populate one row then add it to the main htmlTable.
        local headerRow = mw.html.create("tr")
        headerRow:newline()
        addAllCellsThatShouldBeShowing(headerRow, headersInOrderWithoutSources, viewParameters)

        htmlTable:node(headerRow):newline()

        return headerRow
    end
end

---createDataCell
---@param label string the label to put in the cell
---@return table a new html table data cell
local function createDataCell(label)

    local cell = mw.html.create("td")
    cell:wikitext(label)
    return cell
end

---addDataRow
---@param data table a table of data configured with keys to match the labels in cellsInOrder in this method
---@param viewParameters table a ViewParameters initialized with its constructor
---@return table the data row added, sometimes a second sub-header row
 local function addDataRow(data, viewParameters)

    -- Configure the table; we'll figure out which to actually show in a moment. Separating the setup from the logic makes this significantly easier to read and understand. These cannot be key-value pairs, like [label] = cell, because we have to use numerical indexes to preserve their order.
    local cellsInOrder = {
        { label = HEADER_ID, cell = createDataCell(data[HEADER_ID] or "—") },
        { label = HEADER_NAME, cell = createDataCell(data[HEADER_NAME] or "—") },
        { label = HEADER_RARITY, cell = createDataCell(data[HEADER_RARITY] or "—") },
        { label = HEADER_DESCRIPTION, cell = createDataCell(data[HEADER_DESCRIPTION] or "—") },
        { label = HEADER_ABBR_ALTAR, cell = createDataCell(data[HEADER_ABBR_ALTAR] and HEADER_ABBR_ALTAR or RED_X) },
        { label = HEADER_ABBR_CORNERSTONE, cell = createDataCell(data[HEADER_ABBR_CORNERSTONE] and HEADER_ABBR_CORNERSTONE or RED_X) },
        { label = HEADER_ABBR_ORDER, cell = createDataCell(data[HEADER_ABBR_ORDER] and HEADER_ABBR_ORDER or RED_X) },
        { label = HEADER_ABBR_RELIC, cell = createDataCell(data[HEADER_ABBR_RELIC] and HEADER_ABBR_RELIC or RED_X) },
        { label = HEADER_ABBR_TRADER, cell = createDataCell(data[HEADER_ABBR_TRADER] and HEADER_ABBR_TRADER or RED_X) },
        { label = HEADER_PRICE, cell = createDataCell(data[HEADER_PRICE] or "—") }
    }

    local dataRow = mw.html.create("tr")
    dataRow:newline()
    addAllCellsThatShouldBeShowing(dataRow, cellsInOrder, viewParameters)

    htmlTable:node(dataRow):newline()

    return dataRow
end

---validateStringData
---@param stringData string a string to display
---@return string the same string, or nil if it's empty
local function validateStringData(stringData)

    if "" == stringData or " " == stringData then
        return nil
    else
        return stringData
    end
end

---validateBooleanData
---@param booleanData boolean a boolean to display
---@return boolean the same boolean, or false if it's anything besides a true value
local function validateBooleanData(booleanData)

    if true == booleanData or "true" == booleanData then
        return true
    else
        return nil
    end
end

---validateNumberData
---@param numberData number a number to display
---@return number the same number, or nil if it was invalid or 0
local function validateNumberData(numberData)

    if type(numberData) ~= "number" then
        return tonumber(numberData)
    else
        if 0 > numberData then
            return nil
        else
            return numberData
        end
    end
end

--endregion



--region Public methods

---startTable
---@param caption string the desired caption
---@param viewParameters table a ViewParameters initialized with its constructor
---@return table an html node, the htmlTable member variable
function PerksView.startTable(caption, viewParameters)

    openTable(caption)
    addHeaderRow(viewParameters)

    return htmlTable
end

---addRow
---@param id string the ID of the perk
---@param name string the name of the perk
---@param rarity string the rarity of the perk expressed as a string
---@param description string the description of the perk
---@param isSourceAltar boolean whether the perk is acquired at the altar
---@param isSourceCornerstone boolean whether the perk is acquired as a cornerstone
---@param isSourceOrder boolean whether the perk is acquired from orders
---@param isSourceRelic boolean whether the perk is acquired from relics
---@param isSourceTrader boolean whether the perk is acquired from traders
---@param price number the purchase price at a trader
---@param viewParameters table a ViewParameters initialized with its constructor
---@return table an html node, the htmlTable member variable
function PerksView.addRow(id, name, rarity, description, isSourceAltar, isSourceCornerstone, isSourceOrder, isSourceRelic, isSourceTrader, price, viewParameters)

    local data = {
        [HEADER_ID] = validateStringData(id),
        [HEADER_NAME] = validateStringData(name),
        [HEADER_RARITY] = validateStringData(rarity),
        [HEADER_DESCRIPTION] = validateStringData(description),
        [HEADER_ABBR_ALTAR] = validateBooleanData(isSourceAltar),
        [HEADER_ABBR_CORNERSTONE] = validateBooleanData(isSourceCornerstone),
        [HEADER_ABBR_ORDER] = validateBooleanData(isSourceOrder),
        [HEADER_ABBR_RELIC] = validateBooleanData(isSourceRelic),
        [HEADER_ABBR_TRADER] = validateBooleanData(isSourceTrader),
        [HEADER_PRICE] = validateNumberData(price)
    }

    addDataRow(data, viewParameters)

    return htmlTable
end

---finalize
---@return string the complete view fully rendered
function PerksView.finalize()
    return tostring(htmlTable)
end

--endregion

return PerksView