[TIC-Web] fix the uncheck funcitons
[archive/20170607/tools/tic.git] / public / src / js / page / package.js
1 define([
2     'jquery',
3     'lodash',
4     'js/util',
5     'js/page/settings',
6     'js/logger'
7 ], function (
8     $,
9     _,
10     Util,
11     Settings,
12     Logger
13 ) {
14     'use strict';
15
16     var logger = Logger('package.js');
17
18     var $tree = $('#tic-package-left-col-tree');
19     var repos = [];
20     var packages = [];
21     var pkgInfo = null;
22     var provides = null;
23     var files = null;
24     var groups = null;
25     var groupId = 0;
26
27     function _getChecked() {
28         var checkedList = [];
29         _.forEach(pkgInfo, function(value, key) {
30             if(value.checked === true) {
31                 checkedList.push(value);
32             }
33         });
34         return checkedList;
35     }
36
37     function _find(name, data) {
38         if (_.isEmpty(data)) {
39             data = pkgInfo;
40         }
41         return data[name];
42     }
43
44     function _findTreeNode(name) {
45         var regular = name.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
46         return $tree.treeview('findNodes', [  '^' + regular + '$' ])[0];
47     }
48
49     function _setDefaultPackage(defaultPackages) {
50         var nodes = []
51         var pickDefault = _.pick(pkgInfo, defaultPackages);
52         _.forEach(pickDefault, function(value, key) {
53             // One or more of the same nodes can exist
54             _.forEach(value.view, function(node) {
55                 nodes.push(node);
56             });
57             // update local state
58             value.checked = true;
59         });
60
61         $tree.treeview('checkNode', [nodes, { silent: true }]);
62         // _.forEach(nodes, function(node) {
63         //     node.state.checked = true;
64         // });
65     }
66
67     // Compares two rpm version numbers (e.g. "1.7.1" or "1.2b").
68     // This function is based on https://gist.github.com/em92/d58944f21c68b69433cefb6c49e0defd
69     function versionCompare(v1, v2, options) {
70         if (!v1 && !v2) {
71             return 0;
72         } else if (!v2) {
73             return 1;
74         } else if (!v1) {
75             return -1;
76         }
77
78         var v1parts = v1.split(/[.+-/_]/);
79         var v2parts = v2.split(/[.+-/_]/);
80
81         function compareParts(v1parts, v2parts, options) {
82             var zeroExtend = options && options.zeroExtend;
83
84             if (zeroExtend) {
85                 while (v1parts.length < v2parts.length) v1parts.push("0");
86                 while (v2parts.length < v1parts.length) v2parts.push("0");
87             }
88
89             for (var i = 0; i < v1parts.length; ++i) {
90                 if (v2parts.length === i) {
91                     return 1;
92                 }
93
94                 var v1part = parseInt(v1parts[i]);
95                 var v2part = parseInt(v2parts[i]);
96                 // (NaN == NaN) -> false
97                 var v1part_is_string = !(v1part === v1part);
98                 var v2part_is_string = !(v2part === v2part);
99                 v1part = v1part_is_string ? v1parts[i] : v1part;
100                 v2part = v2part_is_string ? v2parts[i] : v2part;
101
102                 if (v1part_is_string === v2part_is_string) {
103                     if (v1part_is_string === false) {
104                         // integer compare
105                         if (v1part === v2part) {
106                             continue;
107                         } else if (v1part > v2part) {
108                             return 1;
109                         } else {
110                             return -1;
111                         }
112                     } else {
113                         // letters and numbers in string
114                         // split letters and numbers
115                         var v1subparts = v1part.match(/[a-zA-Z]+|[0-9]+/g);
116                         var v2subparts = v2part.match(/[a-zA-Z]+|[0-9]+/g);
117                         if ( (v1subparts.length === 1) && (v2subparts.length === 1) ) {
118                             // only letters in string
119                             v1part = v1subparts[0];
120                             v2part = v2subparts[0];
121                             if (v1part === v2part) {
122                                 continue;
123                             } else if (v1part > v2part) {
124                                 return 1;
125                             } else {
126                                 return -1;
127                             }
128                         }
129                         var result = compareParts(v1subparts, v2subparts);
130                         if (result === 0) {
131                             continue;
132                         } else {
133                             return result;
134                         }
135                     }
136                 } else {
137                     return v2part_is_string ? 1 : -1;
138                 }
139             }
140
141             if (v1parts.length != v2parts.length) {
142                 return -1;
143             }
144             return 0;
145         }
146
147         return compareParts(v1parts, v2parts, options);
148     }
149
150
151
152     // analyze install-dependency (requires)
153     function _analyzeInstallDependency(checkNode) {
154         var stack = [];
155         var pkg_count = Object.keys(pkgInfo).length
156         var number = 0;
157         var sccNum = 0;
158         var groupNum = 0;
159         var selected = new Array(pkg_count);
160         var scc_id = new Array(pkg_count);
161         var min_num = new Array(pkg_count);
162
163         // init check type
164         _.forEach(pkgInfo, function(value, key) {
165             // check type: -1(unchecked), 0(checked), 1~N(checking)
166             selected[value.id] = value.checked ? 0 : -1;
167             scc_id[value.id] = 0;
168         });
169
170         function _comapre_ver(ver1, ver2, flag) {
171             var epoch1 = ver1.epoch;
172             var epoch2 = ver2.epoch;
173             // epoch is a number (optional, default=0)
174             epoch1 = epoch1 ? epoch1 : 0;
175             epoch2 = epoch2 ? epoch2 : 0;
176
177             if (epoch1 === epoch2) {
178                 var result = versionCompare(ver1.ver, ver2.ver);
179                 // version match (ver1 == ver2)
180                 if (result === 0) {
181                     // flag == true means to compare versions between require and provide
182                     if (!flag || ver1.rel) {
183                         return versionCompare(ver1.rel, ver2.rel);
184                     }
185                     return result;
186                 }
187                 return result;
188             } else if (epoch1 > epoch2) {
189                 return 1
190             } else {
191                 return -1;
192             }
193         }
194
195         function _makeSCC(pkgId) {
196             sccNum += 1;
197             var sccList = [];
198
199             // make scc
200             while(!_.isEmpty(stack)) {
201                 var pkg = stack.pop();
202                 scc_id[pkg.id] = sccNum;
203                 sccList.push(pkg);
204
205                 if (pkg.id === pkgId) {
206                     break;
207                 }
208             }
209
210             // circular depependency
211             if (sccList.length > 1) {
212                 groupId += 1;
213                 var groupPkgList = [];
214                 _.forEach(sccList, function(pkg) {
215                     pkg.group = groupId;
216                     groupPkgList.push(pkg.name);
217                 });
218                 groups[groupId] = groupPkgList;
219                 logger.info('circular dependency: groupId(' + groupId + '), list(' + groupPkgList + ')');
220             }
221         }
222
223         function _createReference(node1, node2) {
224             if (!_.isEmpty(node1.forward) && _.includes(node1.forward, node2.name))
225                 return
226
227             if (!_.isEmpty(node1.forward)) {
228                 node1.forward.push(node2.name);
229             } else {
230                 node1.forward = [node2.name];
231             }
232
233             if (!_.isEmpty(node2.backward)) {
234                 node2.backward.push(node1.name);
235             } else {
236                 node2.backward = [node1.name];
237             }
238         }
239
240         function _select_rpm(capability, require) {
241             if (capability.length === 1) {
242                 return pkgInfo[capability[0].name];
243             }
244
245             var provideList = [];
246             // 1. Choose the rpm included in version from provides
247             if (require.ver) {
248                 _.forEach(capability, function(provide) {
249                     var cmpResult = _comapre_ver(require, provide.data, true);
250                     if (cmpResult === 0 && _.includes(['EQ', 'GE', 'LE'], require.flags)) {
251                         provideList.push(provide);
252                     } else if (cmpResult === 1 && _.includes(['LT', 'LE'], require.flags)) {
253                         provideList.push(provide);
254                     } else if (cmpResult === -1 && _.includes(['GT', 'GE'], require.flags)) {
255                         provideList.push(provide);
256                     }
257                 });
258             } else {
259                 provideList = capability;
260             }
261
262             // error case (the rpm does not exist)
263             if (_.isEmpty(provideList)) {
264                 return null;
265             }
266
267             if (provideList.length === 1) {
268                 return pkgInfo[provideList[0].name];
269             }
270
271             // 2 Select one of the rpms by priority
272             // 2-1. Choose the default rpm or the selected rpm
273             // TODO: default profile rpm should be selected
274             _.forEach(provideList, function(provide) {
275                 var tmpInfo = pkgInfo[provide.name];
276                 if (tmpInfo.checked || selected[tmpInfo.id] >= 1) {
277                     return tmpInfo;
278                 }
279             });
280
281             var maxVersion = null;
282             // # 2-2. Select the latest version of rpm
283             _.forEach(provideList, function(provide) {
284                 if (maxVersion) {
285                     var ret = _comapre_ver(maxVersion.data, provide.data)
286                     if (ret === -1) {
287                         maxVersion = provide;
288                     }
289                 } else {
290                     maxVersion = provide;
291                 }
292             });
293
294             return pkgInfo[maxVersion.name];
295         }
296
297         function _analyzeDep(node) {
298             var pkgId = node.id;
299             number += 1;
300             selected[pkgId] = number;
301             min_num[pkgId] = number;
302             stack.push(node);
303
304             var dependents = {};
305             dependents[node.name] = node;
306
307             // installation dependency analysis of package
308             // TODO: recommends
309             _.forEach(['requires'], function(depTag) {
310                 if (_.has(node, depTag)) {
311                     _.forEach(node.requires, function(require) {
312                         var choose = null;
313
314                         if (_.has(provides, require.name)) {
315                             var capList = provides[require.name];
316                             choose = _select_rpm(capList, require);
317                         } else if (_.has(files, require.name)) {
318                             choose = pkgInfo[files[require.name][0]];
319                         }
320
321                         if (choose) {
322                             // add forward/backward reference
323                             _createReference(node, choose);
324
325                             if (selected[choose.id] === -1) {
326                                 var result = _analyzeDep(choose);
327                                 _.forEach(result, function(value, key) {
328                                      if (!_.has(dependents, key)) {
329                                          dependents[key] = value;
330                                      }
331                                 });
332                                 min_num[pkgId] = Math.min(min_num[pkgId], min_num[choose.id])
333                             } else if (selected[choose.id] >= 1 && scc_id[choose.id] === 0) {
334                                 // cross edge that can not be ignored
335                                 min_num[pkgId] = Math.min(min_num[pkgId], min_num[choose.id])
336                             }
337                         } else {
338                             logger.info('the capability('+require.name+')does not exist. should be checked for error');
339                         }
340                     });
341                 }
342             });
343
344             if (min_num[pkgId] === selected[pkgId]) {
345                 _makeSCC(pkgId);
346             }
347
348             return dependents;
349         }
350
351         return _analyzeDep(checkNode);
352     }
353
354     // analyze uncheck dependency
355     function _analyzeUncheckDependency(uncheckNode) {
356
357         function _checkCircularDependency(node) {
358             var groupId = node.group
359             var groupPkgList = groups[groupId];
360             var groupObj = {};
361
362             // Set object for group
363             _.forEach(groupPkgList, function(pkgName) {
364                 groupObj[pkgName] = null;
365             });
366
367             var isUncheckable = true;
368             _.forEach(groupPkgList, function(pkgName) {
369                 var pkg = _find(pkgName);
370
371                 // the node is selfChecked or uncheckable
372                 if (pkg.selfChecked || !isUncheckable) {
373                     isUncheckable = false;
374                     return false;
375                 }
376
377                 _.forEach(pkg.backward, function(backRef) {
378                     // If node is Referenced by another node (Not a node in the group),
379                     // unable to uncheck group nodes
380                     if (!_.has(groupObj, backRef)) {
381                         isUncheckable = false;
382                         return false;
383                     }
384                 });
385             });
386
387             if (isUncheckable) {
388                 // init visited obj
389                 groupVisited[groupId] = {};
390
391                 // Delete backward/forward reference of group node
392                 _.forEach(groupPkgList, function(pkgName) {
393                     var pkg = _find(pkgName);
394                     // backward ref.
395                     pkg.backward = null;
396                     // visited init
397                     groupVisited[groupId][pkg.name] = -1;
398                 });
399                 logger.info('Group(' + groupId +  ') is uncheckable : ' + groupPkgList);
400                 return true;
401             } else {
402                 logger.info('Group(' + groupId +  ') is Not uncheckable: ' + groupPkgList);
403                 return false;
404             }
405         }
406
407         function _analyzeUncheck(parent, node) {
408             if (!_.isEmpty(parent)) {
409                 // TODO: performance
410                 if (!_.isEmpty(node.backward)) {
411                     var bIndex = node.backward.indexOf(parent.name);
412                     if (bIndex > -1) {
413                         // TODO: performance
414                         // remove backward reference (parent)
415                         node.backward.splice(bIndex, 1);
416                     }
417                 }
418                 // selfCheck node is not unchecked
419                 if (node.selfChecked === true) {
420                     return null;
421
422                 }
423             }
424
425             var uncheckPkgs = null;
426             // Check that the selected node is uncheckable
427             if (!_.isEmpty(node.backward)) {
428                 // check circular dependency
429                 if (!node.group || !_checkCircularDependency(node)) {
430                     return null;
431                 }
432             }
433
434             // the selected node is uncheckable
435             uncheckPkgs = {};
436             uncheckPkgs[node.name] = node;
437             // uncheckable pkg of group
438             if (node.group && !_.isEmpty(groupVisited[node.group])) {
439                 groupVisited[node.group][node.name] = 1;
440             }
441
442             // if selected node has forward references
443             if (!_.isEmpty(node.forward)) {
444                 _.forEach(node.forward, function(fname) {
445                     var forwardNode = _find(fname);
446
447                     // If pkg has a circular dependency and is unchekcable,
448                     // circular dep. pkgs can only be visited once
449                     var fvisit = groupVisited[forwardNode.group];
450                     if (!_.isEmpty(fvisit) && fvisit[fname] === 1) {
451                         return; // continue;
452                     }
453
454                     var result = _analyzeUncheck(node, forwardNode);
455
456                     // updates pkgs for uncheck
457                     if (!_.isEmpty(result)) {
458                         _.forEach(result, function(value, key) {
459                             if (!_.has(uncheckPkgs, key)) {
460                                 uncheckPkgs[key] = value;
461                             }
462                         });
463                     }
464                 });
465                 // forward reference reset
466                 node.forward = null;
467                 node.group = null;
468             }
469
470             return uncheckPkgs;
471         }
472
473         // { 'group_id' : {pkgName: -1, ... }, ... }
474         var groupVisited = {};
475         var uncheckPkgs = _analyzeUncheck(null, uncheckNode);
476
477         // delete groupId from groups
478         _.forEach(groupVisited, function(groupList, groupId) {
479             if (!_.isEmpty(groups[groupId])) {
480                 delete groups[groupId];
481             }
482         });
483
484         return uncheckPkgs;
485     }
486
487     /**
488      * Summary page
489      */
490     function _updateSummary() {
491         var pacakgeImageSize = $('#tic-package-image-size').empty();
492         var packageImageInstalledSize = $('#tic-package-image-installed-size').empty();
493         var packageListBadge = $('#tic-package-list-badge').empty();
494         var packageList = $('#tic-package-list').empty();
495
496         var list = _getChecked();
497         var count = _.size(list);
498
499         var imageSize = _.sumBy(list, function getImageSize(item) {
500             return _.toNumber(item.size || 0);
501         });
502         var imageInstalledSize = _.sumBy(list, function getImageInstalled(item) {
503             return _.toNumber(item.installed || 0);
504         });
505
506         if (_.isNumber(imageSize)) {
507             pacakgeImageSize.html(Util.bytesToSize(imageSize));
508         }
509         if (_.isNumber(imageInstalledSize)) {
510             packageImageInstalledSize.html(Util.bytesToSize(imageInstalledSize));
511         }
512         if (_.isNumber(count)) {
513             packageListBadge.html(count);
514         }
515         if (!_.isEmpty(list)) {
516             packageList.html(_.orderBy(_.map(list, 'name')).join('<br>'));
517         }
518
519         $('#tic-package-create').toggleClass('disabled', count === 0);
520
521         require('js/page/image').updateSummary();
522     }
523
524     /**
525      * Treeview: A node is selected.
526      */
527     function _nodeSelected(event, node) {
528         var text = $('#tic-package-info-text').empty();
529         var version = $('#tic-package-info-version').empty();
530         var arch = $('#tic-package-info-arch').empty();
531         var size = $('#tic-package-info-size').empty();
532         var installedSize = $('#tic-package-info-installed-size').empty();
533         var summary = $('#tic-package-info-summary').empty();
534         var description = $('#tic-package-info-description').empty();
535         var dependency = $('#tic-package-info-dependency').empty();
536         var dependencyBadge = $('#tic-package-info-dependency-badge').empty();
537
538         var pkg = pkgInfo[node.text];
539
540         if (!_.isEmpty(pkg.name)) {
541             text.html(pkg.name);
542         }
543         if (!_.isEmpty(pkg.version)) {
544             if (!_.isEmpty(pkg.version.rel)) {
545                 version.html(pkg.version.ver + '-' + pkg.version.rel);
546             } else {
547                 version.html(pkg.version.ver);
548             }
549         }
550         if (!_.isEmpty(pkg.arch)) {
551             arch.html(pkg.arch);
552         }
553         if (!_.isEmpty(pkg.size)) {
554             size.html(Util.bytesToSize(pkg.size));
555         }
556         if (!_.isEmpty(pkg.installed)) {
557             installedSize.html(Util.bytesToSize(pkg.installed));
558         }
559         if (!_.isEmpty(pkg.summary)) {
560             summary.html(pkg.summary);
561         }
562         if (!_.isEmpty(pkg.description)) {
563             description.html(pkg.description);
564         }
565         // if (!_.isEmpty(info.dependency)) {
566         //     dependencyBadge.html(info.dependency.length)
567         //     dependency.html(_.orderBy(info.dependency).join('<br>'));
568         // }
569     }
570
571     /**
572      * Treeview: A node is checked.
573      */
574     function _onNodeChecked(event, node) {
575         var startTS = performance.now();
576         var localNode = _find(node.text);
577
578         if (_.isEmpty(localNode)){
579             // MISC is virtual pacakge.
580             return;
581         }
582         if (localNode.checked === true) {
583             $tree.treeview('uncheckNode', [node, { silent: false }]);
584             return;
585         }
586
587         logger.info('checked: ' + localNode.name);
588
589         _nodeSelected(event, node);
590         localNode.selfChecked = true;
591
592         // analyze install-dependency (requires)
593         var depPkg = _analyzeInstallDependency(localNode)
594
595         var analyzeTS = performance.now();
596
597         // TODO: temporary code
598         var tempcode = [];
599         _.forEach(depPkg, function(value, key) { tempcode.push(value.name); });
600         logger.info(localNode.name + ' install-dependency(' + tempcode.length +'): ' + tempcode);
601
602         var toggleNode = [];
603         if (!_.isEmpty(depPkg)) {
604             _.forEach(depPkg, function(value, key) {
605                 // update local data
606                 value.checked = true;
607                 // update treeview data
608                 _.forEach(value.view, function(node) {
609                     if (node.state.checked === false) {
610                         toggleNode.push(node);
611                     }
612                 });
613             });
614
615             $tree.treeview('checkNode', [toggleNode, { silent: true }]);
616             _updateSummary();
617
618             var endTS = performance.now();
619             logger.info('[Check] Total time: ' + (endTS - startTS) + 'ms');
620             logger.info('[Check] Analyze dep. time: ' + (analyzeTS - startTS) + 'ms');
621             logger.info('[Check] Update view time: ' + (endTS - analyzeTS) + 'ms');
622         }
623     }
624
625     /**
626      * Treeview: A node is unchecked.
627      */
628     function _onNodeUnchecked(event, node) {
629         var startTS = performance.now();
630         var localNode = _find(node.text);
631
632         if (_.isEmpty(localNode)){
633             // MISC is virtual pacakge.
634             return;
635         }
636         if (localNode.checked === false) {
637             $tree.treeview('checkNode', [node, { silent: false }]);
638             return;
639         }
640
641         logger.info('unchecked: ' + node.text);
642
643         var uncheckPkgs = _analyzeUncheckDependency(localNode);
644
645         var analyzeTS = performance.now();
646
647         // TODO: temporary code
648         var tempcode = [];
649         _.forEach(uncheckPkgs, function(value, key) { tempcode.push(value.name); });
650         logger.info(localNode.name + ' uncheck-dependency(' + tempcode.length +'): ' + tempcode);
651
652         var toggleNode = [];
653         if (!_.isEmpty(uncheckPkgs)) {
654             _.forEach(uncheckPkgs, function(value, key) {
655                 // update local data
656                 value.checked = false;
657                 // update treeview data
658                 _.forEach(value.view, function(node) {
659                     if (node.state.checked === true) {
660                         toggleNode.push(node);
661                     }
662                 });
663             });
664
665             $tree.treeview('uncheckNode', [toggleNode, { silent: true }]);
666             localNode.selfChecked = false;
667             _updateSummary();
668         } else {
669             Util.showAlertDialog('Could not uncheck the \'' +localNode.name + '\'' + '<br> because the \'' +  localNode.backward + '\' packages depends on it');
670
671             // selected node change to check state
672             $tree.treeview('checkNode', [[node], { silent: true }]);
673         }
674
675         var endTS = performance.now();
676         logger.info('[Uncheck] Total time: ' + (endTS - startTS) + 'ms');
677         logger.info('[Uncheck] Analyze dep. time: ' + (analyzeTS - startTS) + 'ms');
678         logger.info('[Uncheck] Update view time: ' + (endTS - analyzeTS) + 'ms');
679     }
680
681     /**
682      * Treeview: Initialize tree data.
683      */
684     function updatePackageTree(rawData) {
685         repos = rawData.repos;
686         pkgInfo = rawData.data.packages
687         provides = rawData.data.provides;
688         files = rawData.data.files;
689         groups = rawData.data.groups;
690
691         _.forEach(groups, function(value, key) {
692             groupId += 1;
693         })
694
695         return new Promise(function (resolve, reject) {
696
697             function _onRendered(event, nodes) {
698                 packages = _.values(nodes);
699                 _.forEach(nodes, function(node, key) {
700                      // add a reference variable for treeview
701                      var pkg = pkgInfo[node.text]
702                      if (pkg) {
703                          if (_.isEmpty(pkg.view)) {
704                              pkg.view = [node];
705                          } else {
706                              pkg.view.push(node);
707                          }
708                      }
709                 });
710
711                 _setDefaultPackage(rawData.defaultpackages);
712                 _updateSummary();
713                 resolve();
714             }
715             function _onNodeUnselected(event, node) {
716                 $('#tic-package-info-text').empty();
717                 $('#tic-package-info-version').empty();
718                 $('#tic-package-info-arch').empty();
719                 $('#tic-package-info-size').empty();
720                 $('#tic-package-info-installed-size').empty();
721                 $('#tic-package-info-summary').empty();
722                 $('#tic-package-info-description').empty();
723                 $('#tic-package-info-dependency').empty();
724                 $('#tic-package-info-dependency-badge').empty();
725             }
726
727             $tree.treeview({
728                 data: rawData.view,
729                 showIcon: false,
730                 showCheckbox: true,
731                 onRendered: _onRendered,
732                 onNodeSelected: _nodeSelected,
733                 onNodeUnselected: _onNodeUnselected,
734                 onNodeChecked: _onNodeChecked,
735                 onNodeUnchecked: _onNodeUnchecked
736             });
737         });
738     }
739
740     function init() {
741         logger.info('init');
742
743         function _filter() {
744             var filterText = $('#tic-package-toolbar-input').val();
745             var matchNodes = $tree.treeview('search', [ filterText, {
746                 ignoreCase: true,     // case insensitive
747                 exactMatch: false,    // like or equals
748                 revealResults: true,  // reveal matching nodes
749             }]);
750
751             _.forEach(packages, function (node) {
752                 node.$el.hide();
753             });
754             if (!_.isEmpty(matchNodes)) {
755                 _.forEach(matchNodes, function (node) {
756                     node.$el.show();
757                 });
758             } else {
759                 if (_.isEmpty(filterText)) {
760                     _.forEach(packages, function (node) {
761                         node.$el.show();
762                     });
763                 }
764             }
765             $('#tic-package-toolbar-input-clear').toggleClass('hidden', _.isEmpty(filterText));
766         }
767         $('#tic-package-toolbar-input').on('input change', _filter);
768
769         function _inputClearBtnHandler() {
770             $('#tic-package-toolbar-input').val('').trigger('change').focus();
771             $(this).toggleClass('hidden', true);
772         }
773         $('#tic-package-toolbar-input-clear').on('click', _inputClearBtnHandler);
774
775         function _checkAllBtnHandler() {
776             $tree.treeview('checkAll', { silent: true });
777             _.forEach(pkgInfo, function(value, key){
778                 value.checked = true;
779                 value.selfChecked = false;
780             });
781             _updateSummary();
782         }
783         $('#tic-package-toolbar-checkall').on('click', _checkAllBtnHandler);
784
785         function _uncheckAllBtnHandler() {
786             // FIXME: bug for state.checked = false in treeview objects
787             $tree.treeview('checkAll', { silent: true });
788             $tree.treeview('uncheckAll', { silent: true });
789             _.forEach(pkgInfo, function(value, key){
790                 value.checked = false;
791                 value.selfChecked = false;
792                 value.forward = null;
793                 value.backward = null;
794                 value.group = null;
795                 groups = {};
796             });
797             _updateSummary();
798         }
799         $('#tic-package-toolbar-uncheckall').on('click', _uncheckAllBtnHandler);
800
801         function _collapseAll() {
802             $tree.treeview('collapseAll');
803         }
804         $('#tic-package-left-col-tree-toolbar-collapse-all').on('click', _collapseAll);
805
806         function _expandAll() {
807             $tree.treeview('expandAll');
808         }
809         $('#tic-package-left-col-tree-toolbar-expand-all').on('click', _expandAll);
810
811         // Patch: Bootstrap dropdown menu not working (not dropping down when clicked)
812         $('.dropdown-toggle').dropdown();
813     }
814
815     init();
816
817     return {
818         /**
819          * Initialize for treeview
820          * @method updatePackageTree
821          * @param {array} array of objects
822          * @return Promise
823          */
824         updatePackageTree: updatePackageTree,
825
826         /**
827          * Get checked package nodes
828          * @method getCheckedPackages
829          * @return {array} array of objects
830          */
831         getCheckedPackages: _getChecked
832     }
833 });