Créer une application Mobile avec Vue.js 2 et Cordova

Bonjour, aujourd'hui un petit tutotiel pour réaliser une application mobile pour Android et iOS avec les framework Cordova et VueJs 2. Nous verrons également comment afficher une carte google maps au sein de l'application.
Les sources sont disponibles à la fin de l'article.

Si vous ne connaissez pas Cordova, il s'agit d'un framework permettant d'écrire le code source de son application mobile une seule fois pour plusieurs plateformes.
Allez faire un tour ici
Quant à Vue Js il s'agit du framework javascript frontend MVVM qui monte en ce moment.

Installation des pré requis

Quelques outils sont nécessaires au projet.

  • Il faut Node Js, de préférence une version à jour Node.js
  • Ensuite vous pouvez installer Cordova via npm Cordova Install Cordova: npm install -g cordova
  • un éditeur de code de votre choix

Configurer son projet Cordova

Une fois que tous les outils sont installés il est temps de démarrer le projet.
Dans votre console tapez :

cordova create VueMap  

Cette commande a pour effet de créer la structure nécessaire à notre projet cordova :

- config.xml contient les informations nécessaires sur l'application, la liste des plugins ainsi que les plateformes ciblées - platforms contient les librairies pour les plateformes sur lesquelles vous souhaitez déployer votre application (Android, iOS, navigateur web) - plugins contient les plugins liés à votre projet cordova, en gros tout ce qui doit communiquer avec du natif - www contient le code source javascript/ HTML/ CSS - hooks contient les scripts utilisés pour piloter les builds

Nous allons ajouter deux plateformes : Android et Browser, browser va essentiellement nous servir pour la phase de développement et de debugging, il est plus rapide de lancer l'application dans un navigateur que sur un mobile ou un émulateur. Si vous avez un mac vous pouvez ajouter la plateforme ios.

pour ajouter une plateforme taper la commande suivante :

#Pour Android
cordova platform add android ---save  
#Pour les navigateurs
cordova platform add browser --save  
#Pour iOs
cordova platform add ios --save  

Ensuite pour compiler l'application il faut utiliser la commande build

cordova build browser  

A noter que si vous souhaitez compiler l'application pour iOS il vous faudra un mac.

Enfin vous pouvez tester votre application avec la commande run

cordova run browser  

Nous allons modifier le fichier config.xml afin de décrire notre projet

<?xml version='1.0' encoding='utf-8'?>  
<widget id="io.cordova.cordovavuemap" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">  
    <name>CordovaVueMap</name>
    <description>
        A cordova app to display map with vue js 2
    </description>
    <author email="davidverriere+cheesecode@gmail.com" href="http://cheesecode.fr">
        Cheesecode Team
    </author>
    <content src="index.html" />
......

Prise en main de Vue.js 2

Si vous connaissez déjà vue.js vous pouvez passer cette partie.
Nous allons modifier le fichier www/index.html. Ce fichier correspond au point d'entrée graphique de l'application. Ce fichier est désigné comme point d'entrée grâce à la balise content du fichier config.xml.

Nous allons d'abord référencer vue.js 2 depuis le CDN de vuejs :

        <script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue"></script>  
        <script type="text/javascript" src="js/index.js"></script>
.js

Pour une version en production il conviendra de prendre le fichier minifié avec l'extension .min

Si on execute notre application :

cordova build browser  
cordova run browser  

On s’aperçoit qu'elle ne fonctionne pas correctement, il y a une erreur dans la console du navigateur (qu'on aura lancé sur google chrome en appuyant sur F12 sur windows) :

Cette erreur est causée par l'accès à une ressource externe à l'application, il faut que l'on autorise explicitement l'url du cdn vue js pour le chargement de script.

Pour se faire on va modifier la balise meta portant le tag Content-Security-Policy en ajout la ligne suivant au contenu du tag content

; script-src 'self' https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js.js 'unsafe-eval'

Les content security policy ont été introduit pour limiter les risques d'attaques XSS. Une description complète à cette adresse https://content-security-policy.com/.

Les CSP (content security policy) sont découpés en source (script, images, styles, polices, frame, media HTML5, requêtes AHAX).
Pour chaque source on peut renseigner les informations suivante :

  • 'self' : indique l'application peut charger tous les scripts locaux
  • les urls des ressources externes exemple 'https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js'
  • 'unsafe-eval' : autorise la génération et l'utilisation de code dynamique comme on peut en retrouver dans vue.js

Plus d'informations dans la documentation Cordova.

Si on relance l'application, il n'y a plus d'erreur dans la console.

Nous allons maintenant intégrer les exemples de la documentation vue.js dans notre application.

Nous allons modifier notre fichier index.html afin qu'il ressemble à ceci sous la balise body :

<body>  
    <div id="app">
        {{ message }}
    </div>

On note qu'il y a des doubles accolades appelées également double moustaches. A l'intérieur il y a une propriété qui va nous servir à afficher un message.

Nous allons maintenant modifier le fichier www/js/index.js qui contient le javascript lié à notre écran d'accueil et donc à l'initialisation de l'application.

var app = {  
    // Application Constructor
    initialize: function () {
        this.bindEvents();
        this.initVueJs();
    },
    //Initialisation de notre instance VueJs
    initVueJs: function () {
        var app = new Vue({
            el: '#app',
            data: {
                message: 'Hello Vue!'
            }
        });
    },
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // 'load', 'deviceready', 'offline', and 'online'.
    bindEvents: function () {
        document.addEventListener('deviceready', this.onDeviceReady, false);
    },
    // deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicitly call 'app.receivedEvent(...);'
    onDeviceReady: function () {
        app.receivedEvent('deviceready');
    },
    // Update DOM on a Received Event
    receivedEvent: function (id) {
        console.log('Received Event: ' + id);
    }
};

app.initialize();  

Si on exécute notre application on arrive au résultat suivant :

Rien d'excitant, mais on a réussi à binder une variable en javascript à un élément du DOM HTML.

Si on continue à suivre les exemples de la documentation vuejs, on peut afficher la date d'execution en modifiant la propriété message comme ceci :

    initVueJs: function () {
        var app = new Vue({
            el: '#app',
            data: {
                message: 'You loaded this page on ' + new Date()
            }
        });
    },

script

A chaque fois que l'on lancera l'application sur notre mobile ou que l'on rafraîchira la page dans le navigateur la date se mettra à jour.

On ne va pas rentrer plus dans les détails, la documentation vue.js est très bien faite. Je vous conseille de bien lire au moins la partie introduction de vue.js pour continuer à lire cet article.

Utilisation des composants Vue.js

Nous allons maintenant utiliser l'une des grandes forces de Vue.js les components.
Les composants sont stockés dans des fichiers .vue, ces fichiers contiennent à la fois le html, le javascript et le css, il est possible de les séparer dans plusieurs fichiers mais comme les composants doivent rester le plus simple possible et se limiter à un rôle en particulier ce cas n'est pas souvent nécessaire.

Notre solution ne peut les gérer actuellement, nous allons devoir utiliser la librairie VueIfy qui va s'occuper de transformer nos composants .vue en fichier javascript, html et css intégrables par notre application.

Et pour utiliser cette librairie nous allons avoir besoin d'un outil de "bundlisation", nous prendrons browserify.

Il va falloir que lorsque l'on lance la compilation cordova on appelle browserify pour qu'il puisse effectuer ces tâches et pour cela on va utiliser le système de hook fournit par Cordova.

Un petit schéma pour résumer tout ça :

Création d'un composant

Passons à la pratique.
Nous allons d'abord créer un fichier home.vue dans le répertoire js/components.

Notre composant va ressembler à cela

<template>  
    <div id="home">
        {{ message }}
    </div>
</template>  
<script>

export default {  
    data(){
        return {
            message : 'mon message depuis mon composant'
        };        
    },
    methods:{

    }
}
</script>  

La balise template contient le html.
La balise script contient le code source. On remarque que la syntaxe ES6 de javascript est utilisée.

Utilisation du composant

Nous allons maintenant créer un fichier main.js qui va servir à instancier notre composant home.vue.

var Vue = require('vue');  
var VueResource = require('vue-resource');  
// référence le composant home
var Home = require('./components/home.vue');

Vue.use(VueResource);

var vm = new Vue({  
  el: 'body',
//On référence le composant home dans notre vue, attention le nom 'home-component' doit être ne Kebab case
  components: {
    'home-component': Home
  }
});

Il faut maintenant modifier notre fichier index.html de la sorte :

<body>  
    <home-component>
    </home-component>
    <script src="js/bundle.js"></script>

    <script type="text/javascript" src="cordova.js"></script>
    <script type="text/javascript" src="js/index.js"></script>
</body>  

On supprime la référence à vue.js depuis le cdn et on ajoute notre composant home-component ainsi qu'une référence au fichier js/bundle.js.

Suppression de code inutile

Il faut maintenant mettre à jour le fichier index.js de la sorte :

var app = {  
    // Application Constructor
    initialize: function () {
        this.bindEvents();        
    },    
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // 'load', 'deviceready', 'offline', and 'online'.
    bindEvents: function () {
        document.addEventListener('deviceready', this.onDeviceReady, false);
    },
    // deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicitly call 'app.receivedEvent(...);'
    onDeviceReady: function () {
        app.receivedEvent('deviceready');
    },
    // Update DOM on a Received Event
    receivedEvent: function (id) {
        console.log('Received Event: ' + id);
    }
};

app.initialize();  

vue.js sera instantié par le script main.js qui sera référencé automatiquement par index.html grâce à browserify et vueify.

C'est finit pour le code, il nous reste maintenant à paramétrer la chaîne de compilation.

Chaîne de compilation

Nous allons utiliser npm pour référencer tous les composants nécessaires à notre application.
Pour se faire nous allons créer un fichier package.json à la racine de l'application.

{
  "name": "cordova-vue-map",
  "version": "1.0.0",
  "description": "A mobile app for displaying map from cordova",
  "main": "index.js",
  "dependencies": {
    "browserify": "~13.0.1",
    "vue": "2.1.6",
    "vue-resource": "~1.0.3",
    "vueify": "9.4.0",
    "babel-core": "6.21.0",
    "babel-preset-es2015": "6.18.0",
    "babel-runtime": "6.20.0",
    "babel-plugin-transform-runtime": "6.15.0",
    "vue-hot-reload-api": "2.0.6",
    "aliasify":"2.1.0"
  },
  "author": "David Verrière",
  "license": "Apache version 2.0"
}

Les modules babel et vue-hot-reloading sont nécessaires à vueify.

Pour installer les paquets nodes js, tapez la commande

npm install  

Nous devons le script qui sera appelé par cordova au moment de la compilation. Ce script sera placé dans scripts/vueify-build.js

var fs = require('fs');  
var browserify = require('browserify');  
var vueify = require('vueify');  
var aliasify = require('aliasify');

aliasifyConfig = {  
  aliases: {
    "vue": "vue/dist/vue.js"
  },
  verbose: false
};

browserify('www/js/main.js')  
  .transform(aliasify, aliasifyConfig)
  .transform(vueify)
  .bundle()
  .pipe(fs.createWriteStream('www/js/bundle.js'));  

la transformation via aliasify est un changement de comportement entre vue js 1 et vue js 2, je l'explique ici

Il faut référencer ce script en tant que hook de l'événement before_compile dans le fichier config.xml :

 <hook type="before_compile" src="scripts/vueify-build.js" />

Voila à quoi doit ressembler votre dossier si vous avez suivi les différentes étapes :

On peut maintenant tester l'application et vérifier que tout fonctionne comme avant :

cordova build browser  
cordova run  browser  

Le composant affiche son message

Intégration du composant vue2-google-maps

Afficher un message c'est bien mais on va aller un peu plus loin grâce au composant vue2-google-maps réalisé par xkjyeah.

Pour installer le composant rajoutez les lignes suivantes dans les dependecies du fichier package.json

    "vue2-google-maps": "^0.4.7",
    "css-loader": "0.26.1",
    "marker-clusterer-plus": "2.1.4",
    "babelify": "7.3.0",

puis tapez

npm install  

On crée ensuite un composant dans js/components/googlemaps.vue avec le code de l'exemple fournit sur le npm du composant :

<template>  
    <gmap-map :center="center" :zoom="14" style="height: 600px" >
<gmap-marker v-for="m in markers" :position="m.position" :clickable="true" :draggable="true" @click="center=m.position"></gmap-marker>  
</gmap-map>  
</template>

<script>  
    import { load, Map, Marker } from 'vue2-google-maps';
    import Vue from 'vue';

    load('Votre Clé Google Maps ici', '');

    export default {
        components: {
            gmapMap: Map,
            gmapMarker: Marker
        },
        data() {
            return {
                center: { lat: 43.610394, lng: 3.874469 },
                markers: [{
                    position: { lat: 43.611210, lng: 3.876681 }
                }, {
                    position: { lat: 43.611668, lng:  3.880222 }
                }]
            }
        }
    }
</script>  

Attention quelques adaptations ont été nécessaires par rapport à l'exemple de l'auteur qui utilise webpack.

Pour créer une clé d'api google map vous devez aller sur le site https://developers.google.com/maps/documentation/geocoding/get-api-key?hl=fr

Il faut ensuite intégrer notre composant dans l'écran principal.
Dans le fichier index.html

<body>  
    <div id="app">
        <home-component>
        </home-component>
        <google-maps>
        </google-maps>
    </div>

il faut également modifier les autorisations pour que le composant aille cherché les ressources nécessaires à l'affichage de la carte chez google.
Toujours dans le fichier index.html :
modifier la balise meta http-equiv="Content-Security-Policy" de la sorte :

<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://*.gstatic.com/  https://*.googleapis.com/ 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com/; media-src *; script-src 'self' https://maps.googleapis.com/ 'unsafe-eval'">  

Enfin il faut modifier le fichier js/main.js afin d'instancier notre composant GoogleMaps :

var Vue = require('vue');  
var VueResource = require('vue-resource');  
var Home = require('./components/home.vue');  
var GoogleMaps = require('./components/googlemaps.vue');

Vue.use(VueResource);

var vm = new Vue({  
  components: {
    'home-component': Home,
    'google-maps': GoogleMaps
  }
}).$mount('#app');

on peut lancer l'application

cordova build browser  
cordova run browser  

On teste sous Android

cordova build android  
cordova run android  

Et voila :

Ressources

Cet article a été inspiré par l'article de Michael Viveros sur VueJs 1 et Cordova

Le code source de l'application est disponible sur GitHub