Published by

Il y a 11 ans -

Temps de lecture 10 minutes

Composant Scroll Avec jQuery

Dernièrement, j’ai participé au développement d’un Framework de composants visuels en HTML, jQuery et Java. Parmi ces composants, nous avons mis en place une grille de données évoluée avec des fonctionnalités étendues (Support du drag and drop, rafraichissement partiel de la page, …). Ce composant est intégré dans une application web utilisée la majorité de temps depuis un terminal tactile. Pour naviguer à travers les données, la grille de données utilisait un contrôle de pagination classique dans le style ci-dessous :

Ce type de pagination contraint l’utilisateur à effectuer plusieurs clics pour avancer à travers les données paginées, et atteindre sa page cible. Pour améliorer ce comportement, un ergonome et moi avons proposé une nouvelle stratégie de pagination permettant d’atteindre la page souhaitée avec un minimum de clics, tout en gardant un comportement proche d’une pagination classique (pagination des pages). Le résultat de notre travail est un contrôle scroll permettant de paginer à travers les données en utilisant la fonctionnalité drag-and-drop.

L’expérience tactile des utilisateurs et la résolution fixe du terminal nous imposait certaines règles à respecter pour une interaction facile et aisée avec l’application :

  • La barre de scroll doit avoir une taille fixe (56 pixels dans mon exemple) pour mieux interagir avec les doigts des utilisateurs.
  • Le contrôle scroll (boutons de navigation+espace de scrolling) doit avoir une hauteur fixe selon le nombre de lignes par page et la résolution du terminal. (L’affichage du scroll navigateur est banni)

Une fois le travail réalisé, j’ai trouvé intéressante l’idée de partager cette expérience de code avec des personnes sensibles à l’ergonomie et à l’utilisabilité des interfaces web.

Le Design

Le contrôle Scroll est sensible aux évènements suivants : click, drag-and-drop, et mouse-wheel. Il est composé essentiellement des éléments suivants :

  • Deux boutons de navigation pour avancer et reculer d’une page.
  • Un espace de scrolling.
  • Une barre de scroll pour changer de page.

Les boutons de navigation sont des boutons cliquables qui par souci de design ont la même hauteur qu’une ligne de données. L’espace de scrolling est défini sur la base de nombre de lignes maximum par page. La barre de scroll est un bloc graphique draggable dans l’espace de scrolling. La taille de ce bloc dépend du nombre total de pages remontées par le serveur et de la contrainte de taille des doigts des utilisateurs de l’application.

Les évènements click et mouse-wheel sont branchés sur l’espace scrolling pour permettre une meilleure expérience utilisateur. L’évènement drag-and-drop est branché sur la barre de scroll pour naviguer à travers les données en utilisant cette partie du composant.

La barre de scroll peut avoir une taille dynamique seulement si le nombre total de pages est inférieur à 6 (valeur définie dans mon exemple et correspondant à la résolution maximale utilisée par mon application, vous pouvez redéfinir cette valeur selon vos spécifications). Dans ce cas la taille de la barre de scroll est générée par une formule de calcul et dépend de la taille de l’espace de scrolling et du nombre de pages.

Si le nombre total de pages est supérieur ou égal à 6, la barre de scroll générée par la formule de calcul devient trop petite pour les doigts de l’utilisateur. J’utilise alors une formule de calcul pour générer une barre de scroll à taille fixe quelque soit le nombre de page.

La structure HTML

Pour une meilleure intégration avec les styles CSS, la structure HTML du composant scroll est basée essentiellement sur des balises DIV. Les identifiants avec marqueurs sont nécessaires pour intégrer plusieurs instances du composant scroll dans la même page. Les classes CSS définissent les styles, et le positionnement des éléments dans la page.

<!--Le composant scroll-->
<div id="scrollComponent#{marker}" class="scrollComponent">
       <!--Bouton de navigation (Reculer d’une page)-->
       <div id="scrollUpButton#{marker}" class="scrollUpButton"></div>

       <!--Espace de scrolling-->
       <div id="scrollBarContainer#{marker}" class="scrollBarContainer">
            <!--Barre de scroll-->
            <div id="scrollBar#{marker}" class="scrollBar">
                 <label id="pageNumberLabel#{marker}" class="pageNumberLabel"></label>
            </div>
       </div>

       <!--Bouton de navigation (Avancer d’une page)-->
       <div id="scrollBottomButton#{marker}" class="scrollBottomButton"></div>
</div>

Les styles CSS

Les styles CSS permettent de donner l’aspect visuel du composant Scroll. Le JavaScript est nécessaire pour calculer les dimensions, positionner et afficher les différentes briques du composant scroll en fonction du nombre des pages et des paramètres avancés par l’application.

Le composant scroll

Le style appliqué permet de définir la largeur du composant scroll. Il définit également l’image de fond, et les images des boutons de navigation dans les données et leurs positions.

.scrollComponent {
 width:46px;
 background-image: url("./images/scrollup.png"), url("./images/scrolldown.png"), url("./images/contenu.png");
 background-repeat: no-repeat, no-repeat, repeat-y;
 background-position: center top, center bottom, center top;
}

L’espace de scrolling

Le style appliqué permet de définir la largeur de l’espace de scrolling. La hauteur est calculée en fonction du nombre des lignes maximum à afficher par page.

.scrollBarContainer {
        width:46px;
}

La barre de scroll

La classe CSS scrollBar permet de définir la largeur de la barre du scroll, son positionnement, ses bordures, et son image de fond. La taille de la barre de scroll est calculée selon le nombre de pages. La classe CSS pageNumberLabel permet de définir le positionnement, les propriétés de la police des caractères, et la largeur du libellé utilisé pour l’affichage de la page en cours.

.scrollBar {
 position:absolute;
 width:39px;
 margin-left:4px;
 -webkit-border-radius:150px;
 -moz-border-radius:150px;
 -o-border-radius:150px;
 border-radius:150px;
 background-image:url("./images/texture.png");
}

.pageNumberLabel {
 position:absolute;
 top:40%;
 width:37px;
 font-family:Trebuchet MS,Arial,Helvetica,Jamrul,sans-serif;
 font-size:9px;
 color: #FFFFFF;
 font-weight: bold;
 text-align: center;
}

Les boutons de navigation

Le style appliqué sur les boutons de navigation permet de définir le type de curseur en survolant ces briques.

.scrollUpButton {
 cursor: pointer;
}

.scrollBottomButton {
 cursor: pointer;
}

Le script JavaScript

Le script ci-dessous, permet de définir les propriétés du composant scroll, et de brancher les événements click, drag-and-drop et mouse-wheel sur les différentes briques du composant scroll.

Initilisation du composant scroll

En premier lieu, le script doit définir les dimensions des différentes briques du composant scroll ainsi que leur positionnement. Pour cela, nous avons utilisé la fonction css de jQuery pour accéder et modifier les propriétés des différents styles.

Nous commençons par initialiser la taille de la barre de scroll et la sensibilité de déplacement dans l’espace de scrolling. Ces propriétés sont définies suivant le nombre total de pages et le nombre maximum de lignes à afficher par page.

Si le nombre de pages est inférieur à 6 :

// Hauteur dynamique de la barre de scroll selon le nombre de pages (1)
scrollBarHeight = ((maxRowsPerPage-2) * rowHeight) / totalPages;
// 2 correspond aux boutons de navigation qui sont font partie de l'espace de données.
// Sensibilité de déplacement de la barre de scroll (2)
sensibility = ((maxRowsPerPage-2) * rowHeight) / totalPages;

Si le nombre de pages est supérieur ou égal à 6 :

// Hauteur fixe du composant scrollBar (1)
scrollBarHeight = ((maxRowsPerPage - 2) * rowHeight) / 6;
var oldsensibility = ((((maxRowsPerPage-2)  * rowHeight) / totalPages) - (scrollBarHeight / totalPages));
var diff = (((rowHeight * maxRowsPerPage) - (rowHeight+scrollBarHeight)) -
           (rowHeight + (oldsensibility * (totalPages - 1))) ) / (totalPages - 1);
// Sensibilité de déplacement du composant scrollbar (2)
sensibility = oldsensibility + diff;

Le positionnement de la barre de scroll, et les dimensions des autres briques du composant scroll sont définies par la suite.

// Positionnement du scrollbar selon la page courante (3)
scrollBarBottom = parseFloat(scrollUpButton.position().top)+rowHeight;
var initialScrollBarTop = scrollBarBottom + ((currentPage * sensibility) - sensibility);
scrollBar.css('top',initialScrollBarTop+'px');
// Définition de la taille du composant scroll (4)
scrollComponent.css('height', maxRowsPerPage * rowHeight+'px');
// Définition de la taille d'espace de scrolling (5)
scrollBarContainer.css('height', (maxRowsPerPage - 2) * rowHeight+'px');
// Définition de la taille des boutons de navigation (6)
scrollUpButton.css('height',rowHeight+'px');
scrollBottomButton.css('height',rowHeight+'px');
// Définition de la taille de la barre de scroll
scrollBar.css('height',scrollBarHeight+'px');
// Numéro de page courante (7)
pageNumberLabel.html(currentPage + '/' + totalPages);

Évènements click sur les boutons de navigation

Nous utilisons l’évènement click de l’API jQuery pour utiliser les boutons de navigation.

// Reculer d'une page
scrollUpButton.click(function() {
    previousPage();
});

// Avancer d'une page
scrollBottomButton.click(function() {
    nextPage();
});

// Avancer d'une page
function nextPage() {
    if (currentPage == totalPages) return false;
    currentPage = currentPage + 1;
    changePage();
}

// Reculer d'une page
function previousPage() {
   if (currentPage == 1) return false;
   currentPage = currentPage - 1;
   changePage();
}

// Changer les propriétés de la barre de scroll
function changePage() {
    // Nouvelle position selon la page en cours
    var newTop = scrollBarBottom + ((sensibility * currentPage) - (sensibility));
    scrollBar.css('top',newTop+'px');
    // Mise à jour du numéro de la page
    pageNumberLabel.html(currentPage + '/' + totalPages);
}

Évènement drag-and-drop sur la barre de scroll

Pour donner la vie à notre barre de scroll, nous utilisons la fonction draggable de l’API jQuery UI.

// Evènement DragAndDrop sur le scrollbar
scrollBar.draggable({ axis:'y', opacity: 0.70, currentPage: 'pointer', containment: "#scrollBarContainer"+componentId,
    stop: function(event, ui) {
        // Changer les propriétés de la barre de scroll
        changePage();
        // Charger la page en cours
        loadPage();
    },
   drag: function(event, ui) {
        // Définir la page en cours selon la position de la barre de scroll dans l'espace de scrolling
        var p = scrollBar.position();
        var scrollBarPosition =  parseFloat(p.top+sensibility);
        currentPage = Math.round((scrollBarPosition - scrollBarTop) / sensibility);
        pageNumberLabel.html(currentPage + '/'+ totalPages);
   }
});

Évènement mouse-wheel sur l’espace de scrolling

Pour gérer la souris molette ou le mouse-wheel, nous utilisons le plugin mousewheel de l’API jQuery. Pour des performances optimales, nous attendons un laps de temps après l’arrêt pour charger la dernière page sélectionnée. Toutefois, la barre de scroll change de position pour une meilleure interaction avec l’utilisateur.

// Evènement molette souris sur le composant scroll
scrollComponent.bind('mousewheel', function(event, delta) {
    var direction = delta > 0 ? 'Up' : 'Down';
    if (direction =='Up') {
        previousPage();
    }
    else if  (direction =='Down') {
        nextPage();
    }

    clearTimeout(jQuery.data(this, 'timer'));
    jQuery.data(this, 'timer', setTimeout(function() {
        loadPage();
    }, 250));
});

// Charger la page en cours
function loadPage() {
    if (alreadyLoadedPage != currentPage) {
        alreadyLoadedPage = currentPage;
        // Appel asynchrone pour charger les données ...
    }
}

Exemple d’utilisation

La fonction Ready de jQuery est utilisé pour activer le composant et définir ses propriétés une fois le document DOM est entièrement chargé.

jQuery(document).ready(initialiseScroll);
function initialiseScroll() {
      var scrollComponent = scrollComponentFunction({
                                             componentId: 'test1',
                                             currentPage : 1,
                                             totalPages: 5,
                                             maxRows: 10
                            });
}

Quelques exemples du composant scroll

Conclusion

Le projet de création de notre Framework UI vient en remplacement du Framework RichFaces qui constituait un frein à l’évolution de notre application à tous les niveaux : (développements, intégration avec d’autres technologies, tests IHM, performances des pages générées, évolutivité des composants de base …). En combinant quelques technologies et Frameworks de base (HTML, CSS, jQuery, jQuery UI, Java, …), nous avons réussi à créer des composants UI légers, performants et facilement intégrables … Utiliser des technologies de base pour développer notre propre Framework s’est avéré un pari gagnant. Toutefois, créer son propre Framework est un travail laborieux et couteux dans le temps mais les résultats sont plus que satisfaisants. Utiliser des technologies de base ou partir sur des Frameworks comme GWT, RichFaces … ? C’est un sujet auquel nous essayerons de répondre dans d’autres articles.

Ressources :

Published by

Commentaire

11 réponses pour " Composant Scroll Avec jQuery "

  1. Published by , Il y a 11 ans

    Merci pour cet article!

    N’avez vous pas envisagé d’ajouter vos propres composants à Richfaces ?

  2. Published by , Il y a 11 ans

    Bonjour,

    Article intéressant.

    Quelle est la licence du code fourni?

  3. Published by , Il y a 11 ans

    @Nicolas L. :
    C’était le cas dans l’existant, les développeurs se sont basés sur le composant rich:datatable de RichFaces pour créer un composant custom.
    Le résultat n’était pas du tout satisfaisant, un composant super lourd, avec des performances médiocres et une complexité accrue.
    Une simple petite modification peut prendre plusieurs jours.

    Avec le temps, nous avions de demandes de customisation assez space que les développeurs ont eu beaucoup de mal à les intégrer dans le composant custom.
    Sans parler des bugs de CPU 100% souvent rencontrer en production (un parmi plusieurs : https://issues.jboss.org/browse/RF-7248).
    Sans parler aussi de la difficulté accrue des tests fonctionnels GreenPepper sur les services du composant custom.
    Sans oublier le code HTML généré par le Framework qui est difficilement testable avec des outils comme Selenium.
    Ainsi que L’incompatibilité de Javascript généré par le Framework entre les différents navigateurs …

    Les raisons ne manque pas pour abandonner ce Framework, j’essayerais bientôt de présenter tout ça dans un article plus détaillé dans le futur proche …

  4. Published by , Il y a 11 ans

    @Toub :
    C’est du code libre que vous pouvez utiliser sans limites.

  5. Published by , Il y a 11 ans

    Merci pour ce retour très instructif sur RF que je partage.

    Mon coeur balance entre une stack bas niveau à base de JQuery et une stack plus structurante à base de GWT. J’attends avec une certaine impatience la suite de ton article!

  6. Published by , Il y a 11 ans

    Avez-vous pensé à Primefaces qui est elle aussi basé sur Jquery et qui est (à mon gout) plus performantes que Richfaces?

  7. Published by , Il y a 11 ans

    Salut Amin,

    j’ai eu un besoin similaire sur la meme stack: Seam 2.2.2 et RichFaces 3.3.3, et j’avais besoin de developper une datatable avec drag and drop des lignes, pareil donc :).

    Mon approche a ete un peu differente de la tienne, j’ai moi aussi developpe du code html custom a base de divs en utilisant jQuery et jQuery UI, mais j’ai conserve RichFaces.

    Nous avons identifie les choses a ne pas faire avec RichFaces: rich datatable avec des composants lourds genre rich calendar sur chaque ligne, etc etc. C’est tres bien documente ici: http://javaspecialist.wordpress.com/2010/05/30/performance-tuning-of-seam-jsf-richfaces-for-webapps/.

    Nous avons donc conserve RichFaces, on peut en effet difficilement faire plus simple que RichFaces pour faire des appels Ajax au serveur et modifier la page (reRender), c’est tout simplement excellent. Pas de manipulation de json, pas de modification du dom avec jquery, tout est automatique, gain de temps enorme !

    En revanche, nous avons ete oblige de conserver la version de jQuery embarquee par defaut dans jQuery (1.3.2), ca peut etre limitant…

  8. Published by , Il y a 11 ans

    Je n’ai jamais eu l’occasion d’utiliser ce Framework. Je ne doute pas de ces performances, mais les composants de base ne répondent pas au cahier des charges de mon client. Une extension des composants existants serait nécessaire ce qui pourrait être complexe et délicat à mettre en place.
    Les arguments avancés dans mon commentaire précédent reste valable pour justifier notre choix de partir sur des technologies bas niveau (HTML, CSS, JS) avec des petits Frameworks (jQuery …) pour la réalisation de notre projet.

  9. Published by , Il y a 11 ans

    Salut Guillaume :)
    Je ne l’ai évoqué dans l’article, j’ai eu la même approche que toi. J’ai gardé une partie du Framework JBoss RichFaces (la partie pour faire des appels Ajax et faire les reRender sur la page) qui correspond au petit Framework Ajax4Jsf intégré par JBoss en 2007 pour former le projet JBoss Richfaces. J’ai trouvé dommage de ne pas pouvoir utiliser ce petit Framework indépendamment de JBoss RichFaces ;)

  10. Published by , Il y a 11 ans

    Bonjour,

    Je suis tout à fait d’accord avec vous sur tous les frameworks de « mise en page » (Richfaces, GWT…) très utile mais très lourd à modifier quand on veut faire des choses un peu touchy.
    Concernant vos composants, je pense qu’il serait intéressant que vous lisiez ceci afin de vous intégrer dans les normes JQuery. http://docs.jquery.com/Plugins/Authoring

  11. Published by , Il y a 11 ans

    Merci pour ta remarque, je n’avais pas connaissance de l’existence de ces normes. J’y tiendrai compte la prochaine fois ;)

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.