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
Widget
04/04/2023

Créer une application en tant que composant web avec Stencil

Mise en place dans le cadre de Geotrek, cette solution permet de se passer d'une iFrame pour afficher une application dans n'importe quel site.

Voir l'article
Image
Vue.js + D3.js, un bon combo !
30/03/2023

Vue.js & D3.js : un bon combo pour une data visualisation dynamique

Pour faire des indicateurs dynamiques avec D3.js, l'utilisation d'un framework front comme Vue.js peut simplifier la vie.

Voir l'article
Image
Drapeaux
12/01/2023

Afficher n'importe quel drapeau du monde avec flag-icons

J'ai récemment eu besoin d'implémenter un sélecteur de langue avec des drapeaux dans une application web. J'avais envie de trouver une manière rapide de le faire sans avoir à intégrer tous les drapeaux un par un, et de la manière la plus légère possible.

Voir l'article

Inscription à la newsletter

Nous vous avons convaincus