Source: map/filters/ui/TimeFilter.js

  1. import { FilterUi } from "./FilterUi.js";
  2. import { SliderHeader } from "../../../ui/slider/SliderHeader.js";
  3. import { TimeSlider } from "../../../ui/slider/TimeSlider.js";
  4. import { FilterSettings } from "../FilterSettings.js";
  5. import "./TimeFilter.css";
  6. export { TimeFilter };
  7. /**
  8. * A class that defines the Ui for a Slider-based TimeFilter
  9. *
  10. * @author rhess <robin.hess@awi.de>
  11. * @memberof vef.map.filters.ui
  12. */
  13. class TimeFilter extends FilterUi {
  14. /**
  15. * @param {HTMLElement | string} target
  16. * @param {object} options filter specific options
  17. * @param {LayerManager} layers Used for included/excluded layers
  18. */
  19. constructor(target, options, layers) {
  20. // apply default options
  21. options = Object.assign({
  22. title: "Time Filter",
  23. column: "", // automatically identify time column if not defined
  24. schema: "ymd",
  25. allowMultipleSliderMode: true,
  26. activeSliders: "",
  27. operators: [
  28. "-",
  29. "=",
  30. ">",
  31. "≥",
  32. "<",
  33. "≤"
  34. ],
  35. initialOperator: "-",
  36. initialBegin: "1980-01-01T00:00:00Z",
  37. initialEnd: "2022-12-31T23:59:59Z",
  38. begin: "1980-01-01T00:00:00Z",
  39. end: "2022-12-31T23:59:59Z"
  40. }, options || {});
  41. super(target, options, layers);
  42. this.schemaRegex = new RegExp("^y(m(d)?)?$");
  43. this.sliderContainer_ = null;
  44. this.sliders_ = [];
  45. this.types_ = {
  46. "y": "YEAR",
  47. "m": "MONTH",
  48. "d": "DAY"
  49. };
  50. this.headerConfig = {
  51. // single types
  52. "YEAR": { pattern: '^([0-9]{4})$', maxLength: 4 },
  53. "MONTH": { pattern: '^(1[0-2]|0[1-9])$', maxLength: 2 },
  54. "DAY": { pattern: '^(3[0-1]|2[0-9]|1[0-9]|0[1-9])$', maxLength: 2 },
  55. // schemas
  56. "ymd": { pattern: '^([0-9]{4})(-((1[0-2]|0[1-9])(-(3[0-1]|2[0-9]|1[0-9]|0[1-9])?)?)?)?$', maxLength: 10 },
  57. "ym": { pattern: '^([0-9]{4})(-(1[0-2]|0[1-9])?)?$', maxLength: 7 },
  58. "y": { pattern: '^([0-9]{4})$', maxLength: 4 }
  59. };
  60. this.settingsClass_ = TimeFilterSettings;
  61. this.currentValue_ = null
  62. this.operator_ = options.initialOperator || options.operators[0];
  63. // validate active sliders and schema
  64. let schema = this.options_.schema ? this.options_.schema.toLowerCase() : 'ymd';
  65. let activeSliders = this.options_.activeSliders ? this.options_.activeSliders.toLowerCase() : '';
  66. if (!this.schemaRegex.test(activeSliders)) activeSliders = "";
  67. for (let i = 0; i < activeSliders.length; ++i) {
  68. if (!schema.includes(activeSliders[i])) {
  69. activeSliders = "";
  70. break;
  71. }
  72. }
  73. this.options_.activeSliders = activeSliders;
  74. this.options_.schema = schema;
  75. // check if time needs to be dynamic
  76. this.options_.begin = this.setTimeDynamically(this.options_.begin);
  77. this.options_.end = this.setTimeDynamically(this.options_.end);
  78. this.options_.initialBegin = this.setTimeDynamically(this.options_.initialBegin);
  79. this.options_.initialEnd = this.setTimeDynamically(this.options_.initialEnd);
  80. // initialize element and slider
  81. this.initFilterElement_();
  82. this.initSliders_();
  83. }
  84. /**
  85. * internal method for initializing the main
  86. * HTML structure of the element
  87. *
  88. * @private
  89. */
  90. initFilterElement_() {
  91. this.setTitle(this.options_.title);
  92. const content = this.getContentContainer();
  93. content.classList.add("time-filter");
  94. this.sliderContainer_ = document.createElement("div");
  95. this.sliderContainer_.classList.add("slider-container");
  96. content.appendChild(this.sliderContainer_);
  97. content.appendChild(this.initMultipleSliderMode_());
  98. }
  99. /**
  100. * @private
  101. * @param {string} type
  102. * @returns {string} validation regex pattern and max length
  103. */
  104. getHeaderConfig_(type) {
  105. if (type == "DATE") {
  106. let schema = this.options_.schema;
  107. if (!this.schemaRegex.test(schema)) schema = "ymd";
  108. return this.headerConfig[schema];
  109. } else {
  110. return this.headerConfig[type];
  111. }
  112. }
  113. /**
  114. * @private
  115. * @param {string} type
  116. * @param {object} value
  117. * @param {TimeSlider} slider
  118. */
  119. parseHeaderValue_(type, value, slider) {
  120. let sliderValue = slider.getValue();
  121. let currentValue = sliderValue;
  122. if (slider.options_.handles == 2) {
  123. if (value.type == "begin") {
  124. currentValue = sliderValue.left;
  125. } else {
  126. currentValue = sliderValue.right;
  127. }
  128. }
  129. switch (type.toLowerCase()) {
  130. case "year":
  131. currentValue.setUTCFullYear(Number.parseInt(value.value));
  132. break;
  133. case "month":
  134. currentValue.setUTCDate(1);
  135. currentValue.setUTCMonth(Number.parseInt(value.value) - 1);
  136. break;
  137. case "day":
  138. currentValue.setUTCDate(Number.parseInt(value.value));
  139. break;
  140. default:
  141. const schema = this.options_.schema.toLowerCase();
  142. // initialize day as 1 to prevent month overflow
  143. currentValue.setUTCDate(1);
  144. // the year is always defined
  145. const year = Number.parseInt(value.value.substr(0, 4));
  146. currentValue.setUTCFullYear(year);
  147. // set default month and overwrite if it is set
  148. let month = (value.type == "begin") ? 0 : 11;
  149. if (schema.includes("m") && (value.value.length >= 7)) {
  150. month = Number.parseInt(value.value.substr(5, 2)) - 1;
  151. }
  152. currentValue.setUTCMonth(month);
  153. // set default month and overwrite if it is set
  154. const daysInMonth = new Date(year, month + 1, 0).getDate();
  155. let day = (value.type == "begin") ? 1 : daysInMonth;
  156. if (schema.includes("d") && (value.value.length >= 10)) {
  157. day = Number.parseInt(value.value.substr(8, 2));
  158. if (day > daysInMonth) day = daysInMonth;
  159. }
  160. currentValue.setUTCDate(day);
  161. break;
  162. }
  163. if (slider.options_.handles == 2) {
  164. if (value.type == "begin") {
  165. sliderValue.left = currentValue;
  166. } else {
  167. sliderValue.right = currentValue;
  168. }
  169. } else {
  170. sliderValue = currentValue;
  171. }
  172. slider.setValue(sliderValue);
  173. slider.stop_();
  174. }
  175. /**
  176. * Init a slider header and its events including
  177. * validation and autocompletion of the input
  178. *
  179. * @param {Slider} slider
  180. * @param {string} type
  181. * @param {string[]} operators
  182. * @param {boolean} isLast
  183. * @returns {SliderHeader} header
  184. */
  185. initHeader_(slider, type, operators, isLast) {
  186. const config = this.getHeaderConfig_(type);
  187. const header = new SliderHeader(null, {
  188. title: type,
  189. operators: operators,
  190. defaultOperator: ((isLast) ? this.operator_ : null),
  191. useInput: true,
  192. inputPattern: config.pattern,
  193. inputLength: config.maxLength
  194. });
  195. const validCharacters = "0123456789-";
  196. const removeInvalidCharacters = (str) => {
  197. for (let i = str.length - 1; i >= 0; --i) {
  198. if (!validCharacters.includes(str[i])) str = str.replace(str[i], "");
  199. }
  200. return str;
  201. }
  202. // validation and auto-complete minus character or end
  203. header.on("input", (e) => {
  204. let value = removeInvalidCharacters(e.value);
  205. switch (type.toLowerCase()) {
  206. case "year":
  207. if (value.includes("-")) value = str.replace("-", "");
  208. maxLength = 4;
  209. break;
  210. case "month":
  211. if (value.includes("-")) value = str.replace("-", "");
  212. maxLength = 2;
  213. break;
  214. case "day":
  215. if (value.includes("-")) value = str.replace("-", "");
  216. maxLength = 2;
  217. break;
  218. default:
  219. //toDo
  220. const schema = this.options_.schema.toLowerCase();
  221. const parts = value.split("-");
  222. if (schema.includes("m")) {
  223. if ((value.length > 4) && ((parts.length == 1) || (parts[0].length > 4))) {
  224. value = value.substring(0, 4) + "-" + value.substring(4);
  225. }
  226. }
  227. if (schema.includes("d")) {
  228. if ((value.length > 7) && (parts.length <= 2) && (value.charAt(7) != "-")) {
  229. value = value.substring(0, 7) + "-" + value.substring(7);
  230. }
  231. }
  232. break;
  233. }
  234. header.setValue(e.type, value);
  235. });
  236. // operator selection
  237. if (isLast) {
  238. header.on("select", operator => {
  239. this.operator_ = operator;
  240. this.initSliders_();
  241. this.fire("change", this);
  242. });
  243. }
  244. // apply the header value to the slider
  245. header.on("change", value => this.parseHeaderValue_(type, value, slider));
  246. // insert before slider
  247. slider.getElement().insertAdjacentElement("beforebegin", header.getElement())
  248. return header;
  249. }
  250. /**
  251. * method for creating a slider with a header
  252. * @param {string} mode
  253. * @param {boolean} isLast
  254. * @returns {object}
  255. * @private
  256. */
  257. initSingleSlider_(mode, isLast) {
  258. const options = this.options_;
  259. const operators = (isLast) ? options.operators : ["="];
  260. const type = (this.types_[mode]) ? this.types_[mode] : "DATE"
  261. const sliderOptions = {
  262. min: new Date(options.begin).getTime(),
  263. max: new Date(options.end).getTime()
  264. }
  265. const previousLeft = (this.currentValue_ && (this.currentValue_.left instanceof Date)) ? this.currentValue_.left : this.currentValue_;
  266. const previousRight = (this.currentValue_ && (this.currentValue_.right instanceof Date)) ? this.currentValue_.right : null;
  267. if (isLast && ("-" == this.operator_)) {
  268. sliderOptions.handles = 2;
  269. sliderOptions.value = {
  270. left: new Date(previousLeft || options.initialBegin || options.begin).getTime(),
  271. right: new Date(previousRight || options.initialEnd || options.end).getTime()
  272. }
  273. } else {
  274. sliderOptions.handles = 1;
  275. sliderOptions.value = new Date(previousLeft || options.initialBegin || options.begin).getTime();
  276. }
  277. const container = document.createElement("div");
  278. const slider = new TimeSlider(container, sliderOptions);
  279. const header = this.initHeader_(slider, type, operators, isLast);
  280. const dispose = () => {
  281. header.dispose();
  282. slider.dispose();
  283. container.remove();
  284. };
  285. const item = {
  286. type: type,
  287. container: container,
  288. header: header,
  289. slider: slider,
  290. dispose: dispose
  291. };
  292. slider.on("change", (value) => {
  293. this.updateValues_(item);
  294. });
  295. slider.on("stop", (value) => {
  296. this.updateValues_(item);
  297. this.fire("change", this);
  298. });
  299. this.initArrowControls_(item);
  300. return item;
  301. }
  302. initArrowControls_(item) {
  303. const listener = (type, increment, incValue) => {
  304. const values = this.getValueForSchema_(true, item.slider.getValue());
  305. let value = values[type];
  306. let year = Number.parseInt(value.substring(0, 4));
  307. let month = Number.parseInt(value.substring(5, 7));
  308. let day = Number.parseInt(value.substring(8, 10));
  309. switch (item.type.toLowerCase()) {
  310. case "year":
  311. value = year + ((increment) ? 1 : -1);
  312. break;
  313. case "month":
  314. value = month + ((increment) ? 1 : -1);
  315. break;
  316. case "day":
  317. value = day + ((increment) ? 1 : -1);
  318. break;
  319. default:
  320. const schema = this.options_.schema.toLowerCase();
  321. if (schema.includes("d") && incValue === "d") {
  322. day += (increment) ? 1 : -1;
  323. }
  324. if (schema.includes("m") && incValue === "m") {
  325. month += (increment) ? 1 : -1;
  326. }
  327. if (schema.includes("y") && incValue === "y") {
  328. year += (increment) ? 1 : -1;
  329. }
  330. if (schema.includes("ymd") && incValue === "e") {
  331. day = this.options_.end.substring(8, 10);
  332. month = this.options_.end.substring(5, 7);
  333. year = this.options_.end.substring(0, 4);
  334. }
  335. if (schema.includes("ymd") && incValue === "p") {
  336. day = this.options_.begin.substring(8, 10);
  337. month = this.options_.begin.substring(5, 7);
  338. year = this.options_.begin.substring(0, 4);
  339. }
  340. value = new Date(Date.UTC(year, month - 1, day)).toISOString().substring(0, 10) + value.substring(10);
  341. break;
  342. }
  343. this.parseHeaderValue_(item.type, {
  344. type: type,
  345. value: value
  346. }, item.slider);
  347. }
  348. item.header.on("arrow_down", inputName => listener(inputName, false, "d"));
  349. item.header.on("arrow_up", inputName => listener(inputName, true, "d"));
  350. item.header.on("arrow_down_shift", inputName => listener(inputName, false, "m"));
  351. item.header.on("arrow_up_shift", inputName => listener(inputName, true, "m"));
  352. item.header.on("arrow_down_shift_ctrl", inputName => listener(inputName, false, "y"));
  353. item.header.on("arrow_up_shift_ctrl", inputName => listener(inputName, true, "y"));
  354. item.header.on("pos1", inputName => listener(inputName, true, "p"));
  355. item.header.on("end", inputName => listener(inputName, true, "e"));
  356. item.slider.on("arrow_left", handleName => listener((handleName == "left") ? "begin" : "end", false, "d"));
  357. item.slider.on("arrow_right", handleName => listener((handleName == "left") ? "begin" : "end", true, "d"));
  358. item.slider.on("ArrowRight_Shift", handleName => listener((handleName == "left") ? "begin" : "end", true, "m"));
  359. item.slider.on("ArrowLeft_Shift", handleName => listener((handleName == "left") ? "begin" : "end", false, "m"));
  360. item.slider.on("ArrowRight_Shift_cntrl", handleName => listener((handleName == "left") ? "begin" : "end", true, "y"));
  361. item.slider.on("ArrowLeft_Shift_cntrl", handleName => listener((handleName == "left") ? "begin" : "end", false, "y"));
  362. item.slider.on("home", handleName => listener((handleName == "left") ? "begin" : "end", false, "p"));
  363. item.slider.on("end", handleName => listener((handleName == "left") ? "begin" : "end", false, "e"));
  364. }
  365. /**
  366. * method to update the slider and header values
  367. * @param {object} source slider item
  368. * @param {boolean} init sliders are initialized and not updated
  369. * @private
  370. */
  371. updateValues_(source, init) {
  372. const options = this.options_;
  373. if ((!this.currentValue_) || init) this.currentValue_ = source.slider.getValue();
  374. const totalMin = new Date(options.begin).getTime();
  375. const totalMax = new Date(options.end).getTime();
  376. const newIso = this.getValueForSchema_(true, source.slider.getValue());
  377. const prevIso = this.getValueForSchema_(true);
  378. // transform currentValue depending on slider
  379. for (let i = 0; i < this.sliders_.length; ++i) {
  380. const item = this.sliders_[i];
  381. switch (item.type) {
  382. case "DATE":
  383. this.currentValue_ = source.slider.getValue();
  384. break;
  385. case "YEAR":
  386. {
  387. let beginYear = newIso.begin.substr(0, 4);
  388. let endYear = newIso.end.substr(0, 4);
  389. if (item.slider.options_.handles == 2) {
  390. this.currentValue_ = {
  391. left: new Date(beginYear + "-01-01T00:00:00Z"),
  392. right: new Date(endYear + "-12-31T23:59:59Z")
  393. };
  394. } else {
  395. this.currentValue_ = new Date(beginYear + "-01-01T00:00:00Z");
  396. }
  397. }
  398. break;
  399. case "MONTH":
  400. {
  401. const year = newIso.begin.substr(0, 4);
  402. let beginMonth = newIso.begin.substr(5, 2);
  403. let endMonth = newIso.end.substr(5, 2);
  404. if (item != source) {
  405. beginMonth = prevIso.begin.substr(5, 2);
  406. endMonth = prevIso.end.substr(5, 2);
  407. }
  408. const daysInEndMonth = new Date(year, endMonth, 0).getDate();
  409. if (item.slider.options_.handles == 2) {
  410. this.currentValue_ = {
  411. left: new Date(year + "-" + beginMonth + "-01T00:00:00Z"),
  412. right: new Date(year + "-" + endMonth + "-" + daysInEndMonth + "T23:59:59Z")
  413. };
  414. } else {
  415. this.currentValue_ = new Date(year + "-" + beginMonth + "-01T00:00:00Z");
  416. }
  417. }
  418. break;
  419. case "DAY":
  420. {
  421. const year = newIso.begin.substr(0, 4);
  422. let month = newIso.begin.substr(5, 2);
  423. let beginDay = newIso.begin.substr(8, 2);
  424. let endDay = newIso.end.substr(8, 2);
  425. if (item != source) {
  426. beginDay = prevIso.begin.substr(8, 2);
  427. endDay = prevIso.end.substr(8, 2);
  428. if (source.type != "MONTH") month = prevIso.begin.substr(5, 2);
  429. }
  430. const daysInMonth = new Date(year, month, 0).getDate();
  431. if (parseInt(beginDay) > daysInMonth) beginDay = daysInMonth;
  432. if (parseInt(endDay) > daysInMonth) endDay = daysInMonth;
  433. if (item.slider.options_.handles == 2) {
  434. this.currentValue_ = {
  435. left: new Date(year + "-" + month + "-" + beginDay + "T00:00:00Z"),
  436. right: new Date(year + "-" + month + "-" + endDay + "T23:59:59Z")
  437. }
  438. } else {
  439. this.currentValue_ = new Date(year + "-" + month + "-" + beginDay + "T00:00:00Z");
  440. }
  441. }
  442. break;
  443. }
  444. }
  445. if (this.currentValue_ instanceof Date) {
  446. if (this.currentValue_ < totalMin) this.currentValue_ = totalMin;
  447. if (this.currentValue_ > totalMax) this.currentValue_ = totalMax;
  448. } else {
  449. if (this.currentValue_.left < totalMin) this.currentValue_.left = totalMin;
  450. if (this.currentValue_.left > totalMax) this.currentValue_.left = totalMax;
  451. if (this.currentValue_.right < totalMin) this.currentValue_.right = totalMin;
  452. if (this.currentValue_.right > totalMax) this.currentValue_.right = totalMax;
  453. }
  454. const isoValue = this.getValueForSchema_(true);
  455. const daysInMonth = new Date(isoValue.begin.substr(0, 4), isoValue.begin.substr(5, 2), 0).getDate();
  456. // update all values in headers and sliders
  457. for (let i = 0; i < this.sliders_.length; ++i) {
  458. const item = this.sliders_[i];
  459. let min = totalMin
  460. let max = totalMax
  461. switch (item.type) {
  462. case "DATE":
  463. item.header.setValues(this.getValueForSchema_());
  464. break;
  465. case "YEAR":
  466. item.header.setValues({
  467. begin: isoValue.begin.substr(0, 4),
  468. end: isoValue.end.substr(0, 4)
  469. });
  470. break;
  471. case "MONTH":
  472. min = new Date(isoValue.begin.substr(0, 5) + "01-01T00:00:00Z").getTime();
  473. max = new Date(isoValue.begin.substr(0, 5) + "12-31T23:59:59Z").getTime();
  474. item.header.setValues({
  475. begin: isoValue.begin.substr(5, 2),
  476. end: isoValue.end.substr(5, 2)
  477. });
  478. break;
  479. case "DAY":
  480. min = new Date(isoValue.begin.substr(0, 8) + "01T00:00:00Z").getTime();
  481. max = new Date(isoValue.begin.substr(0, 8) + daysInMonth + "T23:59:59Z").getTime();
  482. item.header.setValues({
  483. begin: isoValue.begin.substr(8, 2),
  484. end: isoValue.end.substr(8, 2)
  485. });
  486. break;
  487. }
  488. item.slider.options_.min = (min >= totalMin) ? min : totalMin;
  489. item.slider.options_.max = (max <= totalMax) ? max : totalMax;
  490. if (init || (item != source)) {
  491. if (item.slider.options_.handles == 2) {
  492. item.slider.options_.value = {
  493. left: new Date(isoValue.begin).getTime(),
  494. right: new Date(isoValue.end).getTime(),
  495. };
  496. } else {
  497. item.slider.options_.value = new Date(isoValue.begin).getTime();
  498. }
  499. item.slider.validate_();
  500. item.slider.updateHandles_();
  501. }
  502. }
  503. }
  504. /**
  505. * internal method for initializing the date slider.
  506. * the slider gets re-initialized everytime the
  507. * operator changes.
  508. *
  509. * @private
  510. */
  511. initSliders_() {
  512. // remove all sliders
  513. for (let i in this.sliders_) this.sliders_[i].dispose();
  514. this.sliders_ = [];
  515. // adjust operator
  516. if (!this.options_.operators.includes(this.operator_)) {
  517. if (this.options_.operators.length == 0) this.options_.operators = ["-"];
  518. this.operator_ = this.options_.operators[0];
  519. }
  520. const activeSliders = this.options_.activeSliders;
  521. if (activeSliders.length == 0) {
  522. const item = this.initSingleSlider_(null, true);
  523. this.sliderContainer_.appendChild(item.container);
  524. this.sliders_.push(item);
  525. } else {
  526. for (let i = 0; i < activeSliders.length; ++i) {
  527. const isLast = (activeSliders.length == (i + 1));
  528. const item = this.initSingleSlider_(activeSliders[i], isLast);
  529. this.sliderContainer_.appendChild(item.container);
  530. this.sliders_.push(item);
  531. }
  532. }
  533. this.updateValues_(this.sliders_[this.sliders_.length - 1], true);
  534. }
  535. sortSchema_(input) {
  536. input = input.toLowerCase();
  537. let output = "";
  538. for (let i in this.types_) {
  539. if (input.includes(i)) {
  540. output += i;
  541. }
  542. }
  543. return output;
  544. }
  545. initMultipleSliderMode_() {
  546. const advanced = document.createElement("div");
  547. advanced.classList.add("advanced");
  548. advanced.style.display = "none";
  549. advanced.innerHTML = `
  550. <button class="btn-close"><i class='fas fa-times'></i></button>
  551. <div class="settings"></div>
  552. `;
  553. const settingsContainer = advanced.querySelector(".settings");
  554. // toggle advanced settings
  555. this.settingsOpen_ = false;
  556. const toggleSettings = () => {
  557. const tool = this.getElement().querySelector(".sidebar-element-tool.multiple-slider-mode");
  558. if (this.settingsOpen_) {
  559. advanced.style.display = "none";
  560. tool.classList.remove("enabled");
  561. } else {
  562. advanced.style.display = "flex";
  563. tool.classList.add("enabled")
  564. }
  565. this.settingsOpen_ = !this.settingsOpen_;
  566. }
  567. const closeButton = advanced.querySelector(".btn-close");
  568. closeButton.addEventListener("click", toggleSettings);
  569. this.addTool("vef vef-filter-settings multiple-slider-mode", toggleSettings, "Advanced Settings", true);
  570. const schema = this.sortSchema_(this.options_.schema);
  571. const activeSliders = this.sortSchema_(this.options_.activeSliders);
  572. for (let i = 0; i < schema.length; ++i) {
  573. if (schema[i] in this.types_) {
  574. const settingsItem = document.createElement("div");
  575. settingsItem.classList.add("settings-item");
  576. settingsItem.dataset.type = schema[i];
  577. settingsItem.innerHTML = `
  578. <span class="fa-stack">
  579. <i class="far fa-circle fa-stack-1x"></i>
  580. <i class="fas fa-check fa-stack-1x"></i>
  581. </span>
  582. ${this.types_[schema[i]].toLowerCase()}
  583. `;
  584. settingsContainer.appendChild(settingsItem);
  585. }
  586. }
  587. const items = settingsContainer.querySelectorAll(".settings-item");
  588. for (let i = 0; i < items.length; ++i) {
  589. const item = items[i];
  590. const prev = item.previousSibling;
  591. if (activeSliders.includes(schema[i]) && (!prev || prev.classList.contains("checked"))) {
  592. item.classList.add("checked");
  593. }
  594. item.addEventListener("click", () => {
  595. if (item.classList.contains("checked")) {
  596. item.classList.remove("checked");
  597. let next = item.nextSibling;
  598. while (next) {
  599. next.classList.remove("checked");
  600. next = next.nextSibling;
  601. }
  602. } else {
  603. item.classList.add("checked");
  604. let prev = item.previousSibling;
  605. while (prev) {
  606. prev.classList.add("checked");
  607. prev = prev.previousSibling;
  608. }
  609. }
  610. let selectedSliders = "";
  611. for (let j = 0; j < items.length; ++j) {
  612. if (items[j].classList.contains("checked")) selectedSliders += items[j].dataset.type;
  613. }
  614. this.options_.activeSliders = this.sortSchema_(selectedSliders);
  615. this.initSliders_();
  616. this.fire("change", this);
  617. });
  618. }
  619. if (!this.options_.allowMultipleSliderMode) this.toggleToolVisibility("multiple-slider-mode", false);
  620. return advanced;
  621. }
  622. /**
  623. * internal method for getting the selected date values
  624. * adjusted to the time resolution defined in options.schema
  625. *
  626. * @param {boolean} iso returns full iso-datestring if true
  627. * @param {object | number} value (optional)
  628. * @returns {object} {begin, end}
  629. */
  630. getValueForSchema_(iso, value) {
  631. value = value || this.currentValue_;
  632. let begin = ((isFinite(value.left)) ? value.left : value).toISOString();
  633. let end = ((isFinite(value.right)) ? value.right : value).toISOString();
  634. let schema = this.options_.schema.toLowerCase();
  635. let activeSliders = this.options_.activeSliders.toLowerCase();
  636. if (!this.schemaRegex.test(schema)) schema = "ymd";
  637. if (!this.schemaRegex.test(activeSliders)) activeSliders = "";
  638. // "Y" -> Year is always required
  639. let validBegin = begin.substr(0, 4);
  640. let validEnd = end.substr(0, 4);
  641. if (schema.includes("m") && ((activeSliders.length == 0) || activeSliders.includes("m"))) {
  642. validBegin = begin.substr(0, 7);
  643. validEnd = end.substr(0, 7);
  644. if (schema.includes("d") && ((activeSliders.length == 0) || activeSliders.includes("d"))) {
  645. validBegin = begin.substr(0, 10);
  646. validEnd = end.substr(0, 10);
  647. if (iso) {
  648. validBegin += "T00:00:00";
  649. validEnd += "T23:59:59";
  650. }
  651. } else if (iso) {
  652. const daysInEndMonth = new Date(end.substr(0, 4), end.substr(5, 2), 0).getDate();
  653. validEnd += "-" + daysInEndMonth + "T23:59:59";
  654. validBegin += "-01T00:00:00";
  655. }
  656. } else if (iso) {
  657. validBegin += "-01-01T00:00:00";
  658. validEnd += "-12-31T23:59:59";
  659. }
  660. return {
  661. begin: validBegin,
  662. end: validEnd
  663. }
  664. }
  665. /**
  666. * Get the filter object to pass it on to the Layers.
  667. *
  668. * @override
  669. * @returns {object} filter object
  670. */
  671. getActiveFilter() {
  672. let val = this.getValueForSchema_(true);
  673. // case "bt" and "eq" and as fallback for invalid operators
  674. let values = [["bt", val.begin, val.end]];
  675. switch (this.operator_) {
  676. case "gt":
  677. case ">":
  678. values = [["gt", val.begin]];
  679. break;
  680. case "gteq":
  681. case "≥":
  682. values = [["gteq", val.begin]];
  683. break;
  684. case "lt":
  685. case "<":
  686. values = [["lt", val.begin]];
  687. break;
  688. case "lteq":
  689. case "≤":
  690. values = [["lteq", val.begin]];
  691. break;
  692. }
  693. const result = {
  694. type: "time",
  695. column: this.options_.column || "",
  696. values: values,
  697. excludedLayers: this.getExcludedLayers()
  698. };
  699. return result;
  700. }
  701. /**
  702. * Get the filter's options-object
  703. */
  704. getOptions() {
  705. const activeFilterValues = this.getActiveFilter().values[0];
  706. const operator = this.operator_;
  707. const options = Object.assign({}, this.options_);
  708. options.initialOperator = operator;
  709. // assign active values
  710. if (operator == "-") {
  711. options.initialBegin = activeFilterValues[1];
  712. options.initialEnd = activeFilterValues[2];
  713. } else {
  714. options.initialBegin = activeFilterValues[1];
  715. options.initialEnd = "";
  716. }
  717. return options;
  718. }
  719. /**
  720. * method to reload options after applying settings
  721. */
  722. reloadOptions_() {
  723. this.setTitle(this.options_.title);
  724. this.toggleToolVisibility("btn-power", this.options_.deactivatable);
  725. this.toggleToolVisibility("multiple-slider-mode", this.options_.allowMultipleSliderMode);
  726. this.initSliders_();
  727. this.fire("change", this);
  728. }
  729. setTimeDynamically(time) {
  730. let access = false;
  731. let notTime = ["today"];
  732. notTime.forEach(element => {
  733. if ((typeof time == "string") && time.includes(element)) access = true;
  734. });
  735. // return time if it is not using the today-syntax and alway assume UTC
  736. if (!access) {
  737. if(time && !time.endsWith("Z") && !time.endsWith("z")) time += "Z";
  738. return time
  739. };
  740. if (time.includes("today")) {
  741. let newDate = new Date();
  742. time = time.replace("today", "");
  743. if (time == "") return newDate.toISOString();
  744. let addOrsub = time[0];
  745. time = time.replace(addOrsub, "")
  746. if (time == "") return newDate.toISOString();
  747. let typeOftime = time[0];
  748. time = time.replace(typeOftime, "")
  749. if (time == "") return newDate.toISOString();
  750. let SkipValue = Number(time);
  751. if (addOrsub === "+") {
  752. switch (typeOftime.toLowerCase()) {
  753. case "d":
  754. newDate.setUTCDate(newDate.getUTCDate() + SkipValue);
  755. break;
  756. case "m":
  757. newDate.setUTCMonth(newDate.getUTCMonth() + SkipValue);
  758. break;
  759. case "y":
  760. newDate.setUTCFullYear(newDate.getUTCFullYear() + SkipValue);
  761. break;
  762. }
  763. } else if (addOrsub === "-") {
  764. switch (typeOftime.toLowerCase()) {
  765. case "d":
  766. newDate.setUTCDate(newDate.getUTCDate() - SkipValue);
  767. break;
  768. case "m":
  769. newDate.setUTCMonth(newDate.getUTCMonth() - SkipValue);
  770. break;
  771. case "y":
  772. newDate.setUTCFullYear(newDate.getUTCFullYear() - SkipValue);
  773. break;
  774. }
  775. }
  776. return newDate.toISOString();
  777. }
  778. return time;
  779. }
  780. setValue(value) {
  781. if (!value) return;
  782. if (!Array.isArray(value)) value = [value];
  783. if (this.sliders_.length == 1) {
  784. const slider = this.sliders_[0].slider;
  785. if (slider.options_.handles == 1) {
  786. slider.setValue(value[0]);
  787. } else if (slider.options_.handles == 2) {
  788. slider.setValue({
  789. left: value[0],
  790. right: value.length == 2 ? value[1] : value[0]
  791. });
  792. }
  793. this.fire('change', this);
  794. } else {
  795. console.warn('TimeFilter with multiple sliders are not supported.');
  796. }
  797. }
  798. }
  799. /**
  800. * Class representing the settings for the TimeFilter
  801. * @private
  802. */
  803. class TimeFilterSettings extends FilterSettings {
  804. htmlExtension = `
  805. <div><label style="width: 100%;"><span class="input-label" style="width: 80px;">Column: </span><input style="width: calc(100% - 80px);" type="text" name="column" placeholder="Let empty to auto-choose a column for each layer"/></label></div>
  806. <div><label style="width: 100%;"><span class="input-label" style="width: 80px;">Schema: </span><select style="width: calc(100% - 80px);" name="schema">
  807. <option value="y">YYYY</option>
  808. <option value="ym">YYYY-MM</option>
  809. <option value="ymd">YYYY-MM-DD</option>
  810. </select></label></div>
  811. <div><label style="width: 100%;"><span class="input-label" style="width: 80px;">Minimum: </span><input style="width: calc(100% - 80px);" type="text" name="minimum"/></label></div>
  812. <div><label style="width: 100%;"><span class="input-label" style="width: 80px;">Maximum: </span><input style="width: calc(100% - 80px);" type="text" name="maximum"/></label></div>
  813. <div>
  814. <label class="input-label" style="width: 80px;">Operators:</label>
  815. <label title="equals" style="margin-right:20px;"><input type="checkbox" name="operator-equals"/> =</label>
  816. <label title="range" style="margin-right:20px;"><input type="checkbox" name="operator-range"/> -</label>
  817. <label title="greater than" style="margin-right:20px;"><input type="checkbox" name="operator-gt"/> &gt;</label>
  818. <label title="greater thanor equal to" style="margin-right:20px;"><input type="checkbox" name="operator-gteq"/> ≥</label>
  819. <label title="less than"><input type="checkbox" name="operator-lt"/> &lt;</label>
  820. <label title="less than or equal to"><input type="checkbox" name="operator-lteq"/> ≤</label>
  821. </div>
  822. <div><label><input type="checkbox" name="allow-multiple-slider-mode"/> Show "Multiple Slider Mode" Button</label></div>
  823. `;
  824. /**
  825. * @param {FilterUi} filter
  826. */
  827. constructor(filter) {
  828. super(filter);
  829. this.query("form").insertAdjacentHTML("beforeend", this.htmlExtension);
  830. }
  831. /**
  832. * Get the currently configured filter options
  833. */
  834. getFilterOptions() {
  835. const options = super.getFilterOptions();
  836. const minimum = this.query("form input[name='minimum']");
  837. const maximum = this.query("form input[name='maximum']");
  838. if (!minimum.checkValidity() || !maximum.checkValidity()) return null;
  839. options.begin = minimum.value + "T00:00:00Z";
  840. options.end = maximum.value + "T23:59:59Z"
  841. options.column = this.query("form input[name='column']").value;
  842. options.schema = this.query("form select[name='schema']").value;
  843. options.allowMultipleSliderMode = this.query("form input[name='allow-multiple-slider-mode']").checked;
  844. const operators = [];
  845. if (this.query("form input[name='operator-equals']").checked) operators.push("=");
  846. if (this.query("form input[name='operator-range']").checked) operators.push("-");
  847. if (this.query("form input[name='operator-gt']").checked) operators.push(">");
  848. if (this.query("form input[name='operator-gteq']").checked) operators.push("≥");
  849. if (this.query("form input[name='operator-lt']").checked) operators.push("<");
  850. if (this.query("form input[name='operator-lteq']").checked) operators.push("≤");
  851. options.operators = operators;
  852. return options;
  853. }
  854. /**
  855. * Update Ui based on the filters current options
  856. */
  857. updateFilterOptions() {
  858. super.updateFilterOptions();
  859. const options = this.filter.options_;
  860. this.query("form input[name='column']").value = options.column;
  861. this.query("form input[name='allow-multiple-slider-mode']").checked = options.allowMultipleSliderMode;
  862. this.query("form select[name='schema']").value = options.schema;
  863. this.query("form input[name='minimum']").value = (options.begin.length > 9) ? options.begin.substring(0, 10) : options.end;
  864. this.query("form input[name='maximum']").value = (options.end.length > 9) ? options.end.substring(0, 10) : options.end;
  865. this.query("form input[name='operator-equals']").checked = options.operators.includes("=");
  866. this.query("form input[name='operator-range']").checked = options.operators.includes("-");
  867. this.query("form input[name='operator-gt']").checked = options.operators.includes(">");
  868. this.query("form input[name='operator-gteq']").checked = options.operators.includes("≥");
  869. this.query("form input[name='operator-lt']").checked = options.operators.includes("<");
  870. this.query("form input[name='operator-lteq']").checked = options.operators.includes("≤");
  871. }
  872. }