views:

325

answers:

2

I have created a jQuery plugin that allows the user to interact with a tree (creating, updating, deleting nodes). There are at least a dozen methods for interacting with the tree. Ideally, I don’t want to pollute the jQuery namespace with all of these tree-specific methods as I am doing now as each methods presents an additional opportunity for a namespace collision. I just want the one method for activating the plugin itself and so I am looking to refactor.

$('ol#tree').tree(options) // just a single jQuery method for my plugin is fine…
$('li#item4').moveNode('li#item6') // …however, we also have this supporting method
$('ol#tree').appendNode(node) // …and this one
$('li#item2').expandBranch() // …and this one, etc.

What are some practices for exposing an interface to the activated DOM element and its sub-elements that don’t pollute the jQuery namespace?

I have toyed with saving jQuery object that comes back when I create the tree.

var tree =  $('ol#tree').tree();

This tree object itself would be extended to include the methods. Unfortunately, as the tree itself is being regularly manipulated, this runs the risk of having the state of that object fall out of sync with the state of the parent DOM element and its sub-elements. I’m not sure trying to maintain state in that object is worth it, since the DOM itself will always have the current state.

I have also considered using a namespace (via John Resig’s Space plugin):

$('ol#tree').tree()
$('ol#tree').tree.appendNode(node)
$('ol#tree').tree.item('li#item2').expandBranch()

How do you handle this problem? Could you point me to some existing plugins that address this issue from whose code I could learn?

+2  A: 

Use a short and crisp name! Not the boring tree, but for example nicetree (Hey, this is just an idea)! Put the interface into the name. Like this:

$("ol#tree").nicetree.appendNode(node);
$("ol#tree").nicetree.item("li#item2").expandBranch();

Another idea is the object that comes back as you proposed, but don't update this object. This object is only a reference to the real object or DOM. This adds a layer of indirection and this might or might not be acceptable to you.

I as a plugin user would like both solutions, but prefer the first one slightly.

nalply
I have seen this approach and like it. I had already worked something out, however, as seen in the answer I entered.
Mario
No reason you can't do both.
BlueRaja - Danny Pflughoeft
How would the appendNode() or item() method refer back to the jQuery object that contains the selected elements (in this case, $("ol#tree"))? It seems like it suffers the problem that "this" in those functions doesn't take you back far enough to find the jQuery object, just the nicetree object, which is a static object created from the jQuery prototype.
thomasrutter
A: 

Here's what I came up with since I asked:

MarioWare = {
  Tree: {
    Methods: {
      nodes: function(){
        var selected = null;
        var context = this[0];
        if (selector === undefined) // tree.nodes();
          selected = $('li', context);
        else if (typeof selector == 'string') // tree.nodes('li:first,li:last');
          selected = $(selector, context);
        else if (typeof selector == 'number') // tree.nodes(2);
          selected = $('li', context).eq(selector);
        return MarioWare.$node(selected);
      },
      appendNode: function(){...}
    }
  },
  $tree: function(selector){
    return $.extend($(selector), MarioWare.Tree.Methods);
  },
  Node: {
    Methods: {
      select: function(multiSelect) {...}
    }
  },
  $node: function(selector){
    return $.extend($(selector), MarioWare.Node.Methods);
  }
}

In this way it works like some of the utility methods found in Prototype.js:

var $tree = MarioWare.$tree; //shortcut
var $node = MarioWare.$node; //shortcut
var tree = $tree('ul:first');
var nodes = tree.nodes();
nodes.eq(0).select();

It always requires a utility accessor (e.g $tree, $node), but it prevents the jQuery namespace from getting polluted. I like that the accessor clarifies the context of what we're working with--a tree or a node, for example.

Mario
For this to work all return values have to rewrap the result with the appropriate utility method. e.g. "select" would have return "$node(result)". I also had to override the basic jQuery functions (like "eq") to return the wrapped result. Ugh! A little clunky--but it works.
Mario