Creative Coding · Session c08

Build WhinnyBot: Objects and Events

A deliberately slow guide to dictionaries, object-oriented thinking, browser events, callbacks, and making the Return key send a message.

Today you will build a tiny chatbot that knows a few prepared answers. The finished program is short, but it combines several ideas that deserve to be learned separately. We will therefore build the mental model first and the final program second.

The big idea: WhinnyBot is made from objects that wait for events. The input object waits for a keyboard event. The button object waits for a mouse or touch event. When one of those events happens, a function runs and chooses a reply from the answer dictionary.

By the end, you should be able to explain—not merely copy—these lines:

sendButton.mousePressed(answerPrompt);

promptInput.elt.addEventListener("keydown", function (event) {
  if (event.key === "Enter") {
    answerPrompt();
  }
});

If those lines currently look like punctuation soup, that is completely reasonable. Each dot, word, pair of parentheses, and pair of braces has a job. We will unpack all of them.

Part 1 · What is an object?

An object is a bundle of related information and abilities that a program treats as one “thing.” A real horse has information such as its name and age, and abilities such as walking or neighing. A program can use the same organisational idea.

A horse in the real world
Information: name, age, colour
Abilities: walk, eat, neigh
An object in a program
Properties: stored information
Methods: functions the object can perform

Property versus method

WordMeaningHow it usually looks
PropertyA value stored on an object.horse.name or event.key
MethodA function belonging to an object—an action it knows how to perform.horse.neigh() or promptInput.value()

The dot means: “look inside this object for the named property or method.”

horse.name       // Read the property called name.
horse.neigh()    // Call the method called neigh.
Parentheses matter. horse.neigh refers to the function itself. horse.neigh() calls the function now. We will use both patterns later.

So what is object-oriented programming?

Object-oriented programming, shortened to OOP, is a way of organising a program around objects. Each object can keep track of its own data and offer methods for working with that data. Larger programs may also use classes, which are reusable plans for making similar objects.

Class: the reusable plan
“Every stable stall has a number, a horse name, and an open/close ability.”
Object: one real instance
“Stall 7 belongs to Luna, and this particular stall is closed.”

You will not write a class in this lesson. p5.js has already used classes behind the scenes. When you call createInput(), p5.js creates and returns an input object for you. You will use that object's properties and methods.

Precise but important: JavaScript calls our answer dictionary an “object,” but merely writing an object literal is not the whole of OOP. The dictionary is one object used mainly to store values. The input and button objects show more OOP-like behaviour because they combine state with useful methods.

Part 2 · Three different objects in WhinnyBot

Object A: the answer dictionary

Our first object stores key-value pairs. A key is the lookup name. Its value is the information stored under that name.

const answers = {
  "hello":  "Neigh there! Welcome to the stable.",
  "horse":  "Horses can sleep standing up or lying down.",
  "carrot": "Carrots are treats, not a whole horse dinner.",
  "joke":   "Why did the pony whisper? It was a little horse!",
  "default": "I do not know that one yet. Try hello, horse, carrot, or joke."
};
let prompt = "carrot";
let answer = answers[prompt];

// JavaScript reads this as answers["carrot"].
// answer now contains the carrot sentence.

Object B: the p5 input object

promptInput = createInput("");

This line does two jobs:

  1. createInput("") asks p5.js to create an empty text box.
  2. The returned object is stored in promptInput, so we can find and control that same text box later.

The variable is not the visible text box itself. Think of it as a named handle or remote control that points to the input object.

promptInput
→
p5.Element wrapper object
.value()
→
method: read or change the typed text
.attribute()
→
method: change an HTML attribute
.elt
→
property: the underlying browser HTML element

Object C: the underlying browser element

p5.js wraps the browser's ordinary HTML input element in a friendlier p5.Element object. Most of the time, p5's methods are enough. For a standard browser keyboard listener, however, we need the original HTML element. p5 gives us that object through the property .elt.

promptInput.elt

Read this from left to right:

  1. Find the object stored in promptInput.
  2. Look inside it for the property called elt.
  3. That property points to the real browser input object.
Why two input objects? p5's wrapper gives us convenient p5 methods such as value(). The browser element gives us standard browser methods such as addEventListener(). They refer to the same visible input box at two different layers.

Part 3 · What is an event?

A normal instruction such as replyText.html(answer) runs when JavaScript reaches that line. An interactive program is different: it often has to wait without knowing when the person will act.

An event is a message from the browser saying that something happened. Examples include a key press, a click or tap, pointer movement, the page loading, or an input value changing.

At a stable
A doorbell rings. The ring is the event. A stable worker is listening for it. The worker then follows the “open the door” instructions.
In the browser
A key goes down. keydown is the event. A registered listener notices it and runs a callback function.

The four pieces of event-driven code

PieceJobWhinnyBot example
TargetThe object we are watching.promptInput.elt
Event typeThe kind of action we care about."keydown"
ListenerThe registration that tells the browser to keep watch.addEventListener(...)
CallbackThe function the browser calls later.function (event) { ... }
1
Input is waiting
→
2
A key goes down
→
3
Browser calls the callback

What is the event object?

When the browser calls our callback, it also gives the callback an object containing details about what happened. We choose to name that object event.

function (event) {
  // event is an object created and supplied by the browser.
}
PropertyExampleMeaning
event.key"a" or "Enter"The meaning of the key.
event.code"KeyA" or "Enter"The physical keyboard position.
event.type"keydown"The kind of event.
Return versus Enter: a keyboard may print “Return” or “Enter” on the key. In this browser event, event.key normally reports the string "Enter". Capitalisation matters.

Live event laboratory

Click or tap in this box and press several keys. Watch the browser describe each keydown event. Try a letter, Space, Backspace, and Return/Enter.

Waiting for a keydown event…

Part 4 · Read the Return-key listener one piece at a time

promptInput.elt.addEventListener("keydown", function (event) {
  if (event.key === "Enter") {
    answerPrompt();
  }
});
  1. promptInput finds the p5 input wrapper object.
  2. .elt follows its property to the browser input element.
  3. .addEventListener(...) asks that browser element to start listening.
  4. "keydown" selects the event type. It fires when any key is pushed down while the input has focus.
  5. function (event) { ... } is the callback. We give it to the browser now; the browser calls it later.
  6. event.key reads the key property from the browser-supplied event object.
  7. === "Enter" checks whether the key was Return/Enter. Other keys fail this test.
  8. answerPrompt() calls our send function immediately after Return/Enter is detected.
  9. }); closes the callback, closes the method call, and ends the statement.

A crucial callback distinction

// Give the function to an object so it can call it later:
sendButton.mousePressed(answerPrompt);

// Call the function now because Enter was just detected:
answerPrompt();

In the first line, there are no parentheses after answerPrompt. We pass the function itself to p5. In the second line, the parentheses call the function right now.

Common bug: sendButton.mousePressed(answerPrompt()) calls the function while the page is being set up. It passes the result—not the function—to mousePressed.

Part 5 · Build WhinnyBot in small stages

Do not type the complete program in one jump. Build one stage, run it, and confirm what works. If a later stage breaks, you then know where to look.

1

Create the answer dictionary

Place this above setup(). It is global, so both setup() and answerPrompt() can find it.

const answers = {
  "hello": "Neigh there! Welcome to the stable.",
  "horse": "Horses can sleep standing up or lying down.",
  "carrot": "Crunchy, orange, and best as a small treat!",
  "joke": "Why did the pony whisper? It was a little horse!",
  "default": "I do not know that one yet."
};

Predict: answers["horse"] returns the horse sentence. answers["dragon"] is undefined because that key does not exist.

2

Declare variables for the interface objects

let promptInput;
let sendButton;
let replyText;

They are declared outside setup() because answerPrompt() also needs them. They begin empty; setup() assigns the objects later.

3

Create the interface and connect the button

function setup() {
  noCanvas();

  promptInput = createInput("");
  promptInput.attribute("placeholder", "Try: hello, horse, carrot, joke");

  sendButton = createButton("Ask WhinnyBot");
  sendButton.mousePressed(answerPrompt);

  replyText = createP("Neigh there! Ask me something.");
}
  • noCanvas() says this program uses HTML controls, not a drawing canvas.
  • Each create... function returns an object.
  • mousePressed(answerPrompt) registers a callback. It does not run it yet.
4

Write one send function for both controls

function answerPrompt() {
  let prompt = promptInput.value().toLowerCase().trim();
  let answer = answers[prompt];

  if (answer === undefined) {
    answer = answers.default;
  }

  replyText.html(answer);
  promptInput.value("");
  promptInput.elt.focus();
}
  1. value() reads the text.
  2. toLowerCase() makes HORSE become horse.
  3. trim() removes spare outside spaces.
  4. answers[prompt] performs the lookup.
  5. An undefined lookup changes to the default.
  6. html(answer) displays the reply.
  7. value("") clears the box.
  8. focus() returns the cursor.
Run it now. Before adding keyboard code, test the button with hello, HORSE, extra spaces, and an unknown word.
5

Add the Return/Enter listener inside setup()

Place this after createInput(). You cannot attach a listener to an object that does not exist yet.

promptInput.elt.addEventListener("keydown", function (event) {
  if (event.key === "Enter") {
    event.preventDefault();
    answerPrompt();
  }
});

preventDefault() stops a normal built-in browser action. A one-line input usually works without it, but it prevents surprises if this code later moves into a form.

The listener is attached only to the input. Return sends the prompt only while that input has focus. This is more controlled than listening to every key everywhere.

The complete program

Now compare the whole program with the five stages. Nothing below is new; it is simply the assembled version.

const answers = {
  "hello": "Neigh there! Welcome to the stable.",
  "horse": "Horses can sleep standing up or lying down.",
  "carrot": "Crunchy, orange, and best as a small treat!",
  "joke": "Why did the pony whisper? It was a little horse!",
  "default": "I do not know that one yet."
};

let promptInput;
let sendButton;
let replyText;

function setup() {
  noCanvas();

  promptInput = createInput("");
  promptInput.attribute("placeholder", "Try: hello, horse, carrot, joke");

  sendButton = createButton("Ask WhinnyBot");
  sendButton.mousePressed(answerPrompt);

  replyText = createP("Neigh there! Ask me something.");

  // Watch the input for keyboard events.
  promptInput.elt.addEventListener("keydown", function (event) {
    // Return and Enter normally produce event.key === "Enter".
    if (event.key === "Enter") {
      event.preventDefault();
      answerPrompt();
    }
  });
}

function answerPrompt() {
  // Read and clean the typed prompt.
  let prompt = promptInput.value().toLowerCase().trim();
  let answer = answers[prompt];

  // Use a safe fallback if the key does not exist.
  if (answer === undefined) {
    answer = answers.default;
  }

  // Display, clear, and return focus.
  replyText.html(answer);
  promptInput.value("");
  promptInput.elt.focus();
}

Try the finished behavior

🐴
WhinnyBotTry: hello, horse, carrot, joke
Neigh there! Type one of my secret keywords below.

Debugging by symptom

The button works, but Return does nothing

The function runs immediately when the page opens

Look for mousePressed(answerPrompt()). Remove the final parentheses: mousePressed(answerPrompt).

Every prompt uses the fallback

“Cannot read properties of undefined”

The code may be using an object before it was created, or a variable name may be misspelled. Compare its declaration, assignment, and use.

Knowledge check

  1. Explain the difference between a property and a method.
  2. What kind of value does createInput() return?
  3. What does .elt give us?
  4. Name the target, event type, and callback in the Return-key listener.
  5. Where does the callback's event object come from?
  6. Why compare with "Enter" when the keyboard may say “Return”?
  7. Why does one line use answerPrompt and another use answerPrompt()?
Check your explanations
Question 1
A property is stored information. A method is a function belonging to an object—an action the object can perform.
Question 2
createInput() returns a p5.Element object wrapping the visible input.
Question 3
.elt gives the underlying browser HTML element, which has methods such as addEventListener() and focus().
Question 4
Target: promptInput.elt. Event type: "keydown". Callback: function (event) { ... }.
Question 5
The browser creates the event object and passes it into the callback when it calls that function.
Question 6
For both Return and Enter, event.key normally contains the exact string "Enter".
Question 7
answerPrompt refers to the function so another object can call it later. answerPrompt() calls it immediately.
Make WhinnyBot yours—one change at a time
  1. Add one dictionary key and test it with the button.
  2. Test the same key with Return/Enter. Both controls should reuse answerPrompt().
  3. Add a multi-word key such as "best horse".
  4. Change the personality without changing the event code. Content and interaction are separate parts.
  5. Stretch: add an else branch that logs non-Enter keys without sending.
Next session → c09: RandomnessWhinnyBot currently gives the same prepared answer every time. Next you will use random() to choose among several replies. The event model stays the same: an action happens, a callback runs, and the program updates what the person sees.
← All lessons
25:00

Ask your tutor