Il leggendario CGIDEV2
Transcript
Il leggendario CGIDEV2
Il leggendario CGIDEV2 Terzo articolo di Giovanni Battista Perotti Il sussidiario di CGIDEV2 Per fare in modo che i lettori possano più comodamente riprodurre - ed eventualmente estendere – gli esempi riportati in questi articoli, abbiamo creato un Sussidiario, composto dalla libreria SUSSIDEV2 e dalla cartella IFS /sussidev2 contenenti il materiale esemplificativo utilizzato in questi articoli. Il Sussidiario si scarica dalla pagina http://easy400.coraltree.co.uk/sussidev2-3.zip . Un-zippando il file ricevuto si ottiene un file di salvataggio ed un testo Readme che spiega come installare il tutto e farlo funzionare su AS/400. E’ richiesto almeno il rilascio V5R2. Questo Sussidiario viene aggiornato ad ogni nuovo articolo. Dopo i primi due articoli di questa serie ci aspettiamo che il lettore sappia ormai che cosa è il protocollo CGI, in che cosa consista la nonpersistenza dei programmi CGI e come CGIDEV2 utilizzi l’HTML esterno. Il lettore ormai dovrebbe avere qualche dimestichezza con l’HTML ed il CSS, dovrebbe essere in grado di scrivere con CGIDEV2 un semplice programma RPG-ILE per la immissione di dati e dovrebbe aver letto qualcosa sul linguaggio Javascript. In questo terzo articolo intendiamo fare un piccolo esempio di Javascript, illustrare un programma CGI che gestisce un “subfile” e spiegare un metodo per emettere quelle finestrelle chiamate pop up. Un esempio di Javascript Javascript è un linguaggio fondato su oggetti, che consente di fornire agli script HTML funzioni elaborative locali progettate dallo sviluppatore. Come l’HTML, Javascript è un linguaggio interpretato (non compilato), supportato da tutti i più importanti browser commerciali. Alcuni browser (per esempio MS Internet Explorer) ne supportano particolari estensioni (dialetti) a loro uso e consumo, ma non capite da altri browser. A parte ciò, la base comune, capìta dai vari browser, è molto ampia e consente di sviluppare con sicurezza funzioni gestibili su qualunque piattaforma client. Come qualunque linguaggio di programmazione, l’uso di Javascript non si limita a particolari funzioni. Nella mia personale esperienza, l’uso più frequente consiste nel controllo (validation) del contenuto dei campi di input e nell’apertura di finestre (pop-up) nella pagina WEB. Quando tempo fa iniziai ad utilizzare Javascript, raccolsi alcuni appunti in Figura 1 un manualetto che successivamente pubblicai, è tuttora disponibile alla pagina http://www.easy400.net/js2/start ed è scaricabile. In Internet si possono reperire innumerevoli manuali di Javascript. Io consiglio quello di W3Schools, http://www.w3schools.com/js/default.asp . Nell’esempio pubblicato nel precedente numero (Immissione Nuovi Clienti), il programma CGI controllava i dati di input e, se li trovava errati o mancanti, emetteva in risposta dei messaggi di errore. Il controllo dei dati di input, almeno per quanto riguarda lo loro completezza ed il loro formato, risulta molto più efficace se effettuato direttamente sul client: si evita una transazione verso il server ed il tempo di risposta è immediato. Alcune regole delle funzioni Javascript Apriamo una parentesi per citare alcune regole del codice Javascript, indispensabili per capire il nostro esempio. • Le funzioni Javascript devono essere racchiuse tra le tag <script language=”javascript”> e </script>. Queste tag possono trovarsi dovunque nell’HTML. Tra le due tag si possono definire quante funzioni si desidera. • Il nome di una funzione è libero, ma deve essere sempre seguito dalle parentesi tonde (). Queste parentesi possono racchiudere nomi di variabili che possono essere passate alla funzione; in questo caso le devono essere separate da virgole. Esempio di funzione: puntoNave(longitudine,latitudine). • In Javascript tutto è case sensitive, cioè sensibile al maiuscolo e al minuscolo. • Le istruzioni della funzione devono essere racchiuse tra parentesi graf { } • Ogni istruzione deve terminare con un ; o con una andata a capo. • Una condizione deve essere racchiusa tra parentesi tonde ().Una sequenza di istruzioni condizionate devono essere racchiuse tra parentesi graf { }. Esempio di condizione e di sequenza di istruzioni condizionate: if (x<10) {alert(“x errato”); return} • Il carattere = significa assegnazione di valore; == significa uguale; && significa AND; || significa OR; ! significa NOT. • Per citare il valore di un campo di input di un form: • Per citare la lunghezza di un campo di input di un form: document.nome_form.nome_campo.value document.nome_form.nome_campo.value.length • Il risultato di un confronto può unicamente essere true (vero) o false (falso) • Per sottomettere (inviare al server) un form si usa la funzione primitiva submit() document.nome_form.submit() Alcune considerazioni sul debbugging di Javascript Commettere errori scrivendo funzioni di Javascript è un fatto normalissimo, il linguaggio è estremamente sensibile al minimo errore. Al tempo stesso, non essendo compilato, non offre l’aiuto cui noi siamo abituati dai compilatori. Pertanto, correggere un errore di Javascript può sembrare una missione impossibile. La funzione non funziona, non succede nulla, tu provi e riprovi, le ore passano ma non ne vieni a capo. Ecco qualche consiglio: 1. Realizza in un piccolo HTML un campione della tua funzione Javascript. Ti basta creare sul PC in piccolo file .html, aggiungervi la funzione Javascript e provarla. Le prove “in vitro” sono più semplici. 2. Usa il browser FireFox. Dopo aver eseguito la funzione, usa la tab Strumenti->Console degli errori per vedere le segnalazioni di errore. A questo punto sai perché la funzione ha arrestato la sua esecuzione. 3. Per una ancor più semplice diagnostica, installa il componente aggiuntivo (gratuito) Firebug che ti consente di rilevare automaticamente gli errori. 4. Se hai necessità di sapere il valore assunto da una variabile, aggiungi nel punto opprtuno della funzione il comando alert(nome_della_variabile) Il Javascript utilizzato nell’esempio Nell’esempio che proponiamo (Vedi Figura 1), si utilizza una funzione Javascript per controllare la completezza dei valori di input ed in particolare per controllare il formato di due di essi, il CAP e l’indirizzo e-mail. Quando si chiede di inviare il form premendo il bottone di invio, la funzione Javascript effettua i controlli: se non vengono trovate eccezioni il form viene inoltrato al server; in caso contrario, viene emesso un messaggio di avviso (alert) ed il form non viene inviato. Per effettuare questi controlli abbiamo creato un nuovo stream file HTML /sussidev2/html/pgm1A.htm inizialmente identico a quello pubblicato nell’articolo precedente, apportandovi poi due modifiche: 1. Tra le tag <head> e </head> abbiamo inserito il Javascript in Figura 2, il quale contiene una funzione denominata “vai()” 2. Alla fine del form abbiamo sostituito l’input di tipo submit con un input di tipo button, il quale – se premuto (onClick) – richiama la funzione javascript “vai()” (vedi Figura 3). In sostanza il form non viene più inviato quando si preme il bottone Invio, in quanto esso non è più di tipo submit. La sottomissione del form è ora delegata alla funzione “vai()”. La funzione vai() <script language="javascript"> function vai() { reCAP=/\d{5}/ reEmail=/.+@.+\..+/ if (document.nuovocli.xragsoc.value.length<1) {alert("Manca la Ragione sociale"); document.nuovocli.xragsoc.focus(); return;} if (document.nuovocli.xindir.value.length<3) {alert("Indirizzo non valido"); document.nuovocli.xindir.focus(); return;} if (reCAP.test(document.nuovocli.xcap.value)==false) {alert("CAP non valido"); document.nuovocli.xcap.focus(); return;} if (document.nuovocli.xlocal.value.length<1) {alert("Manca la Località"); document.nuovocli.xlocal.focus(); return;} if (document.nuovocli.xprov.value.length<2) {alert("Provincia non valida"); document.nuovocli.xprov.focus(); return;} if (document.nuovocli.xcontat.value.length<1) {alert("Manca il Nome per contatto"); document.nuovocli.xcontat.focus(); return;} if (reEmail.test(document.nuovocli.xemail.value)==false) {alert("E-mail non valido"); document.nuovocli.xemail.select(); return;} document.nuovocli.submit(); } </script> Figura 2 <input type=button class="bott" value="invio" onClick=vai()> Figura 3 Dopo aver definito due particolari costanti (reCAP e reMail), viene controllata la lunghezza di ciascun campo di input. Se la lunghezza è inferiore ad un dato valore minimo, viene emesso un messaggio di errore (funzione primitiva alert() ), il cursore viene spostato sul campo in causa (funzione primitiva focus() ) e viene effettuata l’uscita dalla funzione (return). Nel caso che l’errore riguardi l’indirizzo e-mail, anziché focus() si è usata la funzione select() che, oltre a posizionare il cursore, scurisce lo sfondo del campo e lo prepara per la cancellazione. Le due costanti definite all’inizio della funzione sono chiamate Regular Expression. Si tratta di maschere che definiscono il formato di una variabile. Per verificare se una variabile è conforme al formato di una regular expression, si usa la funzione primitiva test() il cui risultato è true o false. regular_expression.test(valore_della_variabile) • Regular expression reCAP=/\d{5}/ Il carattere / iniziale ed il carattere / finale delimitano la regular expression \d{5} indica che sono attesi cinque caratteri numerici • Regular expression reEmail=/.+@.+\..+/ Il carattere / iniziale ed il carattere / finale delimitano la regular expression .+ indica che sono attesi uno o più caratteri di qualunque tipo @ indica che è atteso il carattere @ \. indica che è atteso il carattere punto decimale Il programma RPG Abbiamo pensato di riutilizzare il programma RPG-ILE sussidev2/pgm1 (membro PGM1 di SUSSIDEV2/QRPGLESRC) presentato nell’articolo precedente per utilizzare il nuovo stream file HTML /sussidev2/html/pgm1A.htm contenente il Javascript sopra discusso. Per poter fare in modo che questo programma possa utilizzare a richiesta o il precedente HTML /sussidev2/html/pgm1.htm o quello nuovo /sussidev2/html/pgm1A.htm : 1. Nel form di /sussidev2/html/pgm1A.htm abbiamo aggiunto un campo di input nascosto, di nome “xhtml” <form name="nuovocli" method="post" action="/sussidev2p/pgm1.pgm"> con l’intesa che, se il programma riceve “a” nella variabile di input , dovrà caricare l’HTML esterno /sussidev2/html/pgm1A.htm anziché l’HTML esterno <input type="hidden" name="xhtml" value="a"> /sussidev2/html/pgm1.htm . Viene da sé che in partenza, si dovrà utilizzare la <input type="hidden" name="xrequest" value="immissione"> URL http://.../sussidev2p/pgm1.pgm per fargli caricare l’HTML esterno <table> /sussidev2/html/pgm1.htm , mentre si dovrà utilizzare la URL … … … http://.../sussidev2p/pgm1.pgm?xhtml=a per fargli caricare l’HTML esterno </table> /sussidev2/html/pgm1A.htm . </form> 2. Nel sorgente del programma RPG-ILE sussidev2/pgm1 (membro PGM1 di SUSSIDEV2/QRPGLESRC) abbiamo dovuto a. Posporre il caricamento dell’HTML esterno // Preliminari (LoadHTML) alla ricezione delle variabili di exsr RcvInput; //ricevi le variabili di input input (RcvInput) exsr LoadHTML; //carica l'HTML esterno exsr OpnF; //apri i file b. Ricevere la variabile di input xhtml e far dipendere dal suo valore il caricamento dell’HTML esterno: D xhtml s 1 xhtml=zhbGetVarUpper('xhtml'); if xhtml<>'A'; extHTML='/sussidev2/html/pgm1.htm'; else; extHTML='/sussidev2/html/pgm1A.htm'; endif; IfsMultIndicators=getHtmlIfsMult(%trim(exthtml):'<as400>'); ESEMPIO CGI no. 2 Questo secondo esempio di programma CGI illustra come presentare un “subfile”, in pratica una tabella di dati derivanti dai record di un file, nel nostro caso la Anagrafica Clienti già utilizzata nell’esempio no.1. SCHEMA BASE Intendiamo partire con un programma base, al quale aggiungeremo via via vari dispositivi. Il programma base intende produrre una lista brutale dei record del file, Il risultato voluto è in Figura 4. Figura 4 Si notino i radio-button alla sinistra di ogni riga. In questo caso i radio- button servono per trasmettere al programma il codice cliente. Un altro dispositivo è l’associazione del nome di contatto al suo indirizzo e-mail: vogliamo che, premendo sul nome, il mailer del nostro client apra un nuovo messaggio indirizzato a quella persona. Come al solito, la prima attività consiste nello sviluppare la presentazione, cioè l’HTML esterno, che in questo caso è lo stream file /sussidev2/html/pgm2.htm, illustrato nella figura 5. La sezione “inizio“ non presenta novità, rispetto a quanto già fatto negli esempi precedenti. Abbiamo semplicemente predisposto un’area <script language=”javascript”></script> che per ora è vuota, la utilizzeremo successivamente. La sezione “fine” di coda è ancor più ovvia. L’interesse è tutto per le quattro sezioni con le quali viene gestita la tabella (la tabella è il corrispondente HTML del “subfile” dei file display). 1. Esiste una sezione di inizio tabella, di nome ListStr, la quale a. Dichiara un form di nome clilist con un campo di input nascosto di (hidden) di nome xrequest . Al momento il form non ci interessa, servirà piu avanti, quando da questa pagina si potranno formulare richieste al server. b. Da inizio ad una tabella tramite la tag <table> 2. La tecnica che si utilizza nel CGI per riempire la tabella è quella di emettere , in questo caso, per ogni record di anagrafica letto, una riga di tabella (<tr><td>…</td>…<td></td></tr>) tramite una sezione HTML che qui si chiama Listrow. Chi ha vissuto l’epica del Sistema/36 si ricorderà che questa tecnica si chiamava “riga variabile”. Tra le varie celle (<td>…</td>) segnaliamo: a. La prima cella, un radio-button al quale sarà assegnato, come valore da trasmettere in input qualora fosse selezionato, il codice cliente b. L’ultima cella, in cui il nome della persona di contatto viene circondato da un hyperlink che realizza appunto l’aggancio desiderato al mailer del client per aprire una nuovo messaggio da inviare. 3. Dopo l’ultima riga di tabella, viene scritta la sezione ListButtons che contiene i bottoni “Nuovo”, “Varia” e “Cancella”. Si noti che tutti questi bottoni gestiscono l’evento onClick che serve per richiamare una funzione Javascript quando il bottone viene premuto. In questo momento però le funzioni Javascript Nuovo(), Change() e Delete() non esistono ancora, quindi i bottoni non sono ancora operativi. 4. Se invece la anagrafica clienti non contiene alcun record, il CGI non potrà scrivere alcuna riga di tabella del tipo sopra esposto. Dovrà allora emettere una sola riga di tabella che spieghi che il file è vuoto. Questo è appunto lo scopo della sezione HTML di nome ListNone. 5. Una tabella HTML va sempre chiusa. A questo scopo è predisposta la sezione HTML nome ListEnd, la quale ne approfitta per chiudere anche il form. <as400>inizio Content-type: text/html *** SEZIONE INIZIO HTML <html> <head> <title>Gestione clienti</title> <link rel="SHORTCUT ICON" href="/sussidev2/graphics/favicon.ico"> <link rel="stylesheet" type="text/css" href="/sussidev2/css/css.css"> <script language="javascript"> </script> </head> <body> <table> <tr><td><img src="/sussidev2/graphics/redBaron.jpg"></td> <td width="20"> </td> <td class="title">Gestione Clienti</td></tr> </table> <as400>ListStr *** SEZIONE INIZIO TABELLA <form name="clilist" method="post" action="/sussidev2p/pgm2.pgm"> <input type="hidden" name="xrequest"> <table> <as400>ListRow *** SEZIONE RIGA VARIABILE <tr><td><input type="radio" name="xclicod" value="/%clicod%/"></td> <td>/%cliragsoc%/</td> <td>/%cliindir%/</td> <td>/%clicap%/</td> <td>/%clilocal%/</td> <td>/%cliprov%/</td> <td><a href="mailto: /%cliemail%/">/%clicontat%/</a></td></tr> <as400>ListButtons *** SEZIONE BOTTONI DI FINE TABELLA <tr><td align="center" colspan=”7”> <table> <tr><td><input type="button" class="bott" value="Nuovo" onClick=Nuovo()></td> <td><input type="button" class="bott" value="Varia" onClick=Change("/%totrows%/")></td> <td><input type="button" class="bott" value="Cancella" onClick=Delete("/%totrows%/")></td> </td></tr> </table> </td></tr> <as400>ListNone *** SEZIONE TABELLA VUOTA <tr><td align=center class="errmsg"> Non ci sono clienti</td></tr> <as400>ListEnd *** SEZIONE FINE TABELLA </table> <input type="hidden" name="xposRagSoc"> </form> <as400>fine *** SEZIONE FINE HTML </body> </html> Figura 5 Veniamo ora al programma CGI, che si chiama SUSSIDEV2/PGM2 e si invoca con la URL http://.../sussidev2p/pgm2.pgm . Il suo sorgente è il membro PGM2 nel file SUSSIDEV2/QRPGLESRC ed è esposto in Figura 6. Annotazioni sullo schema base del programma Lo schema base del programma ha grosso modo la stessa struttura del programma PGM1 discusso nel secondo articolo di questa serie, quindi non spendiamo altro tempo per commentarlo, se non per alcuni particolari. • Dei due file specificati, al momento viene utilizzato solo il file ANACLI01, che è organizzato per “ragione sociale”. Questo file inoltre dichiara una Information Data Structure INFDS01 la quale consente di sapere, tramite il sottocampo ONbrRecs, quanti record esistono nel file. • Nella subroutine di ricezione delle variabili di input RcvInput viene ricevuta la variabile xrequest, la quale pero nel form HTML risulta non contenere nulla. Si ricorda che con questa variabile di input noi intendiamo che venga trasmesso al CGI il compito fondamentale da espletare. In assenza di tale informazione, il CGI assume che il compito sia quello di elencare (subroutine List) i record dell’anagrafica. In effetti nel minuscolo albero decisionale select/endsl avviene proprio questo. • La subroutine List ha un particolare problema nell’editare in output HTML il campo clicod del record anagrafica. Il campo è numerico, ma il dato deve essere trasmesso come carattere tramite la procedura updhtmlvar. Nella conversione da numerico a carattere tramite la BIF %char vanno persi gli zeri non significativi, che noi vogliamo invece mantenere. Ecco il perché delle istruzioni marcate in color verde. *============================================================= * Gestione clienti * * CRTBNDRPG PGM(SUSSIDEV2/PGM2) * SRCFILE(SUSSIDEV2/QRPGLESRC) DFTACTGRP(*NO) * ACTGRP(PGM2) DBGVIEW(*SOURCE) * *============================================================= /copy SUSSIDEV2/qrpglesrc,hspecs /copy SUSSIDEV2/qrpglesrc,hspecsbnd FANACLI uf e k disk usropn FANACLI01 if e k disk usropn rename(clircd:clircd01) F infds(InfDS01) /copy SUSSIDEV2/qrpglesrc,prototypeb /copy SUSSIDEV2/qrpglesrc,usec * Information Data Structure per file ANACLI01 D InfDS01 ds D FileFbk 1 80 D OpenFbk 81 240 D OFileName 83 92 D OFileLib 93 102 D OFileMbr 129 138 D ONbrRecs 156 159b 0 D OAccType 160 161 D ODupKeyInd 162 162 D OSrcFilInd 163 163 * Numero delle variabili di input D nbrVars s 10i 0 * Saved query string Dsavedquerystring... D s 32767 varying * D extHtml s 2000 * Indicatori per la procedura GetHtmlIfsMult * (caricamento HTML esterno) D IfsMultIndicators... D ds D NoErrors n D NameTooLong n D NotAccessible n D NoFilesUsable n D DupSections n D FileIsEmpty n * Variabili di input D xrequest s 10 D xclicodC s 9 D xposRagSoc s like(cliRagSoc) D xpagesizeC s 3 D xpagesize s 3p 0 * Altre variabili D rc s 10i 0 D clicodC s 9 D zero9 s 9 inz('000000000') D i s 10i 0 D l s 10i 0 D totrows s 10i 0 Figura 6A *==================================================== * Logica principale *==================================================== /free // Preliminari exsr RcvInput; exsr LoadHTML; exsr OpnF; //ricevi le variabili di input //carica l'HTML esterno //apri i file // Incomincia a scrivere l'inizio dell'HTML di risposta wrtsection('inizio'); // Analizza il tipo di richiesta e prendi il percorso opportuno select; when xrequest='TOPPAGE'; exsr TopPage; exsr List; endsl; // Scrivi la fine dell'HTML di risposta wrtsection('fine'); // Lancia il buffer della risposta HTML e termina il programma exsr Exit; /end-free *================================================= * Ricevi le variabili di input *================================================= /free Begsr RcvInput; //acquisisci il buffer di input nbrVars=zhbgetinput(savedquerystring:qusec); //acquisisci le variabili di input xrequest=zhbGetVarUpper('xrequest'); if xrequest=' '; xrequest='TOPPAGE'; endif; Endsr; /end-free *=============================================== * Carica l'HTML esterno *=============================================== /free Begsr LoadHTML; extHTML='/sussidev2/html/pgm2.htm'; IfsMultIndicators=getHtmlIfsMult(%trim(exthtml):'<as400>'); //Se il caricamento dell'HTML esterno fallisce, ritorna if NoErrors=*off; return; endif; Endsr; /end-free *============================================== * Inizia dalla prima pagina (TOPPAGE) *============================================== /free Begsr TopPage; // Posizionati sul primo record xposRagSoc=*loval; setll xposRagSoc clircd01; Endsr; /end-free Figura 6B *============================================== * Fornisci la tabella clienti *============================================== /free Begsr List; // Se non ci sono clienti, emetti messaggio ed esci if ONbrRecs=0; wrtsection('ListStr ListNone ListEnd'); leavesr; endif; // Scrivi la sezione inizio tabella clienti wrtsection('ListStr'); // Scrivi una riga variabile della tabella clienti // per ogni record letto read clircd01; dow not %eof; clicodC=%char(clicod); l=%len(%trim(clicodC)); if l<%len(clicodC); clicodC=%subst(zero9:1:%len(clicodC)-l) + %trim(clicodC); endif; updhtmlvar('clicod' :clicodC ); updhtmlvar('cliragsoc':cliragsoc); updhtmlvar('cliindir' :cliindir ); updhtmlvar('clicap' :clicap ); updhtmlvar('clilocal' :clilocal ); updhtmlvar('cliprov' :cliprov ); updhtmlvar('clicontat':clicontat); updhtmlvar('cliemail' :cliemail ); wrtsection('ListRow'); totrows=totrows+1; //numero di righe scritte read clircd01; enddo; // Scrivi la sezione Bottoni di fine tabella updhtmlvar('totrows':%editc(totrows:'Z')); wrtsection('ListButtons'); // Scrivi la sezione Fine tabella wrtsection('ListEnd'); *============================================= * Apertura file *============================================= /free Begsr OpnF; if not %open(ANACLI); rc=docmd('ovrdbf ANACLI SUSSIDEV2/ANACLI secure(*yes)'); open ANACLI; endif; if not %open(ANACLI01); rc=docmd('ovrdbf ANACLI01 SUSSIDEV2/ANACLI01 secure(*yes)'); open ANACLI01; endif; Endsr; /end-free *============================================= * Chiusura file *============================================= /free Begsr CloF; if %open(ANACLI); close ANACLI; rc=docmd('dltovr ANACLI'); endif; if %open(ANACLI01); close ANACLI01; rc=docmd('dltovr ANACLI01'); endif; Endsr; /end-free *============================================ * Fine programma *============================================ /free Begsr Exit; exsr CLOF; //chiudi i file wrtsection('*fini'); //spedisci il buffer di output return; //ritorna con *LR in *off Endsr; /end-free Endsr; /end-free Figura 6C Figura 6D PRIMA VARIAZIONE – Il bottone ‘Cancella’ Come prima variazione allo schema base vogliamo attivare il bottone “Cancella”. Per farlo, dovremo dotare l’HTML della funzione Javascript Delete() richiamata nell’onClick del bottone e fare in modo che il programma CGI PGM2 onori la relativa richiesta. Funzioni Javascript Si noti innanzi tutto (Vedi Figura 5) che il bottone Cancella richiama la funzione Delete() passandole un parametro /%totrows% onClick=Delete("/%totrows%/") Questo parametro, che viene riempito dal programma RPG PGM2, è il numero di righe nella tabella. Le funzioni Javascript vengono posizionate tra le tag <script language=”javascript”> e </script> nella sezione di inizio dell’HTML (Vedi Figura 7). Si noti innanzi tutto la presenza di una variabile globale di nome “xindex”. La variabili globali sono variabili accessibili a tutte le funzioni comprese tra le tag <script language=”javascript”> e </script> . Le nuove funzioni sono due: <script language="javascript"> var xindex; function anyradio(n) { if (n>1) { for (i=0; i<n; i++) { if (document.clilist.xclicod[i].checked==true) {xindex=i; return true}; } alert("devi scegliere una voce!"); return false; } else document.clilist.xclicod.checked=true; return true; } Figura 7 function Delete(n) { var ragsoc; if (anyradio(n)==false) return; if (n==1) {ragsoc=document.clilist.xragsoc.value}; if (n>1) {ragsoc=document.clilist.xragsoc[xindex].value}; if (confirm("Confermi la cancellazione del cliente " + ragsoc + " ?")==true) { document.clilist.xrequest.value="delete"; document.clilist.submit();} } </script> 1. anyradio(n) – Questa funzione riceve come parametro (n) il numero di righe della tabella. Il suo scopo è quello di stabilire se sia stato selezionato un radio- button. Quando più variabili di input hanno lo stesso nome (in questo caso i campi di input della riga variabile che hanno nome “xclicod”(i radio-button) e “xragsoc” (campi hidden)), esse vengono a costituire una schiera, in cui il primo elemento ha indice 0, il secondo elemento ha indice 1 e così via. Se il numero di righe n è superiore ad 1, la funzione esegue un loop (di tipo for) per trovare quale sia il radio-button selezionato: se lo trova, salva il suo indice nella variabile globale “xindex” e restituisce true alla funzione chiamante; se non lo trova, emettere un alert e restituisce false alla funzione chiamante. Se il numero di righe n è uguale ad 1, le variabili “xclicod” e “xragsoc” non sono schiere e questa funzione si limita a forzare come selezionato l’unico bottone ed a restituire true alla funzione chiamante. 2. Delete(n) – Questa funzione riceve come parametro (n) il numero di righe della tabella. Dopo aver definito una variabile di nome “ragsoc”, questa funzione invoca la funzione anyradio(n). Se da essa riceve false (ci sono più righe di tabella, ma nessun radio-button è stato scelto), termina senza fare nulla. Altrimenti, avendo ricevuto true (ci sono più righe di tabella ed è stato scelto un bottone, oppure c’è una sola riga in tabella), carica nella variabile “ragsoc” il valore della ragione sociale del cliente (prelevandolo dalla schiera della variabile “xragsoc” in caso di più righe, oppure dall’unica variabile “xragsoc” in caso di una sola riga) e utilizza la variabile “ragsoc” per chiedere all’utente conferma della cancellazione. Se l’utente conferma, allora la funzione forza il valore “delete” nella variabile di input “xrequest” e sottomette il form “clilist”. Variazioni al programma PGM2 Il programma nella variabile di input “xrequest” troverà il valore “delete” ed eseguirà la routine appropriata per cancellare dal file di anagrafica ANACLI (organizzato per codice cliente) il record con il codice cliente passato nella variabile di input “xclicod” (il radio-button selezionato dall’utente). Successivamente il programma provvederà a fornire una nuova lista dei clienti. Ci limitiamo a mostrare in Figura 8 le modifiche apportate al programma. * Variabili di input D xrequest s D xclicodC s D xposRagSoc s 10 9 like(cliRagSoc) xclicodC =zhbGetVar('xclicod'); // Analizza il tipo di richiesta e prendi il percorso opportuno select; when xrequest='TOPPAGE'; exsr List; when xrequest='DELETE'; exsr Delete; exsr List; endsl; *======================================================== * Cancella il cliente di codice "xclicodC" *======================================================== /free Begsr Delete; clicod=%dec(xclicodC:9:0); //convert to packed (9 0) chain clicod clircd; if %found; delete clircd; endif; Endsr; /end-free Figura 8 SECONDA VARIAZIONE – Il bottone ‘Nuovo’ Come seconda variazione allo schema base vogliamo attivare il bottone “Nuovo” (Immissione nuovo cliente). Esistono vari modi per fare ciò. Per esempio, assegnare un nuovo valore alla variabile di input “xrequest” e spedire il form al programma PGM2 perché emetta una pagina per la immissione di un nuovo cliente. Ma noi abbiamo già questo programma, è il PGM1. Potremmo quindi aggiungere qualcosa nell’ HTML /sussidev2/html/pgm2.htm per richiamare il PGM1. Già, ma quando avessimo il PGM1 (Nuovo Cliente) sullo schermo (Figura 1), come faremmo per ritornare al PGM2 (Gestione Figura 9 Clienti, Figura 4)? Tornare indietro nella storia delle pagine del browser? Proprio no, troppo pericoloso, così si commettono errori. La soluzione migliore è quella di chiamare il PGM1 aprendolo in una finestrella apposita, un cosiddetto “pop up” (Vedi Figura 9). Una volta ricevuta la richiesta di immissione nuovo cliente, il PGM1 emette, nella pagina del pop up, un HTML che segnala alla schermata Gestione Clienti di rivisualizzare i dati e quindi ordina al pop up di chiudersi. Tutto questo con un poco di Javascript. Javascript per aprire la pagina Nuovo Cliente in un pop up Nella Figura 10 presentiamo il Javascript da inserire in /sussidev2/html/pgm2.htm per far sì che ciò avvenga. <script src="/sussidev2/js/openPopup.txt"></script> <script language=”Javascript”> function Nuovo() { myurl="/sussidev2p/pgm1.pgm?xhtml=a&xclosepopup=yes" openPop(myurl,"500") } </script> • La funzione Nuovo() (richiamata dal bottone “Nuovo”), chiama la funzione openPop() passandole due parametri: l’URL da richiamare e la dimensione orizzontale (500 pixel) della finestrella di pop up. L’URL contiene il valore "/sussidev2p/pgm1.pgm?xhtml=a&xclosepopup=yes", dove xhtml=a serve a PGM1 per caricare l’html esterno /sussidev2/html/pgm1a.htm xclosepopup=yes fa sapere a PGM1 che dopo aver registrato il cliente deve chiudere il pop up. Figura 10 • La funzione openPop() è abbastanza complessa, fondata sulla funzione primitiva window.open(). Data la sua utilità generale, la abbiamo definita esternamente nello stream file /sussidev2/js/openPopup.txt . Per includerla dinamicamente nell’HTML esterno di /sussidev2/html/pgm2.htm è sufficiente aggiungervi questa dichiarativa: <script src="/sussidev2/js/openPopup.txt"></script> Aggiunte all’HTML esterno del programma PGM1 (Nuovo Cliente) All’HTML esterno del programma PGM1, /sussidev/html/pgm1A.htm, vanno apportate le modifiche seguenti: a. Nel form “nuovocli” va aggiunto la variabile di input nascosta “xclosepopup” in modo che il programma PGM1, quando emette la schermata di immissione nuovo cliente, si ritrovi in input lo stesso valore di “xclosepopup” che aveva ricevuto alla sua chiamata iniziale: <form name="nuovocli" method="post" action="/sussidev2p/pgm1.pgm"> <input type="hidden" name="xhtml" value="a"> <input type="hidden" name="xrequest" value="immissione"> <input type="hidden" name="xclosepopup" value="/%xclosepopup%/"> <table> Figura 11 b. Va aggiunta una sezione di HTML e Javascript che PGM1 deve far pervenire al browser successivamente alla immissione del nuovo cliente perché faccia ri-visualizzare l’elenco clienti e chiuda il pop up: <as400>popupClose Content-type: text/html *** SEZIONE CHIUSURA POP UP <html><head><script language="javascript"> function epilog() { window.opener.document.clilist.xrequest.value=""; window.opener.document.clilist.xposRagSoc="/%xposragsoc%/"; window.opener.document.clilist.submit(); window.close();} </script><body onLoad=epilog()></body></html> Figura 12 L’evento onLoad, che avviene non appena il browser ha ricevuto e interpretato lo script, fa partire la funzione epilog(). Questa imposta i campi di input nascosti del form “clilist” dell’ HTML (Figura 5) che ha aperto (window.opener) il popup, quindi sottomette il form “clilist” in modo tale da far ri-visualizzare l’elenco clienti. Infine esegue la funzione primitiva window.close(), che chiude il pop up. Aggiunte alla logica del programma PGM1 (Nuovo Cliente) Al programma PGM1 vanno apportate delle modifiche per regolare il suo comportamento quando lavora in pop up (xclosepopup=yes). 1. Va definita la variabile di programma in cui va ricevuta la variabile di input “xclosepopup”. Una volta ricevuta, questa variabile va replicata nell’html di output, così da essere ricevuta nuovamente nella fase in cui avviene la registrazione del cliente (xrequest=’IMMISSIONE’) 2. Nella fase di registrazione del cliente (xrequest=’IMMISSIONE’), se la richiesta arriva dal pop up (xclosepopup=’YES’), va emessa una unica sezione HTML dello stream file /sussidev/html/pgm1A.htm, quella denominata “popupClose” (vedi Figura 11). TERZA VARIAZIONE – Il bottone ‘Varia’ Come terza variazione allo schema base vogliamo attivare il bottone “Varia” (Aggiornamento cliente). La tecnica è la stessa utilizzata nella seconda variazione: aprire un pop up che richiami un programma CGI per la variazione. Il programma CGI, una volta effettuata la variazione, farà rivisualizzare l’elenco clienti e chiuderà il pop up. In Figura 13 la funzione Javascript Change() che sarà eseguita quando si preme il bottone “Varia”. • La funzione Change() si comporta come la funzione Delete() in Figura 7. Nella variabile “zclicod” carica il codice cliente selezionato, che poi utilizza come parametro nella URL. • Si noti come nella URL venga invocato il programma PGM1B. Per il sorgente di questo programma e per il suo HTML esterno rimandiamo al Sussidiario (libreria SUSSIDEV2), in quanto in questo caso le tecniche utilizzate non costituiscono una novità. QUARTA VARIAZIONE – Lo ‘scroll’ della tabella function Change(n) { var zclicod; if (anyradio(n)==false) return; if (n==1) {zclicod=document.clilist.xclicod.value}; if (n>1) {zclicod=document.clilist.xclicod[xindex].value}; myurl="/sussidev2p/pgm1b.pgm?xclicod=" + zclicod; openPop(myurl,"500"); } Figura 13 Così come stanno le cose a questo punto, il PGM2 fornisce una tabella contenente tutti i nominativi clienti. A lungo andare, via via che il numero dei clienti cresce, questo metodo non va bene, sia perché la lunghezza della pagina in arrivo comporta tempi di risposta rilevanti, sia perché diventa problematico gestire un elenco di qualche centinaio di nominativi. Bisogna quindi fornire strumenti per • avere pagine di dimensione contenuta • poter avanzare o retrocedere con le pagine • posizionarsi sulla pagina contenente un dato nominativo Chi ha già affrontato la gestione a programma della paginazione di un subfile 5250, ha una idea di come il problema si risolva. Con L’HTML ed il CGI la soluzione è abbastanza simile. Il risultato che vogliamo ottenere con il nostro programma CGI è illustrato in Figura 14. • Per il posizionamento si utilizza questo campo di immissione: Una volta immesso il testo occorre premere la freccetta celeste. • Per muoversi tra le pagine, si utilizza questo dispositivo: prima pagina pagina successiva ultima pagina pagina precedente home Sussidiario Figura 14 • Questo muovendo il serve per variare il numero di righe di una pagina. Dopo aver immesso il numero, occorre uscire dal campo di immissione, cursore con un mouse o con il tasto di Tab. La realizzazione di questo supporto per la paginazione ha richiesto una aggiunta di Javascript allo stream file /sussidev2/html/pgm2.htm e la aggiunta di qualche subroutine al programma CGI SUSSIDEV2/PGM2. HTML e Javascript a supporto della paginazione <script language=”Javascript”> function goHome() {window.location="/sussidev2/html/indice.html"; return false; } function StartFrom() { document.clilist.xposRagSoc.value=document.clilist.posTo.value.toUpperCase(); SamePage();} function SamePage() { document.clilist.xrequest.value="samepage"; document.clilist.submit();} function TopPage() { document.clilist.xrequest.value="toppage"; document.clilist.submit();} function RollDown() { document.clilist.xrequest.value="rolldown"; document.clilist.submit();} function RollUp() { document.clilist.xrequest.value="rollup"; document.clilist.submit();} function LastPage() { document.clilist.xrequest.value="lastpage"; document.clilist.submit();} </script> </head> • Dimensione della pagina – Quando si esce dal campo di input che specifica il numero di righe per pagina e si è variato tale numero, viene eseguita la funzione SamePage(). La funzione SamePage() viene utilizzata per chiedere a PGM2 di emettere un elenco di una pagina di clienti a partire dalla ragione sociale specificata nella variabile di input nascosta xposRagSoc. Questa variabile viene inizializzata da PGM2 quando scrive la prima riga della pagina. • Posizionamento – La freccetta celeste alla destra del campo di input per il posizionamento punta alla funzione StartFrom(). Questa funzione converte in maiuscolo quanto immesso nel campo di posizionamento e assegna il valore risultante ad una variabile di input nascosta, di nome xposRagSoc. Questa variabile viene utilizzata da PGM2 per sapere quale era il record cliente con cui iniziava la pagina. Subito dopo, la funzione StartFrom() invoca la funzione SamePage(). • Pagina avanti, pagina indietro, prima pagina, ultima pagina – Le quattro freccette incaricate di ciò chiamano rispettivamente le funzioni RollUp(), RollDown(), TopPage() e LastPage(). Queste funzioni assegnano un valore appropriato alla variabile di input nascosta xrequest e sottomettono il form clilist. • Home page del Sussidiario – Quando si preme la casetta al centro delle quattro freccette, viene eseguita la funzione goHome(). Figura 15- HTML e Javascript a supporto della paginazione <body> <form name="clilist" method="post" action="/sussidev2p/pgm2.pgm"> <table> <tr><td><img src="/sussidev2/graphics/redBaron.jpg"></td> <td width="20"> </td> <td><table> <tr><td colspan=2 align="center" class="title"> Gestione Clienti</td></tr> <tr><td><input type=text name="posTo" size="30" maxlength="50></td> <td align=center style="cursor:hand"> <img src="/sussidev2/graphics/arrow_back.png" alt="Parti da" title="Parti da" onclick="return StartFrom()"></td></tr> </table> <td width="20"> </td> <td><FIELDSET> <legend class=lgd1>Paginazione</legend> <table cellspacing=0 cellpadding=0 border=0> <tr><td><img src="/sussidev2/graphics/c.gif" width="22" height="22"></td> <td align=center style="cursor:hand"> <img src="/sussidev2/graphics/arrow_top.png" alt="Prima pagina" title="Prima pagina" onclick="return TopPage()"></td> <td></td> <td rowspan=3> <table> <tr><td align=center>righe/pagina:<br> <input type="text" name="xpagesize" size="3" maxlength="3" value="/%pagesize%/" onChange=SamePage()> </td></tr> </table> </td></tr> <tr><td align=center style="cursor:hand"> <img src="/sussidev2/graphics/arrow_back.png" alt="Pagina precedente" title="Pagina precedente" onclick="return RollDown()"></td> <td align=center> <input type=image src="/sussidev2/graphics/home.jpg" alt="Home" title="Home" onClick="return goHome()"></td> <td align=center style="cursor:hand"> <img src="/sussidev2/graphics/arrow_next.png" alt="Pagina successiva" title="Pagina successiva" onclick="return RollUp()"></td></tr> <tr><td><img src="/sussidev2/graphics/c.gif" width="22" height="22"></td> <td align=center style="cursor:hand"> <img src="/sussidev2/graphics/arrow_down.png" alt="Ultima pagina" title="Ultima pagina" onclick="return LastPage()"></td></tr> </table> </FIELDSET> </form> </td> </tr> </table> Subroutine di PGM2 a supporto della paginazione - Nella Figura 16 riportiamo unicamente le zone variate. *========================================= * Logica principale *========================================= /free // Preliminari exsr RcvInput; //ricevi le variabili di input exsr LoadHTML; //carica l'HTML esterno exsr OpnF; //apri i file // Incomincia a scrivere l'inizio dell'HTML di risposta wrtsection('inizio'); // Analizza il tipo di richiesta e prendi il percorso // opportuno select; when xrequest='TOPPAGE'; exsr TopPage; exsr List; when xrequest='SAMEPAGE'; exsr SamePage; exsr List; when xrequest='ROLLDOWN'; exsr RollDown; exsr List; when xrequest='ROLLUP'; exsr RollUp; exsr List; when xrequest='LASTPAGE'; exsr LastPage; exsr List; when xrequest='DELETE'; exsr Delete; exsr List; endsl; // Scrivi la fine dell'HTML di risposta wrtsection('fine'); // Lancia il buffer della risposta HTML e // termina il programma exsr Exit; /end-free *=========================================== * Ricevi le variabili di input *=========================================== /free Begsr RcvInput; //acquisisci il buffer di input nbrVars=zhbgetinput(savedquerystring:qusec); xrequest =zhbGetVarUpper('xrequest'); if xrequest=' '; xrequest='TOPPAGE'; endif; xclicodC =zhbGetVar('xclicod'); xposRagSoc=zhbGetVar('xposRagSoc'); //ricevi e replica il numero righe per pagina xpagesizeC=zhbGetVar('xpagesize'); if xpagesizeC=' '; xpagesizeC='010'; endif; l=%len(%trim(xpagesizeC)); if l<%size(xpagesizeC); xpagesizeC=%subst(zero9:1: %size(xpagesizeC)-l)+ %trim(xpagesizeC); endif; xpagesize=%dec(xpagesizeC:3:0); //cvt packed (3 0) updhtmlvar('pagesize':%editc(xpagesize:'Z')); Endsr; /end-free Figura 16 *========================================== * Stessa pagina (SAMEPAGE) *========================================== /free Begsr SamePage; // Posizionati sul record della prima riga della pagina setll xposRagSoc clircd01; Endsr; /end-free *========================================== * Pagina indietro (ROLLDOWN) *========================================== /free Begsr RollDown; // Posizionati sulla ragione sociale da cui partire setll xposRagsoc clircd01; // Arretra di una pagina i=0; dow i<=xpagesize; readp clircd01; if %eof; cliragsoc=*loval; setll cliragsoc clircd01; leave; endif; i=i+1; enddo; Endsr; /end-free *========================================== * Pagina avanti (ROLLUP) *========================================== /free Begsr RollUp; // Posizionati sulla ragione sociale da cui partire setll xposRagsoc clircd01; // Avanza di una pagina i=0; dow i<=xpagesize-1; read clircd01; if %eof; leave; endif; i=i+1; enddo; //Leggi ancora un record per vedere se siamo a fine file read clircd01; if %eof; exsr LastPage; leavesr; else; readp clircd01; endif; Endsr; /end-free *=========================================== * Ultima pagina (LASTPAGE) *=========================================== /free Begsr LastPage; // Posizionati sulla ragione sociale da cui partire cliragsoc=*hival; setgt cliragsoc clircd01; i=0; dow i<=xpagesize; readp clircd01; if %eof; cliragsoc=*loval; setll cliragsoc clircd01; leave; endif; i=i+1; enddo; Endsr; /end-free Nel prossimo articolo porteremo un esempio di Immissione Ordini che illustrerà come ricevere molteplici variabili di input con lo stesso nome. Inoltre spiegheremo due tecniche per limitare l’accesso alle sole persone autorizzate.