Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use controller-scoped version of this to refer to the current controller in JS?

I ran into the following problem. My JavaScript structure is like the following.

I created an object that contains all the controllers. Those controllers have their own responsibilities. Below code belongs to the main.js file that is called first:

main.js

var App = {};

App.init = function() {

    console.log('init');

    App.uiController.init();
    App.heroController.init();
    
}

Within the function init() I call the controllers' initializers.

A controller object looks like the following:

uiController.js

App.uiController = {
    
    root: 0,
    init: function() {

        // Development
        console.log('init uiController');

        root = this;

        // Call functions
        root.doSomething();

    },

    doSomething: function() {

    }
}

After all the scripts are loaded, I call the last script init.js:

init.js

App.init();

My HTML markup looks like the following :

<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
</head>

<body>

    <!-- // Start HTML \\ -->
    <header id="header">
    </header><!-- End header#header -->

    <main class="main">     
    </main><!-- End main.main -->

    <footer id="footer">
    </footer><!-- End footer#footer -->



    <!-- JavaScript -->
    <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script>
    <script src="scripts/main.js" type="text/javascript"></script>
    
    <script src="scripts/constructors/uiController.js" type="text/javascript"></script>
    <script src="scripts/constructors/heroController.js" type="text/javascript"></script>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenMax.min.js"></script>
    <script src="scripts/init.js" type="text/javascript"></script>

</body>
</html>

Within the functions I use root to select this - that file where I currently been. Normally I create the variable root in every function (scoped) and give it the value this. But I thought this could be better, to create in every controller object one empty root (globally), so I've to only create and add a value once to root, like the following:

App.uiController = {

    root: 0,
    init: function() {

        root = this;

        // Call functions
        root.doSomething();

    },

    doSomething: function() {

    }
}

When I console.log root within the init() function, it will return an object with all the function that lives within the current controller where I am now, in above example App.uiController. So I thought this was working great and I've to create and declare root only once for each controllers. So I did the same for heroController as I did for uiController and log the variable root to the console. I receive to different objects with the right functions in it:

enter image description here

But, here comes the problem. In main.js I called heroController.init() after uiController.init() and When uiController is triggered by an event within the controller, I receive errors in my console that some functions within the uiController not working as expect.

When I remove the global root variable inside each controller and declare it in the init controllers like var root = this; the code will work again.

Why does the above setup not work as I expect?

UPDATE

Based on the answer from Kingshuk basak, I tried some new things. Here are my thoughts:

The answer code is not working for me. It will give me the error: Uncaught ReferenceError: root is not defined. When I use the code from my question and change the root: 0 of the heroController to 1 and log both to the console: console.log(App.uiController.root, App.heroController.root); I get 0 and 1 in the console. It doesn't matter where I place it in both codes, the values will not be overridden by the other ones. This makes it really strange to me.

Then I tried also comment out heroController in main.js, then console.log root in the uiController.init() function. The value that I get back is 0 and that's true, but after that I set root = this; and console.log directly after it root.root the value will be also 0 and not this as the uiController object. Which makes it even more confusing, why root isn't changing to the object instead of 0. Also when there is just one root in the hole code, after I comment out heroController.init() in the file main.js. Console.log only root will give me the uiController object as expected.

Last, I tried also inside both init() functions this:

root = this;
this.root = root;
console.log('heroController', root);

When I do this, the value of root is for each controller correct, because it will contain the object - this object - with all the corresponding functions in it. But this will leave me with the same problem as mentioned before, that uiController is not working as expect.

As you can see, both roots inside each controller as different functions

like image 252
Caspert Avatar asked Jul 29 '16 10:07

Caspert


2 Answers

Summary

Setting root as a property of your controller in order to save you from having to type var root = this in your controller methods is not useful. You should just do var root = this when needed because the property ultimately is not a net benefit over var root = this.

Explanation

The first answer you got is a partial answer. kingshuk basak is right that what you are doing is setting a root variable in the global space. See, if you do this:

function foo() {
  something =  1;
}

you are setting something in the global space. To make it local to your function you need var:

function foo() {
  var something =  1;
}

The lack of var in the first snippet does not make JavaScript interpret it as this.something, even if the function is part of a prototype.

kingshuk basak suggesting you make it part of your controller by using this. This works, yes but then you have to access it using this because that's how JavaScript works. So the naive way to use it would be:

uiController = {
  root: 0,
  init: function() {
    this.root = this;
    this.root.doSomething(); // You havve to use `this` here too!!
  },

  doSomething: function() {
     this.root.somethingElse();
  }
}

From what your question says, you are just trying to save yourself from having to write var root = this. So root won't ever change. But think about this for a second.

What you are doing is adding a property named root to this, which has the exact same value as this. In order to access root, which has the same value as this, you already need to have this. So there's no good reason for this.root to exist. As explained above, if you do this.root = this, then each access to it must be this.root so you've not appreciably reduced your burden. Not only that, but doing this.root.foo() requires an additional indirection at execution: root has to be fetched from this first.

You could do this:

uiController = {
  root: 0,
  init: function() {
    var root = this.root = this;
    root.doSomething();
  },

  doSomething: function() {
     var root = this.root;
     root.somethingElse();
  }
}

But again, this does not reduce the burden.

You should just do var root = this when you need it.

like image 152
Louis Avatar answered Oct 26 '22 19:10

Louis


The reason is simple. The 'root' variable of the uiController is overWritten by the heroController as both are declared in the global namespace. Try different names as root1 and root2 for uiController and heroController, it will work fine.

Note : Ideally you should not pollute the global namespace to avoid such problems. Use closures if necessary.

This is what you have to do :

uiController = {

    root: 0,
    init: function() {

         this.root = this;

        // Call functions
        root.doSomething();

    },

    doSomething: function() {

    }
}
like image 23
kingshuk basak Avatar answered Oct 26 '22 19:10

kingshuk basak