Extjs4 treeStore with that can take JSON where folders have nodes with duplicate ids

Here's something I've written that will work if you have 1 level of folders which each have a level of nodes under each of them that have matching ids. It takes the JSON before loading it into the store and then adds the parent id to the node id. The new read function isn't recursive and as far as I have used/tested only works for JSON, like above, that has the level as folders and the second level as nodes. Feel free to make your own recursive version should you need to. I used the code almost verbatim from 'Ext.data.JsonReader' (so I could step through the process easily) and then added in the method read from 'Ext.data.reader.Reader' and altered the read method. Edit: This might be a bit hacky, and may stop some functions working.

The Ux:

// JavaScript Document
Ext.define('Ext.ux.data.reader.DupeIdTreeReader', {
    extend: 'Ext.data.reader.Reader',
    alternateClassName: 'Ext.data.DupeIdTreeReader',
    alias : 'reader.json',

    root: '',

    /**
     * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
     * See the JsonReader intro docs for more details. This is not often needed and defaults to undefined.
     */

    /**
     * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
     * reading values. Defalts to false.
     * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
     * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name
     * "foo.bar.baz" direct from the root object.
     */
    useSimpleAccessors: false,


    /** :) I put this in to override reader! :)
     * Reads the given response object. This method normalizes the different types of response object that may be passed
     * to it, before handing off the reading of records to the {@link #readRecords} function.
     * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
     * @return {Ext.data.ResultSet} The parsed ResultSet object
     */
    read: function(response) {
        var data = response;

        if (response && response.responseText) {
            data = this.getResponseData(response);
        }

        for(var i=0; i< data.length; i++)
        {
            if(data[i].children){
                for(var j=0; j< data[i].children.length; j++){
                    data[i].children[j].id = data[i].id + "_" + data[i].children[j].id;
                }
            }
        }

        if (data) {
            return this.readRecords(data);
        } else {
            return this.nullResultSet;
        }
    },


    /**
     * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
     * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
     * @param {Object} data The raw JSON data
     * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
     */
    readRecords: function(data) {
        //this has to be before the call to super because we use the meta data in the superclass readRecords
        if (data.metaData) {
            this.onMetaChange(data.metaData);
        }

        /**
         * DEPRECATED - will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
         * @property jsonData
         * @type Mixed
         */
        this.jsonData = data;
        return this.callParent([data]);
    },

    //inherit docs
    getResponseData: function(response) {
        try {
            var data = Ext.decode(response.responseText);
        }
        catch (ex) {
            Ext.Error.raise({
                response: response,
                json: response.responseText,
                parseError: ex,
                msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
            });
        }
        //
        if (!data) {
            Ext.Error.raise('JSON object not found');
        }
        //

        return data;
    },

    //inherit docs
    buildExtractors : function() {
        var me = this;

        me.callParent(arguments);

        if (me.root) {
            me.getRoot = me.createAccessor(me.root);
        } else {
            me.getRoot = function(root) {
                return root;
            };
        }
    },

    /**
     * @private
     * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
     * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
     * @param {Object} root The JSON root node
     * @return {Array} The records
     */
    extractData: function(root) {
        var recordName = this.record,
            data = [],
            length, i;

        if (recordName) {
            length = root.length;

            for (i = 0; i < length; i++) {
                data[i] = root[i][recordName];
            }
        } else {
            data = root;
        }
        return this.callParent([data]);
    },

    /**
     * @private
     * Returns an accessor function for the given property string. Gives support for properties such as the following:
     * 'someProperty'
     * 'some.property'
     * 'some["property"]'
     * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
     */
    createAccessor: function() {
        var re = /[\[\.]/;

        return function(expr) {
            if (Ext.isEmpty(expr)) {
                return Ext.emptyFn;
            }
            if (Ext.isFunction(expr)) {
                return expr;
            }
            if (this.useSimpleAccessors !== true) {
                var i = String(expr).search(re);
                if (i >= 0) {
                    return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
                }
            }
            return function(obj) {
                return obj[expr];
            };
        };
    }()
});

Example JSON:
[
   {
      "id":"employees-table",
      "text":"Employees",
      "cls":"folder",
      "expanded":true,
      "leaf":false,
      "related-tables":"documents-table",
      "children":[
         {
            "text":"Employee Number 1",
            "leaf":true,
            "checked":false,
            "id":"number"
         },
         {
            "text":"Employee Name",
            "leaf":true,
            "checked":false,
            "id":"name"
         },
         {
            "text":"Employee Email",
            "leaf":true,
            "checked":false,
            "id":"email"
         }
      ]
   },
   {
      "id":"documents-table",
      "text":"Documents",
      "cls":"folder",
      "expanded":true,
      "related-tables":"employees-table customers-table",
      "leaf":false,
      "children":[
         {
            "text":"Document Number",
            "leaf":true,
            "checked":false,
            "id":"number"
         },
         {
            "text":"Document Name",
            "leaf":true,
            "checked":false,
            "id":"name"
         },
         {
            "text":"Employee Number",
            "leaf":true,
            "checked":false,
            "id":"employeenumber"
         }
      ]
   },
   {
      "id":"customers-table",
      "text":"Customers",
      "cls":"folder",
      "expanded":true,
      "leaf":false,
      "related-tables":"documents-table",
      "children":[
         {
            "text":"Customer number",
            "leaf":true,
            "checked":false,
            "id":"number"
         },
         {
            "text":"Customer name",
            "leaf":true,
            "checked":false,
            "id":"name"
         },
         {
            "text":"Customer Email",
            "leaf":true,
            "checked":false,
            "id":"email"
         }
      ]
   },
   {
      "id":"addresses-table",
      "text":"Addresses",
      "cls":"folder",
      "expanded":true,
      "leaf":false,
      "children":[
         {
            "text":"Address Line",
            "leaf":true,
            "checked":false,
            "id":"addressline"
         },
         {
            "text":"Postcode",
            "leaf":true,
            "checked":false,
            "id":"postcode"
         },
         {
            "text":"Address Number",
            "leaf":true,
            "checked":false,
            "id":"number"
         }
      ]
   }
]

Example usage :

    var myReader = Ext.create('Ext.ux.data.reader.DupeIdTreeReader',{
    });

    var myTreeStore = Ext.create('Ext.data.TreeStore', {
        model: MyTreeModel,
        proxy: {
            type: 'ajax',
            url: '../js/folders_and_nodes.json',
            reader: myReader
        }
    });

The new read function isn't recursive and as far as I have used/tested only works for JSON, like above, that has the level as folders and the second level as nodes. Feel free to make your own recursive version should you need to.

Comments

Popular posts from this blog

Getting Started: Quick Setup for GXT 3 (includes reset.css link how to)

How to setup GXT 3 examples, samples and demos

ExtJs4 : Dynamically Add Columns