No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
<noinclude>__NOTOC__ | <noinclude>__NOTOC__ | ||
This widget converts with | This widget converts between two units with a single slider (slider always controls the *top* value). | ||
=Usage= | =Usage= | ||
{{#widget:ConvertCalc2 | {{#widget:ConvertCalc2 | ||
| TopLabel=years | | TopLabel=years | ||
| TopFactor=1.3125 | | TopFactor=1.3125 | ||
| Line 12: | Line 11: | ||
| SliderMax=100 | | SliderMax=100 | ||
| SliderStep=0.1 | | SliderStep=0.1 | ||
}} | }} | ||
{| class="wikitable" | {| class="wikitable" | ||
! param !! meaning !! default | ! param !! meaning !! default | ||
|- | |- | ||
| TopLabel || label for the top unit || years | | TopLabel || label for the top unit || years | ||
|- | |- | ||
| TopFactor || | | TopFactor || factor for the top unit (relative to a base) || 1.3125 | ||
|- | |- | ||
| BottomLabel || label for the bottom unit || Cycles | | BottomLabel || label for the bottom unit || Cycles | ||
|- | |- | ||
| BottomFactor || | | BottomFactor || factor for the bottom unit (relative to the same base) || 1 | ||
|- | |- | ||
| SliderMin | | SliderMin || slider minimum (top value) || 1 | ||
|- | |- | ||
| | | SliderMax || slider maximum (top value) || 100 | ||
|- | |- | ||
| | | SliderStep || slider step || 0.1 | ||
|} | |} | ||
</noinclude><includeonly> | </noinclude><includeonly> | ||
<div class="wh- | <div class="wh-cc2" style="max-width:720px;margin:1rem 0;"> | ||
<!-- Top label --> | <!-- Top label (capitalized) --> | ||
<div style="font-weight:700; | <div id="cc2-topLabel" style="font-weight:700;text-transform:capitalize;"></div> | ||
< | |||
<!-- Centered top value --> | |||
<div style="text-align:center;margin:.25rem 0 .5rem 0;"> | |||
<span id="cc2-topVal" style="font-size:1.25rem;font-weight:600;">0</span> | |||
<span id="cc2-topUnit"></span> | |||
</div> | </div> | ||
<!-- | <!-- Slider (controls top value) --> | ||
< | <input id="cc2-slider" type="range" min="<!--{$SliderMin|default:1|escape:'html'}-->" | ||
max="<!--{$SliderMax|default:100|escape:'html'}-->" | |||
step="<!--{$SliderStep|default:0.1|escape:'html'}-->" | |||
value="<!--{$SliderMin|default:1|escape:'html'}-->" | |||
style="width:100%;"> | |||
<!-- | <!-- Inputs row --> | ||
<div style="display:flex;align-items:center; | <div style="display:flex;gap:.75rem;align-items:center;margin:.5rem 0;"> | ||
< | <label style="display:flex;gap:.35rem;align-items:center;flex:1;"> | ||
<span id="cc2-topLabelInline"></span> | |||
<input id="cc2-topInput" type="number" inputmode="decimal" style="width:100%;padding:.25rem;"> | |||
</label> | |||
<button id="cc2-swap" type="button" style="padding:.35rem .6rem;cursor:pointer;">⇅ swap</button> | |||
<label style="display:flex;gap:.35rem;align-items:center;flex:1;"> | |||
<span id="cc2-bottomLabelInline"></span> | |||
<input id="cc2-bottomInput" type="number" inputmode="decimal" style="width:100%;padding:.25rem;"> | |||
</label> | |||
</div> | </div> | ||
<!-- | <!-- Centered bottom value --> | ||
<div style="text-align:center;margin:. | <div style="text-align:center;margin:.25rem 0 .5rem 0;"> | ||
< | <span id="cc2-bottomVal" style="font-size:1.25rem;font-weight:600;">0</span> | ||
<span id="cc2-bottomUnit"></span> | |||
<span id=" | |||
</div> | </div> | ||
<!-- Bottom label --> | <!-- Bottom label (capitalized) --> | ||
<div style="font-weight:700; | <div id="cc2-bottomLabel" style="font-weight:700;text-transform:capitalize;"></div> | ||
</div> | </div> | ||
<script type="text/javascript"> | <script type="text/javascript"> | ||
(function(){ | (function(){ | ||
var | // pull params | ||
function ge( | var TOP_LABEL = "<!--{$TopLabel|default:'years'|escape:'js'}-->"; | ||
var TOP_FACTOR = parseFloat("<!--{$TopFactor|default:1.3125|escape:'js'}-->") || 1.3125; | |||
var BOT_LABEL = "<!--{$BottomLabel|default:'Cycles'|escape:'js'}-->"; | |||
var BOT_FACTOR = parseFloat("<!--{$BottomFactor|default:1|escape:'js'}-->") || 1; | |||
var SL_MIN = parseFloat("<!--{$SliderMin|default:1|escape:'js'}-->") || 1; | |||
var SL_MAX = parseFloat("<!--{$SliderMax|default:100|escape:'js'}-->") || 100; | |||
var SL_STEP = parseFloat("<!--{$SliderStep|default:0.1|escape:'js'}-->") || 0.1; | |||
// generate a unique suffix so multiple widgets can coexist without an id param | |||
var SUF = Math.random().toString(36).slice(2,7); | |||
function reid(id){ return id.replace('cc2-', 'cc2-' + SUF + '-'); } | |||
function ge(id){ return document.getElementById(reid(id)); } | |||
// | // re-id all elements | ||
document.querySelectorAll('.wh-cc2 [id^="cc2-"]').forEach(function(n){ | |||
n.id = reid('#'+n.id).slice(1); | |||
}); | |||
var el = { | var el = { | ||
topLabel: ge('cc2-topLabel'), | |||
topUnit: ge('cc2-topUnit'), | |||
topVal: ge('cc2-topVal'), | |||
slider: ge('cc2-slider'), | |||
topInput: ge('cc2-topInput'), | |||
topInline: ge('cc2-topLabelInline'), | |||
swap: ge('cc2-swap'), | |||
bottomUnit: ge('cc2-bottomUnit'), | |||
bottomVal: ge('cc2-bottomVal'), | |||
bottomInput: ge('cc2-bottomInput'), | |||
bottomInline: ge('cc2-bottomLabelInline'), | |||
bottomLabel: ge('cc2-bottomLabel') | |||
}; | |||
// current state (which side is "top") | |||
var state = { | |||
topLabel: TOP_LABEL, | |||
topFactor: TOP_FACTOR, | |||
botLabel: BOT_LABEL, | |||
botFactor: BOT_FACTOR | |||
}; | }; | ||
// | function effRatio(){ // bottom per 1 top (slider drives top) | ||
function | return state.botFactor / state.topFactor; | ||
function | } | ||
function fmt(n){ | |||
// nice formatting: up to 4 decimal places, strip trailing zeros | |||
var s = (Math.abs(n) < 1e-4) ? "0" : n.toFixed(4); | |||
s = s.replace(/\.?0+$/,''); | |||
return s; | |||
} | |||
function refreshLabels(){ | |||
el.topLabel.textContent = state.topLabel; | |||
el.bottomLabel.textContent = state.botLabel; | |||
el.topUnit.textContent = ' ' + state.topLabel; | |||
el.topInline.textContent = state.topLabel + ':'; | |||
el. | el.bottomUnit.textContent = ' ' + state.botLabel; | ||
el. | el.bottomInline.textContent = state.botLabel + ':'; | ||
el. | |||
} | } | ||
// | function updateFromTop(val){ | ||
// clamp to slider range | |||
var | var v = Math.min(SL_MAX, Math.max(SL_MIN, val)); | ||
var r = effRatio(); | |||
var bottom = v * r; | |||
el.slider.value = v; | |||
el.topInput.value = v; | |||
el.topVal.textContent = fmt(v); | |||
el.bottomInput.value = bottom; | |||
el.bottomVal.textContent = fmt(bottom); | |||
} | } | ||
function | function updateFromBottom(bottomVal){ | ||
var | var r = effRatio(); | ||
var top = bottomVal / r; | |||
updateFromTop(top); | |||
} | } | ||
// | function init(){ | ||
// set slider bounds | |||
el.slider.min = SL_MIN; el.slider.max = SL_MAX; el.slider.step = SL_STEP; el.slider.value = SL_MIN; | |||
refreshLabels(); | |||
updateFromTop(SL_MIN); | |||
// events | |||
el.slider.addEventListener('input', function(){ | |||
updateFromTop(parseFloat(el.slider.value || SL_MIN)); | |||
}); | |||
el.topInput.addEventListener('input', function(){ | |||
var v = parseFloat(el.topInput.value); | |||
if (isNaN(v)) return; | |||
updateFromTop(v); | |||
}); | |||
el.bottomInput.addEventListener('input', function(){ | |||
var v = parseFloat(el.bottomInput.value); | |||
if (isNaN(v)) return; | |||
updateFromBottom(v); | |||
}); | |||
// swap factors | el.swap.addEventListener('click', function(){ | ||
// swap labels & factors; keep current numerical *top* value as top | |||
var tl = state.topLabel, tf = state.topFactor; | |||
state.topLabel = state.botLabel; state.topFactor = state.botFactor; | |||
state.botLabel = tl; state.botFactor = tf; | |||
// after swap, slider still represents top; recompute bottom with new ratio | |||
refreshLabels(); | |||
updateFromTop(parseFloat(el.topInput.value || el.slider.value || SL_MIN)); | |||
}); | |||
} | |||
if (document.readyState === 'loading'){ | |||
document.addEventListener('DOMContentLoaded', init, {once:true}); | |||
} else { init(); } | |||
})(); | })(); | ||
</script> | </script> | ||
</includeonly> | </includeonly> | ||
Revision as of 23:17, 11 November 2025
This widget converts between two units with a single slider (slider always controls the *top* value).
Usage
years
years
Cycles
Cycles
| param | meaning | default |
|---|---|---|
| TopLabel | label for the top unit | years |
| TopFactor | factor for the top unit (relative to a base) | 1.3125 |
| BottomLabel | label for the bottom unit | Cycles |
| BottomFactor | factor for the bottom unit (relative to the same base) | 1 |
| SliderMin | slider minimum (top value) | 1 |
| SliderMax | slider maximum (top value) | 100 |
| SliderStep | slider step | 0.1 |