Chapitre 10. Introduction à la programmation de scripts

Table des Matières

1. Introduction
2. La balise <script>
2.1. Spécificités de Javascript
2.2. Caractères XML dans du script
3. Fonctions
4. Méthodes
4.1. Utilisation de Script pour manipuler les attributs
5. Adressage
6. Méthodes et paramètres
7. Utilisation des attributs pour réduire la complexité

1. Introduction

Les applications LZX peuvent contenir du code procédural, comme décrit au Chapitre 2, Préliminaires sur le language, ainsi que du code déclaratif sous forme de balises XML.

A l'intérieur d'un programme LZX, du script peut être ajouté :

  • entre les balises <script>

  • entre les balises <method>

  • entre guillemets en tant que valeur d'un attribut

2. La balise <script>

La façon la plus rapide pour inclure du script est d'utiliser la balise <script>. Le code placé entre les balises script est exécuté immédiatement. La balise <script> peut seulement être ajoutée à la balise <canvas>.

[Note] Note

Dans le programme ci-dessous, remarquez l'attribut debug="true", qui lance le débogueur OpenLaszlo. Dans ce tutorial nous allons utiliser la sortie du débogueur pour illustrer certains concepts de l'écriture de scripts.

Exemple 10.1. Utilisation du débogueur

<canvas width="500" height="200" debug="true">
  <script>
    Debug.write("Hello, World!");
  </script>
</canvas>

Nous pouvons maintenant faire appel à nos connaissances JavaScript :

Exemple 10.2. Ajout de code JavaScript

<canvas width="500" height="200" debug="true">
  <script>
    // some random scripting
    //
    var someStr = "Hello, World!";
    Debug.write(someStr);
    var first = 4;
    var second = 3;
    var result = first + second;
    Debug.write(result);
   </script>
</canvas>

2.1. Particularités de Javascript

Si nous n'avez jamais travaillé avec JavaScript, vous pourriez être surpris par certaines spécificités du langage. Par exemple, considérez cette petite extension du programme précédent :

Exemple 10.3. Subtilités de JavaScript

<canvas width="500" height="200" debug="true">
  <script>
    // some random scripting
    //
    var someStr = "Hello, World!";
    Debug.write(someStr);
    var first = 4;
    var second = 3;
    var result = first + second;
    Debug.write(result);
    Debug.write(first + " plus " + second + " is " + first + second);
    var two = 2;
    if (2 == two) {
      Debug.write("2 is two");
    }
  </script>
</canvas>

Tout est parfait, sauf pour la ligne "4 plus 3 is 43". Quatre plus trois devrait valoir 7, comme dans la ligne juste au dessus, non ? Ce qui s'est passé est que nous avons concaténé des chiffres avec des chaînes (4 + " plus " + 3), et qu'ainsi les nombres ont été convertis en chaînes. 

La documentation Laszlo n'a pas pour vocation d'être une étude détaillée du langage JavaScript. Certains chapitres suivants abordent des notions avancées, mais nous vous recommandons de garder sous la main une référence JavaScript. 

2.2. Caractères XML dans du script

Prenons une simple boucle "for" :

Exemple 10.4. Les caractères XML génèrent des erreurs

<canvas height="80" width="500" debug="true">
  <-- the following code will not compile because of the angle bracket -->
  <script>
    for (var i = 0; i < 11; i++) {
      Debug.write(i);
    }
  </script>
</canvas>

Zut !

Comme LZX est un langage XML, il y a des règles sur ce qu'on peut placer entre les balises (particulièrement entre les balises <script>). Le caractère "<" dans la boucle for génère une erreur. Il n'est pas possible d'utiliser les caractères "<" et ">" entre les balises LZX.

Heureusement, il y a des solutions pour contourner le problème. 

[Note] Note

Les deux exemples qui suivent ne sont pas interactifs, mais sont des programmes LZX corrects

Exemple 10.5. Encodage des caractères XML

<canvas height="120">
   <script>
     for (var i = 0; i &lt; 11; i++) {
       Debug.write(i);
     }
   </script>
</canvas>

Exemple 10.6. Utilisation de CDATA

<canvas height="120">
   lt;script>
     <![CDATA[
     for (var i = 0; i < 11; i++) {
       Debug.write(i);
     }
     ]]>
   </script>
</canvas>

Les deux solutions ci-dessus fonctionnent. C'est vous qui choisissez celle que vous préférez. La méthode CDATA est probablement la plus pratique pour les grands blocs de code.

3. Fonctions

Vous pouvez définir des fonctions entre les balises <script>.

Exemple 10.7. Fonctions Javascript

<canvas height="200" width="500" debug="true">
  <script>
    <![CDATA[
    // write a word to the debugger
    //
    function writeAWord(someWord) {
      Debug.write(someWord);
    }
    
    writeAWord("HELLO!");
    ]]>
  </script>
</canvas>

Les fonctions sont globales pour tout le document LZX et suivent les mêmes règles de portée qu'avec JavaScript.

4. Méthodes

Les méthodes sont assez similaires aux fonctions. Elles contiennent des blocs de code entre les balises <method>, et sont associées à des classes particulières. Au Chapitre 8, Intoduction aux éléments Multimédias et Artistiques, nous avons vu quelques méthodes de la classes LzView pour commander des ressources :

  • play()

  • stop()

  • getOffset()

  • getMaxOffset()

Approfondissons le concept de méthodes à travers l'exemple d'une fenêtre. L'élément fenêtre ( <window>) est en fait une vue, comme nous l'avons remarqué précédemment. Les vues possèdent des méthodes. Comme la classe window étend la classe LzView, cela implique que les fenêtres héritent de tous les attributs et méthodes des vues.

Exemple 10.8. Une méthode simple

<canvas height="200" width="500">
  <window x="20" y="20" width="150"
          title="Simple Window" resizable="true">
    <button text="My button" 
            onclick="this.parent.setAttribute('title', 'You clicked it');"/>
  </window>
</canvas>

Décomposons le code :

    onclick="this.parent.setTitle('You clicked it');"

D'abord, nous avons :

    onclick=

Comme tous les attributs on[événement], celui-ci reçoit une expression JavaScript qui sera exécutée dans le contexte de l'objet quand l'événement sera reçu.

La partie suivante :

    this.parent

est une référence à un objet. En JavaScript, la portée est généralement globale à moins que vous ne le précisiez. Cela signifie que toute classe, variable et méthode d'instance doivent être précédées du mot-clé this. En ce qui concerne 'parent', disons pour commencer que le système de gestion des vues de lzx affecte à chaque vue une variable 'parent' qui pointe sur le père de la vue dans la hiérarchie. Les hiérarchies de vues sont décrites en détails au Chapitre 23, Vues.

Maintenant, nous allons appeler une méthode. A très peu d'exceptions près, les balises d'un fichier lzx correspondent aux objets du système de vues pendant l'exécution. En utilisant XML, nous pouvons configurer ces objets en affectant des attributs et en plaçant des noeuds fils. En utilisant du script, nous pouvons appeler leurs bibliothèques. D'après la documentation, nous voyons que <window> possède une méthode setTitle() pour changer le titre de la fenêtre avec le texte passé en argument.

La dernière chose à noter est l'utilisation de cotes simples à l'intérieur des appels de fonctions.

4.1. Utilisation de Script pour manipuler les attributs

Souvenez-vous que window hérite de la classe LzView. Cela signifie que chaque fenêtre possède tous les attributs de <view>. Voici un exemple de script pour manipuler quelques-uns des attributs.

Exemple 10.9. Manipulation des attributs de <view>

<canvas width="500" height="200">
  <window x="100" y="30" 
          title="Window 2" name="windowTwo">
    <text>This is the second window.</text>
  </window>
  <window x="20" y="20" width="150"
          title="Simple Window">
    <button text="My button" 
            onclick="this.parent.parent.windowTwo.setAttribute('x', 200)"/>
  </window>
</canvas>

Nous partons ici de l'exemple précédent. Plutôt que de remonter au parent du bouton, nous remontons de deux niveaux, puis redescendons d'un. this.parent.parent référence la toile, et nous pointons sur 'Window 2' en utilisant son nom (name).

Nous utilisons également la méthode setAttribute() qui prend deux arguments : l'attribut à modifier et la valeur à lui donner.

Maintenant, cherchons un moyen de faire bouger la fenêtre 'Window 2' de façon à voir ce qu'il y a derrière, mais sans la déplacer à la souris. Appuyer sur le bouton deux fois ne sert à rien car il se contente de réinitialiser l'attribut x à une valeur fixe (200px).

A la place, nous avons besoin de récupérer où se trouve la seconde fenêtre, puis de faire incrémenter sa position à chaque fois qu'on clique sur le bouton. En regardant de nouveau le contenu de la classe LzView, nous remarquons une méthode getAttribute() avec laquelle nous pouvons récupérer la valeur de l'attribut x.

Nous pouvons donc écrire :

Exemple 10.10. Récupérer des attributs

<canvas width="500" height="200">
  <window x="100" y="30" 
          title="Window 2" name="windowTwo">
    <text>This is the second window.</text>
  </window>
  <window x="20" y="20" width="150"
          title="Simple Window">
    <button text="My button" 
            onclick="this.parent.parent.windowTwo.setAttribute('x',
            this.parent.parent.windowTwo.getAttribute('x') + 20)"/>
  </window>
</canvas>

Cela fonctionne, mais le code devient compliqué. Il vaudrait mieux regrouper le code dans un bloc que nous appellerions quand on clique sur le bouton. Pour faire cela, nous pourrions écrire une fonction :

Exemple 10.11. Déplacement de fenêtre par appel de fonction

<canvas width="500" height="200">
  <script>
    // Moves the second window twenty pixels to the right
    //
    function moveWindow() {
      var increment = 20;
      var originalX = canvas.windowTwo.getAttribute('x');
      var newX = originalX + increment;
      canvas.windowTwo.setAttribute('x', newX);
    }
  </script>
  <window x="100" y="30" 
          title="Window 2" name="windowTwo">
    <text>This is the second window.</text>
  </window>
  <window x="20" y="20" width="150"
          title="Simple Window">
    <button text="My button" 
              onclick="moveWindow();"/>
  </window>
</canvas>

Remarquez la syntaxe "canvas." pour pointer sur la seconde fenêtre. Nous devons référencer la vue de façon absolue. Ce code est beaucoup plus facile à comprendre car nous pouvons le séparer en plusieurs lignes, le commenter et utiliser des variables avec des noms explicites.

Cependant, la fonction est vraiment séparée du bouton. Une façon plus élégante de faire serait d'écrire une méthode du bouton.

Exemple 10.12. D'une fonction à une méthode

<canvas width="500" height="200">
  <window x="100" y="30" 
          title="Window 2" name="windowTwo">
    <text>This is the second window.</text>
  </window>
  <window x="20" y="20" width="200"
          title="Simple Window">
    <button text="My button" onclick="this.moveWindow()">
      <!-- Moves the second window twenty pixels 
           to the right -->
      <method name="moveWindow">
        var increment = 20;
        var originalX = this.parent.parent.windowTwo.getAttribute('x');
        var newX = originalX + increment;
        this.parent.parent.windowTwo.setAttribute('x', newX);
      </method>
    </button>
  </window>
</canvas>

Comme les méthodes ne sont pas globales, nous devons les référencer de façon relative. Dans le cas du bouton, nous avons utilisé this.moveWindow(). En théorie, nous pourrions avoir un second bouton qui une fois déclenché appellerait une méthode du premier bouton. La seule différence serait l'adressage. Avant d'aller plus loin avec les méthodes, voyons de plus près l'adressage.

5. Adressage

Avec LZX, les objets peuvent avoir un nom (name) et/ou un identifiant (id) avec lequel ils peuvent être référencés. Un nom (name) doit être référencé de façon locale, il peut donc y avoir plusieurs vues avec le même nom dans un fichier (ils ne doivent simplement pas avoir le même père). Un identifiant (id) est global, donc il ne peut pas y avoir deux vues avec le même identifiant (id) dans un fichier LZX.

Pour revenir à notre bouton qui doit appeler la méthode d'un autre bouton :

Exemple 10.13. Un bouton qui appelle une méthode d'un autre bouton

<canvas width="500" height="200">
  <window x="100" y="60" 
          title="Window 2" name="windowTwo">
    <text>This is the second window.</text>
  </window>
  <window x="20" y="20" width="210"
          title="Simple Window">
    <simplelayout axis="x" spacing="4"/>
    <button text="My button" name="button1" 
            onclick="this.moveWindow()">
      <!-- Moves the second window twenty pixels 
           to the right -->
      <method name="moveWindow">
        var increment = 20;
        var originalX = this.parent.parent.windowTwo.getAttribute('x');
        var newX = originalX + increment;
        this.parent.parent.windowTwo.setAttribute('x', newX);
      </method>
    </button>
    <button text="Bigger Button" 
            onclick="this.parent.button1.moveWindow()"/>
  </window>
</canvas>

Les deux boutons produisent maintenant un déplacement de la fenêtre. Cependant, c'est source de confusion que d'avoir un bouton appelant une méthode d'un autre bouton. Puisque la fenêtre 'Window2' est celle qui bouge, pourquoi ne pas faire de la méthode une méthode de la fenêtre, et avoir les deux boutons pointer dessus ? Plutôt que d'utiliser la syntaxe this.parent…, nous pouvons lui donner un identifiant id, pour la référencer et y accéder de façon globale :

Exemple 10.14. Utilisation de la balise <ID>

<canvas width="500" height="200">
  <window x="100" y="60" 
          title="Window 2" name="windowTwo" id="windowTwoId">
    <!-- Moves the second window twenty pixels 
         to the right -->
    <method name="moveWindow">
      var increment = 20;
      var originalX = this.getAttribute('x');
      var newX = originalX + increment;
      this.setAttribute('x', newX);
    </method>
    <text>This is the second window.</text>
  </window>
  <window x="20" y="20" width="210"
      title="Simple Window">
    <simplelayout axis="x" spacing="4"/>
    <button text="My button" name="button1" 
            onclick="windowTwoId.moveWindow()"/>
    <button text="Bigger Button" 
            onclick="this.parent.parent.windowTwo.moveWindow()"/>
  </window>
</canvas>

Juste pour illustrer l'utilisatoin de id et name, un bouton référence la fenêtre de façon relative en utilisant name, et l'autre de façon globale en utilisant id. Remarquez que les noms de id et name auraient pu être les mêmes, mais nous avons choisi de les différencier dans cet exemple.

6. Méthodes et arguments

Dans l'exemple précédent, nous avons deux boutons qui font la même chose. Pourquoi ne pas leur faire faire des choses différentes ? Déplacer la fenêtre vers la droite ou vers la gauche par exemple. Nous pourrions écrire une nouvelle méthode pour déplacer la fenêtre vers la gauche, mais il serait plus élégant d'utiliser la même méthode pour les deux directions. De la même façon qu'avec les fonctions, nous pouvons passer des arguments aux méthodes.

Voici une solution possible :

Exemple 10.15. Méthodes et arguments

<canvas width="500" height="200">
  <window x="100" y="60" 
          title="Window 2" name="windowTwo" id="windowTwoId">
    <!-- Moves the second window twenty pixels 
         in specified direction -->
    <method name="moveWindow" args="direction">
      // decide which direction to go
      if (direction == "left") {
       var increment = -20;
      } else if (direction == "right") {
        var increment = 20;
      }
      var originalX = this.getAttribute('x');
      var newX = originalX + increment;
      this.setAttribute('x', newX);
    </method>
    <text>This is the second window.</text>
  </window>
  <window x="20" y="20" width="210"
      title="Simple Window">
    <simplelayout axis="x" spacing="4"/>
    <button text="Move Left" name="button1" 
            onclick="windowTwoId.moveWindow('left')"/>
    <button text="Move Right" 
            onclick="windowTwoId.moveWindow('right')"/>
  </window>
</canvas>

Nous pouvons passer plus d'un argument à une méthode, comme pour les fonctions :

Example 10.16. Passing multiple arguments to a method

<canvas width="500" height="200">
  <window x="100" y="60"
          title="Window 2" name="windowTwo" id="windowTwoId">
    <!-- Moves the second window twenty pixels 
         in specified direction -->
    <method name="moveWindow" args="direction, distance">
      // decide which direction to go
      if (direction == "left") {
        var increment = -1 * distance;
      } else if (direction == "right") {
        var increment = distance;
      }
      var originalX = this.getAttribute('x');
      var newX = originalX + increment;
      this.setAttribute('x', newX);
    </method>
    <text>This is the second window.</text>
  </window>
  <window x="20" y="20" width="300"
      title="Simple Window">
      <simplelayout axis="x" spacing="4"/>
      <button text="Left 2" name="button1" 
              onclick="windowTwoId.moveWindow('left', 2)"/>
      <button text="Left 20" name="button2" 
              onclick="windowTwoId.moveWindow('left', 20)"/>
      <button text="Right 20" 
              onclick="windowTwoId.moveWindow('right', 20)"/>
      <button text="Right 2" 
              onclick="windowTwoId.moveWindow('right', 2)"/>
  </window>
</canvas>

7. Utilisation d'attributs pour réduire la complexité

A partir du même exemple, voyons comment réduire le nombre d'arguments passés. Ici, nous plaçons la fonctionnalité dans le bouton lui-même :

Exemple 10.17. Complément sur les attributs

<canvas width="500" height="200">
  <window x="100" y="60"
        title="Window 2" name="windowTwo" id="windowTwoId">
    <!-- Moves the second window specified number of pixels 
         in specified direction -->
    <method name="moveWindow" args="buttonObj">
      direction = buttonObj.getAttribute("direction");
      distance = parseInt(buttonObj.getAttribute("distance"));
      // decide which direction to go
      if (direction == "left") {
       var increment = -1 * distance;
      } else if (direction == "right") {
        var increment = distance;
      }
      var originalX = this.getAttribute('x');
      var newX = originalX + increment;
      this.setAttribute('x', newX);
    </method>
    <text>This is the second window.</text>
  </window>
  <window x="20" y="20" width="300"
      title="Simple Window">
    <simplelayout axis="x" spacing="4"/>
    <button onclick="windowTwoId.moveWindow(this)">Left 2
      <attribute name="direction" value="'left'"/>
      <attribute name="distance" value="'2'"/>
    </button>
    <button onclick="windowTwoId.moveWindow(this)">Left 20
      <attribute name="direction" value="'left'"/>
      <attribute name="distance" value="'20'"/>
    </button>
    <button onclick="windowTwoId.moveWindow(this)">Right 20
      <attribute name="direction" value="'right'"/>
      <attribute name="distance" value="'20'"/>
    </button>
    <button onclick="windowTwoId.moveWindow(this)">Right 2
      <attribute name="direction" value="right" type="string"/>
      <attribute name="distance" value="'2'"/>
    </button>
  </window>
</canvas>

Les boutons ont des attributs que nous avons appelés "distance" et "direction". Les valeurs de ces attributs doivent être entourées de guillemets pour être des expressions JavaScript et non une chaîne de caractères ou un nombre. C'est pouquoi nous avons value="''".

Nous aurions aussi pu préciser type="string" pour la balise <attribute>, comme pour le bouton "right 2". Dans ce cas, le mot "right" n'a pas besoin de cotes simples supplémentaires.

Cet exemple est en fait plus long que le précédent, mais il démontre la puissance de la programmation orientée objet avec LZX. Si le nombre d'attributs devait augmenter, et si les différents boutons avaient des attributs différents, le code continuerait de rester très clair.