Makina Blog

Le blog Makina-corpus

Modales et pseudo-navigation avec Foundation 6


Manipuler l'historique avec pushState.

Avant-propos

Je nomme pseudo-navigation l'usage de l'API history.pushState() de HTML5 afin de changer dynamiquement l'URL courante mais sans recharger la page courante, l'historique restant cohérent (c'est-à-dire qu'on peut cliquer sur le bouton Précédent du navigateur et on revient à la page précédente).

En général on nomme ce principe "manipulation de l'historique par pushState", je trouve que c'est une dénomination un peu lourde, donc je propose pseudo-navigation, qui me semble adéquat et suffisamment concis.

Note: j'invite humblement tout le monde à soutenir cette dénomination.

Objectif

Notre cas d'utilisation est assez classique :

  • Notre page d'accueil http://monsite.com/ contient des liens vers des trucs et des machins :
  • Quand on clique sur un machin, le contenu correspondant s'affiche dans une fenêtre modale (par un chargement AJAX) et l'URL devient http://monsite.com/machin :
  • Si on ferme la modale ou si on clique sur le bouton Précédent, on revient sur la page d'accueil et l'URL devient http://monsite.com/ :
  • Et si on accède directement à l'URL d'un truc comme http://monsite.com/truc, la modale est ouverte directement :
  • Et là aussi, si on ferme la modale, on revient sur http://monsite.com/.

Foundation 6

Le framework retenu est Foundation 6. Bootstrap et Foundation ont suivi des chemins parallèles, et même si, au grès des versions successives, on a pu trouver l'un supérieur à l'autre, ils sont aujourd'hui relativement équivalents.

La modale de Foundation s'appelle Reveal, et c'est sur elle que nous bâtissons notre solution.

Voici un exemple élémentaire :

<p><a data-open="exampleModal1">Click me for a modal</a></p>

<div class="reveal" id="exampleModal1" data-reveal>
 <h1>Awesome. I Have It.</h1>
</div>

On utilise l'attribut data-open sur le lien pour désigner la modale à ouvrir.

Solution

Chargement dynamique du contenu de la modale

Tout d'abord, nous avons besoin d'un peu de markup pour notre exemple :

<a data-target="/truc" data-open="modal">Truc</a>
<a data-target="/machin" data-open="modal">Machin</a>

<div class="large reveal" id="modal" data-reveal></div>

Notre modale est donc vide initialement, et on a ajouté un attribut data-target, qui n'est pas prévu par Foundation, et qui va nous servir à indiquer l'URL du contenu à charger dans la modale.

Le chargement de notre contenu à l'ouverture de la modale va se faire en 2 temps:

  • sur le click d'un lien, on lit la valeur de data-target, et on la stocke dans un attribut data-url sur l'élément de la modale:
$('a').on('click', function() {
    $('#modal').attr("data-url", $(this).attr('data-target'));
});
  • sur l'événement open.zf.reveal, on charge en AJAX le contenu distant désigné par data-url:
$('#modal').on('open.zf.reveal', function() {
    var url = $(this).attr('data-url');
    $(this).load(getAjaxURL(url));
});

Note: ici, la fonction getAjaxURL() permet d'obtenir l'URL correspondant au contenu de /truc, elle est donc dépendante du backend utilisé.

  • sur l'événement closed.zf.reveal, on vide le contenu de la modal:
$('#modal').on('closed.zf.reveal', function() {
    $(this).html('');
});

Gestion de la pseudo-navigation

Maintenant que notre modale sait afficher un contenu correspondant à une URL, il faut que cette URL soit affichée dans la barre d'adresse et ajoutée dans l'historique de navigation.

Tout d'abord, dans l'événement open.zf.reveal, on va ajouter un pushState:

$('#modal').on('open.zf.reveal', function() {
    var url = $(this).attr('data-url');
    history.pushState({page: url}, null, url); 
    $(this).load(url);
});

Donc maintenant, quand on ouvre la modale, l'URL change et l'historique est alimenté.

Notons qu'on passe un objet en premier paramètre du pushState, contenant un attribut page (ce nom est arbitraire) qui nous servira plus tard.

Ensuite, il faut que la fermeture de la modale revienne à l'URL racine, on ajoute donc aussi un pushState dans closed.zf.reveal :

$('#modal').on('closed.zf.reveal', function() {
    history.pushState({}, null, '/');
    $(this).html('');
});

Et maintenant, il faut aussi gérer le popState, c'est-à-dire restaurer l'état voulu lorsqu'on utilise les boutons Précédent/Suivant du navigateur:

window.addEventListener('popstate', function(e) {
    if(e.state.page) {
        $('#modal').attr("data-url", $(this).attr('data-target'));
        $('#modal').foundation('open');
    } else {
        $('#modal').foundation('close');
    }
});

C'est ici qu'on se sert des données transmises lors du pushState :

  • si state contient notre attribut page, cela signifie que c'est un état correspondant à une modale ouverte et on utilise l'url indiquée par l'attribut page pour ouvrir la modale avec le bon contenu,
  • au contraire, si state n'a pas d'attribut page, il s'agit d'un état correspondant à la page d'accueil, donc on ferme la modale.

Cohérence pour les URL directes

Dernier point, si un utilisateur demande directement la page http://monsite.com/truc (parce qu'on lui a transmis par mail, parce qu'il l'a trouvé dans une recherche Google, etc.), on souhaite que la modale soit automatiquement ouverte avec le contenu truc:

if(location.pathname != "/") {
    $('#modal').attr("data-url", location.pathname);
    $('#modal').foundation('open');
}

Actualités en lien

Image
Symfony + Vue.js
21/06/2022 - 15:02

Créer une application Symfony/Vue.js

Pour faire des essais ou bien démarrer un nouveau projet, vous avez besoin de créer rapidement une application Symfony couplée avec un front Vue.js ? Suivez le guide !

Voir l'article
Image
Logo Cypress
11/10/2021 - 16:38

Retour d’expérience : tests fonctionnels avec Cypress

Dans le cadre d'un de nos projets de développement spécifique, l’équipe Makina Corpus a été amenée à coder un important logiciel métier avec de nombreuses entités et règles de gestion : l’Hydroportail.

Voir l'article
29/03/2019 - 15:28

Des boucles de composants génériques avec Angular

Ou comment faire des composants de listes réutilisables avec n'importe quel objet.

Voir l'article

Inscription à la newsletter

Nous vous avons convaincus