Commit 970ff258 authored by Pithon Kabiro's avatar Pithon Kabiro
Browse files

Use third-party vanilla JS library for drop-downs

parent d3454541
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<meta name="description" content="" /> <meta name="description" content="" />
<meta name="author" content="" /> <meta name="author" content="" />
<title>Dashboard - iCity Bosch</title> <title>Dashboard - iCity Bosch</title>
<link href="css/styles.css" rel="stylesheet" /> <link href="css/thirdparty/styles.css" rel="stylesheet" />
<link <link
href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css"
rel="stylesheet" rel="stylesheet"
...@@ -53,16 +53,17 @@ ...@@ -53,16 +53,17 @@
<!-- Bootstrap dashboard template --> <!-- Bootstrap dashboard template -->
<script <script
defer
src="https://code.jquery.com/jquery-3.5.1.slim.min.js" src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
crossorigin="anonymous" crossorigin="anonymous"
></script> ></script>
<script <script
defer
src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"
crossorigin="anonymous" crossorigin="anonymous"
></script> ></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 --> Custom JS -->
...@@ -133,7 +134,7 @@ ...@@ -133,7 +134,7 @@
<div id="drop-down--bldg-parent"> <div id="drop-down--bldg-parent">
<span><strong>Building</strong></span> <span><strong>Building</strong></span>
<div class="nowrap"> <div class="nowrap">
<select id="drop-down--bldg"></select> <select id="drop-down--bldg" multiple></select>
</div> </div>
</div> </div>
<br /> <br />
...@@ -155,7 +156,6 @@ ...@@ -155,7 +156,6 @@
<span><strong>Chart type</strong></span> <span><strong>Chart type</strong></span>
<div class="nowrap"> <div class="nowrap">
<select id="drop-down--chart-type"> <select id="drop-down--chart-type">
<option>--Select--</option>
<option>Line</option> <option>Line</option>
<option>Heatmap</option> <option>Heatmap</option>
</select> </select>
......
.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;
}
...@@ -27,8 +27,10 @@ import { ...@@ -27,8 +27,10 @@ import {
hideLoadingSpinner, hideLoadingSpinner,
} from "./src_modules/loadingIndicator.mjs"; } from "./src_modules/loadingIndicator.mjs";
import { vanillaSelectBox } from "./thirdparty/vanillaSelectBox.mjs";
const buildingsAvailableSensorsArr = [ const buildingsAvailableSensorsArr = [
["--Select--", "", ""], // ["--Select--", "", ""],
["Bau 101", "Vorlauftemperatur", "15 min"], ["Bau 101", "Vorlauftemperatur", "15 min"],
["Bau 101", "Vorlauftemperatur", "60 min"], ["Bau 101", "Vorlauftemperatur", "60 min"],
...@@ -132,27 +134,42 @@ const makeDropDown = function (dataArr, filtersAsArray, targetElement) { ...@@ -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 * Use the `makeDropDown` function to create the first two levels of the linked drop down lists
* @returns {undefined} * @returns {undefined}
*/ */
const applyDropDown = function () { const applyDropDownLevelOneTwo = function () {
const selectLevel1Value = document.querySelector("#drop-down--bldg").value; 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); 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 * Use the `makeDropDown` function to create the third level of the linked drop down lists
* @returns {undefined} * @returns {undefined}
*/ */
const applyDropDown2 = function () { const applyDropDownLevelThree = function () {
const selectLevel1Value = document.querySelector("#drop-down--bldg").value; const selectLevel1Value = document.querySelector("#drop-down--bldg").value;
const selectLevel2Value = document.querySelector("#drop-down--sensor").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( makeDropDown(
buildingsAvailableSensorsArr, buildingsAvailableSensorsArr,
[selectLevel1Value, selectLevel2Value], [selectLevel1Value, selectLevel2Value],
selectLevel3 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 () { ...@@ -160,21 +177,53 @@ const applyDropDown2 = function () {
* @returns {undefined} * @returns {undefined}
*/ */
const populateFirstLevelDropDown = function () { 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); 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 document
.querySelector("#drop-down--bldg") .querySelector("#drop-down--bldg")
.addEventListener("change", applyDropDown); .addEventListener("change", applyDropDownLevelOneTwo);
document document
.querySelector("#drop-down--sensor") .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 document.addEventListener("DOMContentLoaded", afterDocumentLoads);
populateFirstLevelDropDown();
applyDropDown();
/** /**
* Get the values from the currently selected options in the linked drop dpwn lists * Get the values from the currently selected options in the linked drop dpwn lists
......
"use strict"; "use strict";
import { checkForAndDeleteUniqueObservationsFromLargerArray } from "./chartHelpers.mjs";
import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./fetchData.mjs"; import { getMetadataPlusObservationsFromSingleOrMultipleDatastreams } from "./fetchData.mjs";
import { extractPhenomenonNameFromDatastreamName } from "./fetchedDataProcessing.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) * 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 {String} baseUrl Base URL of the STA server
* @param {Object} urlParams The URL parameters to be sent together with the GET request * @param {Object} urlParams The URL parameters to be sent together with the GET request
* @param {String} buildingId The building ID as a string * @param {String} buildingId The building ID as a string
...@@ -40,21 +43,47 @@ export const calculateVorlaufMinusRuecklaufTemperature = async function ( ...@@ -40,21 +43,47 @@ export const calculateVorlaufMinusRuecklaufTemperature = async function (
[metadataVorlauf, metadataRuecklauf], [metadataVorlauf, metadataRuecklauf],
] = observationsPlusMetadata; ] = 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 // Extract the temperature values
const vorlaufTemperatureValues = vorlaufTemperatureObsArr.map( const vorlaufTemperatureValues = vorlaufTemperatureObsFinalArr.map(
(vlTempObs) => vlTempObs[1] (vlTempObs) => vlTempObs[1]
); );
const ruecklaufTemperatureValues = ruecklaufTemperatureObsArr.map( const ruecklaufTemperatureValues = ruecklaufTemperatureObsFinalArr.map(
(rlTempObs) => rlTempObs[1] (rlTempObs) => rlTempObs[1]
); );
// The arrays have equal length, we need only use one of them for looping // The arrays have equal length, we need only use one of them for looping
// Resulting array contains the following pairs (timestamp + dT) // Resulting array contains the following pairs (timestamp + dT)
const vorlaufMinusRuecklaufTemperatureObs = vorlaufTemperatureObsArr.map( const vorlaufMinusRuecklaufTemperatureObs = vorlaufTemperatureObsArr.map(
(vlTempObs, i) => [ (vlTempObs, i) => {
vlTempObs[0], // timestamp // Use timestamp from VL, since is equal to that of RL
vorlaufTemperatureValues[i] - ruecklaufTemperatureValues[i], 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` // From Vorlauf metadata, extract `name` and `unitOfMeasurement`
......
...@@ -8,6 +8,173 @@ const chartExportOptions = { ...@@ -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 * 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 * @param {String} hexCode Input hex color code
...@@ -142,6 +309,7 @@ const removeTransparencyFromColor = function (rgbaColor) { ...@@ -142,6 +309,7 @@ const removeTransparencyFromColor = function (rgbaColor) {
export { export {
chartExportOptions, chartExportOptions,
checkForAndDeleteUniqueObservationsFromLargerArray,
createCombinedTextDelimitedByAmpersand, createCombinedTextDelimitedByAmpersand,
createCombinedTextDelimitedByComma, createCombinedTextDelimitedByComma,
createFullTitleForLineOrColumnChart, createFullTitleForLineOrColumnChart,
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import { import {
chartExportOptions, chartExportOptions,
checkForAndDeleteUniqueObservationsFromLargerArray,
convertHexColorToRGBColor, convertHexColorToRGBColor,
createCombinedTextDelimitedByAmpersand, createCombinedTextDelimitedByAmpersand,
createCombinedTextDelimitedByComma, createCombinedTextDelimitedByComma,
...@@ -9,173 +10,6 @@ import { ...@@ -9,173 +10,6 @@ import {
removeTransparencyFromColor, removeTransparencyFromColor,
} from "./chartHelpers.mjs"; } 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 * Extracts and combines observation values from two input observation arrays of equal length
* @param {Array} obsArrayOne First set of N observations (timestamp + value) * @param {Array} obsArrayOne First set of N observations (timestamp + value)
......
/*
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 }
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment