So, as the urge to tinker rises again, I've started playing with YUI and cracked open some XoxoOutliner code again. One of the things that struck me like a bolt from the blue recently is the notion of event delegation. For the most part, I've treated event bubbling as a nuisance, except for where it came in handy in keyboard input handling. But avoiding the need to individually track events on every list element on the page via instantiated objects and methods is hot and forehead-slappingly obvious once you've seen it working. Just implement one set of event handlers on the outline root element (ie. UL or OL), and do the right thing as events come in from the list child elements.

Thus, while I can do outline node expand/collapse with ease, my goal is to implement an event delegation based approach to dragging outline nodes around. Apropos of that, I've been poking at how event delegation it works in YUI. While there's nothing much special about it in general, I've run into a bit of a snag when it comes to drag-and-drop.

See, YUI comes with a rather nice drag-and-drop abstraction that I'd like to use. That abstraction, however, requires the instantiation of lots of little objects - one per outline node. Definitely not delegation-based: The problem is that I'll be adding and removing outline nodes at whim throughout the lifetime of the page, which would require lots of care to ensure that I'm maintaining drag-and-drop wrapper objects. I've done that a bit in XoxoOutliner; it sucks.

So, I was able to build part of a delegation-based drag tracker from scratch. I'm sad to lose the other niceties that the YUI DnD offers, but it works. The problem now, though, is the drop part of the equation. I can drag elements around the page until my heart's content, but I can never quite tell in what context I've dropped it.

I poured through the source code for the YUI DnD implementation, as well as the implementation of a few other JS frameworks' DnD offerings. The general solution seems to be calculating and caching the page coordinate regions for each element relevant in the drop operation. In my case, that usually includes every outline node on the page. That's easy when you have lots of little objects instantiated - you just iterate through them and match up regions with the coordinates where the drag stopped. But, part of my win with event delegation was that I don't have to maintain a pile of objects.

Oh yeah, and did I mention that I don't assign IDs to all the little list items making up my outline? And that I don't particularly feel like doing so? That also throws a monkey wrench in the YUI DnD paradigm.

The only idea I have so far is that I need to at least build a cache mapping regions to outline nodes, and keep it fairly well maintained. That should be lighterweight than a fleet of DOM event wrappers, but still annoying. I could trigger a cache refresh when a drag first starts, but that will probably drag down UI response. I could trigger it whenever the outline changes, but that's just moving the burden. And then, who knows how I'll map ID-less elements to their respective regions, while ensuring I stay clear of memory leaks. That's still semi-voodoo to me, and I feel ashamed of that.

Eh, maybe it won't be as bad as I think. But, to anyone who understands what I just spewed: Any ideas?

Archived Comments

  • You know, Marty and I were talking tonight about how some of your posts make NO sense to us. She even said "it's like he's talking another fucking language".

    Damn Programmers!!

    --nick

  • I'm sure I've missed something obvious - why can't you simply use the same event delegation approach on the mouseUp event? getTarget should return the node you've dropped on, what else do you need?

  • Gareth: Yup, the issue I have with the mouseUp is that getTarget gives me what was under the mouse when I let go of the button - namely, the element I've been dragging around, not the element under the thing being dragged. In addition, for user feedback during the drag (ie. at mouseMove time), I want to know what's under the dragged element - so I can highlight the potential landing spot, etc.

  • I presume the problem is that, given a point on the screen, you want to find exactly what DOM element is beneath it.

    I found a solution for this in my proof-of-concept structured editor http://www.fluffy.co.uk/stediting/ -- you need to know where the mouse was clicked to insert the caret.

    It might be a start anyway.

  • I think the dragdrop stuff in scriptaculous would get you most of this... at least, it is good about draggable elements having handles you can specify (e.g. child elements of the thing you want to drag), and flexible about what sorts of targets you can land on.

  • misuba: Nope, the Scriptaculous approach gives me problems too. The first is that I don't like Prototype, unless they've stopped messing with Object.prototype since I last checked. The second is that DnD in Scriptaculous involves juggling lots of little objects ilke most other DnD implementations I've found. When I have an outline of 100's to 1000's of items, that really blows. I think I've got an approach working now that's significantly lighter weight.

  • I'm interested to see your approach. (FWIW, Proto hasn't touched Object.prototype for a couple of versions now.)

  • I am working on the exact same issue for JavaScriptMVC. If you haven't seen JavaScriptMVC, check out its trunk. In the controller2 plugin, you'll find a really nice event delegation library.

    I've only thought of 1 way to do event delegation drops. It's crazy, but it might work.

    First, style Drags so their z-Index is LOWER than the Drops. You want your mouse to hit the droppable areas.

    Once a droppable is moused over by dragging a drag to it, it creates a transparent element in the exact same position as the Droppable. It also lowers the droppable z-Index. This gives the appearance that the draggable is over the droppable.

    To simulate movement, the transparent droppable's mouseovers are sent to the draggable.

    On mousing out of the transparent Droppable, it sets everything back to normal.

    I haven't tried this yet. The biggest issue I see is that when you grab a draggable in the middle, half of it will be behind the droppable until the mouseover happens. This isn't ideal, but maybe there is something that can be done about that.