Archives mensuelles : octobre 2017

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 !

Symfony : Obtenir un enregistrement aléatoire avec doctrine

Doctrine ne permet pas par défaut d’obtenir un enregistrement aléatoire, ce qui est pourtant en principe très simple avec mysql. Par contre il est possible de faire une requête native et de demander a doctrine de « mapper » les résultats. Il devient alors facile de récupérer notre enregistrement aléatoire sous le format habituel (entité doctrine). Voici ci-dessous la méthode d’un repository permettant de la faire.

class ExempleRepository extends \Doctrine\ORM\EntityRepository {

    public function findRandom() {
        $rsm = new \Doctrine\ORM\Query\ResultSetMappingBuilder($this->getEntityManager());
        $rsm->addRootEntityFromClassMetadata(\AppBundle\Entity\Exemple::class, 'exemple');
        $sql = "SELECT * FROM exemple ORDER BY RAND() LIMIT 1";
        return $this->getEntityManager()->createNativeQuery($sql, $rsm)->getOneOrNullResult();
    }

}

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.

Symfony : Automatiser des actions avec les « Event listeners » de doctrine

Le framework Symfony couplé à l’ORM doctrine permet de créer des « Event Listeners », c’est à dire connaitre dés qu’il y’a un événement (création/modification/suppression) sur une entité. C’est un outil très intéressant pour automatiser des actions quand certaines propriétés d’une entité sont mis à jour.

Prenons un exemple concret : Dans les semaines précédentes, nous avons vu comment récupérer les coordonnées d’une adresse grâce à l’API google map. Afin de limiter les appels à l’API google map (payants si un certain quota et dépassé) nous allons enregistrer ces coordonnées en base de données pour pouvoir les réutiliser directement. Il nous faut cependant les enregistrer/mettre à jour à chaque fois qu’une adresse est crée ou modifiée. C’est ici que nous faisons intervenir les Event Listener.

Commençons par configurer nos listeners dans les services (fichiers services.yml du dossier /app/config/) de symfony.

services:
   #...
    #LISTENER DOCTRINE#
    app.listener.doctrineevent
:
        class
: AppBundle\EventListener\DoctrineEvent
        tags
:
            - { name
: doctrine.event_listener, event: prePersist, lazy: true }
            - { name
: doctrine.event_listener, event: preUpdate, lazy: true }

La configuration indique que nous allons écouter les événements « PrePersist » et « PreUpdate ». Ce qui permettra d’effectuer notre action lors de la création d’une adresse et lors de sa modification. Créons la classe qui va gérer tout ça. Comme indiqué il s’agit du fichier : AppBundle\EventListener\DoctrineEvent

Pour appréhender la suite, consultez les commentaires dans le code

namespace AppBundle\EventListener;

use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
//les adresses de nos utilisateur sont stockés dans une entité "Contact"
use AppBundle\Entity\Contact;

class DoctrineEvent implements EventSubscriber {

    public function getSubscribedEvents() {
        return array('prePersist', 'preUpdate');//les événements écoutés
    }

    public function prePersist(LifecycleEventArgs $args) {
        $entity = $args->getEntity();
        //Si c'est bien une entité Contact qui va être "persisté"
        if ($entity instanceof Contact) {
            $entity->updateGmapData();//on met à jour les coordonnées via l'appel à google map
        }
    }

    public function preUpdate(LifecycleEventArgs $args) {
        $entity = $args->getEntity();
        $changeset = $args->getEntityManager()->getUnitOfWork()->getEntityChangeSet($entity);
        //Si c'est bien une entité Contact qui va être modifié
        if ($entity instanceof Contact) {
            //Si il y'a eu une mise a jour sur les propriétés en relation avec l'adresse (ici "address", "city" et "postalCode")
            if (array_key_exists("address", $changeset) || array_key_exists("city", $changeset) || array_key_exists("postalCode", $changeset)) {
                $entity->updateGmapData();//on met à jour les coordonnées via l'appel à google map
            }
        }
    }

}

De cette manière nos coordonnées seront toujours à jour. Il manque pour que l’exemple soit complet la fonction « updateGmapData() » de notre entité Contact. Cela ne concerne pas le sujet des Event Listener, mais je vous en met un exemple qui utilise bien sur la fonction « geocodeAddress() » détaillé ici

    public function updateGmapData() {
        $data = \AppBundle\Utils\GmapApi::geocodeAddress($this->getAddress() . ' ' . $this->getZipcode() . ' ' . $this->getCity());
        $this->setGmapLat($data['lat']);
        $this->setGmapLng($data['lng']);
        $this->setGmapAddress($data['address']);
        $this->setGmapPostalCode($data['postal_code']);
        $this->setGmapCity($data['city']);
        $this->setGmapDepartment($data['department']);
        $this->setGmapRegion($data['region']);
        $this->setGmapCountry($data['country']);
    }

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