[Kendo UI] Funcionamiento del Grid – Parte 4 – LocalStorage

En esta ocasión vamos a utilizar el control Kendo-UI Grid de Telerik enlazado a un servicio ODataV4, junto con la característica LocalStorage de HTML5 para guardar el estado del grid (columnas activas, filtros…) y funcionar en modo offline.

En un artículo anterior vimos cómo realizar operaciones CRUD con el Kendo-UI Grid enlazado a un servicio OData. y como preparar un proyecto VS2015 para trabajar con Kendo UI y el framework AngularJS 1.

El servicio web OData usado para este artículo lo puedes descargar del siguiente enlace.

Cuando desarrollamos aplicaciones web que son utilizadas por múltiples clientes, estos suelen requerir cierta personalización. Por ejemplo, unos clientes necesitan mostrar unas columnas y otros clientes otras diferentes. Para evitar que cada cliente se tenga que personalizar el grid cada vez que lo visualice, vamos a guardar el estado usando el LocalStorage de HTML5. El siguiente fragmento de código muestra un grid de clientes con 2 botones (uno para guardar el estado y otro para restaurarlo).

@{
    ViewBag.Title = "Index";
}
<div ng-app="appGridPersistencia" ng-controller="ctrlGridPersistencia">
    <div class="panel panel-primary">
        <div class="panel-heading">
            <h3 class="panel-title">Grid Kendo UI - Guardar Estado</h3>
        </div>
        <div class="panel-body">
            <div class="box wide">
                <a href="#" class="k-button" id="btnGuardarEstado" ng-click="guardarEstado()">Save State</a>
                <a href="#" class="k-button" id="btnCargarEstado"  ng-click="cargarEstado()">Load State</a>
            </div>
            <div kendo-grid="GridGuardarEstado" k-options="opcionesGridGuardarEstado">
            </div>
        </div>
    </div>
</div>

<script>
    angular.module("appGridPersistencia", ["kendo.directives"])
            .controller("ctrlGridPersistencia", function ($scope, $http) {
                
                $scope.origenDatosClientes = new kendo.data.DataSource({
                    type: "odata-v4",
                    transport: {
                        read: {
                            url: "http://localhost:49952/Clientes?$expand=TipoCliente",
                            dataType: "json"
                        },
                        create: {
                            type: "POST",
                            url: "http://localhost:49952/Clientes",
                            contentType: "application/json",
                            dataType: "json",
                            complete: function (e) {
                                $scope.recuperarClientes();
                            }
                        },
                        update: {
                            url: function (data) {
                                return "http://localhost:49952/Clientes" + "(" + data.Id + ")";
                            },
                            type: "PUT",
                            contentType: "application/json",
                            dataType: "json"
                        },
                        destroy: {
                            url: function (data) {
                                return "http://localhost:49952/Clientes" + "(" + data.Id + ")";
                            },
                            dataType: "json",
                            type: "DELETE"
                        }
                    },
                    schema: {
                        data: function (data) {
                            return data.value;
                        },
                        total: function (data) {
                            return data['@@odata.count'];
                        },
                        model: {
                            id: "Id",
                            fields: {
                                Id: { editable: false, type: "number" },
                                Nombre: { type: "string" },
                                Mail: { type: "string" },
                                Telefono: { type: "string" },
                                Direccion: { type: "string" },
                                TipoClienteId: { type: "number" },
                                TipoCliente: {
                                    defaultValue: { Id: 0, Nombre: "" },
                                    fields: {
                                        Id: { type: "number" },
                                        Nombre: { type: "string" }
                                    }
                                }
                            }
                        }
                    },
                    pageSize: 5,
                    serverPaging: true,
                    serverFiltering: true,
                    serverSorting: true,
                    error: function (e) {
                        alert("Status: " + e.status + "; Error message: " + e.errorThrown);
                    }
                });

                $scope.opcionesComboTipoCliente = {
                    dataSource: new kendo.data.DataSource({
                        type: "odata-v4",
                        transport: {
                            read: {
                                url: "http://localhost:49952/TiposCliente",
                            }
                        }
                    }),
                    dataTextField: "Nombre",
                    dataValueField: "Id"
                }
                $scope.comboBoxTipoCliente = function (container, options) {
                    var editor = $('<select kendo-combo-box k-options="opcionesComboTipoCliente" data-bind="value:' + options.field + '"/>')
                    .appendTo(container);
                }
                $scope.opcionesGridGuardarEstado = {
                    groupable: {
                        messages: {
                            empty: "Para agrupar el listado, arrastre la cabecera de la columna aquí"
                        }
                    },
                    filterable: true,
                    pageable: {
                        pageSize: 5,
                        previousNext: true,
                        numeric: true,
                        buttonCount: 5,
                        input: true,
                        pageSizes: [5, 10, 15, 20, 30, "all"],
                        refresh: true,
                        info: true,
                        messages: {
                            display: "Mostrando {0}-{1} de {2} clientes",
                            empty: "No hay datos de clientes",
                            page: "Introduzca Página",
                            of: " de {0}",
                            itemsPerPage: "registros por página",
                            first: "Primera",
                            last: "Última",
                            next: "Siguiente",
                            previous: "Anterior",
                            refresh: "Actualizar",
                            morePages: "Más paginas"
                        }
                    },
                    sortable: {
                        allowUnsort: true,
                        mode: "multiple"
                    },
                    columnMenu: {
                        columns: true,
                        filterable: true,
                        sortable: true
                    },
                    columns: [
                        { field: "Id", title: "Identificador" },
                        { field: "Nombre", title: "Nombre Completo" },
                        { field: "Mail", title: "e-Mail" },
                        { field: "Telefono", title: "Telefono" },
                        { field: "Direccion", title: "Dirección" },
                        { field: "TipoClienteId", title: "Tipo", editor: $scope.comboBoxTipoCliente, template: "#=TipoCliente.Nombre#" },
                        {
                            command: [
                                {
                                    name: "edit",
                                    text: { edit: "Editar", cancel: "Cancelar Edición", update: "Grabar Cambios" }
                                },
                                {
                                    name: "destroy",
                                    text: "Borrar"
                                }
                            ],
                            title: "&nbsp;",
                            width: "250px"
                        }
                    ],
                    dataSource: $scope.origenDatosClientes,
                    editable: {
                        createAt: "bottom",
                        mode: "inline"
                    },
                    toolbar: [
                        {
                            name: "create",
                            text: "Nuevo Cliente"
                        }

                    ]
                };

                $scope.guardarEstado = function () {
                    localStorage["opcionesGridGuardarEstado"] = kendo.stringify($scope.GridGuardarEstado.getOptions());
                };

                $scope.cargarEstado = function () {
                    var opcionesGrid = localStorage["opcionesGridGuardarEstado"];
                    if (opcionesGrid) {
                        $scope.GridGuardarEstado.setOptions(JSON.parse(opcionesGrid));
                    }
                };
                
                $scope.recuperarClientes = function () {
                    $scope.opcionesGridGuardarEstado.dataSource.read();
                };

            }).run(function () {
                kendo.culture("es-ES");
            })
</script>

Sin entrar en detalles sobre la edición del grid, el proceso para guardar el estado es muy simple:

  • En la directiva “kendo-grid” hemos asignado un nombre al grid, para que podamos hacer referencia a él, desde el $scope.
  • El proceso para guardar el estado realiza las siguientes operaciones:
    • Invoca al método “getOptions” para recuperar el objeto de configuración
    • Usamos la función kendo.stringify para convertir el objeto a una cadena con formato JSON.
    • Guardamos la cadena JSON en el LocalStorage usando la key “opcionesGridGuardarEstado”.
       $scope.guardarEstado = function () {
          localStorage["opcionesGridGuardarEstado"] = kendo.stringify($scope.GridGuardarEstado.getOptions());
       };
    
  • El proceso de recuperar el estado sigue el proceso contrario:
    • Recuperamos la cadena JSON del LocalStorage usando la misma key utilizada en el proceso de guardado
    • Si se encuentran datos, estos son convertidos a un objeto de configuración usando la función JSON.Parse
    • El objeto de configuración es asignado el grid usando la función “setOptions”
       $scope.cargarEstado = function () {
          var opcionesGrid = localStorage["opcionesGridGuardarEstado"];
             if (opcionesGrid) {
                $scope.GridGuardarEstado.setOptions(JSON.parse(opcionesGrid));
             }
       };
    

Para nuestro ejemplo, hemos usado el LocalStorage como almacén para la configuración, pero también podemos enviar el objeto de configuración al servidor y usar una base de datos para guardarlo.

El Grid de Kendo UI incluye la posibilidad de trabajar en modo offline. Para activarlo debemos seguir los siguientes pasos:

  • Establecer una key del LocalStorage a la propiedad “offlineStorage” del objeto “DataSource”
        offlineStorage: "clientesOffline"
    
  • Usar el método “online” del “DataSource” para indicar si queremos activar o no el modo online. El siguiente fragmento de código activa el modo offline.
        $scope.opcionesGridGuardarEstado.dataSource.online(false); 
    

Cuando se activa el modo “offline”, los registros del grid y los cambios realizados, son almacenados automáticamente en el LocalStorage sin realizarse ninguna comunicación con el servidor. Al desactivar el modo “offline”

   $scope.opcionesGridGuardarEstado.dataSource.online(true); 

Todos los cambios son enviados al servidor.

A continuación, mostramos el código completo

@{
    ViewBag.Title = "Index";
}
<div ng-app="appGridPersistencia" ng-controller="ctrlGridPersistencia">
    <div class="panel panel-primary">
        <div class="panel-heading">
            <h3 class="panel-title">Grid Kendo UI - Guardar Estado</h3>
        </div>
        <div class="panel-body">
            <div class="box wide">
                <a href="#" class="k-button" id="btnGuardarEstado" ng-click="guardarEstado()">Save State</a>
                <a href="#" class="k-button" id="btnCargarEstado"  ng-click="cargarEstado()">Load State</a>
            </div>
            <h4>Estado: {{TextoEstadoGrid}}</h4>
            <div style="padding-bottom: 30px;">
                <input id="EstadoGrid" type="checkbox" checked ng-model="EstadoGrid" ng-click="comprobarEstado()">
            </div>
            <div kendo-grid="GridGuardarEstado" k-options="opcionesGridGuardarEstado">
            </div>
        </div>
    </div>
</div>

<script>
    angular.module("appGridPersistencia", ["kendo.directives"])
            .controller("ctrlGridPersistencia", function ($scope, $http) {
                
                $scope.origenDatosClientes = new kendo.data.DataSource({
                    offlineStorage: "clientesOffline",
                    type: "odata-v4",
                    transport: {
                        read: {
                            url: "http://localhost:49952/Clientes?$expand=TipoCliente",
                            dataType: "json"
                        },
                        create: {
                            type: "POST",
                            url: "http://localhost:49952/Clientes",
                            contentType: "application/json",
                            dataType: "json",
                            complete: function (e) {
                                $scope.recuperarClientes();
                            }
                        },
                        update: {
                            url: function (data) {
                                return "http://localhost:49952/Clientes" + "(" + data.Id + ")";
                            },
                            type: "PUT",
                            contentType: "application/json",
                            dataType: "json"
                        },
                        destroy: {
                            url: function (data) {
                                return "http://localhost:49952/Clientes" + "(" + data.Id + ")";
                            },
                            dataType: "json",
                            type: "DELETE"
                        }
                    },
                    schema: {
                        data: function (data) {
                            return data.value;
                        },
                        total: function (data) {
                            return data['@@odata.count'];
                        },
                        model: {
                            id: "Id",
                            fields: {
                                Id: { editable: false, type: "number" },
                                Nombre: { type: "string" },
                                Mail: { type: "string" },
                                Telefono: { type: "string" },
                                Direccion: { type: "string" },
                                TipoClienteId: { type: "number" },
                                TipoCliente: {
                                    defaultValue: { Id: 0, Nombre: "" },
                                    fields: {
                                        Id: { type: "number" },
                                        Nombre: { type: "string" }
                                    }
                                }
                            }
                        }
                    },
                    pageSize: 5,
                    serverPaging: true,
                    serverFiltering: true,
                    serverSorting: true,
                    error: function (e) {
                        alert("Status: " + e.status + "; Error message: " + e.errorThrown);
                    }
                });

                $scope.opcionesComboTipoCliente = {
                    dataSource: new kendo.data.DataSource({
                        type: "odata-v4",
                        transport: {
                            read: {
                                url: "http://localhost:49952/TiposCliente",
                            }
                        }
                    }),
                    dataTextField: "Nombre",
                    dataValueField: "Id"
                }
                $scope.comboBoxTipoCliente = function (container, options) {
                    var editor = $('<select kendo-combo-box k-options="opcionesComboTipoCliente" data-bind="value:' + options.field + '"/>')
                    .appendTo(container);
                }
                $scope.opcionesGridGuardarEstado = {
                    groupable: {
                        messages: {
                            empty: "Para agrupar el listado, arrastre la cabecera de la columna aquí"
                        }
                    },
                    filterable: true,
                    pageable: {
                        pageSize: 5,
                        previousNext: true,
                        numeric: true,
                        buttonCount: 5,
                        input: true,
                        pageSizes: [5, 10, 15, 20, 30, "all"],
                        refresh: true,
                        info: true,
                        messages: {
                            display: "Mostrando {0}-{1} de {2} clientes",
                            empty: "No hay datos de clientes",
                            page: "Introduzca Página",
                            of: " de {0}",
                            itemsPerPage: "registros por página",
                            first: "Primera",
                            last: "Última",
                            next: "Siguiente",
                            previous: "Anterior",
                            refresh: "Actualizar",
                            morePages: "Más paginas"
                        }
                    },
                    sortable: {
                        allowUnsort: true,
                        mode: "multiple"
                    },
                    columnMenu: {
                        columns: true,
                        filterable: true,
                        sortable: true
                    },
                    columns: [
                        { field: "Id", title: "Identificador" },
                        { field: "Nombre", title: "Nombre Completo" },
                        { field: "Mail", title: "e-Mail" },
                        { field: "Telefono", title: "Telefono" },
                        { field: "Direccion", title: "Dirección" },
                        { field: "TipoClienteId", title: "Tipo", editor: $scope.comboBoxTipoCliente, template: "#=TipoCliente.Nombre#" },
                        {
                            command: [
                                {
                                    name: "edit",
                                    text: { edit: "Editar", cancel: "Cancelar Edición", update: "Grabar Cambios" }
                                },
                                {
                                    name: "destroy",
                                    text: "Borrar"
                                }
                            ],
                            title: "&nbsp;",
                            width: "250px"
                        }
                    ],
                    dataSource: $scope.origenDatosClientes,
                    editable: {
                        createAt: "bottom",
                        mode: "inline"
                    },
                    toolbar: [
                        {
                            name: "create",
                            text: "Nuevo Cliente"
                        }

                    ]
                };

                $scope.guardarEstado = function () {
                    localStorage["opcionesGridGuardarEstado"] = kendo.stringify($scope.GridGuardarEstado.getOptions());
                };

                $scope.cargarEstado = function () {
                    var opcionesGrid = localStorage["opcionesGridGuardarEstado"];
                    if (opcionesGrid) {
                        $scope.GridGuardarEstado.setOptions(JSON.parse(opcionesGrid));
                    }
                };
                
                $scope.recuperarClientes = function () {
                    $scope.opcionesGridGuardarEstado.dataSource.read();
                };

                $scope.EstadoGrid = true;

                $scope.TextoEstadoGrid = "Online";

                $scope.comprobarEstado = function () {
                    if ($scope.EstadoGrid) {
                        $scope.TextoEstadoGrid = "Online";
                        
                    } else {
                        $scope.TextoEstadoGrid = "Offline";
                    }
                    $scope.opcionesGridGuardarEstado.dataSource.online($scope.EstadoGrid);
                };

            }).run(function () {
                kendo.culture("es-ES");
            })
</script> 

Y una captura de pantalla

GuardarEstado

Los proyectos de ejemplo usados para este articulo son: