Utilizar GAS para crear aplicaciones web (2ª parte)

Català (Catalán) English (Inglés)

Después del primer artículo explicando un poco qué son las web apps hechas con Google Apps Script (GAS) vamos a ver en este segundo artículo cómo crear una sencilla. Y lo haré con un ejemplo real, relacionado con la evaluación de los alumnos. Ya aviso que este artículo será mucho más técnico y que se necesitan conocimientos de GAS, de html, de estilos css y de javascript. Como dicen mis amigos del grupo de coordinadores del GEG Spain, será en lenguaje «balleno«.

Crearemos una aplicación para que los alumnos rellenen un KPSI (Knowledge and Prior Study Inventory) al iniciar una unidad o proyecto y lo vuelvan a rellenar en terminarlo. Para los que no conozcáis el instrumento KPSI no es nada más que unas preguntas sobre el tema, donde los alumnos no tienen que dar la respuesta, sino que deben indicar si conocen la respuesta. Sirve para ser conscientes de los conocimientos previos y para que el alumno se dé cuenta del progreso que ha hecho al acabar.

Para que se pueda seguir mejor, en esta carpeta se pueden encontrar los archivos finales, tanto la hoja de cálculo como el script con el código y los html.

Haremos una aplicación para que los alumnos respondan el siguiente KPSI de 4 preguntas.

Para empezar, vamos a crear una hoja de cálculo (sin ningún script) para poder guardar los datos. La hoja podría ser como la siguiente (se puede encontrar en la carpeta):

A continuación, vamos a crear un script (este script siempre se crea en mi unidad, pero luego se puede desplazar a la carpeta que queramos).

El script comenzará con algunas definiciones de variables constantes, la dirección de la hoja, el nombre de las hojas y la dirección del usuario, y, de momento, tendrá una sola función, la función doGet.

Como decía en el anterior artículo, para crear web apps hay que saber GAS, html, css y javascript. Así que no me entretendré demasiado a explicar cada función, ya que sino, más que un artículo, esto sería un curso completo.

La función doGet es la única que puede servir páginas html en las web apps. Por tanto, es en esta función donde pondremos el código. De entrada, recogeremos todos los datos que haya en la hoja de cálculo. Comprobamos que el usuario que contesta está en la lista de los alumnos.

//URL de la hoja de cálculo
const full_control="https://docs.google.com/spreadsheets/d/1iwl4xEoPuaFovufPr-YktmK0Z3jk8EvUmxQmgrreicE/edit";

//Nombre de la pestaña de la hoja de cálculo
const full="Hoja 1";

//Reogemos la dirección del usuario l'adreça de l'usuar
const usuari=Session.getActiveUser().getEmail();

function doGet(e) {
 //Comprobamos si el usuario está en la lista de alumnos
 var spreadsheet = SpreadsheetApp.openByUrl(full_control); 
 var sheet = spreadsheet.getSheetByName(full);
 var rang = sheet.getDataRange();
 var alumnes = rang.getValues();
 var numRows = rang.getNumRows();
 let identificacio=0;
 for (let i=1;i<numRows;i++){
  if (alumnes[i][1]==usuari){
   identificacio=1;
  }
 }
 //Si el alumno no está en la llista, se indica con un mensaje emergente
 if (identificacio==0){
  return HtmlService.createHtmlOutputFromFile('html_no_autoritzado').setTitle("Aplicación KPSI")
 }else{

}

En caso de que el usuario no esté en la lista, devolvemos un html. Este html debemos crearlo en el script. En el código que he puesto, lo he llamado html_no_autoritzado.

Aunque la aplicación será pública en nuestro dominio (para quienes conozcan la URL), ya nos hemos asegurado que solo puedan utilizar la aplicación nuestros alumnos.

Ahora nos toca llenar la otra parte del if, el else. Si el usuario es uno de nuestros alumnos, le mostraremos la página html que debe rellenar. Esta página la llamaremos KPSI.html

//Si el alumno no está en la llista, se indica con un mensaje emergente
if (identificacio==0){
  return HtmlService.createHtmlOutputFromFile('html_no_autoritzado').setTitle("Aplicación KPSI")
}else{
  //Abrimos la pantalla de la aplicación, pasando los parámetros necesarios
  plantilla=HtmlService.createTemplateFromFile('KPSI');
  plantilla.usuari=usuari;
  plantilla.alumnes=alumnes;
  return plantilla.evaluate().setTitle("Aplicación KPSI") 
}

A diferencia de cuando no era un alumno nuestro, que utilizábamos createHtmlOutputFromFile para mostrar la página html, ahora utilizamos createTemplateFromFile, que nos permite pasar parámetros. Cuando el usuario no es un alumno, basta con mostrar un mensaje. Ahora, habrá que mostrar una cabecera con el mail del alumno y, si ya ha contestado el KPSI, habrá que mostrar sus respuestas anteriores. Para ello hay que pasar como parámetros, el usuario y el contenido de la Hoja 1.

Vamos ahora por el contenido de la página KPSI. Comenzaremos con dos referencias dentro del head

<html>
<head>
<title>Aplicación KPSI</title>
<base target="_top">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<?!= include('css.html'); ?>

La primera (<link href = «https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css» …) es para poder utilizar los estilos de bootstrap para dar formado a nuestra página. Se podría hacer creando nosotros todo el css, pero sería más complicado de hacer adaptable a diferentes tamaños de pantallas. Si no se conocen las clases de bootstrap, en este enlace encontraréis la documentación.

La segunda (<?! = Include ( ‘css.html’);?>) nos permite tener nuestro propio hoja de estilos. Nos tocará crear otro html que lleve por nombre css y poner nuestros estilos.

<style>
  div.cap
  {
    color:#ffffff;
    background-color: gray;
  }
  div.titol{
    font-weight: bold;
    font-size: 1.4em;
    height: 50px;
  }
</style>
Además, en el archivo de código donde está la función doGet, tendremos que añadir una función nueva, include, que es la que permite incrustar los estilos que creamos en el css.
function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename)
      .getContent();
}

Pero no nos distraigamos y seguimos con la página KPSI.html. Una vez, indicado en el head las referencias, definimos una cabecera ya dentro del body. Aprovechando los estilos de bootstrap, podemos crear como un grid de fondo y colocar los campos donde queramos (en filas y columnas). Además, añadimos css personalizado para dar el aspecto que queremos (como div «cap» y div «titol», que están definidos en el css.html).

<html>
 <head>
  <title>KPSI</title>
  <base target="_top">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
  <?!= include('css.html'); ?>
 </head>
 <body>
  <div class="container">
   <div class="row cap">
    <div class="col titol">
      Aplicació KPSI
    </div>
    <div class="col-2">
      Mail del alumno: <?= usuari ?>
    </div>
   </div>
  <div>
 </body>
</html>

Fíjense que ya hacemos aparecer uno de los parámetros que hemos pasado, el mail del alumno. Simplemente indicamos que es código javascript con los símbolos <?   ?>.

Pero probamos que lo que estamos haciendo funciona. Si hemos creado todos los archivos (html_no_autoritzado, KPSI y css), utilizamos el botón Implementar y elegimos la opción de Nueva implementación.

Elegimos que el tipo sea Aplicación web.

Indicamos una descripción (como el número de versión), que quien ejecute la aplicación seamos nosotros (que somos los únicos que tenemos acceso a la hoja de cálculo) y que tenga acceso a cualquier usuario de nuestro dominio (ya que ya controlamos por código que sea un alumno).

Como todos los scripts, nos aparecerá una pantalla pidiendo acceso y tendremos que otorgar permisos al script. Una vez dados, ya nos aparece la dirección para probarla. Si la dirección de nuestro usuario es uno de los de la lista en la hoja de cálculo, nos aparecerá la siguiente pantalla.

El color de fondo lo podemos cambiar fácilmente con nuestro css. Si hacemos cambios en el código y queremos volver a probarlo, en lugar de utilizar la opción de nueva implementación, es mucho mejor utilizar la opción de implementaciones de pruebas. Cuando ya hayamos hecho todas las pruebas, ya volveremos a hacer una nueva Implementación para obtener la dirección definitiva que deberán utilizar los alumnos.

Ahora hay que seguir trabajando con la página KPSI para que aparezcan las cuatro preguntas que los alumnos deben contestar y los desplegables. Doy por supuesto que se sabe html y javascript, así que pongo el código y comento las cosas más importantes.<

<html>
 <head>
  <title>Apliación KPSI</title>
  <base target="_top">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
  <?!= include('css.html'); ?>
 </head>
 <script>
  function habilitar(){
   //Al cambiar algun valor, se habilita el botón guardar
   document.KPSI.btn_desar.disabled=false;
  }
  function guardar(){
   google.script.run.guardar(document.forms["KPSI"]);  //ejecuta la función guardar del código
   document.KPSI.btn_desar.disabled=true; //Deshabilitamos el botón
   window.alert("Se han guardao los cambos correctamente"); //mostrar un mensage indicando que se ha guardado
  }
 </script>
 <body>
  <div class="container">
   <div class="row cap">
    <div class="col titol">
     Aplicación KPSI
    </div>
   <div class="col-2">
    Mail del alumno: <?= usuari ?>
   </div>
  </div>
 <div>
 <div class="container separador"></div>
 <div class="container">
  <div class="row g-3">
  <div class="col-md-4 titol">
   Alumnos
  </div>
  <div class="col-md-2 titol">
   Inicio
  </div>
  <div class="col-md-2 titol">
   Final
  </div> 
 </div> 
 <div class="container separador"></div> 
 <div class="container">
  <form name="KPSI" id="KPSI" action="javascript:;">
   <? for (let i=2;i<alumnes.length;i++){ //Miramos en qué fila de la hoja de cálculo están los datos del alumno que accede
    if (alumnes[i][1]==usuari){
     var fila=i;
    }
   }
 
   for (let i=2;i<alumnes[0].length;i=i+2){ ?>
    <div class="row g-3">
    <div class="col-md-4">
     <label for="curs_grup"><?= alumnes[0][i]?></label>
    </div>
    <div class="col-md-2">
     <select class="form-select" name="inici[]" onchange="habilitar()">
      <option value=""></option>
      <option value="1" <? if (alumnes[fila][i]==1){ ?>selected<? } ?>>No lo sé</option>
      <option value="2" <? if (alumnes[fila][i]==2){ ?>selected<? } ?>>No lo entiendo</option>
      <option value="3" <? if (alumnes[fila][i]==3){ ?>selected<? } ?>>Creo que sí que lo sé</option>
      <option value="4" <? if (alumnes[fila][i]==4){ ?>selected<? } ?>>Lo podría explicar a mis compañeros/as sin dificultad</option>
     </select>
    </div>
   <div class="col-md-2">
    <select class="form-select" name="final[]" onchange="habilitar()">
     <option value=""></option>
     <option value="1" <? if (alumnes[fila][i]==1){ ?>selected<? } ?>>No lo sé</option>
     <option value="2" <? if (alumnes[fila][i]==2){ ?>selected<? } ?>>No lo entiendo</option>
     <option value="3" <? if (alumnes[fila][i]==3){ ?>selected<? } ?>>Creo que sí que lo sé</option>
     <option value="4" <? if (alumnes[fila][i]==4){ ?>selected<? } ?>>Lo podría explicar a mis compañeros/as sin dificultad</option>
   </select>
  </div> 
 </div>
 <div class="container separador2"></div> 
 <? } ?>
</div>
<div class="container separador2"></div>
<div class="container">
  <div class="row g-3">
   <div class="col-md-4"></div>
   <div class="col-md-4"></div>
    <input type="text" class="form-control" value="<?= fila ?>" id="fila" name="fila" hidden>
   <div class="col-md-2"><button class="btn btn-primary" name="btn_desar" id="btn_desar" disabled onclick="guardar()">Guardar</button></div>
 </div>
</body>
</html>

Básicamente, hemos añadido un formulario y, con la instrucción for, tantas preguntas como se han indicado en la hoja de cálculo. Como en la hoja de cálculo tenemos columnas combinadas para poder tener las respotes del inicio y del final juntas, el for lo hacemos con un incremento de 2 de i (i = i + 2).

Antes, hemos mirado en qué fila estaban los datos del usuario para poder mostrar sus respuestas anteriores, si había hecho. Para no volver a calcularlo después, hemos añadido un campo oculto en el formulario con este número de fila. Así lo podremos pasar al código para poderlo utilizar.

Además, hemos añadido un par de funciones en javascript. Una en los select, onChange, para que el botón guardar sólo se active cuando se haga algún cambio.

La segunda función, guardar, que se activa al pulsar el botón, onclick, es la que nos devolverá a la hoja de código, pasando como parámetros todos los valores que se ha introducido en los desplegables. la función es muy sencilla:

google.script.run.guardar(document.forms["KPSI"]);  //ejecuta la función guardar del código
window.alert("Se han guardao los cambios correctamente"); //mostrar un mensage indicando que se ha guardado

¿Qué nos faltaría? Definir la función guardar en el código. Esta función es la que guardará en la hoja de cálculo los datos.

function guardar(KPSI){
 //Recuperamos los valores del formulario
 var inici=KPSI["inici[]"];
 var final=KPSI["final[]"];
 var fila=parseInt(KPSI["fila"]);

 //Creamos una matriz con los valores inicio y final intercalados, igual que en la hoja de cálculo
 var valors=[];
 valors[0]=[];
 let j=0;
 for (let i=0; i<(inici.length+final.length);i=i+2){
  valors[0][i]=inici[j];
  valors[0][i+1]=final[j];
  j++;
 }
 //Guardoamos los valores en la hoja de cálculo
 var spreadsheet = SpreadsheetApp.openByUrl(full_control); 
 var sheet = spreadsheet.getSheetByName(full);
 sheet.getRange(fila+1,3,1,valors[0].length).setValues(valors);
}

Y ya hemos llegado al final. Hemos creado una pequeña aplicación web donde los alumnos acceden, ven las respuestas anteriores y pueden modificarlas.

El aspecto sería el siguiente.

Para acabar de pulirla, estaría bien controlar cuando los alumnos pueden modificar las respuestas. No tiene mucho sentido que al final de la unidad puedan cambiar las respuestas del inicio. Podríamos añadir en la hoja de cálculo algún checkbox para decir si está activada la posibilidad de respuesta de los alumnos para la columna inicila y otro para la columna final. Recogeríamos este valor y en los selects añadiríamos un condicional. Si no se permite, en el código html añadiría disable. Algo así:

<select class = «form-select» name = «inicio []» onChange = «habilitar ()» <? if (permitido_inicio == false) {?> disabled <? }?>

Pero ya os lo dejo para vosotros. Una vez el script estuviera terminado y probado, debería volver a implementarlo. hay que tener en cuenta que si se realiza una implementación nueva, la URL cambia. Si ya se ha enviado la URL a los alumnos, será bastante problemático, ya que los alumnos se harán un lío. Si se desea hacer una nueva implementación sin que cambie la URL, en lugar de usar la opción nueva implementación, debe usarse la opción Gestionar implementaciones, editar la versión y eligir nueva versión. De esta manera, la implementación no cambia y, por tanto, la URL tampoco.

Espero que el artículo haya sido suficientemente entendedor y que se vea la manera de crear Web apps con Google apps script. Dejo pendiente una 3ª parte para explicar cómo trabajar con diferentes pantallas. Como he dicho al principio, la única función que puede mostrar html es la función doGet. Por ejemplo, la función guardar que hemos creado, no puede mostrar ninguna página html. Por lo tanto, para hacer aplicaciones donde queramos más de una pantalla (que al pulsar un botón se’ muestre una pantalla diferente), tendremos que hacer algún truco. Lo cuento en el siguiente artículo, que será más corto y cerrará la trilogía.

Català (Catalán) English (Inglés)

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.