Ionic : créer l’application

Dans le précédent article on a vu comment construire l’interface d’une application de quiz avec Ionic Creator. On a vu également comment importer sa création et créer un projet avec Intel XDK. Dans le présent article on va maintenant voir comment coder l’application avec cet IDE.

Le projet final complet XDK est disponible ici en téléchargement.

On fait le point du code

On va voir d’abord comment est organisé le code qu’on a obtenu. Voilà les dossiers :

img44

Faisons un peu le point :

  • img : prêt pour recevoir les images, à la base on n’en a pas
  • js : notre application est ici avec les fichiers Javascript pour AngularJS
  • lib : toutes les librairies d’Ionic
  • templates : le code Html de nos pages (à la mode Ionic)
  • xdk : des informations propres à l’IDE pour référencer le projet

Analysons de plus près les fichiers où on va devoir intervenir…

Mais dans un premier temps je constate que Ionic arrive en version 1.1.1 (dans lib/ionic) alors que la dernière version stable est la 1.2.4 comme on peut le voir ici. Alors tant qu’à faire je vais chercher la dernière version pour remplacer celle que Ionic Creator a envoyé. On pourra ainsi profiter des plus récentes possibilités.

index.html

C’est le fichier principal qui donne l’architecture de l’application. Voici le code obtenu :

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <title></title>

    <link href="lib/ionic/css/ionic.css" rel="stylesheet">
    <script src="lib/ionic/js/ionic.bundle.js"></script>

    <!-- cordova script (this will be a 404 during development) -->
    <script src="cordova.js"></script>

    <!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above
    <link href="css/ionic.app.css" rel="stylesheet">
    -->

    <script src="js/app.js"></script>
    <script src="js/controllers.js"></script>
    <script src="js/routes.js"></script>
    <script src="js/services.js"></script>
    <script src="js/directives.js"></script>

    <!-- Only required for Tab projects w/ pages in multiple tabs 
    <script src="lib/ionicuirouter/ionicUIRouter.js"></script>
    -->

  </head>
  <body ng-app="app" animation="slide-left-right-ios7">
  <div style="">
    <div style="">
        <ion-nav-bar class="bar-stable">
            <ion-nav-back-button class="button-icon icon ion-ios-arrow-back">Back</ion-nav-back-button>
        </ion-nav-bar>
        <ion-nav-view></ion-nav-view>
    </div>
</div>
  </body>
</html>

On trouve les références à Ionic et Cordova ainsi que l’appel du Javascript.

Ensuite l’application AngularJS est définie avec ng-app. On trouve aussi deux <div> qui me laissent un peu perplexe et que je vais supprimer rapidement si je n’en trouve pas l’utilité. Ensuite on a le composant ion-nav-bar pour la barre de navigation (documentation ici). Enfin on trouve le composant ion-nav-view qui permet de générer les vues et d’utiliser le routeur d’AngularJS (documentation ici). On va voir comment s’articule tout ça plus loin…

AngularJS

On trouve les fichiers d’AngularJS dans le dossier js :

img45

app.js

C’est là que sont définis l’application et ses modules. On y trouve aussi les initialisations :

angular.module('app', ['ionic', 'app.controllers', 'app.routes', 'app.services', 'app.directives'])

.run(function($ionicPlatform) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
  });
})

 controllers.js

Là on a deux contrôleurs tout prêts pour gérer nos deux pages :

angular.module('app.controllers', [])
  
.controller('questionsCtrl', function($scope) {

})
   
.controller('rSultatsCtrl', function($scope) {

})

 directives.js

Si on a des directives à créer c’est ici (mais on n’en aura pas besoin pour le quiz) :

angular.module('app.directives', [])

.directive('blankDirective', [function(){

}]);

 routes.js

Là c’est intéressant on a les routes de l’application :

angular.module('app.routes', [])

.config(function($stateProvider, $urlRouterProvider) {

  // Ionic uses AngularUI Router which uses the concept of states
  // Learn more here: https://github.com/angular-ui/ui-router
  // Set up the various states which the app can be in.
  // Each state's controller can be found in controllers.js
  $stateProvider

    .state('tabsController.questions', {
      url: '/page2',
      views: {
        'tab1': {
          templateUrl: 'templates/questions.html'
        }
      }
    })

    .state('tabsController.rSultats', {
      url: '/page3',
      views: {
        'tab2': {
          templateUrl: 'templates/rSultats.html'
        }
      }
    })

    .state('tabsController', {
      url: '/page1',
      abstract:true,
      templateUrl: 'templates/tabsController.html'
    })
      
    ;

  // if none of the above states are matched, use this as the fallback
  
  $urlRouterProvider.otherwise('/page1/page3');
  
});

Tout est organisé en états. Pour chaque état on a une url et une vue. Le générateur aurait pu associer les contrôleurs !

services.js

Ici on peut créer des services (on va s’en servir pour les questions et les résultats) :

angular.module('app.services', [])

.factory('BlankFactory', [function(){

}])

.service('BlankService', [function(){

}]);

Les templates

On a un dossier pour les templates :

img46

Très logiquement on retrouve ce qu’on a créé avec Ionic Creator : les deux pages et les onglets. On va devoir intervenir à ce niveau également pour coder l’application.

Les vues

On va commencer par compléter les templates pour obtenir le résultat désiré.

La page index

Voici la page d’index modifiée :

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <title></title>

    <link href="lib/ionic/css/ionic.css" rel="stylesheet">
    <link href="css/style.css" rel="stylesheet">
    <script src="lib/ionic/js/ionic.bundle.js"></script>

    <!-- cordova script (this will be a 404 during development) -->
    <script src="cordova.js"></script>
      
    <!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above
    <link href="css/ionic.app.css" rel="stylesheet">
    -->

    <script src="js/app.js"></script>
    <script src="js/controllers.js"></script>
    <script src="js/routes.js"></script>
    <script src="js/services.js"></script>

    <!-- Only required for Tab projects w/ pages in multiple tabs 
    <script src="lib/ionicuirouter/ionicUIRouter.js"></script>
    -->

  </head>
  <body ng-app="app" animation="slide-left-right-ios7">
    <ion-nav-bar class="bar-stable bar-assertive">
        <ion-nav-back-button class="button-icon icon ion-ios-arrow-back">Back</ion-nav-back-button>
    </ion-nav-bar>
    <ion-nav-view></ion-nav-view>
  </body>
</html>

J’ai ajouté la référence d’une page de style et une classe pour l’aspect.u

tabsController

Là je ne change pas grand chose, juste une petite classe pour la couleur :

<ion-view style="" title="Tabs Controller">
    <ion-tabs style="" class="tabs-stable tabs-icon-top tabs-assertive">
        <ion-tab title="Questions" icon="ion-android-list" href="#/page1/page2">
            <ion-nav-view name="tab1"></ion-nav-view>
        </ion-tab>
        <ion-tab title="Résultats" icon="ion-ios-checkmark" href="#/page1/page3">
            <ion-nav-view name="tab2"></ion-nav-view>
        </ion-tab>
    </ion-tabs>
</ion-view>

questions

Là j’ai un peu plus de travail :

<ion-view title="Questions">
    <ion-content class="has-header" padding="true">
        <div class="list card">
            <div class="item item-text-wrap">{{question}}</div>
            <div class="item item-body">
                <div class="list">
                    <ion-radio ng-class="{ red: red[$index], green: green[$index] }" ng-click="choix($index)" ng-disabled="disabled" ng-repeat="reponse in reponses">{{reponse}}</ion-radio>
                </div>
            </div>
        </div>
        <button ng-show="bouton" ng-click="suivant()" class="button button-dark button-block icon-right" ng-class="iconeBouton">{{texteBouton}}</button>
    </ion-content>
</ion-view>

Il faut prévoir l’interaction avec AngularJS.

Pour les questions/réponses

  • emplacement pour le texte de la question
  • répétition du bouton radio en fonction du nombre de réponses disponibles (ng-repeat)
  • adaptation de la classe des boutons radio pour les rendre vert ou rouge selon que la réponse est vraie ou fausse (ng-class)
  • mise en place d’un événement pour le choix de la réponse (ng-click)
  • possibilité de rendre les boutons inactifs lorsqu’un choix a été fait (ng-disabled)

Pour le bouton :

  • possibilité de le rendre invisible (ng-show)
  • mise en place d’un événement pour le clic (ng-click)
  • emplacement pour le texte du bouton
  • classe pour pourvois changer l’icône (ng-class)

resultats

Là c’est plus simple :

<ion-view title="Résultats" ng-init="setResults()">
    <ion-content class="has-header" padding="true">
        <ion-list></ion-list>
        <ion-list>
            <ion-item>Vrai : {{vrai}}</ion-item>
            <ion-item>Faux : {{faux}}</ion-item>
        </ion-list>
    </ion-content>
</ion-view>

On a besoin à chaque fois que la page apparaît de mettre les valeurs à jour (ng-init).

On prévoit les deux emplacements pour les valeurs des réponses (vrai et faux).

Les routes

Là aussi je n’ai pas changé grand chose :

angular.module('app.routes', [])

.config(function($stateProvider, $urlRouterProvider) {

  $stateProvider
            
    .state('tabsController.questions', {
      url: '/page2',
      views: {
        'tab1': {
          templateUrl: 'templates/questions.html',
          controller: 'questionsCtrl'
        }
      }
    })
        
    .state('tabsController.resultats', {
      cache: false,
      url: '/page3',
      views: {
        'tab2': {
          templateUrl: 'templates/resultats.html',
          controller: 'resultatsCtrl'
        }
      }
    })
        
    .state('tabsController', {
      url: '/page1',
      abstract: true,
      templateUrl: 'templates/tabsController.html'
    })
      
    ;

  $urlRouterProvider.otherwise('/page1/page2');
  
});

J’ai juste ajouté le nom des contrôleurs et annulé le cache pour la vue des résultats pour avoir à chaque fois l’initialisation.

Les services

J’ai prévu deux services, le premier qui gère les questions et les réponses :

.factory('quiz', [function(){

  var questions = [
    {
      question: 'Qui a écrit La Chartreuse de Parme ?',
      reponses: [
        'Balzac',
        'Stendhal',
        'Flaubert',
        'Marivaux'
      ],
      vrai: 1
    },
    {
      question: 'Le Parthénon se trouve :',
      reponses: [
        'En Grèce',
        'En Crète',
        'En Sicile'
      ],
      vrai: 0
    },
    {
      question: 'Qui a composé Le Boléro ?',
      reponses: [
        'Camille Saint-Saens',
        'Maurice Ravel',
        'Serge Prokofiev'
      ],
      vrai: 1
    },
    {
      question: 'Qui a peint le plafond de la chapelle Sixtine ?',
      reponses: [
        'Léonard de Vinci',
        'Michel-Ange',
        'Véronèse'
      ],
      vrai: 1
    },
    {
      question: 'Quel cinéaste a réalisé le film E.T. en 1982 ?',
      reponses: [
        'James Cameron',
        'Georges Lucas',
        'David Lynch',
        'Steven Spielberg'
      ],
      vrai: 3
    }
  ];

  return {
    getQuestion: function (index) {
      return questions[index];
    },
    getMax: function () {
      return questions.length;
    }
  };
  
}])

J’ai juste mis 5 questions pour faire tourner l’application.

Le second service est pour les résultats, étant donné qu’on doit les atteindre avec les deux contrôleurs :

.service('resultats', [function(){

  var vrai = 0;
  var faux = 0

  return {
    getVrai: function () {
      return vrai;
    },
    incrementeVrai: function () {
      ++vrai;
    },
    resetVrai: function () {
      vrai = 0;
    },
    getFaux: function () {
      return faux;
    },
    incrementeFaux: function () {
      ++faux;
    },
    resetFaux: function () {
      faux = 0;
    }
  }
}])

Les contrôleurs

questionsCtrl

C’est le contrôleur principal qui doit gérer les page des questions :

.controller('questionsCtrl', function($scope, quiz, resultats) {

  $scope.question = '';
  $scope.reponses = {};
  $scope.vrai = 0;
  $scope.red = [];
  $scope.green = [];
  $scope.bouton = false;
  $scope.disabled = false;
  $scope.texteBouton = 'Question suivante';
  $scope.iconeBouton = 'ion-android-arrow-forward';
  resultats.resetVrai();
  resultats.resetFaux();

  var step = 0;
    
  $scope.choix = function (index) {
    $scope.disabled = true;
    $scope.bouton = true;
    if(index == $scope.vrai) {
      // Bonne réponse
      $scope.green[index] = true;
      resultats.incrementeVrai();
    } else {
      // Mauvaise réponse
      $scope.red[index] = true;
      $scope.green[$scope.vrai] = true;
      resultats.incrementeFaux();
    }
    if(quiz.getMax() == step) {
      $scope.iconeBouton = 'ion-wand';
      $scope.texteBouton = 'Recommencer';
      step = -1;			
    }
  };

  $scope.suivant = function () {
    if(step == -1) {
      step = 0;
      $scope.texteBouton = 'Question suivante';
      $scope.iconeBouton = 'ion-android-arrow-forward';	
      resultats.resetVrai();
      resultats.resetFaux();
    } 
    getQuestion();
  };

  var getQuestion = function () {
    var item = quiz.getQuestion(step);
    $scope.bouton = false;
    $scope.disabled = false;
    $scope.question = item.question;
    $scope.reponses = item.reponses;
    $scope.vrai = item.vrai;
    $scope.red = [];
    $scope.green = [];
    for (var i = 0; i < item.reponses.length; i++) {
      $scope.red.push(false);
      $scope.green.push(false);
    }
    ++step;
  }

  $scope.suivant();
})

Je ne vais pas entrer dans le détail du code, je vous laisse l’analyser…

resultatsCtrl

Là c’est tout léger parcequ’on doit juste renseigner les résultats :

.controller('resultatsCtrl', function($scope, resultats) {

  $scope.setResults = function () {
    $scope.vrai = resultats.getVrai();
    $scope.faux = resultats.getFaux();
  }

})

Le css

J’ai établi quelques règles css pour l’esthétique :

.scroll-content {
    background-color: brown;
}
.red {
    color: red;
}
.green {
    color: green;
}
.item {
    background-color: darksalmon;
}
.item-radio .item-content {
    background-color: #efbbb0;
}
.overflow-scroll {
    overflow-y: auto;
}

Utilisation de Intel XDK

Emulation

On a vu dans le précédent article comment créer un projet pour le quiz avec Intel XDK. Si on utilise l’onglet Emulate on peut déjà prévisualiser l’application :

img49

Fonctionnement

Si on choisi la bonne réponse elle change en vert et le bouton apparaît :

img50

En cas de mauvaise réponse elle apparaît en rouge et la bonne en vert :

img51

Vous voyez également qu’on a du choix dans le mobile émulé.

A tout moment on peut aller voir les résultats :

img52

Lorsqu’on atteint la dernière question le bouton change d’aspect :

img53

Bon, l’application est évidemment très rudimentaire mais largement suffisante pour se faire une idée du système !

Test en réel

L’émulation c’est pratique mais faire un essai sur un vrai appareil c’est mieux. Intel XDK propose de construire l’application sur leur serveur cloud :

img54

Pour ça il faut créer un compte mais c’est gratuit.

Ensuite il faut installer l’application de prévisualisation sur son appareil à partir d’un store (Google, iOS ou Windows) et on peut ainsi lancer l’application sur son appareil. Simple et efficace !

On peut aussi faire du débogage en local.

Mais on peut surtout générer l’application pour tous les supports :

img55

Mais évidemment il faudra bien renseigner les paramètres pour chaque plateforme (sur la page d’accueil) :

img56

Voilà ce petit tour d’horizon des possibilités d’ionic est terminé. A vous de jouer si ça vous a donné envie !

Laisser un commentaire