You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

496 lines
19 KiB

2 years ago
  1. /*
  2. Copyright (C) Philippe Meyer 2019
  3. Distributed under the MIT License
  4. vanillaSelectBox : v0.30 : The menu stops moving around on window resize and scroll + z-index in order of creation for multiple instances
  5. vanillaSelectBox : v0.26 : Corrected bug in stayOpen mode with disable() function
  6. vanillaSelectBox : v0.25 : New option stayOpen, and the dropbox is no longer a dropbox but a nice multi-select
  7. previous version : v0.24 : corrected bug affecting options with more than one class
  8. https://github.com/PhilippeMarcMeyer/vanillaSelectBox
  9. */
  10. let VSBoxCounter = function () {
  11. let count = 0;
  12. return {
  13. set: function () {
  14. return ++count;
  15. }
  16. };
  17. }();
  18. function vanillaSelectBox(domSelector, options) {
  19. let self = this;
  20. this.instanceOffset = VSBoxCounter.set();
  21. this.domSelector = domSelector;
  22. this.root = document.querySelector(domSelector)
  23. this.main;
  24. this.button;
  25. this.title;
  26. this.isMultiple = this.root.hasAttribute("multiple");
  27. this.multipleSize = this.isMultiple && this.root.hasAttribute("size") ? parseInt(this.root.getAttribute("size")) : -1;
  28. this.drop;
  29. this.top;
  30. this.left;
  31. this.options;
  32. this.listElements;
  33. this.isDisabled = false;
  34. this.search = false;
  35. this.searchZone = null;
  36. this.inputBox = null;
  37. this.ulminWidth = 200;
  38. this.ulminHeight = 25;
  39. this.userOptions = {
  40. maxWidth: 500,
  41. maxHeight: 400,
  42. translations: { "all": "All", "items": "items" },
  43. search: false,
  44. placeHolder: "",
  45. stayOpen:false
  46. }
  47. if (options) {
  48. if (options.maxWidth != undefined) {
  49. this.userOptions.maxWidth = options.maxWidth;
  50. }
  51. if (options.maxHeight != undefined) {
  52. this.userOptions.maxHeight = options.maxHeight;
  53. }
  54. if (options.translations != undefined) {
  55. this.userOptions.translations = options.translations;
  56. }
  57. if (options.placeHolder != undefined) {
  58. this.userOptions.placeHolder = options.placeHolder;
  59. }
  60. if (options.search != undefined) {
  61. this.search = options.search;
  62. }
  63. if (options.stayOpen != undefined) {
  64. this.userOptions.stayOpen = options.stayOpen;
  65. }
  66. }
  67. this.repositionMenu = function(){
  68. let rect = self.main.getBoundingClientRect();
  69. this.drop.style.left = rect.left+"px";
  70. this.drop.style.top = rect.bottom+"px";
  71. }
  72. this.init = function () {
  73. let self = this;
  74. this.root.style.display = "none";
  75. let already = document.getElementById("btn-group-" + self.domSelector);
  76. if (already) {
  77. already.remove();
  78. }
  79. this.main = document.createElement("div");
  80. this.root.parentNode.insertBefore(this.main, this.root.nextSibling);
  81. this.main.classList.add("vsb-main");
  82. this.main.setAttribute("id", "btn-group-" + this.domSelector);
  83. this.main.style.marginLeft = this.main.style.marginLeft;
  84. if(self.userOptions.stayOpen){
  85. this.main.style.minHeight = (this.userOptions.maxHeight+10) + "px";
  86. }
  87. let btnTag = self.userOptions.stayOpen ? "div" : "button";
  88. this.button = document.createElement(btnTag);
  89. let presentValue = this.main.value;
  90. this.main.appendChild(this.button);
  91. this.title = document.createElement("span");
  92. this.button.appendChild(this.title);
  93. this.title.classList.add("title");
  94. let caret = document.createElement("span");
  95. this.button.appendChild(caret);
  96. caret.classList.add("caret");
  97. caret.style.position = "absolute";
  98. caret.style.right = "8px";
  99. caret.style.marginTop = "8px";
  100. if(self.userOptions.stayOpen){
  101. caret.style.display = "none";
  102. this.title.style.paddingLeft = "20px";
  103. this.title.style.fontStyle = "italic";
  104. this.title.style.verticalAlign = "20%";
  105. }
  106. let rect = this.button.getBoundingClientRect();
  107. this.top = rect.bottom;
  108. this.left = rect.left;
  109. this.drop = document.createElement("div");
  110. this.main.appendChild(this.drop);
  111. this.drop.classList.add("vsb-menu");
  112. this.drop.style.zIndex = 2000 - this.instanceOffset;
  113. this.ul = document.createElement("ul");
  114. this.drop.appendChild(this.ul);
  115. if(!this.userOptions.stayOpen ){
  116. window.addEventListener("resize", function (e) {
  117. self.repositionMenu();
  118. });
  119. window.addEventListener("scroll", function (e) {
  120. self.repositionMenu();
  121. });
  122. }
  123. this.ul.style.maxHeight = this.userOptions.maxHeight + "px";
  124. this.ul.style.minWidth = this.ulminWidth + "px";
  125. this.ul.style.minHeight = this.ulminHeight + "px";
  126. if (this.isMultiple) {
  127. this.ul.classList.add("multi");
  128. }
  129. let selectedTexts = ""
  130. let sep = "";
  131. let nrActives = 0;
  132. if (this.search) {
  133. this.searchZone = document.createElement("div");
  134. this.ul.appendChild(this.searchZone);
  135. this.searchZone.classList.add("vsb-js-search-zone");
  136. this.searchZone.style.zIndex = 2001 - this.instanceOffset;
  137. this.inputBox = document.createElement("input");
  138. this.searchZone.appendChild(this.inputBox);
  139. this.inputBox.setAttribute("type", "text");
  140. this.inputBox.setAttribute("id", "search_" + this.domSelector);
  141. let fontSizeForP = this.isMultiple ? "8px" : "6px";
  142. var para = document.createElement("p");
  143. this.ul.appendChild(para);
  144. para.style.fontSize = fontSizeForP;
  145. para.innerHTML = " ";
  146. this.ul.addEventListener("scroll", function (e) {
  147. var y = this.scrollTop;
  148. self.searchZone.parentNode.style.top = y + "px";
  149. });
  150. }
  151. this.options = document.querySelectorAll(this.domSelector + " option");
  152. Array.prototype.slice.call(this.options).forEach(function (x) {
  153. let text = x.textContent;
  154. let value = x.value;
  155. let classes = x.getAttribute("class");
  156. if(classes)
  157. {
  158. classes=classes.split(" ");
  159. }
  160. else
  161. {
  162. classes=[];
  163. }
  164. let li = document.createElement("li");
  165. let isSelected = x.hasAttribute("selected");
  166. self.ul.appendChild(li);
  167. li.setAttribute("data-value", value);
  168. li.setAttribute("data-text", text);
  169. if (classes.length != 0) {
  170. classes.forEach(function(x){
  171. li.classList.add(x);
  172. });
  173. }
  174. if (isSelected) {
  175. nrActives++;
  176. selectedTexts += sep + text;
  177. sep = ",";
  178. li.classList.add("active");
  179. if (!self.isMultiple) {
  180. self.title.textContent = text;
  181. if (classes.length != 0) {
  182. classes.forEach(function(x){
  183. self.title.classList.add(x);
  184. });
  185. }
  186. }
  187. }
  188. li.appendChild(document.createTextNode(text));
  189. });
  190. if (self.multipleSize != -1) {
  191. if (nrActives > self.multipleSize) {
  192. let wordForItems = self.userOptions.translations.items || "items"
  193. selectedTexts = nrActives + " " + wordForItems;
  194. }
  195. }
  196. if (self.isMultiple) {
  197. self.title.innerHTML = selectedTexts;
  198. }
  199. if (self.userOptions.placeHolder != "" && self.title.textContent == "") {
  200. self.title.textContent = self.userOptions.placeHolder;
  201. }
  202. this.listElements = this.drop.querySelectorAll("li");
  203. if (self.search) {
  204. self.inputBox.addEventListener("keyup", function (e) {
  205. let searchValue = e.target.value.toUpperCase();
  206. let searchValueLength = searchValue.length;
  207. if (searchValueLength < 2) {
  208. Array.prototype.slice.call(self.listElements).forEach(function (x) {
  209. x.classList.remove("hide");
  210. });
  211. } else {
  212. Array.prototype.slice.call(self.listElements).forEach(function (x) {
  213. let text = x.getAttribute("data-text").toUpperCase();
  214. if (text.indexOf(searchValue) == -1) {
  215. x.classList.add("hide");
  216. } else {
  217. x.classList.remove("hide");
  218. }
  219. });
  220. }
  221. });
  222. }
  223. if(self.userOptions.stayOpen){
  224. self.drop.style.display = "block";
  225. self.drop.style.boxShadow = "none";
  226. self.drop.style.minHeight = (this.userOptions.maxHeight+10) + "px";
  227. self.drop.style.position = "relative";
  228. self.drop.style.left = "0px";
  229. self.drop.style.top = "0px";
  230. self.button.style.border = "none";
  231. }else{
  232. this.main.addEventListener("click", function (e) {
  233. if (self.isDisabled) return;
  234. self.drop.style.left = self.left + "px";
  235. self.drop.style.top = self.top + "px";
  236. self.drop.style.display = "block";
  237. document.addEventListener("click", docListener);
  238. e.preventDefault();
  239. e.stopPropagation();
  240. if(!self.userOptions.stayOpen ){
  241. self.repositionMenu();
  242. }
  243. });
  244. }
  245. this.drop.addEventListener("click", function (e) {
  246. if (self.isDisabled) return;
  247. if (!e.target.hasAttribute("data-value")) {
  248. e.preventDefault();
  249. e.stopPropagation();
  250. return;
  251. }
  252. let choiceValue = e.target.getAttribute("data-value");
  253. let choiceText = e.target.getAttribute("data-text");
  254. let className = e.target.getAttribute("class");
  255. if (!self.isMultiple) {
  256. self.root.value = choiceValue;
  257. self.title.textContent = choiceText;
  258. if (className) {
  259. self.title.setAttribute("class", className + " title");
  260. } else {
  261. self.title.setAttribute("class", "title");
  262. }
  263. Array.prototype.slice.call(self.listElements).forEach(function (x) {
  264. x.classList.remove("active");
  265. });
  266. if (choiceText != "") {
  267. e.target.classList.add("active");
  268. }
  269. self.privateSendChange();
  270. if(!self.userOptions.stayOpen){
  271. docListener();
  272. }
  273. } else {
  274. let wasActive = false;
  275. if (className) {
  276. wasActive = className.indexOf("active") != -1;
  277. }
  278. if (wasActive) {
  279. e.target.classList.remove("active");
  280. } else {
  281. e.target.classList.add("active");
  282. }
  283. let selectedTexts = ""
  284. let sep = "";
  285. let nrActives = 0;
  286. let nrAll = 0;
  287. for (let i = 0; i < self.options.length; i++) {
  288. nrAll++;
  289. if (self.options[i].value == choiceValue) {
  290. self.options[i].selected = !wasActive;
  291. }
  292. if (self.options[i].selected) {
  293. nrActives++;
  294. selectedTexts += sep + self.options[i].textContent;
  295. sep = ",";
  296. }
  297. }
  298. if (nrAll == nrActives) {
  299. let wordForAll = self.userOptions.translations.all || "all";
  300. selectedTexts = wordForAll;
  301. } else if (self.multipleSize != -1) {
  302. if (nrActives > self.multipleSize) {
  303. let wordForItems = self.userOptions.translations.items || "items"
  304. selectedTexts = nrActives + " " + wordForItems;
  305. }
  306. }
  307. self.title.textContent = selectedTexts;
  308. self.privateSendChange();
  309. }
  310. e.preventDefault();
  311. e.stopPropagation();
  312. if (self.userOptions.placeHolder != "" && self.title.textContent == "") {
  313. self.title.textContent = self.userOptions.placeHolder;
  314. }
  315. });
  316. function docListener() {
  317. document.removeEventListener("click", docListener);
  318. self.drop.style.display = "none";
  319. if(self.search){
  320. self.inputBox.value = "";
  321. Array.prototype.slice.call(self.listElements).forEach(function (x) {
  322. x.classList.remove("hide");
  323. });
  324. }
  325. }
  326. }
  327. this.init();
  328. }
  329. vanillaSelectBox.prototype.setValue = function (values) {
  330. let self = this;
  331. if (values == null || values == undefined || values == "") {
  332. self.empty();
  333. } else {
  334. if (self.isMultiple) {
  335. if (type(values) == "string") {
  336. if (values == "all") {
  337. values = [];
  338. Array.prototype.slice.call(self.options).forEach(function (x) {
  339. values.push(x.value);
  340. });
  341. } else {
  342. values = values.split(",");
  343. }
  344. }
  345. let foundValues = [];
  346. if (type(values) == "array") {
  347. Array.prototype.slice.call(self.options).forEach(function (x) {
  348. if (values.indexOf(x.value) != -1) {
  349. x.selected = true;
  350. foundValues.push(x.value);
  351. } else {
  352. x.selected = false;
  353. }
  354. });
  355. let selectedTexts = ""
  356. let sep = "";
  357. let nrActives = 0;
  358. let nrAll = 0;
  359. Array.prototype.slice.call(self.listElements).forEach(function (x) {
  360. nrAll++;
  361. if (foundValues.indexOf(x.getAttribute("data-value")) != -1) {
  362. x.classList.add("active");
  363. nrActives++;
  364. selectedTexts += sep + x.getAttribute("data-text");
  365. sep = ",";
  366. } else {
  367. x.classList.remove("active");
  368. }
  369. });
  370. if (nrAll == nrActives) {
  371. let wordForAll = self.userOptions.translations.all || "all";
  372. selectedTexts = wordForAll;
  373. } else if (self.multipleSize != -1) {
  374. if (nrActives > self.multipleSize) {
  375. let wordForItems = self.userOptions.translations.items || "items"
  376. selectedTexts = nrActives + " " + wordForItems;
  377. }
  378. }
  379. self.title.textContent = selectedTexts;
  380. self.privateSendChange();
  381. }
  382. } else {
  383. let found = false;
  384. let text = "";
  385. let classNames = ""
  386. Array.prototype.slice.call(self.listElements).forEach(function (x) {
  387. if (x.getAttribute("data-value") == values) {
  388. x.classList.add("active");
  389. found = true;
  390. text = x.getAttribute("data-text")
  391. } else {
  392. x.classList.remove("active");
  393. }
  394. });
  395. Array.prototype.slice.call(self.options).forEach(function (x) {
  396. if (x.value == values) {
  397. x.selected = true;
  398. className = x.getAttribute("class");
  399. if (!className) className = "";
  400. } else {
  401. x.selected = false;
  402. }
  403. });
  404. if (found) {
  405. self.title.textContent = text;
  406. if (self.userOptions.placeHolder != "" && self.title.textContent == "") {
  407. self.title.textContent = self.userOptions.placeHolder;
  408. }
  409. if (className != "") {
  410. self.title.setAttribute("class", className + " title");
  411. } else {
  412. self.title.setAttribute("class", "title");
  413. }
  414. }
  415. }
  416. }
  417. function type(target) {
  418. const computedType = Object.prototype.toString.call(target);
  419. const stripped = computedType.replace("[object ", "").replace("]", "");
  420. const lowercased = stripped.toLowerCase();
  421. return lowercased;
  422. }
  423. }
  424. vanillaSelectBox.prototype.privateSendChange = function () {
  425. let event = document.createEvent('HTMLEvents');
  426. event.initEvent('change', true, false);
  427. this.root.dispatchEvent(event);
  428. }
  429. vanillaSelectBox.prototype.empty = function () {
  430. Array.prototype.slice.call(this.listElements).forEach(function (x) {
  431. x.classList.remove("active");
  432. });
  433. Array.prototype.slice.call(this.options).forEach(function (x) {
  434. x.selected = false;
  435. });
  436. this.title.textContent = "";
  437. if (this.userOptions.placeHolder != "" && this.title.textContent == "") {
  438. this.title.textContent = this.userOptions.placeHolder;
  439. }
  440. this.privateSendChange();
  441. }
  442. vanillaSelectBox.prototype.destroy = function () {
  443. let already = document.getElementById("btn-group-" + this.domSelector);
  444. if (already) {
  445. already.remove();
  446. this.root.style.display = "inline-block";
  447. }
  448. }
  449. vanillaSelectBox.prototype.disable = function () {
  450. let already = document.getElementById("btn-group-" + this.domSelector);
  451. if (already) {
  452. button = already.querySelector("button")
  453. if(button) button.classList.add("disabled");
  454. this.isDisabled = true;
  455. }
  456. }
  457. vanillaSelectBox.prototype.enable = function () {
  458. let already = document.getElementById("btn-group-" + this.domSelector);
  459. if (already) {
  460. button = already.querySelector("button")
  461. if(button) button.classList.remove("disabled");
  462. this.isDisabled = false;
  463. }
  464. }
  465. vanillaSelectBox.prototype.showOptions = function(){
  466. console.log(this.userOptions);
  467. }
  468. // Polyfills for IE
  469. if (!('remove' in Element.prototype)) {
  470. Element.prototype.remove = function () {
  471. if (this.parentNode) {
  472. this.parentNode.removeChild(this);
  473. }
  474. };
  475. }