The source code

    /*!
     * Ext JS Library 3.3.1
     * Copyright(c) 2006-2010 Sencha Inc.
     * licensing@sencha.com
     * http://www.sencha.com/license
     */
    Ext.ns('Ext.ux.tree');

    /**
     * @class Ext.ux.tree.TreeGridSorter
     * @extends Ext.tree.TreeSorter
     * Provides sorting of nodes in a {@link Ext.ux.tree.TreeGrid}.  The TreeGridSorter automatically monitors events on the
     * associated TreeGrid that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange).
     * Example usage:

     *
     new Ext.ux.tree.TreeGridSorter(myTreeGrid, {
         folderSort: true,
         dir: "desc",
         sortType: function(node) {
             // sort by a custom, typed attribute:
             return parseInt(node.id, 10);
         }
     });

     * @constructor
     * @param {TreeGrid} tree
     * @param {Object} config
     */
    Ext.ux.tree.TreeGridSorter = Ext.extend(Ext.tree.TreeSorter, {
        /**
         * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to ['sort-asc', 'sort-desc'])
         */
        sortClasses : ['sort-asc', 'sort-desc'],
        /**
         * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to 'Sort Ascending')
         */
        sortAscText : 'Sort Ascending',
        /**
         * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to 'Sort Descending')
         */
        sortDescText : 'Sort Descending',

        constructor : function(tree, config) {
            if(!Ext.isObject(config)) {
                config = {
                    property: tree.columns[0].dataIndex || 'text',
                    folderSort: true
                }
            }

            Ext.ux.tree.TreeGridSorter.superclass.constructor.apply(this, arguments);

            this.tree = tree;
            tree.on('headerclick', this.onHeaderClick, this);
            tree.ddAppendOnly = true;

            var me = this;
            this.defaultSortFn = function(n1, n2){

                var desc = me.dir && me.dir.toLowerCase() == 'desc',
                    prop = me.property || 'text',
                    sortType = me.sortType,
                    caseSensitive = me.caseSensitive === true,
                    leafAttr = me.leafAttr || 'leaf',
                    attr1 = n1.attributes,
                    attr2 = n2.attributes;

                if(me.folderSort){
                    if(attr1[leafAttr] && !attr2[leafAttr]){
                        return 1;
                    }
                    if(!attr1[leafAttr] && attr2[leafAttr]){
                        return -1;
                    }
                }
                var prop1 = attr1[prop],
                    prop2 = attr2[prop],
                    v1 = sortType ? sortType(prop1) : (caseSensitive ? prop1 : prop1.toUpperCase());
                    v2 = sortType ? sortType(prop2) : (caseSensitive ? prop2 : prop2.toUpperCase());

                if(v1 < v2){
                    return desc ? +1 : -1;
                }else if(v1 > v2){
                    return desc ? -1 : +1;
                }else{
                    return 0;
                }
            };

            tree.on('afterrender', this.onAfterTreeRender, this, {single: true});
            tree.on('headermenuclick', this.onHeaderMenuClick, this);
        },

        onAfterTreeRender : function() {
            if(this.tree.hmenu){
                this.tree.hmenu.insert(0,
                    {itemId:'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'},
                    {itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
                );
            }
            this.updateSortIcon(0, 'asc');
        },

        onHeaderMenuClick : function(c, id, index) {
            if(id === 'asc' || id === 'desc') {
                this.onHeaderClick(c, null, index);
                return false;
            }
        },

        onHeaderClick : function(c, el, i) {
            if(c && !this.tree.headersDisabled){
                var me = this;

                me.property = c.dataIndex;
                me.dir = c.dir = (c.dir === 'desc' ? 'asc' : 'desc');
                me.sortType = c.sortType;
                me.caseSensitive === Ext.isBoolean(c.caseSensitive) ? c.caseSensitive : this.caseSensitive;
                me.sortFn = c.sortFn || this.defaultSortFn;

                this.tree.root.cascade(function(n) {
                    if(!n.isLeaf()) {
                        me.updateSort(me.tree, n);
                    }
                });

                this.updateSortIcon(i, c.dir);
            }
        },

        // private
        updateSortIcon : function(col, dir){
            var sc = this.sortClasses,
                hds = this.tree.innerHd.select('td').removeClass(sc);
            hds.item(col).addClass(sc[dir == 'desc' ? 1 : 0]);
        }
    });