diff --git a/index.html b/index.html
index efcc355baa5f069a0b62b9d85a3962c98da8ba0e..940013fd4ea084b59ce18b4e04f3615fbf3ac0ce 100644
--- a/index.html
+++ b/index.html
@@ -10,7 +10,7 @@
     <meta name="description" content="" />
     <meta name="author" content="" />
     <title>Dashboard - iCity Bosch</title>
-    <link href="css/styles.css" rel="stylesheet" />
+    <link href="css/thirdparty/styles.css" rel="stylesheet" />
     <link
       href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css"
       rel="stylesheet"
@@ -53,16 +53,17 @@
 
     <!-- Bootstrap dashboard template -->
     <script
-      defer
       src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
       crossorigin="anonymous"
     ></script>
     <script
-      defer
       src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"
       crossorigin="anonymous"
     ></script>
-    <script defer src="js/thirdparty/scripts.js"></script>
+    <script src="js/thirdparty/scripts.js"></script>
+
+    <!-- vanillaSelectBox -->
+    <link href="css/thirdparty/vanillaSelectBox.css" rel="stylesheet" />
 
     <!--     
     Custom JS -->
@@ -133,7 +134,7 @@
                       <div id="drop-down--bldg-parent">
                         <span><strong>Building</strong></span>
                         <div class="nowrap">
-                          <select id="drop-down--bldg"></select>
+                          <select id="drop-down--bldg" multiple></select>
                         </div>
                       </div>
                       <br />
@@ -155,7 +156,6 @@
                         <span><strong>Chart type</strong></span>
                         <div class="nowrap">
                           <select id="drop-down--chart-type">
-                            <option>--Select--</option>
                             <option>Line</option>
                             <option>Heatmap</option>
                           </select>
diff --git a/public/css/styles.css b/public/css/thirdparty/styles.css
similarity index 100%
rename from public/css/styles.css
rename to public/css/thirdparty/styles.css
diff --git a/public/css/thirdparty/vanillaSelectBox.css b/public/css/thirdparty/vanillaSelectBox.css
new file mode 100644
index 0000000000000000000000000000000000000000..b12a75766cb800577ead7694bbf72b44de188f14
--- /dev/null
+++ b/public/css/thirdparty/vanillaSelectBox.css
@@ -0,0 +1,271 @@
+.hidden-search {
+	display: none !important;
+}
+
+
+li[data-parent].closed{
+	display:none !important;
+}
+
+li[data-parent].open:not(.hidden-search){
+	display:block !important;
+}
+
+.vsb-menu{
+  cursor:pointer;
+  z-index:1000;
+	display:block;
+	visibility: hidden;
+  position:absolute;/*Don't change*/
+  border:1px solid #B2B2B2;
+  background-color: #fff;
+  background-clip: padding-box;
+  border: 1px solid rgba(0,0,0,.15);
+  box-shadow: 0 6px 12px rgba(0,0,0,.175);
+  border-radius:4px;
+  font-size : 11px;
+}
+
+.vsb-js-search-zone{
+	position:absolute;/*Don't change*/
+    z-index:1001;
+	width: 80%;
+	min-height:1.8em;
+	padding: 2px;
+	background-color: #fff;
+}
+
+.vsb-js-search-zone input{
+	border: 1px solid grey;
+	margin-left: 2px;
+	width: 96%;
+	border-radius: 4px;
+	height: 25px !important;
+}
+
+.vsb-main{
+	position: relative;/*Don't change*/
+	display: inline-block;
+	vertical-align: middle;
+	text-align:left;
+}
+
+.vsb-menu li:hover {
+	background: linear-gradient(#f5f5f5, #e8e8e8);
+}
+
+.vsb-menu ul{
+	user-select:none;
+	list-style:none;
+	white-space: nowrap;
+	margin:0px;
+	margin-top:4px;
+	padding-left:10px;
+	padding-right:10px;
+	padding-bottom:3px;
+	color: #333;
+	cursor:pointer;
+	overflow-y:auto;
+}
+
+li.disabled{
+	cursor:not-allowed;
+	opacity:0.3;
+	background-color: #999;
+}
+
+li.overflow{
+	cursor:not-allowed;
+	opacity:0.3;
+	background-color: #999;
+}
+
+li.short{
+	overflow:hidden;
+	text-overflow: ellipsis;
+}
+
+.vsb-main button{
+	min-width: 120px;
+	border-radius: 0;
+	width: 100%;
+	text-align: left;
+	z-index: 1;
+	color: #333;
+	background: white !important;
+	border: 1px solid #999 !important;
+	line-height:20px;
+	font-size:14px;
+	padding:6px 12px;
+}
+
+.vsb-main button.disabled{
+	cursor:not-allowed;
+	opacity:0.65;
+}
+
+.vsb-main .title {
+	margin-right: 6px;
+	user-select:none;
+}
+
+.vsb-main li:hover {
+	background: linear-gradient(#f5f5f5, #e8e8e8);
+}
+
+.vsb-main ul{
+	white-space: nowrap;
+}
+
+.vsb-menu li {
+	font-size: 14px;
+	background-color: #fff;
+	min-height:1.4em;
+	padding: 0.2em 2em 0.2em 1em;
+}
+
+.vsb-menu li.grouped-option b {
+	display: inline-block;
+	font-size: 15px;
+	margin-left:10px;
+	transform: translate(-18px);
+}
+
+.vsb-menu li.grouped-option.open span {
+	display: inline-block;
+	font-size: inherit;
+	margin-top:-2px;
+	height: 8px;
+	width:  8px;
+	transform: translate(-38px) rotate(45deg);
+	border-bottom:  3px solid black;
+	border-right:  3px solid black;
+	border-radius:2px;
+}
+
+.vsb-menu li.grouped-option.closed span {
+	display: inline-block;
+	font-size: inherit;
+	height: 8px;
+	width:  8px;
+	transform: translate(-38px) rotate(-45deg);
+	border-bottom:  3px solid black;
+	border-right:  3px solid black;
+	border-radius:2px;
+}
+
+.vsb-menu li.grouped-option i {
+	display: inline-block;
+	font-size: inherit;
+	float:left;
+	font-weight:bold;
+	margin-left:22px;
+	margin-right:2px;
+	height: 11px;
+	width:  8px;
+	border : 1px solid;
+	border-radius : 3px;
+	padding: 1px 3px 2px 3px;
+	margin-top:0px;
+	color:black;
+}
+
+.vsb-menu li.grouped-option.checked i::after {
+	content: "";
+	display: inline-block;
+	font-size: inherit;
+	color: #333;
+	float:left;
+	margin-left:0px;
+	display: inline-block;
+	transform: rotate(45deg);
+	height: 8px;
+	width:  5px;
+	border-bottom: 3px solid black;
+	border-right:  3px solid black;
+}
+
+.vsb-menu :not(.multi) li.active {
+	margin-left:7px;
+}
+
+.vsb-menu :not(.multi) li.active::before {
+	content: "";
+	display: inline-block;
+	font-size: inherit;
+	margin-left:-18px;
+	transform: rotate(45deg);
+	height: 10px;
+	width:  5px;
+	border-bottom:  3px solid black;
+	border-right:  3px solid black;
+	border-radius:2px;
+}
+
+.vsb-menu .multi li {
+	font-size: 14px;
+	background-color: #fff;
+	min-height:1.4em;
+	padding: 0.2em 2em 0.2em 26px;
+}
+
+.vsb-menu .multi li.grouped-option {
+	font-size: 15px;
+	padding-left: 5px;
+}
+
+
+.vsb-menu .multi li.grouped-option:hover {
+	font-weight: bold;
+	text-decoration: underline;
+	color:rgb(52, 31, 112);
+}
+
+.vsb-menu .multi li:not(.grouped-option)::before{
+	content: "";
+	display: inline-block;
+	font-size: inherit;
+	float:left;
+	font-weight:bold;
+	margin-left:-22px;
+	margin-right:2px;
+	border : 1px solid;
+	border-radius : 3px;
+	padding : 7px;
+	margin-top:0px;
+	color:black;
+}
+
+.vsb-menu .multi li:not(.grouped-option).active::after {
+	content: "";
+	display: inline-block;
+	font-size: inherit;
+	color: #333;
+	float:left;
+	margin-left:-18px;
+	display: inline-block;
+	transform: rotate(45deg);
+	margin-top:1px;
+	height: 8px;
+	width:  5px;
+	border-bottom: 3px solid black;
+	border-right:  3px solid black;
+}
+
+.caret {
+	display: inline-block;
+	width: 0;
+	height: 0;
+	margin-left: 2px;
+	vertical-align: middle;
+	border-top: 4px dashed;
+	border-top: 4px solid;
+	border-right: 4px solid transparent;
+	border-left: 4px solid transparent;
+}
+
+
+li[data-parent]{
+	padding-left: 50px !important;
+}
+
diff --git a/public/js/dropDownList.js b/public/js/dropDownList.js
index 815c3375a28b07467c89a8a615a40fe6a3a101cc..b59951573304eef9acfbd78ea359a479a8bb0fcb 100644
--- a/public/js/dropDownList.js
+++ b/public/js/dropDownList.js
@@ -27,8 +27,10 @@ import {
   hideLoadingSpinner,
 } from "./src_modules/loadingIndicator.mjs";
 
+import { vanillaSelectBox } from "./thirdparty/vanillaSelectBox.mjs";
+
 const buildingsAvailableSensorsArr = [
-  ["--Select--", "", ""],
+  // ["--Select--", "", ""],
 
   ["Bau 101", "Vorlauftemperatur", "15 min"],
   ["Bau 101", "Vorlauftemperatur", "60 min"],
@@ -132,27 +134,42 @@ const makeDropDown = function (dataArr, filtersAsArray, targetElement) {
  * Use the `makeDropDown` function to create the first two levels of the linked drop down lists
  * @returns {undefined}
  */
-const applyDropDown = function () {
+const applyDropDownLevelOneTwo = function () {
   const selectLevel1Value = document.querySelector("#drop-down--bldg").value;
-  const selectLevel2 = document.querySelector("#drop-down--sensor");
+  const selectLevel2DOMString = "#drop-down--sensor";
+  const selectLevel2 = document.querySelector(selectLevel2DOMString);
   makeDropDown(buildingsAvailableSensorsArr, [selectLevel1Value], selectLevel2);
 
-  applyDropDown2();
+  applyDropDownLevelThree();
+
+  // Create our dropdown list using `vanillaSelectBox`
+  new vanillaSelectBox(selectLevel2DOMString, {
+    "placeHolder": "--Select--",
+  });
 };
 
 /**
  * Use the `makeDropDown` function to create the third level of the linked drop down lists
  * @returns {undefined}
  */
-const applyDropDown2 = function () {
+const applyDropDownLevelThree = function () {
   const selectLevel1Value = document.querySelector("#drop-down--bldg").value;
   const selectLevel2Value = document.querySelector("#drop-down--sensor").value;
-  const selectLevel3 = document.querySelector("#drop-down--sampling-rate");
+  const selectLevel3DOMString = "#drop-down--sampling-rate";
+  const selectLevel3 = document.querySelector(selectLevel3DOMString);
   makeDropDown(
     buildingsAvailableSensorsArr,
     [selectLevel1Value, selectLevel2Value],
     selectLevel3
   );
+
+  // Create our dropdown list using `vanillaSelectBox`
+  new vanillaSelectBox(selectLevel3DOMString, {
+    "placeHolder": "--Select--",
+  });
+
+  // Create our fourth level dropdown
+  styleFourthLevelDropDown();
 };
 
 /**
@@ -160,21 +177,53 @@ const applyDropDown2 = function () {
  * @returns {undefined}
  */
 const populateFirstLevelDropDown = function () {
-  const el = document.querySelector("#drop-down--bldg");
+  const selectLevel1DOMString = "#drop-down--bldg";
+  const selectLevel1 = document.querySelector(selectLevel1DOMString);
   const uniqueList = getUniqueValues(buildingsAvailableSensorsArr, 0);
-  populateDropDown(el, uniqueList);
+  populateDropDown(selectLevel1, uniqueList);
+
+  // Create our dropdown list using `vanillaSelectBox`; supports the selection of multiple options
+  new vanillaSelectBox(selectLevel1DOMString, {
+    "disableSelectAll": true,
+    "maxSelect": 3,
+    "placeHolder": "--Select--",
+    "search": false,
+  });
+};
+
+/**
+ * Use the `vanillaDropDown` library to style the fourth level drop down list
+ *
+ * @returns {undefined}
+ */
+const styleFourthLevelDropDown = function () {
+  const selectLevel4DOMString = "#drop-down--chart-type";
+
+  // Create our dropdown list using `vanillaSelectBox`
+  new vanillaSelectBox(selectLevel4DOMString, {
+    "placeHolder": "--Select--",
+  });
 };
 
 document
   .querySelector("#drop-down--bldg")
-  .addEventListener("change", applyDropDown);
+  .addEventListener("change", applyDropDownLevelOneTwo);
 document
   .querySelector("#drop-down--sensor")
-  .addEventListener("change", applyDropDown2);
+  .addEventListener("change", applyDropDownLevelThree);
+
+/**
+ * Callback function that wraps the logic of populating the linked drop down lists.
+ * Will run on `DOMContentLoaded` event
+ *
+ * @returns {undefined}
+ */
+const afterDocumentLoads = function () {
+  populateFirstLevelDropDown();
+  applyDropDownLevelOneTwo();
+};
 
-// These functions run after "DOMContentLoaded" event
-populateFirstLevelDropDown();
-applyDropDown();
+document.addEventListener("DOMContentLoaded", afterDocumentLoads);
 
 /**
  * Get the values from the currently selected options in the linked drop dpwn lists
diff --git a/public/js/src_modules/calculateTemperatureDiff.mjs b/public/js/src_modules/calculateTemperatureDiff.mjs
index 292252843794a109f8522b8cdad5e314a4eabe86..b429303342217684dabf1b0ca3126ad1b5b189e8 100644
--- a/public/js/src_modules/calculateTemperatureDiff.mjs
+++ b/public/js/src_modules/calculateTemperatureDiff.mjs
@@ -1,11 +1,14 @@
 "use strict";
 
+import { checkForAndDeleteUniqueObservationsFromLargerArray } from "./chartHelpers.mjs";
+
 import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./fetchData.mjs";
 
 import { extractPhenomenonNameFromDatastreamName } from "./fetchedDataProcessing.mjs";
 
 /**
  * Calculate the temperature difference, dT, between Vorlauf temperature [VL] and Rücklauf temperature [RL] (i.e., dT = VL - RL)
+ * @async
  * @param {String} baseUrl Base URL of the STA server
  * @param {Object} urlParams The URL parameters to be sent together with the GET request
  * @param {String} buildingId The building ID as a string
@@ -40,21 +43,47 @@ export const calculateVorlaufMinusRuecklaufTemperature = async function (
       [metadataVorlauf, metadataRuecklauf],
     ] = observationsPlusMetadata;
 
+    // Compare the lengths of the observations arrays for VL and RL,
+    // delete the unique observation(s), if necessary
+    const [vorlaufTemperatureObsFinalArr, ruecklaufTemperatureObsFinalArr] =
+      vorlaufTemperatureObsArr.length === ruecklaufTemperatureObsArr.length
+        ? [vorlaufTemperatureObsArr, ruecklaufTemperatureObsArr]
+        : checkForAndDeleteUniqueObservationsFromLargerArray(
+            vorlaufTemperatureObsArr,
+            ruecklaufTemperatureObsArr
+          );
+
     // Extract the temperature values
-    const vorlaufTemperatureValues = vorlaufTemperatureObsArr.map(
+    const vorlaufTemperatureValues = vorlaufTemperatureObsFinalArr.map(
       (vlTempObs) => vlTempObs[1]
     );
-    const ruecklaufTemperatureValues = ruecklaufTemperatureObsArr.map(
+    const ruecklaufTemperatureValues = ruecklaufTemperatureObsFinalArr.map(
       (rlTempObs) => rlTempObs[1]
     );
 
     // The arrays have equal length, we need only use one of them for looping
     // Resulting array contains the following pairs (timestamp + dT)
     const vorlaufMinusRuecklaufTemperatureObs = vorlaufTemperatureObsArr.map(
-      (vlTempObs, i) => [
-        vlTempObs[0], // timestamp
-        vorlaufTemperatureValues[i] - ruecklaufTemperatureValues[i],
-      ]
+      (vlTempObs, i) => {
+        // Use timestamp from VL, since is equal to that of RL
+        const timestamp = vlTempObs[0];
+
+        // Case 1: One of the observation values is `null`,
+        // no need to calculate temperature difference
+        if (
+          vorlaufTemperatureValues[i] === null ||
+          ruecklaufTemperatureValues[i] === null
+        ) {
+          return [timestamp, null];
+        }
+
+        // Case 2: Neither of the observation values is `null`,
+        // calculate temperature difference
+        return [
+          timestamp,
+          vorlaufTemperatureValues[i] - ruecklaufTemperatureValues[i],
+        ];
+      }
     );
 
     // From Vorlauf metadata, extract `name` and `unitOfMeasurement`
diff --git a/public/js/src_modules/chartHelpers.mjs b/public/js/src_modules/chartHelpers.mjs
index 5b61a426cba4a61a28f80c585f3c0ef135c7e72b..1ebd5b81f3cdca892ed9e9cb717d62642cf47877 100644
--- a/public/js/src_modules/chartHelpers.mjs
+++ b/public/js/src_modules/chartHelpers.mjs
@@ -8,6 +8,173 @@ const chartExportOptions = {
   },
 };
 
+/**
+ * Determines the timestamps that are missing from a smaller set of observations. Based on the comparison of two observation arrays, where one array is larger than the other
+ * @param {Array} obsTimestampArrayOne An array of timestamps for the first set of observations
+ * @param {Array} obsTimestampArrayTwo An array of timstamps for the second set of observations
+ * @returns {Array} An array of timestamps missing from either set of observations
+ */
+const getSymmetricDifferenceBetweenArrays = function (
+  obsTimestampArrayOne,
+  obsTimestampArrayTwo
+) {
+  return obsTimestampArrayOne
+    .filter((timestampOne) => !obsTimestampArrayTwo.includes(timestampOne))
+    .concat(
+      obsTimestampArrayTwo.filter(
+        (timestampTwo) => !obsTimestampArrayOne.includes(timestampTwo)
+      )
+    );
+};
+
+/**
+ * Determines the indexes of timestamps that are unique to the larger set of observatiuons. Based on the comparison of two observation arrays, where one array is larger than the other
+ * @param {Array} uniqueTimestampsArr An array of timestamps unique to the larger set of observations
+ * @param {Array} largerObsTimestampArr An array of timestamps for the larger set of observations
+ * @returns {Array} An array of the indexes of the missing observations
+ */
+const getIndexesOfUniqueObservations = function (
+  uniqueTimestampsArr,
+  largerObsTimestampArr
+) {
+  return uniqueTimestampsArr.map((index) =>
+    largerObsTimestampArr.indexOf(index)
+  );
+};
+
+/**
+ * Removes observations (by modifying array in place) that are unique to a larger set of observations. Based on the comparison of two observation arrays, where one array is larger than the other
+ * @param {Array} uniqueIndexesArr An array of the indexes unique to the larger set of observations
+ * @param {Array} largerObsArr The larger array of observations (timestamp + value)
+ * @returns {Array} The larger array with the unique indexes removed
+ */
+const removeUniqueObservationsFromLargerArray = function (
+  uniqueIndexesArr,
+  largerObsArr
+) {
+  // Create a reversed copy of the indexes array, so that the larger index is removed first
+  const reversedUniqueIndexesArr = uniqueIndexesArr.reverse();
+
+  // Create a copy the larger observation array, will be modified in place
+  const processedLargerObsArr = largerObsArr;
+
+  reversedUniqueIndexesArr.forEach((index) => {
+    if (index > -1) {
+      processedLargerObsArr.splice(index, 1);
+    }
+  });
+
+  return processedLargerObsArr;
+};
+
+/**
+ * Compares the length of two input arrays to determine the larger one
+ * @param {Array} firstArr First input array
+ * @param {Array} secondArr Second input array
+ * @returns {Array} The larger array
+ */
+const getLargerArrayBetweenTwoInputArrays = function (firstArr, secondArr) {
+  if (firstArr.length === secondArr.length) return;
+
+  if (firstArr.length > secondArr.length) return firstArr;
+
+  if (firstArr.length < secondArr.length) return secondArr;
+};
+
+/**
+ * Compares the length of two input arrays to determine the smaller one
+ * @param {Array} firstArr First input array
+ * @param {Array} secondArr Second input array
+ * @returns {Array} The smaller array
+ */
+const getSmallerArrayBetweenTwoInputArrays = function (firstArr, secondArr) {
+  if (firstArr.length === secondArr.length) return;
+
+  if (firstArr.length < secondArr.length) return firstArr;
+
+  if (firstArr.length > secondArr.length) return secondArr;
+};
+
+/**
+ * Utility function for deleting the unique observations from a larger array
+ * @param {Array} obsArrayOne Array of observations (timestamp + value) that is response from SensorThings API
+ * @param {Array} obsArrayTwo Array of observations (timestamp + value) that is response from SensorThings API
+ * @returns {Array} Two arrays of observations (timestamp + value) with matching timestamps and equal lengths
+ */
+const deleteUniqueObservationsFromLargerArray = function (
+  obsArrayOne,
+  obsArrayTwo
+) {
+  // Create arrays with timestamps only
+  const obsArrayOneTimestamp = obsArrayOne.map(
+    (obsTimeValue) => obsTimeValue[0]
+  );
+  const obsArrayTwoTimestamp = obsArrayTwo.map(
+    (obsTimeValue) => obsTimeValue[0]
+  );
+
+  const missingTimestamp = getSymmetricDifferenceBetweenArrays(
+    obsArrayOneTimestamp,
+    obsArrayTwoTimestamp
+  );
+
+  // Determine the larger observation timestamp array
+  const biggerObsTimestampArr = getLargerArrayBetweenTwoInputArrays(
+    obsArrayOneTimestamp,
+    obsArrayTwoTimestamp
+  );
+
+  // Indexes of the missing observations
+  const indexesMissingObsArr = getIndexesOfUniqueObservations(
+    missingTimestamp,
+    biggerObsTimestampArr
+  );
+
+  // Determine the larger observation array
+  const biggerObsArr = getLargerArrayBetweenTwoInputArrays(
+    obsArrayOne,
+    obsArrayTwo
+  );
+
+  // Determine the smaller observation array
+  const smallerObsArr = getSmallerArrayBetweenTwoInputArrays(
+    obsArrayOne,
+    obsArrayTwo
+  );
+
+  // Remove the missing observation from the larger array of observations
+  const modifiedBiggerObsArr = removeUniqueObservationsFromLargerArray(
+    indexesMissingObsArr,
+    biggerObsArr
+  );
+
+  return [modifiedBiggerObsArr, smallerObsArr];
+};
+
+/**
+ * Utility function for deleting the unique observations from a larger array AND ensuring the order of input arrays is maintained
+ * @param {Array} obsArrayOne Array of observations (timestamp + value) that is response from SensorThings API
+ * @param {Array} obsArrayTwo Array of observations (timestamp + value) that is response from SensorThings API
+ * @returns {Array} Two arrays of observations (timestamp + value) with matching timestamps and equal lengths
+ */
+const checkForAndDeleteUniqueObservationsFromLargerArray = function (
+  obsArrayOne,
+  obsArrayTwo
+) {
+  if (obsArrayOne.length === obsArrayTwo.length) return;
+
+  //   Case 1: obsArrayOne.length < obsArrayTwo.length
+  if (obsArrayOne.length < obsArrayTwo.length) {
+    const [biggerObsArr, smallerObsArr] =
+      deleteUniqueObservationsFromLargerArray(obsArrayOne, obsArrayTwo);
+
+    return [smallerObsArr, biggerObsArr];
+  }
+
+  //   Case 2: obsArrayOne.length > obsArrayTwo.length
+  return deleteUniqueObservationsFromLargerArray(obsArrayOne, obsArrayTwo);
+};
+
 /**
  * Convert a hexadecimal color code obtained from the Highcharts object (`Highcharts.getOptions().colors`) to its equivalent RGB color code
  * @param {String} hexCode Input hex color code
@@ -142,6 +309,7 @@ const removeTransparencyFromColor = function (rgbaColor) {
 
 export {
   chartExportOptions,
+  checkForAndDeleteUniqueObservationsFromLargerArray,
   createCombinedTextDelimitedByAmpersand,
   createCombinedTextDelimitedByComma,
   createFullTitleForLineOrColumnChart,
diff --git a/public/js/src_modules/chartScatterPlot.mjs b/public/js/src_modules/chartScatterPlot.mjs
index d690741037927090deb16d23b571e8fcb944784f..e1bd2143d4e744f5dcb0820f50f56872760d29e1 100644
--- a/public/js/src_modules/chartScatterPlot.mjs
+++ b/public/js/src_modules/chartScatterPlot.mjs
@@ -2,6 +2,7 @@
 
 import {
   chartExportOptions,
+  checkForAndDeleteUniqueObservationsFromLargerArray,
   convertHexColorToRGBColor,
   createCombinedTextDelimitedByAmpersand,
   createCombinedTextDelimitedByComma,
@@ -9,173 +10,6 @@ import {
   removeTransparencyFromColor,
 } from "./chartHelpers.mjs";
 
-/**
- * Determines the timestamps that are missing from a smaller set of observations. Based on the comparison of two observation arrays, where one array is larger than the other
- * @param {Array} obsTimestampArrayOne An array of timestamps for the first set of observations
- * @param {Array} obsTimestampArrayTwo An array of timstamps for the second set of observations
- * @returns {Array} An array of timestamps missing from either set of observations
- */
-const getSymmetricDifferenceBetweenArrays = function (
-  obsTimestampArrayOne,
-  obsTimestampArrayTwo
-) {
-  return obsTimestampArrayOne
-    .filter((timestampOne) => !obsTimestampArrayTwo.includes(timestampOne))
-    .concat(
-      obsTimestampArrayTwo.filter(
-        (timestampTwo) => !obsTimestampArrayOne.includes(timestampTwo)
-      )
-    );
-};
-
-/**
- * Determines the indexes of timestamps that are unique to the larger set of observatiuons. Based on the comparison of two observation arrays, where one array is larger than the other
- * @param {Array} uniqueTimestampsArr An array of timestamps unique to the larger set of observations
- * @param {Array} largerObsTimestampArr An array of timestamps for the larger set of observations
- * @returns {Array} An array of the indexes of the missing observations
- */
-const getIndexesOfUniqueObservations = function (
-  uniqueTimestampsArr,
-  largerObsTimestampArr
-) {
-  return uniqueTimestampsArr.map((index) =>
-    largerObsTimestampArr.indexOf(index)
-  );
-};
-
-/**
- * Removes observations (by modifying array in place) that are unique to a larger set of observations. Based on the comparison of two observation arrays, where one array is larger than the other
- * @param {Array} uniqueIndexesArr An array of the indexes unique to the larger set of observations
- * @param {Array} largerObsArr The larger array of observations (timestamp + value)
- * @returns {Array} The larger array with the unique indexes removed
- */
-const removeUniqueObservationsFromLargerArray = function (
-  uniqueIndexesArr,
-  largerObsArr
-) {
-  // Create a reversed copy of the indexes array, so that the larger index is removed first
-  const reversedUniqueIndexesArr = uniqueIndexesArr.reverse();
-
-  // Create a copy the larger observation array, will be modified in place
-  const processedLargerObsArr = largerObsArr;
-
-  reversedUniqueIndexesArr.forEach((index) => {
-    if (index > -1) {
-      processedLargerObsArr.splice(index, 1);
-    }
-  });
-
-  return processedLargerObsArr;
-};
-
-/**
- * Compares the length of two input arrays to determine the larger one
- * @param {Array} firstArr First input array
- * @param {Array} secondArr Second input array
- * @returns {Array} The larger array
- */
-const getLargerArrayBetweenTwoInputArrays = function (firstArr, secondArr) {
-  if (firstArr.length === secondArr.length) return;
-
-  if (firstArr.length > secondArr.length) return firstArr;
-
-  if (firstArr.length < secondArr.length) return secondArr;
-};
-
-/**
- * Compares the length of two input arrays to determine the smaller one
- * @param {Array} firstArr First input array
- * @param {Array} secondArr Second input array
- * @returns {Array} The smaller array
- */
-const getSmallerArrayBetweenTwoInputArrays = function (firstArr, secondArr) {
-  if (firstArr.length === secondArr.length) return;
-
-  if (firstArr.length < secondArr.length) return firstArr;
-
-  if (firstArr.length > secondArr.length) return secondArr;
-};
-
-/**
- * Utility function for deleting the unique observations from a larger array
- * @param {Array} obsArrayOne Array of observations (timestamp + value) that is response from SensorThings API
- * @param {Array} obsArrayTwo Array of observations (timestamp + value) that is response from SensorThings API
- * @returns {Array} Two arrays of observations (timestamp + value) with matching timestamps and equal lengths
- */
-const deleteUniqueObservationsFromLargerArray = function (
-  obsArrayOne,
-  obsArrayTwo
-) {
-  // Create arrays with timestamps only
-  const obsArrayOneTimestamp = obsArrayOne.map(
-    (obsTimeValue) => obsTimeValue[0]
-  );
-  const obsArrayTwoTimestamp = obsArrayTwo.map(
-    (obsTimeValue) => obsTimeValue[0]
-  );
-
-  const missingTimestamp = getSymmetricDifferenceBetweenArrays(
-    obsArrayOneTimestamp,
-    obsArrayTwoTimestamp
-  );
-
-  // Determine the larger observation timestamp array
-  const biggerObsTimestampArr = getLargerArrayBetweenTwoInputArrays(
-    obsArrayOneTimestamp,
-    obsArrayTwoTimestamp
-  );
-
-  // Indexes of the missing observations
-  const indexesMissingObsArr = getIndexesOfUniqueObservations(
-    missingTimestamp,
-    biggerObsTimestampArr
-  );
-
-  // Determine the larger observation array
-  const biggerObsArr = getLargerArrayBetweenTwoInputArrays(
-    obsArrayOne,
-    obsArrayTwo
-  );
-
-  // Determine the smaller observation array
-  const smallerObsArr = getSmallerArrayBetweenTwoInputArrays(
-    obsArrayOne,
-    obsArrayTwo
-  );
-
-  // Remove the missing observation from the larger array of observations
-  const modifiedBiggerObsArr = removeUniqueObservationsFromLargerArray(
-    indexesMissingObsArr,
-    biggerObsArr
-  );
-
-  return [modifiedBiggerObsArr, smallerObsArr];
-};
-
-/**
- * Utility function for deleting the unique observations from a larger array AND ensuring the order of input arrays is maintained
- * @param {Array} obsArrayOne Array of observations (timestamp + value) that is response from SensorThings API
- * @param {Array} obsArrayTwo Array of observations (timestamp + value) that is response from SensorThings API
- * @returns {Array} Two arrays of observations (timestamp + value) with matching timestamps and equal lengths
- */
-const checkForAndDeleteUniqueObservationsFromLargerArray = function (
-  obsArrayOne,
-  obsArrayTwo
-) {
-  if (obsArrayOne.length === obsArrayTwo.length) return;
-
-  //   Case 1: obsArrayOne.length < obsArrayTwo.length
-  if (obsArrayOne.length < obsArrayTwo.length) {
-    const [biggerObsArr, smallerObsArr] =
-      deleteUniqueObservationsFromLargerArray(obsArrayOne, obsArrayTwo);
-
-    return [smallerObsArr, biggerObsArr];
-  }
-
-  //   Case 2: obsArrayOne.length > obsArrayTwo.length
-  return deleteUniqueObservationsFromLargerArray(obsArrayOne, obsArrayTwo);
-};
-
 /**
  * Extracts and combines observation values from two input observation arrays of equal length
  * @param {Array} obsArrayOne First set of N observations (timestamp + value)
diff --git a/public/js/thirdparty/vanillaSelectBox.mjs b/public/js/thirdparty/vanillaSelectBox.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..391efe9e5ae32fb4d1114640683cd69e61c454f6
--- /dev/null
+++ b/public/js/thirdparty/vanillaSelectBox.mjs
@@ -0,0 +1,1332 @@
+/*
+Copyright (C) Philippe Meyer 2019-2021
+Distributed under the MIT License 
+
+vanillaSelectBox : v0.75 : Remote search ready + local search modification : when a check on optgroup checks children only 
+                           if they not excluded from search.
+vanillaSelectBox : v0.72 : Remote search (WIP) bugfix [x] Select all duplicated
+vanillaSelectBox : v0.71 : Remote search (WIP) better code
+vanillaSelectBox : v0.70 : Remote search (WIP) for users to test
+vanillaSelectBox : v0.65 : Two levels: bug fix : groups are checked/unchecked when check all/uncheck all is clicked
+vanillaSelectBox : v0.64 : Two levels: groups are now checkable to check/uncheck the children options 
+vanillaSelectBox : v0.63 : Two levels: one click on the group selects / unselects children
+vanillaSelectBox : v0.62 : New option: maxOptionWidth set a maximum width for each option for narrow menus
+vanillaSelectBox : v0.61 : New option: maxSelect, set a maximum to the selectable options in a multiple choice menu
+vanillaSelectBox : v0.60 : Two levels: Optgroups are now used to show two level dropdowns 
+vanillaSelectBox : v0.59 : Bug fix : search box was overlapping first item in single selects
+vanillaSelectBox : v0.58 : Bug fixes
+vanillaSelectBox : v0.57 : Bug fix (minWidth option not honored)
+vanillaSelectBox : v0.56 : The multiselect checkboxes are a little smaller, maxWidth option is now working + added minWidth option as well
+                           The button has now a style attribute to protect its appearance 
+vanillaSelectBox : v0.55 : All attributes from the original select options are copied to the selectBox element
+vanillaSelectBox : v0.54 : if all the options of the select are selected by the user then the check all checkbox is checked
+vanillaSelectBox : v0.53 : if all the options of the select are selected then the check all checkbox is checked
+vanillaSelectBox : v0.52 : Better support of select('all') => command is consistent with checkbox and selecting / deselecting while searching select / uncheck only the found items
+vanillaSelectBox : v0.51 : Translations for select all/clear all + minor css corrections + don't select disabled items
+vanillaSelectBox : v0.50 : PR by jaguerra2017 adding a select all/clear all check button + optgroup support !
+vanillaSelectBox : v0.41 : Bug corrected, the menu content was misplaced if a css transform was applied on a parent
+vanillaSelectBox : v0.40 : A click on one selectBox close the other opened boxes
+vanillaSelectBox : v0.35 : You can enable and disable items
+vanillaSelectBox : v0.30 : The menu stops moving around on window resize and scroll + z-index in order of creation for multiple instances
+vanillaSelectBox : v0.26 : Corrected bug in stayOpen mode with disable() function
+vanillaSelectBox : v0.25 : New option stayOpen, and the dropbox is no longer a dropbox but a nice multi-select
+previous version : v0.24 : corrected bug affecting options with more than one class
+https://github.com/PhilippeMarcMeyer/vanillaSelectBox
+*/
+
+let VSBoxCounter = function () {
+    let count = 0;
+    let instances = [];
+    return {
+        set: function (instancePtr) {
+            instances.push({ offset: ++count, ptr: instancePtr });
+            return instances[instances.length - 1].offset;
+        },
+        remove: function (instanceNr) {
+            let temp = instances.filter(function (x) {
+                return x.offset != instanceNr;
+            })
+            instances = temp.splice(0);
+        },
+        closeAllButMe: function (instanceNr) {
+            instances.forEach(function (x) {
+                if (x.offset != instanceNr) {
+                    x.ptr.closeOrder();
+                }
+            });
+        }
+    };
+}();
+
+function vanillaSelectBox(domSelector, options) {
+    let self = this;
+    this.instanceOffset = VSBoxCounter.set(self);
+    this.domSelector = domSelector;
+    this.root = document.querySelector(domSelector);
+    this.rootToken = null;
+    this.main;
+    this.button;
+    this.title;
+    this.isMultiple = this.root.hasAttribute("multiple");
+    this.multipleSize = this.isMultiple && this.root.hasAttribute("size") ? parseInt(this.root.getAttribute("size")) : -1;
+    this.isOptgroups = false;
+    this.currentOptgroup = 0;
+    this.drop;
+    this.top;
+    this.left;
+    this.options;
+    this.listElements;
+    this.isDisabled = false;
+    this.search = false;
+    this.searchZone = null;
+    this.inputBox = null;
+    this.disabledItems = [];
+    this.ulminWidth = 140;
+    this.ulmaxWidth = 280;
+    this.ulminHeight = 25;
+    this.maxOptionWidth = Infinity;
+    this.maxSelect = Infinity;
+    this.isInitRemote = false;
+    this.isSearchRemote = false;
+    this.onInit = null;
+    this.onSearch = null; // if isRemote is true : a user defined function that loads more options from the back
+    this.onInitSize = null;
+    this.forbidenAttributes = ["class", "selected", "disabled", "data-text", "data-value", "style"];
+    this.forbidenClasses = ["active", "disabled"];
+    this.userOptions = {
+        maxWidth: 500,
+        minWidth: -1,
+        maxHeight: 400,
+        translations: { "all": "All", "items": "items", "selectAll": "Select All", "clearAll": "Clear All" },
+        search: false,
+        placeHolder: "",
+        stayOpen: false,
+        disableSelectAll: false
+    }
+    if (options) {
+        if (options.maxWidth != undefined) {
+            this.userOptions.maxWidth = options.maxWidth;
+        }
+        if (options.minWidth != undefined) {
+            this.userOptions.minWidth = options.minWidth;
+        }
+        if (options.maxHeight != undefined) {
+            this.userOptions.maxHeight = options.maxHeight;
+        }
+        if (options.translations != undefined) {
+            for (var property in options.translations) {
+                if (options.translations.hasOwnProperty(property)) {
+                    if (this.userOptions.translations[property]) {
+                        this.userOptions.translations[property] = options.translations[property];
+                    }
+                }
+            }
+        }
+        if (options.placeHolder != undefined) {
+            this.userOptions.placeHolder = options.placeHolder;
+        }
+        if (options.search != undefined) {
+            this.search = options.search;
+        }
+        if (options.remote != undefined && options.remote) {
+
+           // user defined onInit  function
+            if (options.remote.onInit!= undefined && typeof options.remote.onInit === 'function') {
+                this.onInit = options.remote.onInit;
+                this.isInitRemote = true;
+            } 
+            if (options.remote.onInitSize != undefined) {
+                this.onInitSize = options.remote.onInitSize;
+                if (this.onInitSize < 3) this.onInitSize = 3;
+            }
+            // user defined remote search function
+            if (options.remote.onSearch != undefined && typeof options.remote.onSearch === 'function') {
+                this.onSearch = options.remote.onSearch;
+                this.isSearchRemote = true;
+            }
+        }
+
+        if (options.stayOpen != undefined) {
+            this.userOptions.stayOpen = options.stayOpen;
+        }
+
+        if (options.disableSelectAll != undefined) {
+            this.userOptions.disableSelectAll = options.disableSelectAll;
+        }
+
+        if (options.maxSelect != undefined && !isNaN(options.maxSelect) && options.maxSelect >= 1) {
+            this.maxSelect = options.maxSelect;
+            this.userOptions.disableSelectAll = true;
+        }
+
+        if (options.maxOptionWidth != undefined && !isNaN(options.maxOptionWidth) && options.maxOptionWidth >= 20) {
+            this.maxOptionWidth = options.maxOptionWidth;
+            this.ulminWidth = options.maxOptionWidth + 60;
+            this.ulmaxWidth = options.maxOptionWidth + 60;
+        }
+    }
+
+    this.closeOrder = function () {
+        let self = this;
+        if (!self.userOptions.stayOpen) {
+            self.drop.style.visibility = "hidden";
+            if (self.search) {
+                self.inputBox.value = "";
+                Array.prototype.slice.call(self.listElements).forEach(function (x) {
+                    x.classList.remove("hide");
+                });
+            }
+        }
+    }
+
+    this.getCssArray = function (selector) {
+        // Why inline css ? To protect the button display from foreign css files
+        let cssArray = [];
+        if (selector === ".vsb-main button") {
+            cssArray = [
+                { "key": "min-width", "value": "120px" },
+                { "key": "border-radius", "value": "0" },
+                { "key": "width", "value": "100%" },
+                { "key": "text-align", "value": "left" },
+                { "key": "z-index", "value": "1" },
+                { "key": "color", "value": "#333" },
+                { "key": "background", "value": "white !important" },
+                { "key": "border", "value": "1px solid #999 !important" },
+                { "key": "line-height", "value": "20px" },
+                { "key": "font-size", "value": "14px" },
+                { "key": "padding", "value": "6px 12px" }
+            ]
+        }
+
+        return cssArrayToString(cssArray);
+
+        function cssArrayToString(cssList) {
+            let list = "";
+            cssList.forEach(function (x) {
+                list += x.key + ":" + x.value + ";";
+            });
+            return list;
+        }
+    }
+
+    this.init = function () {
+        let self = this;
+        if (self.isInitRemote) {
+            self.onInit("",self.onInitSize)
+                .then(function (data) {
+                    self.buildSelect(data);
+                    self.createTree();
+                });
+        } else {
+            self.createTree();
+        }
+    }
+
+    this.createTree = function () {
+
+        this.rootToken = self.domSelector.replace(/[^A-Za-z0-9]+/, "")
+        this.root.style.display = "none";
+        let already = document.getElementById("btn-group-" + this.rootToken);
+        if (already) {
+            already.remove();
+        }
+        this.main = document.createElement("div");
+        this.root.parentNode.insertBefore(this.main, this.root.nextSibling);
+        this.main.classList.add("vsb-main");
+        this.main.setAttribute("id", "btn-group-" + this.rootToken);
+        this.main.style.marginLeft = this.main.style.marginLeft;
+        if (self.userOptions.stayOpen) {
+            this.main.style.minHeight = (this.userOptions.maxHeight + 10) + "px";
+        }
+
+        if (self.userOptions.stayOpen) {
+            this.button = document.createElement("div");
+        } else {
+            this.button = document.createElement("button");
+            var cssList = self.getCssArray(".vsb-main button");
+            this.button.setAttribute("style", cssList);
+        }
+        this.button.style.maxWidth = this.userOptions.maxWidth + "px";
+        if (this.userOptions.minWidth !== -1) {
+            this.button.style.minWidth = this.userOptions.minWidth + "px";
+        }
+
+        this.main.appendChild(this.button);
+        this.title = document.createElement("span");
+        this.button.appendChild(this.title);
+        this.title.classList.add("title");
+        let caret = document.createElement("span");
+        this.button.appendChild(caret);
+
+        caret.classList.add("caret");
+        caret.style.position = "absolute";
+        caret.style.right = "8px";
+        caret.style.marginTop = "8px";
+
+        if (self.userOptions.stayOpen) {
+            caret.style.display = "none";
+            this.title.style.paddingLeft = "20px";
+            this.title.style.fontStyle = "italic";
+            this.title.style.verticalAlign = "20%";
+        }
+
+        this.drop = document.createElement("div");
+        this.main.appendChild(this.drop);
+        this.drop.classList.add("vsb-menu");
+        this.drop.style.zIndex = 2000 - this.instanceOffset;
+        this.ul = document.createElement("ul");
+        this.drop.appendChild(this.ul);
+
+        this.ul.style.maxHeight = this.userOptions.maxHeight + "px";
+        this.ul.style.minWidth = this.ulminWidth + "px";
+        this.ul.style.maxWidth = this.ulmaxWidth + "px";
+        this.ul.style.minHeight = this.ulminHeight + "px";
+        if (this.isMultiple) {
+            this.ul.classList.add("multi");
+            if (!self.userOptions.disableSelectAll) {
+                let selectAll = document.createElement("option");
+                selectAll.setAttribute("value", 'all');
+                selectAll.innerText = self.userOptions.translations.selectAll;
+                this.root.insertBefore(selectAll, (this.root.hasChildNodes())
+                    ? this.root.childNodes[0]
+                    : null);
+            }
+        }
+        let selectedTexts = ""
+        let sep = "";
+        let nrActives = 0;
+
+        if (this.search) {
+            this.searchZone = document.createElement("div");
+            this.ul.appendChild(this.searchZone);
+            this.searchZone.classList.add("vsb-js-search-zone");
+            this.searchZone.style.zIndex = 2001 - this.instanceOffset;
+            this.inputBox = document.createElement("input");
+            this.searchZone.appendChild(this.inputBox);
+            this.inputBox.setAttribute("type", "text");
+            this.inputBox.setAttribute("id", "search_" + this.rootToken);
+            if (this.maxOptionWidth < Infinity) {
+                this.searchZone.style.maxWidth = self.maxOptionWidth + 30 + "px";
+                this.inputBox.style.maxWidth = self.maxOptionWidth + 30 + "px";
+            }
+
+            var para = document.createElement("p");
+            this.ul.appendChild(para);
+            para.style.fontSize = "12px";
+            para.innerHTML = "&nbsp;";
+            this.ul.addEventListener("scroll", function (e) {
+                var y = this.scrollTop;
+                self.searchZone.parentNode.style.top = y + "px";
+            });
+        }
+
+        this.options = document.querySelectorAll(this.domSelector + " > option");
+        Array.prototype.slice.call(this.options).forEach(function (x) {
+            let text = x.textContent;
+            let value = x.value;
+            let originalAttrs;
+            if (x.hasAttributes()) {
+                originalAttrs = Array.prototype.slice.call(x.attributes)
+                    .filter(function (a) {
+                        return self.forbidenAttributes.indexOf(a.name) === -1
+                    });
+            }
+            let classes = x.getAttribute("class");
+            if (classes) {
+                classes = classes
+                    .split(" ")
+                    .filter(function (c) {
+                        return self.forbidenClasses.indexOf(c) === -1
+                    });
+            } else {
+                classes = [];
+            }
+            let li = document.createElement("li");
+            let isSelected = x.hasAttribute("selected");
+            let isDisabled = x.hasAttribute("disabled");
+
+            self.ul.appendChild(li);
+            li.setAttribute("data-value", value);
+            li.setAttribute("data-text", text);
+
+            if (originalAttrs !== undefined) {
+                originalAttrs.forEach(function (a) {
+                    li.setAttribute(a.name, a.value);
+                });
+            }
+
+            classes.forEach(function (x) {
+                li.classList.add(x);
+            });
+
+            if (self.maxOptionWidth < Infinity) {
+                li.classList.add("short");
+                li.style.maxWidth = self.maxOptionWidth + "px";
+            }
+
+            if (isSelected) {
+                nrActives++;
+                selectedTexts += sep + text;
+                sep = ",";
+                li.classList.add("active");
+                if (!self.isMultiple) {
+                    self.title.textContent = text;
+                    if (classes.length != 0) {
+                        classes.forEach(function (x) {
+                            self.title.classList.add(x);
+                        });
+                    }
+                }
+            }
+            if (isDisabled) {
+                li.classList.add("disabled");
+            }
+            li.appendChild(document.createTextNode(" " + text));
+        });
+
+        if (document.querySelector(this.domSelector + ' optgroup') !== null) {
+            this.isOptgroups = true;
+            //this.isRemote = false;// debug
+            this.options = document.querySelectorAll(this.domSelector + " option");
+            let groups = document.querySelectorAll(this.domSelector + ' optgroup');
+            Array.prototype.slice.call(groups).forEach(function (group) {
+                let groupOptions = group.querySelectorAll('option');
+                let li = document.createElement("li");
+                let span = document.createElement("span");
+                let iCheck = document.createElement("i");
+                let labelElement = document.createElement("b");
+                let dataWay = group.getAttribute("data-way");
+                if (!dataWay) dataWay = "closed";
+                if (!dataWay || (dataWay !== "closed" && dataWay !== "open")) dataWay = "closed";
+                li.appendChild(span);
+                li.appendChild(iCheck);
+                self.ul.appendChild(li);
+                li.classList.add('grouped-option');
+                li.classList.add(dataWay);
+                self.currentOptgroup++;
+                let optId = self.rootToken + "-opt-" + self.currentOptgroup;
+                li.id = optId;
+                li.appendChild(labelElement);
+                labelElement.appendChild(document.createTextNode(group.label));
+                li.setAttribute("data-text", group.label);
+                self.ul.appendChild(li);
+
+                Array.prototype.slice.call(groupOptions).forEach(function (x) {
+                    let text = x.textContent;
+                    let value = x.value;
+                    let classes = x.getAttribute("class");
+                    if (classes) {
+                        classes = classes.split(" ");
+                    }
+                    else {
+                        classes = [];
+                    }
+                    classes.push(dataWay);
+                    let li = document.createElement("li");
+                    let isSelected = x.hasAttribute("selected");
+                    self.ul.appendChild(li);
+                    li.setAttribute("data-value", value);
+                    li.setAttribute("data-text", text);
+                    li.setAttribute("data-parent", optId);
+                    if (classes.length != 0) {
+                        classes.forEach(function (x) {
+                            li.classList.add(x);
+                        });
+                    }
+                    if (isSelected) {
+                        nrActives++;
+                        selectedTexts += sep + text;
+                        sep = ",";
+                        li.classList.add("active");
+                        if (!self.isMultiple) {
+                            self.title.textContent = text;
+                            if (classes.length != 0) {
+                                classes.forEach(function (x) {
+                                    self.title.classList.add(x);
+                                });
+                            }
+                        }
+                    }
+                    li.appendChild(document.createTextNode(text));
+                })
+            })
+        }
+
+        if (self.multipleSize != -1) {
+            if (nrActives > self.multipleSize) {
+                let wordForItems = self.userOptions.translations.items || "items"
+                selectedTexts = nrActives + " " + wordForItems;
+            }
+        }
+        if (self.isMultiple) {
+            self.title.innerHTML = selectedTexts;
+        }
+        if (self.userOptions.placeHolder != "" && self.title.textContent == "") {
+            self.title.textContent = self.userOptions.placeHolder;
+        }
+        this.listElements = this.drop.querySelectorAll("li:not(.grouped-option)");
+        if (self.search) {
+            self.inputBox.addEventListener("keyup", function (e) {
+                let searchValue = e.target.value.toUpperCase();
+                let searchValueLength = searchValue.length;
+                let nrFound = 0;
+                let nrChecked = 0;
+                let selectAll = null;
+                if (self.isSearchRemote) {
+                    if (searchValueLength == 0) {
+                        self.remoteSearchIntegrate(null);
+                    } else if (searchValueLength >= 3) {
+                        self.onSearch(searchValue)
+                            .then(function (data) {
+                                self.remoteSearchIntegrate(data);
+                            });
+                    }
+                } else {
+                    if (searchValueLength < 3) {
+                        Array.prototype.slice.call(self.listElements).forEach(function (x) {
+                            if (x.getAttribute('data-value') === 'all') {
+                                selectAll = x;
+                            } else {
+                                x.classList.remove("hidden-search");
+                                nrFound++;
+                                nrChecked += x.classList.contains('active');
+                            }
+                        });
+                    } else {
+                        Array.prototype.slice.call(self.listElements).forEach(function (x) {
+                            if (x.getAttribute('data-value') !== 'all') {
+                                let text = x.getAttribute("data-text").toUpperCase();
+                                if (text.indexOf(searchValue) === -1 && x.getAttribute('data-value') !== 'all') {
+                                    x.classList.add("hidden-search");
+                                } else {
+                                    nrFound++;
+                                    x.classList.remove("hidden-search");
+                                    nrChecked += x.classList.contains('active');
+                                }
+                            } else {
+                                selectAll = x;
+                            }
+                        });
+                    }
+                    if (selectAll) {
+                        if (nrFound === 0) {
+                            selectAll.classList.add('disabled');
+                        } else {
+                            selectAll.classList.remove('disabled');
+                        }
+                        if (nrChecked !== nrFound) {
+                            selectAll.classList.remove("active");
+                            selectAll.innerText = self.userOptions.translations.selectAll;
+                            selectAll.setAttribute('data-selected', 'false')
+                        } else {
+                            selectAll.classList.add("active");
+                            selectAll.innerText = self.userOptions.translations.clearAll;
+                            selectAll.setAttribute('data-selected', 'true')
+                        }
+                    }
+                }
+
+            }); //
+        }
+
+        if (self.userOptions.stayOpen) {
+            self.drop.style.visibility = "visible";
+            self.drop.style.boxShadow = "none";
+            self.drop.style.minHeight = (this.userOptions.maxHeight + 10) + "px";
+            self.drop.style.position = "relative";
+            self.drop.style.left = "0px";
+            self.drop.style.top = "0px";
+            self.button.style.border = "none";
+        } else {
+            this.main.addEventListener("click", function (e) {
+                if (self.isDisabled) return;
+                self.drop.style.left = self.left + "px";
+                self.drop.style.top = self.top + "px";
+                self.drop.style.visibility = "visible";
+                document.addEventListener("click", docListener);
+                e.preventDefault();
+                e.stopPropagation();
+                if (!self.userOptions.stayOpen) {
+                    VSBoxCounter.closeAllButMe(self.instanceOffset);
+                }
+            });
+        }
+
+        this.drop.addEventListener("click", function (e) {
+            if (self.isDisabled) return;
+            if (e.target.tagName === 'INPUT') return;
+            let isShowHideCommand = e.target.tagName === 'SPAN';
+            let isCheckCommand = e.target.tagName === 'I';
+            let liClicked = e.target.parentElement;
+            if (!liClicked.hasAttribute("data-value")) {
+                if (liClicked.classList.contains("grouped-option")) {
+                    if (!isShowHideCommand && !isCheckCommand) return;
+                    let oldClass, newClass;
+                    if (isCheckCommand) { // check or uncheck children
+                        self.checkUncheckFromParent(liClicked);
+                    } else { //open or close
+                        if (liClicked.classList.contains("open")) {
+                            oldClass = "open"
+                            newClass = "closed"
+                        } else {
+                            oldClass = "closed"
+                            newClass = "open"
+                        }
+                        liClicked.classList.remove(oldClass);
+                        liClicked.classList.add(newClass);
+                        let theChildren = self.drop.querySelectorAll("[data-parent='" + liClicked.id + "']");
+                        theChildren.forEach(function (x) {
+                            x.classList.remove(oldClass);
+                            x.classList.add(newClass);
+                        })
+                    }
+                    return;
+                }
+            }
+            let choiceValue = e.target.getAttribute("data-value");
+            let choiceText = e.target.getAttribute("data-text");
+            let className = e.target.getAttribute("class");
+
+            if (className && className.indexOf("disabled") != -1) {
+                return;
+            }
+
+            if (className && className.indexOf("overflow") != -1) {
+                return;
+            }
+
+            if (choiceValue === 'all') {
+                if (e.target.hasAttribute('data-selected')
+                    && e.target.getAttribute('data-selected') === 'true') {
+                    self.setValue('none')
+                } else {
+                    self.setValue('all');
+                }
+                return;
+            }
+
+            if (!self.isMultiple) {
+                self.root.value = choiceValue;
+                self.title.textContent = choiceText;
+                if (className) {
+                    self.title.setAttribute("class", className + " title");
+                } else {
+                    self.title.setAttribute("class", "title");
+                }
+                Array.prototype.slice.call(self.listElements).forEach(function (x) {
+                    x.classList.remove("active");
+                });
+                if (choiceText != "") {
+                    e.target.classList.add("active");
+                }
+                self.privateSendChange();
+                if (!self.userOptions.stayOpen) {
+                    docListener();
+                }
+            } else {
+                let wasActive = false;
+                if (className) {
+                    wasActive = className.indexOf("active") != -1;
+                }
+                if (wasActive) {
+                    e.target.classList.remove("active");
+                } else {
+                    e.target.classList.add("active");
+                }
+                if (e.target.hasAttribute("data-parent")) {
+                    self.checkUncheckFromChild(e.target);
+                }
+
+                let selectedTexts = ""
+                let sep = "";
+                let nrActives = 0;
+                let nrAll = 0;
+                for (let i = 0; i < self.options.length; i++) {
+                    nrAll++;
+                    if (self.options[i].value == choiceValue) {
+                        self.options[i].selected = !wasActive;
+                    }
+                    if (self.options[i].selected) {
+                        nrActives++;
+                        selectedTexts += sep + self.options[i].textContent;
+                        sep = ",";
+                    }
+                }
+                if (nrAll == nrActives) {
+                    let wordForAll = self.userOptions.translations.all || "all";
+                    selectedTexts = wordForAll;
+                } else if (self.multipleSize != -1) {
+                    if (nrActives > self.multipleSize) {
+                        let wordForItems = self.userOptions.translations.items || "items"
+                        selectedTexts = nrActives + " " + wordForItems;
+                    }
+                }
+                self.title.textContent = selectedTexts;
+                self.checkSelectMax(nrActives);
+                self.checkUncheckAll();
+                self.privateSendChange();
+            }
+            e.preventDefault();
+            e.stopPropagation();
+            if (self.userOptions.placeHolder != "" && self.title.textContent == "") {
+                self.title.textContent = self.userOptions.placeHolder;
+            }
+        });
+        function docListener() {
+            document.removeEventListener("click", docListener);
+            self.drop.style.visibility = "hidden";
+            if (self.search) {
+                self.inputBox.value = "";
+                Array.prototype.slice.call(self.listElements).forEach(function (x) {
+                    x.classList.remove("hidden-search");
+                });
+            }
+        }
+    }
+    this.init();
+    this.checkUncheckAll();
+}
+
+vanillaSelectBox.prototype.buildSelect = function (data) {
+    let self = this;
+    if(data == null || data.length < 1) return;
+    if(!self.isOptgroups){
+        self.isOptgroups = data[0].parent != undefined && data[0].parent != "";
+    }
+  
+    if(self.isOptgroups){
+        let groups = {};
+        data = data.filter(function(x){
+            return x.parent != undefined && x.parent != "";
+        });
+    
+        data.forEach(function (x) {
+            if(!groups[x.parent]){
+                groups[x.parent] = true;
+            }
+
+        });
+        for (let group in groups) {
+            let anOptgroup = document.createElement("optgroup");
+            anOptgroup.setAttribute("label", group);
+            
+            options = data.filter(function(x){
+                return x.parent == group;
+            });
+            options.forEach(function (x) {
+                let anOption = document.createElement("option");
+                anOption.value = x.value;
+                anOption.text = x.text;
+                if(x.selected){
+                    anOption.setAttribute("selected",true)
+                }
+                anOptgroup.appendChild(anOption);
+            });
+            self.root.appendChild(anOptgroup);
+        }
+    }else{
+        data.forEach(function (x) {
+            let anOption = document.createElement("option");
+            anOption.value = x.value;
+            anOption.text = x.text;
+            if(x.selected){
+                anOption.setAttribute("selected",true)
+            }
+            self.root.appendChild(anOption);
+        });
+    }
+}
+
+vanillaSelectBox.prototype.remoteSearchIntegrate = function (data) {
+    let self = this;
+
+    if (data == null || data.length == 0) {
+        let dataChecked = self.optionsCheckedToData();
+        if(dataChecked)
+            data = dataChecked.slice(0);
+        self.remoteSearchIntegrateIt(data);
+    } else {
+        let dataChecked = self.optionsCheckedToData();
+        if (dataChecked.length > 0){
+            for (var i = data.length - 1; i >= 0; i--) {
+                if(dataChecked.indexOf(data[i].id) !=-1){
+                    data.slice(i,1);
+                }
+            }
+        }
+        data = data.concat(dataChecked);
+
+        self.remoteSearchIntegrateIt(data);
+    }
+}
+
+vanillaSelectBox.prototype.optionsCheckedToData = function () {
+    let self = this;
+    let dataChecked = [];
+    let treeOptions = self.ul.querySelectorAll("li.active:not(.grouped-option)");
+    let keepParents = {};
+        if (treeOptions) {
+            Array.prototype.slice.call(treeOptions).forEach(function (x) {
+                let oneData = {"value":x.getAttribute("data-value"),"text":x.getAttribute("data-text"),"selected":true};
+                if(oneData.value !== "all"){
+                    if(self.isOptgroups){
+                        let parentId = x.getAttribute("data-parent");
+                        if(keepParents[parentId]!=undefined){
+                            oneData.parent = keepParents[parentId];
+                        }else{
+                            let parentPtr = self.ul.querySelector("#"+parentId);
+                            let parentName = parentPtr.getAttribute("data-text");
+                            keepParents[parentId] = parentName;
+                            oneData.parent = parentName;
+                        }
+                    }
+                    dataChecked.push(oneData);
+                }
+
+            });
+        }
+        return dataChecked;
+}
+
+vanillaSelectBox.prototype.removeOptionsNotChecked = function (data) {
+    let self = this;
+    let minimumSize = self.onInitSize;
+    let newSearchSize = data == null ? 0 : data.length;
+    let presentSize = self.root.length;
+    if (presentSize + newSearchSize > minimumSize) {
+        let maxToRemove = presentSize + newSearchSize - minimumSize - 1;
+        let removed = 0;
+        for (var i = self.root.length - 1; i >= 0; i--) {
+            if (self.root.options[i].selected == false) {
+                if (removed <= maxToRemove) {
+                    removed++;
+                    self.root.remove(i);
+                }
+            }
+        }
+    }
+}
+
+vanillaSelectBox.prototype.remoteSearchIntegrateIt = function (data) {
+    let self = this;
+    if (data == null || data.length == 0) return;
+    while(self.root.firstChild)
+    self.root.removeChild(self.root.firstChild);
+    
+    self.buildSelect(data);
+    self.reloadTree();
+}
+
+vanillaSelectBox.prototype.reloadTree = function () {
+    let self = this;
+    let lis = self.ul.querySelectorAll("li");
+    if (lis != null) {
+        for (var i = lis.length - 1; i >= 0; i--) {
+            if (lis[i].getAttribute('data-value') !== 'all') {
+                self.ul.removeChild(lis[i]);
+            }
+        }
+    }
+
+    let selectedTexts = ""
+    let sep = "";
+    let nrActives = 0;
+    let nrAll = 0;
+
+    if (self.isOptgroups) {
+        if (document.querySelector(self.domSelector + ' optgroup') !== null) {
+            self.options = document.querySelectorAll(this.domSelector + " option");
+            let groups = document.querySelectorAll(this.domSelector + ' optgroup');
+            Array.prototype.slice.call(groups).forEach(function (group) {
+                let groupOptions = group.querySelectorAll('option');
+                let li = document.createElement("li");
+                let span = document.createElement("span");
+                let iCheck = document.createElement("i");
+                let labelElement = document.createElement("b");
+                let dataWay = group.getAttribute("data-way");
+                if (!dataWay) dataWay = "closed";
+                if (!dataWay || (dataWay !== "closed" && dataWay !== "open")) dataWay = "closed";
+                li.appendChild(span);
+                li.appendChild(iCheck);
+                self.ul.appendChild(li);
+                li.classList.add('grouped-option');
+                li.classList.add(dataWay);
+                self.currentOptgroup++;
+                let optId = self.rootToken + "-opt-" + self.currentOptgroup;
+                li.id = optId;
+                li.appendChild(labelElement);
+                labelElement.appendChild(document.createTextNode(group.label));
+                li.setAttribute("data-text", group.label);
+                self.ul.appendChild(li);
+
+                Array.prototype.slice.call(groupOptions).forEach(function (x) {
+                    let text = x.textContent;
+                    let value = x.value;
+                    let classes = x.getAttribute("class");
+                    if (classes) {
+                        classes = classes.split(" ");
+                    }
+                    else {
+                        classes = [];
+                    }
+                    classes.push(dataWay);
+                    let li = document.createElement("li");
+                    let isSelected = x.hasAttribute("selected");
+                    self.ul.appendChild(li);
+                    li.setAttribute("data-value", value);
+                    li.setAttribute("data-text", text);
+                    li.setAttribute("data-parent", optId);
+                    if (classes.length != 0) {
+                        classes.forEach(function (x) {
+                            li.classList.add(x);
+                        });
+                    }
+                    if (isSelected) {
+                        nrActives++;
+                        selectedTexts += sep + text;
+                        sep = ",";
+                        li.classList.add("active");
+                        if (!self.isMultiple) {
+                            self.title.textContent = text;
+                            if (classes.length != 0) {
+                                classes.forEach(function (x) {
+                                    self.title.classList.add(x);
+                                });
+                            }
+                        }
+                    }
+                    li.appendChild(document.createTextNode(text));
+                })
+            })
+        }
+        self.listElements = this.drop.querySelectorAll("li:not(.grouped-option)");
+    } else {
+
+        self.options = self.root.querySelectorAll("option");
+        Array.prototype.slice.call(self.options).forEach(function (x) {
+            let text = x.textContent;
+            let value = x.value;
+            if (value != "all") {
+                let originalAttrs;
+                if (x.hasAttributes()) {
+                    originalAttrs = Array.prototype.slice.call(x.attributes)
+                        .filter(function (a) {
+                            return self.forbidenAttributes.indexOf(a.name) === -1
+                        });
+                }
+                let classes = x.getAttribute("class");
+                if (classes) {
+                    classes = classes
+                        .split(" ")
+                        .filter(function (c) {
+                            return self.forbidenClasses.indexOf(c) === -1
+                        });
+                } else {
+                    classes = [];
+                }
+                let li = document.createElement("li");
+                let isSelected = x.hasAttribute("selected");
+
+                let isDisabled = x.disabled;
+
+                self.ul.appendChild(li);
+                li.setAttribute("data-value", value);
+                li.setAttribute("data-text", text);
+
+                if (originalAttrs !== undefined) {
+                    originalAttrs.forEach(function (a) {
+                        li.setAttribute(a.name, a.value);
+                    });
+                }
+
+                classes.forEach(function (x) {
+                    li.classList.add(x);
+                });
+
+                if (self.maxOptionWidth < Infinity) {
+                    li.classList.add("short");
+                    li.style.maxWidth = self.maxOptionWidth + "px";
+                }
+
+                if (isSelected) {
+                    nrActives++;
+                    selectedTexts += sep + text;
+                    sep = ",";
+                    li.classList.add("active");
+                    if (!self.isMultiple) {
+                        self.title.textContent = text;
+                        if (classes.length != 0) {
+                            classes.forEach(function (x) {
+                                self.title.classList.add(x);
+                            });
+                        }
+                    }
+                }
+                if (isDisabled) {
+                    li.classList.add("disabled");
+                }
+                li.appendChild(document.createTextNode(" " + text));
+            }
+        });
+    }
+
+}
+
+vanillaSelectBox.prototype.disableItems = function (values) {
+    let self = this;
+    let foundValues = [];
+    if (vanillaSelectBox_type(values) == "string") {
+        values = values.split(",");
+    }
+
+    if (vanillaSelectBox_type(values) == "array") {
+        Array.prototype.slice.call(self.options).forEach(function (x) {
+            if (values.indexOf(x.value) != -1) {
+                foundValues.push(x.value);
+                x.setAttribute("disabled", "");
+            }
+        });
+    }
+    Array.prototype.slice.call(self.listElements).forEach(function (x) {
+        let val = x.getAttribute("data-value");
+        if (foundValues.indexOf(val) != -1) {
+            x.classList.add("disabled");
+        }
+    });
+}
+
+vanillaSelectBox.prototype.enableItems = function (values) {
+    let self = this;
+    let foundValues = [];
+    if (vanillaSelectBox_type(values) == "string") {
+        values = values.split(",");
+    }
+
+    if (vanillaSelectBox_type(values) == "array") {
+        Array.prototype.slice.call(self.options).forEach(function (x) {
+            if (values.indexOf(x.value) != -1) {
+                foundValues.push(x.value);
+                x.removeAttribute("disabled");
+            }
+        });
+    }
+
+    Array.prototype.slice.call(self.listElements).forEach(function (x) {
+        if (foundValues.indexOf(x.getAttribute("data-value")) != -1) {
+            x.classList.remove("disabled");
+        }
+    });
+}
+
+vanillaSelectBox.prototype.checkSelectMax = function (nrActives) {
+    let self = this;
+    if (self.maxSelect == Infinity || !self.isMultiple) return;
+    if (self.maxSelect <= nrActives) {
+        Array.prototype.slice.call(self.listElements).forEach(function (x) {
+            if (x.hasAttribute('data-value')) {
+                if (!x.classList.contains('disabled') && !x.classList.contains('active')) {
+                    x.classList.add("overflow");
+                }
+            }
+        });
+    } else {
+        Array.prototype.slice.call(self.listElements).forEach(function (x) {
+            if (x.classList.contains('overflow')) {
+                x.classList.remove("overflow");
+            }
+        });
+    }
+}
+
+vanillaSelectBox.prototype.checkUncheckFromChild = function (liClicked) {
+    let self = this;
+    let parentId = liClicked.getAttribute('data-parent');
+    let parentLi = document.getElementById(parentId);
+    if (!self.isMultiple) return;
+    let listElements = self.drop.querySelectorAll("li");
+    let childrenElements = Array.prototype.slice.call(listElements).filter(function (el) {
+        return el.hasAttribute("data-parent") && el.getAttribute('data-parent') == parentId  && !el.classList.contains('hidden-search') ;
+    });
+    let nrChecked = 0;
+    let nrCheckable = childrenElements.length;
+    if (nrCheckable == 0) return;
+    childrenElements.forEach(function (el) {
+        if (el.classList.contains('active')) nrChecked++;
+    });
+    if (nrChecked === nrCheckable || nrChecked === 0) {
+        if (nrChecked === 0) {
+            parentLi.classList.remove("checked");
+        } else {
+            parentLi.classList.add("checked");
+        }
+    } else {
+        parentLi.classList.remove("checked");
+    }
+}
+
+vanillaSelectBox.prototype.checkUncheckFromParent = function (liClicked) {
+    let self = this;
+    let parentId = liClicked.id;
+    if (!self.isMultiple) return;
+    let listElements = self.drop.querySelectorAll("li");
+    let childrenElements = Array.prototype.slice.call(listElements).filter(function (el) {
+        return el.hasAttribute("data-parent") && el.getAttribute('data-parent') == parentId && !el.classList.contains('hidden-search');
+    });
+    let nrChecked = 0;
+    let nrCheckable = childrenElements.length;
+    if (nrCheckable == 0) return;
+    childrenElements.forEach(function (el) {
+        if (el.classList.contains('active')) nrChecked++;
+    });
+    if (nrChecked === nrCheckable || nrChecked === 0) {
+        //check all or uncheckAll : just do the opposite
+        childrenElements.forEach(function (el) {
+            var event = document.createEvent('HTMLEvents');
+            event.initEvent('click', true, false);
+            el.dispatchEvent(event);
+        });
+        if (nrChecked === 0) {
+            liClicked.classList.add("checked");
+        } else {
+            liClicked.classList.remove("checked");
+        }
+    } else {
+        //check all
+        liClicked.classList.remove("checked");
+        childrenElements.forEach(function (el) {
+            if (!el.classList.contains('active')) {
+                var event = document.createEvent('HTMLEvents');
+                event.initEvent('click', true, false);
+                el.dispatchEvent(event);
+            }
+        });
+    }
+}
+
+vanillaSelectBox.prototype.checkUncheckAll = function () {
+    let self = this;
+    if (!self.isMultiple) return;
+    let nrChecked = 0;
+    let nrCheckable = 0;
+    let checkAllElement = null;
+    if (self.listElements == null) return;
+    Array.prototype.slice.call(self.listElements).forEach(function (x) {
+        if (x.hasAttribute('data-value')) {
+            if (x.getAttribute('data-value') === 'all') {
+                checkAllElement = x;
+            }
+            if (x.getAttribute('data-value') !== 'all'
+                && !x.classList.contains('hidden-search')
+                && !x.classList.contains('disabled')) {
+                nrCheckable++;
+                nrChecked += x.classList.contains('active');
+            }
+        }
+    });
+
+    if (checkAllElement) {
+        if (nrChecked === nrCheckable) {
+            // check the checkAll checkbox
+            checkAllElement.classList.add("active");
+            checkAllElement.innerText = self.userOptions.translations.clearAll;
+            checkAllElement.setAttribute('data-selected', 'true')
+        } else if (nrChecked === 0) {
+            // uncheck the checkAll checkbox
+            self.title.textContent = self.userOptions.placeHolder;
+            checkAllElement.classList.remove("active");
+            checkAllElement.innerText = self.userOptions.translations.selectAll;
+            checkAllElement.setAttribute('data-selected', 'false')
+        }
+    }
+}
+
+vanillaSelectBox.prototype.setValue = function (values) {
+    let self = this;
+    let listElements = self.drop.querySelectorAll("li");
+
+    if (values == null || values == undefined || values == "") {
+        self.empty();
+    } else {
+        if (self.isMultiple) {
+            if (vanillaSelectBox_type(values) == "string") {
+                if (values === "all") {
+                    values = [];
+                    Array.prototype.slice.call(listElements).forEach(function (x) {
+                        if (x.hasAttribute('data-value')) {
+                            let value = x.getAttribute('data-value');
+                            if (value !== 'all') {
+                                if (!x.classList.contains('hidden-search') && !x.classList.contains('disabled')) {
+                                    values.push(x.getAttribute('data-value'));
+                                }
+                                // already checked (but hidden by search)
+                                if (x.classList.contains('active')) {
+                                    if (x.classList.contains('hidden-search') || x.classList.contains('disabled')) {
+                                        values.push(value);
+                                    }
+                                }
+                            }
+                        } else if (x.classList.contains('grouped-option')) {
+                            x.classList.add("checked");
+                        }
+                    });
+                } else if (values === "none") {
+                    values = [];
+                    Array.prototype.slice.call(listElements).forEach(function (x) {
+                        if (x.hasAttribute('data-value')) {
+                            let value = x.getAttribute('data-value');
+                            if (value !== 'all') {
+                                if (x.classList.contains('active')) {
+                                    if (x.classList.contains('hidden-search') || x.classList.contains('disabled')) {
+                                        values.push(value);
+                                    }
+                                }
+                            }
+                        } else if (x.classList.contains('grouped-option')) {
+                            x.classList.remove("checked");
+                        }
+                    });
+                } else {
+                    values = values.split(",");
+                }
+            }
+            let foundValues = [];
+            if (vanillaSelectBox_type(values) == "array") {
+                Array.prototype.slice.call(self.options).forEach(function (x) {
+                    if (values.indexOf(x.value) !== -1) {
+                        x.selected = true;
+                        foundValues.push(x.value);
+                    } else {
+                        x.selected = false;
+                    }
+                });
+                let selectedTexts = ""
+                let sep = "";
+                let nrActives = 0;
+                let nrAll = 0;
+                Array.prototype.slice.call(listElements).forEach(function (x) {
+                    nrAll++;
+                    if (foundValues.indexOf(x.getAttribute("data-value")) != -1) {
+                        x.classList.add("active");
+                        nrActives++;
+                        selectedTexts += sep + x.getAttribute("data-text");
+                        sep = ",";
+                    } else {
+                        x.classList.remove("active");
+                    }
+                });
+                if (nrAll == nrActives) {
+                    let wordForAll = self.userOptions.translations.all || "all";
+                    selectedTexts = wordForAll;
+                } else if (self.multipleSize != -1) {
+                    if (nrActives > self.multipleSize) {
+                        let wordForItems = self.userOptions.translations.items || "items"
+                        selectedTexts = nrActives + " " + wordForItems;
+                    }
+                }
+                self.title.textContent = selectedTexts;
+                self.privateSendChange();
+            }
+            self.checkUncheckAll();
+        } else {
+            let found = false;
+            let text = "";
+            let classNames = ""
+            Array.prototype.slice.call(listElements).forEach(function (x) {
+                if (x.getAttribute("data-value") == values) {
+                    x.classList.add("active");
+                    found = true;
+                    text = x.getAttribute("data-text")
+                } else {
+                    x.classList.remove("active");
+                }
+            });
+            Array.prototype.slice.call(self.options).forEach(function (x) {
+                if (x.value == values) {
+                    x.selected = true;
+                    className = x.getAttribute("class");
+                    if (!className) className = "";
+                } else {
+                    x.selected = false;
+                }
+            });
+            if (found) {
+                self.title.textContent = text;
+                if (self.userOptions.placeHolder != "" && self.title.textContent == "") {
+                    self.title.textContent = self.userOptions.placeHolder;
+                }
+                if (className != "") {
+                    self.title.setAttribute("class", className + " title");
+                } else {
+                    self.title.setAttribute("class", "title");
+                }
+            }
+        }
+    }
+}
+
+vanillaSelectBox.prototype.privateSendChange = function () {
+    let event = document.createEvent('HTMLEvents');
+    event.initEvent('change', true, false);
+    this.root.dispatchEvent(event);
+}
+
+vanillaSelectBox.prototype.empty = function () {
+    Array.prototype.slice.call(this.listElements).forEach(function (x) {
+        x.classList.remove("active");
+    });
+    Array.prototype.slice.call(this.options).forEach(function (x) {
+        x.selected = false;
+    });
+    this.title.textContent = "";
+    if (this.userOptions.placeHolder != "" && this.title.textContent == "") {
+        this.title.textContent = this.userOptions.placeHolder;
+    }
+    this.checkUncheckAll();
+    this.privateSendChange();
+}
+
+vanillaSelectBox.prototype.destroy = function () {
+    let already = document.getElementById("btn-group-" + this.rootToken);
+    if (already) {
+        VSBoxCounter.remove(this.instanceOffset);
+        already.remove();
+        this.root.style.display = "inline-block";
+    }
+}
+vanillaSelectBox.prototype.disable = function () {
+    let already = document.getElementById("btn-group-" + this.rootToken);
+    if (already) {
+        button = already.querySelector("button")
+        if (button) button.classList.add("disabled");
+        this.isDisabled = true;
+    }
+}
+vanillaSelectBox.prototype.enable = function () {
+    let already = document.getElementById("btn-group-" + this.rootToken);
+    if (already) {
+        button = already.querySelector("button")
+        if (button) button.classList.remove("disabled");
+        this.isDisabled = false;
+    }
+}
+
+vanillaSelectBox.prototype.showOptions = function () {
+    console.log(this.userOptions);
+}
+// Polyfills for IE
+if (!('remove' in Element.prototype)) {
+    Element.prototype.remove = function () {
+        if (this.parentNode) {
+            this.parentNode.removeChild(this);
+        }
+    };
+}
+
+function vanillaSelectBox_type(target) {
+    const computedType = Object.prototype.toString.call(target);
+    const stripped = computedType.replace("[object ", "").replace("]", "");
+    const lowercased = stripped.toLowerCase();
+    return lowercased;
+}
+
+export { vanillaSelectBox }