Se você está vendo esta mensagem, significa que estamos tendo problemas para carregar recursos externos em nosso website.

If you're behind a web filter, please make sure that the domains *.kastatic.org and *.kasandbox.org are unblocked.

Conteúdo principal

Um objeto do tipo Button

Uma das melhores maneiras de fazer código reutilizável e poderoso é usar a programação orientada a objeto, especialmente para fazer controles IU como botões. Na programação orientada a objeto, pensando em nosso mundo do programa em termos de tipos de objetos abstratos que têm comportamento particular e então criamos instâncias específicas desses tipos de objetos com parâmetros particulares. Se você não se lembra de como fazer isso em JavaScript, reveja-o novamente aqui.
Para usar a POO na criação de botões, precisamos definir um tipo de objeto Button e, em seguida, definir métodos nele, como para desenhá-lo e lidar com cliques do mouse. Nós gostaríamos de ser capazes de escrever um código como este:
var btn1 = new Button(...);
btn1.draw();

mouseClicked = function() {
  if (btn1.isMouseInside()) {
     println("Wow, você me clicou!");
  }
}
Vamos compará-lo ao código que escrevemos no último artigo:
var btn1 = {...};
drawButton(btn1);

mouseClicked = function() {
  if (isMouseInside(btn1)) {
     println("Wow, você me clicou!");
  }
}
É muito semelhante, não é? Mas há uma grande diferença – todas as funções são definidas no objeto de tipo Button e, na verdade, elas pertencem aos botões. Há uma união mais firme de propriedades e funcionamento, o que tende a levar a um código mais limpo e reutilizável.
Para definir o objeto de tipo Button, precisamos começar com o construtor: a função especial que recebe parâmetros de configuração e define as propriedades iniciais da instância do objeto.
Como uma primeira tentativa, aqui temos um construtor que recebe x, y, largura e altura:
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);
Isso certamente funciona, mas eu tenho outra abordagem para recomendar. Em vez de receber parâmetros individuais, o construtor pode receber um objeto de configuração.
var Button = function(config) {
    this.x = config.x;
    this.y = config.y;
    this.width = config.width;
    this.height = config.height;
    this.label = config.label;
};
A vantagem do objeto de configuração é que podemos continuar adicionando mais parâmetros no construtor (como label), e ainda é fácil para que entendamos o que cada parâmetro faz quando criamos um botão:
var btn1 = new Button({
    x: 100, y: 100,
    width: 150, height: 50,
    label: "Por favor, clique!"});
Mas podemos ir um passo mais longe que isso. E se a maioria dos botões fossem da mesma largura ou altura? Não deveríamos ter que continuar especificando os parâmetros de largura e altura para cada botão, deveríamos apenas ter que especificá-los quando necessário. Podemos fazer com que o construtor verifique se a propriedade está realmente definida no objeto de configuração e definir um valor padrão caso não esteja. Desse modo:
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";
};
Agora, podemos chamá-lo com um conjunto de propriedades, porque as outras vão ser definidas a partir do valor padrão:
var btn1 = new Button({x: 100, y: 100, label: "Por favor, clique!"});
Todo esse trabalho para um construtor? Mas eu garanto que vai valer a pena.
Agora que temos o construtor enquadrado (abotoado?), vamos definir um pouco do comportamento: o método draw. Será o mesmo código que o da função drawButton, mas ele irá abranger todas as propriedades de this, já que ele está definido no próprio protótipo do objeto:
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);
};
Uma vez que isso esteja definido, podemos chamar a função assim:
btn1.draw();
Aqui, temos um programa que usa o objeto Button para criar 2 botões – veja como é fácil criar e desenhar vários botões:
No entanto, pulamos a parte mais difícil: lidar com cliques. Nós podemos começar definindo uma função no protótipo de Button, que irá informar verdadeiro se o usuário clicar no interior da caixa delimitadora de um botão específico. Novamente, essa é a mesma função de antes, mas todas as propriedades de this são integradas a ela no lugar das que seriam passadas do objeto:
Button.prototype.isMouseInside = function() {
    return mouseX > this.x &&
           mouseX < (this.x + this.width) &&
           mouseY > this.y &&
           mouseY < (this.y + this.height);
};
Agora, podemos usá-la de dentro de uma funçãomouseClicked:
mouseClicked = function() {
    if (btn1.isMouseInside()) {
        println("You made the right choice!");
    } else if (btn2.isMouseInside()) {
        println("Yay, you picked me!");
    }
};
Faça um teste, clique em cada um dos botões:
Mas há algo que me incomoda sobre o modo como lidamos com esses cliques. Todo o propósito da programação orientada a objeto é juntar todo o comportamento relacionado a um objeto dentro dele mesmo, e usar propriedades para personalizar esse comportamento. No entanto, deixamos de fora parte do comportamento, osprintln dentro de mouseClicked:
mouseClicked = function() {
    if (btn1.isMouseInside()) {
        println("You made the right choice!");
    } else if (btn2.isMouseInside()) {
        println("Yay, you picked me!");
    }
};
Essas declarações de impressão ficariam melhores se ligadas a cada botão de alguma forma, como algo que passamos para o construtor. Só de olhar para o jeito que está agora poderíamos decidir passar uma mensagem para a configuração do construtor e definir uma função handleMouseClick para imprimi-la.
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: "Por favor, clique!",
    message: "Você fez a escolha certa!"
});

mouseClicked = function() {
   btn1.handleMouseClick();
};
Isso é muito mais agradável, já que tudo que for associado com o comportamento específico de cada botão é embrulhado no construtor. Mas também é excessivamente simples. E se nós quisermos fazer algo além de imprimir uma mensagem, como desenhar algumas formas ou mudar de cena, alguma coisa que levaria algumas linhas de código? Nesse caso, gostaríamos de fornecer ao construtor mais do que uma string-- na verdade queremos dar e ele um monte de código. Como podemos passar um monte de código?
... Com uma função!! Em JavaScript (mas não em todas as linguagens), podemos passar funções como parâmetros de funções. Isso é útil em diversas situações, mas é particularmente útil ao definir comportamentos para controles de interface de usuário, como botões. Podemos dizer ao botão, "Ei, aqui está essa função, é um monte de código que queremos que você chame quando o usuário clicar no botão." Nos referimos a essas funções como funções "callback" pois elas não serão chamada imediatamente, elas serão "chamadas de volta" na hora certa, mais tarde.
Podemos começar passando um parâmetro onClick que é uma função:
var btn1 = new Button({
    x: 100,
    y: 100,
    label: "Por favor clique!",
    onClick: function() {
       text("Você fez a escolha certa!", 100, 300);
    }
});
Então temos que ter certeza de que nosso construtor define uma propriedade onClick de acordo com o que é passado. Para o padrão, caso não haja nenhum onClick passad, criaremos uma função "no-op" -- uma função que faz "nenhuma operação" (do inglês "no operations"). Ela só está lá para que possamos chamá-la e não acontecer um erro:
var Button = function(config) {
    // ...
    this.onClick = config.onClick || function() {};
};
Finalmente, precisamos chamar de volta a função callback, uma vez que o usuário clica o botão. Isso é muito simples - podemos chamá-la escrevendo o nome da propriedade que demos a ela seguido de abre e fecha parênteses sem valor entre eles:
Button.prototype.handleMouseClick = function() {
    if (this.isMouseInside()) {
        this.onClick();
    }
};
Pronto, terminamos - temos um objeto Button que pode criar novos botões a partir dele, fazendo que com cada botão seja diferente e respondendo a diferentes eventos de clique. Clique ao redor do exemplo abaixo e veja o que acontece se você mudar o parâmetro do botão:
Agora que você já tem um modelo, você pode personalizar seus botões de outras formas, usando cores diferentes, ou fazendo com que eles respondam a outros eventos, como quando o usuário deixa o mouse sobre eles. Experimente usá-los em seus programas!

Quer participar da conversa?

  • Avatar male robot hal style do usuário Marlon Luís
    O que eu devo fazer no "Desafio: Corrida de Coelhos"? Já fiz tudo o que as instruções mandam, mas mesmo assim ele não me deixa prosseguir para a etapa de conclusão. Se alguém poder me ajudar ficarei muito agradecido. Olhem como eu fiz:
    var rabbits = [ ];

    var FRICTION = 0.2;
    var HOP_SPEED = 3;

    var Button = function(config) {
    this.x = config.x || 0;
    this.y = config.y || 0;
    this.width = config.width || 80;
    this.height = config.height || 50;
    this.label = config.label || "Click";
    this.color = config.color || color(207, 85, 85);
    this.onClick = config.onClick || function() {};
    };

    Button.prototype.draw = function() {
    if (this.isMouseInside() && mouseIsPressed) {
    fill(255, 255, 255);
    }
    else {
    fill(this.color);
    }
    rectMode(CENTER);
    rect(this.x, this.y, this.width, this.height, 5);
    fill(0, 0, 0);
    textSize(19);
    textAlign(CENTER, CENTER);
    text(this.label, this.x, this.y);
    };

    Button.prototype.isMouseInside = function() {
    return mouseX > this.x-this.width/2 &&
    mouseX < (this.x + this.width/2) &&
    mouseY > this.y - this.height/2 &&
    mouseY < (this.y + this.height/2);
    };

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

    var Rabbit = function(x, y){
    this.x = x;
    this.y = y;
    this.speed = 0;
    this.angle = 90;
    this.steps = 0;
    };

    Rabbit.prototype.hop = function() {
    this.speed = HOP_SPEED;
    };

    Rabbit.prototype.update = function() {
    this.y -= this.speed;
    if(this.speed > 0){
    this.speed -= FRICTION;
    }
    else if(this.speed < 0){
    this.speed = 0;
    }
    };

    Rabbit.prototype.draw = function() {
    ellipseMode(CENTER);
    angleMode = "degrees";
    translate(this.x, this.y);
    rotate(-this.angle-90);
    fill(255, 255, 255);
    noStroke();
    ellipse(0, -7, 2, 5);
    fill(0, 0, 0);
    ellipse(0, 0, 10, 16);
    fill(255, 255, 255);
    ellipse(0, 6, 8, 9);
    fill(0, 0, 0);
    ellipse(0, 9, 6, 8);
    triangle(-3, 8, 0, 8, -1, -1);
    triangle(3, 8, 0, 8, 2, -1);
    fill(255, 255, 255);
    stroke(255, 255, 255);
    triangle(-1, 12, 1, 12, 0, 13);
    resetMatrix();
    };

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

    var btn1 = new Button({
    x: 350,
    y: 350,
    width: this.width,
    height: this.height,
    color: color(28, 23, 128),
    label: "Hop!",
    onCLick: function() {
    rabbits[3].hop();
    }
    });

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

    draw = function() {
    background(98, 122, 54);
    btn1.draw();
    //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();
    };
    (6 votos)
    Avatar Default Khan Academy avatar do usuário
  • Avatar leaf green style do usuário Matheus Claus
    Vocês poderiam fazer vídeo igual o módulo de introdução, fica difícil entender só com texto
    (2 votos)
    Avatar Default Khan Academy avatar do usuário
  • Avatar leaf blue style do usuário Renan Costa
    Qual o código que prepara o canvas? Digo, como preparar um arquivo html para receber esse código javascript com os botões e suas funcionalidades?

    Exemplo:
    <!DOCTYPE HTML>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Title</title>
    <script src="js/jquery-1.7.1.min.js"></script>
    <script type="text/javascript">
    ...
    (1 voto)
    Avatar Default Khan Academy avatar do usuário
  • Avatar ohnoes default style do usuário Igor Soares
    Eu usei botões para alterar a tela de um jogo que estou programando (https://en.khanacademy.org/hour-of-code/teste/5829858346287104), porém quando eu seleciono tutorial, por exemplo, a tela muda, mas as funções dos botões permanecem "atrapalhando" o prosseguir.
    Se alguém souber como concertar, ou puder dar uma olhada no codigo e ver se têm algo errado eu agradeço.

    :)
    (1 voto)
    Avatar Default Khan Academy avatar do usuário
  • Avatar starky ultimate style do usuário #EuSouUmaFoca
    gente pra quem é desatento como eu lembrem-se de colocar as virgulas na parte do
    //create button
    e até mais
    '-' ,-,
    (1 voto)
    Avatar Default Khan Academy avatar do usuário
Você entende inglês? Clique aqui para ver mais debates na versão em inglês do site da Khan Academy.