Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

Jump to content

Module:Calculator

From Wikipedia, the free encyclopedia
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