Search code examples
javascriptd3.jsconstructores6-class

How to reference constructor this in another function


How can I convert the following portion to be converted to one written with JS Class.

svg authored with d3 without JS Class

const svgns = 'http://www.w3.org/2000/svg';
const width = 1280;
const height = 600;

const main = d3.select('div')
    .style('position', 'relative')
    .append('svg')
    .attr('xmlns', svgns)
    .attr('viewBox', `0 0 ${width} ${height}`)
    .attr('id', 'svg');

//listener
const listener = main
    .append('rect')
    .attr('class', 'vBoxRect')
    .attr('width', `${width}`)
    .attr('height', `${height}`)
    .attr('fill', 'none')
    .attr('stroke', 'red')
    .style("pointer-events", "all");

const focusText = main
    .append('g')
    .attr('class', 'someTxt')
    .append('text')
    .attr("opacity", '0')
    .attr('x', '100')
    .attr('y', '100')
    .html('I am a test text');


listener
    .on('mouseover', function() {
        focusText.attr("opacity", 1);
    })
    .on('mouseout', function() {
        focusText.attr("opacity", 0);
    })
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
    <div id="viz"></div>
</body>
<script src="min.js">
</script>

</html>

svg authored with d3 + JS Class and Constructor

const svgns = 'http://www.w3.org/2000/svg';
const width = 1280;
const height = 600;

class Base {
    constructor(svgns, width, height) {
        this.base = d3.select('div')
            .style('position', 'relative')
            .append('svg')
            .attr('xmlns', svgns)
            .attr('viewBox', `0 0 ${width} ${height}`)
            .attr('id', 'svg');

        //listener
        this.listener = this.base
            .append('rect')
            .attr('class', 'vBoxRect')
            .attr('width', `${width}`)
            .attr('height', `${height}`)
            .attr('fill', 'none')
            .attr('stroke', 'red')
            .style("pointer-events", "all");

        this.focusText = this.base
            .append('g')
            .attr('class', 'someTxt')
            .append('text')
            .attr("opacity", '0')
            .attr('x', '100')
            .attr('y', '100')
            .html('I am a test text');


        this.listener
            .on('mouseover', function() {
                this.focusText.attr("opacity", 1);
            })
            .on('mouseout', function() {
                this.focusText.attr("opacity", 0);
            })
    }

};
const svg = new Base(svgns, width, height);
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
    <div id="viz"></div>
</body>
<script src="min.js">
</script>

</html>

The code is currently failing here

//without class 
listener
    .on('mouseover', function() {
        focusText.attr("opacity", 1);
    })
    .on('mouseout', function() {
        focusText.attr("opacity", 0);
    })

//with Class 
this.listener
    .on('mouseover', function() {
        this.focusText.attr("opacity", 1);
    })
    .on('mouseout', function() {
        this.focusText.attr("opacity", 0);
    })

Solution

  • The issue is that the event system sets its own value of this when it calls your listener callback. In DOM events, the value of this is typically the DOM element that triggered the event. There are a couple of ways to override that value of this and specify your own.

    To use the lexical value of this, then use an arrow function instead of a regular function:

    this.listener
        .on('mouseover', () => {
            this.focusText.attr("opacity", 1);
        })
        .on('mouseout', () => {
            this.focusText.attr("opacity", 0);
        })
    

    Arrow functions ignore the value of this that comes from how the function is called and they use the lexical value of this which is the value of this in the execution environment when the function is defined - which is what you want in your context.


    Or, you can also use .bind() (which creates a little stub functions that re-establishes the value of this you want):

    this.listener
        .on('mouseover', function() {
            this.focusText.attr("opacity", 1);
        }.bind(this))
        .on('mouseout', function() {
            this.focusText.attr("opacity", 0);
        }.bind(this))