Merida Design Blog

Publicado el | Tutoriales / ,

Como crear una tabla con registros de campos variables o desconocidos usando AngularJS

Ver código fuente y demo.

En mas de una ocasión he tenido que desarrollar aplicaciones que consumen una API cuyos registros no cuentan con campos definidos, ya sea porque la API está en una fase beta, o porque así está diseñada debido a la naturaleza de sus datos, por ejemplo, una aplicación de bienes raíces en la que cada propiedad puede tener diferentes valores.

Generalmente para este tipo de aplicaciones lo mejor es usar bases de datos NoSQL o en formato JSON como Firebase.

Hacer uso de base de datos con este tipo de estructura tiene muchas ventajas, entre ellas velocidad y flexibilidad, sin embargo presenta algunos retos como el caso que demostraré en este artículo que es, mostrar los registros en una tabla sin conocer sus campos.


Preparando los Datos

Para fines de este tutorial vamos a crear un archivo .JSON que represente el esquema de datos antes mencionado.

Inicia un nuevo proyecto en tu editor preferido y crea un archivo con el nombre data.json, a continuación coloca el siguiente código:

    [
      {
          "id": 1,
          "firstName": "John",
          "lastName": "Doe"
      },
      {
          "id": 2,
          "firstName": "Jane",
          "email": "[email protected]"
      },
      {
          "id": 3,
          "firstName": "Iván",
          "lastName": "Villamil",
          "blog": "blog.meridadesign.com",
          "twitter": "ivillamil"
      }
    ]

Como puedes ver los objetos difieren en algunos campos, incluso nuevos valores agregados podrían diferir completamente, pero ya veremos como eso no nos afecta con la solución que vamos a implementar.


Configurando la App

A continuación necesitamos crear el módulo de Angular que servirá como entorno de ejecución para todas las funciones de nuestra app.

Regresa a tu editor y en la misma carpeta que creamos el archivo data.json crea un nuevo archivo y llámalo app.js, coloca en él el siguiente código:

    (function() {
        'use strict';
        angular.module('dinamicTable', []);  
    } ());

Este código se encarga de crear un nuevo módulo llamado dinamicTable al cual adjuntaremos nuestro controlador y servicio lo cual haremos a continuación.


El Servicio

Ahora necesitamos crear un servicio que se encargue de consultar la fuente de datos (data.json) y devolver el resultado procesado.

Ve al editor y crea el archivo data.service.js, luego copia el siguiente código:

    (function() {
        'use strict';
	
        angular.module('dinamicTable')
            .factory('DataService', DataService);
	
        function DataService($http, $q) {
            /** =Variables privadas ----- */
            var fn = {};
            var url = 'data.json';
	  
            /** =Métodos Públicos ----- */
            fn.query = query;

            /** =Definición de Métodos */
            function query() {
                // Promesas
                var dfd = $q.defer();
		
                $http.get(url)
                    .then(function (response) {
                        dfd.resolve({
                            items: response.data,
                            headers: _getHeaders(response.data)
                        });
                    }, function (error) {
                        dfd.reject(error);
                    });
		
                return dfd.promise;
            }
	  
            /** =Métodos Privados */
            function _getHeaders(items) {
                var headers = [];
                angular.forEach(items, fuction (item) {
                    headers = _.union(headers, Object.getOwnPropertyNames(item);
                });

                return headers;
            }
	  
            return fn;
        }
    } ());

Para este servicio incluimos los servicios $http y $q (línea 7) los cuales nos servirán para consultar los datos del servidor (data.json) y gestionar las llamadas asíncronas respectivamente.

La función query (línea 16) se encarga de consultar el servidor y regresar una promesa (línea 30) mientras espera por la respuesta del servidor, una vez que el servidor responde positivamente regresándonos los datos, resolvemos la promesa (línea 22) regresando un objeto compuesto por 2 valores, el primero es el listado de resultados sin procesar y el segundo es el listado de todos los nombres de campos que se encuentren dentro de la colección de objetos.

El método _getHeaders es el que se encarga de devolver los nombres de los campos de los objetos, para lograrlo es necesario iterar en toda la colección de objetos y en cada iteración obtener los nombres de los campos con Object.getOwnPropertyNames(item) para luego combinarlos en un arreglo mediante la función _.union.

La función _.union es parte de la librería LoDash, la cual incluiremos al momento de crear el archivo index.html.


El Controlador

Siempre en la misma carpeta que los archivos anteriores, crea un archivo y nómbralo table.controller.js, luego copia el siguiente código:

    (function() {
        'use strict';
	  
        angular.module('dinamicTable')
            .controller('TableController', TableController);
	  
        function TableController(DataService) {
            var vm = this;

            /** =Propiedades Públicas ----- */
            vm.items = vm.headers = [];
		
            DataService.query()
                .then(function(data) {
                    vm.headers = data.headers;
                    vm.items = data.items;
                });
        }
    } ());

El código para el controlador es más sencillo, solo necesitamos incluir el DataService (línea 7) que creamos anteriormente y ejecutar la función query, la cual como ya vimos nos regresará una promesa. Cuando la promesa sea resuelta (línea 14) asignamos los datos correspondientes a las propiedades públicas items y headers, a las cuales podremos acceder desde la vista.


La vista

Una vez que ya tenemos nuestro servicio y el controlador listos, vamos a crear la vista en donde vamos a pintar por fin la tabla.

Crea el archivo index.html, y en él coloca el siguiente código:

    <!Doctype html>
    <html lang="es">
        <head>
            <title>Tabla con Atributos Dinámicos</title>
            <link href='http://fonts.googleapis.com/css?family=Roboto:400,300,700' rel='stylesheet' type='text/css'>
            <link rel="stylesheet" href="styles.css" />
        </head>		
        <body ng-app="dinamicTable">
            <table border=0 cellspacing=0 ng-controller="TableController as Table">
                <tr>
                    <th ng-repeat="header in Table.data.headers">
                        {{ header }}
                    </th>
                </tr>
                <tr ng-repeat="item in Table.data.items">
                    <td ng-repeat="header in Table.data.headers">
                        {{ item[header] || '' }}
                    </td>
                </tr>
            </table>
		
            <!-- Código fuente de AngularJS -->
            <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
			
            <!-- Lodash: para procesamiento de arreglos y colecciones entre otros -->
            <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.0/lodash.min.js"></script>
			
            <!-- Archivos de nuestra aplicación -->
            <script src="app.js"></script>
            <script src="data.service.js"></script>
            <script src="table.controller.js"></script>
        </body>
    </html>

Como podemos ver en la línea 11, recorremos el arreglo de los nombres de campos obtenidos en el servicio (DataService) y los pintamos como cabeceras para la tabla.

Luego en la línea 15 recorremos toda la colección de datos y por cada registro recorremos el mismo arreglo que en las cabeceras, pintando el valor del campo cuando lo tenga ó una cadena que nosotros elijamos para representar que ese valor está vacío (línea 17).

De la línea 23 a la 31 incluimos los archivos necesarios para hacer funcionar la aplicación:

  • Línea 23: EL framework AngularJS.
  • Línea 26: La librería LoDash, la cual si recuerdas nos sirvió para unir los nombres de los campos mediante la función _.union.
  • Línea 29,39 y 31: Los archivos creados durante este artículo.

Por último solo nos queda una cosa: los estilos.


Estilos

Crear el archivo styles.css y copia en él el siguiente código:

body {
  background-color: #eee;
  color: #333;
  font-family: 'Roboto', sans-serif;
  font-size: 14px;
}

table {
  border: 1px solid #17729D;
  box-shadow: 3px 3px 2px rgba(0,0,0,0.1);
  left: 50%;
  max-width: 600px;
  position: absolute;
  top: 50%;
  -webkit-transform: translate(-50%,-50%);
  transform: translate(-50%,-50%);
  width :100%;
}

th, td {
  background-color: #fff;
  border-bottom: 1px solid #ccc;
  font-weight: 300;
  padding: .5em;
}

td:not(:last-child) { border-right: 1px solid #efefef; }

th {
  background-color: #17729D;
  border-right: 1px solid rgba(0,0,0,0.08);
  color: #fff;
  text-transform: Capitalize;
}

tr:nth-child(odd) td { background-color: #f6f6f6; }

Ahora abre el archivo index.html en tu explorador, tu tabla se debe ver igual a la de esta imagen:

table1

Ya tenemos nuestra tabla que acepta valores dinámicos, ¿excelente no?… bueno, no tanto, tener como título de la tabla firstName y lastName no es algo muy legible, lo ideal es separar las palabras para así tener First Name y Last Name lo cual es mucho mas legible.

Por fortuna esto es muy fácil de lograr con los filtros de AngularJS, así que vamos a crear uno para separar palabras en cammelCase.


Filtro para separar el cammelCase

Regresa a tu editor, crea el archivo splitCammel.filter.js y copia el siguiente código:

(function () {
  'use strict';
  
  angular.module('dinamicTable')
    .filter('splitCammelCase', SplitCammelCase);
    
  function SplitCammelCase() {
    return function (input) {
      var regex = /([A-Z])/g; 
      var subst = ' $1'; 
 
      return input.replace(re, subst);
    }
  }
} ());

  • En la línea 9 definimos una expresión regular que regresará todas las letras en mayúscula que se encuentren en la cadena evaluada.
  • En la línea 10 definimos la cadena por la cual serán sustituidas las coincidencias, en ella solo agregamos un espacio en blanco y el comodín $1 que regresa la coincidencia de la evaluación en la expresión regular.
  • Por último solo regresamos el resultado de la evaluación y sustitución de los valores.

Ahora en el archivo index.html sustituye la línea 12 como se indica:

 {{ header | splitCammelCase }} 

Listo, ahora los título están separados y es mas fácil de leer.

table2

Aquí puedes mirar el demo y código fuente del artículo.


Conclusión

Como mencioné al principio, este tipo de implementación es ideal para presentar en pantalla listados de propiedades o incluso para usar en CRM en los cuales es necesario agregar objetos cuyas propiedades pueden variar.

Si este artículo te ha sido de utilidad o crees que hay algo que hizo falta mencionar, no olvides dejar tus comentarios, me encantaría conocer tu opinión al respecto ó incluso si ya haz realizado algo similar, que tipo de proyecto haz realizado.



Publicaciones que pueden interesarte

    Deja un comentario

      tope
    Derechos Reservados, Merida Design 2017
    %d bloggers like this: