├── LICENSE ├── README.md ├── Sample.html ├── assets ├── down-arrow.svg ├── minus.svg └── plus.svg ├── comboTreePlugin.js └── comboTreeStyle.css /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 kirlisakal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 1.3.1 Updates 2 | 3 | - Outsource libraries cancelled. 4 | - Refactoring JS and CSS. 5 | - Many bugs resolved on both UI and Functionality sides. 6 | 7 | ## 1.2.1 Updates 8 | 9 | - Filter is fixed & updated. 10 | - icontains.js dependency is deprecated. 11 | 12 | # ComboTree jQuery Plugin v 1.2.1 13 | 14 | ComboTree is a jQuery Plugin which is a combobox item within tree structured data list and multiple/single selection options and more. It has been developed to manage large amount of choices in a combobox and multi selection feature. 15 | 16 | ## Features: 17 | 18 | - Tree structured data list in combobox dropdown menu 19 | - Multiple & Single selection 20 | - Cascade selection (for multiple mode) 21 | - Returns selected item(s) as title or id array 22 | - Filtering (for multiple mode) 23 | - Consumes JSON source 24 | - Key controls are available for both selection and filter inputs. 25 | 26 | ## Dependencies: 27 | 28 | - jQuery 29 | 30 | ## Configurations: 31 | 32 | - **isMultiple**: _{true/false} | default: false_ | decide if it is multiple selection behaviour or single 33 | - **cascadeSelect**: _{true/false} | default: false_ | decide if parent selection should cascade to children in multiple selection 34 | - **source**: _{JSON Data Array}_ | takes source of combobox dropdown menu as a JSON array. 35 | - **selected**: _{JSON Data Array}_ | takes the list of ID's that corespond from the source. 36 | - **collapse**: _{true/false} | default: false_ | makes sub lists collapsed. 37 | - **selectAll**: _{true/false} | default: false_ | decide if use 'select all' feature. 38 | 39 | ## Methods 40 | 41 | - **getSelectedIds()**: Returns selected item(s) id list as array or null. _(i.e. [12, 5, 7], [7], null)_ 42 | - **getSelectedNames()**: Returns selected item(s) name list as array or null. _(i.e. ["Piegon", "Cat", "Horse"], ["Lion"], null)_ 43 | - **setSource()**: You can initialize ComboTree then set source after your JSON data is retrieved. 44 | - **clearSelection()**: Clear selected items. 45 | - **selectAll()**: select all items. 46 | - **setSelection(selectionIdList)**: Set selected values of combotree by id array or single id parameter. If you want to clear previous selections please use _clearSelection()_ before _setSelection()_. _(i.e. ct1.setSelection([12, 5, 7]) | ct1.setSelection(5)_ 47 | 48 | ## Events 49 | 50 | - **onChange(callBackFunction)**: Triggers after selection changes. 51 | 52 | ## Usage 53 | 54 | There should be an input element to apply and a JSON Data source. 55 | 56 | comboTree1 = $('#justAnInputBox').comboTree({ 57 | source : SampleJSONData, 58 | isMultiple: true, 59 | cascadeSelect: true, 60 | selected: ['0'] 61 | }); 62 | 63 | // Array, One title/id, or False value return 64 | var selectedTitles = comboTree1.getSelectedItemsTitle(); 65 | var selectedIds = comboTree1.getSelectedItemsId(); 66 | 67 | // To remove plugin 68 | comboTree1.destroy(); 69 | 70 | ## Source Dataset 71 | 72 | Three parameter are needed: id, title and subs (array). 73 | 74 | Can contain the following options: 75 | 76 | - **isSelectable**: _{true/false} | default: true_ | allows or disallows item to be selected 77 | - **collapse**: _{true/false} | default: false_ | makes sub lists collapsed at start 78 | 79 | var SampleJSONData = [ 80 | { 81 | id: 0, 82 | title: 'Horse' 83 | }, { 84 | id: 1, 85 | title: 'Birds', 86 | subs: [ 87 | { 88 | id: 10, 89 | title: 'Piegon' 90 | }, { 91 | id: 11, 92 | title: 'Parrot' 93 | }, { 94 | id: 12, 95 | title: 'Owl' 96 | }, { 97 | id: 13, 98 | title: 'Falcon' 99 | } 100 | ] 101 | }, { 102 | id: 2, 103 | title: 'Rabbit' 104 | }, { 105 | id: 3, 106 | title: 'Fox' 107 | }, { 108 | id: 5, 109 | title: 'Cats', 110 | subs: [ 111 | { 112 | id: 50, 113 | title: 'Kitty' 114 | }, { 115 | id: 51, 116 | title: 'Bigs', 117 | subs: [ 118 | { 119 | id: 510, 120 | title: 'Cheetah' 121 | }, { 122 | id: 511, 123 | title: 'Jaguar' 124 | }, { 125 | id: 512, 126 | title: 'Leopard' 127 | } 128 | ] 129 | } 130 | ] 131 | }, { 132 | id: 6, 133 | title: 'Fish' 134 | } 135 | ]; 136 | 137 | ## You can donate to support me 138 | 139 | https://www.blockchain.com/btc/address/15c5AxBVgNxkwaHSTBZMiCV5PL41DKe88v 140 | -------------------------------------------------------------------------------- /Sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | ComboTree jQuery Plugin Demos by Erhan FIRAT 11 | 12 | 30 | 31 | 32 |
33 |

ComboTree jQuery Plugin Demos

34 | 35 |
36 | Github Home Page 37 |
38 | 39 |
40 |
41 |

Multi Selection

42 |
 43 |   comboTree1 = $("#justAnInputBox").comboTree({
 44 |     source: SampleJSONData,
 45 |     isMultiple: true,
 46 |     cascadeSelect: false,
 47 |     collapse: true,
 48 |     selectAll: true,
 49 |   });
 50 |           
51 | 57 |
58 | 59 |
60 |

Multi Selection With Cascade Option Select

61 |
 62 |   comboTree3 = $("#justAnInputBox1").comboTree({
 63 |     source: SampleJSONData,
 64 |     isMultiple: true,
 65 |     cascadeSelect: true,
 66 |     collapse: false,
 67 |   });
 68 |           
69 | 75 |
76 | 77 |
78 |

Single Selection

79 |
 80 |   comboTree2 = $("#justAnotherInputBox").comboTree({
 81 |     source: SampleJSONData,
 82 |     isMultiple: false,
 83 |   });
 84 |           
85 | 91 |
92 |
93 |

Default Settings

94 |
 95 |   const defaults = {
 96 |     source: [],
 97 |     isMultiple: false,
 98 |     cascadeSelect: false,
 99 |     selected: [],
100 |     collapse: false,
101 |     selectAll: false,
102 |     animationTime: 200,
103 |   };
104 |           
105 |
106 |
107 |
108 | 109 | 110 | 111 | 112 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /assets/down-arrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/minus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /comboTreePlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery ComboTree Plugin 3 | * Author: Erhan FIRAT 4 | * Mail: erhanfirat@gmail.com 5 | * Licensed under the MIT license 6 | * Version: 1.3.1 7 | */ 8 | 9 | (function ($, window, document, undefined) { 10 | // Default settings 11 | let comboTreePlugin = "comboTree"; 12 | const defaults = { 13 | source: [], 14 | isMultiple: false, 15 | cascadeSelect: false, 16 | selected: [], 17 | collapse: false, 18 | selectAll: false, 19 | animationTime: 200, 20 | }; 21 | 22 | // LIFE CYCLE 23 | function ComboTree(element, options) { 24 | this.options = $.extend({}, defaults, options); 25 | this._defaults = defaults; 26 | this._name = comboTreePlugin; 27 | 28 | this.constructorFunc(element, options); 29 | } 30 | 31 | ComboTree.prototype.constructorFunc = function (element, options) { 32 | this.input = element; 33 | this._input = $(element); 34 | 35 | this.init(); 36 | }; 37 | 38 | ComboTree.prototype.init = function () { 39 | // Setting Doms 40 | this.id = "ct-" + Math.floor(Math.random() * 999999); 41 | 42 | this._input.addClass("ct-input-box"); 43 | this._input.wrap( 44 | '
' 45 | ); 46 | this._input.wrap('
'); 47 | this._wrapper = $("#" + this.id + "-wrapper"); 48 | 49 | this._arrowBtn = $(''); 50 | this._input.after(this._arrowBtn); 51 | 52 | this._wrapper.append('
'); 53 | 54 | // DORP DOWN AREA 55 | this._dropDownContainer = this._wrapper.find(".ct-drop-down-container"); 56 | 57 | this._dropDownContainer.html(this.createSourceHTML()); 58 | this._filterInput = this.options.isMultiple 59 | ? this._wrapper.find("#" + this.id + "-multi-filter") 60 | : null; 61 | this._selectAllInput = 62 | this.options.isMultiple && this.options.selectAll 63 | ? this._wrapper.find(".select-all-input") 64 | : null; 65 | this._sourceUl = this._wrapper.find(".ct-source-ul-main"); 66 | 67 | this._listItems = this._dropDownContainer.find("li"); 68 | this._listItemsTitle = this._dropDownContainer.find( 69 | "span.ct-list-item-title" 70 | ); 71 | 72 | // VARIABLES 73 | this._selectedItem = {}; 74 | this._selectedItems = []; 75 | 76 | this.processSelected(); 77 | 78 | this.bindings(); 79 | }; 80 | 81 | ComboTree.prototype.unbind = function () { 82 | this._arrowBtn.off("click"); 83 | this._input.off("click"); 84 | this._listItems.off("click"); 85 | this._listItemsTitle.off("click"); 86 | this._listItemsTitle.off("mousemove"); 87 | this._input.off("keyup"); 88 | this._input.off("keydown"); 89 | this._input.off("mouseup." + this.id); 90 | $(document).off("mouseup." + this.id); 91 | }; 92 | 93 | ComboTree.prototype.destroy = function () { 94 | this.unbind(); 95 | this._wrapper.before(this._input); 96 | this._wrapper.remove(); 97 | this._input.removeData("plugin_" + comboTreePlugin); 98 | }; 99 | 100 | // CREATE DOM HTMLs 101 | 102 | ComboTree.prototype.removeSourceHTML = function () { 103 | this._dropDownContainer.html(""); 104 | }; 105 | 106 | ComboTree.prototype.createSourceHTML = function () { 107 | let sourceHTML = ""; 108 | if (this.options.isMultiple) 109 | sourceHTML += this.createFilterHTMLForMultiSelect(); 110 | // if (this.options.isMultiple && this.options.selectAll) 111 | // sourceHTML += this.createSelectAllHTMLForMultiSelect(); 112 | sourceHTML += this.createSourceSubItemsHTML(this.options.source, false); 113 | return sourceHTML; 114 | }; 115 | 116 | ComboTree.prototype.createFilterHTMLForMultiSelect = function () { 117 | return ( 118 | '' 121 | ); 122 | }; 123 | 124 | ComboTree.prototype.createSelectAllHTMLForMultiSelect = function () { 125 | return ( 126 | '
  • " 131 | ); 132 | }; 133 | 134 | ComboTree.prototype.createSourceSubItemsHTML = function ( 135 | subItems, 136 | parentId, 137 | collapse = false 138 | ) { 139 | let subItemsHtml = 140 | '"; 157 | return subItemsHtml; 158 | }; 159 | 160 | ComboTree.prototype.createSourceItemHTML = function (sourceItem) { 161 | let itemHtml = ""; 162 | const isThereSubs = sourceItem.hasOwnProperty("subs"); 163 | const collapse = sourceItem.hasOwnProperty("collapse") 164 | ? sourceItem.hasOwnProperty("collapse") 165 | : false; 166 | let isSelectable = 167 | sourceItem.isSelectable === undefined ? true : sourceItem.isSelectable; 168 | let selectableClass = isSelectable ? "selectable" : "not-selectable"; 169 | 170 | itemHtml += 171 | '
  • '; 178 | 179 | itemHtml += `${ 180 | isThereSubs 181 | ? `${ 182 | this.options.collapse || collapse ? "+" : "-" 183 | }` 184 | : "" 185 | }${ 190 | this.options.isMultiple && isSelectable ? '' : "" 191 | }${sourceItem.title}`; 192 | 193 | if (isThereSubs) 194 | itemHtml += this.createSourceSubItemsHTML( 195 | sourceItem.subs, 196 | sourceItem.id, 197 | collapse 198 | ); 199 | 200 | itemHtml += "
  • "; 201 | return itemHtml; 202 | }; 203 | 204 | // BINDINGS 205 | 206 | ComboTree.prototype.bindings = function () { 207 | const _this = this; 208 | 209 | $(this._input).focus(function (e) { 210 | if (!_this._dropDownContainer.is(":visible")) 211 | $(_this._dropDownContainer).slideToggle(_this.options.animationTime); 212 | }); 213 | 214 | this._arrowBtn.on("click", function (e) { 215 | e.stopPropagation(); 216 | _this.toggleDropDown(); 217 | }); 218 | this._input.on("click", function (e) { 219 | e.stopPropagation(); 220 | if (!_this._dropDownContainer.is(":visible")) _this.toggleDropDown(); 221 | }); 222 | this._listItems.on("click", function (e) { 223 | e.stopPropagation(); 224 | if ($(this).hasClass("ct-item-parent")) { 225 | _this.toggleSelectionTree(this); 226 | } 227 | }); 228 | this._listItemsTitle.on("click", function (e) { 229 | e.stopPropagation(); 230 | if (_this.options.isMultiple) _this.multiItemClick(this); 231 | else _this.singleItemClick(this); 232 | }); 233 | this._listItemsTitle.on("mousemove", function (e) { 234 | e.stopPropagation(); 235 | _this.dropDownMenuHover(this); 236 | }); 237 | this._selectAllInput && 238 | this._selectAllInput.parent("label").on("mousemove", function (e) { 239 | e.stopPropagation(); 240 | _this.dropDownMenuHover(this); 241 | }); 242 | 243 | // KEY BINDINGS 244 | this._input.on("keyup", function (e) { 245 | e.stopPropagation(); 246 | 247 | switch (e.keyCode) { 248 | case 27: 249 | _this.closeDropDownMenu(); 250 | break; 251 | case 13: 252 | case 39: 253 | case 37: 254 | case 40: 255 | case 38: 256 | e.preventDefault(); 257 | break; 258 | default: 259 | if (!_this.options.isMultiple) _this.filterDropDownMenu(); 260 | break; 261 | } 262 | }); 263 | 264 | this._filterInput && 265 | this._filterInput.on("keyup", function (e) { 266 | e.stopPropagation(); 267 | 268 | switch (e.keyCode) { 269 | case 27: 270 | if ($(this).val()) { 271 | $(this).val(""); 272 | _this.filterDropDownMenu(); 273 | } else { 274 | _this.closeDropDownMenu(); 275 | } 276 | break; 277 | case 40: 278 | case 38: 279 | e.preventDefault(); 280 | _this.dropDownInputKeyControl(e.keyCode - 39); 281 | break; 282 | case 37: 283 | case 39: 284 | e.preventDefault(); 285 | _this.dropDownInputKeyToggleTreeControl(e.keyCode - 38); 286 | break; 287 | case 13: 288 | _this.multiItemClick(_this._elemHoveredItem); 289 | e.preventDefault(); 290 | break; 291 | default: 292 | _this.filterDropDownMenu(); 293 | break; 294 | } 295 | }); 296 | 297 | this._input.on("keydown", function (e) { 298 | e.stopPropagation(); 299 | 300 | switch (e.keyCode) { 301 | case 9: 302 | _this.closeDropDownMenu(); 303 | break; 304 | case 40: 305 | case 38: 306 | e.preventDefault(); 307 | _this.dropDownInputKeyControl(e.keyCode - 39); 308 | break; 309 | case 37: 310 | case 39: 311 | e.preventDefault(); 312 | _this.dropDownInputKeyToggleTreeControl(e.keyCode - 38); 313 | break; 314 | case 13: 315 | if (_this.options.isMultiple) 316 | _this.multiItemClick(_this._elemHoveredItem); 317 | else _this.singleItemClick(_this._elemHoveredItem); 318 | e.preventDefault(); 319 | break; 320 | default: 321 | if (_this.options.isMultiple) e.preventDefault(); 322 | } 323 | }); 324 | 325 | // ON FOCUS OUT CLOSE DROPDOWN 326 | $(document).on("mouseup." + _this.id, function (e) { 327 | if ( 328 | !_this._wrapper.is(e.target) && 329 | _this._wrapper.has(e.target).length === 0 && 330 | _this._dropDownContainer.is(":visible") 331 | ) 332 | _this.closeDropDownMenu(); 333 | }); 334 | 335 | this._selectAllInput && 336 | this._selectAllInput.on("click", function (e) { 337 | e.stopPropagation(); 338 | let checked = $(e.target).prop("checked"); 339 | if (checked) { 340 | _this.selectAll(); 341 | } else { 342 | _this.clearSelection(); 343 | } 344 | }); 345 | }; 346 | 347 | // EVENTS HERE 348 | 349 | // DropDown Menu Open/Close 350 | ComboTree.prototype.toggleDropDown = function () { 351 | const _this = this; 352 | $(this._dropDownContainer).slideToggle( 353 | this.options.animationTime, 354 | function () { 355 | if (_this._dropDownContainer.is(":visible")) $(_this._input).focus(); 356 | } 357 | ); 358 | }; 359 | 360 | ComboTree.prototype.closeDropDownMenu = function () { 361 | $(this._dropDownContainer).slideUp(this.options.animationTime); 362 | }; 363 | 364 | // Selection Tree Open/Close 365 | ComboTree.prototype.toggleSelectionTree = function (item, direction) { 366 | const subMenu = $(item).children("ul")[0]; 367 | if (direction === undefined) { 368 | if ($(subMenu).is(":visible")) 369 | $(item).children("span.ct-parent-plus").html("+"); 370 | else $(item).children("span.ct-parent-plus").html("-"); 371 | 372 | $(subMenu).slideToggle(this.options.animationTime); 373 | } else if (direction == 1 && !$(subMenu).is(":visible")) { 374 | $(item).children("span.ct-parent-plus").html("-"); 375 | $(subMenu).slideDown(this.options.animationTime); 376 | } else if (direction == -1) { 377 | if ($(subMenu).is(":visible")) { 378 | $(item).children("span.ct-parent-plus").html("+"); 379 | $(subMenu).slideUp(this.options.animationTime); 380 | } else { 381 | this.dropDownMenuHover(item); 382 | } 383 | } 384 | }; 385 | 386 | // SELECTION FUNCTIONS 387 | ComboTree.prototype.selectMultipleItem = function (ctItem) { 388 | if ( 389 | $(ctItem).parent("li").hasClass("ct-item-parent") && 390 | $(ctItem).data("selectable") == false 391 | ) { 392 | this.toggleSelectionTree($(ctItem).parent("li")); 393 | } 394 | 395 | if ($(ctItem).data("selectable") == true) { 396 | this._selectedItem = { 397 | id: $(ctItem).attr("data-id"), 398 | title: $(ctItem).text(), 399 | }; 400 | 401 | const check = this.isItemInArray(this._selectedItem, this.options.source); 402 | if (check) { 403 | const index = this.isItemInArray( 404 | this._selectedItem, 405 | this._selectedItems 406 | ); 407 | if (index) { 408 | this._selectedItems.splice(parseInt(index), 1); 409 | $(ctItem).find("input").prop("checked", false); 410 | } else { 411 | this._selectedItems.push(this._selectedItem); 412 | $(ctItem).find("input").prop("checked", true); 413 | } 414 | } 415 | } 416 | }; 417 | 418 | ComboTree.prototype.singleItemClick = function (ctItem) { 419 | if ($(ctItem).data("selectable") == true) { 420 | this._selectedItem = { 421 | id: $(ctItem).attr("data-id"), 422 | title: $(ctItem).text(), 423 | }; 424 | 425 | this.refreshInputVal(); 426 | this.closeDropDownMenu(); 427 | } else if ($(ctItem).parent("li").hasClass("ct-item-parent")) { 428 | this.toggleSelectionTree($(ctItem).parent("li")); 429 | } 430 | }; 431 | 432 | ComboTree.prototype.multiItemClick = function (ctItem) { 433 | this.selectMultipleItem(ctItem); 434 | 435 | if (this.options.cascadeSelect && $(ctItem).data("selectable")) { 436 | if ($(ctItem).parent("li").hasClass("ct-item-parent")) { 437 | const subMenu = $(ctItem) 438 | .parent("li") 439 | .children("ul") 440 | .first() 441 | .find('input[type="checkbox"]'); 442 | subMenu.each(function () { 443 | const $input = $(this); 444 | if ( 445 | $(ctItem) 446 | .children('input[type="checkbox"]') 447 | .first() 448 | .prop("checked") !== $input.prop("checked") 449 | ) { 450 | $input.prop( 451 | "checked", 452 | !$(ctItem) 453 | .children('input[type="checkbox"]') 454 | .first() 455 | .prop("checked") 456 | ); 457 | $input.trigger("click"); 458 | } 459 | }); 460 | } 461 | } 462 | this.refreshInputVal(); 463 | }; 464 | 465 | // recursive search for item in arr 466 | ComboTree.prototype.isItemInArray = function (item, arr) { 467 | for (let i = 0; i < arr.length; i++) { 468 | if (item.id == arr[i].id && item.title == arr[i].title) return i + ""; 469 | 470 | if (arr[i].hasOwnProperty("subs")) { 471 | const found = this.isItemInArray(item, arr[i].subs); 472 | if (found) return found; 473 | } 474 | } 475 | return false; 476 | }; 477 | 478 | ComboTree.prototype.refreshInputVal = function () { 479 | let tmpTitle = ""; 480 | 481 | if (this.options.isMultiple) { 482 | for (let i = 0; i < this._selectedItems.length; i++) { 483 | tmpTitle += this._selectedItems[i].title; 484 | if (i < this._selectedItems.length - 1) tmpTitle += ", "; 485 | } 486 | } else { 487 | tmpTitle = this._selectedItem.title; 488 | } 489 | 490 | this._input.val(tmpTitle); 491 | this._input.trigger("change"); 492 | 493 | if (this.changeHandler) this.changeHandler(); 494 | }; 495 | 496 | ComboTree.prototype.dropDownMenuHover = function (itemSpan, withScroll) { 497 | this._wrapper.find(".ct-tree-item-hover").removeClass("ct-tree-item-hover"); 498 | $(itemSpan).addClass("ct-tree-item-hover"); 499 | this._elemHoveredItem = $(itemSpan); 500 | if (withScroll && itemSpan) 501 | this.dropDownScrollToHoveredItem(this._elemHoveredItem); 502 | }; 503 | 504 | ComboTree.prototype.dropDownScrollToHoveredItem = function (itemSpan) { 505 | this._sourceUl.parent().scrollTop(itemSpan[0].offsetTop - 30); 506 | }; 507 | 508 | ComboTree.prototype.dropDownInputKeyToggleTreeControl = function (direction) { 509 | const item = this._elemHoveredItem; 510 | if ($(item).parent("li").hasClass("ct-item-parent")) 511 | this.toggleSelectionTree($(item).parent("li"), direction); 512 | else if (direction == -1) this.dropDownMenuHover(item); 513 | }; 514 | 515 | ComboTree.prototype.dropDownInputKeyControl = function (step) { 516 | if (!this._dropDownContainer.is(":visible")) this.toggleDropDown(); 517 | 518 | const list = this._listItems.find("span.ct-list-item-title:visible"); 519 | let i = this._elemHoveredItem 520 | ? list.index(this._elemHoveredItem) + step 521 | : 0; 522 | i = (list.length + i) % list.length; 523 | 524 | this.dropDownMenuHover(list[i], true); 525 | }; 526 | 527 | ComboTree.prototype.filterDropDownMenu = function () { 528 | let searchText = ""; 529 | const _this = this; 530 | if (!this.options.isMultiple) searchText = this._input.val(); 531 | else 532 | searchText = this._wrapper.find("#" + _this.id + "-multi-filter").val(); 533 | 534 | if (searchText != "") { 535 | this._listItemsTitle.hide(); 536 | this._listItemsTitle.siblings("span.ct-parent-plus").hide(); 537 | list = this._listItems 538 | .filter(function (index, item) { 539 | return ( 540 | item.innerHTML.toLowerCase().indexOf(searchText.toLowerCase()) != -1 541 | ); 542 | }) 543 | .each(function (i, elem) { 544 | $(this.children).show(); 545 | $(this).siblings("span.ct-parent-plus").show(); 546 | }); 547 | } else { 548 | this._listItemsTitle.show(); 549 | this._listItemsTitle.siblings("span.ct-parent-plus").show(); 550 | } 551 | }; 552 | 553 | ComboTree.prototype.processSelected = function () { 554 | const elements = this._listItemsTitle; 555 | let selectedItem = this._selectedItem; 556 | let selectedItems = this._selectedItems; 557 | this.options.selected.forEach(function (element) { 558 | let selected = $(elements).filter(function () { 559 | return $(this).data("id") == element; 560 | }); 561 | 562 | if (selected.length > 0) { 563 | $(selected).find("input").attr("checked", true); 564 | 565 | selectedItem = { 566 | id: selected.data("id"), 567 | title: selected.text(), 568 | }; 569 | selectedItems.push(selectedItem); 570 | } 571 | }); 572 | 573 | this._selectedItem = selectedItem; 574 | 575 | this.refreshInputVal(); 576 | }; 577 | 578 | // METHODS 579 | 580 | ComboTree.prototype.findItembyId = function (itemId, source) { 581 | if (itemId && source) { 582 | for (let i = 0; i < source.length; i++) { 583 | if (source[i].id == itemId) 584 | return { id: source[i].id, title: source[i].title }; 585 | if (source[i].hasOwnProperty("subs")) { 586 | let found = this.findItembyId(itemId, source[i].subs); 587 | if (found) return found; 588 | } 589 | } 590 | } 591 | return null; 592 | }; 593 | 594 | // Returns selected id array or null 595 | ComboTree.prototype.getSelectedIds = function () { 596 | if (this.options.isMultiple && this._selectedItems.length > 0) { 597 | const tmpArr = []; 598 | for (i = 0; i < this._selectedItems.length; i++) 599 | tmpArr.push(this._selectedItems[i].id); 600 | 601 | return tmpArr; 602 | } else if ( 603 | !this.options.isMultiple && 604 | this._selectedItem.hasOwnProperty("id") 605 | ) { 606 | return [this._selectedItem.id]; 607 | } 608 | return null; 609 | }; 610 | 611 | // Retuns Array (multiple), Integer (single), or False (No choice) 612 | ComboTree.prototype.getSelectedNames = function () { 613 | if (this.options.isMultiple && this._selectedItems.length > 0) { 614 | const tmpArr = []; 615 | for (i = 0; i < this._selectedItems.length; i++) 616 | tmpArr.push(this._selectedItems[i].title); 617 | 618 | return tmpArr; 619 | } else if ( 620 | !this.options.isMultiple && 621 | this._selectedItem.hasOwnProperty("id") 622 | ) { 623 | return this._selectedItem.title; 624 | } 625 | return null; 626 | }; 627 | 628 | ComboTree.prototype.setSource = function (source) { 629 | this._selectedItems = []; 630 | this.destroy(); 631 | 632 | this.options.source = source; 633 | this.constructorFunc(this.input, this.options); 634 | }; 635 | 636 | ComboTree.prototype.clearSelection = function () { 637 | for (i = 0; i < this._selectedItems.length; i++) { 638 | let itemElemSelector = 639 | "#ct-" + this.id + "-li-" + this._selectedItems[i].id; 640 | itemElemSelector = itemElemSelector.replaceAll(".", "\\."); 641 | let itemElem = this._wrapper.find(itemElemSelector); 642 | $(itemElem).find("input").prop("checked", false); 643 | } 644 | this._selectedItems = []; 645 | this._selectedItem = {}; 646 | if (this._selectAllInput) { 647 | this._selectAllInput.prop("checked", false); 648 | } 649 | this.refreshInputVal(); 650 | }; 651 | 652 | ComboTree.prototype.setSelection = function (selectionIdList) { 653 | if ( 654 | selectionIdList && 655 | selectionIdList.length && 656 | selectionIdList.length > 0 657 | ) { 658 | for (let i = 0; i < selectionIdList.length; i++) { 659 | let selectedItem = this.findItembyId( 660 | selectionIdList[i], 661 | this.options.source 662 | ); 663 | 664 | if (selectedItem) { 665 | let check = this.isItemInArray(selectedItem, this.options.source); 666 | if (check) { 667 | const index = this.isItemInArray(selectedItem, this._selectedItems); 668 | if (!index) { 669 | let selectedItemElemSelector = 670 | "#" + this.id + "-li" + selectionIdList[i]; 671 | selectedItemElemSelector = selectedItemElemSelector.replaceAll( 672 | ".", 673 | "\\." 674 | ); 675 | let selectedItemElem = $(selectedItemElemSelector); 676 | 677 | this._selectedItems.push(selectedItem); 678 | this._selectedItem = selectedItem; 679 | // If cascadeSelect is true, check all children, otherwise just check this item 680 | if (this.options.cascadeSelect) { 681 | $(selectedItemElem).find("input").prop("checked", true); 682 | } else { 683 | $(selectedItemElem).find("input:first").prop("checked", true); 684 | } 685 | } 686 | } 687 | } 688 | } 689 | } 690 | 691 | this.refreshInputVal(); 692 | }; 693 | 694 | ComboTree.prototype.selectAll = function () { 695 | const _this = this; 696 | this._selectedItems = []; 697 | this._wrapper 698 | .find("#" + this.id + "-source-ul-main") 699 | .find("[data-selectable=true] input[type='checkbox']") 700 | .each(function (idx, inputCheck) { 701 | let $itemElem = $(inputCheck).parent("span").first(); 702 | let item = { 703 | id: $itemElem.data("id"), 704 | title: $itemElem.text(), 705 | }; 706 | $(inputCheck).prop("checked", true); 707 | _this._selectedItems.push(item); 708 | }); 709 | if (this._selectAllInput) { 710 | this._selectAllInput.prop("checked", true); 711 | } 712 | this.refreshInputVal(); 713 | }; 714 | 715 | // EVENTS 716 | 717 | ComboTree.prototype.onChange = function (callBack) { 718 | if (callBack && typeof callBack === "function") 719 | this.changeHandler = callBack; 720 | }; 721 | 722 | // ----- 723 | 724 | $.fn[comboTreePlugin] = function (options) { 725 | const ctArr = []; 726 | this.each(function () { 727 | if (!$.data(this, "plugin_" + comboTreePlugin)) { 728 | $.data(this, "plugin_" + comboTreePlugin, new ComboTree(this, options)); 729 | ctArr.push($(this).data()["plugin_" + comboTreePlugin]); 730 | } 731 | }); 732 | 733 | if (this.length == 1) return ctArr[0]; 734 | else return ctArr; 735 | }; 736 | })(jQuery, window, document); 737 | 738 | const comboTreeIcons = { 739 | downIcon: ``, 740 | plus: ``, 741 | minus: ``, 742 | }; 743 | -------------------------------------------------------------------------------- /comboTreeStyle.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery ComboTree Plugin 3 | * Author: Erhan FIRAT 4 | * Mail: erhanfirat@gmail.com 5 | * Licensed under the MIT license 6 | * Version: 1.3.1 7 | */ 8 | 9 | :root { 10 | --ct-bg: #fff; 11 | --ct-border-color: #e1e1e1; 12 | --ct-border-radius: 0.5rem; 13 | --ct-tree-hover: #efefef; 14 | --ct-selection: #418eff; 15 | --ct-padding: 0.5rem; 16 | } 17 | 18 | .ct-wrapper { 19 | position: relative; 20 | text-align: left !important; 21 | box-sizing: border-box; 22 | } 23 | 24 | .ct-wrapper * { 25 | box-sizing: border-box !important; 26 | } 27 | 28 | .ct-input-wrapper { 29 | position: relative; 30 | } 31 | 32 | .ct-input-box { 33 | padding: var(--ct-padding) 1.5rem var(--ct-padding) var(--ct-padding); 34 | border-radius: var(--ct-border-radius); 35 | border: 1px solid var(--ct-border-color); 36 | width: 100%; 37 | } 38 | 39 | .ct-arrow-btn { 40 | position: absolute; 41 | right: 0px; 42 | bottom: 0px; 43 | top: 0px; 44 | padding: 0.5rem; 45 | border: none; 46 | background: none; 47 | cursor: pointer; 48 | } 49 | .ct-arrow-btn:hover { 50 | color: var(--ct-selection); 51 | } 52 | .ct-arrow-btn:active { 53 | color: var(--ct-selection); 54 | } 55 | .ct-input-box:focus + .ct-arrow-btn { 56 | color: var(--ct-selection); 57 | } 58 | 59 | .ct-drop-down-container, 60 | .ct-drop-down-container *, 61 | .ct-arrow-btn { 62 | -webkit-user-select: none; /* Safari */ 63 | -moz-user-select: none; /* Firefox */ 64 | -ms-user-select: none; /* IE10+/Edge */ 65 | user-select: none; /* Standard */ 66 | } 67 | 68 | .ct-drop-down-container { 69 | display: none; 70 | background: var(--ct-bg); 71 | border: 1px solid var(--ct-border-color); 72 | position: absolute; 73 | width: 100%; 74 | z-index: 999; 75 | max-height: 250px; 76 | overflow-y: auto; 77 | box-shadow: 0 5px 15px -5px rgba(0, 0, 0, 0.3); 78 | } 79 | 80 | .ct-drop-down-container ul { 81 | padding: 0; 82 | margin: 0; 83 | } 84 | 85 | .ct-drop-down-container li { 86 | list-style-type: none; 87 | padding-left: 0; 88 | cursor: pointer; 89 | } 90 | 91 | .ct-drop-down-container .ct-item-parent li { 92 | padding-left: 1rem; 93 | } 94 | 95 | .ct-drop-down-container li .not-selectable { 96 | color: #777; 97 | } 98 | 99 | .ct-drop-down-container li:hover { 100 | background-color: var(--ct-tree-hover); 101 | } 102 | .ct-drop-down-container li:hover ul { 103 | background-color: var(--ct-bg); 104 | } 105 | .ct-drop-down-container li span.ct-list-item-title.ct-tree-item-hover, 106 | .ct-drop-down-container label.ct-tree-item-hover { 107 | background-color: var(--ct-selection); 108 | color: var(--ct-bg); 109 | border-radius: 2px; 110 | } 111 | .ct-drop-down-container 112 | li 113 | span[data-selectable="false"].ct-list-item-title.ct-tree-item-hover, 114 | .ct-drop-down-container label.ct-tree-item-hover { 115 | background-color: var(--ct-border-color); 116 | color: #777; 117 | cursor: default; 118 | } 119 | 120 | span.ct-list-item-title, 121 | .ct-drop-down-container .select-all { 122 | display: block; 123 | padding: 0.25rem 1rem; 124 | margin-left: 1rem; 125 | } 126 | .ct-drop-down-container label { 127 | cursor: pointer; 128 | width: 100%; 129 | display: block; 130 | } 131 | .ct-drop-down-container .ct-list-item-title input, 132 | .ct-drop-down-container .select-all input { 133 | position: relative; 134 | top: 2px; 135 | margin: 0px 4px 0px 0px; 136 | } 137 | 138 | .ct-parent-plus { 139 | position: absolute; 140 | display: inline-block; 141 | width: 1rem; 142 | padding-top: 0.25rem; 143 | cursor: pointer; 144 | font-weight: bold; 145 | font-size: 1rem; 146 | text-align: center; 147 | } 148 | 149 | .ct-input-box:focus { 150 | border: 1px solid var(--ct-selection); 151 | outline-width: 0; 152 | } 153 | 154 | .ct-drop-down-container input.ct-multiples-filter { 155 | width: 100%; 156 | padding: var(--ct-padding); 157 | border: none; 158 | border-bottom: 1px solid var(--ct-border-color); 159 | } 160 | --------------------------------------------------------------------------------