TwinXeon by Renaudet
/Accueil/Tous les articles
Une librairie Javascript AWT-like basée sur les canvas HTML5
Introduction

Avec des DIV, du javascript et les feuilles de style CSS, on peut réaliser à peu près n'importe quoi. Cependant, une certaine connaissance du DOM, de HTML, des formulaires HTTP etc. est nécessaire.
Il existe bien sûr des librairies comme JQuery qui proposent pléthore de fonctions et d'objets pour réaliser des interfaces graphiques très complexes.

Comme d'habitude, j'ai préféré ré-inventer la roue, parce que c'est comme cela que l'on apprend.

Mon projet est beaucoup plus modeste et plus spécialisé. Il s'agit d'une librairie JavaScript modulaire qui permet de réaliser des interfaces graphiques pour le Virtual Desktop avec les mêmes concepts objet qu'une librairie comme AWT. Pour cela, l'idée est de re-développer un système de composants graphiques, et le canvas HTML5 est tout indiqué. Exit le HTML !

Avantages ? je ne sais pas s'il y en a en fait. Mais le concept est très clairement de réaliser des interfaces de type RichUI sans coder une seule ligne de HTML. J'ai bien avancé le développement mais comme j'apprends en même temps, il peut y avoir des bugs dans les premiers composants, car je modifie ma technique de codage au fur et à mesure de l'implémentation du projet.

Une librairie modulaire

La librairie de base est /script/twinxeon-graphics.js. Une application graphique pourra déclarer sa dépendance à cette librairie via l'instruction suivante (un peu comme un import du langage Java) :

loadLibrary('/scripts/twinxeon-graphics.js');

La librairie est modulaire. Aussi, l'application n'importe que les composants dont elle a effectivement besoin. Par exemple, si l'application requiert l'utilisation de composants Boutons, on ajoute une dépendance à la librairie ad-hoc :

loadLibrary('/scripts/twinxeon-graphics.js');
loadLibrary('/scripts/twinxeon-graphics-button.js');

Certains composants composites comme le ScrollableEditText déclarent eux-même des dépendances à d'autres librairies, ce qui fait qu'il n'est pas nécessaire par exemple d'ajouter une dépendance à la librairie twinxeon-viewport.js si l'on utilise un ViewPort et un ScrollableEditText.

Principe de base

L'objet de base est un composant GraphicContainer. C'est ce composant qui va créer le canvas HTML5 et enregistrer les gestionnaires d'évènement de base : onMouseDown, onMouseUp, onKeyDown etc. Notons déjà que certains gestionnaires sont déjà trop spécialisés à ce stade, et qu'il faudra les émuler. Ainsi, l'événement onMouseEnter ne sera reçu qu'une seule fois par le canvas, mais si l'on développe des composants virtuels sur la base de ce canvas, on souhaitera bien évidemment pouvoir réagir à un événement onMouseEnter pour chaque sous-composant ajouté !

On créera le GraphicContainer au sein d'une fonction spécialisée, invoquée depuis la fonction callback onDisplay() de l'API UIApplication de la librairie twinxeon-mfc.js. Par exemple au sein d'une méthode createWindowArea() :

app.createWindowArea = function(rootDiv){
  var width = this.window.innerWidth;
  var height = this.window.innerHeight-24;
  this.graphicContainer =
       new GraphicContainer(this.id+'.gc',rootDiv,this.window,width,height); 
  this.graphicContainer.background = '#d0d0d0';
  this.graphicContainer.initialize();
  this.graphicContainer.draw();
}

A ce stade, tout ce que l'on obtient, c'est un fond gris initialisé avec les dimensions initiales de notre fenêtre. Si l'on re-dimensionne la fenêtre, gare aux surprises !.



Il faut donc surcharger la fonction callback onResize() afin de redimensionner notre canvas :

app.onResize = function(){
   var width = this.window.innerWidth;
   var height = this.window.innerHeight-24;
   this.graphicContainer.onResize(width,height);
}
Nous voilà maintenant avec un fonds gris, quelque soit la taille de notre fenêtre. Il nous faut maintenant y ajouter des composants.

Les composants de base

Bouton

Point d'IHM sans le traditionnel Bouton. Celui-ci héritera de la classe... Button. Associé à un label ou à une icône, le bouton aura une taille fixe ou s'étirera au gré de son conteneur parent. On associera les événements à la méthode callback onMouseDown().

Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-button.js');

Exemple de code :

var button = new Button(50,70,70,20);
button.setLabel('Push me');
button.onMouseDown = function(p){
   alert('You pushed me !');
}
this.graphicContainer.addChild(button);

Résultat :



Etiquette ou Label

Les labels font aussi partie des composants incontournables de toute IHM. Ceux-ci hériteront de la classe Label. Le texte affiché dépendra de la propriété label. On utilisera de préférence la méthode setLabel() pour le renseigner.

Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-label.js');

Exemple de code :

var label = new Label(130,70);
label.setLabel('Push it to say Hello !');
this.graphicContainer.addChild(label);

Résultat :



Attachement d'un gestionnaire d'événements au bouton pour modifier le label :

button.onMouseDown = function(p){
   label.setLabel('Hello, World !');
}

Le composant Label peut être modifié par le biais de ses propriétés : foregroundColor, backgroundColor, font, fontSize, fontStyle.

Note : cette même librairie fournit un composant Checkbox.

Edit

Notre dernier composant de base de toute interface graphique est le composant EditText qui permet d'entrer une valeur sous forme de chaîne de caractères. Celui-ci héritera de la classe EditText. La taille du composant pourra être fixe ou variable, suivant les possibilité d'expansion offertes par son parent.

Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-edit.js');

Exemple de code (le gestionnaire d'événement attaché au bouton est modifié) :

var edit = new Edit(250,70,150,20);
this.graphicContainer.addChild(edit);

button.onMouseDown = function(p){
  edit.setText('Hello, World !');
}

Résultat :



Des composants techniques

Les composants de base permettent d'implémenter les interaction utilisateur. Les composants techniques, eux, sont plus orientés look & feel. En particulier, une problématique intrinsèque à ce genre de composant est la question du placement, de l'alignement et du redimensionnement. C'est pourquoi on introduit généralement au sein des librairies de composants graphiques des objets Layout.

HorizontalLayout

Ce type de layout va placer ses composants fils selon une répartition horizontale à distribution égale. En d'autres termes, chaque composant fils du composant HorizontalLayout disposera du même espacement horizontal que ses composants frères. La taille verticale quant à elle est calquée sur la taille verticale disponible du composant parent.

Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-layout.js');

Exemple de code :

var layout = new HorizontalLayout();
var label = new Label(0,0);
label.setLabel('Push it to say Hello !');
layout.addChild(label);
var button = new Button(0,0,-1,20);
button.setLabel('Push me');
layout.addChild(button);
var edit = new Edit(0,0,150,20);
layout.addChild(edit);
this.graphicContainer.addChild(layout);

Résultat :



On remarque que le composant button, dont la largeur a été positionnée à -1, occupe exactement 1/3 de l'espace disponible en largeur. Les autres composants (le label et le composant Edit) sont alignés à gauche dans leur tiers respectif. On notera également que cette fois, le fond de la fenêtre est peint en blanc. Il s'agit de la couleur de fond par défaut du composant HorizontalLayout.

Note : un petit bug du composant Label fait qu'il est nécessaire de redimensionner la fenêtre pour voir le résultat ci-dessus.

Il existe d'autres composants Layout dans cette librairie : VerticalLayout, XYLayout, FlowLayout (en cours de développement au moment de la rédaction de cet article).

Slider

Un composant Slider est utilisé pour partager un écran en deux, généralement dans le sens vertical, sans pour autant vouloir fixer les tailles respectives des deux zones délimitées. Le soin est laissé à l'utilisateur de retailler la largeur de ces deux zones au moyen de la souris.

Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-slider.js');

Exemple de code :

var slider = new Slider();
var button = new Button(50,50,70,20);
button.setLabel('Push me');
slider.addChild(button,'right');
var label = new Label(20,55);
label.setLabel('Push it to say Hello !');
slider.addChild(label,'left');
this.graphicContainer.addChild(slider);

Résultat :



Viewport et Scrollbar

Ces composants servent à créer des composants composites évolués. On les retrouve par exemple dans le macro-composant ScrollableEditText de la librairie /scripts/twinxeon-graphics-edittext.js.

Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-viewport.js');
loadLibrary('/scripts/twinxeon-graphics-scrollbar.js');

Exemple de code : un extrait du code du ScrollableEditText

var edit = new GraphicElement(0, 0, -1, -1);
edit.textArea = new EditText(0,0,-1,-1);
edit.viewport = new Viewport(0,0,10,10,10,1000);
edit.scrollbar = new VerticalScrollbar(0,0,20,10);
edit.viewport.addChild(edit.textArea);
edit.addChild(edit.viewport);
edit.addChild(edit.scrollbar);
edit.scrollbar.onPositionChanged = function(deltay){
    var pos = edit.scrollbar.getPosition();
    edit.viewport.scrollVerticalyToPosition(pos);
}
edit.textArea.onContentChanged = function(){
    var textHeight = edit.textArea.getTextHeight();
    if(textHeight>edit.viewport.virtualSize.y){
        edit.viewport.setVirtualSize(new Point(edit.viewport.virtualSize.x,textHeight*1.2));
     }
}


Résultat (l'éditeur de texte brut du Virtual Desktop):


CSGateway

La 'passerelle client/serveur' n'est pas un composant graphique, mais je l'ai intégré à cette librairie. Il s'agit d'un module facilitant l'envoie de requêtes AJAX vers le serveur, et plus particulièrement de requêtes à destination du module d'exécution de diagrammes d'état côté serveur.

Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-cs.js');

Exemple de code :

app.gateway = new CSGateway();
[...]
button1.onMouseDown = function(p){
   var request = app.gateway.createRequest('/testDiagram.sd');
   request.addParameter('param',edit1.getText());
   request.onServerResponse = function(txt){
      edit2.setText(txt);
   }
   request.execute();
}

Le mot de la fin

Voilà, je termine cette présentation en rappelant que cette librairie est toujours en cours de développement. Je dois encore créer des composants de base comme les radio-bouttons, les images ou les jauges de progression, et des composants composites comme les listes, les arbres, les tableaux... La librairies est déjà disponible depuis n'importe quelle session du Virtual Desktop. Cependant, il faut une session authentifiée pour pouvoir modifier le menu utilisateur, et donc lancer des applications.
Je vais peut-être finalement me lancer dans l'implémentation d'un atelier de développement pour le Virtual Desktop, de manière à générer le squelette du code pour une application fenêtrée, et peut-être même permettre l'édition des menus. Avec ça, j'espère susciter des vocations de développeur pour mon Virtual Desktop  !

[Edit du 23/04/2013]

Cursor

Les curseurs sont utilisés généralement pour faire varier une grandeur de manière continue, comme le volume d'un poste radio, ou par paliers fixés, selon un intervalle de définition bien défini. Ce sont des composants importants des interfaces graphiques d'administration par exemple.


Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-cursor.js');

Exemple de code :

var label1 = new Label(10,10);
label1.setLabel('Value: ');
this.graphicContainer.addChild(label1);

var cursor = new HorizontalCursor(10,40,400,35);
cursor.divisionCount = 50;
cursor.onPositionChanged = function(pos){
   label1.setLabel('Value: '+pos);
}
this.graphicContainer.addChild(cursor);

Résultat :



TabPane

Un composant TabPane permet d'utiliser une même zone de l'interface graphique pour ranger des composants par thème. Très utilisés pour les interfaces de configuration, ils permettent de regrouper des composants par grandes fonctions, et donc de réaliser des interfaces graphiques plus compactes. Un composant TabPane aura une propriété selectedTab représentant le sous-groupe de composants sélectionné par l'utilisateur.

Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-tabpane.js');

Exemple de code :

var tabpane = TabPane(10,10,450,300);
tabpane.addTab('Première Tab');
tabpane.addTab('Seconde Tab');
tabpane.addTab('Troisième Tab');
tabpane.addTab('Dernière Tab');
var button = new Button(50,30,80,20);
button.setLabel('Push me');
tabpane.addTabChild(button,'Seconde Tab');
this.graphicContainer.addChild(tabpane);
Résultat :



BarChartDiagram

Ce composant est encore un prototype. il est destiné à mon outil de monitoring du serveur TwinXeon, mais je l'ai rendu suffisamment générique pour pouvoir être utilisé dans d'autres applications. Il s'agit d'une version simplifiée du composant Diagram, lui aussi destiné à mon outil de monitoring.

Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-diagram.js');

Exemple de code :

var diagram = new BarChartDiagram(0,0,-1,-1);
diagram.maxGridYDiv = 5;
diagram.maxGridXDiv = 50;
diagram.title = 'This diagram Title';
var data = new Array();
data.push(0);
data.push(0);
data.push(10);
data.push(11);
  [...]
data.push(72);
data.push(72);
data.push(73);
data.push(75);
data.push(78);
data.push(82);
diagram.setData(data);
tabpane.addTabChild(diagram,'Dernière Tab');

Résultat (ici représenté dans un composant TabPane) :



Exemple avec un composant Diagram :



Ce dernier composant Diagram peut afficher la moyenne actualisée des valeurs représentées (propriété displayAverage), et, par survol du graphique avec la souris, la valeur de la courbe en un point. Il est représenté dans la copie d'écran ci-dessus au sein d'une interface dynamique à double Slider.

[Edit du 24/04/2013]

Jauge

Les composants Jauge sont très utilisés pour réaliser des indicateurs de progression. On peut également les utiliser pour représenter visuellement une grandeur. Ce composant possède les attributs suivants : minimumValue, maximumValue et bien sûr value.

Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-jauge.js');

Exemple de code :

var jaugeR = new HorizontalJauge(20,30,350,20);
jaugeR.foregroundColor = '#ff0000';
jaugeR.minimumValue = 0;
jaugeR.maximumValue = 255;
jaugeR.value = 128;
this.graphicContainer.addChild(jaugeR);

Résultat (l'exemple de code se réfère à la première barre de progression) :



[Edit du 26/04/2013]

RadioGroup

Très utilisé également pour les interfaces de configuration, boîtes de dialogue etc., ce composant permet de regrouper plusieurs RadioButton dans un groupe qui organise la sélection unique.

Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-radio.js');

Exemple de code :

var group = new RadioGroup('My Group',10,10,200,85);
group.addOption('Choix 1');
group.addOption('Choix 2');
group.addOption('Choix 3');
group.onSelectionChanged = function(selection){
   alert('Selection: '+selection);
}
this.graphicContainer.addChild(group);

Résultat :



[Edit du 20/05/2013]

List

Les composants List permettent de gérer des listes de valeur et la sélection d'un item particulier de la liste. On peut s'en servir pour gérer une liste dynamique d'items (la taille de la liste variera donc potentiellement au cours de la session utilisateur), ou alors pour fournir une source de sélection dans une liste récupérée dynamiquement.

Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-list.js');

Exemple de code :

var edit1 = new Edit(10,10,150,20);
gc.addChild(edit1);

var list = new List(10,35,150,130);
var items = new Array();
items.push('Item 1');
items.push('Item 2');
items.push('Item 3');
items.push('Item 4');
items.push('Item 5');
items.push('Item 6');
items.push('Item 7');
items.push('Item 8');
items.push('Item 9');
items.push('Item 10');
items.push('Item 11');
items.push('Item 12');
items.push('Item 13');
items.push('Item 14');
items.push('Item 15');
list.setItems(items);
gc.addChild(list);

list.onSelectionChanged = function(selectionIndex){
   var text = list.selectedValue();
   edit1.setText(text);
}

Résultat :


ColorPicker

Un composant ColorPicker est uniquement destiné à sélectionner une couleur. Celui-ci est basé sur une matrice HD et permet donc de sélectionner des teintes subtiles.

Déclaration de dépendance :

loadLibrary('/scripts/twinxeon-graphics-color.js');

Exemple de code :

var colorPicker = new ColorPicker(10,10,500,300);
gc.addChild(colorPicker);
var edit1 = new Edit(10,320,500,20);
gc.addChild(edit1);
var canvas = new Canvas(520,10,100,300);
canvas.backgroundColor = 'rgba(128,128,128,1.0)';
gc.addChild(canvas);
colorPicker.onValueChanged = function(selection){
    edit1.setText(selection);
    canvas.setBackgroundColor(selection);
}

Résultat :




SpinEdit

Les composants SpinEdit permettent de sélectionner une valeur numérique sans avoir besoin de la saisir au clavier. Ils gèrent éventuellement une valeur minimum et maximum et la valeur d'incrément est configurable. Ce composant ne permet pas encore la saisie directe au clavier

Déclaration de dépendance :
loadLibrary('/scripts/twinxeon-graphics-spin.js');
Exemple de code :
var spin = new SpinEdit(10,10,80,20);
spin.incrementationValue = 10;
gc.addChild(spin);
Résultat :








Ajoutez votre commentaire :
  Votre pseudo :
  Votre adresse mail (obligatoire):
  Votre commentaire :
 
Site optimisé pour un affichage en 800x600 sous Firefox 8.x - ©Copyright 2011-2012 by Nicolas Renaudet