Dans le premier volet consacré au Datatables Jquery dans un projet ASP.NET MVC 5, nous avons pu voir comment intégrer facilement ce plugin qui permet l’affichage de données sous forme de tableau dans un projet web ASP.NET MVC. Dans cette 2ème partie, nous irons plus loin dans notre démarche d’intégration de ce plugin et nous verrons comment ça se présente dans un vrai projet.

Dans un contexte d’entreprise, nous nous retrouvons à gérer des données grandissantes dans le temps (en tout cas nous devons le prévoir). Ces informations sont, en général, stockées dans des bases de données. Ainsi, il nous faudra charger nos données depuis notre serveur et penser également à limiter le nombre de lignes à afficher dans notre tableau “Datatable” afin d’optimiser le chargement de notre page.

Dans notre projet MVC de démo, nous allons garder notre exemple et en faire un modèle MVC qui nous servira tout au long des exemples à venir.

 

Le modèle Mvc métier

Nous créons donc, côté serveur, notre modèle “Browser” utilisé lors du premier article :

  public class BrowserModel
    {
        public string Engine { get; set; }
        public string Browser { get; set; }
        public string Platform { get; set; }
        public string Version { get; set; }
        public string Grade { get; set; }
    }
}

A partir de ce modèle, nous allons voir comment interagir avec notre plugin afin de l’alimenter côté client.

 

Le modèle du DataTables

Côté serveur, le DataTables Jquery possède également un modèle, permettant de gérer la majorité des scenarii (filtre, pagination, etc). Vous trouverez via le lien ci-dessous les informations relatives à ce modèle :

http://legacy.datatables.net/usage/server-side

Côté C#, voici un exemple de  modèle Mvc contenant quelques propriétés que nous jugeons utiles pour cette démo :

    /// <summary>
    /// Class that encapsulates most common parameters sent by DataTables plugin
    /// </summary>
    public class DataTableParamModel
    {
        /// <summary>
        /// Request sequence number sent by DataTable,
        /// same value must be returned in response
        /// </summary>
        public string sEcho { get; set; }

        /// <summary>
        /// Text used for filtering
        /// </summary>
        public string sSearch { get; set; }

        /// <summary>
        /// Number of records that should be shown in table
        /// </summary>
        public int iDisplayLength { get; set; }

        /// <summary>
        /// First record that should be shown(used for paging)
        /// </summary>
        public int iDisplayStart { get; set; }

        /// <summary>
        /// Number of columns in table
        /// </summary>
        public int iColumns { get; set; }

        /// <summary>
        /// Number of columns that are used in sorting
        /// </summary>
        public int iSortingCols { get; set; }

        /// <summary>
        /// Comma separated list of column names
        /// </summary>
        public string sColumns { get; set; }
    }

Nous verrons plus tard qu’il existe une quantité assez large de propriétés liées au Datatables Jquery.

 

Chargement asynchrone

Côté client

Côté client, nous allons encore une fois utiliser l’appel asynchrone intégré à la librairie afin de charger notre DataTables :

 $(document).ready(function () {

            var oTable = $('#exampleWithAjax');

            oTable.dataTable({
                "oLanguage": {
                    "sProcessing": "Traitement en cours...",
                    "sSearch": "Rechercher&nbsp;:",
                    "sLengthMenu": "Afficher _MENU_ &eacute;l&eacute;ments",
                    "sInfo": "Affichage de l'&eacute;lement _START_ &agrave; _END_ sur _TOTAL_ &eacute;l&eacute;ments",
                    "sInfoEmpty": "Affichage de l'&eacute;lement 0 &agrave; 0 sur 0 &eacute;l&eacute;ments",
                    "sInfoFiltered": "(filtr&eacute; de _MAX_ &eacute;l&eacute;ments au total)",
                    "sInfoPostFix": "",
                    "sLoadingRecords": "Chargement en cours...",
                    "sZeroRecords": "Aucun &eacute;l&eacute;ment &agrave; afficher",
                    "sEmptyTable": "Aucune donn&eacute;e disponible dans le tableau",
                    "oPaginate": {
                        "sFirst": "Premier&nbsp;&nbsp;",
                        "sPrevious": "Pr&eacute;c&eacute;dent&nbsp;&nbsp;",
                        "sNext": "Suivant",
                        "sLast": "&nbsp;&nbsp;Dernier"
                    },
                    "oAria": {
                        "sSortAscending": ": activer pour trier la colonne par ordre croissant",
                        "sSortDescending": ": activer pour trier la colonne par ordre d&eacute;croissant"
                    }
                },
                // activate Ajax call
                "bServerSide": true,
                // show loader
                "bProcessing": true,
                // Ajax call
                "sAjaxSource": "/Home/LoadBrowsers",
                "columns": [
                   { "title": "Engine" },
                   { "title": "Browser" },
                   { "title": "Platform" },
                   { "title": "Version", "class": "center" },
                   { "title": "Grade", "class": "center" }
                ]
            });
        });

Ici, on a spécifié que l’on souhaite charger les données depuis le serveur via la propriété :” bServerSide”, et nous spécifions la méthode “serveur” à appeler : “sAjaxSource”: “/Home/LoadBrowsers”.

 

Fonctionnement

Une fois le Datatables instancié (ainsi qu’à l’instanciation également), un appel asynchrone sera émis vers le serveur afin de charger les données dans notre Tableau. Chaque événement observé engendrera également un appel vers le serveur afin de rafraîchir notre tableau en conséquence (par exemple lors d’une recherche, d’un tri, d’une pagination, etc).

Côté Serveur

Au niveau serveur (Controller MVC), voyons à quoi ressemble notre fonction de chargement du DataTables :

        [HttpGet]
        public JsonResult LoadBrowsers(DataTableParamModel param)
        {
            // Browser list
           var totalBrowserList = SrvBrowser.ListOfBrowsers();

           var filteredBrowserList = totalBrowserList.Skip(param.iDisplayStart).Take(param.iDisplayLength);

           var filteredResult = from b in filteredBrowserList
                           select new[] { b.Engine, b.Browser, b.Platform, b.Version, b.Grade };

            return Json(new
            {
                sEcho = param.sEcho,
                iTotalRecords = totalBrowserList.Count(),
                iTotalDisplayRecords = totalBrowserList.Count(),
                aaData = filteredResult
             },
              JsonRequestBehavior.AllowGet);
        }

Nous considérons ici que la liste d’éléments à afficher (les browsers) nous est fournie par un service tiers SrvBrowser (instance de ServiceBrowser (cf projet Git) ) via la méthode ListOfBrowsers.

Si nous observons le contenu de l’objet DataTableParamModel passé en paramètre, nous constatons qu’il contient bien les données du modèle associé sans que nous n’ayons eu à renseigner ces éléments côté client.

getDtatables

 Aperçu des valeurs passées en paramètre par le Datatables lors de l’appel Ajax

 Notre méthode retourne donc un objet Json contenant :

  • aaData : la liste des éléments à afficher par page
  • sEcho : le numéro de la page courante
  • iTotalRecords : le nombre total d’éléments à afficher
  • iTotalDisplayRecords : le nombre courant d’éléments à afficher par page
  • sSearch :  le mot clé du champs de recherche

A partir de ces propriétés, nous pouvons gérer notre chargement côté serveur, ainsi que la pagination et le filtre d’affichage de lignes prédéfinies. Cependant, dans cet exemple, nous n’effectuons aucun filtre de recherche ni de tri par colonne. Il faut également gérer ces fonctionnalités côté serveur. Voyons le code correspondant pour pallier à cela :

        [HttpGet]
        public JsonResult LoadBrowsersFull(DataTableParamModel param)
        {
            // Browser list
            var totalBrowserList = SrvBrowser.ListOfBrowsers();
            IEnumerable<BrowserModel> filteredBrowserList;

            // filter keyword search
            if (!string.IsNullOrEmpty(param.sSearch))
            {
                // limit search in column 1 and 2
                var isBrowserSearchable = Convert.ToBoolean(Request["bSearchable_1"]);
                var isEngineSearchable = Convert.ToBoolean(Request["bSearchable_0"]);

                filteredBrowserList = totalBrowserList.Where(bwsr =>
                                isBrowserSearchable && Convert.ToString(bwsr.Browser).Contains(param.sSearch.ToLower()) ||
                                isEngineSearchable && bwsr.Engine.ToLower().Contains(param.sSearch.ToLower()));
            }
            else
            {
                // No search filter
                filteredBrowserList = totalBrowserList;
            }

            var isBrowserPlatformSortable = Convert.ToBoolean(Request["bSortable_2"]);
            var sortColumnIndex = Convert.ToInt32(Request["iSortCol_0"]);
            Func<BrowserModel, string> orderingFunction = (bwsr =>
                                    sortColumnIndex == 2
                                    && isBrowserPlatformSortable ? Convert.ToString(bwsr.Platform) : "");

            var sortDirection = Request["sSortDir_0"];

            // asc or desc ofr column Platform
            filteredBrowserList = sortDirection == "asc"
                ? filteredBrowserList.OrderBy(orderingFunction)
                : filteredBrowserList.OrderByDescending(orderingFunction);

            var displayedFilteredBrowserList = filteredBrowserList.Skip(param.iDisplayStart).Take(param.iDisplayLength);

            var filteredResult = from b in displayedFilteredBrowserList
                                 select new[] { b.Engine, b.Browser, b.Platform, b.Version, b.Grade };

            return Json(new
            {
                sEcho = param.sEcho,
                iTotalRecords = totalBrowserList.Count(),
                iTotalDisplayRecords = totalBrowserList.Count(),
                aaData = filteredResult
            },
              JsonRequestBehavior.AllowGet);
        }

Au passage, nous constatons que nous pouvons récupérer d’autres champs du “DataTable Param” dans la “Request” comme par exemple les colonnes à filtrer, à trier, les indexes des colonnes, etc :

DataTable_Param

Aperçu des clés passées en paramètre par le DataTables

Bien que la liste des éléments paramétrables du “Datatables Jquery” soit longue et assez complète, nous pourrions avoir envie d’ajouter des filtres personnalisés : là encore le plugin est assez souple pour le permettre. Il suffira d’ajouter côté client les paramètres voulus via la propriété du Datatables “fnServerParams”. Imaginons un filtre global extérieur à notre Datatables, comme par exemple une liste de dates de création des navigateurs que nous ne souhaitons pas voir apparaître dans notre Tableau; cela nous donnera côté Js :

 // ---------------
'fnServerParams': function (aoData) {
                aoData.push({ 'name': 'bwsrCreatedDate', 'value': $('#bwsrCreatedDate').val() }
                );
            },
 // ---------------

Côté Serveur, il suffira de rajouter le paramètre en entrée de la méthode de filtre et d’en faire ce que nous souhaitons comme filtre :

 [HttpGet]
 public JsonResult LoadBrowsersFull(DataTableParamModel param, string bwsrCreatedDate)
 {

Optimisation Sql

Nous avons alléger le chargement de notre tableau en affichant un nombre limité d’enregistrements notamment via les filtres de lignes d’affichage et la pagination du Datatables. Mais imaginons des données provenant d’une table en Base de données contenant des millions de lignes… On pourra, dans ce cas, déléguer à une procédure stockée par exemple tout ou partie du filtre et chargement de notre Datatables. Les paramètres de filtre étant déjà passés en paramètre côté serveur, il nous suffira de les relayer à notre “proc stock” afin d’effectuer les filtres/tri/recherche par mot clé (avancée si besoin), etc
Voici à quoi pourrait ressembler notre requête SQL sous SQL SERVER :

CREATE PROCEDURE [dbo].[Browser_Search]
	@searchKey	varchar(256) = NULL,
	@skipRows int = 0,
    @takeRows int = 10,
    @count int = 0

AS

BEGIN
	SET NOCOUNT ON;

	DECLARE @TotalCount INT
	SELECT  @TotalCount = COUNT(BrowserId)
	FROM	TblBrowser

	SELECT *
	FROM
	(
		SELECT	ROW_NUMBER() OVER(ORDER BY BrowserId) AS NUMBERROW,
				Engine,
				Browser,
				Platform,
				Version,
				Grade,
				@TotalCount as TotalCount
		FROM TblBrowser
		WHERE
		   (
				@searchKey IS NULL
				OR
				(
						LOWER(Engine)	LIKE '%' +@searchKey + '%'
					OR  LOWER(Browser)	LIKE '%' +@searchKey + '%'
					OR  LOWER(Platform)	LIKE '%' +@searchKey + '%'
					OR  LOWER(Version)	LIKE '%' +@searchKey + '%'
					OR  LOWER(Grade)	LIKE '%' +@searchKey + '%'
				)
			)
		ORDER BY BrowserId DESC

	) AS TBL
	WHERE NUMBERROW BETWEEN (@skipRows + 1) AND (@skipRows + @takeRows)

END

Conclusion

Le “DataTables Jquery” est un plugin complet qui nous permet d’afficher nos données à exploiter sur le web sous forme de tableau. Il nous permet une certaine souplesse et beaucoup de facilités notamment avec la gestion native de filtres – tri – pagination – internationalisation – recherche par mot clé – appel asynchrone  etc. Il reste ainsi paramétrable, et “surchargeable” en terme de fonctionnalités.

Une fois pris en main, il peut s’avérer être un réel atout pour les développeurs web, en leur permettant de rendre disponible ou non tout ou partie d’un ensemble de fonctionnalités déjà embarquées.

En évitant de réinventer la roue, ce plugin vous fera gagner du temps sur bon nombre de fonctions d’affichage basique de données, où l’on s’attarderait communément.

 

Vous trouverez le code source de cet exemple sur mon git (ci-dessous).

github

A très bientôt !