web-dev-qa-db-fra.com

Pagination côté serveur + filtrage + tri pour ng-grid avec WebAPI

J'essaie de créer un exemple de travail simple d'utilisation de ng-grid avec ASP.NET WebAPI. Ainsi, j’ai commencé à partir de l’exemple de pagination côté serveur dans la page d’exemples ng-grid ( http://angular-ui.github.io/ng-grid/ ); de toute façon, ma grille affiche toujours des colonnes vides, même si lors du débogage, je peux confirmer que les données sont correctement reçues. Il me manque probablement quelque chose dans la configuration de la grille, mais tous les échantillons que j'ai trouvés ressemblent au mien. Quelqu'un pourrait-il aider? Voici ce que j'ai fait:

Mise à jour # 1: la solution suggérée semble fonctionner, mais uniquement pour la 1ère page. Chaque fois que je passe à une nouvelle page ou effectue une autre opération nécessitant une actualisation, les données affichées restent identiques, même si le serveur a renvoyé les données modifiées comme prévu. En outre, parmi tous les exemples de code que j'ai trouvés, il semble que la bonne façon de définir les données consiste simplement à remplacer la valeur du membre du tableau plutôt que de la vider et de la remplir à nouveau. J'ai essayé avec apply comme suggéré dans https://groups.google.com/forum/#!searchin/angular/nggrid/angular/vUIfHWt4s_4/oU_C9w8j-uMJ , mais le résultat est identique.


Du côté serveur

Créez simplement une nouvelle application MVC4, mettez à jour les packages NuGet et ajoutez des packages angulaires et ng-grid. Mon faux modèle de données est représenté par la classe Item:

public sealed class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public bool IsFemale { get; set; }
}

J'ajoute également quelques modèles pour gérer la pagination, le filtrage et le tri de différents ensembles de données (il est plus facile d'avoir un modèle de base de pagination commun - PagedFilter - et un certain nombre de modèles dérivés):

public class PagedFilter
{
    private int _nPageSize;
    private int _nPageNumber;

    public int PageSize
    {
        get { return _nPageSize; }
        set
        {
            if (value < 1) throw new ArgumentOutOfRangeException("value");
            _nPageSize = value;
        }
    }

    public int PageNumber
    {
        get { return _nPageNumber; }
        set
        {
            if (value < 1) throw new ArgumentOutOfRangeException("value");
            _nPageNumber = value;
        }
    }

    public int TotalItems { get; set; }

    public int TotalPages
    {
        get { return (int)Math.Ceiling((double)(TotalItems / PageSize)); }
    }

    public PagedFilter()
    {
        _nPageSize = 20;
        _nPageNumber = 1;
    }
}

Voici le ItemFilter:

public class ItemFilter : PagedFilter
{
    public List<string> SortFields { get; set; }
    public List<string> SortDirections { get; set; }
    public string Name { get; set; }
    public int? MinAge { get; set; }
    public int? MaxAge { get; set; }
}

Ensuite, j'ajoute un contrôleur API pour récupérer les éléments:

public class ItemController : ApiController
{
    // fake data
    private readonly List<Item> _items;

    public ItemController()
    {
        Random rnd = new Random();
        _items = new List<Item>();
        char c = 'a';

        for (int i = 0; i < 1000; i++)
        {
            _items.Add(new Item
                            {
                                Id = i,
                                Age = rnd.Next(1, 100),
                                IsFemale = ((i & 1) == 0),
                                Name = String.Format(CultureInfo.InvariantCulture, "{0:00000}-{1}",
                                    i, new string(c, 5))
                            });
            if (++c > 'z') c = 'a';
        }
    }

    public dynamic Get([FromUri] ItemFilter filter)
    {
        var items = _items.AsQueryable();

        // filtering
        if (!String.IsNullOrEmpty(filter.Name))
            items = items.Where(i => i.Name.Contains(filter.Name));

        if (filter.MinAge.HasValue)
            items = items.Where(i => i.Age >= filter.MinAge.Value);

        if (filter.MaxAge.HasValue)
            items = items.Where(i => i.Age <= filter.MaxAge.Value);

        // ...sorting (using Dynamic Linq) omitted for brevity...

        // paging
        int nTotalItems = items.Count();
        items = items.Skip((filter.PageNumber - 1) * filter.PageSize)
                     .Take(filter.PageSize);
        return new
                   {
                       totalItems = nTotalItems,
                       items = items.ToArray()
                   };
    }
}

Côté client

Côté client, mon application angulaire n’est qu’un seul contrôleur modélisé sur l’échantillon ng-grid: j’ajoute donc directement les propriétés à $ scope, même si dans un scénario réel, je préférerais utiliser un modèle (probablement généré à partir d’un fichier). Classe TypeScript). HTML:

<div ng-app="MyApp" ng-controller="MainController">
    <div ng-grid="gridOptions" style="height: 400px">
    </div>
</div>

JS:

var app = angular.module('MyApp', ['ngGrid']);

app.controller('MainController', ['$scope', '$http', function ($scope, $http, $apply) {
    $scope.items = [];

    // filter
    $scope.filterOptions = {
        filterText: "",
        useExternalFilter: true
    };

    // paging
    $scope.totalServerItems = 0;
    $scope.pagingOptions = {
        pageSizes: [25, 50, 100],
        pageSize: 25,
        currentPage: 1
    };

    // sort
    $scope.sortOptions = {
        fields: ["name"],
        directions: ["ASC"]
    };

    // grid
    $scope.gridOptions = {
        data: "items",
        columnDefs: [
            { field: "name", displayName: "Name", pinnable: true },
            { field: "age", displayName: "Age", width: "60" },
            { field: "isFemale", displayName: "F", width: "40" }
        ],
        enablePaging: true,
        enablePinning: true,
        pagingOptions: $scope.pagingOptions,        
        filterOptions: $scope.filterOptions,
        keepLastSelected: true,
        multiSelect: false,
        showColumnMenu: true,
        showFilter: true,
        showGroupPanel: true,
        showFooter: true,
        sortInfo: $scope.sortOptions,
        totalServerItems: "totalServerItems",
        useExternalSorting: true,
        i18n: "en"
    };

    $scope.refresh = function() {
        setTimeout(function () {
            var p = {
                name: $scope.filterOptions.filterText,
                pageNumber: $scope.pagingOptions.currentPage,
                pageSize: $scope.pagingOptions.pageSize,
                sortFields: $scope.sortOptions.fields,
                sortDirections: $scope.sortOptions.directions
            };

            $http({
                url: "/api/item",
                method: "GET",
                params: p
            }).success(function(data, status, headers, config) {
                $scope.totalServerItems = data.totalItems;
                // SUGGESTION #1 -- empty and fill the array
                /* $scope.items.length = 0;
                angular.forEach(data.items, function (item) {
                   $scope.items.Push(item);
                }); 
                */
                // https://groups.google.com/forum/#!searchin/angular/nggrid/angular/vUIfHWt4s_4/oU_C9w8j-uMJ
                $scope.$apply(function () { $scope.items = data.items; });
                if (!$scope.$$phase) {
                    $scope.$apply();
                }
            }).error(function(data, status, headers, config) {
                alert(JSON.stringify(data));
            });
        }, 100);
    };

    // watches
    $scope.$watch('pagingOptions', function (newVal, oldVal) {
        if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
            $scope.refresh();
        }
    }, true);

    $scope.$watch('filterOptions', function (newVal, oldVal) {
        if (newVal !== oldVal) {
            $scope.refresh();
        }
    }, true);

    $scope.$watch('sortOptions', function (newVal, oldVal) {
        if (newVal !== oldVal) {
            $scope.refresh();
        }
    }, true);

    $scope.refresh();
}]);

Dans mon code, le rappel de succès est appelé et je peux parcourir tous les éléments retournés dans data.items. Pourtant, rien ne s'affiche dans la grille. Aucune erreur n'apparaît dans la console.

24
Naftis

Après avoir essayé un peu, je pense avoir trouvé le bon code. Cet article sur $ apply m'a aidé un peu: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html . En fait, si je comprends bien, l’appel à candidature ne devrait pas être nécessaire du tout, étant donné que mes données proviennent de $ http qui le fournit déjà. J'ai donc fini par définir simplement la variable des éléments de portée dans le rappel de réussite. Voici à nouveau le JS complet, espérons que cela pourra aider un nouveau venu comme moi. Maintenant, je vais étendre le test aux modèles TypeScript, aux services et à tout ce qui se passe dans le monde réel: je crains de devoir publier de nouvelles publications ... :)

var app = angular.module('MyApp', ['ngGrid']);

app.controller('MainController', ['$scope', '$http', function ($scope, $http, $apply) {
    $scope.items = [];

    // filter
    $scope.filterOptions = {
        filterText: "",
        useExternalFilter: true
    };

    // paging
    $scope.totalServerItems = 0;
    $scope.pagingOptions = {
        pageSizes: [25, 50, 100],
        pageSize: 25,
        currentPage: 1
    };

    // sort
    $scope.sortOptions = {
        fields: ["name"],
        directions: ["ASC"]
    };

    // grid
    $scope.gridOptions = {
        data: "items",
        columnDefs: [
            { field: "id", displayName: "ID", width: "60" },
            { field: "name", displayName: "Name", pinnable: true },
            { field: "age", displayName: "Age", width: "60" },
            { field: "isFemale", displayName: "F", width: "40" }
        ],
        enablePaging: true,
        enablePinning: true,
        pagingOptions: $scope.pagingOptions,        
        filterOptions: $scope.filterOptions,
        keepLastSelected: true,
        multiSelect: false,
        showColumnMenu: true,
        showFilter: true,
        showGroupPanel: true,
        showFooter: true,
        sortInfo: $scope.sortOptions,
        totalServerItems: "totalServerItems",
        useExternalSorting: true,
        i18n: "en"
    };

    $scope.refresh = function() {
        setTimeout(function () {
            var sb = [];
            for (var i = 0; i < $scope.sortOptions.fields.length; i++) {
                sb.Push($scope.sortOptions.directions[i] === "DESC" ? "-" : "+");
                sb.Push($scope.sortOptions.fields[i]);
            }

            var p = {
                name: $scope.filterOptions.filterText,
                pageNumber: $scope.pagingOptions.currentPage,
                pageSize: $scope.pagingOptions.pageSize,
                sortInfo: sb.join("")
            };

            $http({
                url: "/api/item",
                method: "GET",
                params: p
            }).success(function(data, status, headers, config) {
                $scope.totalServerItems = data.totalItems;
                $scope.items = data.items;
            }).error(function(data, status, headers, config) {
                alert(JSON.stringify(data));
            });
        }, 100);
    };

    // watches
    $scope.$watch('pagingOptions', function (newVal, oldVal) {
        if (newVal !== oldVal) {
            $scope.refresh();
        }
    }, true);

    $scope.$watch('filterOptions', function (newVal, oldVal) {
        if (newVal !== oldVal) {
            $scope.refresh();
        }
    }, true);

    $scope.$watch('sortOptions', function (newVal, oldVal) {
        if (newVal !== oldVal) {
            $scope.refresh();
        }
    }, true);

    $scope.refresh();
}]);

(En clair, le code indique que je passe une chaîne unique pour les données de tri, plutôt que deux tableaux pour les champs et les directions. En fait, je ne trouvais pas le bon moyen de recevoir des tableaux en tant que membres de mon entrée. model dans le contrôleur C #, je passe donc une chaîne unique où chaque nom de champ est précédé de + ou - en fonction du sens croissant/décroissant).

13
Naftis

Vous définissez la source de données sur ng-grid sur items, mais vous ne mettez jamais à jour le tableau d’éléments lors du rappel de réussite du serveur.

En cas de rappel, faites quelque chose comme ça

$scope.totalServerItems = data.totalItems;
angular.forEach(data.items, function(item) {
   $scope.items.Push(item);
});
4
Chandermani

Cela pourrait aider aussi

L'échantillon de code HTML

<html ng-app="myApp">  
    <head lang="en">
        <meta charset="utf-8">
        <title>Getting Started With ngGrid code-sample</title>  
        <script type="text/javascript" src="angular.js"></script>
        <script type="text/javascript" src="ng-grid-1.3.2.js"></script>
    </head>
    <body ng-controller="MyCtrl">
        <div class="gridStyle" ng-grid="gridOptions"></div>
    </body>
</html>

L'échantillon de code AngularJs

var app = angular.module('myApp', ['ngGrid']);
app.controller('MyCtrl', function($scope, $http) {
    $scope.filterOptions = {
        filterText: "",
        useExternalFilter: true
    }; 
    $scope.totalServerItems = 0;
    $scope.pagingOptions = {
        pageSizes: [250, 500, 1000],
        pageSize: 250,
        currentPage: 1
    };  
    $scope.setPagingData = function(data, page, pageSize){  
        var pagedData = data.slice((page - 1) * pageSize, page * pageSize);
        $scope.myData = pagedData;
        $scope.totalServerItems = data.length;
        if (!$scope.$$phase) {
            $scope.$apply();
        }
    };
    $scope.getPagedDataAsync = function (pageSize, page, searchText) {
        setTimeout(function () {
            var data;
            if (searchText) {
                var ft = searchText.toLowerCase();
                $http.get('jsonFiles/largeLoad.json').success(function (largeLoad) {        
                    data = largeLoad.filter(function(item) {
                        return JSON.stringify(item).toLowerCase().indexOf(ft) != -1;
                    });
                    $scope.setPagingData(data,page,pageSize);
                });            
            } else {
                $http.get('jsonFiles/largeLoad.json').success(function (largeLoad) {
                    $scope.setPagingData(largeLoad,page,pageSize);
                });
            }
        }, 100);
    };

    $scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage);

    $scope.$watch('pagingOptions', function (newVal, oldVal) {
        if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
          $scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage, $scope.filterOptions.filterText);
        }
    }, true);
    $scope.$watch('filterOptions', function (newVal, oldVal) {
        if (newVal !== oldVal) {
          $scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage, $scope.filterOptions.filterText);
        }
    }, true);

    $scope.gridOptions = {
        data: 'myData',
        enablePaging: true,
        showFooter: true,
        totalServerItems: 'totalServerItems',
        pagingOptions: $scope.pagingOptions,
        filterOptions: $scope.filterOptions
    };
});
1
Anil Singh

J'ai récemment travaillé avec ng-grid. J'ai rencontré des problèmes similaires lorsque je faisais référence à la nouvelle version d'AngularJS. Assurez-vous de référencer le fichier angulaire min 1.0.2.

Voici mon code côté client pour la grille-ng avec pagination. Cela fonctionne parfaitement une fois que vous avez implémenté la version appropriée de Angular JS.

var app = angular.module('myApp', ['ngGrid']);

app.controller('MyCtrl', function ($scope, $http) {
// We needed to bring back mer becase we were using a variable that was being reassigned later on
var mer = [{ Item: "Bottle", Pcode: 50, OHQ: 333, AQ: 33, Details: "CLICK" },
    { Item: "Bottle", Pcode: 43, OHQ: 2350, AQ: 1250, Details: "CLICK" },
    { Item: "Bottle", Pcode: 27, OHQ: 4000, AQ: 3000, Details: "CLICK" },
    { Item: "Bottle", Pcode: 29, OHQ: 55, AQ: 10, Details: "CLICK" },
    { Item: "Bottle", Pcode: 34, OHQ: 27, AQ: 2, Details: "CLICK" },
    { Item: "Bottle", Pcode: 50, OHQ: 111, AQ: 33, Details: "CLICK" },
    { Item: "Bottle", Pcode: 43, OHQ: 123, AQ: 1250, Details: "CLICK" },
    { Item: "Bottle", Pcode: 27, OHQ: 1234, AQ: 3000, Details: "CLICK" },
    { Item: "Bottle", Pcode: 29, OHQ: 5678, AQ: 10, Details: "CLICK" },
    { Item: "Bottle", Pcode: 34, OHQ: 0, AQ: 2, Details: "CLICK" }];


$scope.filterOptions = {
    filterText: "",
    useExternalFilter: false
};
$scope.totalServerItems = 0;
$scope.pagingOptions = {
    pageSizes: [5, 10],
    pageSize: 5,
    currentPage: 1
};

$scope.setPagingData = function (data, page, pageSize) {
    var pagedData = data.slice((page - 1) * pageSize, page * pageSize);
    $scope.myData = pagedData;
    $scope.totalServerItems = data.length;
    if (!$scope.$$phase) {
        $scope.$apply();
    }
};

// I rearranged some of the code in this function.  I noticed we were calling the same function
// in the end just with a slightly different set of data....so instead of having 18-ish lines of code
// we have 12 (YAY)
$scope.getPagedDataAsync = function (pageSize, page, searchText) {
    setTimeout(function () {
        var data = mer;
        if (searchText) {
            var ft = searchText.toLowerCase();
            data = mer.filter(function (item) {
                JSON.stringify(item).toLowerCase().indexOf(ft) != -1;
            });
        }
        $scope.setPagingData(data, page, pageSize);
    }, 100);
};

$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage);

$scope.$watch('pagingOptions', function (newVal, oldVal) {
    // Got rid of the other check here...this is what was causing the filter to not change the data when it changed.
    if (newVal !== oldVal) {
        $scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage, $scope.filterOptions.filterText);
    }
}, true);

$scope.$watch('filterOptions', function (newVal, oldVal) {
    if (newVal !== oldVal) {
        $scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage, $scope.filterOptions.filterText);
    }
}, true);

$scope.gridOptions = {
    data: 'myData',
    enablePaging: true,
    showFooter: true,
    totalServerItems: 'totalServerItems',
    pagingOptions: $scope.pagingOptions,
    filterOptions: $scope.filterOptions
};

});

0
maddev

La dernière documentation est assez explicite sur cette question: http://ui-grid.info/docs/#/tutorial/308_external_filtering

Mon code résultant:

var pagination = {
    pageNumber: 1,
    pageSize: 10,
    // list fields to be sorted
    sort: [{field:'dup_percentage', direction:'desc'}],
    // list fields to be filtered
    filter: []
};

$scope.gridOptions = {
    enableFiltering: true,
    useExternalFiltering: true,
    columnDefs: [...],
    onRegisterApi: function( gridApi ) {
        $scope.gridApi = gridApi;
        $scope.gridApi.core.on.filterChanged( $scope, function() 
        {
                var grid = this.grid;

                // reset filters
                pagination.filter = [];

                // loop over all columns
                angular.forEach(grid.columns, function(column, i)
                {
                    // loop over filters
                    if(typeof column.filters!==undefined)
                    {
                        angular.forEach(column.filters, function(filter, j)
                        {
                            // add column name and value to filter array
                            // to be send server side
                            if(typeof filter.term!=undefined && filter.term!==undefined)
                            {
                                //console.log('add filter', {column:column.name, search:filter.term});
                                pagination.filter.Push({column:column.name, search:filter.term});
                            }
                        });
                    }
                });


                // when user types it's search term
                // server would be hitting too much 
                // so we add 500ms throttle
                if (angular.isDefined($scope.filterTimeout))
                {
                    $timeout.cancel($scope.filterTimeout);
                }
                $scope.filterTimeout = $timeout(function () 
                {
                    // use pagination var which contains all info
                    // needed server side
                    getPage();
                }, 500);
            });

OK maintenant le côté client est terminé! Vous devez le traiter côté serveur, je ne peux pas vous aider avec .Net WebAPI puisque je conduis avec PHP/Mysql ...

0
hugsbrugs