<commit_before>export default function(ngapp, xelib) {
    var mainTreeViewController = function($scope, $element, $timeout, xelibService, stylesheetService) {
        // TODO: Load this from disk
        $scope.columns = [
            {
                label: "FormID",
                canSort: true,
                width: '315px',
                getData: function(node) {
                    switch (node.element_type) {
                        case 'etFile':
                            return xelib.DisplayName(node.handle);
                        case 'etGroupRecord':
                            // TODO: include signature as well based on setting
                            return xelib.Name(node.handle);
                        case 'etMainRecord':
                            if (node.fid == 0) {
                                return 'File Header';
                            } else {
                                return xelibService.intToHex(node.fid, 8);
                            }
                    }
                }
            },
            {
                label: "EditorID",
                canSort: true,
                width: '150px',
                getData: function(node) {
                    if (node.element_type === 'etMainRecord' && node.fid > 0) {
                        return xelib.EditorID(node.handle, true);
                    }
                }
            },
            {
                label: "Name",
                canSort: true,
                getData: function(node) {
                    if (node.element_type === 'etMainRecord' && node.fid > 0) {
                        return xelib.FullName(node.handle, true);
                    }
                }
            }
        ];

        var tree;
        $scope.sort = {
            column: 'FormID',
            reverse: false
        };

        $scope.buildTree = function() {
            tree = xelib.GetElements(0, '', $scope.sort.column).map(function(handle) {
                return $scope.buildNode(handle, -1);
            });
            if ($scope.sort.reverse) tree.reverse();
            $scope.data.tree = tree;
        };

        var getNewNode = function(node) {
            let handles = xelib.GetDuplicateHandles(node.handle);
            for (let i = 0; i < handles.length; i++) {
                let handle = handles[i];
                let newNode = tree.find((node) => { return node.handle == handle; });
                if (newNode) return newNode;
            }
        };

        var reExpandNode = function(node) {
            let newNode = getNewNode(node);
            if (newNode) {
                $scope.getNodeData(newNode);
                $scope.expandNode(newNode);
            }
        };

        var reSelectNode = function(node, scroll) {
            let newNode = getNewNode(node);
            if (newNode) {
                selectSingle(newNode, true, true, false);
                if (scroll) scrollToNode(newNode, true);
            }
        };

        var freeHandles = function(nodes1, nodes2) {
            let a = [];
            nodes1.forEach((n) => a.contains(n.handle) || a.push(n.handle));
            nodes2.forEach((n) => a.contains(n.handle) || a.push(n.handle));
            a.forEach((handle) => xelib.Release(handle));
        };

        $scope.reloadNodes = function() {
            let start = Date.now();
            $scope.reloading = true;
            let oldExpandedNodes = [];
            let oldSelectedNodes = selectedNodes.slice();
            tree.forEach(function(node) {
                if (node.expanded) {
                    oldExpandedNodes.push(node);
                } else if (!node.selected) {
                    xelib.Release(node.handle);
                }
            });
            $scope.clearSelection(true);
            $scope.buildTree();
            oldExpandedNodes.forEach((n) => reExpandNode(n));
            oldSelectedNodes.forEach((n, i, a) => reSelectNode(n, i == a.length - 1));
            freeHandles(oldExpandedNodes, oldSelectedNodes);
            console.log(`Rebuilt tree (${tree.length} nodes) in ${Date.now() - start}ms`);
        };

        $scope.toggleSort = function(column) {
            let oldReverse = $scope.sort.reverse;
            if ($scope.sort.column !== column.label) {
                $scope.sort.column = column.label;
                $scope.sort.reverse = false;
            } else {
                $scope.sort.reverse = !$scope.sort.reverse;
            }
            let reverseChanged = oldReverse != $scope.sort.reverse;
            $scope.reloadNodes(reverseChanged);
        };

        var mainStylesheet = stylesheetService.getStylesheet(1);
        $scope.columnResized = function(index, width) {
            let selector = `.main-tree-view .column-${index}`;
            let rule = stylesheetService.getRule(mainStylesheet, selector);
            if (!rule) {
                stylesheetService.makeRule(mainStylesheet, selector, `width: ${width};`);
            } else {
                rule.style["width"] = width;
            }
        };

        $scope.toggleColumnsModal = function(visible) {
            $scope.showColumnsModal = visible;
        };

        $scope.getNodeStatus = function(node) {
            let status = {
                modified: false //xelib.GetModified(handle)
            };
            if (node.element_type === xelib.etMainRecord) {
                status.override = xelib.IsOverride(node.handle);
                status.injected = xelib.IsInjected(node.handle);
                status.conflict = 'ctNoConflict'; //xelib.ConflictThis(node.handle);
            }
            node.status = status;
        };

        $scope.getChildrenCount = function(node) {
            if (node.element_type === xelib.etMainRecord) {
                let childGroup = xelib.GetElement(node.handle, 'Child Group', true);
                node.children_count = childGroup && xelib.ElementCount(childGroup);
            } else {
                node.children_count = xelib.ElementCount(node.handle);
            }
        };

        $scope.buildColumnValues = function(node) {
            node.column_values = $scope.columns.map(function(column) {
                try {
                    return column.getData(node) || "";
                } catch (x) {
                    console.log(x);
                    return "";
                }
            }).trimFalsy();
        };

        $scope.getNodeData = function(node) {
            node.element_type = xelib.ElementType(node.handle);
            if (node.element_type === xelib.etMainRecord) {
                node.fid = xelib.GetFormID(node.handle);
                node.is_record = true;
            }
            $scope.getNodeStatus(node);
            $scope.getChildrenCount(node);
            $scope.buildColumnValues(node);
            return node;
        };

        $scope.buildNode = function(handle, depth) {
            return {
                handle: handle,
                depth: depth + 1
            }
        };

        $scope.buildNodes = function(node) {
            let path = node.element_type === xelib.etMainRecord ? 'Child Group' : '';
            let nodes = xelib.GetElements(node.handle, path, $scope.sort.column).map(function(handle) {
                return $scope.buildNode(handle, node.depth);
            });
            if ($scope.sort.reverse) nodes.reverse();
            return nodes;
        };

        $scope.expandNode = function(node) {
            let start = Date.now();
            node.expanded = true;
            let children = $scope.buildNodes(node);
            let childrenLength = children.length;
            if (childrenLength > 0) {
                node.children_count = childrenLength;
                children.forEach((child) => child.parent = node);
                let insertionIndex = tree.indexOf(node) + 1;
                tree.splice(insertionIndex, 0, ...children);
                console.log(`Built ${childrenLength} nodes in ${Date.now() - start}ms`);
            } else {
                node.children_count = 0;
                node.expanded = false;
            }
        };

        $scope.collapseNode = function(node) {
            if (node.expanded) delete node.expanded;
            let startIndex = tree.indexOf(node) + 1,
                endIndex = startIndex;
            for (; endIndex < tree.length; endIndex++) {
                let child = tree[endIndex];
                if (child.depth <= node.depth) break;
                if (child.selected) selectSingle(child, false);
            }
            let removedNodes = tree.splice(startIndex, endIndex - startIndex);
            removedNodes.forEach((node) => xelib.Release(node.handle));
            if (prevNode && prevNode.parent === node) {
                prevNode = undefined;
            }
        };

        $scope.toggleNode = function(e, node) {
            if (node.expanded) {
                $scope.collapseNode(node);
            } else {
                $scope.expandNode(node);
            }
            e.stopPropagation();
        };

        var selectedNodes = [];
        var lastRange = [];
        var prevNode;
        $scope.clearSelection = function(clearPrevNode) {
            lastRange = [];
            selectedNodes.forEach((node) => node.selected = false);
            selectedNodes = [];
            if (clearPrevNode) prevNode = undefined;
        };

        var scrollTo = function(offset) {
            treeElement.scrollTop = offset;
        };

        var nodeHeight = 20;
        var scrollToNode = function(node, center = false) {
            let index = tree.indexOf(node);
            let nodeOffset = index * nodeHeight;
            let scrollOffset = treeElement.scrollTop;
            let height = treeElement.clientHeight;
            if (center) {
                scrollTo(nodeOffset - Math.floor(height / 2));
            } else if (scrollOffset + height < nodeOffset + nodeHeight) {
                scrollTo(nodeOffset - height + nodeHeight);
            } else if (scrollOffset > nodeOffset) {
                scrollTo(nodeOffset);
            }
        };

        var selectSingle = function(node, newValue, setPrev = true, scroll = true) {
            if (!node || newValue && node.selected) return;
            if (selectedNodes.length > 0 && node.depth != prevNode.depth) return;
            node.selected = angular.isDefined(newValue) ? newValue : !node.selected;
            selectedNodes[node.selected ? 'push' : 'remove'](node);
            if (node.selected) {
                if (setPrev) prevNode = node;
                if (scroll) scrollToNode(node);
            }
        };

        var persistRange = function(start, end) {
            if (start > lastRange[0]) {
                selectSingle(tree[lastRange[0]], false, false);
            }
            if (end < lastRange[1]) {
                selectSingle(tree[lastRange[1]], false, false);
            }
        };

        var selectRange = function(n1, n2, persist = false) {
            if (n1.depth !== n2.depth) return;
            let i1 = tree.indexOf(n1),
                i2 = tree.indexOf(n2),
                startIndex = Math.min(i1, i2),
                endIndex = Math.max(i1, i2),
                targetDepth = n1.depth;
            persist && lastRange.length && persistRange(startIndex, endIndex);
            lastRange = [startIndex, endIndex];
            if (i1 < i2) {
                for (let i = i2; i >= i1; i--) {
                    let node = tree[i];
                    if (node.depth === targetDepth) {
                        selectSingle(node, true, false);
                    }
                }
            } else {
                for (let i = i2; i <= i1; i++) {
                    let node = tree[i];
                    if (node.depth === targetDepth) {
                        selectSingle(node, true, false);
                    }
                }
            }
        };

        $scope.selectNode = function(e, node) {
            lastRange = [];
            if (!e.ctrlKey) $scope.clearSelection();
            if (e.shiftKey && prevNode) {
                selectRange(node, prevNode);
            } else {
                selectSingle(node);
            }
            e.stopPropagation();
        };

        // expand node or navigate to first child when right arrow is pressed
        var handleRightArrow = function(e) {
            let node = selectedNodes.last();
            if (!node || !node.children_count) return;
            $scope.clearSelection();
            if (!node.expanded) {
                $scope.expandNode(node);
                selectSingle(node);
            } else {
                let index = tree.indexOf(node) + 1;
                selectSingle(tree[index]);
            }
        };

        // navigate to parent or collapse node when left arrow is pressed
        var handleLeftArrow = function(e) {
            let node = selectedNodes.last();
            if (!node) return;
            $scope.clearSelection();
            if (node.expanded) {
                $scope.collapseNode(node);
                selectSingle(node);
            } else {
                selectSingle(node.parent);
            }
        };

        var findNextNode = function(node, sameDepth = true) {
            let index = tree.indexOf(node);
            if (!sameDepth) {
                return tree[index + 1];
            } else {
                let targetDepth = node.depth;
                for (let i = index + 1; i < tree.length; i++) {
                    let n = tree[i];
                    if (n.depth == targetDepth) return n;
                    if (n.depth < targetDepth) return;
                }
            }
        };

        //navigate down a node when down arrow is pressed
        var handleDownArrow = function(e) {
            let node = selectedNodes.last();
            if (!node) return;
            let targetNode = findNextNode(node, e.shiftKey);
            if (!targetNode) return;
            if (e.shiftKey) {
                selectRange(targetNode, prevNode, true);
                scrollToNode(targetNode);
            } else {
                $scope.clearSelection();
                selectSingle(targetNode);
            }
        };

        var findPreviousNode = function(node, sameDepth = true) {
            let index = tree.indexOf(node);
            if (!sameDepth) {
                return tree[index - 1];
            } else {
                let targetDepth = node.depth;
                for (let i = index - 1; i >= 0; i++) {
                    let n = tree[i];
                    if (n.depth == targetDepth) return n;
                    if (n.depth < targetDepth) return;
                }
            }
        };

        //navigate up a node when up arrow is pressed
        var handleUpArrow = function(e) {
            let node = selectedNodes.last();
            if (!node) return;
            let targetNode = findPreviousNode(node, e.shiftKey);
            if (!targetNode) return;
            if (e.shiftKey) {
                selectRange(targetNode, prevNode, true);
                scrollToNode(targetNode);
            } else {
                $scope.clearSelection();
                selectSingle(targetNode);
            }
        };

        $scope.onKeyDown = function(e) {
            if (e.keyCode == 39) {
                handleRightArrow(e);
            } else if (e.keyCode == 37) {
                handleLeftArrow(e);
            } else if (e.keyCode == 40) {
                handleDownArrow(e)
            } else if (e.keyCode == 38) {
                handleUpArrow(e);
            } else {
                return;
            }
            e.stopPropagation();
            e.preventDefault();
        };

        // initialize tree
        $scope.data = $scope.$parent.tab.data;
        $scope.buildTree();

        var treeElement;
        $timeout(function() {
            treeElement = $element[0].nextElementSibling.lastElementChild;
        }, 10);
    };

    ngapp.service('mainTreeViewFactory', function() {
        let factory = this;

        this.releaseChildren = function(node) {
            node.children.forEach(function(child) {
                if (child.children) factory.releaseChildren(child);
                xelib.Release(child.handle);
            });
        };

        this.destroy = function(view) {
            factory.releaseChildren(view.data.tree);
        };

        this.new = function() {
            return {
                templateUrl: 'partials/mainTreeView.html',
                controller: mainTreeViewController,
                class: 'main-tree-view',
                data: {
                    tabLabel: 'Tree View',
                    tree: {}
                },
                destroy: factory.destroy
            }
        };
    });

    ngapp.run(function(viewFactory, mainTreeViewFactory) {
        viewFactory.registerView('mainTreeView', mainTreeViewFactory.new);
    });
};<commit_msg>now building columns on the main tree view dynamically<commit_after>export default function(ngapp, xelib) {
    var mainTreeViewController = function($scope, $element, $timeout, xelibService, stylesheetService, columnsService) {
        var tree;
        $scope.allColumns = columnsService.columns;
        $scope.sort = {
            column: 'FormID',
            reverse: false
        };

        $scope.buildColumns = function() {
            $scope.columns = $scope.allColumns.filter((column) => { return column.enabled; });
            let width = $scope.columns.reduce(function(width, c) {
                if (c.width) width += parseInt(c.width.slice(0, -1));
                return width;
            }, 0);
            if (width > 100) {
                let defaultWidth = Math.floor(100 / $scope.columns.length) + '%';
                $scope.columns.slice(0, -1).forEach((column) => column.width = defaultWidth);
            }
            $scope.resizeColumns();
        };

        $scope.buildTree = function() {
            tree = xelib.GetElements(0, '', $scope.sort.column).map(function(handle) {
                return $scope.buildNode(handle, -1);
            });
            if ($scope.sort.reverse) tree.reverse();
            $scope.data.tree = tree;
        };

        var getNewNode = function(node) {
            let handles = xelib.GetDuplicateHandles(node.handle);
            for (let i = 0; i < handles.length; i++) {
                let handle = handles[i];
                let newNode = tree.find((node) => { return node.handle == handle; });
                if (newNode) return newNode;
            }
        };

        var reExpandNode = function(node) {
            let newNode = getNewNode(node);
            if (newNode) {
                $scope.getNodeData(newNode);
                $scope.expandNode(newNode);
            }
        };

        var reSelectNode = function(node, scroll) {
            let newNode = getNewNode(node);
            if (newNode) {
                selectSingle(newNode, true, true, false);
                if (scroll) scrollToNode(newNode, true);
            }
        };

        var freeHandles = function(nodes1, nodes2) {
            let a = [];
            nodes1.forEach((n) => a.contains(n.handle) || a.push(n.handle));
            nodes2.forEach((n) => a.contains(n.handle) || a.push(n.handle));
            a.forEach((handle) => xelib.Release(handle));
        };

        $scope.reloadNodes = function() {
            let start = Date.now();
            $scope.reloading = true;
            let oldExpandedNodes = [];
            let oldSelectedNodes = selectedNodes.slice();
            tree.forEach(function(node) {
                if (node.expanded) {
                    oldExpandedNodes.push(node);
                } else if (!node.selected) {
                    xelib.Release(node.handle);
                }
            });
            $scope.clearSelection(true);
            $scope.buildTree();
            oldExpandedNodes.forEach((n) => reExpandNode(n));
            oldSelectedNodes.forEach((n, i, a) => reSelectNode(n, i == a.length - 1));
            freeHandles(oldExpandedNodes, oldSelectedNodes);
            console.log(`Rebuilt tree (${tree.length} nodes) in ${Date.now() - start}ms`);
        };

        $scope.toggleSort = function(column) {
            let oldReverse = $scope.sort.reverse;
            if ($scope.sort.column !== column.label) {
                $scope.sort.column = column.label;
                $scope.sort.reverse = false;
            } else {
                $scope.sort.reverse = !$scope.sort.reverse;
            }
            let reverseChanged = oldReverse != $scope.sort.reverse;
            $scope.reloadNodes(reverseChanged);
        };

        var mainStylesheet = stylesheetService.getStylesheet(1);
        $scope.columnResized = function(index, width) {
            let selector = `.main-tree-view .column-${index}`;
            let rule = stylesheetService.getRule(mainStylesheet, selector);
            if (!rule) {
                stylesheetService.makeRule(mainStylesheet, selector, `width: ${width};`);
            } else {
                rule.style["width"] = width;
            }
        };

        $scope.resizeColumns = function() {
            $scope.columns.forEach(function(column, index) {
                if (column.width) $scope.columnResized(index, column.width)
            });
        };

        $scope.toggleColumnsModal = function(visible) {
            $scope.showColumnsModal = visible;
        };

        $scope.getNodeStatus = function(node) {
            let status = {
                modified: false //xelib.GetModified(handle)
            };
            if (node.element_type === xelib.etMainRecord) {
                status.override = xelib.IsOverride(node.handle);
                status.injected = xelib.IsInjected(node.handle);
                status.conflict = 'ctNoConflict'; //xelib.ConflictThis(node.handle);
            }
            node.status = status;
        };

        $scope.getChildrenCount = function(node) {
            if (node.element_type === xelib.etMainRecord) {
                let childGroup = xelib.GetElement(node.handle, 'Child Group', true);
                node.children_count = childGroup && xelib.ElementCount(childGroup);
            } else {
                node.children_count = xelib.ElementCount(node.handle);
            }
        };

        $scope.buildColumnValues = function(node) {
            node.column_values = $scope.columns.map(function(column) {
                try {
                    if (column.getData) return column.getData(node) || "";
                    return eval(column.getDataCode) || "";
                } catch (x) {
                    console.log(x);
                    return "";
                }
            }).trimFalsy();
        };

        $scope.getNodeData = function(node) {
            node.element_type = xelib.ElementType(node.handle);
            if (node.element_type === xelib.etMainRecord) {
                node.fid = xelib.GetFormID(node.handle);
                node.is_record = true;
            }
            $scope.getNodeStatus(node);
            $scope.getChildrenCount(node);
            $scope.buildColumnValues(node);
            return node;
        };

        $scope.buildNode = function(handle, depth) {
            return {
                handle: handle,
                depth: depth + 1
            }
        };

        $scope.buildNodes = function(node) {
            let path = node.element_type === xelib.etMainRecord ? 'Child Group' : '';
            let nodes = xelib.GetElements(node.handle, path, $scope.sort.column).map(function(handle) {
                return $scope.buildNode(handle, node.depth);
            });
            if ($scope.sort.reverse) nodes.reverse();
            return nodes;
        };

        $scope.expandNode = function(node) {
            let start = Date.now();
            node.expanded = true;
            let children = $scope.buildNodes(node);
            let childrenLength = children.length;
            if (childrenLength > 0) {
                node.children_count = childrenLength;
                children.forEach((child) => child.parent = node);
                let insertionIndex = tree.indexOf(node) + 1;
                tree.splice(insertionIndex, 0, ...children);
                console.log(`Built ${childrenLength} nodes in ${Date.now() - start}ms`);
            } else {
                node.children_count = 0;
                node.expanded = false;
            }
        };

        $scope.collapseNode = function(node) {
            if (node.expanded) delete node.expanded;
            let startIndex = tree.indexOf(node) + 1,
                endIndex = startIndex;
            for (; endIndex < tree.length; endIndex++) {
                let child = tree[endIndex];
                if (child.depth <= node.depth) break;
                if (child.selected) selectSingle(child, false);
            }
            let removedNodes = tree.splice(startIndex, endIndex - startIndex);
            removedNodes.forEach((node) => xelib.Release(node.handle));
            if (prevNode && prevNode.parent === node) {
                prevNode = undefined;
            }
        };

        $scope.toggleNode = function(e, node) {
            if (node.expanded) {
                $scope.collapseNode(node);
            } else {
                $scope.expandNode(node);
            }
            e.stopPropagation();
        };

        var selectedNodes = [];
        var lastRange = [];
        var prevNode;
        $scope.clearSelection = function(clearPrevNode) {
            lastRange = [];
            selectedNodes.forEach((node) => node.selected = false);
            selectedNodes = [];
            if (clearPrevNode) prevNode = undefined;
        };

        var scrollTo = function(offset) {
            treeElement.scrollTop = offset;
        };

        var nodeHeight = 20;
        var scrollToNode = function(node, center = false) {
            let index = tree.indexOf(node);
            let nodeOffset = index * nodeHeight;
            let scrollOffset = treeElement.scrollTop;
            let height = treeElement.clientHeight;
            if (center) {
                scrollTo(nodeOffset - Math.floor(height / 2));
            } else if (scrollOffset + height < nodeOffset + nodeHeight) {
                scrollTo(nodeOffset - height + nodeHeight);
            } else if (scrollOffset > nodeOffset) {
                scrollTo(nodeOffset);
            }
        };

        var selectSingle = function(node, newValue, setPrev = true, scroll = true) {
            if (!node || newValue && node.selected) return;
            if (selectedNodes.length > 0 && node.depth != prevNode.depth) return;
            node.selected = angular.isDefined(newValue) ? newValue : !node.selected;
            selectedNodes[node.selected ? 'push' : 'remove'](node);
            if (node.selected) {
                if (setPrev) prevNode = node;
                if (scroll) scrollToNode(node);
            }
        };

        var persistRange = function(start, end) {
            if (start > lastRange[0]) {
                selectSingle(tree[lastRange[0]], false, false);
            }
            if (end < lastRange[1]) {
                selectSingle(tree[lastRange[1]], false, false);
            }
        };

        var selectRange = function(n1, n2, persist = false) {
            if (n1.depth !== n2.depth) return;
            let i1 = tree.indexOf(n1),
                i2 = tree.indexOf(n2),
                startIndex = Math.min(i1, i2),
                endIndex = Math.max(i1, i2),
                targetDepth = n1.depth;
            persist && lastRange.length && persistRange(startIndex, endIndex);
            lastRange = [startIndex, endIndex];
            if (i1 < i2) {
                for (let i = i2; i >= i1; i--) {
                    let node = tree[i];
                    if (node.depth === targetDepth) {
                        selectSingle(node, true, false);
                    }
                }
            } else {
                for (let i = i2; i <= i1; i++) {
                    let node = tree[i];
                    if (node.depth === targetDepth) {
                        selectSingle(node, true, false);
                    }
                }
            }
        };

        $scope.selectNode = function(e, node) {
            lastRange = [];
            if (!e.ctrlKey) $scope.clearSelection();
            if (e.shiftKey && prevNode) {
                selectRange(node, prevNode);
            } else {
                selectSingle(node);
            }
            e.stopPropagation();
        };

        // expand node or navigate to first child when right arrow is pressed
        var handleRightArrow = function(e) {
            let node = selectedNodes.last();
            if (!node || !node.children_count) return;
            $scope.clearSelection();
            if (!node.expanded) {
                $scope.expandNode(node);
                selectSingle(node);
            } else {
                let index = tree.indexOf(node) + 1;
                selectSingle(tree[index]);
            }
        };

        // navigate to parent or collapse node when left arrow is pressed
        var handleLeftArrow = function(e) {
            let node = selectedNodes.last();
            if (!node) return;
            $scope.clearSelection();
            if (node.expanded) {
                $scope.collapseNode(node);
                selectSingle(node);
            } else {
                selectSingle(node.parent);
            }
        };

        var findNextNode = function(node, sameDepth = true) {
            let index = tree.indexOf(node);
            if (!sameDepth) {
                return tree[index + 1];
            } else {
                let targetDepth = node.depth;
                for (let i = index + 1; i < tree.length; i++) {
                    let n = tree[i];
                    if (n.depth == targetDepth) return n;
                    if (n.depth < targetDepth) return;
                }
            }
        };

        //navigate down a node when down arrow is pressed
        var handleDownArrow = function(e) {
            let node = selectedNodes.last();
            if (!node) return;
            let targetNode = findNextNode(node, e.shiftKey);
            if (!targetNode) return;
            if (e.shiftKey) {
                selectRange(targetNode, prevNode, true);
                scrollToNode(targetNode);
            } else {
                $scope.clearSelection();
                selectSingle(targetNode);
            }
        };

        var findPreviousNode = function(node, sameDepth = true) {
            let index = tree.indexOf(node);
            if (!sameDepth) {
                return tree[index - 1];
            } else {
                let targetDepth = node.depth;
                for (let i = index - 1; i >= 0; i++) {
                    let n = tree[i];
                    if (n.depth == targetDepth) return n;
                    if (n.depth < targetDepth) return;
                }
            }
        };

        //navigate up a node when up arrow is pressed
        var handleUpArrow = function(e) {
            let node = selectedNodes.last();
            if (!node) return;
            let targetNode = findPreviousNode(node, e.shiftKey);
            if (!targetNode) return;
            if (e.shiftKey) {
                selectRange(targetNode, prevNode, true);
                scrollToNode(targetNode);
            } else {
                $scope.clearSelection();
                selectSingle(targetNode);
            }
        };

        $scope.onKeyDown = function(e) {
            if (e.keyCode == 39) {
                handleRightArrow(e);
            } else if (e.keyCode == 37) {
                handleLeftArrow(e);
            } else if (e.keyCode == 40) {
                handleDownArrow(e)
            } else if (e.keyCode == 38) {
                handleUpArrow(e);
            } else {
                return;
            }
            e.stopPropagation();
            e.preventDefault();
        };

        // initialization
        $scope.data = $scope.$parent.tab.data;
        $scope.buildColumns();
        $scope.buildTree();

        var treeElement;
        $timeout(function() {
            treeElement = $element[0].nextElementSibling.lastElementChild;
        }, 10);
    };

    ngapp.service('mainTreeViewFactory', function() {
        let factory = this;

        this.releaseChildren = function(node) {
            node.children.forEach(function(child) {
                if (child.children) factory.releaseChildren(child);
                xelib.Release(child.handle);
            });
        };

        this.destroy = function(view) {
            factory.releaseChildren(view.data.tree);
        };

        this.new = function() {
            return {
                templateUrl: 'partials/mainTreeView.html',
                controller: mainTreeViewController,
                class: 'main-tree-view',
                data: {
                    tabLabel: 'Tree View',
                    tree: {}
                },
                destroy: factory.destroy
            }
        };
    });

    ngapp.run(function(viewFactory, mainTreeViewFactory) {
        viewFactory.registerView('mainTreeView', mainTreeViewFactory.new);
    });
};<|endoftext|>


<commit_before>// ==UserScript==
// @name            IPP Show Moar Connections
// @author          Francois Laithier
// @description     In the Intuit Partner Platform app portal, show all your connections by default, instead of just 10.
// @license         MIT License
// @version	        1.0
// @include         http://www.example.org/*
// @include         https://developer.intuit.com/v2/ui#/connection/detailedreport*
// ==/UserScript==

(function(){

    // Get the div that contains the selector, since it almost nothing has an ID
    var selectorContainers = document.getElementsByClassName('ngRowCountPicker');

    if(!selectorContainers.length) {
        console.info('Page size selector not found');
        return;
    }

    var selectors = selectorContainers[0].getElementsByTagName('select');

    if(!selectors.length) {
        console.info('Page size selector not found');
        return;
    }

    // Create a new option by cloning one, to have all the classes and stuff
    var selector = selectors[0];
    var newOption = selector.getElementsByTagName('option')[0].cloneNode();

    newOption.value = "9999999";
    newOption.text = "ALL OF THEM";

    selector.appendChild(newOption);
})();
<commit_msg>Execute when page is loaded<commit_after>// ==UserScript==
// @name            IPP Show Moar Connections
// @author          Francois Laithier
// @description     In the Intuit Partner Platform app portal, show all your connections by default, instead of just 10.
// @license         MIT License
// @version         1.0.2
// @include         https://developer.intuit.com/v2/ui#/connection/detailedreport*
// ==/UserScript==

/* globals console, angular */

window.addEventListener('load', fixConnectionsPagination, false);

function fixConnectionsPagination(){

    // Get the div that contains the selector, since it almost nothing has an ID
    var selectorContainers = document.getElementsByClassName('ngRowCountPicker');

    if(!selectorContainers.length) {
        console.info('Page size selector not found');
        return;
    }

    var selectors = selectorContainers[0].getElementsByTagName('select');

    if(!selectors.length) {
        console.info('Page size selector not found');
        return;
    }

    // Create a new option by cloning one, to have all the classes and stuff
    var selector = selectors[0];
    var newOption = selector.firstElementChild.cloneNode();

    // Update the values and append it to the selector
    newOption.value = "9999999";
    newOption.text = "ALL OF THEM";
    selector.appendChild(newOption);
}
<|endoftext|>


<commit_before>/*
    Copyright (C) 2014 Härnösands kommun

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
/**
 * Add layer view
 * View includes a drag/drop and collapsable layer tree
 */

Ext.define('OpenEMap.view.layer.Add' ,{
    extend: 'OpenEMap.view.layer.Tree',

    requires: [
        'OpenEMap.data.DataHandler',
        'OpenEMap.view.layer.TreeFilter',
        'OpenEMap.action.MetadataInfoColumn'
    ],

    title: 'Lägg till lager',

    width: 250,
    height: 550,

    headerPosition: 'top',
    collapsible: true,
    collapseMode: 'header',
    collapseDirection : 'right',
    titleCollapse: true,

    viewConfig: {
         plugins: {
            ptype: 'treeviewdragdrop',
            enableDrop: false
        },
        copy: true
    },

    // Layer tree filtering
    plugins: {
        ptype: 'treefilter',
        allowParentFolders: true
    },
    dockedItems: [
        {
            xtype: 'toolbar',
            dock: 'top',
            layout: 'fit',
            items: [{
                xtype: 'trigger',
                triggerCls: 'x-form-clear-trigger',
                onTriggerClick: function () {
                    this.reset();
                    this.focus();
                },
                listeners: {
                    change: function (field, newVal) {
                        var tree = field.up('treepanel');
                        tree.filter(newVal);
                    },
                    buffer: 250
                }
            }]
        }
    ],

    initComponent: function() {
        var me = this;
        this.on('checkchange', function(node, checked, eOpts) {
            node.cascadeBy(function(n){
                if(checked) {
                    me.loadLayer(n);
                } else {
                    me.unLoadLayer(n);
                }
            });
        });

        // Add columns
        this.columns = [
            {
                xtype: 'treecolumn',
                flex: 1,
                dataIndex: 'text'
            },
            me.metadataColumn
        ];

        // Create store for the layer tree
        this.store = Ext.create('OpenEMap.data.GroupedLayerTree');

        Ext.Ajax.request({
            url: OpenEMap.wmsURLs.basePath + '?service=WMS&request=GetCapabilities',
            success: function(response) {
                var format = new OpenLayers.Format.WMSCapabilities();
                var wms = format.read(response.responseText);

                var root = this.store.setRootNode({
                });

                //console.log(wms);

                wms.capability.layers.forEach(function(layer) {
                    root.appendChild({
                        'text': layer.name,
                        'isGroupLayer': false,
                        'isSearchable': true,
                        'wms':{
                            'url': OpenEMap.wmsURLs.url,
                            'params': {
                                'layers': layer.name,
                                'format': 'image/png'
                            }
                        }
                    });
                }, this);
            },
            scope: this
        });

        // Create server store
        /*this.serverStore = Ext.create('OpenEMap.data.Servers',{
            proxy: {
                url: OpenEMap.wsUrls.basePath + OpenEMap.wsUrls.servers,
                type: 'ajax',
                reader: {
                    type: 'json',
                    root: 'configs'
                }
            }
        });*/

        // Wait for server load to initiate layer tree
        /*this.serverStore.load({
            callback: function() {
                me.dataHandler.getLayers(function(layers) {
                    if(layers) {
                        var parser = new OpenEMap.config.Parser({
                            serverStore: me.serverStore
                        });
                        var layerTree = parser.parseLayerTree(layers);

                        me.store.setRootNode({
                            text: 'Lager',
                            expanded: true,
                            layers: layerTree
                        });
                    }

                });
            }
        });*/

        this.callParent(arguments);

    },

    /**
    * Load a layer to GeoExt.MapPanel
    * @param {Ext.data.NodeInterface}   node    tree node
    */
    loadLayer: function(node) {
        var layer = node.get('layer');
        if(layer && layer !== '' && this.mapPanel) {
            layer.setVisibility(true);
            layer.displayInLayerSwitcher = false;
            this.mapPanel.layers.add(layer);
        }
    },

    /**
    * Unload a layer from GeoExt.MapPanel
    * @param {Ext.data.NodeInterface}   node    tree node
    */
    unLoadLayer: function(node) {
        var layer = node.get('layer');
        if(layer && layer !== '' && this.mapPanel) {
            this.mapPanel.layers.remove(layer);
        }
    }

});
<commit_msg>Refactoring<commit_after>/*
    Copyright (C) 2014 Härnösands kommun

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
/**
 * Add layer view
 * View includes a drag/drop and collapsable layer tree
 */

Ext.define('OpenEMap.view.layer.Add' ,{
    extend: 'OpenEMap.view.layer.Tree',

    requires: [
        'OpenEMap.data.DataHandler',
        'OpenEMap.view.layer.TreeFilter',
        'OpenEMap.action.MetadataInfoColumn'
    ],

    title: 'Lägg till lager',

    width: 250,
    height: 550,

    headerPosition: 'top',
    collapsible: true,
    collapseMode: 'header',
    collapseDirection : 'right',
    titleCollapse: true,

    viewConfig: {
         plugins: {
            ptype: 'treeviewdragdrop',
            enableDrop: false
        },
        copy: true
    },

    // Layer tree filtering
    plugins: {
        ptype: 'treefilter',
        allowParentFolders: true
    },
    dockedItems: [
        {
            xtype: 'toolbar',
            dock: 'top',
            layout: 'fit',
            items: [{
                xtype: 'trigger',
                triggerCls: 'x-form-clear-trigger',
                onTriggerClick: function () {
                    this.reset();
                    this.focus();
                },
                listeners: {
                    change: function (field, newVal) {
                        var tree = field.up('treepanel');
                        tree.filter(newVal);
                    },
                    buffer: 250
                }
            }]
        }
    ],

    initComponent: function() {
        var me = this;

        this.columns = [
            {
                xtype: 'treecolumn',
                flex: 1,
                dataIndex: 'text'
            },
            me.metadataColumn
        ];

        this.store = Ext.create('OpenEMap.data.GroupedLayerTree');

        Ext.Ajax.request({
            url: OpenEMap.wmsURLs.basePath + '?service=WMS&request=GetCapabilities',
            success: function(response) {
                var format = new OpenLayers.Format.WMSCapabilities();
                var wms = format.read(response.responseText);

                var root = this.store.setRootNode({
                });

                //console.log(wms);

                var children = wms.capability.layers.map(function(layer) {
                    return {
                        'text': layer.name,
                        'leaf': true,
                        'title': layer.title,
                        'isGroupLayer': false,
                        'isSearchable': true,
                        'wms':{
                            'url': OpenEMap.wmsURLs.url,
                            'params': {
                                'layers': layer.name,
                                'format': 'image/png'
                            }
                        }
                    };
                });

                root.appendChild(children);
            },
            scope: this
        });

        this.callParent(arguments);
    },

    parseCapabilities: function(response) {
        var format = new OpenLayers.Format.WMSCapabilities();
        var wms = format.read(response.responseText);

        var root = this.store.setRootNode({});

        wms.capability.layers.forEach(function(layer) {
            root.appendChild({
                'text': layer.name,
                'leaf': true,
                'title': layer.title,
                'isGroupLayer': false,
                'isSearchable': true,
                'wms':{
                    'url': OpenEMap.wmsURLs.url,
                    'params': {
                        'layers': layer.name,
                        'format': 'image/png'
                    }
                }
            });
        }, this);
    }

});
<|endoftext|>


<commit_before>var MonoSoftware;
(function (MonoSoftware) {
    (function (Baasic) {
        var AUTH_HEADER = "Authorization";
        (function (Application) {
            var apiUrl;
            var apiKey;
            var isInitialized = false;
            var userInfoKey = "baasic-user-info";
			var browserLanguage = navigator.language || navigator.userLanguage;

			var settings = {
                useSSL: false,
                defaultLanguage: "en",
                apiRootUrl: "api.baasic.com",
			    apiVersion: "v1"
            };

            var currentUser = JSON.parse(localStorage.getItem(userInfoKey));
            var userAccessTokenTimer = null;
            if (currentUser != null) {
                userAccessTokenTimer = setExpirationTimer(currentUser.token);
                updateCurrentUserObject(currentUser.user, currentUser.token);
            } else {
                updateCurrentUserObject(null, null);
            }

            function setExpirationTimer(token) {
                if (token && token != null && token.expire_time) {
                    var expiresIn = token.expire_time - new Date().getTime();
                    if (expiresIn > 0) {
                        return setTimeout(function () {
                            set_user(null, null);
                        },
                        expiresIn);
                    } else {
                        set_user(null, null);
                    }
                }

                return null;
            }

            $(window).on("storage", function (e) {
                switch (e.originalEvent.key) {
                    case userInfoKey:
                        var value = e.originalEvent.newValue;
                        if (value === undefined || value == null || value == '') {
                            updateCurrentUserObject(null, null);
                        } else {
                            var userObject = JSON.parse(value);
                            updateCurrentUserObject(userObject.user, userObject.token);
                        }
                        break;
                }
            })

            function initialize(apiKey, options) {
                if (!isInitialized) {
                    set_apiKey(apiKey);

                    $.extend(settings, options);

                    var scheme = settings.useSSL ? "https" : "http";
                    apiUrl = scheme + "://" + settings.apiRootUrl + "/" + settings.apiVersion + "/" + apiKey + "/";

                    isInitialized = true;
                }

                return this;
            }
            Application.init = initialize;

            function get_apiKey() {
                return apiKey;
            }
            Application.get_apiKey = get_apiKey;
            function set_apiKey(apiKeyValue) {
                apiKey = apiKeyValue;
            }

            function get_apiUrl() {
                return apiUrl;
            }
            Application.get_apiUrl = get_apiUrl;

            function get_accessToken() {
                return currentUser.token;
            }
            Application.get_accessToken = get_accessToken;

			function update_accessToken(value) {
			    set_user(currentUser.user, value);
            }
			Application.update_accessToken = update_accessToken;

            function get_user() {
                return currentUser;
            }
            Application.get_user = get_user;

            function set_user(userDetails, token) {
                clearTimeout(userAccessTokenTimer);
                if (token === undefined || token == null || userDetails === undefined || userDetails == null) {
                    localStorage.removeItem(userInfoKey);
                    updateCurrentUserObject(null, null);
                } else {
                    updateCurrentUserObject(userDetails, token);
                    localStorage.setItem(userInfoKey, JSON.stringify(currentUser));
                    userAccessTokenTimer = setExpirationTimer(currentUser.token)
                }

                $.event.trigger("userChange", { user: currentUser });
            }
            Application.set_user = set_user;

            function updateCurrentUserObject(userDetails, token) {
                var user = {
                    isAuthenticated: function () {
                        return this.token && this.token != null;
                    }
                };

                if (userDetails != null)
                    user.user = userDetails;

                if (token != null)
                {
                    user.token = token;
                }

                currentUser = user;
            }

			function get_currentLanguage() {
			    if (settings.language) {
			        return settings.language;
                }
                else if (browserLanguage) {
                    return browserLanguage;
                } else {
                    return get_defaultLanguage();
                }
            }
            Application.get_currentLanguage = get_currentLanguage;

            function get_defaultLanguage() {
                return settings.defaultLanguage;
            }
            Application.get_defaultLanguage = get_defaultLanguage;

        })(Baasic.Application || (Baasic.Application = {}));
        var Application = Baasic.Application;
    })(MonoSoftware.Baasic || (MonoSoftware.Baasic = {}));
    var Baasic = MonoSoftware.Baasic;
})(MonoSoftware || (MonoSoftware = {}));<commit_msg>- removed jQuery dependency<commit_after>(function (MonoSoftware) {
    (function (Baasic) {
        var AUTH_HEADER = "Authorization";
        (function (Application) {
            var apiUrl;
            var apiKey;
            var isInitialized = false;
            var userInfoKey = "baasic-user-info";
			var browserLanguage = navigator.language || navigator.userLanguage;

			var settings = {
                useSSL: false,
                defaultLanguage: "en",
                apiRootUrl: "api.baasic.com",
			    apiVersion: "v1"
            };

			function extend(obj1, obj2) {
				for	(var prop in obj2) {
					obj1[prop] = obj2[prop];
				}

				return obj1;
			}

			function addEvent(evnt, elem, func) {
			   if (elem.addEventListener)
				  elem.addEventListener(evnt,func,false);
			   else if (elem.attachEvent) {
				  elem.attachEvent("on"+evnt, func);
			   }
			   else {
				  elem["on" + evnt] = func;
			   }
			}

			function triggerEvent(element, eventName, additionalData) {
					var event; // The custom event that will be created

					if (CustomEvent) {
						event = new CustomEvent(eventName, additionalData);
						element.dispatchEvent(event);

						return;
					}
					else if (document.createEvent) {
						event = document.createEvent("Event");
						event.initEvent(eventName, true, true);

						if (additionalData) {
							extend(event, additionalData);
						}

						if (element.dispatchEvent) {
							element.dispatchEvent(event);
						} else {
							document.dispatchEvent(event);
						}

						return;

					} else {
						event = document.createEventObject();
						event.eventType = eventName;

						if (additionalData) {
							extend(event, additionalData);
						}
						element.fireEvent("on" + event.eventType, event);

						return;
					}

					if (element.dispatchEvent) {
						element.dispatchEvent(event);
					} else if (element.fireEvent) {
						element.fireEvent("on" + event.eventType, event);
					} else {
						var handler = element["on" + event.eventType];
						if (handler) handler(event);
					}
			}

            var currentUser = JSON.parse(localStorage.getItem(userInfoKey));
            var userAccessTokenTimer = null;
            if (currentUser != null) {
                userAccessTokenTimer = setExpirationTimer(currentUser.token);
                updateCurrentUserObject(currentUser.user, currentUser.token);
            } else {
                updateCurrentUserObject(null, null);
            }

            function setExpirationTimer(token) {
                if (token && token != null && token.expire_time) {
                    var expiresIn = token.expire_time - new Date().getTime();
                    if (expiresIn > 0) {
                        return setTimeout(function () {
                            set_user(null, null);
                        },
                        expiresIn);
                    } else {
                        set_user(null, null);
                    }
                }

                return null;
            }

            addEvent("storage", window, function (e) {
				e = e || event;
                switch (e.key) {
                    case userInfoKey:
                        var value = e.newValue;
                        if (value === undefined || value == null || value == '') {
                            updateCurrentUserObject(null, null);
                        } else {
                            var userObject = JSON.parse(value);
                            updateCurrentUserObject(userObject.user, userObject.token);
                        }
                        break;
                }
            });

            function initialize(apiKey, options) {
                if (!isInitialized) {
                    set_apiKey(apiKey);

                    extend(settings, options);

                    var scheme = settings.useSSL ? "https" : "http";
                    apiUrl = scheme + "://" + settings.apiRootUrl + "/" + settings.apiVersion + "/" + apiKey + "/";

                    isInitialized = true;
                }

                return this;
            }
            Application.init = initialize;

            function get_apiKey() {
                return apiKey;
            }
            Application.get_apiKey = get_apiKey;
            function set_apiKey(apiKeyValue) {
                apiKey = apiKeyValue;
            }

            function get_apiUrl() {
                return apiUrl;
            }
            Application.get_apiUrl = get_apiUrl;

            function get_accessToken() {
                return currentUser.token;
            }
            Application.get_accessToken = get_accessToken;

			function update_accessToken(value) {
			    set_user(currentUser.user, value);
            }
			Application.update_accessToken = update_accessToken;

            function get_user() {
                return currentUser;
            }
            Application.get_user = get_user;

            function set_user(userDetails, token) {
                clearTimeout(userAccessTokenTimer);
                if (token === undefined || token == null || userDetails === undefined || userDetails == null) {
                    localStorage.removeItem(userInfoKey);
                    updateCurrentUserObject(null, null);
                } else {
                    updateCurrentUserObject(userDetails, token);
                    localStorage.setItem(userInfoKey, JSON.stringify(currentUser));
                    userAccessTokenTimer = setExpirationTimer(currentUser.token)
                }

				triggerEvent(document, "userChange", { user: currentUser });
            }
            Application.set_user = set_user;

            function updateCurrentUserObject(userDetails, token) {
                var user = {
                    isAuthenticated: function () {
                        return this.token && this.token != null;
                    }
                };

                if (userDetails != null)
                    user.user = userDetails;

                if (token != null)
                {
                    user.token = token;
                }

                currentUser = user;
            }

			function get_currentLanguage() {
			    if (settings.language) {
			        return settings.language;
                }
                else if (browserLanguage) {
                    return browserLanguage;
                } else {
                    return get_defaultLanguage();
                }
            }
            Application.get_currentLanguage = get_currentLanguage;

            function get_defaultLanguage() {
                return settings.defaultLanguage;
            }
            Application.get_defaultLanguage = get_defaultLanguage;

        })(Baasic.Application || (Baasic.Application = {}));
        var Application = Baasic.Application;
    })(MonoSoftware.Baasic || (MonoSoftware.Baasic = {}));
    var Baasic = MonoSoftware.Baasic;
})(MonoSoftware || (MonoSoftware = {}));<|endoftext|>


<commit_before>import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {findDOMNode} from 'react-dom';

import IconButton from '../IconButton';
import TextField from '../TextField';
import Popup from '../Popup';
import List from '../List';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import Dom from '../_vendors/Dom';

import './MultipleSelect.css';

export default class MultipleSelect extends Component {

    constructor(props) {

        super(props);

        this.triggerEl = null;

        this.state = {
            selectedCollapsed: true,
            value: props.value,
            filter: '',
            popupVisible: false,
            isAbove: false
        };

        this.isAbove = this::this.isAbove;
        this.filterData = this::this.filterData;
        this.removeSelected = this::this.removeSelected;
        this.toggleSelectedCollapse = this::this.toggleSelectedCollapse;
        this.focusHandle = this::this.focusHandle;
        this.blurHandle = this::this.blurHandle;
        this.filterChangeHandle = this::this.filterChangeHandle;
        this.closePopup = this::this.closePopup;
        this.popupRenderHandle = this::this.popupRenderHandle;
        this.changeHandle = this::this.changeHandle;
        this.triggerHandle = this::this.triggerHandle;

    }

    isAbove() {

        const multipleSelect = this.refs.multipleSelect;

        if (!this.popupHeight || !multipleSelect) {
            return false;
        }

        const {top} = Dom.getOffset(multipleSelect),
            scrollTop = Dom.getScrollTop();

        if (top + this.triggerHeight + this.popupHeight - scrollTop > window.innerHeight) {
            return true;
        }

        return false;

    }

    filterData(filter = this.state.filter, data = this.props.data) {

        if (!filter) {
            return data;
        }

        const {displayField, filterCallback, isGrouped} = this.props;

        if (filterCallback) {
            return filterCallback(filter, data);
        }

        const filterFunc = (originData) => {
            return originData.filter(item => typeof item === 'object' && !!item[displayField] ?
                item[displayField].toString().toUpperCase().includes(filter.toUpperCase())
                :
                item.toString().toUpperCase().includes(filter.toUpperCase()));
        };

        if (isGrouped) {
            return data.map(group => {

                const children = filterFunc(group.children);

                if (children.length < 1) {
                    return;
                } else {
                    return {
                        ...group,
                        children
                    };
                }

            }).filter(item => !!item);
        }

        return filterFunc(data);

    }

    removeSelected(index) {

        const {value} = this.state;

        if (!value || value.length < 1) {
            return;
        }

        value.splice(index, 1);

        this.setState({
            value
        }, () => {
            const {onChange} = this.props;
            onChange && onChange(value);
        });

    }

    toggleSelectedCollapse() {
        this.setState({
            selectedCollapsed: !this.state.selectedCollapsed
        });
    }

    focusHandle() {

        const {disabled, onFocus} = this.props;

        !disabled && this.setState({
            popupVisible: true
        }, () => {
            onFocus && onFocus();
        });

    }

    blurHandle() {

        const {disabled, onBlur} = this.props;

        !disabled && onBlur && onBlur();

    }

    filterChangeHandle(filter) {
        this.setState({
            filter
        });
    }

    closePopup() {
        this.setState({
            popupVisible: false
        });
    }

    popupRenderHandle(popupEl) {

        this.popupEl = findDOMNode(popupEl);
        this.popupHeight = this.popupEl.offsetHeight;

        const isAbove = this.isAbove();

        if (isAbove !== this.state.isAbove) {
            this.setState({
                isAbove
            });
        }

    }

    changeHandle(value) {

        const {autoClose} = this.props,
            state = {
                value
            };

        if (autoClose) {
            state.popupVisible = false;
        }

        this.setState(state, () => {
            const {onChange} = this.props;
            onChange && onChange(value);
        });

    }

    triggerHandle(el, triggerEl, popupEl, triggerMode, currentVisible) {

        if (!triggerEl) {
            return true;
        }

        while (el) {
            if (el == this.refs.multipleSelect || el == popupEl || el == triggerEl) {
                return currentVisible;
            }
            el = el.parentNode;
        }

        return false;

    }

    componentDidMount() {
        this.triggerEl = findDOMNode(this.refs.trigger);
        this.triggerHeight = this.triggerEl.clientHeight;
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.value !== this.state.value) {
            this.setState({
                value: nextProps.value
            });
        }
    }

    render() {

        const {
                className, popupClassName, style, popupStyle, name, placeholder, isGrouped,
                disabled, iconCls, rightIconCls, valueField, displayField, descriptionField, noMatchedMsg
            } = this.props,
            {selectedCollapsed, isAbove, value, filter, popupVisible} = this.state,

            emptyEl = [{
                renderer() {
                    return (

                            {
                                noMatchedMsg ?
                                    noMatchedMsg
                                    :

                                        No matched value.

                            }

                    );
                }
            }],

            valueLen = (value ? value.length : 0),

            multipleSelectClassName = (valueLen > 0 ? ' not-empty' : '') + (popupVisible ? ' activated' : '')
                + (isAbove ? ' above' : ' blow') + (className ? ' ' + className : ''),
            selectedClassName = (selectedCollapsed ? ' collapsed' : ''),
            selectPopupClassName = (isAbove ? ' above' : ' blow') + (popupClassName ? ' ' + popupClassName : ''),
            selectPopupStyle = Object.assign({
                width: this.triggerEl && getComputedStyle(this.triggerEl).width
            }, popupStyle),

            listData = this.filterData();

        return (

                {
                    name ?
                        <input type="hidden"
                               name={name}
                               value={Util.getValueByValueField(value, valueField, displayField)}/>
                        :
                        null
                }

                {
                    value && valueLen > 0 ?
                        (

                                    {`${valueLen} selected`}

                                {
                                    value.map((item, index) => {

                                        const text = Util.getTextByDisplayField(item, displayField, valueField);

                                        return (

                                                {text}

 {
                                                         this.removeSelected(index);
                                                     }}>
                                                    ×

                                        );
                                    })
                                }

                                <IconButton className="multiple-select-selected-collapse-button"
                                            iconCls="fa fa-angle-double-up"
                                            onTouchTap={this.toggleSelectedCollapse}/>

                        )
                        :
                        null
                }

                <TextField ref="trigger"
                           className="multiple-select-trigger"
                           value={filter}
                           placeholder={placeholder}
                           disabled={disabled}
                           iconCls={iconCls}
                           rightIconCls={rightIconCls || 'fa fa-search'}
                           onFocus={this.focusHandle}
                           onBlur={this.blurHandle}
                           onChange={this.filterChangeHandle}/>

                <Popup className={'multiple-select-popup' + selectPopupClassName}
                       style={selectPopupStyle}
                       visible={popupVisible}
                       triggerEl={this.triggerEl}
                       hasTriangle={false}
                       triggerMode={Popup.TriggerMode.OPEN}
                       position={isAbove ? Popup.Position.TOP_LEFT : Popup.Position.BOTTOM_LEFT}
                       triggerHandle={this.triggerHandle}
                       onRender={this.popupRenderHandle}
                       onRequestClose={this.closePopup}>

                    <List className="multiple-select-list"
                          value={value}
                          mode={List.Mode.CHECKBOX}
                          isGrouped={isGrouped}
                          items={listData.length < 1 ? emptyEl : listData}
                          valueField={valueField}
                          displayField={displayField}
                          descriptionField={descriptionField}
                          onChange={this.changeHandle}/>

                </Popup>

        );
    }

};

MultipleSelect.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * The CSS class name of the popup element.
     */
    popupClassName: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * Override the styles of the popup element.
     */
    popupStyle: PropTypes.object,

    /**
     * The name of the auto complete.
     */
    name: PropTypes.string,

    /**
     * The placeholder of the field.
     */
    placeholder: PropTypes.string,

    /**
     * Children passed into the List.
     */
    data: PropTypes.oneOfType([

        // not grouped
        PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.shape({

            /**
             * The CSS class name of the list button.
             */
            className: PropTypes.string,

            /**
             * Override the styles of the list button.
             */
            style: PropTypes.object,

            /**
             * The theme of the list button.
             */
            theme: PropTypes.oneOf(Object.keys(Theme).map(key => Theme[key])),

            /**
             * The text value of the list button.Type can be string or number.
             */
            value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

            /**
             * The desc value of the list button. Type can be string or number.
             */
            desc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

            /**
             * If true, the list button will be disabled.
             */
            disabled: PropTypes.bool,

            /**
             * If true,the button will be have loading effect.
             */
            isLoading: PropTypes.bool,

            /**
             * If true,the element's ripple effect will be disabled.
             */
            disableTouchRipple: PropTypes.bool,

            /**
             * Use this property to display an icon. It will display on the left.
             */
            iconCls: PropTypes.string,

            /**
             * Use this property to display an icon. It will display on the right.
             */
            rightIconCls: PropTypes.string,

            /**
             * You can create a complicated renderer callback instead of value and desc prop.
             */
            renderer: PropTypes.func,

            /**
             * Callback function fired when a list item touch-tapped.
             */
            onTouchTap: PropTypes.func

        }), PropTypes.string, PropTypes.number])),

        // grouped
        PropTypes.array

    ]).isRequired,

    /**
     * If true, the auto complete will be disabled.
     */
    disabled: PropTypes.bool,

    /**
     * The value field name in data. (default: "value")
     */
    valueField: PropTypes.string,

    /**
     * The display field name in data. (default: "text")
     */
    displayField: PropTypes.string,

    /**
     * The description field name in data. (default: "desc")
     */
    descriptionField: PropTypes.string,

    /**
     * If true, the popup list automatically closed after selection.
     */
    autoClose: PropTypes.bool,

    /**
     * Callback function fired when value changed.
     */
    filterCallback: PropTypes.func,

    /**
     * Use this property to display an icon.
     */
    iconCls: PropTypes.string,

    /**
     * Use this property to display an icon.
     */
    rightIconCls: PropTypes.string,

    /**
     * The message of no value matched.
     */
    noMatchedMsg: PropTypes.string,

    /**
     * If true,the multipleSelect will be grouped.
     */
    isGrouped: PropTypes.bool,

    /**
     * Callback function fired when MultipleSelect changed.
     */
    onChange: PropTypes.func,

    /**
     * Callback function fired when the text field focused.
     */
    onFocus: PropTypes.func,

    /**
     * Callback function fired when the text field Defocused.
     */
    onBlur: PropTypes.func

};

MultipleSelect.defaultProps = {

    className: '',
    popupClassName: '',
    style: null,
    popupStyle: null,

    name: '',
    placeholder: '',
    data: [],
    disabled: false,

    valueField: 'value',
    displayField: 'text',
    descriptionField: 'desc',

    autoClose: false,
    iconCls: '',
    rightIconCls: '',
    noMatchedMsg: '',
    isGrouped: false

};<commit_msg>update onTouchTap to MultipleSelect<commit_after>import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {findDOMNode} from 'react-dom';

import IconButton from '../IconButton';
import TextField from '../TextField';
import Popup from '../Popup';
import List from '../List';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import Dom from '../_vendors/Dom';

import './MultipleSelect.css';

export default class MultipleSelect extends Component {

    constructor(props) {

        super(props);

        this.triggerEl = null;

        this.state = {
            selectedCollapsed: true,
            value: props.value,
            filter: '',
            popupVisible: false,
            isAbove: false
        };

        this.isAbove = this::this.isAbove;
        this.filterData = this::this.filterData;
        this.removeSelected = this::this.removeSelected;
        this.toggleSelectedCollapse = this::this.toggleSelectedCollapse;
        this.focusHandle = this::this.focusHandle;
        this.blurHandle = this::this.blurHandle;
        this.filterChangeHandle = this::this.filterChangeHandle;
        this.closePopup = this::this.closePopup;
        this.popupRenderHandle = this::this.popupRenderHandle;
        this.changeHandle = this::this.changeHandle;
        this.triggerHandle = this::this.triggerHandle;

    }

    isAbove() {

        const multipleSelect = this.refs.multipleSelect;

        if (!this.popupHeight || !multipleSelect) {
            return false;
        }

        const {top} = Dom.getOffset(multipleSelect),
            scrollTop = Dom.getScrollTop();

        if (top + this.triggerHeight + this.popupHeight - scrollTop > window.innerHeight) {
            return true;
        }

        return false;

    }

    filterData(filter = this.state.filter, data = this.props.data) {

        if (!filter) {
            return data;
        }

        const {displayField, filterCallback, isGrouped} = this.props;

        if (filterCallback) {
            return filterCallback(filter, data);
        }

        const filterFunc = (originData) => {
            return originData.filter(item => typeof item === 'object' && !!item[displayField] ?
                item[displayField].toString().toUpperCase().includes(filter.toUpperCase())
                :
                item.toString().toUpperCase().includes(filter.toUpperCase()));
        };

        if (isGrouped) {
            return data.map(group => {

                const children = filterFunc(group.children);

                if (children.length < 1) {
                    return;
                } else {
                    return {
                        ...group,
                        children
                    };
                }

            }).filter(item => !!item);
        }

        return filterFunc(data);

    }

    removeSelected(index, e) {

        e.preventDefault();

        setTimeout(() => {

            const {value} = this.state;

            if (!value || value.length < 1) {
                return;
            }

            value.splice(index, 1);

            this.setState({
                value
            }, () => {
                const {onChange} = this.props;
                onChange && onChange(value);
            });

        }, 0);

    }

    toggleSelectedCollapse() {
        this.setState({
            selectedCollapsed: !this.state.selectedCollapsed
        });
    }

    focusHandle() {

        const {disabled, onFocus} = this.props;

        !disabled && this.setState({
            popupVisible: true
        }, () => {
            onFocus && onFocus();
        });

    }

    blurHandle() {

        const {disabled, onBlur} = this.props;

        !disabled && onBlur && onBlur();

    }

    filterChangeHandle(filter) {
        this.setState({
            filter
        });
    }

    closePopup() {
        this.setState({
            popupVisible: false
        });
    }

    popupRenderHandle(popupEl) {

        this.popupEl = findDOMNode(popupEl);
        this.popupHeight = this.popupEl.offsetHeight;

        const isAbove = this.isAbove();

        if (isAbove !== this.state.isAbove) {
            this.setState({
                isAbove
            });
        }

    }

    changeHandle(value) {

        const {autoClose} = this.props,
            state = {
                value
            };

        if (autoClose) {
            state.popupVisible = false;
        }

        this.setState(state, () => {
            const {onChange} = this.props;
            onChange && onChange(value);
        });

    }

    triggerHandle(el, triggerEl, popupEl, triggerMode, currentVisible) {

        if (!triggerEl) {
            return true;
        }

        while (el) {
            if (el == this.refs.multipleSelect || el == popupEl || el == triggerEl) {
                return currentVisible;
            }
            el = el.parentNode;
        }

        return false;

    }

    componentDidMount() {
        this.triggerEl = findDOMNode(this.refs.trigger);
        this.triggerHeight = this.triggerEl.clientHeight;
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.value !== this.state.value) {
            this.setState({
                value: nextProps.value
            });
        }
    }

    render() {

        const {
                className, popupClassName, style, popupStyle, name, placeholder, isGrouped,
                disabled, iconCls, rightIconCls, valueField, displayField, descriptionField, noMatchedMsg
            } = this.props,
            {selectedCollapsed, isAbove, value, filter, popupVisible} = this.state,

            emptyEl = [{
                renderer() {
                    return (

                            {
                                noMatchedMsg ?
                                    noMatchedMsg
                                    :

                                        No matched value.

                            }

                    );
                }
            }],

            valueLen = (value ? value.length : 0),

            multipleSelectClassName = (valueLen > 0 ? ' not-empty' : '') + (popupVisible ? ' activated' : '')
                + (isAbove ? ' above' : ' blow') + (className ? ' ' + className : ''),
            selectedClassName = (selectedCollapsed ? ' collapsed' : ''),
            selectPopupClassName = (isAbove ? ' above' : ' blow') + (popupClassName ? ' ' + popupClassName : ''),
            selectPopupStyle = Object.assign({
                width: this.triggerEl && getComputedStyle(this.triggerEl).width
            }, popupStyle),

            listData = this.filterData();

        return (

                {
                    name ?
                        <input type="hidden"
                               name={name}
                               value={Util.getValueByValueField(value, valueField, displayField)}/>
                        :
                        null
                }

                {
                    value && valueLen > 0 ?
                        (

                                    {`${valueLen} selected`}

                                {
                                    value.map((item, index) => {

                                        const text = Util.getTextByDisplayField(item, displayField, valueField);

                                        return (

                                                {text}

 {
                                                         this.removeSelected(index, e);
                                                     }}>
                                                    ×

                                        );
                                    })
                                }

                                <IconButton className="multiple-select-selected-collapse-button"
                                            iconCls="fa fa-angle-double-up"
                                            onTouchTap={this.toggleSelectedCollapse}/>

                        )
                        :
                        null
                }

                <TextField ref="trigger"
                           className="multiple-select-trigger"
                           value={filter}
                           placeholder={placeholder}
                           disabled={disabled}
                           iconCls={iconCls}
                           rightIconCls={rightIconCls || 'fa fa-search'}
                           onFocus={this.focusHandle}
                           onBlur={this.blurHandle}
                           onChange={this.filterChangeHandle}/>

                <Popup className={'multiple-select-popup' + selectPopupClassName}
                       style={selectPopupStyle}
                       visible={popupVisible}
                       triggerEl={this.triggerEl}
                       hasTriangle={false}
                       triggerMode={Popup.TriggerMode.OPEN}
                       position={isAbove ? Popup.Position.TOP_LEFT : Popup.Position.BOTTOM_LEFT}
                       triggerHandle={this.triggerHandle}
                       onRender={this.popupRenderHandle}
                       onRequestClose={this.closePopup}>

                    <List className="multiple-select-list"
                          value={value}
                          mode={List.Mode.CHECKBOX}
                          isGrouped={isGrouped}
                          items={listData.length < 1 ? emptyEl : listData}
                          valueField={valueField}
                          displayField={displayField}
                          descriptionField={descriptionField}
                          onChange={this.changeHandle}/>

                </Popup>

        );
    }

};

MultipleSelect.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * The CSS class name of the popup element.
     */
    popupClassName: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * Override the styles of the popup element.
     */
    popupStyle: PropTypes.object,

    /**
     * The name of the auto complete.
     */
    name: PropTypes.string,

    /**
     * The placeholder of the field.
     */
    placeholder: PropTypes.string,

    /**
     * Children passed into the List.
     */
    data: PropTypes.oneOfType([

        // not grouped
        PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.shape({

            /**
             * The CSS class name of the list button.
             */
            className: PropTypes.string,

            /**
             * Override the styles of the list button.
             */
            style: PropTypes.object,

            /**
             * The theme of the list button.
             */
            theme: PropTypes.oneOf(Object.keys(Theme).map(key => Theme[key])),

            /**
             * The text value of the list button.Type can be string or number.
             */
            value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

            /**
             * The desc value of the list button. Type can be string or number.
             */
            desc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

            /**
             * If true, the list button will be disabled.
             */
            disabled: PropTypes.bool,

            /**
             * If true,the button will be have loading effect.
             */
            isLoading: PropTypes.bool,

            /**
             * If true,the element's ripple effect will be disabled.
             */
            disableTouchRipple: PropTypes.bool,

            /**
             * Use this property to display an icon. It will display on the left.
             */
            iconCls: PropTypes.string,

            /**
             * Use this property to display an icon. It will display on the right.
             */
            rightIconCls: PropTypes.string,

            /**
             * You can create a complicated renderer callback instead of value and desc prop.
             */
            renderer: PropTypes.func,

            /**
             * Callback function fired when a list item touch-tapped.
             */
            onTouchTap: PropTypes.func

        }), PropTypes.string, PropTypes.number])),

        // grouped
        PropTypes.array

    ]).isRequired,

    /**
     * If true, the auto complete will be disabled.
     */
    disabled: PropTypes.bool,

    /**
     * The value field name in data. (default: "value")
     */
    valueField: PropTypes.string,

    /**
     * The display field name in data. (default: "text")
     */
    displayField: PropTypes.string,

    /**
     * The description field name in data. (default: "desc")
     */
    descriptionField: PropTypes.string,

    /**
     * If true, the popup list automatically closed after selection.
     */
    autoClose: PropTypes.bool,

    /**
     * Callback function fired when value changed.
     */
    filterCallback: PropTypes.func,

    /**
     * Use this property to display an icon.
     */
    iconCls: PropTypes.string,

    /**
     * Use this property to display an icon.
     */
    rightIconCls: PropTypes.string,

    /**
     * The message of no value matched.
     */
    noMatchedMsg: PropTypes.string,

    /**
     * If true,the multipleSelect will be grouped.
     */
    isGrouped: PropTypes.bool,

    /**
     * Callback function fired when MultipleSelect changed.
     */
    onChange: PropTypes.func,

    /**
     * Callback function fired when the text field focused.
     */
    onFocus: PropTypes.func,

    /**
     * Callback function fired when the text field Defocused.
     */
    onBlur: PropTypes.func

};

MultipleSelect.defaultProps = {

    className: '',
    popupClassName: '',
    style: null,
    popupStyle: null,

    name: '',
    placeholder: '',
    data: [],
    disabled: false,

    valueField: 'value',
    displayField: 'text',
    descriptionField: 'desc',

    autoClose: false,
    iconCls: '',
    rightIconCls: '',
    noMatchedMsg: '',
    isGrouped: false

};<|endoftext|>