Functions and Event handlers


#1

Hi All,

I am noticing a lot of students defining functions and event handlers inside other event handlers or functions. It is interesting because they have never seen this modeled before. My understanding is that this is a no-no, but AppLab lets them get away with it. Thus far I have been telling them it is a “ease of reading” sort of thing, but I don’t actually know the reason why they shouldn’t write “nested” event handlers (or function definitions).

I am wondering if others see this issue, if/how you correct it, and any sort of “historical perspective” you can give on this.

Thanks!
KT


#2

Hey @kaitie_o_bryan, I obviously can’t speak to how I handle this in a classroom but I can at least tell you a little bit about why this is “wrong”.

Incorrectly Using a Function Defined in Another Function:

// Example of declaring a function inside another function
function outer(){
  console.log("outer");
  function inner(){
    console.log("inner");
  }
}

outer();  // Will print the word "outer" to the console
inner();  // Will lead to an error. Why?

The example above will lead to an error because the functions have scope, just like variables. If a function is created in global scope (outside any other functions) then it can be used anywhere in your program. This is why outer() works as you’d expect. If a function is created inside of another function it can only be called from within that same function. This is why calling inner() leads to an error. It’s being called from outside the scope in which it was created.

Successfully Using a Function Defined in Another Function:

// Example of declaring a function inside another function and successfully calling both
function outer(){
  console.log("outer");
  function inner(){
    console.log("inner");
  }
  inner();
}

outer();  // Will print the word "outer" and "inner" to the console, on separate lines

This second example shows that you can define a function inside another function and call it, so long as you do so from the same scope. In this case, inner() is both created and called inside of outer() and therefore it runs to print the word "inner" to the console.

Why do this?: Now if at this point you’re scratching your head and wondering “Why in the heck would anyone want to do something like that?” the answer is that there’s reasons but they almost certainly won’t arise in the scope of what we teach in our CSP class. This is a really interesting feature of the JavaScript language that lets you do powerful things, and since App Lab takes the low-floor high-ceiling approach it’s absolutely allowed, but these concepts aren’t part of this course.

onEvent: The situation in the case of onEvent() is a little different. When you run an onEvent() block you are telling the program that from now on it should listen for an event to happen to a specific element and run a piece of code. In this case, scope doesn’t matter. Wherever onEvent() runs, even if it’s inside another function or onEvent() it will create this “listener”. Check out this example below.

// Assume there's a button with id "button1"
onEvent("button1", "click", function() {
  console.log("outer");
  onEvent("button1", "click", function() {
    console.log("inner");
  });
});
// First click logs "outer"
// Second click logs "outer" and "inner", on separate lines
// Third click logs "outer", "inner", "inner", on 3 separate lines

This is a really weird example but wrapping your head around it likely will explain a lot about onEvent(). When you hit "Run" to start your program the outer onEvent() is run which creates a listener. When you click the button, the code inside of it runs. This first logs the word “outer”. Then a second onEvent() block is encountered so it creates a second listener. However, since the click already happened, this onEvent does not run the code inside. The second time you click the button both listeners were already created, so both the “outer” and “inner” event handlers’ code will run. However, part of running the outer event handler is running that onEvent() code again. This creates yet another listener. This is why the third time you click the button it will log “outer”, “inner”, “inner”.

If you’ve followed the explanation above then hopefully it’s starting to become clear why using onEvent() inside another onEvent() is a bad idea. You’re likely to keep accidentally adding more and more listeners in the background of your program that will start doing weird things (like running the same code multiple times). The major misconception here is that the onEvent() is what’s running when you click a button. A better summation is that onEvent() creates a “listener” that runs code when you click a button. By running the same onEvent() multiple times (as in the example above) you can easily end up with more listeners than you intended, leading to the same code running multiple times.

The short answer for both functions and onEvent() is that they should be used only in global scope for essentially every situation in our CSP course. This is something I can imagine us creating more gutter warnings (yellow triangles) for so I’ll ask about it at our next meeting with the engineering team as well.

I hope that helped explain some of the issues here but let me know if you want further clarification!


#3

Thanks! That is helpful! The event handler’s in particular are really good to know about - I can see this coming up for students who have some elaborate project ideas. It will certainly help me answer student questions more articulately.

Thank you!
KT


#4

I agree with the prior comments. I do have an example of a time when I have wanted to define event handlers in functions, and in fact, in loops. The code sample below creates 20 buttons that scroll. Only 11 are visible at any time. I’ve used a similar approach to making a spreadsheet like display in appLab. This is clearly beyond the minimum scope of CSP, but I have students who ask, so it is nice to be able to show them how it can be done.

https://studio.code.org/projects/applab/WVw_r8mdpwB2dRK6hEs9Ng

// make a bunch of buttons that do similar things