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