Una de las mejores maneras de hacer código reutilizable y poderoso es utilizar programación orientada a objetos, especialmente para hacer controles en la interfaz de usuario, como botones. En programación orientada a objetos, pensamos en nuestro mundo del programa en términos de tipos de objeto abstractos que tienen un comportamiento particular, y luego creamos instancias específicas de esos tipos de objeto con parámetros particulares. Si no recuerdas cómo hacerlo en JavaScript, revísalo aquí.
Para utilizar programación orientada a objetos (POO) para hacer botones, necesitaremos definir un tipo de objeto Button, y luego definir métodos en él, tales como dibujarlo y responder a los clics del ratón. Nos gustaría poder escribir código que se vea así:
var btn1 = new Button(...);
btn1.draw();

mouseClicked = function() {
  if (btn1.isMouseInside()) {
     println("¡Eh, me diste clic!");
  }
}
Veamos eso en contraste con el código que escribimos en el artículo anterior:
var btn1 = {...};
drawButton(btn1);

mouseClicked = function() {
  if (isMouseInside(btn1)) {
     println("¡Eh, me diste clic!");
  }
}
Es muy parecido, ¿no es cierto? Pero hay una gran diferencia: todas las funciones están definidas en el objeto de tipoButton, y en realidad pertenecen a los botones. Hay un acoplamiento más estrecho entre las propiedades y el comportamiento, y eso tiende a conducir a código más limpio y reutilizable.
Para definir el objeto de tipo Button, tenemos que empezar con el constructor: la función especial que recibe los parámetros de configuración y establece las propiedades iniciales de la instancia del objeto.
Como un primer intento, he aquí un constructor que utiliza x, y, ancho y alto:
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);
Eso ciertamente funciona, pero tengo otro enfoque que me gustaría recomendar. En lugar de recibir parámetros individuales, el constructor podría utilizar un objeto de configuración.
var Button = function(config) {
    this.x = config.x;
    this.y = config.y;
    this.width = config.width;
    this.height = config.height;
    this.label = config.label;
};
La ventaja del objeto de configuración es que podemos agregar más parámetros para que los maneje el constructor (tales como label), y aún es fácil para nosotros entender qué hace cada parámetro cuando construimos un botón:
var btn1 = new Button({
    x: 100, y: 100,
    width: 150, height: 50,
    label: "¡Por favor haz clic!"});
Pero podemos ir un paso más allá. ¿Qué pasa si la mayoría de los botones tienen el mismo ancho o alto? No deberíamos tener que especificar los parámetros width y height para cada botón; solo tendríamos que especificarlos cuando fuera necesario. Podemos hacer que el constructor revise si la propiedad en realidad está definida en el objeto de configuración y establecer un valor predeterminado en otro caso. De esta manera:
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 || "Clic";
};
Ahora podemos llamarlo solo con un subconjunto de las propiedades, pues las demás se asignarán a un valor por omisión:
var btn1 = new Button({x: 100, y: 100, label: "¡Por favor haz clic!"});
¿Todo ese trabajo para un constructor, no? Pero valdrá la pena, lo prometo.
Ahora que el constructor está bien, definamos algo del comportamiento: el método de dibujar draw. Será el mismo código que la función drawButton, pero tomará todas las propiedades de this, puesto que está definido en el prototipo del objeto mismo:
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);
};
Una vez definido, podemos llamarlo así:
btn1.draw();
He aquí un programa que utiliza el objeto Button para crear 2 botones: observa lo fácil que es crear y dibujar varios botones:
Sin embargo, nos hemos saltado la parte difícil: el manejo de los clics. Podemos empezar por definir una función en el prototipo de Button que regrese true si el usuario hizo clic dentro de los límites de un botón en particular. Una vez más, esto es igual a nuestra función anterior, pero obtiene todas las propiedades de this en lugar del objeto que le hayamos pasado:
Button.prototype.isMouseInside = function() {
    return mouseX > this.x &&
           mouseX < (this.x + this.width) &&
           mouseY > this.y &&
           mouseY < (this.y + this.height);
};
Ahora podemos usar eso desde dentro de una función mouseClicked:
mouseClicked = function() {
    if (btn1.isMouseInside()) {
        println("Escogiste la opción correcta!");
    } else if (btn2.isMouseInside()) {
        println("¡Sí, me escogiste!");
    }
};
Pruébalo a continuación, haciendo clic en cada uno de los botones:
Pero, hay algo que me molesta en la forma en que hemos definido el manejo de los clics. El punto central de la programación orientada a objetos es juntar todo el comportamiento relacionado con un objeto dentro del objeto, y utilizar propiedades para personalizar el comportamiento. Pero, hemos dejado parte del comportamiento colgando fuera del objeto, los println dentro de mouseClicked:
mouseClicked = function() {
    if (btn1.isMouseInside()) {
        println("Escogiste la opción correcta!");
    } else if (btn2.isMouseInside()) {
        println("¡Sí, me escogiste!");
    }
};
Esas instrucciones de impresión estarían mejor atadas de alguna manera a cada botón, como algo que le pasamos al constructor. Solo con mirar la forma en que está ahora, podríamos decidir pasarle un mensaje a la configuración del constructor, y definir una función handleMouseClick para imprimirlo:
var Button = function(config) {
    ...
    this.message = config.message || "¡Clic!";
};

Button.prototype.handleMouseClick = function() {
    if (this.isMouseInside()) {
         println(this.message);
    }
};

var btn1 = new Button({
    x: 100,
    y: 100,
    label: "¡Por favor clic!",
    message: "¡Elegiste lo correcto!"
});

mouseClicked = function() {
   btn1.handleMouseClick();
};
Eso está mucho mejor, pues ahora todo lo asociado con el comportamiento particular de cada botón está dentro del constructor. Pero también es demasiado sencillo. ¿Qué pasa si queremos hacer algo además de imprimir un mensaje, como dibujar algunas figuras o cambiar escenas, algo que tomara algunas líneas de código? En ese caso, quisiéramos proveerle al constructor algo más que una cadena de caracteres: en realidad queremos proporcionarle un montón de código. ¿Cómo podemos pasar un montón de código?
...¡Con una función! En JavaScript (y no en todos los lenguajes), a las funciones podemos pasarles otras funciones como parámetros. Eso es útil en muchas situaciones, pero particularmente para definir el comportamiento de controles en la interfaz de usuario, tales como botones. Podemos decirle al botón, "hey, aquí está esta función, es un montón de código que quiero que invoques cuando el usuario haga clic en el botón". Nos referimos a esas funciones como "callback" porque no se invocan inmediatamente, son "llamadas de regreso", en un momento apropiado.
Podemos empezar por pasarle un parámetro onClick que sea una función:
var btn1 = new Button({
    x: 100,
    y: 100,
    label: "¡Por favor haz clic!",
    onClick: function() {
       text("¡Elegiste lo correcto!", 100, 300);
    }
});
Entonces tenemos que asegurarnos que nuestro constructor tenga una propiedad onClick de acuerdo a lo que se le haya pasado. Para el valor por omisión, en caso de que no se le haya pasado onClick, simplemente crearemos una función "no-op" (una función que "no hace operaciones" en absoluto; está ahí solo para que podamos llamarla y no generar un error):
var Button = function(config) {
    // ...
    this.onClick = config.onClick || function() {};
};
Por último, de hecho tenemos que la invocar a la función callback una vez que el usuario haga clic en el botón. Eso es muy sencillo; podemos invocarla simplemente con el nombre de la propiedad con la que la guardamos, seguido de paréntesis vacíos:
Button.prototype.handleMouseClick = function() {
    if (this.isMouseInside()) {
        this.onClick();
    }
};
Y ya terminamos: tenemos un objeto Button a partir del cual podemos crear nuevos botones, haciendo que cada botón se vea diferente y responda de manera diferente a eventos de clic. Haz clic en el siguiente ejemplo y ve qué pasa cuando cambias los parámetros del botón:
Ahora que tienes eso como una plantilla, puedes personalizar tus botones de otras maneras, como con colores diferentes, o hacer que respondan a otros eventos, como mouseover. ¡Pruébalo en tus programas!
Cargando