Click "Don't fire event" after dragging (sometimes) to d3.js

Observed behavior

I am using d3.js and I am in a situation where I want to update some data based on drag

and redraw everything after the event dragend

. Dragged items also have behavior click

.

Dragged items can only move along the x-axis. When an element is being dragged and the cursor is directly over the dragged element on dragend/mouseup

, the element must be double-clicked after re-drawing for the event click

to fire. When an element is being dragged but dragend/mouseup

not directly over the element, the event click

fires as expected (on the first try) after being redrawn.

Desired behavior

I would like the event to click

always fire on the first click after dragging, no matter where the cursor is.

If I replace the event click

on the dragged items with an event mouseup

, everything works as expected, but click

this is an event I'd really like to handle.

Demonstration

Here's an example: http://jsfiddle.net/RRCyq/2/

And here is the relevant javascript code:

var data, click_count,did_drag;
// this is the data I'd like to render
data = [
    {x : 100, y : 150},
    {x : 200, y : 250}
];
// these are some elements I'm using for debugging
click_count = d3.select('#click-count');
did_drag = d3.select('#did-drag');

function draw() {
    var drag_behavior,dragged = false;

    // clear all circles from the svg element
    d3.select('#test').selectAll('circle')
        .remove();

    drag_behavior = d3.behavior.drag()
        .origin(Object)
        .on("drag", function(d) {
            // indicate that dragging has occurred
            dragged = true;
            // update the data
            d.x = d3.event.x;
            // update the display
            d3.select(this).attr('cx',d.x);
        }).on('dragend',function() {
            // data has been updated. redraw.
            if(dragged) { draw(); }
        });

    d3.select('#test').selectAll('circle')
        .data(data)
        .enter()
        .append('circle')
        .attr('cx',function(d) { return d.x; })
        .attr('cy',function(d) { return d.y; })
        .attr('r',20)
        .on('click',function() {
            did_drag.text(dragged.toString());
            if(!dragged) {
                // increment the click counter
                click_count.text(parseInt(click_count.text()) + 1);
            }
        }).call(drag_behavior);
}

draw();

      

+3


source to share


2 answers


After we noticed that the click required before my SVG circles start responding to click events could again happen anywhere in the document, I settled into a hack whereby I simulate a click event on the document (thanks to fooobar. com / questions / 2828 / ... ) after the drag is complete. It's ugly, but it works.

Here's a function to simulate the event (again, thanks to fooobar.com/questions/2828 / ... )

function eventFire(el, etype){
  if (el.fireEvent) {
    (el.fireEvent('on' + etype));
  } else {
    var evObj = document.createEvent('Events');
    evObj.initEvent(etype, true, false);
    el.dispatchEvent(evObj);
  }
}

      

And here's the updated drag and drop behavior:



drag_behavior = d3.behavior.drag()
    .origin(Object)
    .on("drag", function(d) {
        // indicate that dragging has occurred
        dragged = true;
        // update the data
        d.x = d3.event.x;
        // update the display
        d3.select(this).attr('cx',d.x);
    }).on('dragend',function() {
        // data has been updated. redraw.
        if(dragged) { draw(); }
        // simulate a click anywhere, so the svg circles
        // will start responding to click events again
        eventFire(document,'click');
    });

      

Here is a complete working example of my hacky "fix":

http://jsfiddle.net/RRCyq/3/

+2


source


A little late to the party, buuuut ...

The documentation suggests that you use d3.event.defaultPrevented

in your click

event to see if an element was just being dragged. If you combine this with events drag

and dragend

, a much simpler approach is to call the function you want (see When and how it is called flashRect

):

http://jsfiddle.net/langdonx/fE5gN/



var container,
    rect,
    dragBehavior,
    wasDragged = false;

container = d3.select('svg')
    .append('g');

rect = container.append('rect')
    .attr('width', 100)
    .attr('height', 100);

dragBehavior = d3.behavior.drag()
    .on('dragstart', onDragStart)
    .on('drag', onDrag)
    .on('dragend', onDragEnd);

container
    .call(dragBehavior)
    .on('click', onClick);

function flashRect() {
    rect.attr('fill', 'red').transition().attr('fill', 'black');
}

function onDragStart() {
    console.log('onDragStart');
}

function onDrag() {
    console.log('onDrag');

    var x = (d3.event.sourceEvent.pageX - 50);

    container.attr('transform', 'translate(' + x + ')');

    wasDragged = true;
}

function onDragEnd() {
    if (wasDragged === true) {
        console.log('onDragEnd');

        // always do this on drag end
        flashRect();
    }

    wasDragged = false;
}

function onClick(d) {
    if (d3.event.defaultPrevented === false) {
        console.log('onClick');

        // only do this on click if we didn't just finish dragging
        flashRect();
    }
}

      

I didn't like the global variable, so I made a revision to use the data: http://jsfiddle.net/langdonx/fE5gN/1/

+6


source







All Articles