├── 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 |
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 | ' [Select All] "
131 | );
132 | };
133 |
134 | ComboTree.prototype.createSourceSubItemsHTML = function (
135 | subItems,
136 | parentId,
137 | collapse = false
138 | ) {
139 | let subItemsHtml =
140 | '';
149 |
150 | if (parentId === false && this.options.isMultiple && this.options.selectAll)
151 | subItemsHtml += this.createSelectAllHTMLForMultiSelect();
152 |
153 | for (let i = 0; i < subItems.length; i++) {
154 | subItemsHtml += this.createSourceItemHTML(subItems[i]);
155 | }
156 | subItemsHtml += " ";
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 |
--------------------------------------------------------------------------------