1. #1
    Ext User
    Join Date
    Mar 2007
    Posts
    27
    Vote Rating
    0
    dselkirk is on a distinguished road

      0  

    Default Parent based TreeLoader

    Parent based TreeLoader


    During development I found the leaf method doesn't fit our needs. I've built a custom treeloader which uses a parent base hierarchy method. It performs a single call to the server to retrieve the full json tree. It then loads the root nodes. The child nodes are not loaded into the tree until it is required. This allows us to minimize our server calls, simplify our server side work and optimize the user interactions.

    ParentLoader.js:
    Code:
    Ext.namespace("Ext.ux");
    Ext.namespace("Ext.ux.tree");
    
    Ext.ux.tree.ParentLoader = function(config) {
      this.baseParams = {};
      this.test='testing';
      this.requestMethod = "POST";
      Ext.apply(this, config);
      this.addEvents({
        "beforeload" : true,
        "load" : true,
        "loadexception" : true
      });
      Ext.ux.tree.ParentLoader.superclass.constructor.call(this);
    };
    
    Ext.extend(Ext.ux.tree.ParentLoader, Ext.tree.TreeLoader, {
    
      getChildren: function(parent){
       var items = new Array();
       this.store.each(function(rec) {
         if(rec.get('parent')==parent) items[items.length]=rec;
       });
       return items;
      },
      
      hasChildren: function(id){
        return (this.getChildren(id).length > 0);
      },
    
      addChildren: function(parent) {
        Ext.each(this.getChildren(parent.attributes.id), function(rec){
          var attr = {};
          if(this.baseAttrs) Ext.applyIf(attr, this.baseAttrs);
          if(this.applyLoader !== false) attr.loader = this;
          Ext.each(this.dataFields, function(item){
            attr[item] = rec.get(item);
          });
          var node =(this.hasChildren(attr.id)?
            new Ext.tree.AsyncTreeNode(attr):
            new Ext.tree.TreeNode(attr));
          parent.appendChild(node);
        },this);
      },
    
      load : function(node, callback){
        if(!!this.store) {
          this.addChildren(node);
          if(typeof callback == "function") callback();
        }else{
          this.requestData(node, callback);
        }
      },
    
      requestData : function(node, callback){
        if(this.fireEvent("beforeload", this, node, callback) !== false){
          this.store = new Ext.data.JsonStore({
            url: this.dataUrl,
            root: this.dataRoot,
            fields: this.dataFields
          });
          this.store.on('load', this.handleResponse.createDelegate(this, [node, callback], 1));
          this.store.on('loadexception', this.handleFailure.createDelegate(this, [node, callback], 1));
          this.store.load();
        }else{
          if(typeof callback == "function")callback();
        }
       },
       
       processResponse : function(response, node, callback){
         try {
           this.load.call(this, node, callback);
          }catch(e){
            this.handleFailure(response);
          }
        }, 
       
       handleResponse : function(response, node, callback){
         this.transId = false;
         this.processResponse(response, node, callback); 
         this.fireEvent("load", this, node, response);
       },
    
       handleFailure : function(response, node, callback){
         this.transId = false;
         this.fireEvent("loadexception", this, node);
         if(typeof callback == "function") callback(this, node, response);
       }
    
    });
    Sample Json:
    Code:
    {"pages":[
      {"id":1,"parent":0,"text":"default","url":"\/default\/"},
      {"id":2,"parent":0,"text":"contactus","url":"\/contactus\/"},
      {"id":3,"parent":0,"text":"sitemap","url":"\/sitemap\/"},
      {"id":4,"parent":0,"text":"aboutus","url":"\/aboutus\/"},
      {"id":5,"parent":4,"text":"company-mission","url":"\/aboutus\/company-mission\/"}
    ]}
    Usage:
    Code:
      var new Ext.tree.TreePanel({
        loader: new Ext.ux.tree.ParentLoader({
          dataUrl: '/load-pages.aspx',
          dataRoot: 'pages',
          dataFields: ['id','parent','text','url']
        }),
        title: 'Site Map',
        autoScroll:true,
        animate:true,
        enableDD:true,
        containerScroll: true,
        rootVisible: false,
        listeners: {
          dblclick: function(node){
            alert(node.attributes.url);
          }
        }
      });
      var root = new Ext.tree.AsyncTreeNode({text: 'Root', draggable:false, id:'0', url:'/', expanded:true});
      tree.setRootNode(root);
    I hope someone else finds its useful.

  2. #2
    Ext JS Premium Member mikegiddens's Avatar
    Join Date
    Mar 2007
    Location
    Denver, Colorado
    Posts
    273
    Vote Rating
    1
    mikegiddens will become famous soon enough

      0  

    Default


    I have not looked at the code but I did read what you ar doing and it seems that this features already exists. it is call children as in:

    Code:
    {records:[{'name':'node1'
                     , children:[ {name:'child1'
                                      , children:[{name:'subchild... etc'}]
                                       }
                                    ]
                  } ] }
    And if you want them to be rendered you can use: TreeLoader => preloadChildren : Boolean
    If set to true, the loader recursively loads "children" attributes when doing the first load on nodes.
    Mike Giddens
    =======================
    Opportunity is missed by most people because it is dressed in overalls and looks like work - Thomas Edison

  3. #3
    Ext User
    Join Date
    Mar 2007
    Posts
    27
    Vote Rating
    0
    dselkirk is on a distinguished road

      0  

    Default


    This was done as a combination of fastest way to generate the data and using our existing parent based structure. Instead of building a complex json that would require server side recursion we simple output a single level json array. We also did not want the children to load initially. We have a site which has happen to grown to over 2000 pages. One option was to load all the nodes upfront but it takes way to long to generate that many. The other option was to use async but we found the response time to be to slow and it added extra load to a server that wasn't needed. Our answer was to perform a single call to server to retrieve the data. Then we simple load the nodes as required. This allowed us to maintain our parent structure but also keep the server and client side loads lite.

    Thanks for the suggestion.

  4. #4
    Ext JS Premium Member mikegiddens's Avatar
    Join Date
    Mar 2007
    Location
    Denver, Colorado
    Posts
    273
    Vote Rating
    1
    mikegiddens will become famous soon enough

      0  

    Default


    I know you already have it done but another way to think about it is if you have a static list you can use a script/cronjob that generates cache files somewhere for your async requests that way it doesn't have to hit a db or do a file recursion every time.

    Sometimes the fastest way is a lookup table methodology. It is sometimes overlooked when the data is not changed very often.

    Either way it is always good to have more ux additions.

    cheers!
    Mike Giddens
    =======================
    Opportunity is missed by most people because it is dressed in overalls and looks like work - Thomas Edison

  5. #5
    Ext User
    Join Date
    Mar 2007
    Posts
    27
    Vote Rating
    0
    dselkirk is on a distinguished road

      0  

    Default


    I actually do cache my list on my server to prevent extra db calls. It was also the issue with the extra calls to the server. On a slower connection the wait for the async node to call the server, respond and then generate nodes can hurt user interactions.

    I guess it really is based upon preferences, the focused audenence and application requirements.

    Thanks for all your feedback.

  6. #6
    Ext User
    Join Date
    Jan 2008
    Posts
    1
    Vote Rating
    0
    mudou is on a distinguished road

      0  

    Default


    {"pages":[
    {"id":1,"parent":0,"text":"default","url":"\/default\/"},
    {"id":2,"parent":0,"text":"contactus","url":"\/contactus\/"},
    {"id":3,"parent":0,"text":"sitemap","url":"\/sitemap\/"},
    {"id":4,"parent":0,"text":"aboutus","url":"\/aboutus\/"},
    {"id":5,"parent":4,"text":"company-mission","url":"\/aboutus\/company-mission\/"}
    ]}

    if i change all above ‘text’ to ‘name’,and i also changed the line

    ”dataFields: ['id','parentid','text','url'] “ to “dataFields: ['id','parentid','name','url'] ”

    the treenode’name do not display !!! why?

  7. #7
    Sencha Premium Member Fredric Berling's Avatar
    Join Date
    Sep 2007
    Location
    Sweden
    Posts
    186
    Vote Rating
    15
    Fredric Berling has a spectacular aura about Fredric Berling has a spectacular aura about

      0  

    Default Interesting indeed

    Interesting indeed


    I am in the same situation with a structure built on the childrens knowledge of their parent object and find your solution quite interesting for me. I tested you example but when i have expanded a folder it looses its "folder look" have no "minus" to click. Any ideas?

  8. #8
    Ext User
    Join Date
    Feb 2008
    Posts
    12
    Vote Rating
    0
    KirkOlson is on a distinguished road

      0  

    Default


    I always found this extension come in very handy. However, it doesn't seem to be working in Ext 3.1 anymore. Does anyone have a solution?