Utilitzar GAS per crear aplicacions web (2a part)

Español (Spanish) English

Després del primer article explicant una mica què són les web apps fetes amb Google Apps Script (GAS) anem a veure en aquest segon article com crear-ne una de senzilla. I ho faré amb un exemple real, relacionat amb l’avaluació dels alumnes. Ja aviso que aquest article serà molt més tècnic i que calen coneixements de GAS, d’html, d’estils css i de javascript. Com diuen els meus amics del grup de coordinadors del GEG Spain, serà en llenguatge “balleno“.

Crearem una aplicació per tal que els alumnes omplin un KPSI (Knowledge and Prior Study Inventory) en iniciar una unitat o projecte i el tornin a omplir en acabar-lo. Pels que no conegueu l’instrument KPSI no és res més que unes preguntes sobre el tema on els alumnes no han de donar la resposta, sinó que han d’indicar si coneixen la resposta. Serveix per ser conscients dels coneixements previs i per tal que l’alumne s’adoni del progrés que ha fet en acabar.

Perquè es pugui seguir millor, en aquesta carpeta trobareu els fitxers finals, tant el full de càlcul com l’script amb el codi i els html.

Farem una aplicació perquè els alumnes responguin el següent KPSI de 4 preguntes.

Per començar, crearem un full de càlcul (sense cap script) per poder desar les dades. El full podria ser el següent (el podeu trobar dins la carpeta):

A continuació, crearem un script (aquest script sempre es crea a la meva unitat, però després es pot desplaçar a la carpeta que vulguem).

L’script començarà amb algunes definicions de variables constants, l’adreça del full i el nom dels fulls, i, de moment, tindrà una sola funció, la funció doGet.

Com deia en l’anterior article, per crear web apps cal saber GAS, html, css i javascript. Així que no m’entretindré massa a explicar cada funció, ja que sinó, més que un article, això seria un curs complet.

La funció doGet és l’única que pot servir pàgines html en les web apps. Per tant, és en aquesta funció on posarem el codi. D’entrada, recollirem el nom d’usuari i totes les dades que hi hagi en el full de càlcul. Comprovem que l’usuari que contesta està a la llista dels alumnes.

//Adreça del full de càlcul
const full_control="https://docs.google.com/spreadsheets/d/1iwl4xEoPuaFovufPr-YktmK0Z3jk8EvUmxQmgrreicE/edit";

//Nom de la pestanya del fuls de càlcul
const full="Full 1";
//Recollim l'adreça de l'usuari 
const usuari=Session.getActiveUser().getEmail();
function doGet(e) {
  //Comprovem si l'usuari és a la llista d'alumnes
  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){ //El mail de l'alumne està a la columna B que és la 1 de la matriu
     identificacio=1;
    }
  }

  //Si l'alumne no està en la llista, s'indica amb un misstage emergent
  if (identificacio==0){
   return HtmlService.createHtmlOutputFromFile('html_no_autoritzat').setTitle("KPSI")
  }else{

  }
}

En cas que l’usuari no sigui a la llista, retornem un html. Aquest html l’hem de crear a l’script.  En el codi que he posat, l’he anomenat html_no_autoritzat.

Tot i que l’aplicació serà pública en el nostre domini (pels qui en coneguin la URL), ja ens hem assegurat que només puguin utilitzar l’aplicació els nostres alumnes.

Ara ens toca omplir l’altra part del if, l’else. Si l’usuari és un dels nostres alumnes, li mostrarem la pàgina html que ha d’omplir. Aquesta pàgina l’anomenarem KPSI.html

 //Si l'alumne no està en la llista, s'indica amb un misstage emergent
if (identificacio==0){
  return HtmlService.createHtmlOutputFromFile('html_no_autoritzat').setTitle("Aplicació KPSI")
}else{
  //Obrim la pantalla de l'aplicació, passant els paràmetres necessaris
  plantilla=HtmlService.createTemplateFromFile('KPSI');
  plantilla.usuari=usuari;
  plantilla.alumnes=alumnes;
  return plantilla.evaluate().setTitle("Aplicació KPSI") 
}

A diferència de quan no era un alumne nostre, que utilitzavem createHtmlOutputFromFile per mostrar la pàgina html, ara utilitzem createTemplateFromFile, que ens permet passar paràmetres. Quan l’usuari no és un alumne, només cal mostrar un missatge. Ara, caldrà mostrar una capçalera amb el mail de l’alumne i, si ja ha contestat el KPSI, caldrà mostrar les seves respostes anteriors. Per això cal passar com a paràmetres, l’usuari i el contingut del Full 1.

Anem ara pel contingut de la pàgina KPSI. Començarem amb dues referències dins del head

<html>
<head>
<title>Apliació 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”…) és per poder utilitzar els estils de bootstrap per donar format a la nostra pàgina. Es podria fer creant nosaltres tot el css, però seria més complicat de fer adaptable a diferents mides de pantalles. Si no coneixeu les classes de bootstrap, en aquest enllaç trobareu la documentació.

La segona (<?!= include(‘css.html’); ?>) ens permet tenir el nostre propi full d’estils. Ens tocarà crear un altre html que es digui css i posar-hi els nostres estils.

<style>
  div.cap
  {
    color:#ffffff;
    background-color: gray;
  }
  div.titol{
    font-weight: bold;
    font-size: 1.4em;
    height: 50px;
  }
</style>
A més, en el fitxer de codi on hi ha la funció doGet, haurem d’afegir una funció nova, include, que és la que permet incrustar els estils que creem en el css.
function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename)
      .getContent();
}

Però no ens distraguem i seguim amb la pàgina KPSI.html. Un cop, indicat en el head les referències, definim una capçalera ja dins el body. Aprofitant els estils de bootstrap, podem crear com un grid de fons i col·locar els camps on vulguem (en files i columnes). A més, hi afegim css personalitzat per donar l’aspecte que volem (com ara div “cap” i div “titol”, que estan definits 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">
      Adreça de l'alumne: <?= usuari ?>
    </div>
   </div>
  <div>
 </body>
</html>

Fixeu-vos que ja fem aparèixer un  dels paràmetres que hem passat, el mail de l’alumne. Simplement indiquem que és codi javascript amb els símbols <?   ?>.

Però provem que el que estem fent funciona. Si hem creat tots els arxius (html_no_autoritzat, KPSI i css), utilitzem el botó Implementar i triem l’opció de Nova implementació.

Triem que el tipus sigui Aplicació web.

Indiquem una descripció (com ara el número de versió), que qui executi l’aplicació siguem nosaltres (que som els únics que tenim accés al full de càlcul) i que hi tingui accés qualsevol usuari del nostre domini (ja que ja controlem per codi que sigui un alumne).

Com tots els scripts, ens apareixerà una pantalla demanant accés i haurem d’atorgar permisos a l’script. Un cop donats, ja ens apareix l’adreça per provar-la. Si l’adreça del nostre usuari és un dels de la llista en el full de càlcul, ens apareixerà la següent pantalla.

El color de fons el podem canviar fàcilment amb el nostre css. Si fem canvis al codi i volem tornar a provar-ho, en lloc d’utilitzar l’opció de nova implementació, és molt millor utilitzar l’opció d’implementacions de proves. Quan ja haguem fet totes les proves, ja tornarem a fer una nova Implementació per obtenir l’adreça definitiva que hauran d’utilitzar els alumnes.

Ara cal seguir treballant amb la pàgina KPSI per tal que hi apareguin les quatre preguntes que els alumnes han de contestar i els desplegables. Dono per suposat que sabeu html i javascript, així que poso el codi i comento les coses més importants.

<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>
  <script>
    function habilitar(){
      //En canviar algun valor, s'habilita el boto desar
        document.KPSI.btn_desar.disabled=false;
    }
    function desar(){
      google.script.run.desar(document.forms["KPSI"]);  //executa la funció desar del codi
      document.KPSI.btn_desar.disabled=true; //Deshabilitem el botó
      window.alert("S'han desat els canvis correctament"); //mostra un missatge indicant que sha desat
    }
  </script>
  <body>
    <div class="container">
      <div class="row cap">
        <div class="col titol">
         Aplicació KPSI
        </div>
        <div class="col-2">
          Adreça de l'alumne: <?= usuari ?>
        </div>
      </div>
    <div>
    <div class="container separador"></div>
    <div class="container">
      <div class="row g-3">
        <div class="col-md-4 titol">
          Alumnes
        </div>
        <div class="col-md-2 titol">
          Inici
        </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++){ //Mirem a quina fila del full de càlcul hi ha les dades de l'alumne que accedeix
            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 ho sé</option>
                  <option value="2" <? if (alumnes[fila][i]==2){ ?>selected<? } ?>>No ho entenc</option>
                  <option value="3" <? if (alumnes[fila][i]==3){ ?>selected<? } ?>>Crec que sí que ho sé</option>
                  <option value="4" <? if (alumnes[fila][i]==4){ ?>selected<? } ?>>Ho podria explicar als meus companys/es sense dificultat</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]==1){ ?>selected<? } ?>>No ho sé</option>
                  <option value="2" <? if (alumnes[fila][i+1]==2){ ?>selected<? } ?>>No ho entenc</option>
                  <option value="3" <? if (alumnes[fila][i+1]==3){ ?>selected<? } ?>>Crec que sí que ho sé</option>
                  <option value="4" <? if (alumnes[fila][i+1]==4){ ?>selected<? } ?>>Ho podria explicar als meus companys/es sense dificultat</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="desar()">Desar</button></div>
    </div>
  </body>
</html>

Bàsicament, hem afegit un formulari i, amb la instrucció for, tantes preguntes com s’han indicat en el full de càlcul. Com en el full de càlcul tenim columnes combinades per poder tenir les respotes de l’inici i del final juntes, el for el fem amb un increment de 2 de i (i=i+2).

Abans, hem mirat a quina fila estaven les dades de l’usuari per poder mostrar les seves respostes anteriors, si n’havia fet. Per no tornar-ho a calcular després, hem afegit un camp ocult en el formulari amb aquest número de fila. Així el podrem passar al codi per poder desar.

A més, hem afegit un parell de funcions en javascript. Una en els select, onchange, per tal que el botó desar només s’activi quan es faci algun canvi.

La segona funció, desar, que s’activa en prémer el boto, onclick, és la que ens retornarà al full de codi, passant com a paràmetres tots els valors que s’ha introduit en els desplegables. la funció és molt senzilla:

google.script.run.desar(document.forms["KPSI"]);  //executa la funció desar del codi
window.alert("S'han desat els canvis correctament"); //mostra un missatge indicant que sha desat

Què ens faltaria? Definir la funció desar en el codi. Aquesta funció és la que desarà en el full de càlcul les dades.

function desar(KPSI){
  //Recuperem els valors del formulari
  var inici=KPSI["inici[]"];
  var final=KPSI["final[]"];
  var fila=parseInt(KPSI["fila"]); //Recuperem i ho considerem un nombre

  //Fem una matriu amb els valors inici i final intercalats, igual que en el full de càlcul
  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++;
  }

  //Desem els valor en el full de càlcul
  var spreadsheet = SpreadsheetApp.openByUrl(full_control);  
  var sheet = spreadsheet.getSheetByName(full);
  sheet.getRange(fila+1,3,1,valors[0].length).setValues(valors);
}

I ja hem arribat al final. Hem creat una petita aplicació web on els alumnes accedeixen, veuen les respostes anteriors i poden modificar-les.

L’aspecte seria aquest.

Per acabar de pulir-la, estaria bé controlar quan els alumnes poden modificar les respostes. No té gaire sentit que al final de la unitat puguin canviar les respostes de l’inici. Podriem afegir en el full de càlcul algun checkboc per dir si està activaad la possibilitat de resposta dels alumnes per la columna inici i un altre per la columna final. Recolliríem aquest valor i en els selects afegiriem un condicional. Si no està permesa, en el codi html s’afegiria disable. Una cosa així:

<select class=”form-select” name=”inici[]” onchange=”habilitar()” <? if (permes_inici==false){ ?>disabled<? } ?>

Però ja us ho deixo per vosaltres. Un cop l’script estigués acabat i provat, hauríeu de tornar-lo a implementar. Compte que si feu implementació nova, la URL canviarà. Si ja l’he enviada als alumnes, serà bastant problemàtic, que els alumnes es faran un embolic. Si voleu fer una nova implementació sense que canviï la URL, enlloc de fer-ne una de nova, trieu Gestionar, editeu la versió i trieu versió nova. D’aquetsa manera, la implementació no canvia i, per tant, la URL tampoc.

Espero que l’article hagi estat prou entendor i que es vegin la manera de crear Web apps amb Google apps script. Deixo pendent una 3a part per explicar com treballar amb diferents pantalles. Com he dit al principi, l’única funció que pot mostrar html és la funció doGet. Per exemple, la funció desar que hem creat, no pot mostrar cap html. Per tant, per fer aplicacions que vulguem que tinguin més d’una pantalla (que en prémer un botó se’nsmostri una pantalla diferent), haurem de fer algun truc. L’explico en el següent article, que serà més curt i tancarà la triologia.

 

 

Español (Spanish) English

Deixa un comentari

L'adreça electrònica no es publicarà. Els camps necessaris estan marcats amb *

Aquest lloc utilitza Akismet per reduir els comentaris brossa. Apreneu com es processen les dades dels comentaris.