javascriptd3.jssvgdom-eventsmodule-pattern

Issue attaching javascript events using variation on module pattern


I'm trying to attach Javascript events to SVG elements using code that is inside the "module pattern". I'm very new to Javascript, as I'm sure will be evident by the code below. I've attempted to recreate the situation using a much simpler version of the code I am actually using. Ironically, I am actually further away from getting it to work with this example, but my brain hurts from attempting to get it to work and researching elsewhere on this site. So, I am reaching out to all the clever people out there to see if they can help. The problem I am having (when I get it to work!) is that the Javascript events do not appear to be binding to the DOM elements and just firing on page load. When I then fire the event again (by moving the mouse) I get a Javascript error caused by the D3 library:

"Uncaught TypeError: n.apply is not a function"

If anyone can look past the rushed effort at recreating a module pattern and get this example to work - hopefully I will be able to incorporate the same into my actual code (the only real difference between the example and real code is that there are a bunch of "extender" modules rather than everything being in one).

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
    <div id="box1"></div>
    <div id="box2"></div>

    <script>

        function mouseOver(i) {
            return this.targets[i].select("#" + this.targets[i].container).select("circle").attr("fill", "blue");
        }

        function mouseOut(i) {
            return this.targets[i].select("#" + this.targets[i].container).select("circle").attr("fill", "green");
        }

        var example = (function () {

            return {

                addRect: function addRect(container) {
                    this.box = d3.select("#" + container).append("svg");

                    this.rect = this.box.append("rect")
                        .attr({
                            "fill": "red",
                            "width": 50,
                            "height": 50,
                        });

                },

                addCircle: function addControl(targets) {
                    this.targets = targets

                    for (target in this.targets) {
                        tt = this.targets[target]

                        tt.circle = tt.box.append("circle")
                            .attr({
                                "fill": "green",
                                "cx": -10,
                                "cy":-10,
                                "r": 10,
                                "transform": "translate(30,30)"
                            })
                    };
                },

                addControl: function addControl(targets) {
                    this.targets = targets

                    for (target in this.targets) {
                        tt = this.targets[target]

                        tt.control = tt.box.append("rect")
                            .attr({
                                "fill": "none",
                                "width": 50,
                                "height": 50,
                            })
                            .on("mouseover", mouseOver(target))
                            .on("mouseout", mouseOut(target))
                    };  

                },

            };
        })();


        var first = new example.addRect("box1");
        var second = new example.addRect("box2");

        var circs = new example.addCircle([first, second]);
        var controls = new example.addControl([first, second]);


    </script>
</body>
</html>

Solution

  • Problem1: The event gets fired on page load

    Reason:

    You are doing

    .on("mouseover", mouseOver(target)) //this is wrong the second parameter should have been a function.
    

    something like this

    .on("mouseover", function(){mouseOver(target)})
    

    Problem 2:

    tt.control = tt.box.append("rect")
                                .attr({
                                    "fill": "none",//this should not be none else mouse over will not work as you expect
                                    "width": 50,
                                    "height": 50,
                                })
                                .on("mouseover", mouseOver(target))
                                .on("mouseout", mouseOut(target))
    

    It should be done with opacity 0 so that its not visble

    for (target in this.targets) {
                tt = this.targets[target]
                tt.control = tt.box.append("rect")
                  .attr({
                    "fill": "blue",
                    "width": 50,
                    "height": 50,
                    "opacity": 0
                  })
    

    Problem 3:

    You can set the parameter to the DOM rather than passing it in the function. Something like this

      for (target in this.targets) {
        tt = this.targets[target]
        tt.control = tt.box.append("rect")
          .attr({
            "fill": "blue",
            "width": 50,
            "height": 50,
            "opacity": 0
          })
    
          .on("mouseover", function() {
            mouseOver(tt)
          })
          .on("mouseout", function() {
            mouseOut(tt)
          });
          tt.control.node().dataset = tt;//binding the data to the dom
      };
    

    And in the mouse event get the bound object like this:

    function mouseOver(target) {
          //gettting the data from the dom
          event.target.dataset.circle.attr("fill", "blue");
        }
    
        function mouseOut(target) {
          event.target.dataset.circle.attr("fill", "green");
        }
    

    Working code here.