If you're seeing this message, it means we're having trouble loading external resources on our website.

Si estás detrás de un filtro de páginas web, por favor asegúrate de que los dominios *.kastatic.org y *.kasandbox.org estén desbloqueados.

Contenido principal

Un tipo de objeto Button

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!

¿Quieres unirte a la conversación?

  • Avatar spunky sam blue style para el usuario George Cano
    usame como boton de no entiendo nada
    (28 votos)
    Avatar Default Khan Academy avatar para el usuario
  • Avatar marcimus purple style para el usuario Preppy
    La solución del desafío por si alguien lo necesita :)

    /**************
    *MAIN PROGRAM
    ***************/

    /** create object instances **/

    //create rabbits
    var rabbits = [];
    for (var i = 0; i < 4; i++) {
    rabbits.push(new Rabbit(50 + 100 * i, 300));
    }

    //create button
    var btn1;


    draw = function() {
    background(98, 122, 54);

    //Draw the finish line
    rectMode(CORNER);
    stroke(0, 0, 0);
    for (var i = 0; i < width - 20; i += 40) {
    fill(0, 0, 0);
    rect(i, 20, 20, 20);
    rect(i + 20, 40, 20, 20);
    fill(255, 255, 255);
    rect(i+20, 20, 20, 20);
    rect(i, 40, 20, 20);
    }

    //Draw the racers
    for (var i = 0; i < rabbits.length; i++) {
    rabbits[i].update();
    rabbits[i].draw();

    if (i < 3 && frameCount % 15 === 0) {
    if (random(1) < 0.5) {
    rabbits[i].hop();
    }
    }
    }

    //Draw the button
    btn1.draw();
    };

    mouseClicked = function() {
    btn1.handleMouseClick();
    };



    //create button
    var btn1 = new Button({
    x:350,
    y:350,
    width:50,
    height: 26,
    color: color(204, 200, 92),
    label: "hola",
    onClick: function (){
    rabbits[3].hop();
    }

    });
    (3 votos)
    Avatar Default Khan Academy avatar para el usuario
  • Avatar spunky sam blue style para el usuario George Cano
    está muy guapo el jueguito, pero no me funciona

    soluciones xfa
    (3 votos)
    Avatar Default Khan Academy avatar para el usuario
  • Avatar blobby green style para el usuario Carlos Mendoza
    En la sección: desafio carrera de conejos para hacer que el boton haga algo en realidad.. alguien me puede ayudar a resolver? estoy sumamente perdido y no se que colocar dentro del arreglo rabbits o lo que le sigue... si me pueden responder con una explicación seria muchísimo mejor.. muchas gracias a quien me responda de corazon
    (2 votos)
    Avatar Default Khan Academy avatar para el usuario
    • Avatar spunky sam blue style para el usuario Yoel Astudillo
      Me trabe en lo mismo. El problema que comentas se resuelve con este código dentro del btn1:
      "onClick: function() {
      rabbits[3].hop();}"
      Según lo entendí yo, el rabbit que no se mueve es rabbits[3], que en la seccion //Draw the racers esta excluido del if que nada mas llega hasta 2(menor a 3):
      if (i < 3 && frameCount % 14 === 0) {
      if (random(1) < 0.5) {
      rabbits[i].hop();
      De ahí deduci que rabbits[3] no tiene llamada a ".hop" y por lo tanto resulta el codigo que falta al boton.
      Lo que no entiendo bien es el funcionamiento interno de los métodos rabbits, si alguien puede seguir aportando estaré muy agradecido!
      (10 votos)
  • Avatar blobby green style para el usuario Evangy
    en el desafio: "carrera de conejos" como paso una cadena de caracteres para la etiqueta del botón??
    (1 voto)
    Avatar Default Khan Academy avatar para el usuario
    • Avatar orange juice squid orange style para el usuario Sara
      Hola, si quieres cambiar lo que dice el botón usa label cuando crees la nueva instancia del objeto Button(btn1), por ejemplo:
      var btn1 = new Button({
      label: "avanzar",
      x:350,
      y:350
      });

      Nota: Si quieres escribir texto no olvides las comillas.
      si tienes otra pregunta dimela y si te sirvió dale un voto
      (3 votos)
  • Avatar blobby green style para el usuario Edwing Hernández
    Hola, tengo una pregunta. ¿"isMouseInside" hace parte de la librería de Processing, es decir, ya está definida por defecto?. Es que en el desafío de los conejos, primero la usan y después si la definen como un método. ¡Me confunde!
    (1 voto)
    Avatar Default Khan Academy avatar para el usuario
  • Avatar orange juice squid orange style para el usuario Sara
    Alguien sabe porque la función OnClick se pone en el objeto con this, ¿no se podría poner con prototype?
    (1 voto)
    Avatar Default Khan Academy avatar para el usuario
  • Avatar male robot johnny style para el usuario climaxfran
    estoy trabado en el segundo paso y no se por que. me dice que revise el uso de mayúscula pero si cambio la letra mayuscula de la function btn1 y en ves de Button escribo button con minisculas el chico ow no! me dice que mi codigo esta mal comento todo mi codigo para revisar y el error siempre esta con la letra que le ponga a Button alguien que me ayude por favor
    (0 votos)
    Avatar Default Khan Academy avatar para el usuario
  • Avatar piceratops tree style para el usuario Pablo Yaciuk
    Alguien puede decirme cómo funciona la función "return"? (Usada para definir Button.prototype.isMouseInside).

    Somebody can explain me how does the "return" function works? (Used to define Button.prototype.isMouseInside).
    (0 votos)
    Avatar Default Khan Academy avatar para el usuario
    • Avatar leafers ultimate style para el usuario gorka.elorduy.garcia
      Te devuelve true si se cumplen TODAS las condiciones,
      Button.prototype.isMouseInside = function() {
      return mouseX > this.x &&
      mouseX < (this.x + this.width) &&
      mouseY > this.y &&
      mouseY < (this.y + this.height);
      };
      Es decir, si se cumplen todas las condiciones, el ratón está dentro del botón, y si no se cumplen, el ratón está fuera. Te devuelve un booleano, true o false, si se cumplen o no todas las condiciones (ten en cuenta que usar el operador AND (&)).

      Un saludo
      (1 voto)
  • Avatar male robot johnny style para el usuario Angel Jassin Romero
    Buenas tardes, estoy realizando el desafío de la carrera de conejos, y en el último punto no se que valores debo colocar on click para que el conejo se mueva. la ayuda dice que hay rabbits.[].----(); no se que hacer.
    (0 votos)
    Avatar Default Khan Academy avatar para el usuario
¿Sabes inglés? Haz clic aquí para ver más discusiones en el sitio en inglés de Khan Academy.