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

Si vous avez un filtre web, veuillez vous assurer que les domaines *. kastatic.org et *. kasandbox.org sont autorisés.

Contenu principal

Un type d'objet Bouton

L'une des meilleures façons de rendre le code réutilisable et riche est d'utiliser la programmation orientée objet, particulièrement lorsqu'il s'agit de créer des éléments d'interface utilisateur comme les boutons. Dans la programmation orientée objet, on pense notre environnement de programmation en terme de types d'objet abstrait possédant un comportement spécifique, puis on crée des instances particulières de ces types d'objet avec des paramètres qui leur sont propres. Si vous ne vous souvenez pas comment faire cela en JavaScript, vous pouvez réviser ici.
Pour utiliser la programmation orientée objet pour créer des boutons, il nous faut définir un type d'objet Button, puis incorporer des méthodes, comme dessiner ou réceptionner les clics de souris. Nous aimerions écrire un code qui ressemble à ceci :
var btn1 = new Button(...);
btn1.draw();

mouseClicked = function() {
  if (btn1.isMouseInside()) {
    println("Ouah, vous m'avez cliqué !");
  }
}
Comparons avec le code que nous avions écrit dans l'article précédent :
var btn1 = {...};
drawButton(btn1);

mouseClicked = function() {
  if (isMouseInside(btn1)) {
    println("Ouah, vous m'avez cliqué !");
  }
}
C'est pratiquement pareil, non ? Il y a pourtant une grosse différence : toutes les fonctions sont définies sur le type d'objet Button. Elles appartiennent aux boutons. Le lien entre les propriétés et les comportements est plus étroit, et cela tend à plus de clarté et permet un code mieux réutilisable.
Pour définir le type d'objet Button, il nous faut commencer par le constructeur : la fameuse fonction qui prend en paramètre une configuration et définit les propriétés initiales des instances d'un objet.
Comme premier essai, voici un constructeur qui prend x, y, width (largeur) et height (hauteur) :
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);
Cela marche sûrement, mais j'ai une autre approche à vous recommander. Plutôt que de prendre des paramètres individuels, le constructeur pourrait prendre un objet de configuration :
var Button = function(config) {
    this.x = config.x;
    this.y = config.y;
    this.width = config.width;
    this.height = config.height;
    this.label = config.label;
};
L'avantage de l'objet config, c'est qu'on peut continuer à ajouter des paramètres à récupérer au constructeur (comme label), et cela reste facile pour nous de comprendre ce que chaque paramètre fait à la construction d'un bouton :
var btn1 = new Button({
    x: 100, y: 100,
    width: 150, height: 50,
    label: "Please click!"});
Mais nous pouvons pousser encore un cran plus loin. Et si la plupart des boutons avaient les mêmes largeur et hauteur ? Nous n'aurions pas à spécifier des paramètres de largeur et de hauteur pour chaque bouton, sauf en cas de nécessité. Nous pouvons faire en sorte que le constructeur teste si la propriété est définie dans l'objet config, et définisse une valeur par défaut en cas de réponse négative. Comme ceci :
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";
};
On peut maintenant l'appeler sans renseigner toutes les propriétés, car les autres seront définies avec leurs valeurs par défaut :
var btn1 = new Button({x: 100, y: 100, label: "Veuillez cliquez ici !"});
Tout ces efforts pour un constructeur, hein ? Mais cela vaut le coup, garanti !
Maintenant que nous possédons un constructeur en béton (en bouton ?), définissons un comportement : la méthode draw. Elle va avoir le même code que la fonction drawButton, mais elle récupérera les propriétés sur this, puisqu'elle est définie sur l'objet prototype lui-même :
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);
};
Une fois qu'on l'a définie, nous pouvons l'appeler comme cela :
btn1.draw();
Voici un programme qui utilise l'objet Button pour créer 2 boutons (remarquez comme il est aisé de créer et dessiner plusieurs boutons) :
Nous avons esquivé la partie difficile, cependant : le traitement des clics de souris. Nous pouvons commencer par définir une fonction sur le prototype de Button qui renverra true (vrai) si l'utilisateur clique à l'intérieur des frontières d'un bouton en particulier. Une fois encore, celle-ci sera identique à notre fonction précédente, mais elle récupérera les propriétés sur this plutôt qu'à partir d'un objet passé en argument :
Button.prototype.isMouseInside = function() {
    return mouseX > this.x &&
    mouseX < (this.x + this.width) &&
    mouseY > this.y &&
    mouseY < (this.y + this.height);
};
Maintenant, nous pouvons l'utiliser à l'intérieur de la fonction mouseClicked :
mouseClicked = function() {
    if (btn1.isMouseInside()) {
        println("You made the right choice!");
    } else if (btn2.isMouseInside()) {
        println("Yay, you picked me!");
    }
};
Essayez le code ci-dessous, en cliquant sur chacun des boutons :
Mais il y a quelque chose qui m'agace dans la façon dont nous avons géré ce traitement de clic de souris. Le principe de la programmation orientée objet est d'empaqueter tous les comportements en relation avec un objet à l'intérieur de l'objet, et d'utiliser des propriétés pour les rendre personnalisables. Or, nous avons laissé des comportements en dehors de l'objet : les println à l'intérieur de mouseClicked :
mouseClicked = function() {
    if (btn1.isMouseInside()) {
        println("You made the right choice!");
    } else if (btn2.isMouseInside()) {
        println("Yay, you picked me!");
    }
};
Ces instructions d'affichage doivent, d'une manière ou d'une autre, être mieux reliées à chaque bouton, comme quelque chose qu'on pourrait passer dans le constructeur. Juste en s'adaptant à l'organisation actuelle, nous pourrions décider de passer un message dans la config du constructeur, et définir une fonction handleMouseClick pour l'afficher dans la console.
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();
};
C'est beaucoup mieux, car maintenant tout ce qui est associé au comportement spécifique d'un bouton est pris en main par le constructeur. Mais c'est aussi exagérément simple. Et si nous voulions faire quelque chose au-delà de simplement afficher un message, comme dessiner quelques formes ou changer de scène, quelque chose qui prendrait plusieurs lignes de code ? Dans ce cas, nous souhaiterions apporter au constructeur plus qu'une chaîne de caractères (en fait un morceau de code). Comment pouvons-nous passer une morceau de code ?
... Avec une fonction ! En JavaScript (mais pas dans tous les langages), nous pouvons passer des fonctions en paramètres de fonctions. C'est utile dans beaucoup de situations, et particulièrement utile lorsqu'on définit le comportement d'éléments interface utilisateur comme les boutons. Nous pouvons demander au bouton : "Eh, tu vois cette fonction ? C'est un morceau de code que je veux que tu appelles quand l'utilisateur clique sur le bouton." On se réfère à ces fonctions comme des fonctions de "rappel" (callback en anglais) car elles ne seront pas appelées tout de suite. Elles seront "rappelées" au moment opportun.
Nous pouvons commencer par passer un paramètre onClick qui est une fonction :
var btn1 = new Button({
    x: 100,
    y: 100,
    label: "Please click!",
    onClick: function() {
        text("You made the right choice!", 100, 300);
    }
});
Puis nous devons veiller à ce que notre constructeur définisse une propriété onClick s'accordant à ce qui a été passé. Par défaut, en cas où aucune fonction onClick n'a été passée, on va juste créer une fonction "no-op" (une fonction qui ne réalise "aucune opération" du tout. Elle est juste là pour que nous puissions l'appeler et éviter un message d'erreur) :
var Button = function(config) {
    // ...
    this.onClick = config.onClick || function() {};
};
Enfin, il nous faut réellement rappeler la fonction de rappel quand l'utilisateur clique sur le bouton. C'est en fait très simple (il suffit juste de l'appeler en écrivant le nom de la propriété que nous avons enregistré et ajouter des parenthèses vides) :
Button.prototype.handleMouseClick = function() {
    if (this.isMouseInside()) {
        this.onClick();
    }
};
Et voilà, c'est terminé ! Nous avons un objet Button avec lequel nous pouvons aisément créer de nouveaux boutons, chacun avec un aspect différent et répondant différemment aux événements de clics de souris. Cliquez un peu partout dans l'exemple ci-dessous, et regardez ce qui se passe si vous modifiez les paramètres des boutons :
Maintenant que vous avez à disposition ce modèle, vous pouvez personnaliser vos boutons sur d'autres points, comme la couleur, ou faire qu'ils répondent à d'autres événements, comme le survol de la souris. Essayez dans vos programmes !

Vous souhaitez rejoindre la discussion ?

  • blobby green style l'avatar de l’utilisateur Jasmin Dion
    Bonjour, je ne comprend pas ce qu'il faut mettre dans cette partie de code(ou il y a des ?) quelqu'un pourrais m'aider? je suis bloquer au défi des lapins de course

    onClick: function(){
    rabbits[?].(?);
    (2 votes)
    Default Khan Academy avatar l'avatar de l’utilisateur
  • blobby green style l'avatar de l’utilisateur Lucie Faire
    Bonjour,
    Comment fait-on pour enlever le contour autour du bouton. Sur le défi Lapin j'ai tenté un noStroke(); et testé un stroke:nostroke(), qu'il ne connait pas.
    A ce propos j'ai dû louper un truc dans les précédentes vidéos mais les déclarations type x:..., je ne comprends pas cette syntaxe avec les : et la , qui change du = et ;
    (1 vote)
    Default Khan Academy avatar l'avatar de l’utilisateur
  • blobby green style l'avatar de l’utilisateur Nawan
    Très intéressant.
    Par ailleurs, je remarque que lorsqu'on clique plusieurs fois les boutons, les textes "You made the right choice!" et "Yay, you picked me!" deviennent de plus en plus gras. Or, ce comportement n'est pas prévu dans le code.
    Quelqu'un pourrait-il m'aider à comprendre ce comportement ?

    Merci d'avance et désolé si ma question n'est pas subtile.
    (1 vote)
    Default Khan Academy avatar l'avatar de l’utilisateur
Vous comprenez l'anglais ? Cliquez ici pour participer à d'autres discussions sur Khan Academy en anglais.