Module:Calculator
Appearance
local Calculator = {}
Calculator.__index__ = Calculator
function Calculator:new(params)
params = params or {}
local root = params.root or mw.html.create(getElement(params))
if params.scoped ~= false then
root:addClass('calculator-container')
:attr('data-calculator-refresh-on-load', boolString(params.refreshOnLoad))
end
root:addClass(params.class)
:attr('style', params.style)
local obj = {
root = root,
noJSFallback = '',
templatestyles = {}
}
if params.noJSFallback then
-- If fallback exists, wrap with calculatorgadget-enabled class
root:addClass('calculatorgadget-enabled')
:css('display', 'none')
-- No need to create html node for fallback if it's an empty string
if params.noJSFallback ~= '' then
obj.noJSFallback = tostring(
mw.html.create(getElement(params))
:addClass('calculatorgadget-fallback')
:wikitext(params.noJSFallback)
)
end
end
self.__index = self
return setmetatable(obj, Calculator)
end
function Calculator:field(params)
return self.root:tag('span')
:addClass('calculator-field')
:addClass(params.class)
:attr('id', params.id and ('calculator-field-' .. params.id) or nil)
:attr('data-calculator-type', params.type or 'number')
:attr('style', params.style)
:css('display', params.type == 'hidden' and 'none' or nil)
:attr('data-calculator-formula', params.formula)
:attr('data-calculator-readonly', boolString(params.readonly))
:attr('data-calculator-size', params.size)
:attr('data-calculator-max', params.max)
:attr('data-calculator-min', params.min)
:attr('data-calculator-placeholder', params.placeholder)
:attr('data-calculator-name', params.name)
:attr('data-calculator-precision', params.precision)
:attr('data-calculator-exponential-precision', params['exponential-precision'])
:attr('data-calculator-decimals', params.decimals)
:attr('data-calculator-nan-text', params['NaN-text'])
:attr('data-calculator-class', params['class-live'])
:attr('aria-describedby', params['aria-describedby'])
:attr('aria-labelledby', params['aria-labelledby'])
:attr('aria-label', params['aria-label'])
:attr('data-calculator-enterkeyhint', params.enterkeyhint)
:attr('data-calculator-mapping', params.mapping)
:attr('data-calculator-aria-role', params.role)
:attr('data-calculator-aria-atomic', params['aria-atomic'])
:attr('data-calculator-aria-relevant', params['aria-relevant'])
:attr('data-calculator-aria-live', params['aria-live'])
:attr('data-calculator-checked', boolString(params.checked))
:attr('data-calculator-value', params.value)
:attr('data-calculator-inputmode', params.inputmode or
(params.type == 'text' and params.mapping == nil and 'numeric' or nil))
:attr('data-calculator-step', params.step)
:wikitext(params.default or '')
end
function Calculator:button(params)
local button = self.root:tag('span')
:addClass('calculator-field-button')
:addClass(params.class)
:attr('id', params.id)
:attr('title', params.title)
:attr('style', params.style)
:attr('data-calculator-alt', params.alt)
:attr('data-calculator-disabled', boolString(params.disabled))
:attr('data-calculator-for', params['for'])
:attr('data-calculator-formula', params.formula)
:attr('role', params.role)
:attr('data-calculator-aria-live', params['aria-live'])
:attr('data-calculator-delay', params.delay)
:attr('data-calculator-max-iterations', params['max iterations'])
:attr('data-calculator-toggle', params.toggle)
:attr('data-calculator-class', params['class-live'])
:wikitext(params.contents or params.text or params[1] or 'Click me')
local type = params.type or 'plain'
if type ~= 'plain' then
button:addClass('cdx-button')
button:addClass('cdx-button--action-'..type)
button:addClass('cdx-button--weight-'..(params.weight or 'normal'))
button:addClass('cdx-button--size-'..(params.size or 'medium'))
end
return button
end
function Calculator:hidden(params)
params.type = 'hidden'
return self:field(params)
end
function Calculator:plain(params)
params.type = 'plain'
return self:field(params)
end
function Calculator:text(params)
params.type = 'text'
return self:field(params)
end
function Calculator:radio(params)
params.type = 'radio'
return self:field(params)
end
function Calculator:checkbox(params)
params.type = 'checkbox'
return self:field(params)
end
function Calculator:codexText(params)
params.type = 'text'
params['class-live'] = 'cdx-text-input__input ' .. (params['class-live'] or '')
return self:field(params)
end
function Calculator:codexRadio(params)
params.type = 'radio'
params['class-live'] = 'cdx-radio__input ' .. (params['class-live'] or '')
local outer = self:subContainer({
class = 'cdx-radio '
.. (params.inline and 'cdx-radio--inline' or '')
.. (params['codex-div-class'] or '')
})
local inner = outer:subContainer({
class = 'cdx-radio__wrapper'
})
inner:field(params)
inner:tag('span'):addClass('cdx-radio__icon')
inner:codexLabel({
['codex-class'] = 'cdx-radio__label',
['for'] = params.id,
description = params.description,
label = params.label
})
if params.custominput then
outer:tag('div')
:addClass('cdx-radio__custom-input')
:wikitext(params.custominput)
end
return outer
end
function Calculator:codexCheckbox(params)
params.type = 'checkbox'
params['class-live'] = 'cdx-checkbox__input ' .. (params['class-live'] or '')
local outer = self:subContainer({
class = 'cdx-checkbox '
.. (params.inline and 'cdx-checkbox--inline' or '')
.. (params['codex-div-class'] or '')
})
local inner = outer:subContainer({
class = 'cdx-checkbox__wrapper'
})
inner:field(params)
inner:tag('span'):addClass('cdx-checkbox__icon')
inner:codexLabel({
['codex-class'] = 'cdx-checkbox__label',
['for'] = params.id,
description = params.description,
label = params.label
})
if params.custominput then
outer:tag('div')
:addClass('cdx-checkbox__custom-input')
:wikitext(params.custominput)
end
return outer
end
function Calculator:codexToggle(params)
params.type = 'checkbox'
params['class-live'] = 'cdx-toggle-switch__input ' .. (params['codex-live'] or '')
params.default = '<span style="display:none">' .. (params.default or '') .. '</span>'
local container = self:subContainer({
element = 'span',
class = 'cdx-toggle-switch'
})
container:field(params)
container:tag('span'):addClass('cdx-toggle-switch__switch')
:tag('span'):addClass('cdx-toggle-switch__switch__grip')
container:codexLabel({
['codex-class'] = 'cdx-toggle-switch__label '
.. (params.hiddendescription and 'cdx-label--visually-hidden' or ''),
['for'] = params.id,
description = params.description,
label = params.label,
style = params.labelstyle
})
return container
end
function Calculator:passthru(params)
params.type = 'passthru'
return self:field(params)
end
function Calculator:label(params)
if params.codex then
return self:codexLabel(params)
end
return self.root:tag('span')
:addClass('calculator-field-label')
:addClass(params.class)
:attr('data-for', params['for'] and ('calculator-field-'.. params['for']) or nil)
:attr('data-calculator-class', params['class-live'])
:attr('title', params.title)
:attr('id', params.id)
:attr('style', params.style)
:wikitext(params.label or params[1])
end
function Calculator:codexLabel(params)
local div = self.root:tag('div')
:addClass('cdx-label')
:addClass(params['codex-class'])
local outerSpan = div:tag('span')
:addClass('calculator-field-label cdx-label__label')
:addClass(params.class)
:attr('data-for', params['for'] and ('calculator-field-' .. params['for']) or nil)
:attr('data-calculator-class', params['class-live'])
:attr('title', params.title)
:attr('id', params.id)
:attr('style', params.style)
outerSpan:tag('span')
:addClass('cdx-label__label__text')
:wikitext(params.label or params[1])
if params.flag then
outerSpan:tag('span')
:addClass('cdx-label__label__optional-flag')
:wikitext(' ' .. params.flag)
end
if params.description then
-- Note: this doesn't yet set proper aria-describedby on the input widget
div:tag('span')
:addClass('cdx-label__description')
:attr('id', params['description-id'])
:wikitext(params.description)
end
return div
end
function Calculator:hideIfZero(params)
table.insert(self.templatestyles, 'Template:Calculator-hideifzero/styles.css')
return self.root:tag(getElement(params))
:addClass('calculator-field calculator-hideifzero')
:addClass(params.starthidden and 'calculator-value-false' or nil)
:addClass(params.class)
:attr('data-calculator-type', 'passthru')
:attr('style', params.style)
:attr('data-calculator-formula', params.formula)
:attr('data-calculator-aria-live', params['aria-live'])
:attr('data-calculator-aria-atomic', params['aria-atomic'])
:attr('role', params.role)
:attr('data-calculator-class', params['class-live'])
:wikitext(params.text or params[1])
end
-- Add arbitrary wikitext within the calculator container
function Calculator:wikitext(text)
self.root:wikitext(text)
end
-- Add arbitrary html node within the calculator container.
-- Note: if you would need to add calculator fields within that
-- node, use subContainer() instead.
function Calculator:tag(tagName)
return self.root:tag(tagName)
end
-- Add a wrapper html node within the calculator container,
-- for instance for styling. Additional calculator fields can
-- be added inside it.
function Calculator:subContainer(params)
params = params or {}
local subRoot = self.root:tag(getElement(params))
return Calculator:new({
root = subRoot,
scoped = false,
class = params.class,
style = params.style,
noJSFallback = params.noJSFallback
})
end
function Calculator:__tostring()
local frame = mw.getCurrentFrame()
local styles = ''
for idx, page in ipairs(self.templatestyles) do
styles = styles .. frame:extensionTag('templatestyles', '', {src = page})
end
return styles
.. tostring(self.root)
.. self.noJSFallback
.. '[[Category:Pages using gadget Calculator]]'
end
function getElement(params)
return params.element or (params.block == false and 'span' or 'div')
end
function boolString(value)
return value and tostring(value) or nil
end
return Calculator