Programming Concept onEvent

This is a sample that I wrote. I am curious about how and why it works
Sample Partial TicTacToe

I expect the onEvent to be called only when it is written outside a function. In the sample provided, the onEvent is embedded in a function checkIfTaken() that is called. Does this mean the event handler is registered and ready to accept events as long as the program is running once the function is called? I think this is incorrect program code.

var player = "O";
checkIfTaken("b11");
checkIfTaken("b12");
checkIfTaken("b13");
checkIfTaken("b21");
checkIfTaken("b22");
checkIfTaken("b23");
checkIfTaken("b31");
checkIfTaken("b32");
checkIfTaken("b33");

// WHY DOES THIS WORK
function checkIfTaken(choice){
  onEvent(choice, "click", function(event) {
    console.log(choice + " is clicked!");
    if(player == "X")
      setProperty(choice, "image","icon://fa-times");
    else
      setProperty(choice, "image","icon://fa-circle-thin");
    checkWin(player);
  });
}

Hello Maya,

Here are two forum threads that have discussions about onEvents and how they work. Hope this helps.

There is no reason why you can’t do this. The simple answer to your question “Does this mean the event handler is registered and ready to accept events as long as the program is running once the function is called?” is yes.

If the 9 squares in the game are represented by buttons and given the ids b11, b12, etc. then what you have done is added an event handler to each button without duplicating the code 9 times. The event handler is straight forward in that it sets the button to be an x or an o. This is a clever way to avoid all the repeated code, but there is a better way. But first, let me answer your second question “WHY DOES THIS WORK”.

The way it works is called a closure. The function checkIfTaken is a function and as such has its own environment in which choice is declared and assigned a value of “b11”, or “b12”, etc. When the statement onEvent is called it creates an unnamed function in that environment. That function uses the variable choice whose value is the id of one of the 9 buttons. That unnamed function then becomes the callback function for the button with id equal to the value of choice. When clicked that function is called.

The closure part is that since the callback function was defined inside an environment where choice was declared and given a value equal to the button’s id it will continue to have that value. setProperty(choice, "image","icon://fa-times"); will set the image property of the button that is pressed because the callback function uses the environment created for checkIfTaken and in that environment choice is set to the button id that is the button whose callback this function is. This is an advanced Javascript topic not covered by CSP.

This is also not covered in CS A because Java doesn’t have closures. Javascript is based on the language Scheme which is based on the language LISP which does have closures.

So how could you do this without creating closures? Use the button id from the event object. The callback function is given an object as a parameter. That object is called an event and has all kinds of information including the id of the button that was pressed. You just need to understand that onEvent needs a function for the callback and you don’t have to create a fresh function every time. Hence:

var player = "O";
onEvent("b11", "click", squareChosen);
onEvent("b12", "click", squareChosen);
onEvent("b13", "click", squareChosen);
onEvent("b21", "click", squareChosen);
onEvent("b22", "click", squareChosen);
onEvent("b23", "click", squareChosen);
onEvent("b31", "click", squareChosen);
onEvent("b32", "click", squareChosen);
onEvent("b33", "click", squareChosen);

function squareChosen(event){
  var choice = event.currentTargetId;
  console.log(choice + " is clicked!");
  if(player == "X")
    setProperty(choice, "image","icon://fa-times");
  else
    setProperty(choice, "image","icon://fa-circle-thin");
  checkWin(player);
}

will also work without duplicating the callback function code 9 times.

1 Like

@bhatnagars Thank you for the links. Now I have to figure how to discourage my hackers from using this code pattern. To them if it is not causing a warning or error, it works! :crazy_face: :smile:

Thank you for your explanation. Your example was proof enough on the right way to use/write the callback function. I was able to share the correct approach with my students.

I’ve accomplished a similar task using this format that reads when anything on the screen is clicked:

var player = “O”;
onEvent(“screen1”, “click”, function(event){
var choice = event.currentTargetId;
console.log(choice + " is clicked!");
if(choice.substring(0,1) == “b”){
if(player == “X”){
setProperty(choice, “image”,“icon://fa-times”);
} else {
setProperty(choice, “image”,“icon://fa-circle-thin”);
}
checkWin(player);
}
});

Out of curiosity, are there any potential issues from doing it this way, as long as the names are selected to ensure that no other elements on the screen1 begin with the letter b?

I guess that is the key to have the code suggested work.

1 Like

That works if you use var choice = event.srcElementId; because currentTargetId is always going to be “screen1”.

Given that change it does work. It is what we call a kludge.

2 Likes

I learned about closure and kludge from you. :grinning:

1 Like

That’s what I meant to use - srcElementId. I know it’s been working in my case because I’ve used it for several apps and had plenty of students testing them out, but I was just curious if there were any issues setting the code up this way. I’ve only really been involved in programming since I’ve been teaching AP CSP the last couple years, so I was curious on your perspective because you have such an excellent JS background. Do you have any resources you’d recommend for more advanced JS concepts like kludge and closure? I’ve been using w3schools.com a lot and really like that content. Thanks in advance!

Last year, I wanted to learn about data records, and I had a variety pack of 150 rubber ducks I was going to give to my students, so I decided to make a Duck Shop app where they would only be able to login and shop once I enabled their login. I setup the whole thing and it worked really well for the most part, and I learned a ton about App Lab, data records, and JS in general, so it was a really fun project for me. In case you want to check it out, here’s a link to the app and the login when the prompt appears is 9999
https://studio.code.org/projects/applab/M5RfE3d-4y6J7uRpErYGk_5SjomG2paQxvwSvPGrT_o/view

A kludge is not language specific. Kludgy is a property of code that works but we don’t like the way it works. I find that code kludgy because it has extra logic that could cause problems. Like if you forget and create a button with the wrong name.

One of the big things Javascript gives us is a way to interact with the DOM (Document Object Model.) The DOM is created by parsing the HTML code creating a hierarchy of elements (or as us out to pasture programmers call them widgets.) There is a missed opportunity to use that hierarchy to remove unnecessary logic from your code. We can attach an event listener to each button that doesn’t need to test if a button was pressed. You already know when the callback fires why it has fired.

From a purely App Lab point of view, there is an issue that can occur when you code onEvent without an actual element name. It is discussed here Bug - unit 5 lesson 8 - #2 by jdonwells Basically if you use a variable instead of an element name the dropdown for the event type isn’t specific to that element type and you can choose an event that throws a run time error.

Javascript and hence App Lab support 3 programming paradigms. Namely procedural, functional, and object oriented. AP CSP really stresses the procedural portion of Javascript. But Javascript was really created to stress the functional paradigm. If you find yourself teaching AP CS A you will learn object oriented till you scream. But, to learn more about functional programming in App Lab I would read Javascript Allonge. Read JavaScript Allongé (ES5) | Leanpub This is a link to the ES5 version which is the version of App Lab.

If you want me to take a look at that project and tell you the good, bad, and ugly I can do that. It will take a couple days.

1 Like

Thank you so much for your time and for sharing your knowledge! I really appreciate it. I understand what you mean by the difference between testing if a button was pressed vs. the callback firing. In my case, I used it to avoid having to create dozens of event listeners for the user clicking on each duck image, so it reads which one they clicked and uses that to sell a duck (change the inventory and then assign it to themself in the data set so I could determine who purchased each duck).

I understand the bug that you’re referring to with properties not making sense based on the type of element, but I don’t see that as a problem in my case as long as I’m careful with which properties I use, and I never use the blocks when I’m coding.

Thanks so much for the JS ES5 link; I’ll definitely give that a read. You’re welcome to look over the project and offer any feedback, but please don’t feel obligated. I’m sure you’ve got plenty on your plate, as we all do these days. But, if you do have any advice or feedback, I’m always open to it. Thanks again for being so generous with your knowledge and time!

First we see something good. You have eliminated some of the magic numbers. We often see code that calculates something and there are a bunch of numbers. Where did those numbers come from? What do they do? When you instead use a variable like var duckSize = 100; we know what that number is in the formula. No magic numbers!

Next we have an ugly. You created 6 shop screens instead of reusing 1. This is why you are looking for an alternate solution to having one event per element. With 72 buttons that is a lot of onEvent code. You really only need one screen though.

Here is an example of magic numbers.

function loadDucks() {
  for(var yd = 0; yd < (420-duckSize); yd = yd + duckSize + padding) {
    for(var xd = 5; xd <= (320-duckSize); xd = xd + duckSize + padding) {
      var tempd = "duck"+duckNum;
      image(tempd, "outOfStock.png");
      setPosition(tempd, xd, yd, duckSize, duckSize);
      duckNum++;
      if(duckNum >= 80){
        return;
      }
    }
  }
}

Where did that 5 come from? I know 420 and 320 are screen sizes, but does everyone? I have no idea what 80 means. No magic numbers!

Another good is the way you broke up the code into functions. You separate handling input, showing the ducks, and functionality. One change you could make is trying to also separate out the database interactions.

A bad. I am seeing lots of function expressions. That is when you create a function and pass it as a parameter as a callback. I wouldn’t do that with a function that is more than one or two lines of code. For example readRecords("studentRoster",{},function(record){ is followed by 41 lines of code. What you can do instead is create a named function function duckPurchasedByStudent(students) { and then pass that as the callback readRecords("studentRoster",{},duckPurchasedByStudent); When you do that the function duckPurchase slims down from 53 lines to 10. You can probably break duckPurchasedByStudent up even further so you have small functions.

The good. You have anticipated or discovered all the things that can go wrong and added code to handle problems gracefully with an error message.

Overall the duck store was a difficult problem that you solved well. The multiple screens is the only ugly.

Here is my version. I am not going to say this is right or wrong or even better. But some of the things I have done is to separate handlers from functions that change the screen from functions that work with the databases from functions to purchase ducks. In some cases, I create functions that do nothing but set screens. But it is consistent with the separation of responsibilities.

Overall, I try to eliminate searching for stuff by tweaking my data structures. Eliminate magic numbers. Keep functions short. Keep indention levels to a minimum.

One change I made that might introduce a bug is only loading the student database once. You will have to let me know.

Your turn to tell me the good, bad, and ugly.