Retour : Page Principale > sommaire applications botanique > Appli parcours

Aide pour les développeurs


Base de données

La base de données utilise webSQL. Pour plus de détails, voir la fiche explicative sur la page du widget de saisie mobile dans la section Photographies.

Initialisation

Deux fichiers CSV sont fournis au script de construction des fichiers de remplissage de la base de données : un fichier, bdd_parcours.csv, contenant les informations sur les parcours et un fichier, bdd_flore.csv, contenant la liste des espèces. Les deux fichiers sont analysés en même temps pour définir l'appartenance d'une espèce à un parcours. L'appel se fait avec la commande suivante :
/opt/lampp/bin/php cli.php flora -a completerFlore

Le script crée quatre fichiers en sortie : parcours.csv, espece.csv, critere.csv et avoir_critere.csv. Les entêtes des deux fichiers d'entrée sont :
Parcours : nom parcours;Latitude géocentre;Longitude géocentre;Fichier carte;Photo 1;Photo 2;description
//Il est possible de rajouter des photos en les séparant par une virgule dans l'une des deux colonnes dédiées.

Espèce : nn;morpho:morphologie;morpho:type_fleur;morpho:couleur_fleur;morpho:forme_feuille;pheno:mois_feuillaison_debut;pheno:mois_feuillaison_fin;pheno:mois_floraison_debut;pheno:mois_floraison_fin;pheno:mois_fructification_debut;pheno:mois_fructification_fin;parcours:Lez;parcours:Aqueduc;parcours:Champ de Mars;parcours:<...>

L'ordre des critères de détermination importe peu. En revanche, il est fortement souhaité (voire impératif) de placer la liste des parcours à la fin du fichier avec la mention parcours: devant le nom. De plus, le nom donné doit être le même que celui du fichier de parcours.

Le script complète les informations des espèces en récupérant les données suivantes : nom scientifique, référentiel, nom de la famille, numéro taxonomique, premier nom vernaculaire français, description (Texte wiki > Coste > Baseflor) ainsi que les deux images les mieux notées. A noter : penser à récupérer les images et à les placer dans le dossier img/.
Attention ! Pour les pictogrammes de la clef de détermination, les noms de ceux-ci doivent être indiqués à la main dans le fichier critere.csv et sont récupérées depuis le dossier css/images/.

Attention ! L'intégration des légendes des photos, notamment pour les parcours, n'a pas été prévue. A voir.


Caractéristiques

- Si un parcours est défini dans bdd_parcours.csv mais qu'aucune espèce de bdd_flore.csv n'appartient à ce parcours, celui-ci ne sera pas pris en compte dans le fichier final (le parcours ne sera pas disponible dans l'application).

RĂ©initialisation

Pour forcer la réinitialisation de la base de données, il faut changer le numéro de version (var VERSION_APP) du fichier principal.

Backbone

Cet excellent tutoriel que j'ai découvert après en avoir besoin, résume très bien l'utilisation conjointe de Backbone avec Phonegap.
Backbone définit et utilise les termes suivants :
- le modèle : les modèles sont au centre de toutes les applications Javascript car ils contiennent les données ainsi que l'environnement qui accompagne ces données telles que les opérations de vérification, de conversion, d'accès ou les propriétés qui sont calculées. Avec Backbone, un modèle est une entité unique. Nous avons les modèles suivants : Parcours, Espèce, Critère, Observation, Photo et Utilisateur.
- la collection : en plus du modèle, Backbone utilise les collections, liées à un modèle, et qui représentent une liste d'éléments de ce modèle. Les collections sont généralement renvoyées par la base de données lors que la requête demandée renvoie plusieurs modèles.
- la vue : Backbone donne à la vue un rôle passif qui consiste à "écouter" les évènements produits par le DOM et/ou envoyés par une collection ou un modèle. En plus de cela, la vue est chargée d'afficher à l'utilisateur les données qu'elle reçoit.
- le contrôleur : également appelé routeur, il analyse l'URL demandée par l'application, appelle le modèle concerné et contacte la vue avec la réponse de ce dernier.
- les DAO (Data Access Objects) : chaque table de la base est gérée par un DAO et chaque modèle de l'application est lié à un DAO.

Initialisation

'use strict';
var ___FC = {
	models: {},
	views: {},
	utils: {},
	dao: {}
};


DAO

___FC.dao.ParcoursDAO = function(db) {
	this.db = db;
};
_.extend(___FC.dao.ParcoursDAO.prototype, {
	findById: function(id, callback) {
		this.db.transaction(function(tx) {
			var sql = 
				"SELECT id, nom, latitude_centre, longitude_centre, fichier_carte, description, photos, ce_critere " +
				"FROM parcours " +
				"WHERE id = :id_parcours";
			tx.executeSql(sql, [id], function(tx, results) {
				callback(results.rows.length === 1 ? results.rows.item(0) : null);
			});
		};
	},
	
	//fonction d'initialisation de la base de données
	populate: function(callback) { 
		[...]
	}
});
_.extend(___FC.dao.ParcoursDAO.prototype, ___FC.dao.baseDAOBD);


Modèle et collection

___FC.models.Parcours = Backbone.Model.extend({
	dao: ___FC.dao.ParcoursDAO,
	initialize: function() {	}
});
___FC.models.ParcoursCollection = Backbone.Collection.extend({
	dao: ___FC.dao.ParcoursDAO,
	model: ___FC.models.Parcours,

	findAll: function() {
		var parcoursDAO = new ___FC.dao.ParcoursDAO(___FC.db),
			self = this;
		parcoursDAO.findAll(function(data) {
			//console.log('ParcoursCollection | findAll ', data);
			self.reset(data);
		});
	}
});


ContrĂ´leur/routeur

___FC.Router = Backbone.Router.extend({
	routes: {
		'parcours/:id_parcours' : 'parcoursDetails'
// une variable signalée par deux points (:) sera transmise en paramètre à la fonction demandée
	},

	parcoursDetails: function(id) {
//id contient la valeur de :id_parcours dans la route définie ci-dessus
		var parcours = new ___FC.models.Parcours({id: id}),
			self = this;
//avec le paramètre id passé dans le modèle, la fonction sync va automatiquement renvoyer les résultats de findById du DAO Parcours
		parcours.fetch({
			success: function(data) {
//la variable data contient les résultats de l'appel
				self.slidePage(new ___FC.views.ParcoursPage({model: data}).render());
			}
		});
	}


Vue

___FC.views.ParcoursPage = Backbone.View.extend({
	initialize: function(data) {
		this.model.bind('reset', this.render, this);	
		this.template = _.template(___FC.utils.templateLoader.get('parcours-page'));
	},
	
	render: function(eventName) {
		var arr_photos = new Array(),
			temp_photos = String(this.model.attributes.photos).split(',');
		for (var i = 0; i < temp_photos.length; i++) {
			if (temp_photos[i] != '') {
				arr_photos.push(temp_photos[i]);
			}
		}
		this.model.attributes.photos = arr_photos;
		$(this.el).html(this.template(this.model.toJSON()));
//this.model.toJSON crée des variables en fonction du contenu de this.model.attributes
		return this;
	}
});


Template

Le principal du single page est simple, le contenu des templates est injecté en AJAX dans un élément de la page principale. Ici, il s'agit de la div d'identifiant content. Un changement de "page" implique la perte de l'environnement lié au template. Toutes les actions qui ont un comportement global (modales par exemple) doivent être définis dans ce fichier (en dehors de la div) et appelées à loisir dans les templates. Les balises de Backbone en HTML sont <% et %>. <%= ... %> permet d'afficher la valeur de la variable demandée.
<h4 class="text-center"><i><%= nom %></i></h4>
	<img src="img/<%= fichier_carte %>" />
	<% _.each(photos, function (photo, i) { %>
		<img src="img/<%= photo %>" width="100"/>
		LĂ©gende
	<% }); %>


Avec webSQL

Pour le tutoriel que j'ai suivi concernant l'utilisation de webSQL avec Phonegap, c'est par ici !
Attention, cette étape est très importante ! Il faut remplacer la fonction Backbone.sync pour aller récupérer dans la base de données locale. Cette étape n'est pas franchement claire dans ma tête alors j'ai fait comme j'ai pu.

Backbone.sync = function(method, model, options) {
	var dao = new model.dao(___FC.db);
	
	if (method === 'read') {
		if (model.id) {      
 //si le paramètre id est présent, appeler la fonction findById
//le paramètre id est présent dans les modèles liés aux parcours, aux espèces et aux observations
			dao.findById(model.id, function(data) {
				options.success(data);
			});
		} else {
			if (model.id_obs) {
 //si le paramètre id_obs est présent, appeler la fonction findByObs
//le paramètre id_obs est présent uniquement dans le modèle lié aux photos
				dao.findByObs(model.id_obs, function(data) {
					options.success(data);
				});
			} else {
				dao.findAll(function(data) {
					options.success(data);
				});
			}
		}
	}
};


Phonegap

Phonegap Build est un service en ligne officiel d'Adobe qui propose aux utilisateurs de compiler automatiquement une application web en applications natives sur toutes les plateformes compatibles en un seul clic. Celui-ci est lié à un dépôt gitHub qui contient de référence un fichier config.xml qui demande les accès aux fonctionnalités du support, indique les plugins phonegap à prendre en compte, définit les icones et les écrans d'accueil pour la plupart des systèmes mobiles et gère les règles d'accès à des domaines externes. Pour plus de détails, voir le fichier config.xml de Flora Clapas ou la documentation officielle.
Pour l'installation de phonegap en local, plus de détails sont disponibles sur cette page

Device Ready

Pour pouvoir utiliser phonegap, il faut impérativement définir une fonction comme suit (à adapter selon les besoins) :
var fileSystem,
	pictureSource,
	destinationType;		
				
document.addEventListener('deviceready', onDeviceReady, false);
function onDeviceReady() {
	pictureSource = navigator.camera.PictureSourceType;
	destinationType = navigator.camera.DestinationType;
	window.requestFileSystem(LocalFileSystem.PERSISTENT, 0,
		function(object) {
			fileSystem = object;
		}, 
		function(error) {
			alert('Le système de fichiers est inaccessible.');
		}
	);
}

Attention, il est impératif d'ajouter la ligne
<script src="phonegap.js" type="text/javascript" ></script>
dans le fichier principal pour la compilation.


Utilisation de la géolocalisation

if (navigator.geolocation) {
	navigator.geolocation.getCurrentPosition(surSuccesGeoloc, surErreurGeoloc);
}
var surSuccesGeoloc = function(position) {
    alert('Latitude: '          + position.coords.latitude          + '\n' +
            'Longitude: '         + position.coords.longitude         + '\n' +
            'Accuracy: '          + position.coords.accuracy          + '\n' +
//les valeurs suivantes ne sont (encore) pas reconnues sur mobile
            'Altitude: '          + position.coords.altitude          + '\n' +
            'Altitude Accuracy: ' + position.coords.altitudeAccuracy  + '\n' +
            'Heading: '           + position.coords.heading           + '\n' +
            'Speed: '             + position.coords.speed             + '\n' +
            'Timestamp: '         + position.timestamp                + '\n');
};
function surErreurGeoloc(error) {
    alert('code: '    + error.code    + '\n' +
            'message: ' + error.message + '\n');
}

Pour plus de détails, rendez-vous sur la page officielle phonegap.

Utilisation de l'appareil photo

var options = { 
	destinationType: destinationType.FILE_URI,                  //renvoie une URI plutĂ´t que la base64
	encodingType: Camera.EncodingType.JPEG               //force le type de l'image (JPEG ou PNG)
};
if (this.id == 'chercher-photos') {
	options.sourceType = pictureSource.PHOTOLIBRARY;         //accès à la galerie photo plutôt que l'appareil photo
}
navigator.camera.getPicture(
	onPhotoSuccess, 
	function(message){
		console.log('CAMERA failed because: ' + message);
	},
	options
);
function onPhotoSuccess(imageData){
	[...]
}

Pour plus de détails et plus d'options, rendez-vous sur la page officielle phonegap.

Utilisation du système de fichiers

var REPERTOIRE = 'FloraClapas';
function onPhotoSuccess(imageData){
	fileSystem.root.getDirectory(REPERTOIRE, { create: true, exclusive: false }, function(dossier) {
//dossier contient la référence vers REPERTOIRE dans la mémoire du téléphone !
		var fichier = new FileEntry();    //création d'un nouveau fichier
		fichier.fullPath = imageData;   //chemin de ce fichier (ici, lien renvoyé par l'appareil photo)
//déplacement du fichier-image avec un nom unique dans le dossier REPERTOIRE
		fichier.copyTo(dossier, (new Date()).getTime()+'.jpg', surPhotoSuccesCopie, surPhotoErreurAjout);
	}, surPhotoErreurAjout);
}

Attention ! Si l'option exclusive est utilisée (valeur = true) avec l'option create = true, cela annule le processus de création si le fichier (ici le dossier) existe déjà. Ca fait échouer la fonction quoi.


Spécificité Android

Pour pouvoir compiler l'application sous Android, il faut impérativement "dériver" le code de index.html dans un autre fichier, par exemple main.html. Le code de index.html est une redirection vers ce fichier.
<!doctype html>
<html>
  <head>
   <title>tittle</title>
   <script>
     window.location='./main.html';
   </script>
  <body>
  </body>
</html>


Mise Ă  jour

Pour éviter l'initialisation de la base de données à chaque lancement, un système de version très simple a été mis en place avec le local storage, par comparaison avec la valeur donnée dans le fichier main.hml. Lors d'un changement dans la base de données, il suffit simplement de changer cette valeur (et accessoirement de relancer la compilation sur phonegap build) pour que les tables liées aux parcours, aux espèces, aux critères et à la connexion espèces/critères soient vidées et reconstruites.

Visualisation

La visualisation de l'application par navigateur n'est possible que sous Chrome qui est le seul à (encore) reconnaître les appels à webSQL (non soutenu par le W3C depuis 2010). Pour simuler une interface mobile, il suffit d'installer le petit outil ripple dans Chrome.