angularjs - columndefs - Navegación en el lado del servidor+filtrado+clasificación para ng-grid con WebAPI
ui-grid columndefs (6)
Estoy tratando de crear un ejemplo de trabajo simple de usar ng-grid con ASP.NET WebAPI. Por lo tanto, comencé desde el ejemplo de paginación del lado del servidor en la página de ejemplos de ng-grid ( http://angular-ui.github.io/ng-grid/ ); de todos modos, mi cuadrícula siempre muestra columnas vacías, incluso si al depurar puedo confirmar que los datos se reciban correctamente. Probablemente me falta algo en la configuración de la cuadrícula, pero todas las muestras que encontré son similares a las mías. ¿Alguien podría ayudar? Aquí esta lo que hice:
Actualización n. ° 1 : la solución sugerida parece funcionar pero solo para la primera página. Cada vez que me muevo a una nueva página o realizo cualquier otra operación que requiera una actualización, los datos mostrados siguen siendo los mismos, incluso si el servidor devolvió los datos como se esperaba. Además, de todos los ejemplos de código que encontré, parece que la forma correcta de configurar los datos es simplemente reemplazar el valor del miembro de la matriz en lugar de vaciarlo y llenarlo nuevamente. Intenté con aplicar como se sugiere en https://groups.google.com/forum/#!searchin/angular/nggrid/angular/vUIfHWt4s_4/oU_C9w8j-uMJ , pero obtengo el mismo resultado.
Lado del servidor
Simplemente cree una nueva aplicación MVC4, actualice los paquetes NuGet y agregue paquetes angulares y ng-grid. Mi modelo de datos falsos está representado por la clase 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; }
}
También agrego un par de modelos para tratar con paginación, filtrado y clasificación de varios conjuntos de datos (me resulta más fácil tener un modelo base de paginación común -PagedFilter-, y una serie de modelos derivados):
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;
}
}
Aquí está el 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; }
}
Luego agrego un controlador API para obtener elementos:
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()
};
}
}
Lado del cliente
En el lado del cliente, mi aplicación angular es solo un controlador modelado en la muestra ng-grid: por lo tanto, agrego propiedades directamente a $ scope, incluso si en un escenario del mundo real prefiero usar un modelo (probablemente generado a partir de Clase de 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();
}]);
En mi código, se llama a la devolución de llamada correcta y puedo buscar todos los elementos devueltos en data.items. Sin embargo, no se muestra nada en la cuadrícula. Ningún error aparece en la consola.
Después de experimentar un poco, creo que encontré el código correcto. Esta publicación sobre $ apply me ayudó un poco: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html . De hecho, si entiendo bien, la llamada para aplicar no debería ser necesaria en absoluto, dado que mis datos proceden de $ http, que ya lo proporciona. Entonces, terminé simplemente configurando la variable de elementos de ámbito en la devolución de llamada correcta. Aquí está el JS completo nuevamente, espero que esto pueda ayudar a un recién llegado como yo. Ahora voy a ampliar la prueba con modelos de TypeScript, servicios y todas las cosas del mundo real: me temo que tendré que hacer una nueva publicación ... :)
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();
}]);
(Como nota al margen, puede ver en el código que estoy pasando una sola cadena para ordenar datos, en lugar de dos matrices para campos y direcciones. De hecho, no pude encontrar la forma correcta de recibir matrices como miembros de mi entrada modelo en el controlador C #, por lo que estoy pasando una sola secuencia donde cada nombre de campo está prefijado por + o - de acuerdo con la dirección ascendente / descendente).
Está configurando el origen de datos en ng-grid para los items
pero nunca actualiza la matriz de elementos en la devolución de llamada exitosa del servidor.
En la devolución de llamada de succcess haz algo como esto
$scope.totalServerItems = data.totalItems;
angular.forEach(data.items, function(item) {
$scope.items.push(item);
});
La última documentación es bastante explícita sobre esta pregunta: http://ui-grid.info/docs/#/tutorial/308_external_filtering
Mi código resultante:
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);
});
¡Bien, ahora el lado del cliente está hecho! Tienes que procesarlo desde el servidor, no puedo ayudarte con .Net WebAPI ya que estoy manejando PHP / Mysql ...
Recientemente he estado trabajando con ng-grid. Me encontré con problemas similares cuando hacía referencia a la nueva versión de AngularJS. Asegúrese de hacer referencia al archivo min angular 1.0.2.
Aquí está mi código del lado del cliente para la ng-grid con paginación. Funciona perfectamente una vez que se implementó la versión adecuada 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
};
});
Simplemente haz clic en Muestra en el sitio web angular:
$http({
url: "/payments/GetPayments",
method: "GET",
params: p
}).success(function(data, status, headers, config) {
// Как в примере
$scope.items = data.items;
$scope.totalServerItems = data.totalItems;
if (!$scope.$$phase) {
$scope.$apply();
}
}).error(function(data, status, headers, config) {
alert(JSON.stringify(data));
});
También podría ayudar
El código HTML-muestra
<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>
La muestra de código de AngulaJs
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
};
});