| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711 | <!DOCTYPE html><html><head>  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  <title>The source code</title>  <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />  <script type="text/javascript" src="../resources/prettify/prettify.js"></script>  <style type="text/css">    .highlight { display: block; background-color: #ddd; }  </style>  <script type="text/javascript">    function highlight() {      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";    }  </script></head><body onload="prettyPrint(); highlight();">  <pre class="prettyprint lang-js"><span id='Ext-data-TreeStore'>/**</span> * The TreeStore is a store implementation that is backed by by an {@link Ext.data.Tree}. * It provides convenience methods for loading nodes, as well as the ability to use * the hierarchical tree structure combined with a store. This class is generally used * in conjunction with {@link Ext.tree.Panel}. This class also relays many events from * the Tree for convenience. * * # Using Models * * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}. * The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed * in the {@link Ext.data.NodeInterface} documentation. * * # Reading Nested Data * * For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property, * so the reader can find nested data for each node (if a root is not specified, it will default to * 'children'). This will tell the tree to look for any nested tree nodes by the same keyword, i.e., 'children'. * If a root is specified in the config make sure that any nested nodes with children have the same name. * Note that setting {@link #defaultRootProperty} accomplishes the same thing. */Ext.define('Ext.data.TreeStore', {    extend: 'Ext.data.AbstractStore',    alias: 'store.tree',    requires: [        'Ext.util.Sorter',        'Ext.data.Tree',        'Ext.data.NodeInterface'    ],<span id='Ext-data-TreeStore-cfg-root'>    /**</span>     * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root     * The root node for this store. For example:     *     *     root: {     *         expanded: true,     *         text: "My Root",     *         children: [     *             { text: "Child 1", leaf: true },     *             { text: "Child 2", expanded: true, children: [     *                 { text: "GrandChild", leaf: true }     *             ] }     *         ]     *     }     *     * Setting the `root` config option is the same as calling {@link #setRootNode}.     */<span id='Ext-data-TreeStore-cfg-clearOnLoad'>    /**</span>     * @cfg {Boolean} [clearOnLoad=true]     * Remove previously existing child nodes before loading.      */    clearOnLoad : true,<span id='Ext-data-TreeStore-cfg-clearRemovedOnLoad'>    /**</span>     * @cfg {Boolean} [clearRemovedOnLoad=true]     * If `true`, when a node is reloaded, any records in the {@link #removed} record collection that were previously descendants of the node being reloaded will be cleared from the {@link #removed} collection.     * Only applicable if {@link #clearOnLoad} is `true`.     */    clearRemovedOnLoad: true,<span id='Ext-data-TreeStore-cfg-nodeParam'>    /**</span>     * @cfg {String} [nodeParam="node"]     * The name of the parameter sent to the server which contains the identifier of the node.     */    nodeParam: 'node',<span id='Ext-data-TreeStore-cfg-defaultRootId'>    /**</span>     * @cfg {String} [defaultRootId="root"]     * The default root id.     */    defaultRootId: 'root',<span id='Ext-data-TreeStore-cfg-defaultRootProperty'>    /**</span>     * @cfg {String} [defaultRootProperty="children"]     * The root property to specify on the reader if one is not explicitly defined.     */    defaultRootProperty: 'children',        // Keep a copy of the default so we know if it's been changed in a subclass/config    rootProperty: 'children',<span id='Ext-data-TreeStore-cfg-folderSort'>    /**</span>     * @cfg {Boolean} [folderSort=false]     * Set to true to automatically prepend a leaf sorter.     */    folderSort: false,    constructor: function(config) {        var me = this,            root,            fields,            defaultRoot;        config = Ext.apply({}, config);<span id='Ext-data-TreeStore-property-fields'>        /**</span>         * If we have no fields declare for the store, add some defaults.         * These will be ignored if a model is explicitly specified.         */        fields = config.fields || me.fields;        if (!fields) {            config.fields = [                {name: 'text', type: 'string'}            ];            defaultRoot = config.defaultRootProperty || me.defaultRootProperty;            if (defaultRoot !== me.defaultRootProperty) {                config.fields.push({                    name: defaultRoot,                       type: 'auto',                       defaultValue: null,                     persist: false                });            }        }        me.callParent([config]);        // We create our data tree.        me.tree = new Ext.data.Tree();        me.relayEvents(me.tree, [<span id='Ext-data-TreeStore-event-append'>            /**</span>             * @event append             * @inheritdoc Ext.data.Tree#append             */            "append",<span id='Ext-data-TreeStore-event-remove'>            /**</span>             * @event remove             * @inheritdoc Ext.data.Tree#remove             */            "remove",<span id='Ext-data-TreeStore-event-move'>            /**</span>             * @event move             * @inheritdoc Ext.data.Tree#move             */            "move",<span id='Ext-data-TreeStore-event-insert'>            /**</span>             * @event insert             * @inheritdoc Ext.data.Tree#insert             */            "insert",<span id='Ext-data-TreeStore-event-beforeappend'>            /**</span>             * @event beforeappend             * @inheritdoc Ext.data.Tree#beforeappend             */            "beforeappend",<span id='Ext-data-TreeStore-event-beforeremove'>            /**</span>             * @event beforeremove             * @inheritdoc Ext.data.Tree#beforeremove             */            "beforeremove",<span id='Ext-data-TreeStore-event-beforemove'>            /**</span>             * @event beforemove             * @inheritdoc Ext.data.Tree#beforemove             */            "beforemove",<span id='Ext-data-TreeStore-event-beforeinsert'>            /**</span>             * @event beforeinsert             * @inheritdoc Ext.data.Tree#beforeinsert             */            "beforeinsert",<span id='Ext-data-TreeStore-event-expand'>            /**</span>             * @event expand             * @inheritdoc Ext.data.Tree#expand             */            "expand",<span id='Ext-data-TreeStore-event-collapse'>            /**</span>             * @event collapse             * @inheritdoc Ext.data.Tree#collapse             */            "collapse",<span id='Ext-data-TreeStore-event-beforeexpand'>            /**</span>             * @event beforeexpand             * @inheritdoc Ext.data.Tree#beforeexpand             */            "beforeexpand",<span id='Ext-data-TreeStore-event-beforecollapse'>            /**</span>             * @event beforecollapse             * @inheritdoc Ext.data.Tree#beforecollapse             */            "beforecollapse",<span id='Ext-data-TreeStore-event-sort'>            /**</span>             * @event sort             * @inheritdoc Ext.data.Tree#sort             */            "sort",<span id='Ext-data-TreeStore-event-rootchange'>            /**</span>             * @event rootchange             * @inheritdoc Ext.data.Tree#rootchange             */            "rootchange"        ]);        me.tree.on({            scope: me,            remove: me.onNodeRemove,            // this event must follow the relay to beforeitemexpand to allow users to            // cancel the expand:            beforeexpand: me.onBeforeNodeExpand,            beforecollapse: me.onBeforeNodeCollapse,            append: me.onNodeAdded,            insert: me.onNodeAdded,            sort: me.onNodeSort        });        me.onBeforeSort();        root = me.root;        if (root) {            delete me.root;            me.setRootNode(root);        }        //<deprecated since=0.99>        if (Ext.isDefined(me.nodeParameter)) {            if (Ext.isDefined(Ext.global.console)) {                Ext.global.console.warn('Ext.data.TreeStore: nodeParameter has been deprecated. Please use nodeParam instead.');            }            me.nodeParam = me.nodeParameter;            delete me.nodeParameter;        }        //</deprecated>    },    // inherit docs    setProxy: function(proxy) {        var reader,            needsRoot;        if (proxy instanceof Ext.data.proxy.Proxy) {            // proxy instance, check if a root was set            needsRoot = Ext.isEmpty(proxy.getReader().root);        } else if (Ext.isString(proxy)) {            // string type, means a reader can't be set            needsRoot = true;        } else {            // object, check if a reader and a root were specified.            reader = proxy.reader;            needsRoot = !(reader && !Ext.isEmpty(reader.root));        }        proxy = this.callParent(arguments);        if (needsRoot) {            reader = proxy.getReader();            reader.root = this.defaultRootProperty;            // force rebuild            reader.buildExtractors(true);        }    },    // inherit docs    onBeforeSort: function() {        if (this.folderSort) {            this.sort({                property: 'leaf',                direction: 'ASC'            }, 'prepend', false);        }    },<span id='Ext-data-TreeStore-method-onBeforeNodeExpand'>    /**</span>     * Called before a node is expanded.     * @private     * @param {Ext.data.NodeInterface} node The node being expanded.     * @param {Function} callback The function to run after the expand finishes     * @param {Object} scope The scope in which to run the callback function     */    onBeforeNodeExpand: function(node, callback, scope) {        if (node.isLoaded()) {            Ext.callback(callback, scope || node, [node.childNodes]);        }        else if (node.isLoading()) {            this.on('load', function() {                Ext.callback(callback, scope || node, [node.childNodes]);            }, this, {single: true});        }        else {            this.read({                node: node,                callback: function() {                    Ext.callback(callback, scope || node, [node.childNodes]);                }            });        }    },    //inherit docs    getNewRecords: function() {        return Ext.Array.filter(this.tree.flatten(), this.filterNew);    },    //inherit docs    getUpdatedRecords: function() {        return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);    },<span id='Ext-data-TreeStore-method-onBeforeNodeCollapse'>    /**</span>     * Called before a node is collapsed.     * @private     * @param {Ext.data.NodeInterface} node The node being collapsed.     * @param {Function} callback The function to run after the collapse finishes     * @param {Object} scope The scope in which to run the callback function     */    onBeforeNodeCollapse: function(node, callback, scope) {        callback.call(scope || node, node.childNodes);    },    onNodeRemove: function(parent, node, isMove) {        var me = this,            removed = me.removed;        if (!node.isReplace && Ext.Array.indexOf(removed, node) == -1) {            removed.push(node);        }        if (me.autoSync && !me.autoSyncSuspended && !isMove) {            me.sync();        }    },    onNodeAdded: function(parent, node) {        var me = this,            proxy = me.getProxy(),            reader = proxy.getReader(),            data = node.raw || node[node.persistenceProperty],            dataRoot;        Ext.Array.remove(me.removed, node);        if (!node.isLeaf()) {            dataRoot = reader.getRoot(data);            if (dataRoot) {                me.fillNode(node, reader.extractData(dataRoot));                delete data[reader.root];            }        }        if (me.autoSync && !me.autoSyncSuspended && (node.phantom || node.dirty)) {            me.sync();        }    },    onNodeSort: function() {        if(this.autoSync && !this.autoSyncSuspended) {            this.sync();        }    },<span id='Ext-data-TreeStore-method-setRootNode'>    /**</span>     * Sets the root node for this store.  See also the {@link #root} config option.     * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root     * @return {Ext.data.NodeInterface} The new root     */    setRootNode: function(root, /* private */ preventLoad) {        var me = this,            model = me.model,            idProperty = model.prototype.idProperty        root = root || {};        if (!root.isModel) {            // create a default rootNode and create internal data struct.            Ext.applyIf(root, {                id: me.defaultRootId,                text: 'Root',                allowDrag: false            });            if (root[idProperty] === undefined) {                root[idProperty] = me.defaultRootId;            }            Ext.data.NodeInterface.decorate(model);            root = Ext.ModelManager.create(root, model);        } else if (root.isModel && !root.isNode) {            Ext.data.NodeInterface.decorate(model);        }        // Because we have decorated the model with new fields,        // we need to build new extactor functions on the reader.        me.getProxy().getReader().buildExtractors(true);        // When we add the root to the tree, it will automaticaly get the NodeInterface        me.tree.setRootNode(root);        // If the user has set expanded: true on the root, we want to call the expand function        if (preventLoad !== true && !root.isLoaded() && (me.autoLoad === true || root.isExpanded())) {            me.load({                node: root            });        }        return root;    },<span id='Ext-data-TreeStore-method-getRootNode'>    /**</span>     * Returns the root node for this tree.     * @return {Ext.data.NodeInterface}     */    getRootNode: function() {        return this.tree.getRootNode();    },<span id='Ext-data-TreeStore-method-getNodeById'>    /**</span>     * Returns the record node by id     * @return {Ext.data.NodeInterface}     */    getNodeById: function(id) {        return this.tree.getNodeById(id);    },        // inherit docs    getById: function(id) {        return this.getNodeById(id);        },<span id='Ext-data-TreeStore-method-load'>    /**</span>     * Loads the Store using its configured {@link #proxy}.     * @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}     * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.     * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will     * default to the root node.     */    load: function(options) {        options = options || {};        options.params = options.params || {};        var me = this,            node = options.node || me.tree.getRootNode();        // If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just        // create one for them.        if (!node) {            node = me.setRootNode({                expanded: true            }, true);        }        // Assign the ID of the Operation so that a REST proxy can create the correct URL        options.id = node.getId();        if (me.clearOnLoad) {            if(me.clearRemovedOnLoad) {                // clear from the removed array any nodes that were descendants of the node being reloaded so that they do not get saved on next sync.                me.clearRemoved(node);            }            // temporarily remove the onNodeRemove event listener so that when removeAll is called, the removed nodes do not get added to the removed array            me.tree.un('remove', me.onNodeRemove, me);            // remove all the nodes            node.removeAll(false);            // reattach the onNodeRemove listener            me.tree.on('remove', me.onNodeRemove, me);        }        Ext.applyIf(options, {            node: node        });        options.params[me.nodeParam] = node ? node.getId() : 'root';        if (node) {            node.set('loading', true);        }        return me.callParent([options]);    },<span id='Ext-data-TreeStore-method-clearRemoved'>    /**</span>     * Removes all records that used to be descendants of the passed node from the removed array     * @private     * @param {Ext.data.NodeInterface} node     */    clearRemoved: function(node) {        var me = this,            removed = me.removed,            id = node.getId(),            removedLength = removed.length,            i = removedLength,            recordsToClear = {},            newRemoved = [],            removedHash = {},            removedNode,            targetNode,            targetId;        if(node === me.getRootNode()) {            // if the passed node is the root node, just reset the removed array            me.removed = [];            return;        }        // add removed records to a hash so they can be easily retrieved by id later        for(; i--;) {            removedNode = removed[i];            removedHash[removedNode.getId()] = removedNode;        }        for(i = removedLength; i--;) {            removedNode = removed[i];            targetNode = removedNode;            while(targetNode && targetNode.getId() !== id) {                // walk up the parent hierarchy until we find the passed node or until we get to the root node                targetId = targetNode.get('parentId');                targetNode = targetNode.parentNode || me.getNodeById(targetId) || removedHash[targetId];            }            if(targetNode) {                // removed node was previously a descendant of the passed node - add it to the records to clear from "removed" later                recordsToClear[removedNode.getId()] = removedNode;            }        }        // create a new removed array containing only the records that are not in recordsToClear        for(i = 0; i < removedLength; i++) {            removedNode = removed[i];            if(!recordsToClear[removedNode.getId()]) {                newRemoved.push(removedNode);            }        }        me.removed = newRemoved;    },<span id='Ext-data-TreeStore-method-fillNode'>    /**</span>     * Fills a node with a series of child records.     * @private     * @param {Ext.data.NodeInterface} node The node to fill     * @param {Ext.data.Model[]} newNodes The records to add     */    fillNode: function(node, newNodes) {        var me = this,            ln = newNodes ? newNodes.length : 0,            sorters = me.sorters,            i, sortCollection,            needsIndexSort = false,            performLocalSort = ln && me.sortOnLoad && !me.remoteSort && sorters && sorters.items && sorters.items.length,            node1, node2;        // See if there are any differing index values in the new nodes. If not, then we do not have to sortByIndex        for (i = 1; i < ln; i++) {            node1 = newNodes[i];            node2 = newNodes[i - 1];            needsIndexSort = node1[node1.persistenceProperty].index != node2[node2.persistenceProperty].index;            if (needsIndexSort) {                break;            }        }        // If there is a set of local sorters defined.        if (performLocalSort) {            // If sorting by index is needed, sort by index first            if (needsIndexSort) {                me.sorters.insert(0, me.indexSorter);            }            sortCollection = new Ext.util.MixedCollection();            sortCollection.addAll(newNodes);            sortCollection.sort(me.sorters.items);            newNodes = sortCollection.items;            // Remove the index sorter            me.sorters.remove(me.indexSorter);        } else if (needsIndexSort) {            Ext.Array.sort(newNodes, me.sortByIndex);        }        node.set('loaded', true);        for (i = 0; i < ln; i++) {            node.appendChild(newNodes[i], undefined, true);        }        return newNodes;    },<span id='Ext-data-TreeStore-method-sortByIndex'>    /**</span>     * Sorter function for sorting records in index order     * @private     * @param {Ext.data.NodeInterface} node1     * @param {Ext.data.NodeInterface} node2     * @return {Number}     */    sortByIndex: function(node1, node2) {        return node1[node1.persistenceProperty].index - node2[node2.persistenceProperty].index;    },    // inherit docs    onProxyLoad: function(operation) {        var me = this,            successful = operation.wasSuccessful(),            records = operation.getRecords(),            node = operation.node;        me.loading = false;        node.set('loading', false);        if (successful) {            if (!me.clearOnLoad) {                records = me.cleanRecords(node, records);            }            records = me.fillNode(node, records);        }        // The load event has an extra node parameter        // (differing from the load event described in AbstractStore)<span id='Ext-data-TreeStore-event-load'>        /**</span>         * @event load         * Fires whenever the store reads data from a remote data source.         * @param {Ext.data.TreeStore} this         * @param {Ext.data.NodeInterface} node The node that was loaded.         * @param {Ext.data.Model[]} records An array of records.         * @param {Boolean} successful True if the operation was successful.         */        // deprecate read?        me.fireEvent('read', me, operation.node, records, successful);        me.fireEvent('load', me, operation.node, records, successful);        //this is a callback that would have been passed to the 'read' function and is optional        Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);    },        onCreateRecords: function(records) {        this.callParent(arguments);                var i = 0,            len = records.length,            tree = this.tree,            node;        for (; i < len; ++i) {            node = records[i];            tree.onNodeIdChanged(node, null, node.getId());        }            },        cleanRecords: function(node, records){        var nodeHash = {},            childNodes = node.childNodes,            i = 0,            len  = childNodes.length,            out = [],            rec;                    // build a hash of all the childNodes under the current node for performance        for (; i < len; ++i) {            nodeHash[childNodes[i].getId()] = true;        }                for (i = 0, len = records.length; i < len; ++i) {            rec = records[i];            if (!nodeHash[rec.getId()]) {                out.push(rec);                }        }                return out;    },    // inherit docs    removeAll: function() {        var root = this.getRootNode();        if (root) {            root.destroy(true);        }        this.fireEvent('clear', this);    },    // inherit docs    doSort: function(sorterFn) {        var me = this;        if (me.remoteSort) {            //the load function will pick up the new sorters and request the sorted data from the proxy            me.load();        } else {            me.tree.sort(sorterFn, true);            me.fireEvent('datachanged', me);            me.fireEvent('refresh', me);        }        me.fireEvent('sort', me);    }}, function() {    var proto = this.prototype;    proto.indexSorter = new Ext.util.Sorter({        sorterFn: proto.sortByIndex    });});</pre></body></html>
 |