Slider (Multirange)

Slider (Multirange)

Overview

A multi-range or multi-thumb slider is built on the same concept as a simple slider except that there are two sliding controls, to allow users to set a minimum value and a maximum value. Mobile support is a problem with multi-range sliders, so it is wise to supplement the slider with editable text fields or other similar controls that give users an alternative way to specify the range.



Turn on a screen reader to experience this example in action.

Search for house by price




Initial HTML Markup

<div class="deque-slider-multirange horizontal">
  <h3 class="neutral">Search for house by price</h3>
  <span id="label"></span>
  <div>
    <label>
      min
      <input type="number" data-class="startInput" />
    </label>
    <label>
      max
      <input type="number" data-class="stopInput" />
    </label>
    <br />
    <br />
    <div class="slider" style="height: 2px; width: 200px">
      <span class="minPricelb"></span>
      <span class="maxPricelb"></span>
      <span class="colorOverlay"></span>

      <button
        class="minPrice"
        role="slider"
        aria-valuemin="150000"
        aria-valuemax="450000"
        aria-orientation="horizontal"
        aria-label="Min Price"
        aria-valuenow="220000"
        data-increment="10000"
        data-text="start"
      >
        <span class="minPriceMovingLb" role="note"></span>
      </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"
        data-text="stop"
      >
        <span class="maxPriceMovingLb" role="note"></span>
      </button>
    </div>
  </div>
  <div style="clear: both; margin-bottom: 20px"><br /></div>

  <div id="alertRegion"></div>
</div>

JavaScript

Required: The complete JavaScript file (for all patterns in the library): deque-patterns.min.js

Key parts of the JavaScript file

Note: The code below is functional only in the context of the complete JavaScript file.

In the @slider-multirange section:


      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 bindElementToKeydownValue(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 orient = startThumb.getAttribute("aria-orientation");

if (!orient) {
  orient = "horizontal";
}
deque.createMultirange(multirangeSlider, thumbs, minValue, maxValue, orient);

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);

startInput.addEventListener("blur", triggerEmptyMessage);
stopInput.addEventListener("blur", triggerEmptyMessage);


function triggerEmptyMessage(e) {
  alertRegion.innerHTML = "";
  e.currentTarget.value = parseInt(e.currentTarget.value);
  if (isNaN(e.currentTarget.value) || e.currentTarget.value == "") {
    e.currentTarget.value = 0;
    e.currentTarget.classList.add("invalid");
    showMessage("Please enter a valid dollar amount", ["error"]);
    e.currentTarget.setAttribute("aria-invalid", true);
    e.currentTarget.focus();
    return false;
  }
  updateSliderRange();

  validateInputs(e);
  return true;
  // return true;
  // if (e.currentTarget == stopInput) {
  //   if (parseInt(e.currentTarget.value) <= 0) {
  //     e.currentTarget.value = 0;
  //     e.currentTarget.classList.add("invalid");
  //     showMessage("Please enter a valid dollar amount", ["error"]);
  //     e.currentTarget.setAttribute("aria-invalid", true);
  //     e.currentTarget.focus();
  //     return false;
  //   }
  // }

  // if (parseInt(e.currentTarget.value) <= 1 || isNaN(e.currentTarget.value)) {
  //   e.currentTarget.value = 0;
  //   e.target.classList.add("invalid");
  //   showMessage("Please enter a valid dollar amount", ["error"]);
  //   e.target.setAttribute("aria-invalid", true);
  //   e.currentTarget.focus();
  //   return false;
  // } else if (e.currentTarget.value.length != 0) {
  //   e.target.classList.remove("invalid");
  //   e.target.setAttribute("aria-invalid", false);
  //   createAriaLiveContainer("");
  //   updateSliderRange();
  // }
}

startThumb.addEventListener("click", function (e) {
  e.currentTarget.focus();
});

function startThumb_getPoint(e) {
  var x =
    e.touches[0].pageX - startThumb.parentElement.getBoundingClientRect().left;
  var y =
    e.touches[0].pageY - startThumb.parentElement.getBoundingClientRect().top;

  return { x: x, y: y };
}

function stopThumb_getPoint(e) {
  var x =
    e.touches[0].pageX - stopThumb.parentElement.getBoundingClientRect().left;
  var y =
    e.touches[0].pageY - stopThumb.parentElement.getBoundingClientRect().top;

  return { x: x, y: y };
}

startThumb.addEventListener("touchmove", function (e) {
  var _getPoint = startThumb_getPoint(e);
  var x = Math.ceil(_getPoint.x);
  var y = _getPoint.y;
  // console.log(x, y);
  e.currentTarget.focus();
  if (x <= 0) {
    x = 0;
    // startInput.value = parseInt(startInput.value) + parseInt(dataIncrement);
  }
  if (x > parseInt(startThumb.parentElement.style.width)) {
    x = parseInt(startThumb.parentElement.style.width);
    // startInput.value = parseInt(startInput.value) + parseInt(dataIncrement);
  }
  multiThumbCalculation(e.currentTarget, startThumb.parentElement, x, "start");
});

stopThumb.addEventListener("touchmove", function (e) {
  var _getPoint = stopThumb_getPoint(e);
  var x = _getPoint.x;
  var y = _getPoint.y;
  // console.log(x, y);
  e.currentTarget.focus();
  if (x <= 0) {
    x = 0;
  }

  if (x > parseInt(stopThumb.parentElement.style.width)) {
    x = parseInt(stopThumb.parentElement.style.width);
  }
  e.currentTarget.style.left = x + "px";
  multiThumbCalculation(e.currentTarget, stopThumb.parentElement, x, "stop");
});

stopThumb.addEventListener("click", function (e) {
  e.currentTarget.focus();
});

startThumb.addEventListener("keyup", function (e) {
  multiThumbCalculation(
    e.currentTarget,
    startThumb.parentElement,
    parseInt(startThumb.style.left),
    "start"
  );
});

stopThumb.addEventListener("keyup", function (e) {
  multiThumbCalculation(
    e.currentTarget,
    stopThumb.parentElement,
    parseInt(stopThumb.style.left),
    "stop"
  );
});

var minPricelb = document.querySelectorAll(".minPricelb")[0];
var maxPricelb = document.querySelectorAll(".maxPricelb")[0];
var colorOverlay = document.querySelectorAll(".colorOverlay")[0];
var minPriceMovingLb = document.querySelectorAll(".minPriceMovingLb")[0];
var maxPriceMovingLb = document.querySelectorAll(".maxPriceMovingLb")[0];
var betweenText = document.querySelectorAll(
  ".deque-slider-multirange #label"
)[0];



var minSliderValue = 0;
var maxSliderValue = 0;

minSliderValue = startThumb.getAttribute("aria-valuemin");
maxSliderValue = startThumb.getAttribute("aria-valuemax");
startInput.value = minSliderValue;
stopInput.value = maxSliderValue;

function multiThumbCalculation(element, parent_element, x, position) {
  // validateInputs(element);

  // console.log(startThumb.style.left, stopThumb.style.left, position);

  if (position == "start") {
    if (
      parseInt(startThumb.style.left) >=
      parseInt(stopThumb.style.left) - 10
    ) {
      // console.log(">>>>>> 1");
      startThumb.style.left = parseInt(stopThumb.style.left) - 11 + "px";
      return false;
    }
  }

  if (position == "stop") {
    if (
      parseInt(startThumb.style.left) + 10 >=
      parseInt(stopThumb.style.left)
    ) {
      // console.log("<<<<< 2");
      stopThumb.style.left = parseInt(startThumb.style.left) + 11 + "px";
      return false;
    }
  }

  var ariaValueNow = element.getAttribute("aria-valuenow");
  var dataIncrement = element.getAttribute("data-increment");
  var ariaValueText = element.getAttribute("aria-valuetext");
  var ariaValueMax = element.getAttribute("aria-valuemax");
  var ariaValueMin = element.getAttribute("aria-valuemin");

  minPricelb.innerHTML = formatMoney(ariaValueMin);
  maxPricelb.innerHTML = formatMoney(ariaValueMax);


  // console.log(minSliderValue);
  // console.log(maxSliderValue);

  var parent_bar_width = parseInt(parent_element.style.width);
  var ariaValueDifference = parseInt(ariaValueMax) - parseInt(ariaValueMin);
  var pointValue = parseInt(ariaValueDifference / parent_bar_width);
  var thumbValue = ariaValueMin;

  if (x <= 0) {
    x = 0;
  }
  if (x >= parent_bar_width) {
    x = parent_bar_width;
  }

  if (position == "start") {
    if (x >= 0) {
      thumbValue = Math.ceil(
        parseInt(ariaValueMin) + parseFloat(pointValue * x)
      );
      minSliderValue = thumbValue;
      element.style.left = x + "px";
      // console.log("START THUMB : ", thumbValue);
    }
  } else if (position == "stop") {
    if (x <= parent_bar_width) {
      thumbValue = Math.ceil(
        parseInt(ariaValueMin) + parseFloat(pointValue * x)
      );
      maxSliderValue = thumbValue;
      element.style.left = x + "px";
      // console.log("ELEMENT STOP LEFT ", element.style.left);
      // console.log("STOP THUMB : ", thumbValue);
    }
  }

  if (position == "start") {
    var startThumbPosition = parseInt(startThumb.style.left) + 10;
    if (startThumbPosition >= parseInt(stopThumb.style.left)) {
      startThumb.style.left = parseInt(stopThumb.style.left) - 10 + "px";
      return false;
    }
  }

  if (position == "stop") {
    var stopThumbPosition = parseInt(stopThumb.style.left);
    if (stopThumbPosition <= parseInt(startThumb.style.left) + 10) {
      stopThumb.style.left = parseInt(startThumb.style.left) + 10 + "px";
      return false;
    }
  }

  updateOverlay();
  var minFormatValue = formatMoney(
    parseFloat(minSliderValue / 1000).toFixed(2)
  );
  var maxFormatVaue = formatMoney(parseFloat(maxSliderValue / 1000).toFixed(2));

  startThumb.setAttribute("aria-valuenow", minSliderValue);
  stopThumb.setAttribute("aria-valuenow", maxSliderValue);

  startThumb.setAttribute("aria-valuetext", formatMoney(minSliderValue));
  stopThumb.setAttribute("aria-valuetext", formatMoney(maxSliderValue));

  minPriceMovingLb.innerHTML = (minFormatValue + "K").replace(".00", "");
  minPriceMovingLb.setAttribute("aria-label", minSliderValue);
  maxPriceMovingLb.innerHTML = (maxFormatVaue + "K").replace(".00", "");
  maxPriceMovingLb.setAttribute("aria-label", maxSliderValue);

  betweenText.innerHTML =
    "Between " +
    formatMoney(minSliderValue) +
    " and " +
    formatMoney(maxSliderValue);

  if (parseInt(stopThumb.style.left) - parseInt(startThumb.style.left) <= 50) {
    minPriceMovingLb.style.top = "auto";
    minPriceMovingLb.style.bottom = "106%";
  } else {
    minPriceMovingLb.style.bottom = "auto";
    minPriceMovingLb.style.top = "106%";
  }

  return true;
}
multiThumbCalculation(
  startThumb,
  startThumb.parentElement,
  parseInt(startThumb.style.left),
  "start"
);
multiThumbCalculation(
  stopThumb,
  stopThumb.parentElement,
  parseInt(stopThumb.style.left),
  "stop"
);

function updateOverlay() {
  colorOverlay.style.left = startThumb.style.left;
  colorOverlay.style.width =
    parseInt(stopThumb.style.left) - parseInt(startThumb.style.left) + "px";
}

function onEnter(e) {
  if (!triggerEmptyMessage(e)) {
    e.currentTarget.value = 0;
    e.target.classList.add("invalid");
    showMessage("Please enter a valid dollar amount", ["error"]);
    e.target.setAttribute("aria-invalid", true);
    e.currentTarget.focus();
    return false;
  }

  if (e.which === 13) {
    validateInputs(e);
    updateSliderRange();
  }
}

function updateSliderRange() {
  if (startInput.value < stopInput.value || 1) {
    startThumb.setAttribute("aria-valuemin", startInput.value);
    startThumb.setAttribute("aria-valuemax", stopInput.value);
    stopThumb.setAttribute("aria-valuemin", startInput.value);
    stopThumb.setAttribute("aria-valuemax", stopInput.value);

    minSliderValue = startInput.value;
    maxSliderValue = stopInput.value;
    betweenText.innerHTML =
      "Between " +
      formatMoney(startInput.value) +
      " and " +
      formatMoney(stopInput.value);

    multiThumbCalculation(
      startThumb,
      startThumb.parentElement,
      parseInt(startThumb.style.left),
      "start"
    );

    multiThumbCalculation(
      stopThumb,
      stopThumb.parentElement,
      parseInt(stopThumb.style.left),
      "stop"
    );
  }
}

function validateInputs(e) {
  alertRegion.innerHTML = "";
  // console.log(e.currentTarget);
  if (e.currentTarget == stopInput) {
    if (
      parseInt(startInput.value) >= parseInt(stopInput.value) ||
      parseInt(stopInput.value) < 1000
    ) {
      e.currentTarget.classList.add("invalid");
      showMessage(
        "Please enter a valid dollar amount and maximum value must be greater than 1000",
        ["error"]
      );
      e.currentTarget.setAttribute("aria-invalid", true);
      e.currentTarget.focus();
      return false;
    }
  } else if (e.currentTarget == startInput) {
    if (parseInt(e.currentTarget.value) < 0 || isNaN(e.currentTarget.value)) {
      e.currentTarget.value = 0;
      e.currentTarget.classList.add("invalid");
      showMessage("Please enter a valid dollar amount", ["error"]);
      e.currentTarget.setAttribute("aria-invalid", true);
      e.currentTarget.focus();
      return false;
    }
  }

  if (startInput.value == stopInput.value) {
    stopInput.classList.add("invalid");
    showMessage("minimum and maximum values should not be same", ["error"]);
    stopInput.setAttribute("aria-invalid", true);
    stopInput.focus();
    console.log(stopInput);
    return false;
  }

  startInput.classList.remove("invalid");
  startInput.setAttribute("aria-invalid", false);
  stopInput.classList.remove("invalid");
  stopInput.setAttribute("aria-invalid", false);

  // if (!triggerEmptyMessage(e) && e.currentTarget == stopInput) {
  //   console.log("error");
  //   e.currentTarget.value = 0;
  //   e.target.classList.add("invalid");
  //   showMessage("Please enter a valid dollar amount", ["error"]);
  //   e.target.setAttribute("aria-invalid", true);
  //   e.currentTarget.focus();
  //   return false;
  // }

  // if (
  //   (startInput.value <= 0 || stopInput.value <= 1) &&
  //   e.currentTarget == stopInput
  // ) {
  //   e.target.classList.add("invalid");
  //   showMessage("Please enter a valid dollar amount", ["error"]);
  //   e.target.setAttribute("aria-invalid", true);
  //   e.currentTarget.focus();
  //   return false;
  // }

  // if (e.currentTarget == stopInput) {
  //   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 
  */
  // console.log("START INPUT :::: ", startInput.value);
  // console.log("STOP INPUT :::: ", stopInput.value);
  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 (
    parseInt(startInput.value) >= parseInt(stopInput.value) &&
    stopInput.value != ""
  ) {
    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");
    updateSliderRange();
  }
}

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;
  updateOverlay();
}

setMultirangeSliderLabel();
updateSliderRange();

CSS

Required: The complete CSS file (for all patterns in the library): deque-patterns.min.css

Key styles within the CSS file (other styles may also be necessary):


.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;
}
.deque-slider-multirange label input ::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.deque-slider-multirange label input ::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.deque-slider-multirange .slider {
  height: 4px;
  background: rgba(0, 0, 0, 0.4);
  margin-top: 12px;
  margin: auto;
  position: relative;
  margin-left: 70px;
}
.deque-slider-multirange .slider {
  height: 4px;
  background: rgba(0, 0, 0, 0.4);
  margin-top: 12px;
  margin: auto;
  position: relative;
  margin-left: 70px;
}
.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 label input ::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.deque-slider-multirange label input ::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.deque-slider-multirange .slider {
  height: 4px;
  background: rgba(0, 0, 0, 0.4);
  margin-top: 12px;
  margin: auto;
  position: relative;
  margin-left: 70px;
}
.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;
}
.minPricelb {
  position: absolute;
  right: 102%;
  top: -14px;
  font-size: 13px;
}
.maxPricelb {
  position: absolute;
  left: 105%;
  top: -14px;
  font-size: 13px;
}
.colorOverlay {
  height: 3px;
  background: #0078d7;
  position: absolute;
  margin-top: -1px;
}
.minPriceMovingLb,
.maxPriceMovingLb {
  position: absolute;
  top: 106%;
  left: -11px;
  font-size: 12px;
  font-weight: bold;
  color: #000;
}

Implementation Instructions

Step 1: Add Dependencies

Add deque-patterns.min.css in the <head> of the document.

<link rel="stylesheet" type="text/css" href="deque-patterns.min.css">

Add a script link to deque-patterns.min.js to the bottom of the page.

<script type="text/javascript" src="deque-patterns.min.js"></script>

Step 2: Add HTML

  • Create a <div> or <span> container, with class="deque-slider-multirange horizontal". This includes the alerts and any elements required to trigger the alert.
    • Include a <h4> container with class="neutral" for styling purposes. The inner text will be the title of the slider.
    • Add a <h3> container with id="label". This container will be filled automatically by the javascript with the inputs the user submits.
    • Create a <div> container to surround the slider pattern.
      • Create a <label> container with the inner text being "min". The javascript will add an <input> container which will update automatically when the slider controlling the minimum amount is moved and that the user can use to manually update the slider by inputing their desired amount.
      • Make the slider using a <div> container with class="slider". Also specify in the style class the height and width of the slider (e.g. style="height: 2px; width: 200px;").
        • Create two <button> containers with a unique class and role="slider", one for the minimum number and one for the maximum. For each include the following attributes:
        • aria-valuemin
          The lowest value your slider can represent.
          aria-valuemax
          The highest value your slider can represent.
          aria-orientation
          Your slider's orientation. Can be either 'horizontal' or 'vertical'.
          aria-label
          An accessible label for your slider.
          aria-valuenow
          The current value of this slider.
          data-increment
          The value of by how much the slider can change.
      • Create a <label> container with the inner text being "max". The javascript will add an <input> container which will update automatically when the slider controlling the maximum amount is moved and that the user can use to manually update the slider by inputing their desired amount.
    • Add a <h3> container with id="alertRegion". This container will be filled automatically by the javascript if the user makes an error.

Step 3: Add Javascript

Copy this slider-multirange.js file. As shown in the example select the container holding your slider using document.querySelector(".deque-slider-multirange") and assign it to a variable named "multirangeSlider". Then select your two buttons using their unique class names and assign them to variables named "startThumb" and "stopThumb". The local javascript and the external file will automatically initialize the slider pattern using these variables.