Main content
A Button object type
One of the best ways to make code reusable and powerful is to use object-oriented programming, especially for making UI controls like buttons. In object-oriented programming, we think of our program world in terms of abstract object types that have particular behavior, and then we create specific instances of those object types with particular parameters. If you don't remember how to do it in JavaScript, review it here.
To use OOP to make buttons, we'll need to define a
Button
object type and then define methods on it, like to draw it and handle mouse clicks. We'd like to be able to write code that looks like this:var btn1 = new Button(...);
btn1.draw();
mouseClicked = function() {
if (btn1.isMouseInside()) {
println("Whoah, you clicked me!");
}
}
Let's contrast that to the code we wrote in the last article:
var btn1 = {...};
drawButton(btn1);
mouseClicked = function() {
if (isMouseInside(btn1)) {
println("Whoah, you clicked me!");
}
}
It's very similar, isn't it? But there's a big difference -- all the functions are defined on the
Button
object type, they actually belong to the buttons. There's a tighter coupling of properties and behavior, and that tends to lead to cleaner and more reusable code.To define the
Button
object type, we need to start with the constructor: the special function that takes in configuration parameters and sets the initial properties of the object instance.As a first attempt, here's a constructor that takes in x, y, width, and height:
var Button = function(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
};
var btn1 = new Button(100, 100, 150, 150);
That certainly works, but I have another approach I'd like to recommend. Instead of taking in individual parameters, the constructor could take in a configuration object.
var Button = function(config) {
this.x = config.x;
this.y = config.y;
this.width = config.width;
this.height = config.height;
this.label = config.label;
};
The advantage of the config object is that we can keep adding more parameters for the constructor to handle (like
label
), and it's still easy for us to understand what each parameter does when we construct a button:var btn1 = new Button({
x: 100, y: 100,
width: 150, height: 50,
label: "Please click!"});
But we can go a step farther than that. What if most buttons will be the same width or height? We shouldn't have to keep specifying the width and height parameters for every button, we should only have to specify them when necessary. We can have the constructor check if the property is actually defined in the config object, and set a default value if not. Like so:
var Button = function(config) {
this.x = config.x || 0;
this.y = config.y || 0;
this.width = config.width || 150;
this.height = config.height || 50;
this.label = config.label || "Click";
};
Now we can just call it with a subset of the properties, because the other ones will be set to the default value:
var btn1 = new Button({x: 100, y: 100, label: "Please click!"});
All that work for a constructor, ay? But, it'll be worth it, I swear.
Now that we have the constructor squared away (buttoned away?), let's define a bit of behavior: the
draw
method. It'll be the same code as the drawButton
function, but it will grab all the properties from this
, since it's defined on the object prototype itself:Button.prototype.draw = function() {
fill(0, 234, 255);
rect(this.x, this.y, this.width, this.height, 5);
fill(0, 0, 0);
textSize(19);
textAlign(LEFT, TOP);
text(this.label, this.x+10, this.y+this.height/4);
};
Once that's defined, we can call it like so:
btn1.draw();
Here's a program that uses that
Button
object to create 2 buttons - notice how easy it is to create and draw multiple buttons:We skipped the hard part, however: handling clicks. We can start by defining a function on the
Button
prototype that will report true if the user clicked inside a particular button's bounding box. Once again, this is just like our function from before, but it grabs all the properties from this
instead of a passed in object:Button.prototype.isMouseInside = function() {
return mouseX > this.x &&
mouseX < (this.x + this.width) &&
mouseY > this.y &&
mouseY < (this.y + this.height);
};
Now we can use that from inside a
mouseClicked
function:mouseClicked = function() {
if (btn1.isMouseInside()) {
println("You made the right choice!");
} else if (btn2.isMouseInside()) {
println("Yay, you picked me!");
}
};
Try it out below, clicking each of the buttons:
But, there's something that irks me about the way we've set up that click handling. The whole point of object oriented programming is to bundle up all the behavior related to an object inside the object, and to use properties to customize behavior. But, we've left some of the behavior dangling outside the object, the
println
s inside mouseClicked
:mouseClicked = function() {
if (btn1.isMouseInside()) {
println("You made the right choice!");
} else if (btn2.isMouseInside()) {
println("Yay, you picked me!");
}
};
Those print statements should be better tied to each button somehow, like as something that we pass into the constructor. Just looking at it the way it is now, we could decide to pass a message into the constructor config, and define a
handleMouseClick
function to print it out:var Button = function(config) {
...
this.message = config.message || "Clicked!";
};
Button.prototype.handleMouseClick = function() {
if (this.isMouseInside()) {
println(this.message);
}
};
var btn1 = new Button({
x: 100,
y: 100,
label: "Please click!",
message: "You made the right choice!"
});
mouseClicked = function() {
btn1.handleMouseClick();
};
That's much nicer, since now everything associated with the each button's particular behavior is wrapped up in the constructor. But it's also overly simple. What if we wanted to do something besides printing a message, like draw a few shapes or change scenes, something that would take a few lines of code? In that case, we'd want to provide the constructor with more than just a string-- we actually want to provide it with a bunch of code. How can we pass around a bunch of code?
...With a function! In JavaScript (but not all languages), we can pass functions as parameters to functions. That's useful in many situations, but particularly useful when defining behavior for UI controls like buttons. We can tell the button, "hey, here's this function, it's a bunch of code that I want you to call when the user clicks the button." We refer to those functions as "callback" functions because they won't be called immediately, they'll be "called back" at an appropriate time later.
We can start by passing an
onClick
parameter that's a function:var btn1 = new Button({
x: 100,
y: 100,
label: "Please click!",
onClick: function() {
text("You made the right choice!", 100, 300);
}
});
We then have to make sure our constructor sets an
onClick
property according to what's passed in. For the default, in case there's no onClick
passed in, we'll just create a "no-op" function -- a function that does "no operations" at all. It's just there so that we can call it and not experience an error:var Button = function(config) {
// ...
this.onClick = config.onClick || function() {};
};
Finally, we need to actually call back the callback function once the user clicks the button. That's actually pretty simple- we can just call it by writing the property name we saved it into and following that with empty parentheses:
Button.prototype.handleMouseClick = function() {
if (this.isMouseInside()) {
this.onClick();
}
};
And now we're done - we have a
Button
object that we can easily create new buttons out of, making each button look different and responding differently to click events. Click around on the example below, and see what happens when you change button parameters:Now that you have that as a template, you could customize your buttons in other ways, like different colors, or have them respond to other events, like mouseover. Try it out in your programs!
Want to join the conversation?
- Why are there
||
in the code above? What do they mean? I've never seen them before except for absolute value things (|#|
), but I don't think that's what this is...(83 votes) - What does the "config." mean?
Also, every time I go away from this page, my progress disappears. All the circles that have been filled in green in the Buttons section turn grey again.(58 votes)var Buttons = function(confg)
Whenever you put anything inside of a functions parenthesis,( )
, it means you are declaring a variable and that you plan to pass something into this function. Later on when you declare the new button, the command isvar btn1 = new Buttons( { .... } ) ;
Notice the squiggly braces{ }
. Squiggly braces are only used to declare objects. So, what this is doing is you are creating a new object inside of thenew Buttons( )
function, thus passing the object to the Buttons Constructor Function which becomes the variableconfig
.(63 votes)
- Hello, I keep having a problem with the rabbit racer challenge.
I run this codevar btn1 = new Button({
x : 350,
y : 350,
width : this.width,
height : this.height,
color : color(28, 23, 122),
label : ("Hop")
onClick: function(){
rabbits[3].hop;
}
});
But it keeps saying I have a syntax error.... I have no idea what that means.
Any help is welcome(28 votes)- You need a comma after your
label
declaration.(26 votes)
- What does " UI controls " mean?(25 votes)
- It's like a computer doing something for you.(5 votes)
- Help! I am doing the challenge and I am stuck at the first step
//Draw the button
var btn1 = new Button({
x : 350,
y : 350
});
draw = function() {
btn1.draw; - my line of difficulty
};
I tried putting different things intstead of draw next to btn1, but the error buddy stays where he is, as firm a rock boulder. Could you please help me out?(10 votes)- Yes, it has to be
btn1.draw();
or else you will get an error.(2 votes)
- Why do we have to use
isMouseInside()
function in the following code?var btn1 = {...};
drawButton(btn1);
mouseClicked = function() {
if (isMouseInside(btn1)) {
println("Whoah, you clicked me!");
}
}
It would not be easier this way?var btn1 = {...};
drawButton(btn1);
mouseClicked = function() {
println("Whoah, you clicked me!");
}(2 votes)isMouseInside
checks whether you click on the button (iftrue
) or anywhere in the program (iffalse
). If you ommit it,println("Whoah, you clicked me!");
would be "activated" even when you click outside of the button, and you don`t want that :)(25 votes)
- What is the difference between this:
var Button = function ( ) { //object oriented
this.onClick = function ( ) { rect(... };
};
and this:var Button = function ( ) { //object oriented
};
Button.prototype.onClick = function ( ) { rect(... };(12 votes)- In the first bunch of codes, "onClick" is a property of every object under "Button" (and can be different with each "Button" object if u set it as a parameter of the function). When u wanna call it, u say "this.onClick();".
In the second bunch, "onClick" is a method of the "Button" class, and have to be the same with all the "Button" objects, which might make all buttons the same function. When u wanna call it, u say "btn.onClick();".(4 votes)
- Ok, step 1 of the challenge Rabbit Racer is really annoying and I can't figure it out. I put this at the bottom of the draw function:
var btn1 = new Button({
x: 350,
y: 300
});
And then I'm supposed to do ANOTHER draw function according the hint where I call btn1.------
Btw Im not sure if Im supposed to put btnt.draw(); there or not...But I've tried putting everything OUTSIDE of the other draw function and that doesn't work either...I thought your not allowed to have 2 draw functions? Please help!(4 votes)- Okay. I found the place. Line 92, the var btn1 needs to get the x, and y parameters. Thanks.(7 votes)
- Apologies if this is covered in a module further along, but in the "Object-oriented Button (with Click Handling)" program (last one on the "A Button object type" page), would it not be better to add something like:
this.onClick = function() {
text(config.clickText, this.x, this.y+this.height);
to the button constructor (I did at line 8) so that way when creating a new button, rather than having to include the onClick function there with each, you can just use the text you want as "clickText" and have button creation be shorter and less repetitive?(4 votes)- What you suggest is true. However, what if I wanted one button to show a text string and the other to show an image, and a third button to show an explosion? As coded, the button is generic, leaving the user supplied
onClick
to handle the specifics policies that the application demands.(4 votes)
- I need help with the last step of Challenge : Rabbit Racer(6 votes)
- In Step 3 of the Rabbit Racer challenge, we are asked to
The HINT for this step isChange the
onClick
method for your button, so that it
calls a method on the player's rabbit (the last rabbit
in therabbits
array) to make it hop forward.
In the starter code, the code forvar btn1 = new Button({
x: ___,
y: ___,
width: ___,
height: ___,
color: color(_______),
label: ___,
onClick: function() {
rabbits[__].(___);
}
});btn1
is on line 115.
You should have completed most of the button in Steps 1 and 2. In Step 3, we should be just looking at theonClick
method. We want to apply this method to the player's rabbit, which we know from the instruction is the last rabbit in therabbits
array.
On line 110, we have the code that creates theRabbit
objects and adds them to therabbits
array. Look at this code to determine which index to use inside theonClick
method.
Take a look at the code for theRabbit
object (lines 51 - 102). In this code, find a method you can use to make the rabbit hop forward.(2 votes)