Monday, February 24, 2025

dragging and dropping abandoning jQuery UI and getting a handle on things

 (Sorry, title is a bad joke; basically I'm saying I'm tired of jQuery being dragged, and I'm not dropping it)

One of the centerpieces of my porchfest empire has been "the Hourtron", a drag and drop tool for putting bands on porches. I posted about its first prototype in 2015. Since then I'd rewritten it a few times.

I remembered a failed rewrite in 2019... I think one big mistake was avoiding resue of tragically unhip jQueryUI, which seems to have a particularly good drag and drop facility - I didn't search THAT hard for a lighter replacement this go 'round, but the ones I did try didn't have a simple "snap" facility.

Also I'm not sure if in 2019 if I was doing Declarative drag and drop "properly", but here I have the server endpoint (in PHP) return the entire state of band, porches, and the gigs linking them, and I remove the whole grid and build it up again in Vanilla JS every time a band is dropped (avoiding "batch processing" was THE major impetus for the rewrite; some porchfests now have porches scheduling their own bands, and i didn't want to risk overwriting that information. The other big win was the "untimed" column where you could show a band was associated with a porch (often at the band's request) while holding off on figuring just when the band would play.

Honestly I love the conciseness of old school jQuery in messing with the DOM; $(".someclass").css() or $("id") is so much more concise than the vanilla equivalents, making `map()` like behavior over all matching elements effortless, years before JS had .map() built in

and the drag and drop code was similarly concise, here are some bits and pieces for the flavor:

Setting up each band:

$band.draggable({
    snap:".target",
    snapMode: "inner",
    snapTolerance: 10,
    cursor:"move",
    start: function() {
        $(this).addClass('dragging');
    },
    stop: function() {
        $(this).removeClass('dragging');
    }
});

Making the drop targets:

$(".timeblock").droppable({hoverClass:'over',drop:bandDropOnTime});
$(".untimedblock").droppable({hoverClass:'over',drop:bandDropOnUntimed});
$(".unplacedblock").droppable({hoverClass:'over',drop:bandDropOnUnplaced});

And then getting the drop and the drag (using the jQuery paradigm of stuff things as ".data()" on the element.

function bandDropOnTime(event,ui){  
    const $timeblock = $(this);
    const porchid = $timeblock.data("porchid");
    const hour = $timeblock.data("hour");
    const minute = $timeblock.data("minute");
 
    const $bandblock = ui.draggable;
    const band = $bandblock.data("band");
    const bandid = $bandblock.data("bandid");

    saveGigData(bandid,porchid,hour,minute,band.performancelength,band.actname);
}


One gotcha with that was not using fat arrow notation; jQuery relies on a sense of "this" (via "$(this)") when attaching an event handler - but "this" has fallen from grace (and rightfully so, it was pretty confusing) and fat arrow tends to blow away - so oldschool anonymous "function()"

Another gotcha I had forgotten about: the draggable bands were much wider than the target time slots, and dropping would often not put the band where it looked like it had been let go. So the actual "draggable" part of the band element is the size of one time slot, and then it has a child that's as wide it needs to be to indicate the desired performance length. That seemed to be the simplest workaround for making it clear where the user was trying to drop the band (as you can see in the screen shot I use an extra shot of "red" for the start time- I started that as a diagnostic placeholder but actually highlighting the start time is a reasonable UX thing to do.)

No comments:

Post a Comment