DojoToolkit - un container avec gestion fluide de la mise en page
Principe
Le container HVLayout permet de mettre en page des éléments enfants, horizontalement ou verticalement, en gérant les dimensions de chacun des enfants, soit en valeur absolue, soit de manière proportionnelle en automatique.
Par exemple :
<div id=''hlayout1'' mode=''horizontal'' dojotype=''mioga.HVLayout''>
<div id=''col1'' width=''30%'' dojotype=''dijit.layout.ContentPane''>
Col1
</div>
<div id=''col2'' width=''auto'' dojotype=''dijit.layout.ContentPane''>
Col2
</div>
<div id=''col2'' width=''200px'' dojotype=''dijit.layout.ContentPane''>
Col2
</div>
</div>
permet de définir une organisation horizontale avec une première colonne prenant 30% de la taille du parent, la deuxième utilisant tout l'espace disponible restant et la dernière étant fixe à 200 pixels.
Un container HVLayout occupe toute la place que lui donne son parent et donc tente d'optimiser la place occupée par les enfants.
Dans la suite de l'article, nous estimons acquis les principes d'écriture d'objets Dojo. Dans le cas contraire, une lecture des articles sur le sujet sera certainement nécessaire.
Nota bene : cet exemple provient du projet Mioga2 qui utilise DojoToolkit .
L'objet HVLayout utilise trois attributs supplémentaires :
- mode : 'horizontal' ou 'vertical' pour donner l'orientation de la mise en page,
- width : exprimée en pourcentage, en valeur absolue (pixels), ou laissée libre en fonction de la place restante (auto). Ceci est utilisé pour le mode horizontal,
- height : comme ci-dessus, pour le mode vertical.
Dans le cas où une dimension est omise, elle sera remplacée automatiquement par le mot clé « auto ».
Création de l'objet
Cet objet n'implémente qu'une petite partie des fonctions nécessaires à un objet de type Layout.
dojo.provide("mioga.HVLayout");
dojo.require("dijit.layout._LayoutWidget");
dojo.declare( "mioga.HVLayout", dijit.layout._LayoutWidget, {
baseClass: "miogaHVLayout",
startup: function() {
if(this._started){ return; }
dojo.forEach(this.getChildren(), this._setupChild, this);
this.inherited(arguments);
},
_setupChild: function(/*dijit._Widget*/ child){
dojo.addClass(child.domNode, this.baseClass+"Pane");
this.inherited(arguments);
},
layout: function(){
// Implement _LayoutWidget.layout() virtual method.
this._layoutChildren();
},
addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
this.inherited(arguments);
if(this._started){
this.layout();
}
},
removeChild: function(/*dijit._Widget*/ child){
this.inherited(arguments);
if(this._started){
this.layout();
}
},
Il y a deux actions qui obligent à recalculer les tailles des widgets enfants : l'ajout (ou le retrait) d'un enfant et le changement de taille du container. Pour effectuer les calculs de taille, il faut prendre en compte le modèle de boite CSS http://www.w3.org/TR/CSS2/box.html pour lequel Dojo apporte quelques fonctions d'aide et des valeurs pré-initialisées par la classe parente :
- this._borderBox : donne la taille de la widget sans les marges,
- this._contentBox : donne la place disponible pour les enfants une fois les valeurs de padding appliquées.
La gestion du positionnement des enfants s'effectue dans la fonction _layoutChildren() qui va définir la taille disponible pour chacun des enfants en fonction des demandes exprimées. Cette fonction commence par récupérer les dimensions demandées pour chacun des enfants, width ou height en fonction du mode de mise en page choisi, en comptant les enfants souhaitant une dimension automatique.
_layoutChildren: function(){
var mb = dojo.marginBox(this.domNode);
var cb = this._contentBox;
var children = this.getChildren();
var dim = new Array();
var new_dim = 0;
var property;
var ref;
if (this.mode === 'vertical') {
property = 'height';
ref = cb.h;
}
else {
property = 'width';
ref = cb.w;
}
var auto_count = 0;
// Get fixed dimensions and count auto widgets
dojo.forEach(children, function(child){
var result = dojo.getNodeProp(child.domNode, property);
var value = 0;
// Default to auto when property is not set
if ((result == null) || (result === 'auto')) {
value = 0;
auto_count++;
}
else {
if (result.match(/%/)) {
value = result.match(/\d*/);
value = Math.round(ref * value / 100);
}
else {
value = result.match(/\d*/);
}
}
dim.push( {'d':parseInt(value), 'child': child});
new_dim += parseInt(value);
}, this);
Ensuite le positionnement commence, après calcul de la dimension par défaut des widgets en mode automatique. Si le total des dimensions dépasse la taille possible, la mise ne page sera perturbée et les widgets en mode auto auront une taille nulle.
Les enfants sont positionnés en mode absolu et les dimensions sont fixées par l'appel de la fonction resize de chaque widget enfant (il faut donc que ce soit des containers).
// Calculate position for children from given dimensions
var gap;
var auto_val = 0; // No place for "auto" children
if (new_dim < ref) {
gap = ref - new_dim;
auto_val = Math.round(gap / auto_count);
}
var pos = 0;
dojo.forEach(dim, function(d){
var newSize = new Object();
if (d.d === 0) {
d.d = auto_val;
}
if (this.mode === 'vertical') {
d.child.domNode.style.position = "absolute";
newSize.t = pos;
newSize.l = 0;
newSize.h = d.d;
newSize.w = cb.w;
}
else {
d.child.domNode.style.position = "absolute";
newSize.l = pos;
newSize.t = 0;
newSize.w = d.d;
newSize.h = cb.h;
}
d.child.resize(newSize);
pos += d.d;
}, this);
},
destroy: function(){
this.inherited(arguments);
}
});
Exemple d'utilisation
La représentation ci dessous est obtenue avec le code HTML/CSS donné après.
<style type="text/css">
#portal {width:1200; height:400; margin:0px; padding:0px; background-color:gray; position:relative; }
#header { margin:10px; background-color:cyan; border: 1px solid black; padding:20px;}
#col1 { margin:0px; border: 1px solid blue; background-color:red; padding:0px; }
#col2 { background-color:green; }
#col3 { background-color:blue; }
.box { background-color:white; border:1px solid black; margin:5px; }
.inbox { margin:10px; width:auto; height:auto; background-color:cyan; border:1px solid black; }
.miogaHVLayout { margin:0px; width:100%; height:100%; }
body { height:100%; width:100%; }
</style>
<div id="portal">
<div id="vlayout" mode="vertical" dojotype="mioga.HVLayout" >
<div id="header" height="100px" dojotype="dijit.layout.ContentPane" >
header
</div>
<div id="hlayout" mode="horizontal" height="auto" dojotype="mioga.HVLayout" >
<div id="col1" width="30%" dojotype="dijit.layout.ContentPane" >
<div id="vlayout2" mode="vertical" dojotype="mioga.HVLayout" >
<div id="col1-1" class="box" height="auto" dojotype="dijit.layout.ContentPane" >
col1-1
</div>
<div id="col1-2" class="box" dojotype="dijit.layout.ContentPane" >
<div class="inbox">
col1-2
</div>
</div>
<div id="col1-3" class="box" height="auto" dojotype="dijit.layout.ContentPane" >
col1-3
</div>
</div>
</div>
<div id="col2" width="auto" dojotype="dijit.layout.ContentPane" >
Col2
</div>
<div id="col3" width="200px" dojotype="dijit.layout.ContentPane" >
Col3
</div>
</div>
</div>
</div>
Gilles Polart-Donat

