0

I have what I suspect is a rather basic question on how d3 works. I've started off with this code that works perfectly and triggers the nodeClick actions...

var nodeEnter = node.enter()
                        .append("g")
                        .attr("class", "node")
                        .on("click", nodeClick)

But then I changed it to use a function on click to determine whether a single or double click was used...

.on("click", function(d) {
                            if some logic ()  {
                               console.log("double click");
                            } else {
                                console.log("single click only");
                                nodeClick;
                            }
                        })

This works perfectly in terms of outputting the correct console messages, but it seems like my call to nodeClick isn't working properly while embedded within in a function (i.e. the node click behaviours aren't triggered). I tried changing to nodeClick() and nodeClick(d) but that just results in errors.

Am I missing something that could explain this behaviour? Seems very strange to me that I'm seeing 2 different behaviours to calling "nodeClick" from outside and inside a function.

Thanks for any help!

Here's the complete code in question...

dblclick_timer = false;
//.on("click", nodeClick) //works perfectly
.on("click", function(d) {
    if ( dblclick_timer )  {
        clearTimeout(dblclick_timer)
        dblclick_timer = false
        console.log("double click");
        d.fixed=false;
    }
    else dblclick_timer = setTimeout( function(){
                            dblclick_timer = false
                            console.log("single click only");
                            d3.select(this).nodeClick;
                        }, 250)
})

After all the great feedback, here's the working solution by storing d3.event before it becomes null...

.on("click", function(d,i) {
                            var cacheEvent = d3.event;
                            if ( dblclick_timer )  {
                                clearTimeout(dblclick_timer)
                                dblclick_timer = false
                                console.log("double click");
                                d.fixed=false;
                                force.start(); 
                            }
                            else dblclick_timer = setTimeout( function(){
                                dblclick_timer = false
                                console.log("single click only");
                                d3.event = cacheEvent;
                                nodeClick.call(d3.select(this), d, i);
                            }, 250)
                        })
6
  • You need to pass the context while calling a function from inside the callback function. Trying passing "this" or outside context in some way and calling with that should work . (Ex: this.nodeClick) Commented Jul 2, 2015 at 17:13
  • Thanks Tarang. I tried this.nodeClick and d3.select(this).nodeClick but it hasn't worked - is there anyway to go 1 step further back in terms of the this selection?? the nodeClick is actually embedded within a second function() that I omitted from my original post to help make simpler! Commented Jul 2, 2015 at 17:17
  • P.s. I've updated my original post with complete code to show the issue more clearly! Commented Jul 2, 2015 at 17:25
  • You're not calling nodeClick. You need to use parentheses to call the function. If that causes an error, then please share the error and the code where it occurs and we can work from there. Commented Jul 2, 2015 at 17:37
  • But then I'm not sure why I don't need parentheses when calling directly with ".on("click", nodeClick)"? Here's the error I get when I add "()" - "Uncaught TypeError: Cannot read property 'defaultPrevented' of null" This relates to the first line of the nodeClick function... if (d3.event.defaultPrevented) return; // ignore drag Commented Jul 2, 2015 at 17:41

2 Answers 2

2

As stated in the first comment, you have to establish the correct context: it's a javaScript thing, not a d3 thing. If you establish the correct this context and pass the same parameters it will work exactly the same. The key is to use Function.prototype.call to invoke the callback. This is standard javaScript.

.on("click", function(d, i) {
                            if some logic ()  {
                               console.log("double click");
                            } else {
                                console.log("single click only");
                                nodeClick.call(this, d, i);
                            }
                        })  

As stated in the documents

The specified listener is invoked in the same manner as other operator functions, being passed the current datum d and index i, with the this context as the current DOM element.

Sign up to request clarification or add additional context in comments.

Comments

1

As pointed out in the comments, you're not actually calling the function. In the case

.on("click", nodeClick)

the function nodeClick() is installed as the handler for the click event. Here nodeClick refers to the function object -- that is, the function itself, not any value it may return for a particular input.

In the second case, you're doing the same thing, except that you're passing in an anonymous (i.e. defined on the spot) function rather than a named function. In both cases, the function will be called when the event occurs.

The statement nodeClick; on its own has no effect -- it is essentially the same as saying 1; or foo; where foo is a variable or function defined elsewhere.

To make this work, you need to call the function -- add () after the name. In addition, you need to pass any arguments the callback has received -- nodeClick(d). You also need to call it with the same this context as the implementation of the function relies on it. All of this is done for you if you use selection.each():

d3.select(this).each(nodeClick);

9 Comments

Thanks Lars! I think that's nearly got me there. The problem is that, using exactly "d3.select(this).each(nodeClick);" underneath "console.log("single click only");" still hits the same error, although changing to "d3.select(this).nodeClick(d);" provides what I hope is a better error message of 'Uncaught TypeError: d3.select(...).nodeClick is not a function'. Does this mean I haven't set the context correctly? I.e. that nodeClick isn't visible from where I am (embedded in 2 functions)
Where are you defining nodeClick?
That's a fairly expensive way to establish the context actually and it doesn't quite get it right because the second parameter (i) will always be zero, so the actual index of the node will be lost, that's why it will not always work the same as the original case. Function.prototype.call is the go here.
Good point! It doesn't matter in this particular case as the index isn't used, but you're right that in general, your solution is better.
Actually, that's an assumption Lars coz we haven't had the privilege of seeing the famous nodeClick, for all we know it consumes the i argument. :p
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.