NodeJs : Exemples d’utilisation avec l’ORM Sequelize

Sequelize est un ORM pour node.js compatible avec différents moteurs de base de données comme Mysql, Sqlite…etc.
Dans cet article nous allons survoler quelques cas pratiques permettant de définir le modèle de la base, et d’effectuer des requêtes dessus.

Une fois installé nous allons commencer à structurer notre base de données en créant les différents objets la composant. Tout sera situé dans un fichier « Model.js » avec pour commencer les informations de connexion à la base (ici de type mysql) :

var Sequelize = require('sequelize');
var sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql',
logging: false,//passer a true pour voir les différentes requêtes effectuées par l'ORM
});
//on exporte pour utiliser notre connexion depuis les autre fichiers.
var exports = module.exports = {};
exports.sequelize = sequelize;

On va pouvoir créer notre modèle, partons sur un cas pratique simple, ou on va avoir des utilisateurs « User » qui ont un rôle « Role ». On déclare les tables et leurs champs de la manière suivante :

/**
 * ROLE
 */

const Role = sequelize.define('role', {
    id: {type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true},
    name: {type: Sequelize.STRING(255), allowNull: false},
},
        {tableName: 'role', timestamps: false, underscored: true}//par default "tableName" serait "roles" (au pluriel), "timestamps" crée 2 champs automatique pour les dates de création et de modification (très pratique si nécessaire) et "underscored" permet de créer automatiquement des champs de "relation" entre les tables de type "role_id" plutôt que "UserId".
);
exports.Role = Role;

/*
 * USER
 */

const User = sequelize.define('user', {
    id: {type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true},
    name: {type: Sequelize.STRING(255), allowNull: false, },
    email: {type: Sequelize.STRING(255), allowNull: false, unique: true},
},
        {tableName: 'user', timestamps: false, underscored: true}
);
exports.User = User;

On veux maintenant créer une relation entre ces 2 tables. L’utilisateur à un et un seul rôle. Pour cela il suffit d’ajouter la ligne suivante :

User.belongsTo(Role);//l'utilisateur à un rôle.

Cela va créer un champ « role_id » dans la table « user » (roleId si on laisse la configuration « underscored » à false par default).
Pour générer ces tables on peux utiliser la ligne suivante (tables générées seulement si elles n’existent pas)

sequelize.sync({logging: console.log});

Voyons maintenant comment récupérer nos données à travers ces objets :

    //on importe le modèle
    var Model = require('./Model');
    //recherche de tous les utilisateurs
    Model.User.findAll().then(users => {
        //on récupère ici un tableau "users" contenant une liste d'utilisateurs
        console.log(users);
    }).catch(function (e) {
        //gestion erreur
        console.log(e);
    });

La partie import du modèle et gestion des erreurs restant toujours la même, ci-dessous des exemple plus concis de différentes requêtes.

    //requête avec critère et ordre
    Model.User.findAll({
        where: {role_id: 2}, //on veux uniquement ceux qui ont le role "2"
        order: [['name', 'ASC']] //classer par ordre alphabétique sur le nom
    }).then(users => {
       //traitement terminé...
    });

    //requête d'un utilisateur par son identifiant avec inclusion de la relation "Role"
    let id =  19; //id
    Model.User.findById(id, {
        include: [{model: Model.Role}]
    }).then(user => {
        //on peux directement afficher le nom du rôle de l'utilisateur
        console.log(user.role.name);
    });

    //exemple de création d'un utilisateur, puis de sa suppression dans la foulée. Ce qui permet de voir comment effectuer des requêtes successives.
    Model.User.create({
        name: 'Test',
        email : 'test@testmail.com'
    }).then(user => {
        return user.destroy();
    }).then(destroy => {
        //traitement terminé...
    }).catch(function (e) {
        //gestion erreur
    });

    //exemple de requête d'update d'un utilisateur
    let id = 19;//id
    Model.User.update(
            {name: 'Numa'},
            {where: {id: id}}
    ).then(user => {
        //traitement terminé...
    });

Pour la suite et voir une utilisation un peu plus poussé, nous allons apporter des modifications à notre modèle. Pour l’instant on peux récupérer le rôle d’un utilisateur, mais on voudrait aussi pouvoir faire l’inverse. C’est à dire, à partir d’un rôle connaitre la liste des utilisateurs correspondant. Pour cela il nous faut ajouter une relation « hasMany » :

Role.hasMany(User);

Exemple de requête :

    //recuperations des utilisateurs correspondants au différents rôles
    Model.Role.findAll({include: [{model: Model.User}]}).then(roles => {
        //pour chaque role on peux parcourir la liste des ses utilisateurs
        roles.forEach((role) => {
            console.log(role.name);
            role.users.forEach((user) => {
                console.log(user.name);
            });
        });
    //...
    });

Revenons à nouveau sur notre modèle, et regardons comment indiquer une référence sur la même table. Ici nos utilisateurs « Operator » peuvent avoir d’autre utilisateurs qui sont leurs « Manager ».
On veux donc une clé manager_id sur notre table user. Et comme il sera intéressants de récupérer la liste des « Operators » d’un « Manager » nous créons aussi la liaison inverse « hasMany », Voici comment faire :

User.hasMany(User, {foreignKey: 'manager_id', as: 'Operators'});//l'utilisateur peux avoir des "Operators"
User.belongsTo(User, {foreignKey: 'manager_id', as: 'Manager'});//l'utilisateur peux avoir un "Manager"

Ici lors des « include » dans nos requêtes nous devrons répéter les éléments « as ». Nous allons aussi voir que nous pouvons imbriquer ces « include », et leur appliquer des critères.

    Model.User.findAll({
        include: [
            //j'inclus les roles de mon utilisateur
            {model: Model.Role},
            //mais aussi la liste de ses "Operators" qui ont comme valeur role_id ="1" je récupére également leur propre role.
            {model: Model.User, as: "Operators", where: {role_id: '149999900000000002'}, include: [{model: Model.Role}]}
        ]
    }).then(users => {
        users.forEach((user) => {
            console.log('----');
            console.log(user.name + " : " + user.role.name);
            console.log('----');
            user.Operators.forEach((operator) => {
                console.log(operator.name + " : " + operator.role.name);
            });
        });
    });

On peux bien sur aussi effectuer des requête brutes, et utiliser l’échappement de données dans celles-ci, voici un exemple :

    //parametre à echaper
    let param_name = '%a%';
    let param_role = 1;
    //sql brut
    let sql = 'SELECT (ROUND(id / 5)+123) as nimportequoi FROM user WHERE name LIKE $1 AND role_id > $2';
    Model.sequelize.query(sql, {bind: [param_name, param_role], type: Model.sequelize.QueryTypes.SELECT}).then(results => {
        console.log(results);
    });

Ce sera tout pour ce premier article au sujet de nodeJs et Sequelize !

MapBox : Utiliser le « map matching » pour tracer un itinéraire routier à partir de points GPS

Nous allons voir dans cet article comment utiliser l’API MapBox pour faire différentes choses :
– Tracer un trajet à partir de points GPS
– Tracer un itinéraire routier à partir de ces points GPS
– Afficher des points cliquables sur la carte
– Recentrer la carte autour de ces tracés/points

Le résultat que l’on va obtenir est visible ici

Pour commencer il vous faut un « token » d’accès à l’API que vous trouverez dans votre compte MapBox.

Commençons par inclure les fichiers css et js nécessaires à l’utilisation de l’API. Dans la balise « head » de notre page :

<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.50.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.50.0/mapbox-gl.css' rel='stylesheet' />

Le html minimum pour afficher la carte dans le « body » de la page : c’est un div avec un identifiant (ici « map ») ainsi qu’une largeur et une hauteur :

<div id="map" style="width: 100%; height: 400px;"></div>

Tout est prêt, on peux attaquer le code javascript. Nous partons ici du principe que nous avons une liste de coordonnées GPS sur lesquelles on veux travailler, voici celles de mon exemple :

//la liste de nos points GPS, attention dans l'ordre [LONGITUDE, LATITUDE] (et non l'inverse)
var coords = [
    [4.141553499740439, 44.052572457451014],
    [4.143273931900012, 44.05242402365157],
    [4.14427862409957, 44.05275366184478],
    [4.145185210746604, 44.05318932120335],
    [4.143211104911643, 44.053065948966925],
    [4.141692974609214, 44.05368666292508],
    [4.142165043395835, 44.05420327703502]
];

On initialise notre map sur le div #map, et au chargement on appelle nos différentes fonctions qui effectueront les traitements voulus (fonctions que l’on va créées une par une juste après)
Les 4 fonctions sont indépendantes, vous pouvez bien sur appeler uniquement celle qui vous intéresse.

//token mapBox
var accessToken = 'VOTRE-TOKEN-MAPBOX';
mapboxgl.accessToken = accessToken;
//on initialise notre map sur le div #map
var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v10',
    center: [4.141056, 44.050022399999996], //un centre initial [longitude, latitude] (facultatif)
    zoom: 13 //un zoom initial (facultatif)
});
//au chargement de la map
map.on('load', function () {
    //on recadre la carte en fonction de nos différents points GPS
    fitMap(map, coords);
    //on affiche le tracé reliant nos différents points GPS
    displayJourney(map, coords);
    //on affiche l'itinéraire routier correspondant à nos points GPS
    displayJourneyReshaped(map, coords);
    //on affiche des marqueurs à la position de nos points GPS, indiquant leur numéros et recentrant la carte sur eux au clic.
    placeMarkers(map, coords);
});

Pour que cela fonctionne il nous faut bien sur créer les fonctions correspondantes. On attaque avec la première « fitMap » qui va permettre de cadrer la carte afin que toutes les coordonnées qu’on lui fournit soit visible :

function fitMap(map, coords) {
    var bounds = coords.reduce(function (bounds, coord) {
        return bounds.extend(coord);
    }, new mapboxgl.LngLatBounds(coords[0], coords[0]));
    map.fitBounds(bounds, {
        padding: 30 //marge autour des points
    });
}

On passe à « displayJourney » qui trace une ligne droite entre chacun des points donnés :

function displayJourney(map, coords) {
    map.addLayer({
        "id": "journey", //identifiant unique de l'objet
        "type": "line",
        "source": {
            "type": "geojson",
            "data": {
                "type": "Feature",
                "properties": {},
                "geometry": {
                    "type": "LineString",
                    "coordinates": coords
                }
            }
        },
        "paint": {
            "line-color": "#888", //couleur de la ligne
            "line-width": 2 //epaisseur de la ligne
        }
    });
}

C’est déjà pas mal, mais on veux maintenant à la place de ces lignes droites, définir le trajet « réel » utilisable par un automobiliste (ou un cycliste / marcheur selon les paramètres) sur les chemins connus. Attention il vous faut moins de 100 points pour que cela fonctionne. Attention aussi, l’utilisation de cette fonctionnalité est limité a 1000 appel par MapBox avant de devenir payant. Il peut donc être intéressant de stoker le résultat si on doit l’afficher à plusieurs reprise.
Voici la fonction « displayJourneyReshaped » qui lance un appel ajax à l’API Map Matching de MapBox pour calculer l’itinéraire :

function displayJourneyReshaped(map, coords) {
    //on transforme nos coordonées en string pour l'appel de l'API
    var coordsString = coords.join(';');
    //choix du type d'itinéraire que l'on souhaite calculer (par exemple avec "walking" on ne fera pas le tour d'un rond point, avec "driving" si.
    var typeRoute = 'driving'; //cycling, walking, driving-traffic
    var directionsRequest = 'https://api.mapbox.com/matching/v5/mapbox/'+typeRoute+'/' + coordsString + '?geometries=geojson&access_token=' + accessToken;
    var xhr = new XMLHttpRequest();
    xhr.open('GET', directionsRequest);
    xhr.onload = function () {
        if (xhr.status === 200) {
            var response = JSON.parse(xhr.responseText);
            //on récupère la données calculé qui nous permettra d'afficher l'itinéraire
            var route = response.matchings[0].geometry;
            //add layer
            map.addLayer({
                id: 'journeyReshaped', //identifiant unique de l'objet
                type: 'line',
                source: {
                    type: 'geojson',
                    data: {
                        type: 'Feature',
                        geometry: route //utilisation de l'itinéraire
                    }
                },
                paint: {
                    'line-color': "#3399ff", //couleur de la ligne
                    'line-width': 4, //epaisseur de la ligne
                    'line-opacity': 0.7 //opacité de la ligne
                }
            });
        } else {
            //en cas d'erreur ajax
            console.log('Request failed.  Returned status of ' + xhr.status);
        }
    };
    xhr.send();
}

En bonus la fonction « placeMarkers » qui permet d’afficher des points cliquable sur la carte. Pour cela on commence par ajouter un peu de style css à notre page pour l’affichage de nos marqueurs ayant la classe « marker ». A mettre donc dans le style de votre page ou votre fichier css :

.marker {background: #888;width: 22px;height: 22px;border-radius: 50%;text-align: center;color:#fff;}
.marker:hover{background: #ff0;color:#000;}

Et enfin la fonction d’affichage et clic des différents points :

function placeMarkers(map, coords) {
    var markers = [];
    //pour chaque point GPS dans coords
    coords.forEach(function (coord, index) {
        //creation d'un div avec la classe 'marker' pour l'affichage du marker
        var el = document.createElement('div');
        el.className = 'marker';
        el.setAttribute('data-index', index);//on stocke son numéro pour l'utilisation au click
        //creer un élément pour indiquer le numéro du marquer dans celui-ci
        var content = document.createTextNode(index);
        el.appendChild(content);
        //on ajoute les marquers sur notre carte
        markers[index] = new mapboxgl.Marker({element: el}).setLngLat([coord[0], coord[1]]).addTo(map);
        //au clic sur chacun d'eux on recentre la carte sur sa position
        el.addEventListener("click", function (e) {
            map.flyTo({center: markers[e.target.dataset.index].getLngLat()});
        });
    });
}

Je pense que c’est un bon début pour travailler avec MapBox, maintenant à vous le tour !

Jquery : Trier des éléments en fonction d’attributs data-*

Nous allons voir aujourd’hui comment classer des éléments d’une page en fonctions de certaines données (nom, position) grâce à quelques lignes de javascript.
La démonstration du résultat que l’on va obtenir est visible ici

On commence par créer notre html de manière classique, comme d’habitude j’importe bootstrap et jquery pour la forme et la simplicité.
En plus de ça on affiche dans notre page une série d’élément ayant la classe « card » ainsi que des attributs « data-position », « data-note » et « data-name » pour stocker les valeurs sur lesquelles on va effectuer le classement. On affiche auparavant un « select » qui nous permettra de choisir l’ordre que l’on souhaite afficher.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <!-- CSS -->
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
        <link rel="stylesheet" href="./css/style.css">
        <!-- JS -->
        <script src="./js/jquery-3.3.1.min.js"></script>
        <script src="./js/custom.js"></script>
    </head>
    <body>
        <div class="container main-container">
            <div class="form-group">
                <label for="select-order">Classer par</label>
                <select class="form-control" id="select-order">
                    <option value="position">Position</option>
                    <option value="note">Note</option>
                    <option value="name">Nom</option>
                </select>
            </div>
            <div class="card-container row">
                <div class="item col-md-4" data-position="1" data-note="8" data-name="Paul">
                    <div class="card mb-3">
                        <img class="card-img-top" src="http://lorempixel.com/400/200/abstract" alt="illustration">
                        <div class="card-body">
                            <h3 class="card-title">Paul</h3>
                            <div class="card-subtitle">Position : 1</div>
                            <div class="card-text">Note : 8</div>
                        </div>
                    </div>
                </div>
                <!-- d'autres éléments avec différentes valeurs -->
            </div>
        </div>
    </body>
</html>

On va maintenant s’occuper du javascript qui nous permettra de réordonner les éléments, il s’agit de détecter le choix sélectionné et de lancer le tri (numérique, numérique inversé ou alphabétique) sur nos attributs data-*. Le code suivant est suffisant :

$(document).ready(function () {
    /* à la sélection du classement on met à jour */
    $(document).on("change", "#select-order", function () {
        updateOrder();
    });
    /* au chargement de la page on lance le classement */
    updateOrder();
});

function updateOrder() {
    var order = $("#select-order").val();
    if (order == 'position' || order == 'note' || order == 'name') {
        var divList = $(".card");
        if (order == 'position') { //tri numerique croissant
            divList.sort(function (a, b) {
                return  $(a).data(order) - $(b).data(order);
            });
        } else if (order == 'note') {//tri numerique decroissant
            divList.sort(function (a, b) {
                return $(b).data(order) - $(a).data(order);
            });
        } else if (order == 'name') { //tri alphabetique
            divList.sort(function (a, b) {
                var compA = $(a).data(order).toUpperCase();
                var compB = $(b).data(order).toUpperCase();
                return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
            });
        }
        $(".card-container").html(divList);
    }
}

Javascript : Mettre en place un lecteur audio avec « AmplitudeJS »

Aujourd’hui nous allons découvrir AmplitudeJS qui permet de mettre en place sur vos page un lecteur audio flexible et complètement personnalisable. Comme on peux le voir sur leurs exemples avec un peu de travail on peux avoir des résultats très sympas.

Avant d’en arriver la, je vous propose un exemple bien plus simple. Le résultat est visible ici et il suffit de regarder le code source de la page pour comprendre le fonctionnement.

Le visuel obtenu sera le suivant, mais graphiquement tout est possible, il n’y à pas de contraintes à ce niveau.

Quelques explications supplémentaires ci-dessous.

Pour commencer on va inclure la « library » « amplitude.js », ainsi que « bootstrap », « font-awesome » et un fichier « styles.css » pour la mise en page.

    <!-- CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css" >
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
    <link rel="stylesheet" href="./css/style.css">
    <!-- JS -->
    <script src="./js/amplitude.min.js"></script>

Dans le corps de la page mettons en place notre player en utilisant les éléments « amplitude », les boutons « bootstrap » et les icones « font-awesome » :

    <div class="glob-player">
        <div class="glob-btn">
            <span class="amplitude-prev btn btn-primary"><i class="fa fa-step-backward" aria-hidden="true"></i></span>
            <span class="btn amplitude-play-pause btn-primary" amplitude-main-play-pause="true"><i class="fa fa-play" aria-hidden="true"></i> <i class="fa fa-pause" aria-hidden="true"></i></span>
            <span class="my-next-btn amplitude-next btn btn-primary"><i class="fa fa-step-forward" aria-hidden="true"></i></span>
        </div>
        <div>
            <div class="meta-name" amplitude-song-info="name" amplitude-main-song-info="true"></div>
            <progress class="amplitude-song-played-progress" amplitude-main-song-played-progress="true" id="song-played-progress"></progress>
        </div>
    </div>

On ajoute juste quelques ligne pour le style dans notre fichier « styles.css »

/* player */
.glob-player{padding: 10px;border-radius: 5px;background: #000;}
.glob-player .glob-btn{text-align: center;}
.glob-player .meta-name{color:#fff;text-align: center;padding:10px;}
/*progres-bar*/
.amplitude-song-played-progress {
    -webkit-appearance: none;-moz-appearance: none;appearance: none;
    background-color: #ccc;width: 100%;height: 5px;display: block;border: none;
}
progress.amplitude-song-played-progress[value]::-webkit-progress-bar {
    background-color: #0069d9;
}
progress.amplitude-song-played-progress[value]::-moz-progress-bar {
    background-color: #0069d9;
}
progress.amplitude-song-played-progress[value]::-webkit-progress-value {
    background-color: #0069d9;
}

Il nous suffit maintenant d’uploader nos MP3 dans le dossier que l’on souhaite et d’indiquer à « amplitude » de les prendre en charge.

<script>
    Amplitude.init({
        "songs": [
            {
                "name": "Le bal masqué",
                "artist": "Opium du peuple",
                "album": "La révolte des opiumettes",
                "url": "./song/le-bal-masque.mp3",
            },
            {
                "name": "Poupée de cire, poupée de son",
                "artist": "Opium du peuple",
                "album": "La révolte des opiumettes",
                "url": "./song/poupee-de-cire.mp3",
            }
        ],
        callbacks: {
            //pour démarrer la lecture à cuaque fois que l'on passe au morceau suivant ou préc
            song_change: function () {
                Amplitude.play();
            }
        }
    });
</script>

jQuery : Sélection des départements français sur une carte

Dans cet article nous allons voir comment transformer un select multiple contenant la liste des département français en carte permettant de sélectionner/désélectionner les départements en cliquant dessus.

Nous allons passer d’un select multiple classique comme celui-ci :

à un sélecteur visuel comme ci-dessous :

Une page de démonstration est visible ici.

Pour cela nous utiliseront jQuery, le plugin jVectorMap et sa carte pour les départements français disponible ici

On attaque par la mise en place du code html, il s’agit d’un select multiple classique, on va juste prendre soin de lui donner un identifiant « map-selector » et de l’englober dans un « div » parent. Pour l’utiliser avec la carte, nous allons donner pour chaque département la valeur « FR-CODE_DU_DEPARTEMENT », exemple « FR-34 » pour l’Hérault.

<div>
    <select id="map-selector" name="departements" multiple="multiple">
        <option value="FR-01">Ain</option>
        <option value="FR-02">Aisne</option>
        <option value="FR-03">Allier</option>
        <!-- etc... -->
    </select>
</div>

On inclus dans la page les script nécessaires (jquey, jvectormap, la carte, un fichier « map-selector.js » ou l’on mettra notre code ainsi qu’une feuille de styles css.)

<link rel="stylesheet" href="jquery-jvectormap-2.0.3.css">
<link rel="stylesheet" href="style.css">
<script src="jquery-3.2.1.min.js"></script>
<script src="jquery-jvectormap-2.0.3.min.js"></script>
<script src="jquery-jvectormap-fr-merc.js"></script>
<script src="map-selector.js"></script>

Tout est prêt ! Il nous reste plus qu’à coder notre fichier « map-selector.js » pour mettre en place le système. Le code de quelques lignes commenté ci-dessous :

$(document).ready(function () {
    //on masque le select classique
    $("#map-selector").css("display", "none");
    //on ajoute un div #container-map-selector qui contiendra la carte
    $("#map-selector").parent().append("<div id='container-map-selector'></div>");
    //on initie la carte sur cet élément
    var map = new jvm.Map({
        container: $("#container-map-selector"),
        map: 'fr_merc',
        regionsSelectable: true,
       //à chaque clic sur un département
        onRegionSelected: function () {
            //on vide le select
            $("#map-selector").val("");
            //et on sélectionne chaque options correspondant au département sélectionné sur la carte
            $.each(map.getSelectedRegions(), function (index, region) {
                $("#map-selector option[value=" + region + "]").prop("selected", true);
            });
        }
    });
    //au départ si des options du select sont présélectionnés, on les sélectionnes sur la carte
    $("#map-selector option:selected").each(function () {
        map.setSelectedRegions($(this).val());
    });
});

Javascript : Faire parler le navigateur

les navigateurs récents possèdent une fonctionnalité JavaScript permettant de leur demander de lire un texte.

Il s’agit de la « Web Speech API », et il suffit des quelques lignes suivantes pour demander à votre navigateur de déclamer un texte dans la langue de votre souhait

//création de l'objet message avec le texte de votre choix
var message = new SpeechSynthesisUtterance("Mon message texte");
//on paramètre la langue
message.lang = 'fr-FR';
//on lance la lecture du message
window.speechSynthesis.speak(message);

Je ne résiste pas à vous proposer une page de démonstration pour faire mumuse !

jQuery : rendre l’option d’un select permanente avec le plugin « select2 »

Le plugin jQuery « Select2 » permet d’améliorer les champs de type « select » (liste déroulante). Une de ces principales fonctionnalité et d’intégrer un champs de recherche à la liste. Très pratique pour retrouver et sélectionner un éléments dans une longue liste.

Nous allons voir ci-dessous comment mettre en place une astuce afin qu’une option de la liste reste toujours disponible. Afin qu’un résultat de secours soit toujours sélectionnable quand on recherche un élément inexistant.

Notre exemple consiste à sélectionner un instrument dans la liste, avec la possibilité toujours visible de sélectionner « Autre instrument » si celui que l’on recherche n’est pas disponible. On en profitera pour afficher des champs supplémentaires quand cette option est sélectionné afin de pouvoir récolter les informations concernant l’instrument réellement recherché.

L’exemple est disponible ici

Comme d’habitude on commence par inclure jQuery, le plugin select2 et un fichier qui contiendra notre script dans le head de la page :

<!-- jQuery -->
<script src="./js/jquery-3.2.1.min.js"></script>
<!-- select2 -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.4/css/select2.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.4/js/select2.min.js"></script>
<!--custom -->
<script src="./js/main.js"></script>

On met en place notre html de manière classique, avec une option de valeur « autre » dans la liste des option de notre champs « select »

<div class="form-group">
    <label for="instrument">Instrument</label>
    <select id="instrument" class="form-control" name="instrument">
        <option value="1">accordéon</option>
        <option value="2">bandonéon</option>
        <option value="3">banjo</option>
        <option value="4">batterie</option>
        <!-- Toutes les autres options -->
        <!-- Notre option de valeur "autre" -->
        <option value="autre">Autre instrument</option>
    </select>
</div>
<!-- les autre champs à afficher/masquer selon le choix dans la liste précédente -->
<div class="form-group">
    <label for="otherInstrumentCategory">Catégorie de l'instrument</label>
    <select class="form-control" id="otherInstrumentCategory" name="otherInstrumentCategory" >
        <option value="1">Instrument à corde</option>
        <option value="2">Percussion</option>
        <option value="4">Instrument à vent</option>
        <option value="5">MAO</option>
        <option value="6">Autre</option>
    </select>
</div>
<div class="form-group">
    <label for="otherInstrument">Nom de l'
instrument</label>
    <input type="text" id="otherInstrument" name="otherInstrument" class="form-control">
</div>

Et pour finir notre javascript qui va permettre de toujours laisser en place l’option « autre instrument » malgré une recherche, ainsi que d’afficher ou masquer les champs supplémentaires. Rien de bien complexe, suivez les commentaires dans le code pour comprendre.

$(document).ready(function () {
    /*au changement du champs instrument on met à jour l'affichage des champs*/
    $(document).on("change", "#instrument", function () {
        updateFields();
    });
    /*initialisation du champs instrument, pour l'utilisation du plugin select2 avec un filtre de recherche particulier*/
    $('select#instrument').select2({matcher: select2matchCustomInstrument});
    /*initialisation de 'laffichage des champs*/
    updateFields();
});

/*On va utiliser le système de recherche classique du plugin select2*/
var defaultMatcher = $.fn.select2.defaults.defaults.matcher;
function select2matchCustomInstrument(params, data) {
    /* Si l'option est "Autre" on l'affiche toujours */
    if (params.term && data.id == "autre") {
        return data;
    }
    /* Sinon utilisation de la recherche classique */
    return defaultMatcher(params, data);
}

/*la fonction de mise à jour de l'affichage des champs*/
function updateFields() {
    /* Si la valeur du champ instrument est "autre" on affiche les champs supplémentaire, sinon on les masque */
    if ($("#instrument").val() == "autre") {
        $("#otherInstrumentCategory").parents(".form-group").css("display", "block");
        $("#otherInstrument").parents(".form-group").css("display", "block");
    } else {
        $("#otherInstrumentCategory").parents(".form-group").css("display", "none");
        $("#otherInstrument").parents(".form-group").css("display", "none");
    }
}

Ce plugin peux être adapté de façon diverse, comme par exemple pour générer un font-picker comme vu dans un article précédent.

jQuery : soumettre un formulaire contenant des fichiers grâce à FormData

Aujourd’hui il est possible d’uploader des fichiers en ajax grâce à XMLHttpRequest Niveau 2 et l’interface FormData de javascript. Pour l’utiliser avec la méthode ajax de jQuery il suffit de lui passer comme données l’objet FormData et de configurer les options correctement comme ci-dessous.

    //au clic du bouton submit des formulaires ayant la classe "form-ajax"
    $(document).on("submit", ".form-ajax", function (e) {
        e.preventDefault();//on bloque l'envoie classique
        var formData = new FormData($(this)[0]);//récupération des données du formulaire dans l'objet FormData
        //appel ajax jQuery
        $.ajax({
            url: '/ma/route/ajax',
            method: "POST",
            data: formData,
            processData: false,//ne pas oublier cette option
            contentType: false,//ne pas oublier cette option
            error: function (msg, string) {
                //gestion des erreurs
            },
            success: function (response) {
                //gestion de la réponse
            }
        });
    });

De cette manière vous pouvez traiter coté serveur le formulaire de la même manière que si il n’était pas en ajax.

Pour un exemple de traitement d’upload d’image coté serveur en php vous pouvez vous référer a cet article

PHP & Google Map API : Récupérer les détails d’une adresse (latitude, longitude, département, région…)

La semaine dernière nous avons vu une fonction permettant de calculer la distance entre deux coordonnées GPS, mais comment récupérer ces coordonnées (et d’autres informations) à partir d’une adresse ? Nous allons voir comment faire avec l’API google Map, il vous faudra tout d’abord récupérer une clé pour les appel à l’API (par ici).

Ensuite vous pouvez utiliser la classe et la fonction ci-dessous afin de récupérer les informations (adresse complète, latitude, longitude, vile, code postal, département, région, pays) correspondant à l’adresse passé au format texte.

class GmapApi {

    private static $apikey = 'VOTRE_API_KEY';

    public static function geocodeAddress($address) {
        //valeurs vide par défaut
        $data = array('address' => '', 'lat' => '', 'lng' => '', 'city' => '', 'department' => '', 'region' => '', 'country' => '', 'postal_code' => '');
        //on formate l'adresse
        $address = str_replace(" ", "+", $address);
        //on fait l'appel à l'API google map pour géocoder cette adresse
        $json = file_get_contents("https://maps.google.com/maps/api/geocode/json?key=" . self::$apikey . "&address=$address&sensor=false&region=fr");
        $json = json_decode($json);
        //on enregistre les résultats recherchés
        if ($json->status == 'OK' && count($json->results) > 0) {
            $res = $json->results[0];
            //adresse complète et latitude/longitude
            $data['address'] = $res->formatted_address;
            $data['lat'] = $res->geometry->location->lat;
            $data['lng'] = $res->geometry->location->lng;
            foreach ($res->address_components as $component) {
                //ville
                if ($component->types[0] == 'locality') {
                    $data['city'] = $component->long_name;
                }
                //départment
                if ($component->types[0] == 'administrative_area_level_2') {
                    $data['department'] = $component->long_name;
                }
                //région
                if ($component->types[0] == 'administrative_area_level_1') {
                    $data['region'] = $component->long_name;
                }
                //pays
                if ($component->types[0] == 'country') {
                    $data['country'] = $component->long_name;
                }
                //code postal
                if ($component->types[0] == 'postal_code') {
                    $data['postal_code'] = $component->long_name;
                }
            }
        }
        return $data;
    }

}

Par exemple en appelant la fonction de cette manière :

$data = GmapApi::geocodeAddress('151 avenue du pont trinquat 34070 Montpellier');
//on affiche les différente infos
echo '<ul>';
foreach ($data as $key=>$value){
    echo '<li>'.$key.' : '.$value.'</li>';
}
echo '</ul>';
/* va afficher
address : 151 Avenue du Pont-Trinquat, 34000 Montpellier, France
lat : 43.6008177
lng : 3.8873392
city : Montpellier
department : Hérault
region : Occitanie
country : France
postal_code : 34000
*/

On peux maintenant utiliser cette fonction pour trouver la distance entre 2 adresses, grâce à notre fonction de l’article précédent. Par exemple :

$data1 = GmapApi::geocodeAddress('151 avenue du pont trinquat 34000 montpellier');
$data2 = GmapApi::geocodeAddress('Avenue des cévennes 30360 vézénobre');
echo round(Misc::distance($data1['lat'], $data1['lng'], $data2['lat'], $data2['lng'])).' Km';
//Affiche : 54 Km

jQuery : Créer un « font picker » avec le plugin Select2

Créer une liste déroulante (select) proposant un choix parmi plusieurs polices de caractères, tout en permettant de prévisualiser le style d’écriture de la police dans cette liste n’est pas la chose la plus évidente à mettre en place.
Ci-dessous une image présentant le résultat recherché. Et ici une page de démonstration

font-picker

Pour réaliser cela nous allons utiliser le plugin jQuery select2 qui permet de transformer les listes déroulantes. On peux grâce à lui avoir un champs de recherche dans la liste -ce n’est pas la fonctionnalité qui nous intéresse- on peux aussi donner du styles aux options, c’est ce que l’on va utiliser.

On commence donc par importer jQuery et ce plugin sur notre page, ainsi que les polices que l’on va proposer, j’utilise ici l’outil google fonts pour cela.

<!-- google fonts -->
<link href="https://fonts.googleapis.com/css?family=Alfa+Slab+One|Amatic+SC|Anton|Cinzel|Cutive+Mono|Frijole|Great+Vibes|Indie+Flower|Josefin+Sans|Just+Another+Hand|Lobster|Monoton|Pacifico|Permanent+Marker|Playfair+Display|Raleway|Righteous|Roboto+Condensed|Roboto+Slab|Saira+Semi+Condensed|Sedgwick+Ave+Display" rel="stylesheet">
<!-- jquery + select2 -->
<script src="./js/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css" rel="stylesheet" />
<!-- le script nous permettant de mettre en place le font picker -->
<script src="./js/font-picker.js"></script>

Dans la page on utiliser une balise select classique à laquelle on attribue un identifiant particulier (ici « select-font »)

<select id="select-font" class="form-control">
 <option value="Arial">Arial</option>
 <option value="Times New Roman">Times New Roman</option>
 <option value="Alfa Slab One">Alfa Slab One</option>
 <option value="Amatic SC">Amatic SC</option>
 <option value="Anton">Anton</option>
 ....
</select>

Et voici ci-dessous les quelques lignes de javascript permettant de faire fonctionner le système.

$(document).ready(function () {
    //on applique le plugin "select2" sur notre liste deroulante en lui indiquant d'utiliser un template particulier
    $("#select-font").select2({templateResult: formatFont}).on('change', function (e) {
        updateSelectFontStyle($(this));
    });
    //on initialise la liste au chargement de la page en fonction de la police sélectionné.
    updateSelectFontStyle($("#select-font"));
});

/**
 * Permet d'appliquer la bonne police sur l'option du select2
 */

function formatFont(opt) {
    if (!opt.id) {
        return opt.text;
    }
    var $state = $('<span style="font-family:\'' + opt.element.value + '\';"> ' + opt.text + '</span>');
    return $state;
}

/**
 * Donne la font family sélectionné à l'élément passé en paramètre
 */

function updateSelectFontStyle($el) {
    var font = $el.val();
    var id = '#select2-' + $el.attr("id") + '-container';
    $(id).css("font-family", "'" + font + "'");
}

Terminé !