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 :

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 :

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