Si vous souhaitez participer à la vie de dojotookit-fr, nous recherchons : des traducteurs, des personnes pour animer le blog ou écrire des articles, etc...
Prévenez nous par mail (ben_AT_dojotoolkit-fr_DOT_org) ou par chat (#dojo-fr sur irc.freenode.net)

Tutoriel : Ma première application Dojo

Tutoriel original par Cyril Berthenet

Pour bien commencer...

Avant de commencer à implémenter l'exemple présenté dans ce tutoriel, il vous faut tout d'abord :

  1. Avoir fait le tutoriel "mon premier widget Dojo" (où au moins avoir télécharger le code source du tutoriel)
  2. # Copier les librairies dijit, dojo et dojox du framework Dojo dans le répertoire tutoDojo
    Télécharger le framework Dojo à cette adresse http://download.dojotoolkit.org/
  3. Créer ensuite le répertoire exemple2 en effectuant un simple copier/coller du dossier exemple1.
    Arborescence du projet.
  4. Editer les fichiers Search.js et searchWidget.html pour modifier le namespace

    Search.js

    1
    2
    3
    4
    5
    6
    7
    89
    10
    11
    12
    13
    14
    15
    161718
    19
    2021
    22
    23
    24
    25
    26
    27
    2829
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    4344
    4546
    47
    48
    49
    50
    51
    52
    5354
    55
    56
    57
    58
    59
    60
    61
    62
    63
    6465
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110111
    112
    113
    114
    115
    116
    117
    118
    119120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    
    /**********************************************/
    /*                                            */
    /* Widget : Search                            */
    /* Author : Berthenet Cyril                   */
    /*                                            */
    /**********************************************/
     
    dojo.provide("exemple2.widget.Search");  
    dojo.require("dijit._Templated");
    dojo.require("dijit._Widget");
     
    dojo.require("dijit.form.Button");
    dojo.require("dijit.Menu");
     
     dojo.requireLocalization("exemple2", "Locale"); dojo.requireLocalization("exemple2", "Ressources");  
    /* Class: Search */
    dojo.declare("exemple2.widget.Search", [dijit._Widget,dijit._Templated], {  
      /* Object: _nls
         stockage du wording et des ressources du widget */
      _nls:{locale:null,ressources:null},
      
      /* String: templatePath
         chemin d'acces vers le template du widget */
      templatePath: dojo.moduleUrl("exemple2.widget", "template/Search.html"),   
      /* Object: _dropdown     
      Bouton de selection du moteur de recherche */
      _dropdown:null,
     
      /* Array: _engines
         Liste des moteurs de recherche */  
            _engines:null,
     
      /* Function: constructor
         Constructeur */
      constructor: function(){    
            console.log("exemple2.widget.Search::constructor");
        this._nls.locale=
          dojo.i18n.getLocalization("exemple2","Locale").searchWidget;          this._nls.ressources=
          dojo.i18n.getLocalization("exemple2","Ressources").searchWidget;          this._engines=this._nls.locale.engines;
        this._dropdown={};
      },
            
      /* Function: destroy
      Destructeur */
      destroy: function(){
        console.log("exemple2.widget.Search::destroy");    this.inherited("destroy",arguments);
        if(this._dropdown){this._dropdown.destroy();}
        delete this._nls;
        delete this._engines; 
      },
            
      /* Function: startup
      Methode publique appelee apres l'instanciation du widget et la creation de tous 
         ses noeuds DOM dans document.body */
      startup: function(){
        console.log("exemple2.widget.Search::startup");    this.inherited("startup",arguments); 
            
        this.searchField.value=(this.text==null||this.text=="")?
        this._nls.locale.text:this.text;
     
        var menu = new dijit.Menu(); 
            
        dojo.forEach(this._engines, function(engine){
                    
          var menuItem = new dijit.MenuItem({
            label: engine,
            onClick: dojo.hitch(this, function(param) { 
              this._dropdown.attr("label",param);
              this._dropdown.attr("engine",param);
            },engine)
          });                    
              
        menu.addChild(menuItem);
                    
        },this);
            
        this._dropdown = new dijit.form.DropDownButton({
          label: this._engines[0],
          engine: this._engines[0],
          dropDown: menu
        }, dojo.byId(this.id+"Selector"));
            
        this._dropdown.startup();
     
      },
      
              /* Function: _onKeyPress
         Methode privee executee lors de la saisie d'un caractere dans le champs de recherche */
      _onKeyPress: function(/*Object*/evt) {
        console.log("exemple2.widget.Search::_onKeyPress");    if(evt.keyCode == dojo.keys.ENTER){
            this._search();
          dojo.stopEvent(evt);
          return;
        }
      },
      
      /* Function: _clear
         Methode privee permettant d'effacer le texte du champs de saisie */
      _clear: function() {
        console.log("exemple2.widget.Search::_clear");    if(this.searchField.value==this._nls.locale.text||this.searchField.value==this.text) {
          this.searchField.value="";
        }
      },
      
              /* Function: _search
         Methode privee executant la recherche */
      _search: function() {
        console.log("exemple2.widget.Search::_search");        
            if(  this.searchField.value!=this._nls.locale.text &&
          this.searchField.value!=this.text && 
          this.searchField.value != "") {
     
          var url = "";
          var fieldValue = this.searchField.value;
          var engines = this._nls.ressources.engines;
                            
          switch(this._dropdown.attr('engine')){
            case this._engines[0]:
                    url = engines.google;
              break;
            case this._engines[1]:
              url = engines.yahoo;
              break;
                      case this._engines[2]:
              url = engines.wikipedia;
              break;
          }
           
               dojo.byId("viewer").src=url+escape(fieldValue);
                            
        }
        else {
           
              var dialogbox = dijit.byId("dialogbox");
                            
          var content = this._nls.ressources.popup;
          dialogbox.attr("title",this._nls.locale.popup.title);
          dialogbox.attr("content",dojo.string.substitute(content,{
              message:this._nls.locale.popup.message,
            btnLabel:this._nls.locale.popup.btnLabel
          }));
                            
          dialogbox.show();
          }
      }             
     
     
    });

    searchWidget.html

    26
    27
    28
    29
    30
    3132
    33
    34
    35
    36
    3738
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    
     <script type="text/javascript" language="javascript">
     
          dojo.require("dojo.i18n");
          dojo.require("dijit.Dialog");
          
              dojo.require("exemple2.widget.Search");           
          dojo.addOnLoad(init);
            
          function init(){ 
              
            var _search = new exemple2.widget.Search({                  id:"search",
              text:"saisir votre texte ici..."
            }, dojo.byId("search"));
                    
            _search.startup();
        
            var dialog = new dijit.Dialog({
              title: "title" 
                    }, dojo.byId("dialogbox"));
     
            dialog.startup();
                                            
          }
              
        </script>

Conventions de codage adoptées dans ce tutoriel

  1. commentaires : /* commentaire sur une ou plusieurs lignes */
  2. ajout d'une ligne console.log en début de fonction pour identifier les méthodes appelées lors de l'exécution du code
  3. les méthodes privées sont préfixées par un underscore
  4. afin d'éviter les problèmes d'encodage, les caractères accentués sont proscrit du code Dojo

Définition du template du widget 'Viewer'

Ce composant graphique va nous permettre d'afficher le résultat de la recherche sous forme d'une page HTML.
Il est composé des éléments suivants :

  1. un div container qui nous permettra par la suite de définir le style de notre widget
  2. d'une iframe pour l'affichage

Viewer.html

1
2
3
4
5
6
7
8
9
<div class="viewer">
  <iframe name="iframe" 
          class="iframe"
          src="${_nls.ressources.defaultPage}"
          dojoAttachPoint="viewer"
          scrolling="yes"
          frameborder="no">
  </iframe>
</div>

lignes :
4 : url de la page qui sera affichée par défaut dans le viewer (gestion du lien dans les ressources)

Modification des ressources

nls/Ressources.js et nls/fr/Ressources.js

20
212223
},
"viewerWidget" : {    "defaultPage": "static/empty.html"}

Définition du style du widget 'Viewer'

viewer.css

1
2
3
4
5
.viewer .iframe {
  width: 800px;
  height: 400px;
  border:3px solid #B0B0B0;
}

Modification de la page statique

empty.html

1
2
3
4
56
7
<html>
  <head>
  </head>
  <body>
    Le r&eacute;sultat de votre recherche s'affichera dans ce viewer  </body>
</html>

Définition de la classe 'Viewer'

Diagramme de classe

Viewer

- _nls : object
- templatePath : string
- _topics : array
+ constructor()
+ destroy()
+ postCreate()
+ setContent()
+ reset()

Viewer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**********************************************/
/*                                            */
/* Widget : Viewer                            */
/* Author : Berthenet Cyril                   */
/*                                            */
/**********************************************/
 
dojo.provide("exemple2.widget.Viewer");
 
dojo.require("dijit._Templated");
dojo.require("dijit._Widget");
 
dojo.requireLocalization("exemple2", "Ressources");
 
/* Class: Search */
dojo.declare("exemple2.widget.Viewer", [dijit._Widget,dijit._Templated], {
 
  /* Object: _nls
    stockage du wording et des ressources du widget */
  _nls:{ressources:null},
  
  /* String: templatePath
     chemin d'acces au template du widget */
  templatePath: dojo.moduleUrl("exemple2.widget", "template/Viewer.html"),
          
        /* Function: constructor
     Constructeur */
  constructor: function(){
    console.log("exemple2.widget.Viewer::constructor");
    this._nls.ressources= 
        dojo.i18n.getLocalization("exemple2","Ressources").viewerWidget;
    },
        
  /* Function: destroy
     Destructeur */ 
        destroy: function(){
    console.log("exemple2.widget.Search::destroy");
    delete this._nls;
  },
     
   /* Function: setContent
     Modification du contenu du viewer */
  setContent: function(/*String*/url) {
    console.log("exemple2.widget.Viewer::setContent");
    this.viewer.src=url;
        },
        
  /* Function: reset
     Remise a zero du contenu du viewer */
  reset: function() {
    console.log("exemple2.widget.Viewer::setContent");
    this.setContent(this._nls.ressources.defaultPage);
  }
        
});

lignes :
30 et 31 : chargement des ressources du widget
43 à 46 : méthode permettant de modifier le contenu de l'iframe avec une url passée en paramètre
50 à 53 : remise à zéro du contenu du viewer par l'affichage de la page par défaut

Création d'un sample de test

viewerWidget.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset="utf-8" />
                    
        <title>Viewer</title>
        
    <link rel="stylesheet" type="text/css" href="./css/viewer.css" />
 
    <script type="text/javascript"> 
        
      var djConfig = {
        parseOnLoad: false,
        isDebug: true
      }; 
          
    </script>
        
    <script type="text/javascript" src="../dojo/dojo.js" charset="utf-8"></script>
    
        <script type="text/javascript" language="javascript">
 
      dojo.require("dojo.i18n");
      
      dojo.require("exemple2.widget.Viewer"); 
          
      dojo.addOnLoad(init);
        
      function init(){
          
         var _viewer = new exemple2.widget.Viewer({
          id:"viewer"
        }, dojo.byId("viewer"));
                                            
        _viewer.setContent("http://www.google.fr");        
                
      }
    
    </script>
  </head>
  <body>
    <div id="viewer"></div>
  </body>
</html>

lignes :
8 : chargement du style
31 à 33 : instanciation du widget "Viewer"
35 : affichage par défaut du moteur google

Définition de la classe de notre application

Nous avons fait le choix dans ce tutoriel de construire notre exemple à partir d'une classe appelant un template regroupant les widgets de base de notre application. Les widgets qui pourraient avoir d'autres fonctions que celle de la rechercher dans une application plus complète seront déportés vers le sample de test (ex: la boîte de dialogue)

MonAppli.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**********************************************/
/*                                            */
/* Widget : Mon application                   */
/* Author : Berthenet Cyril                   */
/*                                            */
/**********************************************/
 
dojo.provide("exemple2.widget.MonAppli");
 
dojo.require("dijit._Templated");
dojo.require("dijit._Widget");
 
dojo.require("dijit.layout.ContentPane");
dojo.require("dijit.layout.BorderContainer");
dojo.require("dijit.Dialog"); 
 
dojo.require("exemple2.widget.Search");
dojo.require("exemple2.widget.Viewer");
 
dojo.requireLocalization("exemple2", "Ressources"); 
 
/* Class: Search */
dojo.declare("exemple2.widget.MonAppli", [dijit._Widget,dijit._Templated], {
 
  /* Boolean: widgetsInTemplate 
  affichage de widgets dans un template (false par defaut) */
  widgetsInTemplate: true,
  
  /* String: templatePath
     chemin d'acces au template du widget */ 
         templatePath: dojo.moduleUrl("exemple2.widget", "template/MonAppli.html"),
 
  /* Function: constructor
     Constructeur */
  constructor: function(){ 
     console.log("exemple2.widget.MonAppli::constructor");
  }
 
});

ligne :
27 : la variable widgetsInTemplate est très importante car elle permet l'interprétation des widgets à l'intérieur d'un template

Définition du template de notre application

Le template de notre application regroupe les composants graphiques permettant la mise en page (dijit.layout) et ceux liés à la fonction de recherche (Search et Viewer).

MonAppli.html

1
2
3
4
5
6
7
8
<div dojoType="dijit.layout.BorderContainer" design="screenDesign">
  <div dojoType="dijit.layout.ContentPane" region="top">
    <div dojoType="exemple2.widget.Search"></div>
  </div>
  <div dojoType="dijit.layout.ContentPane" region="center">
    <div dojoType="exemple2.widget.Viewer"></div>
  </div>
</div>

Distribution des données dans l'application

Les widgets publient leurs données et/ou s'abonnent pour en obtenir.
remplacer la ligne :

Search.js

139
dojo.byId("viewer").src=url+escape(fieldValue);

par :

Search.js

139
dojo.publish("displayResult",[url+escape(fieldValue)]);

Viewer.js

262728
29
30
31
32
33
34
3536
37
38
39
40
41
42
4344
45
4647484950
/* Array: topics */_topics:null,  
/* Function: constructor
   Constructeur */
   constructor: function(){
    console.log("exemple2.widget.Viewer::constructor");
    this._nls.ressources=dojo.i18n.getLocalization("exemple2","Ressources")
.viewerWidget;
    this._topics=[];        },
        
/*Function: destroy
  Destructeur */
destroy: function(){
    console.log("exemple2.widget.Search::destroy");
    delete this._nls;
    delete this._topics;        },
        
 /* Function: postCreate Methode appelee apres la creation du widget */ postCreate: function() {    this._topics[0]=dojo.subscribe("displayResult", this, "setContent");},

lignes :
27 : définition d'un tableau pour le stockage des topics.
35 : initialisation du tableau de topics.
43 : destruction du tableau de topics.
48 à 50 : méthode appelée automatiquement après la création du widget.
49 : déclenchement de la méthode setContent() du Viewer lorsque un topic "displayResult" est publié dans l'application.

note : le widget 'Search' envoi un topic "displayResult" avec l'url de recherche dans toute l'application. Le widget 'Viewer' qui a souscrit à ce topic va exécuter sa méthode setContent() avec l'url passée en argument du topic

Modification des styles

Séparation des styles propres à chacun des widgets et ceux pouvants être réutilisé dans toute l'application. On a préféré faire ici plusieurs fichiers css qui seront compressés en un seul fichier css par le processus de buid.

search.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.search {
  height:30px;
}
.search .btn {
  background: url(../images/loupe.png) no-repeat;
  width:20px;
  height:19px;
  border-left:0px;
  cursor:pointer;
}
.search input{  
  height:21px;
  font:13px verdana, sans-serif;
}

monappli.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.floatLeft {
  float:left;
}
.greyBorder {
  border:1px solid #B0B0B0;
 }
 
/****************************************/
/*    Surcharge theme nihilo de Dojo       */
/****************************************/ 
 
.nihilo .dijitDropDownButton .dijitReset {
  height:16px;
}
.nihilo .dijitDropDownButton {
  float: left;
  margin:0 !important;
}
.nihilo .dijitDropDownButton span {
  font:10px verdana, sans-serif;
}
.nihilo .dijitButtonNode {
  width:80px;
  border:1px solid #B0B0B0 !important;
  border-right:0px !important;
}

Test

monApplication.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset="utf-8" />
           
   <title>mon application</title>
        
    <link rel="stylesheet" type="text/css" href="./css/viewer.css" />
    <link rel="stylesheet" type="text/css" href="./css/search.css" />
    <link rel="stylesheet" type="text/css" href="./css/monappli.css" />        
        
    <style type="text/css" title="text/css">
      @import "../dijit/themes/dijit.css";
      @import "../dijit/themes/nihilo/nihilo.css";
    </style>                
        
    <script>
 
      var djConfig = {
        parseOnLoad: false,        
                isDebug: true
      };
 
    </script>
    
        <script type="text/javascript" src="../dojo/dojo.js" charset="utf-8"></script>
                
    <script type="text/javascript" language="javascript">
 
      dojo.require("dojo.i18n");      
          
      dojo.require("exemple2.widget.MonAppli");
 
      dojo.addOnLoad(init);
      
          function init(){
 
        var _appli = new exemple2.widget.MonAppli({}, dojo.byId("appli"));
        
        var dialog = new dijit.Dialog({}, dojo.byId("dialogbox")); 
                
        dialog.startup();
                
      }
          
      </script>
  </head>
  <body class="nihilo">
    <div id="appli"></div>
    <div id="dialogbox"></div> 
        </body>
</html>

lignes :
8 : chargement du style du widget 'Viewer'
9 : chargement du style du widget 'Search'
10 : chargement des styles communs partagés par toute l'application
12 à 15 : chargement des thèmes dijit et nihilo pour la création du bouton à menu déroulant
38 : instanciation de l'application
40 : instanciation de la boîte de dialogue pour l'affichage des erreurs. On aurait pu inclure ce widgets dans le template de notre application en imaginant qu'il puisse avoir une autre fonction que l'affichage des erreurs liées à la recherche dans une application plus complète.

L'application finale est composée des widgets 'Search' et 'Viewer'.


Berthenet Cyril
http://www.life-behind-the-mirror.com/codes/tutoDojo/myFirstDojoAppli.php