PageSourceSearch

https://berezlib.ru/bitrix/templates/simai.sf4biblio/components/si…/sf-tree-modify/script.js?159254483045729

js berezlib.ru collected 2026-10-02 12:25:24 UTC 45,729 bytes, 1,250 lines download raw bytes

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

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.