Difficulty storing object property in Javascript

Asked

Viewed 359 times

5

I’m having a hard time (which is probably pretty silly) storing a value in a Javascript object property. I’ve done something similar recently using this.propriedade = valor within the "constructor" function and had no problems reusing the value in a function defined as `Object.prototype.function_name".

But in this example (available on Jsfiddle) a property (this.m_oClickCallback) is not visible in mouse click processing function (MyButton.prototype.handleClick), generating the following exception when the button is clicked:

Uncaught Typeerror: Object [Object global] has no method 'm_oClickCallback'

I imagine that probably the mistake is due to something very silly, but I’m not able to see what it is.

Here is the html of the example I prepared:

<body onload="init();">
    <canvas id="myCanvas" width="800" height="600">
        O seu navegador não suporta HTML5. (<i>Your browser does not support HTML5.</i>)
    </canvas>
</body>

And the Javascript code:

(function() {

    // Objeto de botão customizado
    var MyButton = function(sLabel, sColor, oClickCallback) {
        // Chama o initialize do objeto protótipo (createjs.Container)
        this.initialize();

        // Armazena a referência para o callback do evento de click
        this.m_oClickCallback = oClickCallback;

        // Cria o conteúdo do botão
        var oText = new createjs.Text(sLabel, "40px Arial", "#ffffff");
        oText.textBaseline = "top";
        oText.textAlign = "center";

        var iWidth = oText.getMeasuredWidth() + 30;
        var iHeight = oText.getMeasuredHeight() + 20;

        var oBackground = new createjs.Shape();
        oBackground.name = "Background";
        oBackground.graphics.beginFill(sColor).drawRoundRect(0, 0, iWidth, iHeight, 10);

        oText.x = iWidth / 2;
        oText.y = 10;

        this.addChild(oBackground, oText);

        // Captura o evento de click        
        this.addEventListener("click", this.handleClick);
    }  

    // Define o protótipo
    MyButton.prototype = new createjs.Container();

    // Função de tratamento do click no botão. Invoca o callback armazenado.
    MyButton.prototype.handleClick = function(oEvent) {
        this.m_oClickCallback(oEvent.target);
    } 

    // Atribui o objeto ao escopo global de "window"
    window.MyButton = MyButton;
}());

// Função de inicialização. Cria o botão no onload do corpo da página.
function init() {
    g_oStage = new createjs.Stage("myCanvas");
    var oButton = new MyButton("Olá mundo!", "red", function() { alert("Funciona!"); });
    g_oStage.addChild(oButton);
    g_oStage.update();
}

EDIT: the idea is to store the property so that it is differentiated between each new instance of the object (as if it were a private or public attribute of a class in other languages). Solutions in which the property is shared (as if it were static) are not the interest of the question.

EDIT2:

I found out that in createjs. Container there is a property called this.mouseChildren that when disabled causes the object to target sent on payload of the mouse events be directly the object MyButton rather than its components (the text or the Shape background color). Using this property and the object target I can solve the problem as follows (see this new example Jsfiddle, now with two instances of button):

//. . .

// Faz o target dos eventos ser diretamente o objeto MyButton, ao invés do texto
// ou do background nele inclusos
this.mouseChildren = false;

//. . .

// Função de tratamento do click no botão. Invoca o callback armazenado.
MyButton.prototype.handleClick = function(oEvent) {
    oEvent.target.clickCallback(oEvent.target);
}

Logging into the this on the console, I realized that if it is in fact the object window. That’s why the code didn’t work. I did not add this conclusion as an answer because I still have to wonder why the mouse click handling function is not invoked in the scope of the button, but rather in the browser window. Does anyone know?

4 answers

6


Not always this will refer to the object you are creating: when you use this.addEventListener("click", this.handleClick); the value of this is no longer the object and becomes the global scope, usually window.

This problem can be solved in several ways. I won’t go into much detail because this tutorial (in English) in detail aborts this subject, but based on it you would solve your problem simply by doing this:

/* remova */ this.addEventListener("click", this.handleClick);
/* insira */ this.addEventListener("click", this.handleClick.bind(this));
  • Okay, good answer. Thanks for the reference link. :)

3

From what I saw in your code, at no time do you declare the m_oClickCallBack within the scope of MyButton. It is necessary within the MyButtonadd the line:

var m_oClickCallBack;

For more information, see that answer.

  • Yes, it is true, but I did so because I understood that using the this.m_oClickCallback i would be creating a "public property" (see this link: http://phrogz.net/JS/classes/OPinJS.html). In any way, using var m_oClickCallBack = oClickCallBack I get the bug Uncaught ReferenceError: m_oClickCallback is not defined.

1

Luiz Vieira the problem is that Ecmascript 5.1 (if I’m not mistaken) puts the variable this as global scope, the most indicated is you replace the line

this.m_oClickCallback = oClickCallback;

For

MyButton.m_oClickCallback = oClickCallback;

And when it’s time to call m_oClickCallback you can do so

this.MyButton.m_oClickCallback(oEvent.target);
  • Um, it works. But what is the "explanation" in this case? : ) In addition, thus making the variable m_oClickCallback will not share the value between all instances of MyButton?

  • 1

    Actually when you put a Mybutton.m_oClickCallback the m_oClickCallback becomes a property of Mybutton

  • As I imagined, your suggestion is of a property defined in the base object (something like the 'class', I honestly don’t know how to refer to it in Javascript) and not in its instance. So much so that in this example you note that although the two buttons function they invoke the same callback (and that is not the intention): http://jsfiddle.net/luigivieira/K6u2b/5/

1

Look, I managed to make it work see this example on Jsfiddle

But I had to change your code, you may not accept my solution, however, it is functional, what I did was:

In this part of creation:

    // Armazena a referência para o callback do evento de click
    this.m_oClickCallback = oClickCallback;

I modified it to:

    // Armazena a referência para o callback do evento de click
    m_oClickCallback = oClickCallback;

And where you assign the event to the button:

// Função de tratamento do click no botão. Invoca o callback armazenado.
MyButton.prototype.handleClick = function(oEvent) {
      this.m_oClickCallback(oEvent.target);
} 

I modified it to:

// Função de tratamento do click no botão. Invoca o callback armazenado.
MyButton.prototype.handleClick = function(oEvent) {
  m_oClickCallback(oEvent.target);
} 

I don’t think that’s the best solution, but it works.

I hope I’ve helped.

  • 1

    I think in this case the variable is being declared globally, and so it works. But if I have two objects MyButton using the same global variable this will be problematic, it is not?

  • I’m going to test it with two points, and I’m telling you, but I think it would really be global.

  • 1

    Although global are sometimes necessary, it is possible to avoid them in this case. Create a global one in this way, without first declaring it with var in the global scope, it is not a best practice, because if it causes a bug (like the one @Luizvieira mentioned), it can become difficult to identify. Tip: try programming in Javascript’s "Strict mode", which throws an exception if you do such a thing. It is a good way to develop language learning without falling into some traps that it, for historical reasons, has.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.