en.wikipedia.org

Module:Calculator - Wikipedia

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