Module:MysteriesView
From Against the Storm Official Wiki
Documentation for this module may be created at Module:MysteriesView/doc
---
--- Serves the Mysteries searching template by providing markup to the controller for display on the page.
---
---@module MysteriesView
local MysteriesView = {}
--region Dependencies
local StyleUtils = require("Module:StyleUtils")
--endregion
--region Private constants
local DEFAULT_CAPTION = "Forest Mysteries"
local CLASS_MYSTERIES_TABLE = "wikitable sortable mw-collapsible"
local CLASS_UNSORTABLE = "unsortable"
local MYSTERY_LINK_ICON_SIZE = StyleUtils.IMG_L()
local HEADER_ID = "ID"
local HEADER_NAME = "Name"
local HEADER_DESCRIPTION = "Description"
local HEADER_SEVERITY = "Severity"
local HEADER_HOSTILITY_LEVEL = "Hostility Level"
local HEADER_SEASON = "Season"
local HEADER_CONDITIONS = "Conditions"
local HEADER_ABBR_HOUSING = tostring(mw.html.create("abbr"):attr({ title="Housing Need Satisfied" }):wikitext("Hs"))
local HEADER_ABBR_FOOD = tostring(mw.html.create("abbr"):attr({ title="Complex Food Need Satisfied" }):wikitext("CF"))
local HEADER_ABBR_SERVICES = tostring(mw.html.create("abbr"):attr({ title="Services Need Satisfied" }):wikitext("Sv"))
local RED_X = tostring(mw.html.create("span"):attr({ style="color: #FF2A00;" }):wikitext("×"))
local REPLACEMENT_FILENAME = "Question_mark.png"
--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.
MysteriesView.ViewParameters = {}
-- Indexes
local ARG_SKIP_CONDITIONS = "skip_conditions"
local ARG_SHOW_ID = "show_id"
local ARG_SHOW_DESCRIPTION = "show_description"
local ARG_SHOW_SEVERITY = "show_severity"
local ARG_SHOW_HOSTILITY_LEVEL = "show_hostility"
local ARG_SHOW_SEASON = "show_season"
local ARG_SHOW_CONDITION_HOUSING = "show_condition_housing"
local ARG_SHOW_CONDITION_FOOD = "show_condition_food"
local ARG_SHOW_CONDITION_SERVICES = "show_condition_services"
-- Flags
local ARG_SKIP_FLAG_VALUE = "skip"
local ARG_SHOW_FLAG_VALUE = "show"
function MysteriesView.ViewParameters.isShowingID(self)
return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_ID]
end
function MysteriesView.ViewParameters.isShowingName()
return true
end
function MysteriesView.ViewParameters.isShowingDescription(self)
return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_DESCRIPTION]
end
function MysteriesView.ViewParameters.isShowingSeverity(self)
return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_SEVERITY]
end
function MysteriesView.ViewParameters.isShowingHostilityLevel(self)
return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_HOSTILITY_LEVEL]
end
function MysteriesView.ViewParameters.isShowingSeason(self)
return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_SEASON]
end
function MysteriesView.ViewParameters.isShowingConditions(self)
-- This comparison is different!
return ARG_SKIP_FLAG_VALUE ~= self[ARG_SKIP_CONDITIONS]
end
function MysteriesView.ViewParameters.isShowingConditionHousing(self)
return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_CONDITION_HOUSING]
end
function MysteriesView.ViewParameters.isShowingConditionFood(self)
return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_CONDITION_FOOD]
end
function MysteriesView.ViewParameters.isShowingConditionServices(self)
return ARG_SHOW_FLAG_VALUE == self[ARG_SHOW_CONDITION_SERVICES]
end
function MysteriesView.ViewParameters.countShownConditionColumns(self)
local count = 0
if self:isShowingConditionHousing() then
count = count + 1
end
if self:isShowingConditionFood() then
count = count + 1
end
if self:isShowingConditionServices() 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] = MysteriesView.ViewParameters.isShowingID,
[HEADER_NAME] = MysteriesView.ViewParameters.isShowingName,
[HEADER_DESCRIPTION] = MysteriesView.ViewParameters.isShowingDescription,
[HEADER_SEVERITY] = MysteriesView.ViewParameters.isShowingSeverity,
[HEADER_HOSTILITY_LEVEL] = MysteriesView.ViewParameters.isShowingHostilityLevel,
[HEADER_SEASON] = MysteriesView.ViewParameters.isShowingSeason,
[HEADER_CONDITIONS] = MysteriesView.ViewParameters.isShowingConditions,
[HEADER_ABBR_HOUSING] = MysteriesView.ViewParameters.isShowingConditionHousing,
[HEADER_ABBR_FOOD] = MysteriesView.ViewParameters.isShowingConditionFood,
[HEADER_ABBR_SERVICES] = MysteriesView.ViewParameters.isShowingConditionServices
}
---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 MysteriesView.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 MysteriesView.constructViewParametersFromTemplateFrame(frame)
local newInstance = {}
newInstance[ARG_SKIP_CONDITIONS] = frame.args[ARG_SKIP_CONDITIONS] or "not by default"
newInstance[ARG_SHOW_ID] = frame.args[ARG_SHOW_ID] or "not by default"
newInstance[ARG_SHOW_DESCRIPTION] = frame.args[ARG_SHOW_DESCRIPTION] or ARG_SHOW_FLAG_VALUE
newInstance[ARG_SHOW_SEVERITY] = frame.args[ARG_SHOW_SEVERITY] or ARG_SHOW_FLAG_VALUE
newInstance[ARG_SHOW_HOSTILITY_LEVEL] = frame.args[ARG_SHOW_HOSTILITY_LEVEL] or ARG_SHOW_FLAG_VALUE
newInstance[ARG_SHOW_SEASON] = frame.args[ARG_SHOW_SEASON] or ARG_SHOW_FLAG_VALUE
newInstance[ARG_SHOW_CONDITION_HOUSING] = frame.args[ARG_SHOW_CONDITION_HOUSING] or ARG_SHOW_FLAG_VALUE
newInstance[ARG_SHOW_CONDITION_FOOD] = frame.args[ARG_SHOW_CONDITION_FOOD] or ARG_SHOW_FLAG_VALUE
newInstance[ARG_SHOW_CONDITION_SERVICES] = frame.args[ARG_SHOW_CONDITION_SERVICES] or ARG_SHOW_FLAG_VALUE
-- Attach methods to the instance
setmetatable(newInstance, { __index = MysteriesView.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_MYSTERIES_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({ rowspan="2" })
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 = MysteriesView.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 headersInOrderWithoutConditions = {
{ label = HEADER_ID, cell = createHeaderCell(HEADER_ID) },
{ label = HEADER_NAME, cell = createHeaderCell(HEADER_NAME) },
{ label = HEADER_DESCRIPTION, cell = createHeaderCell(HEADER_DESCRIPTION):makeUnsortable() },
{ label = HEADER_SEVERITY, cell = createHeaderCell(HEADER_SEVERITY) },
{ label = HEADER_HOSTILITY_LEVEL, cell = createHeaderCell(HEADER_HOSTILITY_LEVEL) },
{ label = HEADER_SEASON, cell = createHeaderCell(HEADER_SEASON) },
}
local headersInOrderWithConditions = {
{ label = HEADER_ID, cell = createHeaderCell(HEADER_ID):spanTwoRows() },
{ label = HEADER_NAME, cell = createHeaderCell(HEADER_NAME):spanTwoRows() },
{ label = HEADER_DESCRIPTION, cell = createHeaderCell(HEADER_DESCRIPTION):spanTwoRows():makeUnsortable() },
{ label = HEADER_SEVERITY, cell = createHeaderCell(HEADER_SEVERITY):spanTwoRows() },
{ label = HEADER_HOSTILITY_LEVEL, cell = createHeaderCell(HEADER_HOSTILITY_LEVEL):spanTwoRows() },
{ label = HEADER_SEASON, cell = createHeaderCell(HEADER_SEASON):spanTwoRows() },
{ label = HEADER_CONDITIONS, cell = createHeaderCell(HEADER_CONDITIONS):spanMultipleColumns(viewParameters:countShownConditionColumns()) },
}
local subheadersInOrderUnderConditions = {
{ label = HEADER_ABBR_HOUSING, cell = createHeaderCell(HEADER_ABBR_HOUSING) },
{ label = HEADER_ABBR_FOOD, cell = createHeaderCell(HEADER_ABBR_FOOD) },
{ label = HEADER_ABBR_SERVICES, cell = createHeaderCell(HEADER_ABBR_SERVICES) },
}
-- If there are subheaders, populate two rows then add them to the main htmlTable
if viewParameters:isShowingConditions() then
local headerRow = mw.html.create("tr")
headerRow:newline()
addAllCellsThatShouldBeShowing(headerRow, headersInOrderWithConditions, viewParameters)
local subheaderRow = mw.html.create("tr")
subheaderRow:newline()
addAllCellsThatShouldBeShowing(subheaderRow, subheadersInOrderUnderConditions, 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, headersInOrderWithoutConditions, 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 cellsInOrderWithoutConditions = {
{ label = HEADER_ID, cell = createDataCell(data[HEADER_ID] or "—") },
{ label = HEADER_NAME, cell = createDataCell(data[HEADER_NAME] or "—") },
{ label = HEADER_DESCRIPTION, cell = createDataCell(data[HEADER_DESCRIPTION] or "—") },
{ label = HEADER_SEVERITY, cell = createDataCell(data[HEADER_SEVERITY] or "—") },
{ label = HEADER_HOSTILITY_LEVEL, cell = createDataCell(data[HEADER_HOSTILITY_LEVEL] or "—") },
{ label = HEADER_SEASON, cell = createDataCell(data[HEADER_SEASON] or "—") }
}
local cellsInOrderWithConditions = {
{ label = HEADER_ID, cell = createDataCell(data[HEADER_ID] or "—") },
{ label = HEADER_NAME, cell = createDataCell(data[HEADER_NAME] or "—") },
{ label = HEADER_DESCRIPTION, cell = createDataCell(data[HEADER_DESCRIPTION] or "—") },
{ label = HEADER_SEVERITY, cell = createDataCell(data[HEADER_SEVERITY] or "—") },
{ label = HEADER_HOSTILITY_LEVEL, cell = createDataCell(data[HEADER_HOSTILITY_LEVEL] or "—") },
{ label = HEADER_SEASON, cell = createDataCell(data[HEADER_SEASON] or "—") },
{ label = HEADER_ABBR_HOUSING, cell = createDataCell(data[HEADER_ABBR_HOUSING] and HEADER_ABBR_HOUSING or RED_X) },
{ label = HEADER_ABBR_FOOD, cell = createDataCell(data[HEADER_ABBR_FOOD] and HEADER_ABBR_FOOD or RED_X) },
{ label = HEADER_ABBR_SERVICES, cell = createDataCell(data[HEADER_ABBR_SERVICES] and HEADER_ABBR_SERVICES or RED_X) },
}
-- Skipping the sources altogether overrides individual column visibility.
if viewParameters:isShowingConditions() then
local dataRow = mw.html.create("tr")
dataRow:newline()
addAllCellsThatShouldBeShowing(dataRow, cellsInOrderWithConditions, viewParameters)
htmlTable:node(dataRow):newline()
return dataRow
else
local dataRow = mw.html.create("tr")
dataRow:newline()
addAllCellsThatShouldBeShowing(dataRow, cellsInOrderWithoutConditions, viewParameters)
htmlTable:node(dataRow):newline()
return dataRow
end
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 booleanData == true or booleanData == "true" 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 numberData < 0 then
return nil
else
return numberData
end
end
end
---createPageLink
---@param name string the name of the forest mystery
---@param iconFilename string the filename, excluding the extension
---@return string html markup representing the link to the page, with its icon
local function createMysteryPageLink(name, iconFilename)
if not iconFilename or iconFilename == "" then
iconFilename = REPLACEMENT_FILENAME
else
iconFilename = iconFilename .. ".png"
end
local iconPart = string.format("[[File:%s|%s|link=%s|alt=%s|%s]] ", iconFilename, MYSTERY_LINK_ICON_SIZE, name, name, name)
local pagePart = "[[" .. name .. "]]"
return mw.getCurrentFrame():preprocess(iconPart .. pagePart)
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 MysteriesView.startTable(caption, viewParameters)
openTable(caption)
addHeaderRow(viewParameters)
return htmlTable
end
---addRow
---@param id string the ID of the mystery
---@param name string the name of the mystery
---@param icon string the icon filename of the mystery, excluding the extension
---@param description string the description of the mystery
---@param severity string the severity of the mystery, for example, "Beneficial" or "Harsh"
---@param hostilityLevel number the hostility when the mystery kicks in
---@param season number the season when the mystery kicks in every year
---@param isConditionHousing boolean whether the mystery requires housing need to be satisfied to avoid
---@param isConditionFood boolean whether the mystery requires complex food need to be satisfied to avoid
---@param isConditionServices boolean whether the mystery requires services need to be satisfied to avoid
function MysteriesView.addRow(id, name, icon, description, severity, hostilityLevel, season, isConditionHousing, isConditionFood, isConditionServices, viewParameters)
local data = {
[HEADER_ID] = validateStringData(id),
[HEADER_NAME] = createMysteryPageLink(name, icon),
[HEADER_DESCRIPTION] = validateStringData(description),
[HEADER_SEVERITY] = validateStringData(severity),
[HEADER_HOSTILITY_LEVEL] = validateNumberData(hostilityLevel),
[HEADER_SEASON] = validateNumberData(season),
[HEADER_ABBR_HOUSING] = validateBooleanData(isConditionHousing),
[HEADER_ABBR_FOOD] = validateBooleanData(isConditionFood),
[HEADER_ABBR_SERVICES] = validateBooleanData(isConditionServices)
}
addDataRow(data, viewParameters)
return htmlTable
end
---finalize
---@return table the complete html table node, fully rendered
function MysteriesView.finalize()
return htmlTable
end
--endregion
return MysteriesView


