5

I have a function that searches through a tree of folders and finds the selected folder's parent folder.

Here is the function.

        getParentFolder: function (searchroot, childFolder) {
            searchroot.subfolders.forEach(function (folder) {
                if (folder.key == childFolder.key) {
                    return searchroot;
                }
                else {
                    if (folder.subfolders) {
                        return this.getParentFolder(folder, childFolder);
                    }
                }
            });
        }

When I call this with this.getParentFolder(rootFolder, childFolder);

It simply just gives me: Uncaught TypeError: this.getParentFolder is not a function Why is this? In the same file I call other functions they work perfectly fine. This is the only function that I fail to be able to call. Is it because of the recursion?

Scath
  • 3,777
  • 10
  • 29
  • 40
nanobots
  • 437
  • 9
  • 23

2 Answers2

5

You have to keep this in a variable as you change your context inside the forEach method.

getParentFolder: function(searchroot, childFolder) {
  var self = this;
  searchroot.subfolders.forEach(function(folder) {
    if (folder.key == childFolder.key) {
      return searchroot;
    } else {
      if (folder.subfolders) {
        return self.getParentFolder(folder, childFolder);
      }
    }
  });
}

Also, return statement won't work the way you want. I recommend you to enumerate the array using a for loop :

getParentFolder: function(searchroot, childFolder) {
  for (var i = 0; i < searchroot.subfolders.length; i++) {
    var folder = searchroot.subfolders[i];
    if (folder.key == childFolder.key) {
      return searchroot;
    } else {
      if (folder.subfolders) {
        return self.getParentFolder(folder, childFolder);
      }
    }
  }
}
Serge K.
  • 5,303
  • 1
  • 20
  • 27
  • so does this mean each time I call this in any loop because the nature of javascript, I have to reasign this to self every single time? – nanobots Aug 16 '17 at 15:13
  • 1
    Absolutely, if you want to keep track of you main object. Alternatively, you can use [`bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) – Serge K. Aug 16 '17 at 15:14
  • 1
    @nanobots, you can pass the outer `this` as an argument to the `forEach` which avoids having to use `self`. – Sumner Evans Aug 16 '17 at 15:16
  • @SumnerEvans forgot this one :) – Serge K. Aug 16 '17 at 15:16
  • 2
    I see I see because the temp function created by the foreach(function()) changes the context of this that is why I am having this problem is that correct? – nanobots Aug 16 '17 at 15:17
  • 1
    @nanobots, exactly. – Sumner Evans Aug 16 '17 at 15:18
  • @NathanP., +1 for note on return statement not being correct. – Sumner Evans Aug 16 '17 at 15:18
  • I see if I use for loop instead of foreach this would be avoided as this never changes. However when I apply the recursion without the foreach and with just simple for loop, do I still have to do return self.getParentFolder, or this.getParentFolder? I mean this doesn't change , but would the this changes inside the call of recursion? – nanobots Aug 16 '17 at 15:22
  • If you use the for loop solution, you can avoid using self since you stay inside your object. – Serge K. Aug 16 '17 at 17:01
3

The problem is that your this is different inside of function you passed into the forEach. You need to bind the outer this to the inner function:

getParentFolder: function(searchroot, childFolder) {
  searchroot.subfolders.forEach(function(folder) {
    if (folder.key == childFolder.key) {
      return searchroot;
    } else {
      if (folder.subfolders) {
        return this.getParentFolder(folder, childFolder);
      }
   }
  }, this); // pass in outer this as context for inner function
}

From Array.prototype.forEach() on MDN:

Syntax:

arr.forEach(function callback(currentValue, index, array) {
   //your iterator
}[, thisArg]);

Alternative Solution using ES6:

As mishu mentioned in the comments, the new ES6 arrow syntax also solves this problem. Your code in ES6 would look something like this:

getParentFolder: function(searchroot, childFolder) {
  searchroot.subfolders.forEach((folder) => {
    if (folder.key == childFolder.key) {
      return searchroot;
    } else {
      if (folder.subfolders) {
        return this.getParentFolder(folder, childFolder);
      }
    }
  });
}

Arrow functions is ES6 do not bind this (see MDN) so the outer this can be accessed from within the arrow function.

Note that not all browsers support arrow functions yet (see Browser compatibility on MDN). To support older browsers, you can transpile ES6 to ES5 using Babel.

Community
  • 1
  • 1
Sumner Evans
  • 8,951
  • 5
  • 30
  • 47
  • 2
    wouldn't the new ES6 arrow syntax also solve this issue, as there's no binding on `this`? https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions#No_binding_of_this – mishu Aug 17 '17 at 10:07
  • 1
    @mishu, yes, updated my answer to include that. Good suggestion! – Sumner Evans Aug 17 '17 at 15:08