pjekel / cbtree

The Dijit Tree with Multi State Checkboxes, project code 'cbtree' , is a highly configurable dojo/dijit tree with support for multi-state checkboxes or third party widgets capable of presenting a so-called 'checked' state.
Other
75 stars 34 forks source link

Retrieving depth of an item #46

Closed aramk closed 10 years ago

aramk commented 10 years ago

Is there any way to get the depth of an item in the tree? The $root$ node would be 0 and the immediate children would be 1 etc. I could write the traversal function using the model's getParents() to get the current item's depth and use it with getChildren() to determine the children depths.

pjekel commented 10 years ago

The with the next cbtree release (1.0) each node will have a depth property. With releases 0.9x you could use the node's indent property instead. Be aware though that if the tree property showRoot is set to false the indent of the root node will be -1 and the immediate children of the root get indent 0.

aramk commented 10 years ago

Thanks for that! I ended up implementing a depth property pretty easily since I already had a method to recursively find all parents and children of an item. I'll post it here for anyone who finds it useful:

// Inside a Dojo class called _TreeMixin:

/**
 * @param item - A store item.
 * @returns {dojo.Deferred} Gets all parents and all children recursively.
 */
getAllParentsAndChildren: function(item) {
  var results = {
        parents: [],
        children: []
      },
      df = new Deferred();
  this.getAllParents(item).then(lang.hitch(this, function(parents) {
    results.parents = parents;
    var index = 0;
    for (; index < results.parents.length; index++) {
      parents[index].index = index;
    }
    item.index = index;
    results.children = this.getAllChildren(item);
    df.resolve(results);
  }), function(err) {
    df.reject(err);
  });
  return df;
},

/**
 * @param item - A store item.
 * @returns {dojo.Deferred} A promise which contains an array of all recursive parents of the
 * item.
 */
getAllParents: function(item) {
  var getParent = lang.hitch(this, function(someItem) {
    var df = new Deferred(),
        reject = function(err) {
          df.reject(err);
        };
    this.model.getParents(someItem).then(lang.hitch(this, function(parents) {
      var dfs = [],
          allParents = [];
      array.forEach(parents, function(parent) {
        dfs.push(getParent(parent));
      }, this);
      all(dfs).then(lang.hitch(this, function(manyParents) {
        manyParents.push(parents);
        array.forEach(manyParents, function(someParents) {
          array.forEach(someParents, function(aParent) {
            if (!this.isRoot(aParent)) {
              allParents.push(aParent);
            }
          }, this);
        }, this);
        df.resolve(allParents);
      }), reject);
    }), reject);
    return df;
  });
  return getParent(item);
},

/**
 * @param item - A store item.
 * @returns {Array.<Object>} A promise which contains an array of all recursive children of the
 * item.
 */
getAllChildren: function(item) {
  var allChildren = [];
  var toVisit = ([]).concat(item);
  while (toVisit.length > 0) {
    var item = toVisit.pop();
    this.model.getChildren(item, lang.hitch(this, function(children) {
      // Opted not to use concat() since it recreates the entire array.
      array.forEach(children, function(child) {
        var index = item.index;
        if (index !== undefined) {
          child.index = index + 1;
        }
        allChildren.push(child);
        toVisit.push(child);
      }, this);
    }));
  }
  return allChildren;
}
pjekel commented 10 years ago

As a FYI:

If you use a cbtree Hierarchy or Object store you could use the store extension Ancestry which offers all the functionality you are looking for. The extension has methods like: getAncestors() or getDescendants().

The extension can be found @ cbtree/store/extensions/Ancestry

aramk commented 10 years ago

Thanks for that, definitely a better option than reinventing the wheel!

pjekel commented 10 years ago

Be aware though, accessing the store directly from the tree violates the Model-View-Controller pattern, which may be fine for your application. Also, the store's getAncestors() and getDescendants() methods rely on the store hierarchy which may be different from the tree hierarchy.

Consider the following:

var carData = [
      { name:"Cars" ,parent: null , type:"toys" },
      { name:"Audi" ,parent:"Cars", type:"factory" },
      { name:"BMW"  ,parent:"Cars", type:"factory" },
      { name:"A3"   ,parent:"Audi", type:"sedan" },
      { name:"Q7"   ,parent:"Audi", type:"suv" },
      { name:"M3"   ,parent:"BMW" , type:"sedan" },
      { name:"535"  ,parent:"BMW" , type:"sedan" },
      { name:"750"  ,parent:"BMW" , type:"sedan" },
    ];
                  ...
var myStore = new Hierarchy({data: carData, ... });
var myModel = new ForestStoreModel({store: store, query: {type:"sedan"}, ... });

In this case the tree will show "A3", "M3", "535" and "750" as children of the tree root. whereas:

var parents = store.getAncestors("A3");

returns the objects "Audi" and "Cars", not the tree root., and

var parents = store.getAncestors("535");

returns the objects "BMW" and "Cars"

The same applies when using the model getParents() method. See also: Store Root versus Tree Root

Hope this helps.

aramk commented 10 years ago

Ah thanks for that clarification!