Functions and Event handlers

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 Likes

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!

4 Likes

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

1 Like

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

2 Likes

So is this considered technically sound? I’ve had students do similar things:

> //calling the color changing function
> colorChange("#EFACAC",18,"#000000");
> 
> //the function of changing the color of the dropdwon input
> function colorChange(backColor,fontSize,fontColor){
>   //when the mouse up in the position dropdown, its background color would change to red, indicated the change of the condition
>   //when the mouse up in the position dropdown, its font size would get more bigger, indicated the change of the condition
>   //when the mouse up in the position dropdown, its font color would change more black, indicated the change of the condition
>   onEvent("positionDropDown","mouseup",function(){
>     setProperty("positionDropDown","background-color",backColor);
>     setProperty("positionDropDown","font-size",fontSize);
>     setProperty("positionDropDown","text-color",fontColor);
>   });
>   //when the mouse up in the ages dropdown, its background color would change to red, indicated the change of the condition
>   //when the mouse up in the ages dropdown, its font size would get more bigger, indicated the change of the condition
>   //when the mouse up in the ages dropdown, its font color would change more black, indicated the change of the condition
>   onEvent("timePeriodsDropDown","mouseup",function(){
>     setProperty("timePeriodsDropDown","background-color",backColor);
>     setProperty("timePeriodsDropDown","font-size",fontSize);
>     setProperty("timePeriodsDropDown","text-color",fontColor);
>   });
> }
> ```

Can anyone tell me if this is a sound way of coding? I’ve seen several students take down the amount of code they’ve needed by doing this.

Thanks in advance!

The important difference in what @mhwatts is doing is the creation of buttons in a loop. His program creates a button and calls onEvent() one after another. Each button gets one and only one event handler.

What is being done there is to create screen elements one to one for all of the data and scroll the buttons over the screen. A better way to do that is to create a fixed number of buttons and dynamically assign data to the buttons. Instead of scrolling buttons, scroll the data across the buttons. With a fixed number of buttons you don’t need the loop to create them. Unless you really want to.

If all of your elements are created in design mode then all of your elements can have event handlers added at the global level.

Your example works. It isn’t too convoluted. Unless you are going to call colorChange() more than once.

Personally, I would not add that code at all. I would instead go into Design mode and make the top choice of the drop down be “Choose…”. I can see if a choice for all the drop downs was made without turning them all red. More importantly I can test if a choice was made from my program and pop up an appropriate error message.

If I had to I would write this instead

// indicate a selection was made by changing background color, font size and text color
onEvent("positionDropDown","mouseup", mouseupEventCallback);
onEvent("timePeriodsDropDown","mouseup",mouseupEventCallback);

function mouseupEventCallback (event) {
  setProperty(event.targetId,"background-color","#EFACAC");
  setProperty(event.targetId,"font-size",18);
  setProperty(event.targetId,"text-color","#000000");
}

Your example uses a closure which is an advanced topic not covered in AP CSP. It works, but do students know why without explaining the advanced topic?

Here is a spread sheet program. What I have done instead is create a grid of buttons that remain stationary. I scroll the content across the stationary buttons. There is then no need to create one button per cell.

It is also an example of calling onEvent from inside a function. I have constants set to the desired number of rows and columns to display on my spread sheet. I then calculate the size of each cell and then create each cell as a button adding an event handler for each.

I could have done this in Design mode, but then I couldn’t change the size. Letting the program itself do the size calculations makes that easy. I can then write the following code which creates and initializes a grid of visible buttons to the size I want.

initialize(4,10); // 4 across 10 down 

initialize is a function starting at line 324 that takes those two arguments and creates the spread sheet with that many cells showing. It eventually calls a function for each cell I create:

function onCellClickEvent (id) { onEvent(id,"click",cellClicked); }

Note that this is made to be simple by using a single function for all the callbacks. Starting on line 492.

function cellClicked (event) {
  var buttonXY = event.targetId.cellIdToXY();
  moveCursorToButton(buttonXY);
  currentCell = buttonXY.toCellXY();
  showCell(currentCell);
}

Unit tests start on line 714. I recommend all programs have them. I have also added integration tests starting at line 803. Note that what I have done here is created a program that interacts with the callback functions as if a user were interacting with the program. I can watch it execute and notice any errors that occur.