# TreeSelectionModel.js

    /**
     * @class Ext.tree.DefaultSelectionModel
     * @extends Ext.util.Observable
     * The default single selection for a TreePanel.
     */
    Ext.tree.DefaultSelectionModel = function(){
       this.selNode = null;

       this.addEvents({
           /**
            * @event selectionchange
            * Fires when the selected node changes
            * @param {DefaultSelectionModel} this
            * @param {TreeNode} node the new selection
            */
           "selectionchange" : true,

           /**
            * @event beforeselect
            * Fires before the selected node changes, return false to cancel the change
            * @param {DefaultSelectionModel} this
            * @param {TreeNode} node the new selection
            * @param {TreeNode} node the old selection
            */
           "beforeselect" : true
       });
    };

    Ext.extend(Ext.tree.DefaultSelectionModel, Ext.util.Observable, {
        init : function(tree){
            this.tree = tree;
            tree.el.on("keydown", this.onKeyDown, this);
            tree.on("click", this.onNodeClick, this);
        },

        onNodeClick : function(node, e){
            this.select(node);
        },

        /**
         * Select a node.
         * @param {TreeNode} node The node to select
         * @return {TreeNode} The selected node
         */
        select : function(node){
            var last = this.selNode;
            if(last != node && this.fireEvent('beforeselect', this, node, last) !== false){
                if(last){
                    last.ui.onSelectedChange(false);
                }
                this.selNode = node;
                node.ui.onSelectedChange(true);
                this.fireEvent("selectionchange", this, node, last);
            }
            return node;
        },

        /**
         * Deselect a node.
         * @param {TreeNode} node The node to unselect
         */
        unselect : function(node){
            if(this.selNode == node){
                this.clearSelections();
            }
        },

        /**
         * Clear all selections
         */
        clearSelections : function(){
            var n = this.selNode;
            if(n){
                n.ui.onSelectedChange(false);
                this.selNode = null;
                this.fireEvent("selectionchange", this, null);
            }
            return n;
        },

        /**
         * Get the selected node
         * @return {TreeNode} The selected node
         */
        getSelectedNode : function(){
            return this.selNode;
        },

        /**
         * Returns true if the node is selected
         * @param {TreeNode} node The node to check
         * @return {Boolean}
         */
        isSelected : function(node){
            return this.selNode == node;
        },

        /**
         * Selects the node above the selected node in the tree, intelligently walking the nodes
         * @return TreeNode The new selection
         */
        selectPrevious : function(){
            var s = this.selNode || this.lastSelNode;
            if(!s){
                return null;
            }
            var ps = s.previousSibling;
            if(ps){
                if(!ps.isExpanded() || ps.childNodes.length < 1){
                    return this.select(ps);
                } else{
                    var lc = ps.lastChild;
                    while(lc && lc.isExpanded() && lc.childNodes.length > 0){
                        lc = lc.lastChild;
                    }
                    return this.select(lc);
                }
            } else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){
                return this.select(s.parentNode);
            }
            return null;
        },

        /**
         * Selects the node above the selected node in the tree, intelligently walking the nodes
         * @return TreeNode The new selection
         */
        selectNext : function(){
            var s = this.selNode || this.lastSelNode;
            if(!s){
                return null;
            }
            if(s.firstChild && s.isExpanded()){
                 return this.select(s.firstChild);
             }else if(s.nextSibling){
                 return this.select(s.nextSibling);
             }else if(s.parentNode){
                var newS = null;
                s.parentNode.bubble(function(){
                    if(this.nextSibling){
                        newS = this.getOwnerTree().selModel.select(this.nextSibling);
                        return false;
                    }
                });
                return newS;
             }
            return null;
        },

        onKeyDown : function(e){
            var s = this.selNode || this.lastSelNode;
            // undesirable, but required
            var sm = this;
            if(!s){
                return;
            }
            var k = e.getKey();
            switch(k){
                 case e.DOWN:
                     e.stopEvent();
                     this.selectNext();
                 break;
                 case e.UP:
                     e.stopEvent();
                     this.selectPrevious();
                 break;
                 case e.RIGHT:
                     e.preventDefault();
                     if(s.hasChildNodes()){
                         if(!s.isExpanded()){
                             s.expand();
                         }else if(s.firstChild){
                             this.select(s.firstChild, e);
                         }
                     }
                 break;
                 case e.LEFT:
                     e.preventDefault();
                     if(s.hasChildNodes() && s.isExpanded()){
                         s.collapse();
                     }else if(s.parentNode && (this.tree.rootVisible || s.parentNode != this.tree.getRootNode())){
                         this.select(s.parentNode, e);
                     }
                 break;
            };
        }
    });

    /**
     * @class Ext.tree.MultiSelectionModel
     * @extends Ext.util.Observable
     * Multi selection for a TreePanel.
     */
    Ext.tree.MultiSelectionModel = function(){
       this.selNodes = [];
       this.selMap = {};
       this.addEvents({
           /**
            * @event selectionchange
            * Fires when the selected nodes change
            * @param {MultiSelectionModel} this
            * @param {Array} nodes Array of the selected nodes
            */
           "selectionchange" : true
       });
    };

    Ext.extend(Ext.tree.MultiSelectionModel, Ext.util.Observable, {
        init : function(tree){
            this.tree = tree;
            tree.el.on("keydown", this.onKeyDown, this);
            tree.on("click", this.onNodeClick, this);
        },

        onNodeClick : function(node, e){
            this.select(node, e, e.ctrlKey);
        },

        /**
         * Select a node.
         * @param {TreeNode} node The node to select
         * @param {EventObject} e (optional) An event associated with the selection
         * @param {Boolean} keepExisting True to retain existing selections
         * @return {TreeNode} The selected node
         */
        select : function(node, e, keepExisting){
            if(keepExisting !== true){
                this.clearSelections(true);
            }
            if(this.isSelected(node)){
                this.lastSelNode = node;
                return node;
            }
            this.selNodes.push(node);
            this.selMap[node.id] = node;
            this.lastSelNode = node;
            node.ui.onSelectedChange(true);
            this.fireEvent("selectionchange", this, this.selNodes);
            return node;
        },

        /**
         * Deselect a node.
         * @param {TreeNode} node The node to unselect
         */
        unselect : function(node){
            if(this.selMap[node.id]){
                node.ui.onSelectedChange(false);
                var sn = this.selNodes;
                var index = -1;
                if(sn.indexOf){
                    index = sn.indexOf(node);
                }else{
                    for(var i = 0, len = sn.length; i < len; i++){
                        if(sn[i] == node){
                            index = i;
                            break;
                        }
                    }
                }
                if(index != -1){
                    this.selNodes.splice(index, 1);
                }
                delete this.selMap[node.id];
                this.fireEvent("selectionchange", this, this.selNodes);
            }
        },

        /**
         * Clear all selections
         */
        clearSelections : function(suppressEvent){
            var sn = this.selNodes;
            if(sn.length > 0){
                for(var i = 0, len = sn.length; i < len; i++){
                    sn[i].ui.onSelectedChange(false);
                }
                this.selNodes = [];
                this.selMap = {};
                if(suppressEvent !== true){
                    this.fireEvent("selectionchange", this, this.selNodes);
                }
            }
        },

        /**
         * Returns true if the node is selected
         * @param {TreeNode} node The node to check
         * @return {Boolean}
         */
        isSelected : function(node){
            return this.selMap[node.id] ? true : false;
        },

        /**
         * Returns an array of the selected nodes
         * @return {Array}
         */
        getSelectedNodes : function(){
            return this.selNodes;
        },

        onKeyDown : Ext.tree.DefaultSelectionModel.prototype.onKeyDown,

        selectNext : Ext.tree.DefaultSelectionModel.prototype.selectNext,

        selectPrevious : Ext.tree.DefaultSelectionModel.prototype.selectPrevious
    });

Ext - Copyright © 2006-2007 Ext JS, LLC
All rights reserved.