Module:OrdersView

From Against the Storm Official Wiki

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

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

--region Dependencies
--- remove this dependency later
local OrdersData = require("Module:OrdersData")
--endregion



--region Private constants

local DEFAULT_CAPTION = "Orders"

local CLASS_ORDERS_TABLE = "wikitable mw-collapsible table-nobands"
local CLASS_ORDERS_COLLAPSED = "wikitable mw-collapsible mw-collapsed table-nobands"
local CLASS_UNSORTABLE = "unsortable"

local ORDER_LINK_ICON_SIZE = "large"

local HEADER_ID = "ID"
local HEADER_NAME = "Name"
local HEADER_REQUIREMENTS = "Requirements"
local HEADER_REWARDS = "Rewards"
local HEADER_TIER = "Tier"
local HEADER_TIMED = "Timed Order?"
local HEADER_TIME_LIMIT = "Time Limit (seconds)"
local HEADER_DIFFICULTY = "Difficulty"

--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.
OrdersView.ViewParameters = {}

-- Indexes
local ARG_SHOW_ID = "show_id"
local ARG_SHOW_TIER = "show_tier"
local ARG_SHOW_TIMED = "show_timed"
local ARG_SHOW_TIME_LIMIT = "show_time_limit"
local ARG_SHOW_DIFFICULTY = "show_difficulty"

-- Flags
local ARG_SKIP_FLAG_VALUE = "skip"
local ARG_SHOW_FLAG_VALUE = "show"

function OrdersView.ViewParameters.isShowingID(self)
    return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_ID]
end
function OrdersView.ViewParameters.isShowingName()
    return true
end
function OrdersView.ViewParameters.isShowingTier(self)
    return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_TIER]
end
function OrdersView.ViewParameters.isShowingTimed(self)
    return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_TIMED]
end
function OrdersView.ViewParameters.isShowingTimeLimit(self)
    return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_TIME_LIMIT]
end
function OrdersView.ViewParameters.isShowingRequirements()
    return true
end
function OrdersView.ViewParameters.isShowingRewards()
    return true
end
function OrdersView.ViewParameters.isShowingDifficulty(self)
    return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_DIFFICULTY]
end

function OrdersView.ViewParameters.countShownExtraColumns(self)

    local count = 0
    if self:isShowingID() then
        count = count + 1
    end
    if self:isShowingTier() then
        count = count + 1
    end
    if self:isShowingTimed() then
        count = count + 1
    end
    if self:isShowingTimeLimit() then
        count = count + 1
    end
    if self:isShowingDifficulty() then
        count = count + 1
    end

    -- No matter what, one column.
    if 0 == count then
        return 1
    else
        return count
    end
end

local checkerSwitch = {
    [HEADER_ID] = OrdersView.ViewParameters.isShowingID,
    [HEADER_NAME] = OrdersView.ViewParameters.isShowingName,
    [HEADER_TIER] = OrdersView.ViewParameters.isShowingTier,
    [HEADER_TIMED] = OrdersView.ViewParameters.isShowingTimed,
    [HEADER_REQUIREMENTS] = OrdersView.ViewParameters.isShowingRequirements,
    [HEADER_REWARDS] = OrdersView.ViewParameters.isShowingRewards,
    [HEADER_TIME_LIMIT] = OrdersView.ViewParameters.isShowingTimeLimit,
    [HEADER_DIFFICULTY] = OrdersView.ViewParameters.isShowingDifficulty,
}

---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 OrdersView.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 OrdersView.constructViewParametersFromTemplateFrame(frame)
    local newInstance = {}
    newInstance[ARG_SHOW_ID] = frame.args[ARG_SHOW_ID] or "not by default"
    newInstance[ARG_SHOW_TIER] = frame.args[ARG_SHOW_TIER] or ARG_SHOW_FLAG_VALUE
    newInstance[ARG_SHOW_TIMED] = frame.args[ARG_SHOW_TIMED] or ARG_SHOW_FLAG_VALUE
    newInstance[ARG_SHOW_TIME_LIMIT] = frame.args[ARG_SHOW_TIME_LIMIT] or ARG_SHOW_FLAG_VALUE
    newInstance[ARG_SHOW_DIFFICULTY] = frame.args[ARG_SHOW_DIFFICULTY] or ARG_SHOW_FLAG_VALUE
    -- Attach methods to the instance
    setmetatable(newInstance, { __index = OrdersView.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, collapsed)

    htmlTable = mw.html.create("table")
    if collapsed then
    	htmlTable:addClass(CLASS_ORDERS_COLLAPSED):newline()
    else
    	htmlTable:addClass(CLASS_ORDERS_TABLE):newline()
    end
    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.spanMultipleRows = function(self, rowsToSpan)
        self:attr({ rowspan=rowsToSpan })
        return self
    end
    cell.makeUnsortable = function(self)
        self:addClass(CLASS_UNSORTABLE)
        return self
    end
    cell.spanMultipleColumns = function(self, columnsToSpan)
        self:attr({ colspan=columnsToSpan })
        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 = OrdersView.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 headersInOrder = {
        { label = HEADER_ID, cell = createHeaderCell(HEADER_ID) },
        { label = HEADER_TIER, cell = createHeaderCell(HEADER_TIER) },
        { label = HEADER_TIMED, cell = createHeaderCell(HEADER_TIMED) },
        { label = HEADER_TIME_LIMIT, cell = createHeaderCell(HEADER_TIME_LIMIT) },
        { label = HEADER_NAME, cell = createHeaderCell(HEADER_NAME) },
        { label = HEADER_DIFFICULTY, cell = createHeaderCell(HEADER_DIFFICULTY):makeUnsortable() },
        { label = HEADER_REQUIREMENTS, cell = createHeaderCell(HEADER_REQUIREMENTS):makeUnsortable() },
        { label = HEADER_REWARDS, cell = createHeaderCell(HEADER_REWARDS) }
    }

    -- If there are subheaders, populate two rows then add them to the main htmlTable
    
    -- No subheaders, just populate one row then add it to the main htmlTable.
    local headerRow = mw.html.create("tr")
    headerRow:newline()
    addAllCellsThatShouldBeShowing(headerRow, headersInOrder, viewParameters)

    htmlTable:node(headerRow):newline()

    return headerRow

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)
    -- Assign the methods so they can be chained
    cell.spanMultipleRows = function(self, rowsToSpan)
        self:attr({ rowspan=rowsToSpan })
        return self
    end
    cell.makeUnsortable = function(self)
        self:addClass(CLASS_UNSORTABLE)
        return self
    end
    cell.spanMultipleColumns = function(self, columnsToSpan)
        self:attr({ colspan=columnsToSpan })
        return self
    end
    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, rowSpan)

    -- 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 = {}

    if rowSpan > 0 then
    	cellsInOrder = {
        { label = HEADER_ID, cell = createDataCell(data[HEADER_ID] or "—"):spanMultipleRows(rowSpan) },
        { label = HEADER_TIER, cell = createDataCell(data[HEADER_TIER] or "—"):spanMultipleRows(rowSpan) },
        { label = HEADER_TIMED, cell = createDataCell(data[HEADER_TIMED] or "—"):spanMultipleRows(rowSpan) },
        { label = HEADER_TIME_LIMIT, cell = createDataCell(data[HEADER_TIME_LIMIT] or "—"):spanMultipleRows(rowSpan) },
        { label = HEADER_NAME, cell = createDataCell(data[HEADER_NAME] or "—"):spanMultipleRows(rowSpan) },
        { label = HEADER_DIFFICULTY, cell = createDataCell(data[HEADER_DIFFICULTY] or "—") },
        { label = HEADER_REQUIREMENTS, cell = createDataCell(data[HEADER_REQUIREMENTS] or "—") },
        { label = HEADER_REWARDS, cell = createDataCell(data[HEADER_REWARDS] or "—"):spanMultipleRows(rowSpan) }
    }
    else
    	cellsInOrder = {
        { label = HEADER_ID, cell = createDataCell(data[HEADER_ID] or "—") },
        { label = HEADER_TIER, cell = createDataCell(data[HEADER_TIER] or "—") },
        { label = HEADER_TIMED, cell = createDataCell(data[HEADER_TIMED] or "—") },
        { label = HEADER_TIME_LIMIT, cell = createDataCell(data[HEADER_TIME_LIMIT] or "—") },
        { label = HEADER_NAME, cell = createDataCell(data[HEADER_NAME] or "—") },
        { label = HEADER_DIFFICULTY, cell = createDataCell(data[HEADER_DIFFICULTY] or "—") },
        { label = HEADER_REQUIREMENTS, cell = createDataCell(data[HEADER_REQUIREMENTS] or "—") },
        { label = HEADER_REWARDS, cell = createDataCell(data[HEADER_REWARDS] or "—") }
    }
    end
	

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

    htmlTable:node(dataRow):newline()

    return dataRow


end


---addExtraRow
---@param data table a table of data configured with keys to match the labels in cellsInOrder in this method
---@return table the data row added, which only includes the difficulty as a separate row
 local function addExtraRow(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_DIFFICULTY, cell = createDataCell(data[HEADER_DIFFICULTY] or "—") },
        { label = HEADER_REQUIREMENTS, cell = createDataCell(data[HEADER_REQUIREMENTS] or "—") },
    }

    -- If skipping the sources altogether overrides individual column visibility.
   
    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)

	stringData = tostring(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

local function createOrderLink(id)

    return mw.getCurrentFrame():expandTemplate{
        title = "ol",
        args = { ["id"] = id, ["iconsize"] = ORDER_LINK_ICON_SIZE }
    }
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 OrdersView.startTable(caption, viewParameters, collapsed)

    openTable(caption, collapsed)
    addHeaderRow(viewParameters)

    return htmlTable
end

---addRow
---@param id string the ID of the order
---@param _ string the name of the order is not actually used, but asked for because the Controller doesn't need to know
---@param rarity string the rarity of the order expressed as a string
---@param description string the description of the order
---@param isSourceAltar boolean whether the order is acquired at the altar
---@param isSourceCornerstone boolean whether the order is acquired as a cornerstone
---@param isSourceOrder boolean whether the order is acquired from orders
---@param isSourceRelic boolean whether the order is acquired from relics
---@param isSourceTrader boolean whether the order 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 OrdersView.addRow(id, _, tier, timed, timeLimit, difficulty, requirements, rewards, viewParameters, isExtraRow, rowSpan)

    local data = {
        [HEADER_ID] = validateStringData(id),
        ---TODO: change back to createOrderLink once ready
        [HEADER_NAME] = validateStringData(OrdersData.getNameByID(id)), --createOrderLink(validateStringData(id)),
        [HEADER_TIER] = validateStringData(tier),
        [HEADER_TIMED] = validateStringData(timed),
        [HEADER_TIME_LIMIT] = validateNumberData(timeLimit),
        [HEADER_DIFFICULTY] = validateStringData(difficulty),
        [HEADER_REQUIREMENTS] = validateStringData(requirements),
        [HEADER_REWARDS] = validateStringData(rewards),
    }


	if isExtraRow then
		addExtraRow(data, viewParameters)
	else
    	addDataRow(data, viewParameters, rowSpan)
    end

    return htmlTable
end

---finalize
---@return table the complete html table node, fully rendered
function OrdersView.finalize()

    return htmlTable
end

--endregion

return OrdersView