Announcement

Collapse
No announcement yet.

Callback function is breaking Javascript?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Callback function is breaking Javascript?

    If this is a bug, then it goes all the way back to the 7.91 release. I've tested these scripts on that, 8.0r2 and 10.5.1721.1140.

    eSignal Development Staff, please take a look at this.

    First, let's start off with something that works. I create a simple object and initialize a variable in that object, flag, to false. When I invoked the sub1() function, the flag property is set to true. Then, when I call sub2(), true for the flag property is correctly printed out because the object retains the value of flag between function calls.

    PHP Code:
    var bInit false;
    function 
    preMain()
    {
      
    setPriceStudy(true);
    }

    function 
    main()
    {
      if (
    bInit == false) {
        var 
    obj = new myObject();
        
    obj.sub1();
        
    obj.sub2();
        
    bInit true;
      }
    }

    function 
    myObject()
    {
      
    this.flag false;
      
    this.sub1 = function() { this.flag true; }
      
    this.sub2 = function() { debugPrintln("in sub2(), this.flag = " this.flag); }

    This next piece of code shows what I think is a bug. What it's supposed to do is draw a "hello" button onto the chart and then every time the user clicks and drags the button with the left mouse button and releases it, the button will be moved to the new location on the chart.

    This code is not working properly because, as you can see from the debugPrintln() statements when you test it on a chart, the bFlag variable within the object is not retaining its value between successive object method calls.

    The only thing I can figure out is that somehow the usage of callbacks is stepping on the bDrag variable in a way to where it isn't updating between function calls as it should.

    One more note about this code. You can see at the top that I'm pointing a global variable to a function internal to the button object so that the drawTextAbsolute() function inside it can call it. Currently, these callbacks can only be bound to global variable names and this hack is a workaround (see detailed posts by me in another thread about this issue).

    PHP Code:
    var bInit false;
    var 
    first true;
    var 
    button = new HelloButton();
    var 
    moveButton button.moveButton;

    function 
    preMain()
    {
      
    setPriceStudy(true);
      
    debugClear();
    }

    function 
    main()
    {
      if (
    bInit == false)
      {
        
    bInit true;
      }
      else if (
    first && isLastBarOnChart())
      {
        
    button.drawButton();
        
    first false;
      }
    }

    function 
    onLButtonUp(barIndexyValue)
    {
      
    button.onLButtonUp(barIndexyValue);
    }

    function 
    HelloButton()
    {
      
    this.bDrag false;

      
    this.drawButton = function()
                        {
                          
    debugPrintln("drawButton called");
                          
    drawTextAbsolute(3close(0), " hello @URL=EFS:moveButton"nullnull,
                             
    Text.RELATIVEBOTTOM Text.VCENTER Text.FRAME"Arial"11"T1");
                        }

      
    this.moveButton = function(nButtonPressed)
                        {
                          
    debugPrintln("moveButton called");
                          
    this.bDrag true;
                          
    debugPrintln("this.bDrag = " this.bDrag);
                        }

      
    this.onLButtonUp = function(barIndexyValue)
                         {
                           
    debugPrintln("onLButtonUp called with this.bDrag = " this.bDrag);
                           if (
    this.bDrag == true)
                           {
                             
    drawTextAbsolute(barIndex-1yValue" hello @URL=EFS:moveButton"nullnull,
                                
    Text.RELATIVEBOTTOM Text.VCENTER Text.FRAME"Arial"11"T1");
                             
    this.bDrag false;
                           }
                         }

    The workaround for the above code so that the button moving functions as expected is to make the bDrag variable global and have the object's methods modify it as the mouse is pressed down and released. I would be delighted if I am wrong and I'm missing something that is dead simple and doesn't require some gawd awful hacking in the global namespace.

    Here is the global flag hack to the button moving code shown below to make it work as it should (from a user's perspective).

    PHP Code:
    var bInit false;
    var 
    first true;
    var 
    button = new HelloButton();
    var 
    moveButton button.moveButton;
    var 
    bDrag false;

    function 
    preMain()
    {
      
    setPriceStudy(true);
      
    debugClear();
    }

    function 
    main()
    {
      if (
    bInit == false)
      {
        
    bInit true;
      }
      else if (
    first && isLastBarOnChart())
      {
        
    button.drawButton();
        
    first false;
      }
    }

    function 
    onLButtonUp(barIndexyValue)
    {
      
    button.onLButtonUp(barIndexyValue);
    }

    function 
    HelloButton()
    {
      
    this.drawButton = function()
                        {
                          
    debugPrintln("drawButton called");
                          
    drawTextAbsolute(3close(0), " hello @URL=EFS:moveButton"nullnull,
                             
    Text.RELATIVEBOTTOM Text.VCENTER Text.FRAME"Arial"11"T1");
                        }

      
    this.moveButton = function(nButtonPressed)
                        {
                          
    debugPrintln("moveButton called");
                          
    bDrag true;
                          
    debugPrintln("bDrag = " bDrag);
                        }

      
    this.onLButtonUp = function(barIndexyValue)
                         {
                           
    debugPrintln("onLButtonUp called with bDrag = " bDrag);
                           if (
    bDrag == true)
                           {
                             
    drawTextAbsolute(barIndex-1yValue" hello @URL=EFS:moveButton"nullnull,
                                
    Text.RELATIVEBOTTOM Text.VCENTER Text.FRAME"Arial"11"T1");
                             
    bDrag false;
                           }
                         }

    Last edited by SteveH; 03-17-2010, 11:00 PM.

  • #2
    Re: Callback function is breaking Javascript?

    SteveH,

    I am trying to help you here. Please do not complain that I am not eSignal Development Staff.

    What you are experiencing is how JavaScript handles the 'this' scope keyword. It enterprets this in the context of the scope in which it is executed. What you are seeing is a textbook example of how scope can get tricky, and it is strictly an issue with JavaScript that is pretty well documented.

    I have several links in my fileshare on JavaScript on scope and closures. Here is one I quickly looked up with Google that discusses how you have to form a closure to preserve the correct scope context.



    If you were to rewrite your constructor using a closure to preserve the correct scope (this), you will solve the issue you describe as a bug.

    Steve

    Originally posted by SteveH
    If this is a bug, then it goes all the way back to the 7.91 release. I've tested these scripts on that, 8.0r2 and 10.5.1721.1140.

    eSignal Development Staff, please take a look at this.


    ...

    Comment


    • #3
      Wow, so the "this" reference within the callback is not the same "this" scoped to the HelloButton object.

      Big thanks.

      Here's the corrected implementation:

      PHP Code:
      var bInit false;
      var 
      first true;
      var 
      button = new HelloButton();
      var 
      moveButton button.moveButton;

      function 
      preMain()
      {
        
      setPriceStudy(true);
        
      debugClear();
      }

      function 
      main()
      {
        if (
      bInit == false)
        {
          
      bInit true;
        }
        else if (
      first && isLastBarOnChart())
        {
          
      button.drawButton();
          
      first false;
        }
      }

      function 
      onLButtonUp(barIndexyValue)
      {
        
      button.onLButtonUp(barIndexyValue);
      }

      function 
      HelloButton()
      {
        var 
      self this;   // create a closure to reference any future properties of HelloButton
        
      this.bDrag false;
        
      this.drawButton = function()
                          {
                            
      debugPrintln("drawButton called with this.bDrag = " this.bDrag);
                            
      drawTextAbsolute(3close(0), " hello @URL=EFS:moveButton"nullnull,
                               
      Text.RELATIVEBOTTOM Text.VCENTER Text.FRAME"Arial"11"T1");
                        }
        
      this.moveButton = function(nButtonPressed)
                          {
                            
      debugPrintln("moveButton called");
                            
      self.bDrag true;    // in a callback, "self" contains the correct HelloButton scope, not "this"
                            
      debugPrintln("this.bDrag = " self.bDrag);
                            }
        
      this.onLButtonUp = function(barIndexyValue)
                           {
                             
      debugPrintln("onLButtonUp called with this.bDrag = " this.bDrag);
                             if (
      this.bDrag == true)
                             { 
                               
      drawTextAbsolute(barIndex-1yValue" hello @URL=EFS:moveButton"nullnull,
                                  
      Text.RELATIVEBOTTOM Text.VCENTER Text.FRAME"Arial"11"T1");
                               
      this.bDrag false;
                             }
                           }

      Comment


      • #4
        Hi SteveH,

        You are welcome of course. Closures can be very powerful.

        I try and limit their use to ensuring the objects' scope is captured as you did. FWIW, I use the JavaScript verifier (JSLint), one of the links at the bottom of my post, to ensure I set them properly.

        Comment


        • #5
          stevehare,

          This is a better implementation of the sample. I have no choice but to create a global variable to point to the HelloButton's moveButton function, but at least I can hide it within the object that's creating it by defining the global variable there and by putting an underscore in front of the global name to help prevent a name clash to any unsuspecting user of the class.

          PHP Code:
          var bInit false;
          var 
          first true;
          var 
          button = new HelloButton();
          function 
          preMain()
          {
            
          setPriceStudy(true);
            
          debugClear();
          }
          function 
          main()
          {
            if (
          bInit == false)
            {
              
          bInit true;
            }
            else if (
          first && isLastBarOnChart())
            {
              
          button.drawButton();
              
          first false;
            }
          }
          function 
          onLButtonUp(barIndexyValue)
          {
            
          button.onLButtonUp(barIndexyValue);
          }


          function 
          HelloButton()
          {
            var 
          self this;   // create a closure to reference any future properties of HelloButton
            
          this.bDrag false;
            
          this.drawButton = function()
                              {
                                
          _moveButton this.moveButton;  // global _moveButton variable is pointing to HelloButton's moveButton function
                                
          drawTextAbsolute(3close(0), " hello @URL=EFS:_moveButton"nullnull,
                                   
          Text.RELATIVEBOTTOM Text.VCENTER Text.FRAME"Arial"11"T1");
                              }

            
          this.moveButton = function(nButtonPressed)
                              {
                                
          self.bDrag true;    // in a callback, "self" contains the correct HelloButton scope, not "this"
                              
          }
            
          this.onLButtonUp = function(barIndexyValue)
                               {
                                 if (
          this.bDrag == true)
                                 {
                                   
          drawTextAbsolute(barIndex-1yValue" hello @URL=EFS:_moveButton"nullnull,
                                      
          Text.RELATIVEBOTTOM Text.VCENTER Text.FRAME"Arial"11"T1");
                                   
          this.bDrag false;
                                 }
                               }

          Comment


          • #6
            Hi SteveH,

            I wrote an alternate implementation without creating an object using the new keyword and the associated constructor. I wanted to show you an alternate implementation .

            I call the function with a reference to the global object. Then I create a closure object and use the closure to retain all the functions and variables that have to stick around. I create the requisite global pointers from within the function, not true encapsulation, more of a pseudo encapsulation. Then return the closure from the function to the button global variable ensures the closure sticks around.

            I also added provisions to create multiple buttons using some techniques I'm sure you are familiar with.

            What I did can also be done by creating objects with a constructor function and using the 'this' keyword and a closure to capture scope.

            Interestingly, with the function residing in the global scope, if I had used the 'this' keyword in my function, it would have referred to the global scope since I did not use the new object constructor technique (i.e. - I didn't really need to pass the global scope, see attached file). If you used something like this in a library outside the global scope, you would have to capture and pass the global scope.

            PHP Code:
            var bInit false;
            var 
            first true;
            debugClear();
            var 
            button helloButton(this); //~ this is the gobal  scope
            //~ var button1 = helloButton(this);var button2 = helloButton(this);  // these can be uncommented  to create multiple seprately scoped buttons

            function preMain() {
              
            setPriceStudy(true);
                
            setStudyTitle("helloButton"+button.rn);
            }

            function 
            main() {
              if (
            bInit == false) {
                
            bInit true;
              }
              else if (
            first && isLastBarOnChart()) {
                
            button.drawButton();  
                    
            //~ button1.drawButton();    button2.drawButton();  // these can be uncommented  to draw the additional buttons
                
            first false;
              }
            }

            function 
            onLButtonUp(barIndexyValue) {
              
            debugPrintln("24: Native onLButtonUp function called from helloButton pointer"+button.rn);
            }

            function 
            helloButton(scopeG){
                var 
            self
                function 
            closure(scope){self={scopeG:scopeG,bDrag:false,onLButtonUpPointer:function(){}};}
                
            closure(); // creates a closure 
                
            self.rn=Math.floor(1+Math.random()*90)+"_"+getInvokerID(); //~ creates unique number for the drawn button object Tag ID
                
            self.name="_moveButton_"+self.rn//~ creates unique name for global pointer
                    
                
            if("onLButtonUp" in scopeG){ //~ checking if there is a onLButtonUp function already defined in the global Scope
                    
            self.onLButtonUpPointer=scopeG.onLButtonUp
                }

              
            self.drawButton = function () {
                    
            drawTextAbsolute(3close(0),  " hello_"+self.rn+" @URL=EFS:"+self.namenullnullText.RELATIVEBOTTOM Text.VCENTER Text.FRAME"Arial"11self.rn);
              };
                
              
            self.moveButton = function (nButtonPressed) {
                
            self.bDrag true
              };
                
              
            self.onLButtonUp = function (barIndexyValue) {
                if (
            self.bDrag === true) {
                  
            drawTextAbsolute(barIndex 1yValue" hello_"+self.rn+" @URL=EFS:"+self.namenullnullText.RELATIVEBOTTOM Text.VCENTER Text.FRAME"Arial"11self.rn);
                  
            self.bDrag false;
                }
                    if(
            self.onLButtonUpPointer){
                        
            self.onLButtonUpPointer(barIndexyValue); //~ pointer to original onLButtonUp function
                    
            }
              };
                
            scopeG.onLButtonUp=self.onLButtonUp//~ pointer to self.onLButtonUp
                
            scopeG[self.name] = self.moveButton//~ pointer to self.moveButton

                
            return self//~ returning closure

            hope you find this interesting,

            Steve


            Originally posted by SteveH
            stevehare,

            This is a better implementation of the sample. I have no choice but to create a global variable to point to the HelloButton's moveButton function, but at least I can hide it within the object that's creating it by defining the global variable there and by putting an underscore in front of the global name to help prevent a name clash to any unsuspecting user of the class.
            Attached Files

            Comment


            • #7
              Steve, thanks for posting such a nice, alternative example!

              Comment


              • #8
                Hi SteveH,

                You are most welcome, working on this with you was a pleasure.

                Steve

                Originally posted by SteveH
                Steve, thanks for posting such a nice, alternative example!

                Comment


                • #9
                  stevehare,

                  This is my take on your example.

                  I did some reading on the web and liked the way the "module pattern" looked. I think it's very easy to read where the closures are more subsumed into the code layout.

                  PHP Code:
                  var bInit false;
                  var 
                  first true;
                  var global = 
                  this

                  var 
                  button1 helloButton("B1");
                  var 
                  button2 helloButton("B2");
                  var 
                  button3 helloButton("B3");

                  function 
                  preMain()
                  {
                    
                  setPriceStudy(true);
                    
                  debugClear();
                  }

                  function 
                  main()
                  {
                    if (
                  bInit == false)
                    {
                      
                  bInit true;
                    }
                    else if (
                  first && isLastBarOnChart())
                    {
                      
                  button1.drawButton(3close(0));
                      
                  button2.drawButton(3close(0) + 1.0);
                      
                  button3.drawButton(3close(0) + 2.0);
                      
                  first false;
                    }
                  }

                  function 
                  onLButtonUp(barIndexyValue)
                  {
                    
                  placeButton(barIndexyValue);
                  }

                  function 
                  placeButton(xy)
                  {
                    
                  debugPrintln("no button was moved");
                  }

                  function 
                  helloButton(id)
                  {
                    return (function() {
                      
                  // private members
                      
                  var bDrag false;
                      var 
                  name "_moveButton_" id;

                      function 
                  moveButton(nButtonPressed)
                      {
                        
                  bDrag true;
                        
                  _globalPlaceButton = global["placeButton"];
                        global[
                  "placeButton"] = placeButton;
                      }

                      function 
                  drawButton(xy)
                      {
                        
                  drawTextAbsolute(xy"button_" id "@URL=EFS:" namenullnullText.RELATIVEBOTTOM Text.VCENTER Text.FRAME"Arial"11id);
                      }

                      function 
                  placeButton(barIndexyValue)
                      {
                        if (
                  bDrag == true)
                        {
                          
                  drawTextAbsolute(barIndex-1yValue"button_" id "@URL=EFS:" namenullnullText.RELATIVEBOTTOM Text.VCENTER Text.FRAME"Arial"11id);
                          global[
                  "placeButton"] = _globalPlaceButton;
                          
                  bDrag false;
                        }
                      }

                      global[
                  name] = moveButton;

                      return { 
                  // public members
                        
                  drawButton : function (xy)
                                     {
                                       
                  drawButton(xy);
                                     },
                        
                  placeButton : function(barIndexyValue)
                                      {
                                        
                  placeButton(barIndexyValue);
                                      }
                      };
                    })();

                  Attached Files

                  Comment

                  Working...
                  X