PageSourceSearch

https://macmachine.ru/assets/js/bootstrap-treeview.min.js

js macmachine.ru collected 2026-10-05 22:27:41 UTC 42,357 bytes, 1,245 lines download raw bytes

1/* =========================================================
2 * bootstrap-treeview.js v1.3.0
3 * =========================================================
4 * Copyright 2021 DealiAxy
5 * Project URL : https://github.com/Deali-Axy/bootstrap5-treeview
6 * 
7 * (fork from) Copyright 2013 Jonathan Miles
8 * (fork from) Project URL : http://www.jondmiles.com/bootstrap-treeview
9 *
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
21 * ========================================================= */
22
23;(function ($, window, document, undefined) {
24
25    /*global jQuery, console*/
26
27    'use strict';
28
29    let pluginName = 'treeview';
30
31    let _default = {};
32
33    _default.settings = {
34
35        injectStyle: true,
36
37        levels: 2,
38
39        expandIcon: "fa fa-chevron-right",
40        collapseIcon: "fa fa-chevron-down",
41        emptyIcon: 'fa',
42        nodeIcon: '',
43        selectedIcon: '',
44        checkedIcon: 'fa fa-check-circle-o',
45        uncheckedIcon: 'fa fa-circle-thin',
46
47        color: undefined, // '#000000',
48        backColor: undefined, // '#FFFFFF',
49        borderColor: undefined, // '#dddddd',
50        onhoverColor: '#F5F5F5',
51        selectedColor: '#FFFFFF',
52        selectedBackColor: '#428bca',
53        searchResultColor: '#D9534F',
54        searchResultBackColor: undefined, //'#FFFFFF',
55
56        enableLinks: false,
57        enableIndent: true, // 添加了控制是否启用缩进的开关
58        highlightSelected: true,
59        highlightSearchResults: true,
60        showBorder: true,
61        showIcon: true,
62        showCheckbox: false,
63        showTags: false,
64        multiSelect: false,
65
66        // Event handlers
67        onNodeChecked: undefined,
68        onNodeCollapsed: undefined,
69        onNodeDisabled: undefined,
70        onNodeEnabled: undefined,
71        onNodeExpanded: undefined,
72        onNodeSelected: undefined,
73        onNodeUnchecked: undefined,
74        onNodeUnselected: undefined,
75        onSearchComplete: undefined,
76        onSearchCleared: undefined
77    };
78
79    _default.options = {
80        silent: false,
81        ignoreChildren: false
82    };
83
84    _default.searchOptions = {
85        ignoreCase: true,
86        exactMatch: false,
87        revealResults: true
88    };
89
90    let Tree = function (element, options) {
91
92        this.$element = $(element);
93        this.elementId = element.id;
94        this.styleId = this.elementId + '-style';
95
96        this.init(options);
97
98        return {
99
100            // Options (public access)
101            options: this.options,
102
103            // Initialize / destroy methods
104            init: $.proxy(this.init, this),
105            remove: $.proxy(this.remove, this),
106
107            // Get methods
108            getNode: $.proxy(this.getNode, this),
109            getParent: $.proxy(this.getParent, this),
110            getSiblings: $.proxy(this.getSiblings, this),
111            getSelected: $.proxy(this.getSelected, this),
112            getUnselected: $.proxy(this.getUnselected, this),
113            getExpanded: $.proxy(this.getExpanded, this),
114            getCollapsed: $.proxy(this.getCollapsed, this),
115            getChecked: $.proxy(this.getChecked, this),
116            getUnchecked: $.proxy(this.getUnchecked, this),
117            getDisabled: $.proxy(this.getDisabled, this),
118            getEnabled: $.proxy(this.getEnabled, this),
119
120            // Select methods
121            selectNode: $.proxy(this.selectNode, this),
122            unselectNode: $.proxy(this.unselectNode, this),
123            toggleNodeSelected: $.proxy(this.toggleNodeSelected, this),
124
125            // Expand / collapse methods
126            collapseAll: $.proxy(this.collapseAll, this),
127            collapseNode: $.proxy(this.collapseNode, this),
128            expandAll: $.proxy(this.expandAll, this),
129            expandNode: $.proxy(this.expandNode, this),
130            toggleNodeExpanded: $.proxy(this.toggleNodeExpanded, this),
131            revealNode: $.proxy(this.revealNode, this),
132
133            // Expand / collapse methods
134            checkAll: $.proxy(this.checkAll, this),
135            checkNode: $.proxy(this.checkNode, this),
136            uncheckAll: $.proxy(this.uncheckAll, this),
137            uncheckNode: $.proxy(this.uncheckNode, this),
138            toggleNodeChecked: $.proxy(this.toggleNodeChecked, this),
139
140            // Disable / enable methods
141            disableAll: $.proxy(this.disableAll, this),
142            disableNode: $.proxy(this.disableNode, this),
143            enableAll: $.proxy(this.enableAll, this),
144            enableNode: $.proxy(this.enableNode, this),
145            toggleNodeDisabled: $.proxy(this.toggleNodeDisabled, this),
146
147            // Search methods
148            search: $.proxy(this.search, this),
149            clearSearch: $.proxy(this.clearSearch, this)
150        };
151    };
152
153    Tree.prototype.init = function (options) {
154
155        this.tree = [];
156        this.nodes = [];
157
158        if (options.data) {
159            if (typeof options.data === 'string') {
160                options.data = $.parseJSON(options.data);
161            }
162            this.tree = $.extend(true, [], options.data);
163            delete options.data;
164        }
165        this.options = $.extend({}, _default.settings, options);
166
167        this.destroy();
168        this.subscribeEvents();
169        this.setInitialStates({nodes: this.tree}, 0);
170        this.render();
171    };
172
173    Tree.prototype.remove = function () {
174        this.destroy();
175        $.removeData(this, pluginName);
176        $('#' + this.styleId).remove();
177    };
178
179    Tree.prototype.destroy = function () {
180
181        if (!this.initialized) return;
182
183        this.$wrapper.remove();
184        this.$wrapper = null;
185
186        // Switch off events
187        this.unsubscribeEvents();
188
189        // Reset this.initialized flag
190        this.initialized = false;
191    };
192
193    Tree.prototype.unsubscribeEvents = function () {
194
195        this.$element.off('click');
196        this.$element.off('nodeChecked');
197        this.$element.off('nodeCollapsed');
198        this.$element.off('nodeDisabled');
199        this.$element.off('nodeEnabled');
200        this.$element.off('nodeExpanded');
201        this.$element.off('nodeSelected');
202        this.$element.off('nodeUnchecked');
203        this.$element.off('nodeUnselected');
204        this.$element.off('searchComplete');
205        this.$element.off('searchCleared');
206    };
207
208    Tree.prototype.subscribeEvents = function () {
209
210        this.unsubscribeEvents();
211
212        this.$element.on('click', $.proxy(this.clickHandler, this));
213
214        if (typeof (this.options.onNodeChecked) === 'function') {
215            this.$element.on('nodeChecked', this.options.onNodeChecked);
216        }
217
218        if (typeof (this.options.onNodeCollapsed) === 'function') {
219            this.$element.on('nodeCollapsed', this.options.onNodeCollapsed);
220        }
221
222        if (typeof (this.options.onNodeDisabled) === 'function') {
223            this.$element.on('nodeDisabled', this.options.onNodeDisabled);
224        }
225
226        if (typeof (this.options.onNodeEnabled) === 'function') {
227            this.$element.on('nodeEnabled', this.options.onNodeEnabled);
228        }
229
230        if (typeof (this.options.onNodeExpanded) === 'function') {
231            this.$element.on('nodeExpanded', this.options.onNodeExpanded);
232        }
233
234        if (typeof (this.options.onNodeSelected) === 'function') {
235            this.$element.on('nodeSelected', this.options.onNodeSelected);
236        }
237
238        if (typeof (this.options.onNodeUnchecked) === 'function') {
239            this.$element.on('nodeUnchecked', this.options.onNodeUnchecked);
240        }
241
242        if (typeof (this.options.onNodeUnselected) === 'function') {
243            this.$element.on('nodeUnselected', this.options.onNodeUnselected);
244        }
245
246        if (typeof (this.options.onSearchComplete) === 'function') {
247            this.$element.on('searchComplete', this.options.onSearchComplete);
248        }
249
250        if (typeof (this.options.onSearchCleared) === 'function') {
251            this.$element.on('searchCleared', this.options.onSearchCleared);
252        }
253    };
254
255    /*
256        Recurse the tree structure and ensure all nodes have
257        valid initial states.  User defined states will be preserved.
258        For performance we also take this opportunity to
259        index nodes in a flattened structure
260    */
261    Tree.prototype.setInitialStates = function (node, level) {
262
263        if (!node.nodes) return;
264        level += 1;
265
266        let parent = node;
267        let _this = this;
268        $.each(node.nodes, function checkStates(index, node) {
269
270            // nodeId : unique, incremental identifier
271            node.nodeId = _this.nodes.length;
272
273            // parentId : transversing up the tree
274            node.parentId = parent.nodeId;
275
276            // if not provided set selectable default value
277            if (!node.hasOwnProperty('selectable')) {
278                node.selectable = false;
279            }
280
281            // where provided we should preserve states
282            node.state = node.state || {};
283
284            // set checked state; unless set always false
285            if (!node.state.hasOwnProperty('checked')) {
286                node.state.checked = false;
287            }
288
289            // set enabled state; unless set always false
290            if (!node.state.hasOwnProperty('disabled')) {
291                node.state.disabled = false;
292            }
293
294            // set expanded state; if not provided based on levels
295            if (!node.state.hasOwnProperty('expanded')) {
296                if (!node.state.disabled &&
297                    (level < _this.options.levels) &&
298                    (node.nodes && node.nodes.length > 0)) {
299                    node.state.expanded = true;
300                } else {
301                    node.state.expanded = false;
302                }
303            }
304
305            // set selected state; unless set always false
306            if (!node.state.hasOwnProperty('selected')) {
307                node.state.selected = false;
308            }
309
310            // index nodes in a flattened structure for use later
311            _this.nodes.push(node);
312
313            // recurse child nodes and transverse the tree
314            if (node.nodes) {
315                _this.setInitialStates(node, level);
316            }
317        });
318    };
319
320    Tree.prototype.clickHandler = function (event) {
321
322        if (!this.options.enableLinks) event.preventDefault();
323
324        let target = $(event.target);
325        let node = this.findNode(target);
326        if (!node || node.state.disabled) return;
327
328        let classList = target.attr('class') ? target.attr('class').split(' ') : [];
329        if ((classList.indexOf('expand-icon') !== -1)) {
330
331            this.toggleExpandedState(node, _default.options);
332            this.render();
333        } else if ((classList.indexOf('check-icon') !== -1)) {
334
335            this.toggleCheckedState(node, _default.options);
336            this.render();
337        } else {
338
339            if (node.selectable) {
340                this.toggleSelectedState(node, _default.options);
341            } else {
342                this.toggleExpandedState(node, _default.options);
343            }
344
345            this.render();
346        }
347    };
348
349    // Looks up the DOM for the closest parent list item to retrieve the
350    // data attribute nodeid, which is used to lookup the node in the flattened structure.
351    Tree.prototype.findNode = function (target) {
352
353        let nodeId = target.closest('li.list-group-item').attr('data-nodeid');
354        let node = this.nodes[nodeId];
355
356        if (!node) {
357            console.log('Error: node does not exist');
358        }
359        return node;
360    };
361
362    Tree.prototype.toggleExpandedState = function (node, options) {
363        if (!node) return;
364        this.setExpandedState(node, !node.state.expanded, options);
365    };
366
367    Tree.prototype.setExpandedState = function (node, state, options) {
368
369        if (state === node.state.expanded) return;
370
371        if (state && node.nodes) {
372
373            // Expand a node
374            node.state.expanded = true;
375            if (!options.silent) {
376                this.$element.trigger('nodeExpanded', $.extend(true, {}, node));
377            }
378        } else if (!state) {
379
380            // Collapse a node
381            node.state.expanded = false;
382            if (!options.silent) {
383                this.$element.trigger('nodeCollapsed', $.extend(true, {}, node));
384            }
385
386            // Collapse child nodes
387            if (node.nodes && !options.ignoreChildren) {
388                $.each(node.nodes, $.proxy(function (index, node) {
389                    this.setExpandedState(node, false, options);
390                }, this));
391            }
392        }
393    };
394
395    Tree.prototype.toggleSelectedState = function (node, options) {
396        if (!node) return;
397        this.setSelectedState(node, !node.state.selected, options);
398    };
399
400    Tree.prototype.setSelectedState = function (node, state, options) {
401
402        if (state === node.state.selected) return;
403
404        if (state) {
405
406            // If multiSelect false, unselect previously selected
407            if (!this.options.multiSelect) {
408                $.each(this.findNodes('true', 'g', 'state.selected'), $.proxy(function (index, node) {
409                    this.setSelectedState(node, false, options);
410                }, this));
411            }
412
413            // Continue selecting node
414            node.state.selected = true;
415            if (!options.silent) {
416                this.$element.trigger('nodeSelected', $.extend(true, {}, node));
417            }
418        } else {
419
420            // Unselect node
421            node.state.selected = false;
422            if (!options.silent) {
423                this.$element.trigger('nodeUnselected', $.extend(true, {}, node));
424            }
425        }
426    };
427
428    Tree.prototype.toggleCheckedState = function (node, options) {
429        if (!node) return;
430        this.setCheckedState(node, !node.state.checked, options);
431    };
432
433    Tree.prototype.setCheckedState = function (node, state, options) {
434
435        if (state === node.state.checked) return;
436
437        if (state) {
438
439            // Check node
440            node.state.checked = true;
441
442            if (!options.silent) {
443                this.$element.trigger('nodeChecked', $.extend(true, {}, node));
444            }
445        } else {
446
447            // Uncheck node
448            node.state.checked = false;
449            if (!options.silent) {
450                this.$element.trigger('nodeUnchecked', $.extend(true, {}, node));
451            }
452        }
453    };
454
455    Tree.prototype.setDisabledState = function (node, state, options) {
456
457        if (state === node.state.disabled) return;
458
459        if (state) {
460
461            // Disable node
462            node.state.disabled = true;
463
464            // Disable all other states
465            this.setExpandedState(node, false, options);
466            this.setSelectedState(node, false, options);
467            this.setCheckedState(node, false, options);
468
469            if (!options.silent) {
470                this.$element.trigger('nodeDisabled', $.extend(true, {}, node));
471            }
472        } else {
473
474            // Enabled node
475            node.state.disabled = false;
476            if (!options.silent) {
477                this.$element.trigger('nodeEnabled', $.extend(true, {}, node));
478            }
479        }
480    };
481
482    Tree.prototype.render = function () {
483
484        if (!this.initialized) {
485
486            // Setup first time only components
487            this.$element.addClass(pluginName);
488            this.$wrapper = $(this.template.list);
489
490            this.injectStyle();
491
492            this.initialized = true;
493        }
494
495        this.$element.empty().append(this.$wrapper.empty());
496
497        // Build tree
498        this.buildTree(this.tree, 0);
499    };
500
501    // Starting from the root node, and recursing down the
502    // structure we build the tree one node at a time
503    Tree.prototype.buildTree = function (nodes, level) {
504
505        if (!nodes) return;
506        level += 1;
507
508        let _this = this;
509        $.each(nodes, function addNodes(id, node) {
510
511            // 最外层包装
512            let treeItem = $(_this.template.itemWrapper)
513                .addClass('node-' + _this.elementId)
514                .addClass(node.state.checked ? 'node-checked' : '')
515                .addClass(node.state.disabled ? 'node-disabled' : '')
516                .addClass(node.state.selected ? 'node-selected' : '')
517                .addClass(node.searchResult ? 'search-result' : '')
518                .attr('data-nodeid', node.nodeId)
519                .attr('style', _this.buildStyleOverride(node));
520
521            // item 内分成两个元素,用flex布局分布在左右两边
522            let treeItemLeft = $(_this.template.itemLeftElem)
523            let treeItemRight = $(_this.template.itemRightElem)
524            treeItem.append(treeItemLeft)
525            treeItem.append(treeItemRight)
526
527            // Add indent/spacer to mimic tree structure
528            // 添加了控制是否启用缩进的开关
529            if (_this.options.enableIndent) {
530                for (let i = 0; i < (level - 1); i++) {
531                    treeItemLeft.append(_this.template.indent);
532                }
533            }
534
535            // Add expand, collapse or empty spacer icons
536            let classList = [];
537            if (node.nodes) {
538                classList.push('expand-icon');
539                if (node.state.expanded) {
540                    classList.push(_this.options.collapseIcon);
541                } else {
542                    classList.push(_this.options.expandIcon);
543                }
544            } else {
545                classList.push(_this.options.emptyIcon);
546            }
547
548            treeItemLeft.append(
549                $(_this.template.icon).addClass(classList.join(' '))
550            );
551
552
553            // Add node icon
554            if (_this.options.showIcon) {
555
556                let classList = ['node-icon'];
557
558                classList.push(node.icon || _this.options.nodeIcon);
559                if (node.state.selected) {
560                    classList.pop();
561                    classList.push(node.selectedIcon || _this.options.selectedIcon ||
562                        node.icon || _this.options.nodeIcon);
563                }
564
565                treeItemLeft.append(
566                    $(_this.template.icon).addClass(classList.join(' '))
567                );
568            }
569
570            // Add check / unchecked icon
571            if (_this.options.showCheckbox) {
572
573                let classList = ['check-icon'];
574                if (node.state.checked) {
575                    classList.push(_this.options.checkedIcon);
576                } else {
577                    classList.push(_this.options.uncheckedIcon);
578                }
579
580                treeItemLeft.append(
581                    $(_this.template.icon).addClass(classList.join(' '))
582                );
583            }
584
585            // Add text
586            if (_this.options.enableLinks) {
587                // Add hyperlink
588                treeItemLeft.append(
589                    $(_this.template.link)
590                        .attr('href', node.href)
591                        .append(node.text)
592                );
593            } else {
594                // otherwise just text
595                treeItemLeft.append(node.text);
596            }
597
598            // Add tags as badges
599            if (_this.options.showTags && node.tags) {
600                $.each(node.tags, function addTag(id, tag) {
601                    treeItemRight.append(
602                        $(_this.template.badge).append(tag)
603                    );
604                });
605            }
606
607            // Add item to the tree
608            _this.$wrapper.append(treeItem);
609
610            // Recursively add child ndoes
611            if (node.nodes && node.state.expanded && !node.state.disabled) {
612                return _this.buildTree(node.nodes, level);
613            }
614        });
615    };
616
617    // Define any node level style override for
618    // 1. selectedNode
619    // 2. node|data assigned color overrides
620    Tree.prototype.buildStyleOverride = function (node) {
621
622        if (node.state.disabled) return '';
623
624        let color = node.color;
625        let backColor = node.backColor;
626
627        if (this.options.highlightSelected && node.state.selected) {
628            if (this.options.selectedColor) {
629                color = this.options.selectedColor;
630            }
631            if (this.options.selectedBackColor) {
632                backColor = this.options.selectedBackColor;
633            }
634        }
635
636        if (this.options.highlightSearchResults && node.searchResult && !node.state.disabled) {
637            if (this.options.searchResultColor) {
638                color = this.options.searchResultColor;
639            }
640            if (this.options.searchResultBackColor) {
641                backColor = this.options.searchResultBackColor;
642            }
643        }
644
645        return 'color:' + color +
646            ';background-color:' + backColor + ';';
647    };
648
649    // Add inline style into head
650    Tree.prototype.injectStyle = function () {
651
652        if (this.options.injectStyle && !document.getElementById(this.styleId)) {
653            $('<style type="text/css" id="' + this.styleId + '"> ' + this.buildStyle() + ' </style>').appendTo('head');
654        }
655    };
656
657    // Construct trees style based on user options
658    Tree.prototype.buildStyle = function () {
659
660        let style = '.node-' + this.elementId + '{';
661
662        if (this.options.color) {
663            style += 'color:' + this.options.color + ';';
664        }
665
666        if (this.options.backColor) {
667            style += 'background-color:' + this.options.backColor + ';';
668        }
669
670        if (!this.options.showBorder) {
671            style += 'border:none;';
672        } else if (this.options.borderColor) {
673            style += 'border:1px solid ' + this.options.borderColor + ';';
674        }
675        style += '}';
676
677        if (this.options.onhoverColor) {
678            style += '.node-' + this.elementId + ':not(.node-disabled):hover{' +
679                'background-color:' + this.options.onhoverColor + ';' +
680                '}';
681        }
682
683        return this.css + style;
684    };
685
686    Tree.prototype.template = {
687        list: '<ul class="list-group"></ul>',
688        itemWrapper: '<li class="list-group-item d-flex justify-content-between align-items-start"></li>',
689        itemLeftElem: '<div class="w-100"></div>',
690        itemRightElem: '<div></div>',
691        indent: '<span class="mx-1"></span>',
692        icon: '<span class="icon"></span>',
693        //link: '<a class="w-75" href="#" style="display:inline-block; color:inherit; text-decoration:none;"></a>',
694        link: '<a class="w-75" style="display:inline-block; color:inherit; text-decoration:none;"></a>',
695        badge: '<span class="ml-1 badge bg-primary rounded-pill"></span>'
696    };
697
698    Tree.prototype.css = '.treeview .list-group-item{cursor:pointer}' +
699        // '.treeview span.indent{margin-left:5px;margin-right:5px}' +
700        '.treeview span.icon{width:12px;margin-right:5px}' +
701        '.treeview .node-disabled{color:silver;cursor:not-allowed}'
702
703
704    /**
705     Returns a single node object that matches the given node id.
706     @param {Number} nodeId - A node's unique identifier
707     @return {Object} node - Matching node
708     */
709    Tree.prototype.getNode = function (nodeId) {
710        return this.nodes[nodeId];
711    };
712
713    /**
714     Returns the parent node of a given node, if valid otherwise returns undefined.
715     @param {Object|Number} identifier - A valid node or node id
716     @returns {Object} node - The parent node
717     */
718    Tree.prototype.getParent = function (identifier) {
719        let node = this.identifyNode(identifier);
720        return this.nodes[node.parentId];
721    };
722
723    /**
724     Returns an array of sibling nodes for a given node, if valid otherwise returns undefined.
725     @param {Object|Number} identifier - A valid node or node id
726     @returns {Array} nodes - Sibling nodes
727     */
728    Tree.prototype.getSiblings = function (identifier) {
729        let node = this.identifyNode(identifier);
730        let parent = this.getParent(node);
731        let nodes = parent ? parent.nodes : this.tree;
732        return nodes.filter(function (obj) {
733            return obj.nodeId !== node.nodeId;
734        });
735    };
736
737    /**
738     Returns an array of selected nodes.
739     @returns {Array} nodes - Selected nodes
740     */
741    Tree.prototype.getSelected = function () {
742        return this.findNodes('true', 'g', 'state.selected');
743    };
744
745    /**
746     Returns an array of unselected nodes.
747     @returns {Array} nodes - Unselected nodes
748     */
749    Tree.prototype.getUnselected = function () {
750        return this.findNodes('false', 'g', 'state.selected');
751    };
752
753    /**
754     Returns an array of expanded nodes.
755     @returns {Array} nodes - Expanded nodes
756     */
757    Tree.prototype.getExpanded = function () {
758        return this.findNodes('true', 'g', 'state.expanded');
759    };
760
761    /**
762     Returns an array of collapsed nodes.
763     @returns {Array} nodes - Collapsed nodes
764     */
765    Tree.prototype.getCollapsed = function () {
766        return this.findNodes('false', 'g', 'state.expanded');
767    };
768
769    /**
770     Returns an array of checked nodes.
771     @returns {Array} nodes - Checked nodes
772     */
773    Tree.prototype.getChecked = function () {
774        return this.findNodes('true', 'g', 'state.checked');
775    };
776
777    /**
778     Returns an array of unchecked nodes.
779     @returns {Array} nodes - Unchecked nodes
780     */
781    Tree.prototype.getUnchecked = function () {
782        return this.findNodes('false', 'g', 'state.checked');
783    };
784
785    /**
786     Returns an array of disabled nodes.
787     @returns {Array} nodes - Disabled nodes
788     */
789    Tree.prototype.getDisabled = function () {
790        return this.findNodes('true', 'g', 'state.disabled');
791    };
792
793    /**
794     Returns an array of enabled nodes.
795     @returns {Array} nodes - Enabled nodes
796     */
797    Tree.prototype.getEnabled = function () {
798        return this.findNodes('false', 'g', 'state.disabled');
799    };
800
801
802    /**
803     Set a node state to selected
804     @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
805     @param {optional Object} options
806     */
807    Tree.prototype.selectNode = function (identifiers, options) {
808        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
809            this.setSelectedState(node, true, options);
810        }, this));
811
812        this.render();
813    };
814
815    /**
816     Set a node state to unselected
817     @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
818     @param {optional Object} options
819     */
820    Tree.prototype.unselectNode = function (identifiers, options) {
821        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
822            this.setSelectedState(node, false, options);
823        }, this));
824
825        this.render();
826    };
827
828    /**
829     Toggles a node selected state; selecting if unselected, unselecting if selected.
830     @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
831     @param {optional Object} options
832     */
833    Tree.prototype.toggleNodeSelected = function (identifiers, options) {
834        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
835            this.toggleSelectedState(node, options);
836        }, this));
837
838        this.render();
839    };
840
841
842    /**
843     Collapse all tree nodes
844     @param {optional Object} options
845     */
846    Tree.prototype.collapseAll = function (options) {
847        let identifiers = this.findNodes('true', 'g', 'state.expanded');
848        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
849            this.setExpandedState(node, false, options);
850        }, this));
851
852        this.render();
853    };
854
855    /**
856     Collapse a given tree node
857     @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
858     @param {optional Object} options
859     */
860    Tree.prototype.collapseNode = function (identifiers, options) {
861        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
862            this.setExpandedState(node, false, options);
863        }, this));
864
865        this.render();
866    };
867
868    /**
869     Expand all tree nodes
870     @param {optional Object} options
871     */
872    Tree.prototype.expandAll = function (options) {
873        options = $.extend({}, _default.options, options);
874
875        if (options && options.levels) {
876            this.expandLevels(this.tree, options.levels, options);
877        } else {
878            let identifiers = this.findNodes('false', 'g', 'state.expanded');
879            this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
880                this.setExpandedState(node, true, options);
881            }, this));
882        }
883
884        this.render();
885    };
886
887    /**
888     Expand a given tree node
889     @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
890     @param {optional Object} options
891     */
892    Tree.prototype.expandNode = function (identifiers, options) {
893        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
894            this.setExpandedState(node, true, options);
895            if (node.nodes && (options && options.levels)) {
896                this.expandLevels(node.nodes, options.levels - 1, options);
897            }
898        }, this));
899
900        this.render();
901    };
902
903    Tree.prototype.expandLevels = function (nodes, level, options) {
904        options = $.extend({}, _default.options, options);
905
906        $.each(nodes, $.proxy(function (index, node) {
907            this.setExpandedState(node, (level > 0) ? true : false, options);
908            if (node.nodes) {
909                this.expandLevels(node.nodes, level - 1, options);
910            }
911        }, this));
912    };
913
914    /**
915     Reveals a given tree node, expanding the tree from node to root.
916     @param {Object|Number|Array} identifiers - A valid node, node id or array of node identifiers
917     @param {optional Object} options
918     */
919    Tree.prototype.revealNode = function (identifiers, options) {
920        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
921            let parentNode = this.getParent(node);
922            while (parentNode) {
923                this.setExpandedState(parentNode, true, options);
924                parentNode = this.getParent(parentNode);
925            }
926            ;
927        }, this));
928
929        this.render();
930    };
931
932    /**
933     Toggles a nodes expanded state; collapsing if expanded, expanding if collapsed.
934     @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
935     @param {optional Object} options
936     */
937    Tree.prototype.toggleNodeExpanded = function (identifiers, options) {
938        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
939            this.toggleExpandedState(node, options);
940        }, this));
941
942        this.render();
943    };
944
945
946    /**
947     Check all tree nodes
948     @param {optional Object} options
949     */
950    Tree.prototype.checkAll = function (options) {
951        let identifiers = this.findNodes('false', 'g', 'state.checked');
952        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
953            this.setCheckedState(node, true, options);
954        }, this));
955
956        this.render();
957    };
958
959    /**
960     Check a given tree node
961     @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
962     @param {optional Object} options
963     */
964    Tree.prototype.checkNode = function (identifiers, options) {
965        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
966            this.setCheckedState(node, true, options);
967        }, this));
968
969        this.render();
970    };
971
972    /**
973     Uncheck all tree nodes
974     @param {optional Object} options
975     */
976    Tree.prototype.uncheckAll = function (options) {
977        let identifiers = this.findNodes('true', 'g', 'state.checked');
978        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
979            this.setCheckedState(node, false, options);
980        }, this));
981
982        this.render();
983    };
984
985    /**
986     Uncheck a given tree node
987     @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
988     @param {optional Object} options
989     */
990    Tree.prototype.uncheckNode = function (identifiers, options) {
991        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
992            this.setCheckedState(node, false, options);
993        }, this));
994
995        this.render();
996    };
997
998    /**
999     Toggles a nodes checked state; checking if unchecked, unchecking if checked.
1000     @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1001     @param {optional Object} options
1002     */
1003    Tree.prototype.toggleNodeChecked = function (identifiers, options) {
1004        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1005            this.toggleCheckedState(node, options);
1006        }, this));
1007
1008        this.render();
1009    };
1010
1011
1012    /**
1013     Disable all tree nodes
1014     @param {optional Object} options
1015     */
1016    Tree.prototype.disableAll = function (options) {
1017        let identifiers = this.findNodes('false', 'g', 'state.disabled');
1018        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1019            this.setDisabledState(node, true, options);
1020        }, this));
1021
1022        this.render();
1023    };
1024
1025    /**
1026     Disable a given tree node
1027     @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1028     @param {optional Object} options
1029     */
1030    Tree.prototype.disableNode = function (identifiers, options) {
1031        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1032            this.setDisabledState(node, true, options);
1033        }, this));
1034
1035        this.render();
1036    };
1037
1038    /**
1039     Enable all tree nodes
1040     @param {optional Object} options
1041     */
1042    Tree.prototype.enableAll = function (options) {
1043        let identifiers = this.findNodes('true', 'g', 'state.disabled');
1044        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1045            this.setDisabledState(node, false, options);
1046        }, this));
1047
1048        this.render();
1049    };
1050
1051    /**
1052     Enable a given tree node
1053     @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1054     @param {optional Object} options
1055     */
1056    Tree.prototype.enableNode = function (identifiers, options) {
1057        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1058            this.setDisabledState(node, false, options);
1059        }, this));
1060
1061        this.render();
1062    };
1063
1064    /**
1065     Toggles a nodes disabled state; disabling is enabled, enabling if disabled.
1066     @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1067     @param {optional Object} options
1068     */
1069    Tree.prototype.toggleNodeDisabled = function (identifiers, options) {
1070        this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1071            this.setDisabledState(node, !node.state.disabled, options);
1072        }, this));
1073
1074        this.render();
1075    };
1076
1077
1078    /**
1079     Common code for processing multiple identifiers
1080     */
1081    Tree.prototype.forEachIdentifier = function (identifiers, options, callback) {
1082
1083        options = $.extend({}, _default.options, options);
1084
1085        if (!(identifiers instanceof Array)) {
1086            identifiers = [identifiers];
1087        }
1088
1089        $.each(identifiers, $.proxy(function (index, identifier) {
1090            callback(this.identifyNode(identifier), options);
1091        }, this));
1092    };
1093
1094    /*
1095        Identifies a node from either a node id or object
1096    */
1097    Tree.prototype.identifyNode = function (identifier) {
1098        return ((typeof identifier) === 'number') ?
1099            this.nodes[identifier] :
1100            identifier;
1101    };
1102
1103    /**
1104     Searches the tree for nodes (text) that match given criteria
1105     @param {String} pattern - A given string to match against
1106     @param {optional Object} options - Search criteria options
1107     @return {Array} nodes - Matching nodes
1108     */
1109    Tree.prototype.search = function (pattern, options) {
1110        options = $.extend({}, _default.searchOptions, options);
1111
1112        this.clearSearch({render: false});
1113
1114        let results = [];
1115        if (pattern && pattern.length > 0) {
1116
1117            if (options.exactMatch) {
1118                pattern = '^' + pattern + '$';
1119            }
1120
1121            let modifier = 'g';
1122            if (options.ignoreCase) {
1123                modifier += 'i';
1124            }
1125
1126            results = this.findNodes(pattern, modifier);
1127
1128            // Add searchResult property to all matching nodes
1129            // This will be used to apply custom styles
1130            // and when identifying result to be cleared
1131            $.each(results, function (index, node) {
1132                node.searchResult = true;
1133            })
1134        }
1135
1136        // If revealResults, then render is triggered from revealNode
1137        // otherwise we just call render.
1138        if (options.revealResults) {
1139            this.revealNode(results);
1140        } else {
1141            this.render();
1142        }
1143
1144        this.$element.trigger('searchComplete', $.extend(true, {}, results));
1145
1146        return results;
1147    };
1148
1149    /**
1150     Clears previous search results
1151     */
1152    Tree.prototype.clearSearch = function (options) {
1153
1154        options = $.extend({}, {render: true}, options);
1155
1156        let results = $.each(this.findNodes('true', 'g', 'searchResult'), function (index, node) {
1157            node.searchResult = false;
1158        });
1159
1160        if (options.render) {
1161            this.render();
1162        }
1163
1164        this.$element.trigger('searchCleared', $.extend(true, {}, results));
1165    };
1166
1167    /**
1168     Find nodes that match a given criteria
1169     @param {String} pattern - A given string to match against
1170     @param {optional String} modifier - Valid RegEx modifiers
1171     @param {optional String} attribute - Attribute to compare pattern against
1172     @return {Array} nodes - Nodes that match your criteria
1173     */
1174    Tree.prototype.findNodes = function (pattern, modifier, attribute) {
1175
1176        modifier = modifier || 'g';
1177        attribute = attribute || 'text';
1178
1179        let _this = this;
1180        return $.grep(this.nodes, function (node) {
1181            let val = _this.getNodeValue(node, attribute);
1182            if (typeof val === 'string') {
1183                return val.match(new RegExp(pattern, modifier));
1184            }
1185        });
1186    };
1187
1188    /**
1189     Recursive find for retrieving nested attributes values
1190     All values are return as strings, unless invalid
1191     @param {Object} obj - Typically a node, could be any object
1192     @param {String} attr - Identifies an object property using dot notation
1193     @return {String} value - Matching attributes string representation
1194     */
1195    Tree.prototype.getNodeValue = function (obj, attr) {
1196        let index = attr.indexOf('.');
1197        if (index > 0) {
1198            let _obj = obj[attr.substring(0, index)];
1199            let _attr = attr.substring(index + 1, attr.length);
1200            return this.getNodeValue(_obj, _attr);
1201        } else {
1202            if (obj.hasOwnProperty(attr)) {
1203                return obj[attr].toString();
1204            } else {
1205                return undefined;
1206            }
1207        }
1208    };
1209
1210    let logError = function (message) {
1211        if (window.console) {
1212            window.console.error(message);
1213        }
1214    };
1215
1216    // Prevent against multiple instantiations,
1217    // handle updates and method calls
1218    $.fn[pluginName] = function (options, args) {
1219
1220        let result;
1221
1222        this.each(function () {
1223            let _this = $.data(this, pluginName);
1224            if (typeof options === 'string') {
1225                if (!_this) {
1226                    logError('Not initialized, can not call method : ' + options);
1227                } else if (!$.isFunction(_this[options]) || options.charAt(0) === '_') {
1228                    logError('No such method : ' + options);
1229                } else {
1230                    if (!(args instanceof Array)) {
1231                        args = [args];
1232                    }
1233                    result = _this[options].apply(_this, args);
1234                }
1235            } else if (typeof options === 'boolean') {
1236                result = _this;
1237            } else {
1238                $.data(this, pluginName, new Tree(this, $.extend(true, {}, options)));
1239            }
1240        });
1241
1242        return result || this;
1243    };
1244
1245})(jQuery, window, document);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.