Lucas
Lucas

Reputation: 8133

How to arrange top-level node items in a dijit Tree via drag and drop

I have a dijit.Tree backed by a ForestStoreModel that uses a custom data store to provide the data. It works well, but I want to provide the ability for the user to rearrange the top-level items (and only the top level) items via the dojo drag-and-drop facility.

The problem is the the ForestStoreModel::pasteItem function checks to see if item is a child of the root and then passes a null to the TreeStoreModel::pasteItem.

pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){
        // summary:
        //      Move or copy an item from one parent item to another.
        //      Used in drag & drop
        if(oldParentItem === this.root){
            if(!bCopy){
                // It's onLeaveRoot()'s responsibility to modify the item so it no longer matches
                // this.query... thus triggering an onChildrenChange() event to notify the Tree
                // that this element is no longer a child of the root node
                this.onLeaveRoot(childItem);
            }
        }
        dijit.tree.TreeStoreModel.prototype.pasteItem.call(this, childItem,
            oldParentItem === this.root ? null : oldParentItem,
            newParentItem === this.root ? null : newParentItem,
            bCopy,
            insertIndex
        );
        if(newParentItem === this.root){
            // It's onAddToRoot()'s responsibility to modify the item so it matches
            // this.query... thus triggering an onChildrenChange() event to notify the Tree
            // that this element is now a child of the root node
            this.onAddToRoot(childItem);
        }
    }

The TreeStoreModel does not update the underlying data store if the passed parent items are null and the onLeaveRoot and onAddToRoot events do not pass in the insertIndex, so I cannot use them to update my data store (which seems like it would be a bit backwards, anyway).

At this point I think the only viable option is to extend the ForestStoreModel in order to allow me to set the synthetic $root$ item to a compatible data store object and allow the ForestStoreModel to pass it, unaltered, through to the TreeStoreModel.

Are there other way of approaching this problem?

Update

The eventual solution turned out to be even simpler than suggested. My ForestStoreModel is already a custom class because I am actually using the dojo 1.6 ObjectStore as a data source, so I can pass the desired index in the options argument to the object store put method. The fix was just a one-liner as I let the parent class take care of calling onLeaveRoot and onAddRoot:

pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){
    // Handle drag & drop at the root level
    if (oldParentItem === this.root && newParentItem === this.root){
        this.store.put(childItem, { index: insertIndex });
    } 
    this.inherited(arguments);
}

Upvotes: 2

Views: 1861

Answers (1)

Gustavo Giráldez
Gustavo Giráldez

Reputation: 2690

You will need to subclass ForestTreeModel, you can't escape that. But, you'll only need to override pasteItem. You can't pass the synthetic root to the TreeStoreModel because it doesn't know anything about it.

If you need to modify the underlying data store, you're better off calling this.store.setValues() directly. That should trigger the onSetItem event and in turn call _requeryTop() for you, which will fetch the roots from the underlying store in whatever order you arrange them, so be sure to reflect that in your modification.

dojo.declare('MyForestTreeModel', [ dijit.tree.ForestTreeModel ], {
  pasteItem: function(childItem, oldParentItem, newParentItem, bCopy, insertIndex) {
    if (oldParentItem == this.root && newParentItem == this.root) {
      if (!bCopy) { this.onLeaveRoot(childItem); }
      // modify the underlying store somehow so the call to _requeryTop() fetches
      // the items in the correct order.
      // (you decide what's 'order' and what new_order() returns)
      this.store.setValues(childItem, 'order', new_order(insertIndex));
      this.onAddRoot(childItem);
    } else {
      // call super
      this.inherited(arguments);
    }
  }
});

The other, easier way, is to manipulate this.root.children yourself and then emit the event onChildrenChange to notify the views. Beware, that method won't persist the order.

dojo.declare('MyForestTreeModel', [ dijit.tree.ForestTreeModel ], {
  pasteItem: function(childItem, oldParentItem, newParentItem, bCopy, insertIndex) {
    if (oldParentItem == this.root && newParentItem == this.root) {
      if (!bCopy) { this.onLeaveRoot(childItem); }
      // manipulate this.root.children to reorder childItem
      // remove child from the current position
      var children = dojo.filter(this.root.children, function(x) {
        return x != childItem;
      });
      // and insert it into the new index
      children.splice(insertIndex, 0, childItem);
      this.root.children = children;
      // notify views
      this.onChildrenChanged(this.root, children);
      this.onAddRoot(childItem);
    } else {
      // call super
      this.inherited(arguments);
    }
  }
});

Upvotes: 1

Related Questions