1. #1
    Sencha Premium Member
    Join Date
    Jun 2011
    Location
    Toronto
    Posts
    74
    Vote Rating
    10
    roger.spall will become famous soon enough

      0  

    Default Tri-state tree

    Tri-state tree


    I guess it will take time for the 3.3 extemsions to catch up with ExtJs 4.0.1...

    But I would still like to know if anyone can point me to an ExtJS 4 tri-state tree?

    Thanks,

    Roger

  2. #2
    Sencha Premium Member
    Join Date
    Jun 2011
    Location
    Toronto
    Posts
    74
    Vote Rating
    10
    roger.spall will become famous soon enough

      0  

    Default Solution - critiques / feedback required...

    Solution - critiques / feedback required...


    I have included my solution below and would appreciate any feedback / suggestions / comments from others...

    USAGE:
    Code:
     var permissionsTree = Ext.create('Ext.tree.Panel', {
      title : 'Permissions',
      region : 'east',
      width: '100%',
      hideHeaders : true,
      rootVisible : false,
      animate : false,
      store : functionStore,
      viewConfig : {
       plugins : [ tristate ],
       listeners : {
        'beforetristate' : confirmPermissionsModified,
        'tristate' : permissionsModified
       }
      },
      columns : [ {
       xtype : 'tristatetreecolumn',
       flex : 2,
       sortable : true,
       dataIndex : 'name'
      } ], buttons:[{text:"Reset", disabled: true,id:'reset', handler: resetPermissions},{text:"Save", disabled: true,id:'save', handler: savePermissions}]
     });
    
    EXTENSION IMPLEMENTATION:

    Code:
     
    Ext.define('tristate.Plugin', {
     init : function(view) {
      view.updateParent = function(node) {
       if (node != null) {
        var tristate = -1;
        node.eachChild(function(rec) {
         var siblingTristrate = rec.get('tristate');
         if (tristate == -1) {
          tristate = siblingTristrate;
         } else {
          if (siblingTristrate != tristate) {
           tristate = 2;
           return false;
          }
         }
        });
        if (tristate != -1) {
         node.set('tristate', tristate);
        }
        this.updateParent(node.parentNode);
       }
      };
      view.onCheckboxChange = function(e, t) {
       var item = e.getTarget(this.getItemSelector(), this.getTargetEl()), record, value, newValue;
       if (item) {
        record = this.getRecord(item);
        value = record.get('tristate');
        newValue = value == 0 ? 1 : 0;
        var shouldContinue=this.fireEvent('beforetristate', record, newValue);
        if (shouldContinue){
         var affectedRecords=[];
         record.cascadeBy(function(rec) {
          rec.set('tristate', newValue);
          affectedRecords[affectedRecords.length]=rec;
         });
         record.set('tristate', newValue);
         this.updateParent(record.parentNode);
         this.fireEvent('tristate', record, newValue, affectedRecords);
        }
       }
      };
      view.setTristate = function(record, value) {
       record.cascadeBy(function(rec) {
        rec.set('tristate', value);
       });
       record.set('tristate', value);
       this.updateParent(record.parentNode);
      };
      view.setAllTristate = function(status) {
       this.getStore().each(function(rec) {
        rec.cascadeBy(function(rec) {
         rec.set('tristate', status);
        });
       }, true);
      };
      view.getChecked = function() {
       this.getChecked(false);
      };
      view.getChecked = function(partialMeansChecked) {
       var checked = [];
       this.node.cascadeBy(function(rec) {
        var ts = rec.get('tristate');
        if (ts > 0 && (partialMeansChecked || ts == 1)) {
         checked.push(rec);
        }
       });
       return checked;
      };
      view.getCheckedLeafs = function() {
       var checked = [];
       this.node.cascadeBy(function(rec) {
        if (rec.isLeaf() && rec.get('tristate') > 0) {
         checked.push(rec);
        }
       });
       return checked;
      };
     }
    });
    Ext.define('tristate.Column', {
     extend : 'Ext.grid.column.Column',
     alias : 'widget.tristatetreecolumn',
     initComponent : function() {
      var origRenderer = this.renderer || this.defaultRenderer, origScope = this.scope || window;
      this.renderer = function(value, metaData, record, rowIdx, colIdx, store, view) {
       var buf = [], format = Ext.String.format, depth = record.getDepth(), treePrefix = Ext.baseCSSPrefix + 'tree-', elbowPrefix = treePrefix + 'elbow-', expanderCls = treePrefix + 'expander', imgText = '<img src="{1}" class="{0}" />', checkboxText = '<input type="button" role="checkbox" class="{0}" {1} />', formattedValue = origRenderer.apply(origScope, arguments), href = record
         .get('href'), target = record.get('hrefTarget'), cls = record.get('cls');
       while (record) {
        if (!record.isRoot() || (record.isRoot() && view.rootVisible)) {
         if (record.getDepth() === depth) {
          buf.unshift(format(imgText, treePrefix + 'icon ' + treePrefix + 'icon' + (record.get('icon') ? '-inline ' : (record.isLeaf() ? '-leaf ' : '-parent ')) + (record.get('iconCls') || ''), record.get('icon') || Ext.BLANK_IMAGE_URL));
          var tristate = record.get('tristate');
          if (tristate != null) {
           buf.unshift(format(checkboxText, (treePrefix + 'checkbox') + (tristate == 1 ? ' ' + treePrefix + 'checkbox-checked' : (tristate == 2 ? ' ' + treePrefix + 'checkbox-partial-checked' : '')), record.get('checked') ? 'aria-checked="true"' : ''));
           if (tristate > 0) {
            metaData.tdCls += (' ' + Ext.baseCSSPrefix + 'tree-checked');
           }
          }
          if (record.isLast()) {
           if (record.isLeaf() || (record.isLoaded() && !record.hasChildNodes())) {
            buf.unshift(format(imgText, (elbowPrefix + 'end'), Ext.BLANK_IMAGE_URL));
           } else {
            buf.unshift(format(imgText, (elbowPrefix + 'end-plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
           }
          } else {
           if (record.isLeaf() || (record.isLoaded() && !record.hasChildNodes())) {
            buf.unshift(format(imgText, (treePrefix + 'elbow'), Ext.BLANK_IMAGE_URL));
           } else {
            buf.unshift(format(imgText, (elbowPrefix + 'plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
           }
          }
         } else {
          if (record.isLast() || record.getDepth() === 0) {
           buf.unshift(format(imgText, (elbowPrefix + 'empty'), Ext.BLANK_IMAGE_URL));
          } else if (record.getDepth() !== 0) {
           buf.unshift(format(imgText, (elbowPrefix + 'line'), Ext.BLANK_IMAGE_URL));
          }
         }
        }
        record = record.parentNode;
       }
       if (href) {
        formattedValue = format('<a href="{0}" target="{1}">{2}</a>', href, target, formattedValue);
       }
       if (cls) {
        metaData.tdCls += ' ' + cls;
       }
       return buf.join("") + formattedValue;
      };
      this.callParent(arguments);
     },
     defaultRenderer : function(value) {
      return value;
     }
    });
    

  3. #3
    Sencha User beavx420's Avatar
    Join Date
    May 2007
    Location
    New Jersey
    Posts
    32
    Vote Rating
    1
    beavx420 is on a distinguished road

      0  

    Default


    Roger... can you wrap your post in code tags, so the forums will format it correctly? Thanks!!

  4. #4
    Sencha User
    Join Date
    Apr 2009
    Posts
    48
    Vote Rating
    0
    morfeusz is on a distinguished road

      0  

    Default


    Can it be used for simply form`s checkBox?

  5. #5
    Sencha Premium Member
    Join Date
    Jun 2011
    Location
    Toronto
    Posts
    74
    Vote Rating
    10
    roger.spall will become famous soon enough

      0  

    Default


    No, it is strictly intended for grid trees

  6. #6
    Sencha User
    Join Date
    Mar 2010
    Posts
    51
    Vote Rating
    2
    koblass is on a distinguished road

      0  

    Default Do you have a running example ?

    Do you have a running example ?


    Hi,

    I've tried your code without any success...
    Do you have a running example ?

    Best
    Daniel

  7. #7
    Sencha User
    Join Date
    Nov 2010
    Posts
    12
    Vote Rating
    0
    x10 is on a distinguished road

      0  

    Default


    Does it work? I have also tried the code without any success.

  8. #8
    Sencha Premium Member
    Join Date
    Jun 2011
    Location
    Toronto
    Posts
    74
    Vote Rating
    10
    roger.spall will become famous soon enough

      0  

    Default


    It definitely works and is in production at a client:

    Code:
    Ext.define('common.admin.DealerUserPermissionsTreePanel', {
                   extend : 'Ext.tree.Panel',
                   requires : [ "common.TristatePlugin" ],
                   title : 'Permissions',
                   width : '100%',
                   height: '100%',
                   hideHeaders : true,
                   rootVisible : false,
                   constructor : function(config) {
                   Ext.define('FunctionModel', {
                                  extend : 'Ext.data.Model',
                                  fields : [ 'oid', 'name' ]
                   });
                  
                   this.functionStore = Ext.create('Ext.data.TreeStore', {
                                  model : 'FunctionModel',
                                  sorters : [ {
                                                 property : 'name',
                                                 direction : 'ASC'
                                  } ],
                                  proxy : {
                                                 type : 'memory'
                                  },
                                  folderSort : true
                   });
                   var tristate = Ext.create('common.TristatePlugin');
                   this.callParent([ {
                                  store : this.functionStore,
                                  viewConfig : {
                                                 plugins : [ tristate ],
                                                 listeners : {
                                                                'beforetristate' : {
                                                                               fn : this.confirmPermissionsModified,
                                                                               scope : this
                                                                },
                                                                'tristate' : {
                                                                               fn : this.permissionsModified,
                                                                               scope : this
                                                                }
                                                 }
                                  },
                                  columns : [ {
                                                 xtype : 'tristatetreecolumn',
                                                 flex : 2,
                                                 sortable : true,
                                                 dataIndex : 'name'
                                  } ],
                                  buttons : [ {
                                                 text : "Save",
                                                 disabled : true,
                                                 scope : this,
                                                 id : 'save',
                                                 handler : this.savePermissions
                                  },
                                  {
                                                 text : "Reset",
                                                 disabled : true,
                                                 scope : this,
                                                 id : 'reset',
                                                 handler : this.resetPermissions
                                  }
                                  ]                             
                                 
                   }]);
                   this.loadFunctions();
                   },
                   resetPermissions : function() {
                                  this.loadPermissions(this.userOid,this.userId);
                   },
                   savePermissions : function() {
                                  var functions = this.getView().getCheckedLeafs();
                                  var permissions = [];
                                  for ( var i = 0; i < functions.length; i++) {
                                                 var oid = functions[i].get("oid");
                                                 if (Ext.Array.indexOf(permissions, oid) == -1) {
                                                                permissions[permissions.length] = oid;
                                                 }
                                  }
                                  Ext.Ajax.request({
                                                 url : getDataRequestURL('userPermissions', 'setPermissions'),
                                                 params : {
                                                                userOid : this.userOid,
                                                                permissions : permissions
                                                 },
                                                 scope : this,
                                                 success : this.successfulSave,
                                                 failure : this.failedSave
                                  });
                   },
                   successfulSave : function(response, request) {
                                  this.setModified(false);
                                  Ext.Msg.alert('Success','Saved');
                   },
                   failedSave : function(response, request) {
                                  Ext.Msg.alert('Failed','Failed to save: "' + response.status + " - " + response.statusText + '"');
                   },
                   loadPermissions : function(userOid, userId) {
                                  this.userOid = userOid;
                                  this.userId = userId;
                                  Ext.Ajax.request({
                                                 url : getDataRequestURL('userPermissions', 'getPermissions'),
                                                 params : {
                                                                userOid : userOid
                                                 },
                                                 scope : this,
                                                 success : this.setPermissions,
                                                 failure : function(response, request) {
                                                                                              Ext.Msg.alert('Failed', response.responseText);
                                                                                 }
                                  });
                   },
                   setPermissions : function(response, request) {
                                  if (response.status == 200) {
                                                 var security = Ext.decode(response.responseText);
                                                 this.setTitle("Permissions for user '" + this.userId+ "'");
                                                 this.setCurrentUserPermissions(security.permissions);
                                                 this.setModified(false);
                                  } else {
                                                 Ext.Msg.alert('Failed','Failed to Set Threads from server response');
                                  }
                   },
                   setCurrentUserPermissions : function(permissions) {
                                  var view = this.getView();
                                  view.setAllTristate(0);
                                  permissions.sort();
                                  var root = this.functionStore.getRootNode();
                                  root.cascadeBy(function(rec) {
                                                 if (rec.isLeaf()) {
                                                                var oid = rec.get("oid");
                                                                var pos = Ext.Array.indexOf(permissions, oid);
                                                                if (pos > -1) {
                                                                               rec.set("tristate", 1);
                                                                               view.updateParent(rec);
                                                                }
                                                 }
                                  });
                   },
                   loadFunctions : function(){
                                  //this.getEl().mask('Loading data...');                       
                                  Ext.Ajax.request({
                                                 url : getDataRequestURL('userPermissions', 'getFunctions'),
                                                 scope : this,
                                                 success : this.setFunctions
                                  });
                                 
                   },
                   setFunctions : function(response, request) {
                                  //this.getEl().unmask();
                                  if (response.status == 200) {
                                                 var result = Ext.decode(response.responseText);
                                                 var root = this.functionStore.getRootNode();
                                                 root.removeAll();
                                                 for ( var i = 0; i < result.items.length; i++) {
                                                                this.buildNode(root, result.items[i]);
                                                 }
                                                 this.modified = false;                                     
                                  } else {
                                                 alert("Failed to Set Functions from server response");
                                  }
                   },
                   buildNode : function(root, items) {
                                  if (items[0].length) {
                                                 var parent = root.appendChild({
                                                                name : items[items.length - 1],
                                                                leaf : false,
                                                                tristate : 0
                                                 });
                                                 for ( var i = 0; i < items.length - 1; i++) {
                                                                this.buildNode(parent, items[i]);
                                                 }
                                  } else {
                                                 root.appendChild({
                                                                oid : items[0],
                                                                name : items[1],
                                                                leaf : true,
                                                                tristate : 0
                                                 });
                                  }
                   },
                   confirmPermissionsModified : function(record, value) {
                                  if (!this.userOid){
                                                 showError("Must Select a User first");
                                                 return false;
                                  }
                   },
                   permissionsModified : function(record, value, affected) {
                                  var affectedIds = [];
                                  var affectedOids = [];
                                  for ( var i = 0; i < affected.length; i++) {
                                                 var rec = affected[i];
                                                 if (rec.isLeaf()) {
                                                                affectedIds[affectedIds.length] = rec.id;
                                                                affectedOids[affectedOids.length] = rec.get("oid");
                                                 }
                                  }
                                  affectedIds.sort();
                                  affectedOids.sort();
                                  var view = this.getView();
                                  this.functionStore.getRootNode().cascadeBy(function(rec) {
                                                 if (Ext.Array.indexOf(affectedOids, rec.get("oid")) > -1 && Ext.Array.indexOf(affectedIds, rec.id) == -1) {
                                                                view.setTristate(rec, value);
                                                 }
                                  });
                                  this.setModified(true);
                   },
                   setModified : function(modified) {
                                  this.query("#save")[0].setDisabled(!modified);
                                  this.query("#reset")[0].setDisabled(!modified);
                   }
                  
    });

  9. #9
    Sencha User
    Join Date
    Nov 2010
    Posts
    12
    Vote Rating
    0
    x10 is on a distinguished road

      0  

    Default


    Thanks Roger. It works, but not 'definitely'.
    1. I have splitted your 'extension code' from 2nd post into 2 files/classes.
    2. I have correctly namespaced them (and registered into my Ext.Loader 'paths' config)
    3. I had to define field 'tristate' in my Model of TreeStore. It was not working without it.
    4. I have disabled the test 'shouldContinue', because i don't have any listener for 'beforetristate'. IMO the cascade should be always recalculated. (?)
    5. Now it is working, but the style for tristate==2 ('checkbox-partial-checked') is never being rendered. I see only standard on/off checkboxes. (?)

    ? Is your code in 2nd post still valid ?

  10. #10
    Sencha User
    Join Date
    Nov 2010
    Posts
    12
    Vote Rating
    0
    x10 is on a distinguished road

      0  

    Default


    Quote Originally Posted by x10 View Post
    5. Now it is working, but the style for tristate==2 ('checkbox-partial-checked') is never being rendered. I see only standard on/off checkboxes. (?)
    SOLVED:
    Code:
    .x-tree-checkbox-partial-checked {
        background-position:0 -26px;
    }