Bruno R

Google Apps Script, parte 4: Recuperar información de una hoja de cálculos

x
portfolio-next-l5vb64p5k-bandikyu.vercel.app

Bueno, en la publicación pasada vimos cómo conectar nuestra aplicación con una hoja de cálculos de Google Drive. En esta ocasión vamos a mostrar esa información en un HTML.

Nuestro archivo GAS había quedado de la siguiente forma:

const SS = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/[ID-DE-TU-HOJA-DE-CALCULOS]/edit#gid=0");

function doGet() {
  let layout = HtmlService.createTemplateFromFile("layout");
  return layout.evaluate();
}

function getCategorias(){

  let ws = SS.getSheetByName("Categorias");

  let data = ws.getRange(
    2,
    1,
    ws.getLastRow() - 1,
    1
  ).getValues();

  return data;

}

(Recordar cambiar URL con tu hoja de cálculos)

Ahora vamos a editar nuestro archivo HTML. Para los siguientes ejemplos yo usaré Bootstrap y Vue.js. Si no estás familiarizado con este último, no te preocupes, igual no será nada complicado.

Así que volvemos a nuestro HTML, en mi caso llamado layout:

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <title> Prueba de Google App Script </title>
    <!-- Bootstrap -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <!-- Vue.js -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
  </head>
  <body>

  </body>
</html>

Dentro del body vamos a crear un contenido que nos permitirá manipular el menú de nuestra hoja de cálculos. Así que vamos a agregar dentro de la etiqueta body una tabla con este aspecto:

<div id="app" class="container">

        <h1> Menú </h1>

        <table class="table">
            <thead>
                <tr>
                    <th> Nombre </th>
                    <th> Descripción </th>
                    <th> Categoría </th>
                    <th> </th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>
                        <input type="text" class="form-control" placeholder="Ingrese el nombre" />
                    </td>
                    <td>
                        <input type="text" class="form-control" placeholder="Ingrese una descripción" />
                    </td>
                    <td>
                        <select class="form-control">
                            <option value=""> Ingrese la categoría </option>
                        </select>
                    </td>
                    <td>
                        <button type="button" class="btn btn-primary"> Agregar </button>
                    </td>
                </tr>
                <tr>
                    <td colspan="3">
                        <input type="text" class="form-control" placeholder="Buscar" />
                    </td>
                    <td>
                        <button type="button" class="btn btn-secondary"> Buscar </button>
                        <button type="button" class="btn btn-secondary"> Traer todo </button>
                    </td>
                </tr>
            </tbody>
        </table>

    </div>
nadaimage by fernando-gaitan.com.ar

La primer fila de la tabla nos servirá para guardar registros nuevos en el menú, mientras que la segunda será para buscar ítem ya creados.

También vamos a instanciar un objeto Vue:

<script>
        new Vue({
            el: '#app'
        });
</script>

Si nunca viste Vue, lo que hace básicamente este código es vincular la instancia con un elemento, que en este caso tiene nombre «app». Es decir el div que creamos antes, y que tiene un atributo id con el valor «app» debe coincidir con el atributo de Vue, el (element).

Llamar a un método del backend desde el frontend

Cada ítem del menú estará tendrá una categoría. Por ejemplo, una pizza grande muzzarella tendrá la categoría Pizzas, como así una agua mineral sin gas en la categoría Bebidas. Entonces para agregar un ítem deberíamos llenar los combos.

Si volvemos al archivo GAS, podemos ver que tenemos una función llamada getCategorias():

function getCategorias(){

  let ws = SS.getSheetByName("Categorias");

  let data = ws.getRange(
    2,
    1,
    ws.getLastRow() - 1,
    1
  ).getValues();

  return data;

}

Podemos llamar a este método desde el frontend de la siguiente forma:

google.script.run.getCategorias();

Como se ve en el ejemplo el nombre debe coincidir como está escrito en el código .gs.

El problema ahora es que si bien la llamada se realizará correctamente, al tratarse de algo asíncrono, no podremos saber cuando nos respondió el servidor.

Método withSuccessHandler()

Para solucionar esto debemos usar el método withSuccessHandler(). Método que recibe como parámetro un callback, función que se va a ejecutar cuando el servidor responda.

Algo como esto:

google.script.run.withSuccessHandler(function(categorias){
   console.log(categorias);
}).getCategorias();

Entonces, ahora que sabemos cómo hacer para recuperar la lista de categorías, vamos a agregar al objeto Vue un array para guardar las mismas y un método para llamar al backend y recuperarlas.

Nuestro objeto vue al final quedará así:

new Vue({
            el: '#app',
            data: function(){
                return {
                    categorias: []
                }
            },
            created: function(){
                this.getCategorias();
            },
            methods: {
                getCategorias: function(){
                    let that = this;
                    google.script.run.withSuccessHandler(function(categorias){
                        that.categorias = categorias;
                    }).getCategorias();
                }
            }
        });

Para los que no sepan vue:

data: Es donde se guarda la información con la que vamos a trabajar.
created: Es un método que se dispara cuando el objeto Vue se crea.
methods: Contiene los métodos o acciones del objeto.

En este caso apenas el objeto Vue esté listo, va a llamar al método getCategorias(), es que el que se comunica con el backend, y finalmente recupera la lista de categorías que guardamos en la data con el mismo nombre. Podemos modificar los combos para mostrar esa información:

<select class="form-control">
   <option value=""> Ingrese la categoría </option>
   <option v-for="c in categorias" v-bind:value="c[0]"> {{ c[0] }} </option>
</select>

La directiva v-for nos permite recorrer una lista en Vue.

Arrays de Arrays

Una cosa que también es bueno aclarar es que Gas nos va a devolver información de la siguiente forma: Un array en donde cada posición representa a una fila. Cada una de estas filas es otro array, en donde cada posición es una columna, la primer posición como en todo array comenzará en 0:

[
	"A1", "B1", "C1",
	"A2", "B2", "C2",
	"A3", "B3", "C3"
]

Supongamos que tenemos una hoja como ésta:

nadaimage by fernando-gaitan.com.ar

El Array que nos devuelve GAS tendrá la siguiente estructura:

[
	["Cristiano Ronaldo", 7, "Juventus"],
	["Messi", 10, "Barcelona"],
	["Mbappé", 7, "PSG"]
]

Por tanto al recorrer la lista, si de pronto quisiéramos mostrar el nombre de cada jugador, deberíamos llamar a la posición [0]. Para el número de camiseta [1] y el equipo donde juega [2].

Por eso en el ejemplo del combo usamos el índice 0:

<option v-for="c in categorias" v-bind:value="c[0]"> {{ c[0] }} </option>

La hoja de categorías tiene una sola columna, si la queremos mostrar debemos usar sub 0. Si tuviese más columnas deberíamos poner el índice contando siempre de izquierda hacia la derecha y empezando por cero.

Finalmente el código quedará de la siguiente forma:

Código GAS:

const SS = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/[ID-DE-TU-HOJA-DE-CALCULOS]/edit#gid=0");

function doGet() {
  let layout = HtmlService.createTemplateFromFile("layout");
  return layout.evaluate();
}

function getCategorias(){

  let ws = SS.getSheetByName("Categorias");

  let data = ws.getRange(
    1,
    1,
    ws.getLastRow(),
    1
  ).getValues();

  return data;

}

Código HTML:

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <title> Prueba de Google App Script </title>
    <!-- Bootstrap -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <!-- Vue.js -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
  </head>
  <body>

    <div id="app" class="container">

        <h1> Menú </h1>

        <table class="table">
            <thead>
                <tr>
                    <th> Nombre </th>
                    <th> Descripción </th>
                    <th> Categoría </th>
                    <th> </th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>
                        <input type="text" class="form-control" placeholder="Ingrese el nombre" />
                    </td>
                    <td>
                        <input type="text" class="form-control" placeholder="Ingrese una descripción" />
                    </td>
                    <td>
                        <select class="form-control">
                            <option value=""> Ingrese la categoría </option>
                            <option v-for="c in categorias" v-bind:value="c[0]"> {{ c[0] }} </option>
                        </select>
                    </td>
                    <td>
                        <button type="button" class="btn btn-primary"> Agregar </button>
                    </td>
                </tr>
                <tr>
                    <td colspan="3">
                        <input type="text" class="form-control" placeholder="Buscar" v-model="texto_busqueda" />
                    </td>
                    <td>
                        <button type="button" class="btn btn-secondary" v-on:click="getMenuFiltrado()"> Buscar </button>
                        <button type="button" class="btn btn-secondary" v-on:click="getMenu()"> Traer todo </button>
                    </td>
                </tr>
            </tbody>
        </table>

    </div>

    <script>

        new Vue({
            el: '#app',
            data: function(){
                return {
                    categorias: []
                }
            },
            created: function(){
                this.getCategorias();
            },
            methods: {
                getCategorias: function(){
                    let that = this;
                    google.script.run.withSuccessHandler(function(categorias){
                        that.categorias = categorias;
                    }).getCategorias();
                }
            }
        });

    </script>


  </body>
</html>

En la siguiente publicación vamos a ver cómo insertar registros nuevos en nuestra hoja de cálculos.