Skip to content

Instantly share code, notes, and snippets.

@dorianmariecom
Last active February 12, 2020 06:50
Show Gist options
  • Save dorianmariecom/6edcff53f1b28525d78b999ce6c9e516 to your computer and use it in GitHub Desktop.
Save dorianmariecom/6edcff53f1b28525d78b999ce6c9e516 to your computer and use it in GitHub Desktop.
function createThumbControl(thumbObject, thumb, minValue, maxValue, pixels, orientation) {
if (orientation !== 'horizontal' && orientation !== 'vertical') {
throw new Error('Orientation must be either \'horizontal\' or \'vertical\'');
}
if (isNaN(thumb.stepSize)) {
throw new Error('Step size must be a number.');
}
if (isNaN(thumb.initialValue)) {
throw new Error('initial value must be a number.');
}
if (thumb.initialValue < minValue || thumb.initialValue > maxValue) {
throw new Error('initial value must be between min and max, inclusive.');
}
adjustAppearance(thumb.initialValue);
var valueText = thumb.labelFromValue(thumb.initialValue);
if (valueText !== thumb.initialValue) {
thumbObject.setAttribute('aria-valuetext', valueText);
}
// output.style.position = 'absolute';
// output.style.height = '10px';
// if (orientation === 'horizontal') {
// output.style.top = '-7px';
// } else {
// output.style.left = '-2px';
// }
//
// output.style.width = '10px';
// output.style.marginLeft = '-5px';
function setValueAttributes(value) {
var valueText = thumb.labelFromValue(value);
thumbObject.setAttribute('aria-valuenow', value);
if (valueText !== value) {
thumbObject.setAttribute('aria-valuetext', valueText);
} else {
thumbObject.removeAttribute('aria-valuetext');
}
var e;
try {
e = new CustomEvent('change', { 'bubbles': true, 'cancelable': true });
} catch (error) {
// hi IE, you're great. Let's go for a walk.
e = document.createEvent('CustomEvent');
e.initCustomEvent('change', true, true, {});
}
thumbObject.dispatchEvent(e);
}
function getPercentage(val) {
var range = maxValue - minValue;
var normalizedValue = val - minValue;
var percent = normalizedValue / range;
return percent;
}
// this should round to the nearest value that is
// an integer factor of the stepSize.
// when using keyboard, you always move up/down
// by either 1 or 10 times the stepSize; when using
// a mouse, though, the dragging is more analog - so
// you might get floating point values that are
// outside of the allowable range. This fixes that.
function normalize(v) {
var range = {
low: Math.floor(v / thumb.stepSize) * thumb.stepSize,
high: Math.ceil(v / thumb.stepSize) * thumb.stepSize
};
var lowDelta = range.low - v;
var highDelta = range.high - v;
if (Math.abs(lowDelta) <= Math.abs(highDelta)) {
return range.low;
}
return range.high;
}
function adjustValue(pixelPosition) {
var range = maxValue - minValue;
var percent = pixelPosition / pixels;
var oldValue = parseFloat(thumbObject.getAttribute('aria-valuenow'));
var newValue = minValue + range * percent;
newValue = normalize(newValue);
if (newValue !== oldValue) {
setValueAttributes(newValue);
}
}
function adjustAppearance(value) {
var offset = pixels * getPercentage(value);
if (orientation === 'horizontal') {
thumbObject.style.left = offset + 'px';
} else {
thumbObject.style.bottom = offset - 7 + 'px';
}
}
function applyDelta(delta) {
var oldValue = thumbObject.getAttribute('aria-valuenow');
var newValue = parseFloat(oldValue) + parseFloat(delta);
while (newValue < minValue) {
newValue++;
}
while (newValue > maxValue) {
newValue--;
}
if (newValue !== oldValue) {
setValueAttributes(newValue);
adjustAppearance(newValue);
}
}
kb.onElementRight(thumbObject, function (e) {
e.preventDefault();
e.stopPropagation();
applyDelta(thumb.stepSize);
});
kb.onElementUp(thumbObject, function (e) {
e.preventDefault();
e.stopPropagation();
applyDelta(thumb.stepSize);
});
kb.onElementLeft(thumbObject, function (e) {
e.preventDefault();
e.stopPropagation();
applyDelta(-thumb.stepSize);
});
kb.onElementDown(thumbObject, function (e) {
e.preventDefault();
e.stopPropagation();
applyDelta(-thumb.stepSize);
});
kb.onElementPageUp(thumbObject, function (e) {
e.preventDefault();
e.stopPropagation();
applyDelta(10 * thumb.stepSize);
});
kb.onElementPageDown(thumbObject, function (e) {
e.preventDefault();
e.stopPropagation();
applyDelta(-10 * thumb.stepSize);
});
thumbObject.addEventListener('mousedown', function () {
startDrag();
});
function startDrag() {
thumbObject.classList.add('dragging');
document.body.addEventListener('mousemove', onDrag);
document.body.addEventListener('mouseup', stopDrag);
}
function getPoint(e) {
var x = e.clientX - thumbObject.parentElement.getBoundingClientRect().left;
var y = e.clientY - thumbObject.parentElement.getBoundingClientRect().top;
return { x: x, y: y };
}
function onDrag(e) {
var _getPoint = getPoint(e),
x = _getPoint.x,
y = _getPoint.y;
var targetValue = orientation === 'horizontal' ? x - 10 : pixels - y;
while (targetValue < 0) {
targetValue++;
}
while (targetValue > pixels) {
targetValue--;
}
if (orientation === 'horizontal') {
thumbObject.style.left = targetValue + 'px';
} else {
thumbObject.style.bottom = targetValue - 7 + 'px';
}
adjustValue(targetValue);
}
function stopDrag() {
thumbObject.classList.remove('dragging');
document.body.removeEventListener('mousemove', onDrag);
document.body.removeEventListener('mouseup', stopDrag);
var finalValue = thumbObject.getAttribute('aria-valuenow');
adjustAppearance(parseFloat(finalValue));
}
adjustAppearance(thumb.initialValue);
thumbObject.setValue = function (v) {
var value = normalize(v);
setValueAttributes(value);
adjustAppearance(value);
};
}
.deque-slider-multirange label {
display: inline-block;
margin: 10px;
padding: 10px;
font-size: 13px;
font-family: 'Open Sans', sans-serif;
}
.deque-slider-multirange label input {
margin: 0 0 0 10px;
padding: 5px;
}
.deque-slider-multirange label input:focus {
outline: 1px dashed #000000;
}
.deque-slider-multirange .slider {
position: relative;
height: 4px;
background: rgba(0, 0, 0, 0.4);
margin-top: 12px;
}
.deque-slider-multirange .slider button {
height: 24px;
width: 8px;
background: #0078d7;
padding: 0;
border-radius: 4px;
position: absolute;
top: -12px;
outline: none;
max-width: auto;
min-width: auto;
border: 1px solid transparent;
}
.deque-slider-multirange .slider button:focus,
.deque-slider-multirange .slider button:active {
outline: 1px dashed #000000;
}
<div class="deque-slider-multirange horizontal">
<h4 class="neutral" >Search for house by price</h4>
<span id="label"></span>
<div>
<label>
min
<input>
</label>
<div class="slider" style="height: 2px; width: 200px;">
<button class="minPrice"
role="slider"
aria-valuemin="150000"
aria-valuemax="450000"
aria-orientation="horizontal"
aria-label="Min Price"
aria-valuenow="220000"
data-increment="10000">
</button>
<button class="maxPrice"
role="slider"
aria-valuemin="150000"
aria-valuemax="450000"
aria-orientation="horizontal"
aria-label="Max Price"
aria-valuenow="360000"
data-increment="10000">
</button>
</div>
<label>
max
<input>
</label>
</div>
<div id="alertRegion"></div>
</div>
function createMultirange(slider, thumbs, minValue, maxValue, orientation) {
var reverse = false;
if (reverse) {
thumbs.forEach(function (t) {
return t.stepSize *= -1;
});
}
var pixels = 200;
if (orientation === null) {
orientation = 'horizontal';
}
if (isNaN(minValue) || isNaN(maxValue)) {
throw new Error('min, max, initial values must all be numbers. StepSize must be a number.');
}
if (orientation !== 'horizontal' && orientation !== 'vertical') {
throw new Error('orientation must be either "horizontal" or "vertical", or blank (defaults to horizontal)');
}
if (orientation === 'vertical') {
var shouldSetOrient = function shouldSetOrient() {
// eslint-disable-line no-inner-declarations
// Internet Explorer 6-11
var isIE = false || !!document.documentMode; //@cc_on!@
// Edge 20+
var isEdge = !isIE && !!window.StyleMedia;
// Firefox 1.0+
var isFirefox = typeof InstallTrigger !== 'undefined';
return isIE || isEdge || isFirefox;
};
if (shouldSetOrient() === true) {
slider.setAttribute('orient', 'vertical');
}
}
// the rest of this code is only relevant if there is
// more than one thumb.
//slider.style.height = orientation === 'horizontal' ? '2px' : pixels + 'px';
//slider.style.width = orientation === 'horizontal' ? pixels + 'px' : '2px';
var inputs = slider.querySelectorAll('input');
var minValueInput = inputs[0];
var maxValueInput = inputs[1];
var thumbObjects = slider.querySelectorAll('button');
thumbObjects = Array.prototype.slice.call(thumbObjects);
for (var i = 0; i < thumbs.length; i++) {
(0, _thumb.createThumbControl)(thumbObjects[i], thumbs[i], minValue, maxValue, pixels, orientation);
}
thumbObjects.forEach(function (t, i) {
if (i === 0) {
bindInputToThumb(minValueInput, t, minValue, maxValue);
} else if (i === 1) {
bindInputToThumb(maxValueInput, t, minValue, maxValue);
}
});
}
function bindInputToThumb(input, thumb, min, max) {
input.addEventListener('blur', updateThumb);
(0, _keyboardUtils.onElementEnter)(input, function (e) {
e.preventDefault();
e.stopPropagation();
updateThumb();
});
function updateThumb() {
var val = input.value;
if (thumb.textParser) {
val = thumb.textParser(val);
}
val = parseFloat(val);
if (!isNaN(val) && val <= max && val >= min && val != thumb.getAttribute('aria-valuenow')) {
thumb.setValue(val);
}
}
thumb.addEventListener('change', updateTextInput);
function updateTextInput() {
if (thumb.textParser) {
input.value = thumb.getAttribute('aria-valuetext');
} else {
input.value = thumb.getAttribute('aria-valuenow');
}
}
updateTextInput();
}
// In the @slider section:
function createSlider(slider, output, initialContent) {
var minValue = slider.getAttribute('min');
var initialValue = initialContent;
var maxValue = slider.getAttribute('max');
var stepSize = slider.getAttribute('step');
var orientation = slider.getAttribute('aria-orientation');
if (orientation === null) {
orientation = 'horizontal';
}
if (isNaN(minValue) || isNaN(maxValue) || isNaN(initialValue) || isNaN(stepSize)) {
throw new Error('min, max, initial values must all be numbers. StepSize must be a number.');
}
if (orientation !== 'horizontal' && orientation !== 'vertical') {
throw new Error('orientation must be either "horizontal" or "vertical", or blank (defaults to horizontal)');
}
if (orientation === 'vertical') {
var shouldSetOrient = function shouldSetOrient() {
// eslint-disable-line no-inner-declarations
// Internet Explorer 6-11
var isIE = /*@cc_on!@*/false || !!document.documentMode;
// Edge 20+
var isEdge = !isIE && !!window.StyleMedia;
// Firefox 1.0+
var isFirefox = typeof InstallTrigger !== 'undefined';
return isIE || isEdge || isFirefox;
};
if (shouldSetOrient() === true) {
slider.setAttribute('orient', 'vertical');
}
}
if (output) {
output.innerText = slider.value;
var triggerEventOutput = function triggerEventOutput() {
slider.setAttribute('aria-valuetext', slider.value);
output.innerText = slider.value;
};
slider.addEventListener('change', triggerEventOutput, false);
slider.addEventListener('input', triggerEventOutput, false);
}
slider.setAttribute('aria-valuetext', slider.value);
}
function activateAllSliders() {
var sliders = document.querySelectorAll('.deque-slider');
for (var i = 0; i < sliders.length; i++) {
var slider = sliders[i].querySelector('.deque-slider-widget');
var output = sliders[i].querySelector('span');
var initialContent = output.innerText;
createSlider(slider, output, initialContent);
}
}
activateAllSliders();
// In the @keyboardUtils section:
var KEYS = exports.KEYS = {
BACKSPACE: 8,
TAB: 9,
ENTER: 13,
SHIFT: 16,
CTRL: 17,
ALT: 18,
ESCAPE: 27,
SPACE: 32,
LEFT: 37,
RIGHT: 39,
UP: 38,
DOWN: 40,
F10: 121,
HOME: 36,
END: 35,
PAGE_UP: 33,
PAGE_DOWN: 34
};
function bindElementToEventValue(element, eventName, testValue, handler) {
function localHandler(e) {
if (e.which === testValue) {
handler(e);
}
}
element.addEventListener(eventName, localHandler);
return function () {
element.removeEventListener(eventName, localHandler);
};
}
function bindElementToKeypressValue(element, testValue, handler) {
return bindElementToEventValue(element, 'keypress', testValue, handler);
}
function bindElementToKeydownValue(element, testValue, handler) {
return bindElementToEventValue(element, 'keydown', testValue, handler);
}
function onElementEnter(element, handler) {
return bindElementToKeypressValue(element, KEYS.ENTER, handler);
}
function onElementEscape(element, handler) {
return bindElementToKeydownValue(element, KEYS.ESCAPE, handler);
}
function onElementSpace(element, handler) {
return bindElementToKeypressValue(element, KEYS.SPACE, handler);
}
function onElementLeft(element, handler) {
return bindElementToKeydownValue(element, KEYS.LEFT, handler);
}
function onElementRight(element, handler) {
return bindElementToKeydownValue(element, KEYS.RIGHT, handler);
}
function onElementUp(element, handler) {
return bindElementToKeydownValue(element, KEYS.UP, handler);
}
function onElementDown(element, handler) {
return bindElementToKeydownValue(element, KEYS.DOWN, handler);
}
function onElementHome(element, handler) {
return bindElementToKeydownValue(element, KEYS.HOME, handler);
}
function onElementEnd(element, handler) {
return bindElementToKeydownValue(element, KEYS.END, handler);
}
function onElementPageUp(element, handler) {
return bindElementToKeydownValue(element, KEYS.PAGE_UP, handler);
}
function onElementPageDown(element, handler) {
return bindElementToKeydownValue(element, KEYS.PAGE_DOWN, handler);
}
function onElementF10(element, handler) {
return bindElementToKeydownValue(element, KEYS.F10, handler);
}
function isAlphaNumeric(charCode) {
return charCode >= 48 && charCode <= 90 /* numbers, uppercase letters */
|| charCode >= 97 && charCode <= 122 /* lowercase letters */;
}
function onElementCharacter(element, handler) {
function localHandler(e) {
var charCode = e.which;
if (isAlphaNumeric(charCode)) {
handler(e);
}
}
element.addEventListener('keypress', localHandler);
return function () {
element.removeEventListener('keypress', localHandler);
};
}
function trapEnter(element) {
onElementEnter(element, function (e) {
e.stopPropagation();
e.preventDefault();
});
}
// Required: Initialization JavaScript (with functionality specific to individual pattern instances):
var formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
});
function formatMoney(v) {
return formatter.format(v);
}
function deformatMoney(v) {
return parseFloat(v.replace(/[^0-9-.]/g, ''));
}
function removeDollarSign(v) {
return v.replace(/(\.|\,|\$)+/g, '');
}
function showMessage(message, classes) {
var alert = deque.createAlert(message, classes);
alertRegion.appendChild(alert);
createAriaLiveContainer(message);
}
function createAriaLiveContainer(message) {
var liveregion_assertive = document.querySelector('#liveregion_assertive');
if(!liveregion_assertive) {
var parentDequeSliderContainer = document.querySelector('.deque-slider-multirange');
var liveRegionElement = document.createElement('div');
liveRegionElement.id = 'liveregion_assertive';
liveRegionElement.setAttribute('role', 'alert');
liveRegionElement.setAttribute('aria-live', 'assertive');
liveRegionElement.setAttribute('aria-atomic', 'true');
liveRegionElement.style.height = '0px';
liveRegionElement.style.overflow = 'hidden';
parentDequeSliderContainer.appendChild(liveRegionElement);
}
document.getElementById('liveregion_assertive').innerText = message;
}
var multirangeSlider = document.querySelector(".deque-slider-multirange");
var startThumb = multirangeSlider.querySelector('.minPrice');
var stopThumb = multirangeSlider.querySelector('.maxPrice');
var thumbs = [{
label: startThumb.getAttribute('aria-label'),
labelFromValue: formatMoney,
stepSize: startThumb.getAttribute('data-increment'),
initialValue: startThumb.getAttribute('aria-valuenow'),
classes: [],
textParser: deformatMoney
},{
label: stopThumb.getAttribute('aria-label'),
labelFromValue: formatMoney,
stepSize: stopThumb.getAttribute('data-increment'),
initialValue: stopThumb.getAttribute('aria-valuenow'),
classes: [],
textParser: deformatMoney
}]
var minValue = startThumb.getAttribute('aria-valuemin');
minValue = parseInt(minValue);
var maxValue = startThumb.getAttribute('aria-valuemax');
maxValue = parseInt(maxValue);
var orientation = startThumb.getAttribute('aria-orientation');
deque.createMultirange(multirangeSlider, thumbs, minValue, maxValue, orientation);
var alertRegion = multirangeSlider.querySelector('#alertRegion');
var startThumb = multirangeSlider.querySelector('.minPrice');
var stopThumb = multirangeSlider.querySelector('.maxPrice');
var multirangeLabel = multirangeSlider.querySelector('#label');
var startInput = multirangeSlider.querySelector('input:first-child');
var stopInput = multirangeSlider.querySelector('label:nth-of-type(2) input');
startInput.addEventListener('blur', validateInputs);
startInput.addEventListener('keydown', onEnter);
stopInput.addEventListener('blur', validateInputs);
stopInput.addEventListener('keydown', onEnter);
startInput.addEventListener('keyup', triggerEmptyMessage);
stopInput.addEventListener('keyup', triggerEmptyMessage);
function triggerEmptyMessage(e) {
if(e.currentTarget.value.length != 0) {
createAriaLiveContainer('');
}
}
startThumb.addEventListener('click', function(e) {
e.currentTarget.focus();
});
stopThumb.addEventListener('click', function(e) {
e.currentTarget.focus();
});
function onEnter(e) {
if(e.which === 13) {
validateInputs(e);
}
}
function validateInputs(e) {
alertRegion.innerHTML = '';
if(!e.currentTarget.value) {
e.currentTarget.focus();
e.target.classList.add('invalid');
showMessage('Value can not be empty', ['error']);
e.target.setAttribute('aria-invalid', true);
return false;
} else {
createAriaLiveContainer('');
e.target.classList.remove('invalid');
e.target.removeAttribute('aria-invalid');
}
var newVal = deformatMoney(e.target.value);
var maxVal = startThumb.getAttribute('aria-valuemax');
var minVal = startThumb.getAttribute('aria-valuemin');
var maxValNow = stopThumb.getAttribute('aria-valuenow');
var minValNow = startThumb.getAttribute('aria-valuenow');
if (newVal > maxVal || newVal < minVal) {
e.target.classList.add('invalid');
showMessage('Value must be between $150,000 and $450,000', ['error']);
e.target.setAttribute('aria-invalid', true);
e.currentTarget.focus();
} else if (isNaN(removeDollarSign(e.target.value))) {
e.target.classList.add('invalid');
showMessage('Please enter a valid dollar amount', ['error']);
e.target.setAttribute('aria-invalid', true);
e.currentTarget.focus();
} else if (minValNow > maxValNow){
e.target.classList.add('invalid');
showMessage('The minimum value must be less than the maximum value', ['error']);
e.target.setAttribute('aria-invalid', true);
e.currentTarget.focus();
} else {
e.target.classList.remove('invalid');
e.target.removeAttribute('aria-invalid');
}
}
multirangeSlider.addEventListener('change', setMultirangeSliderLabel);
multirangeSlider.addEventListener('change', validateSlider);
function validateSlider(e){
alertRegion.innerHTML = '';
var maxValNow = stopThumb.getAttribute('aria-valuenow');
var minValNow = startThumb.getAttribute('aria-valuenow');
if (minValNow > maxValNow){
e.target.classList.add('invalid');
showMessage('The minimum value must be less than the maximum value', ['error']);
e.target.setAttribute('aria-invalid', true);
} else {
createAriaLiveContainer('');
e.target.classList.remove('invalid');
e.target.removeAttribute('aria-invalid');
}
e.target.focus();
}
function setMultirangeSliderLabel() {
var label = 'Between ' + startThumb.getAttribute('aria-valuetext');
label += ' and ' + stopThumb.getAttribute('aria-valuetext');
multirangeLabel.innerText = label;
}
setMultirangeSliderLabel();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment