Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Results 1 to 8 of 8
  1. #1
    New to the CF scene
    Join Date
    Jun 2011
    Posts
    5
    Thanks
    1
    Thanked 0 Times in 0 Posts

    Question OOP - maintain prototype instances (this) beyond Ajax callbacks and onclick events

    I'm pretty new to Javascript and just started with some OOP concepts, so this might be an obvious one and I apologize if the question is much longer than it should be...

    Basically I am trying to create a reusable class object with several internal functions, that is completely abstracted, meaning the class object itself should never have to reference the global declarations or window object. This class should be aware of itself at any point in the execution of it's own functions, and that is what I'm having trouble with at the moment.

    What I'm trying to do is create reusable javascript "Apps" for my HTML web application, where multiple instances can be instantiated and used independently from one another.

    My main problem is "this" loses context in the Ajax callback and onclick handlers. I'm not sure how to persist the context of "this", or at least keep a reference to prototype instance itself.

    Let's set up a simple class called "App" to demonstrate what I mean...

    Code:
    var myApp = new App("MyFirstApp");
    
    function App(argName) {
        this.name = argName;
        this.GetDirectory("C:\Program Files");
    }
    
    App.prototype.GetDirectory = function(argPath) {
        // "this" equals the App object instance (good!), meaning this.name should equal "MyFirstApp"
        Ajax.GetDirectory(argPath, this.GetDirectoryCallback, this.GetDirectoryTimeout, this.GetDirectoryError);
    }
    
    App.prototype.GetDirectoryCallback = function(argFilePaths) {
        // "this" equals the "window" object (bad!), meaning this.name should equal a null reference
        // argFilePaths contains a string of file paths (delimited using ;) returned by the server
        var mFilePaths = split(argFilePaths, ";");
        // for each file path, add a div to the document body containing that file path, with an onclick handler
        for (var i in mFilePaths) {
            var mFilePath = mFilePaths[i];
            var mFilePathDiv = document.createElement("div");
            mFilePathDiv.innerHTML = mFilePath;
            mFilePathDiv.setAttribute("onclick", "javascript:this.FilePathDivClickHandler();return false;");
            document.getElementById("body").appendChild(mFilePathDiv);
        }
    }
    
    App.prototype.FilePathDivClickHandler = function() {
        // I need a reference to the App object instance here, but I'm not sure where to grab it...
        // what I want to do is call GetDirectory again, referencing the div's innerHTML which contains another file path, like this:
        this.GetDirectory(event.target.innerHTML);
    }
    The onclick handler using "this" is obviously not going to work because "this" is a reference to the "window" object which does not have a function called FilePathDivClickHandler(). So what I can do is nest the GetDirectoryCallback() function inside the GetDirectory() function so that GetDirectoryCallback() can reference the variables in the outer function (GetDirectory).

    Code:
    App.prototype.GetDirectory = function(argPath) {
        // "this" equals the App object instance (good!), meaning this.name should equal "MyFirstApp"
        // "this" will still lose context inside the callback, so we can set up a variable called "instance" that the callback can reference
        var instance = this;
        Ajax.GetDirectory(argPath, GetDirectoryCallback, GetDirectoryTimeout, GetDirectoryError);
        function GetDirectoryCallback(argFilePaths) {
            // "this" equals the window object (bad!), meaning this.name should equal a null reference
            // "instance" equals the App object instance (good!), meaning instance.name should equal "MyFirstApp"
            // argFilePaths contains a string of file paths (delimited using ;) returned by the server
            var mFilePaths = split(argFilePaths, ";");
            // for each file path, add a div to the document body containing that file path, with an onclick handler
            for (var i in mFilePaths) {
                var mFilePath = mFilePaths[i];
                var mFilePathDiv = document.createElement("div");
                mFilePathDiv.innerHTML = mFilePath;
                mFilePathDiv.setAttribute("onclick", "javascript:instance.FilePathDivClickHandler();return false;");
                document.getElementById("body").appendChild(mFilePathDiv);
            }
        }
    }
    Even though we have persisted the App object instance through the use of the "instance" variable in the outer function, the onclick event handler still does not work; when it fires, "instance" is an unknown object.

    Would placing the FilePathDivClickHandler() function inside the GetDirectory() function, much like what was done with the callback, allow me to use "instance" correctly for the onclick handler?
    Last edited by hectavex; 06-16-2011 at 05:43 PM.

  • #2
    Senior Coder rnd me's Avatar
    Join Date
    Jun 2007
    Location
    Urbana
    Posts
    4,298
    Thanks
    10
    Thanked 584 Times in 565 Posts
    Code:
    function App(argName) {
       this.that=this;
        this.name = argName;
        this.GetDirectory("C:\Program Files");
    }
    now use that instead of this to refer to on-boject properties and methods.

    as for your event handlers:

    and add that as a local in your GetDirectoryCallback function:
    Code:
       var that=this;
       for (var i in mFilePaths) {
    convert to functions instead of strings:
    Code:
    mFilePathDiv.onclick=  function(){ that.FilePathDivClickHandler();return false;};
    my site (updated 13/9/26)
    BROWSER STATS [% share] (2014/5/28) IE7:0.1, IE8:5.3, IE11:8.4, IE9:3.2, IE10:3.2, FF:18.2, CH:46, SF:7.9, NON-MOUSE:32%

  • Users who have thanked rnd me for this post:

    hectavex (06-16-2011)

  • #3
    New to the CF scene
    Join Date
    Jun 2011
    Posts
    5
    Thanks
    1
    Thanked 0 Times in 0 Posts

    Cool

    That was such a quick reply...you are awesome! I'm going to give this a shot and post back my results.

  • #4
    New to the CF scene
    Join Date
    Jun 2011
    Posts
    5
    Thanks
    1
    Thanked 0 Times in 0 Posts
    I tried converting to a function when setting the onclick property, and this is what the div element's onclick now looks like in the HTML source:

    Code:
    onclick="function(){alert('asd');}"
    But when the event gets fired in Chrome (also tested FireFox 4), I get an error:
    Uncaught SyntaxError: Unexpected token (

    If I remove the "function()" part it works fine:

    Code:
    onclick="alert('asd');"
    What am I doing wrong?
    Last edited by hectavex; 06-16-2011 at 10:42 PM.

  • #5
    Senior Coder
    Join Date
    Apr 2011
    Location
    London, England
    Posts
    2,120
    Thanks
    15
    Thanked 354 Times in 353 Posts
    Code:
    onclick=function (){ alert("asd"); };
    you don't need the outer quotes when using an anonymous function.
    "I'm here to save your life. But if I'm going to do that, I'll need total uninanonynymity." Me Myself & Irene.
    Validate your HTML and CSS

  • #6
    New to the CF scene
    Join Date
    Jun 2011
    Posts
    5
    Thanks
    1
    Thanked 0 Times in 0 Posts
    That's how I assign it programmatically, but the HTML source contains outer quotes. When I try to remove the outer quotes using Chrome's inspector, it just adds the outer quotes back in. Not sure why...

    I should mention that I'm using:

    Code:
    var new_onclick = function() { alert("test"); };
    .setAttribute("onclick", new_onclick);
    Because .onclick = new_onclick will not work.
    Last edited by hectavex; 06-16-2011 at 10:05 PM.

  • #7
    Senior Coder rnd me's Avatar
    Join Date
    Jun 2007
    Location
    Urbana
    Posts
    4,298
    Thanks
    10
    Thanked 584 Times in 565 Posts
    i don't know what your're doing wrong, but you can't use setAttrib to create events if you want to use closure to capture the App object...

    the syntax in my third code sample is what you need
    i use it all the time and it works, so check your spelling and if you get stuck, post the whole html page error and all and i'll fix it.
    my site (updated 13/9/26)
    BROWSER STATS [% share] (2014/5/28) IE7:0.1, IE8:5.3, IE11:8.4, IE9:3.2, IE10:3.2, FF:18.2, CH:46, SF:7.9, NON-MOUSE:32%

  • #8
    New to the CF scene
    Join Date
    Jun 2011
    Posts
    5
    Thanks
    1
    Thanked 0 Times in 0 Posts

    Thumbs up

    Looks like I found a solution. I used the closure method found here.

    Example:

    Code:
    function associateObjWithEvent(obj, methodName) {
        // here "this" equals the html div element, but that's ok because we passed the App object instance to this function via obj
        alert("associateObjWithEvent fired...");
        return (function(e) {
            e = e || window.event;
            return obj[methodName](event, this);
        });
    }
    I also had to use:

    Code:
    mDiv.addEventListener('click', associateObjWithEvent(that, "FilePathDivClickHandler"), false);
    Then I modified the FilePathDivClickHandler() function:

    Code:
    App.prototype.FilePathDivClickHandler = function() {
        alert('FilePathDivClickHandler fired...');
        // oh my goodness... "this" is equal to the App object instance and "event.target" is equal to the element that was clicked... perfect!
        this.GetDirectory(event.target.innerHTML);
    }
    The basic mDiv.onclick=value wasn't working, but the event listener does!

    I'm hoping someone can explain what the difference is and why some of those methods I tried do not work...
    Last edited by hectavex; 06-17-2011 at 05:06 AM.


  •  

    Tags for this Thread

    Posting Permissions

    • You may not post new threads
    • You may not post replies
    • You may not post attachments
    • You may not edit your posts
    •