Kosmous beyond the clouds: Sviluppo, Formazione e Consulenza Informatica

Griglia dati con ASP

In questo articolo vedremo una delle caratteristiche più interessanti ma anche più sconosciute del linguaggio ASP: le classi.

Molti programmatori che lavorano in ambiente MS Visual Basic, purtroppo, non hanno mai avuto a che fare con la programmazione Object Oriented dato che, come si sa, questo linguaggio viene utilizzato quasi esclusivamente per la scrittura di programmi gestionali con uso intensivo di oggetti già scritti o Stored Procedure.

Spesso i programmatori Visual Basic affrontano la scrittura di semplici classi per costruire componenti COM da utilizzare nelle loro pagine ASP trascurando soluzioni ugualmente funzionanti e più facilmente manutenibili per una scarsa conoscenza dell'argomento.

Scrivere classi nelle pagine ASP è quasi come scrivere classi per componenti COM in Visual Basic.
Dico quasi perchè alcune caratteristiche negative dei linguaggi di Scripting lato server come la mancanza di tipizzazione e l'impossibilità di una buona gestione degli errori influiscono anche nella scrittura delle classi ASP.

Prima di procedere oltre vediamo alcune delle caratteristiche più importanti delle classi:
  • Ci consentono di nascondere la complessità del codice che implementa le varie funzionalità dietro una interfaccia semplice e ben definita
  • Ci consentono di riutilizzare il codice scritto una sola volta in più punti delle nostre applicazioni, semplificando notevolmente la manutenzione e la correzione dei bachi
  • Utilizzando un corretto stile di programmazione possiamo verificare i dati che l'utente della classe ci fornisce prima di utilizzarli, impedendo il verificarsi di errori a catena difficilmente rintracciabili
...altre caratteristiche comuni di altri linguaggi, come l'ereditarietà ed il polimorfismo, non sono purtroppo supportate in ASP.

Vediamo ora come sfrutteremo le classi per costruire la nostra griglia dati.
La prima cosa che ci preoccupiamo di fare è di stabilire le caratteristiche che vorremmo implementare nella nostra griglia:
  1. Visualizzazione di una tabella appartenente ad un database in stile MS-SQLServer
  2. Possibilità di inserire un record semplicemente spostandosi all'ultima riga della griglia ed inserendo qualcosa nei campi
  3. Possibilità di selezionare una o più righe e di cancellarle premendo il tasto canc
  4. Possibilità di gestire Insert e Update nella tabella senza dover ricaricare la pagina dal server web ma semplicemente al cambiamento della riga
  5. Possibilità di suddividere le righe della tabella in pagine
  6. Possibilità di stabilire la larghezza della tabella e delle colonne
  7. Capacità della classe di offrire una interfaccia semplice che comporti la scrittura di un minimo di codice per generare la griglia

Al termine del lavoro la griglia si presenterà nel browser come nella figura seguente:





Il prossimo passo che dobbiamo fare è definire l'interfaccia, ossia le proprietà ed i metodi, che la nostra classe deve implementare:

Proprietà
ConnectionString La stringa di connessione per connetterci al nostro database
Connection Con questa proprietà rendiamo disponibile l'oggetto connessione. La connessione può anche essere impostata con una connessione già presente sulla pagina.
DataSource Come nel caso precedente con questa proprietà diamo all'utente la possibilità di accedere al recordset della tabella per effettuare altre operazioni
TableName Il nome della tabella alla quale vogliamo collegare la nostra griglia
BoundColumn Come nei controlli dati standard di Visual Basic utilizzeremo questa proprietà per comunicare alla classe qual'è il campo chiave primaria della tabella, tale campo non sarà visualizzato nella griglia. Se non viene impostata la proprietà utilizzeremo il primo campo della tabella come chiave primaria
RecordForPage Il numero di record che vogliamo visualizzare per pagina
ColWidth(i) La proprietà che ci consentirà di impostare le dimensioni delle colonne in pixel
GridWidth La dimensione orizzontale della griglia in pixel. Se impostiamo la dimensione delle singole colonne questa proprietà sarà ignorata, mentre se impostiamo la dimensione della griglia, la dimensione delle colonne sarà impostata automaticamente dividendo la dimensione della griglia per il numero di colonne della tabella
ColumnHeaders Proprietà booleana che ci consentirà di specificare se vogliamo o no vedere il nome delle colonne come intestazione della griglia
TraceError Una proprietà che utilizzeremo con due costanti: ERROR_TRACE (traccia gli errori senza generare la pagina di errore del Server Web); ERROR_RAISE (Rilascia gli errori normalmente)
UpdateOption Con questa proprietà daremo all'utente la possibilità di aggiornare la tabella senza dover ricaricare la pagina, utilizzando il componente Microsoft XmlHTTP. Anche questa proprietà la utilizzeremo con due costanti: EXPLICIT_RECORDSET_UPDATE (In questo caso l'update della tabella sarà effettuato esplicitamente dall'utente premendo un pulsante Update e ricaricando la pagina); ROW_UPDATE (Con questa opzione effettueremo aggiornamenti al cambio di riga senza ricaricare la pagina)
Metodi
BuildTable Utilizzeremo questo metodo per generare l'HTML della griglia collegata alla tabella
ExecuteRemoteCommand Questo metodo invece gestirà tutte le operazioni di INSERT, UPDATE e DELETE eventualmente inviate alla pagina da un'azione dell'utente sulla griglia.


Prima di definire la nostra classe dichiareremo le costanti utilizzate per le proprietà TraceError e UpdateOption, in questo modo saranno pubblicamente utilizzabili all'interno della pagina al posto del relativo valore che è sicuramente meno esplicativo
Const EXPLICIT_RECORDSET_UPDATE = 0
Const ROW_UPDATE = 1
Const ERROR_TRACE = 0
Const ERROR_RAISE = 1
Per definire la nostra classe utilizzeremo la parola chiave class seguita dal nome della classe come nell'esempio che segue:
Class Grid

[...]

End Class
All'interno della definizione della classe dichiareremo le nostre variabili private, ossia non visibili all'esterno della stessa La maggior parte di queste ci serviranno per tenere in memoria i valori impostati dall'utente tramite le routines Property Get e Property Let:
Class Grid
   Dim IConnection             'As ADODB.Connection
   Dim IDataSource             'As ADODB.Recordset
   Dim IBoundColumn            'As String
   Dim IUpdateOption           'As Integer
   Dim IColumnHeaders          'As Boolean
   Dim IConnectionString       'As String
   Dim ITraceError             'As Integer
   Dim IGridWidth              'As Long
   Dim IArrCol                 'As Long
   Dim IColWidth()             'As Long
   Dim ColWidthSet             'As Boolean
   Dim ErrFlag                 'As Boolean
   Dim IRecordForPage          'As Long
   Dim ITableName              'As String
   Dim IPageNumber             'As Long
   Dim IPageName               'As String
come noterete al fianco di ogni variabile dichiarata si trova un commento con il tipo della variabile come sarebbe stata dichiarata in Visual Basic. Tale accorgimento è assolutamente ininfluente sul parser ASP ma migliora la leggibilità del codice.

Esistono due procedure particolari che vengono scritte all'interno di una classe ASP:
  • Class_Initialize()
  • Class_Terminate()
tali procedure vengono generalmente chiamate costruttore e distruttore della classe e ci consentono di effettuare operazioni di inizializzazione interne alla classe ogni volta che la stessa viene istanziata con la parola chiave New

Per conto nostro sfrutteremo queste due particolari procedure per impostare dei parametri di DEFAULT delle proprietà della classe ed inizializzare altri parametri di vario genere in questo modo:
Public Sub Class_Initialize()
  IUpdateOption = EXPLICIT_RECORDSET_UPDATE
  IColumnHeaders = true
  ITraceError = ERROR_TRACE
  IConnectionString = ""
  IGridWidth = 100
  IBoundColumn = ""
  IArrCol = 0
  IPageName = Request.ServerVariables("SCRIPT_NAME")
  Redim IColWidth(IArrCol)
  ColWidthSet = false
  IRecordForPage = 10
  if ((Request("page") <> "") and & _
  (IsNumeric(Request("page")))) then
	 IPageNumber = CLng(Request("page"))
  else
	 IPageNumber = 1
  end if
End Sub
Public Sub Class_Terminate()
  Set IDataSource = Nothing
  Set IConnection = Nothing
End Sub
Le proprietà dell'oggetto Grid verranno impostate utilizzando un particolare tipo di routine, la routine property, avente la seguente sintassi:
Property Let  NomeDellaProprieta(ValoreDaImpostare)
   NomeVariabilePrivata = ValoreDaImpostare
End Property
Property Get  NomeDellaProprieta()
   NomeDellaProprieta = NomeVariabilePrivata
End Property
dove la prima procedura imposta la proprietà, la seconda consente di leggerla.

Come si può vedere ogni proprietà utilizza una variabile privata della classe per memorizzarvi il valore, questa strategia viene utilizzata per consentire un maggiore controllo durante le operazioni di lettura e scrittura delle variabili.

Un'altro metodo possibile sarebbe stato dichiarare variabili pubbliche all'interno della definizione della classe, tali variabili sarebbero state visibili all'esterno come proprietà a tutti gli effetti, tuttavia l'utilizzatore della classe avrebbe potuto impostare il loro valore con qualsiasi tipo di dato, anche quelli non consentiti dato che non esiste nessun controllo sull'impostazione della variabile.

Tale possibilità va considerata maggiormente nei linguaggi non tipizzati poichè consente la scrittura di dati di tipo differente da quelli che l'autore della classe si aspetta.

Nel codice che scriveremo per implementare le proprietà della classe Grid eseguiremo il controllo dei valori in ingresso solamente su procedure critiche in tal modo non ci perderemo nei dettagli delle procedure property.

Ecco quindi il codice che scriveremo per implementare le proprietà della classe:
'
'Imposta o restituisce il nome della tabella
'
Public Property Let TableName(strTableName)
  ITableName = strTableName
End Property
Public Property Get TableName()
  TableName = ITableName
End Property

'
'Imposta o restituisce il numero di record per pagina
'
Public Property Let RecordForPage(numRec)
  IRecordForPage = numRec
End Property
Public Property Get RecordForPage()
  RecordForPage = IRecordForPage
End Property

'
'Gestisce la proprieta' ColWidth della classe
'che imposta o restituisce la dimensione delle colonne
'
Public Property Let ColWidth(numCol, pxWidth)
  if numCol > IArrCol then
	 IArrCol = numCol
	 Redim Preserve IColWidth(IArrCol)
  end if
  IColWidth(numCol) = pxWidth
  ColWidthSet = true
End Property
Public Property Get ColWidth(numCol)
  if numCol > IArrCol then
	 ColWidth = 0
  else
	 ColWidth = IColWidth(numCol)
  end if
End Property

'
'Imposta o restituisce la dimensione della griglia
'
Public Property Let GridWidth(pxWidth)
  IGridWidth = pxWidth
End Property
Public Property Get GridWidth()
  GridWidth = IGridWidth
End Property

'
'Abilita la visualizzazione della prima
'riga con il nome delle colonne
'
Public Property Let ColumnHeaders(boolOpt)
  IColumnHeaders = boolOpt
End Property
Public Property Get ColumnHeaders()
  ColumnHeaders = IColumnHeaders
End Property

'
'Gestisce la proprieta' TraceError della classe
'che imposta o restituisce la modalita' di tracciamento
'degli errori
'
Public Property Let UpdateOption(upOpt)
  if ((upOpt <> EXPLICIT_RECORDSET_UPDATE) and & _
	(upOpt <> ROW_UPDATE)) then
	 Call ReleaseError(vbObjectError + 1, & _
"Grid.UpdateOption: Parametro non valido " & upOpt)
	 Exit Property
  end if
  IUpdateOption = upOpt
End Property
Public Property Get UpdateOption()
  UpdateOption = IUpdateOption
End Property

'
'Gestisce la proprieta' UpdateOption della classe
'che imposta o restituisce il metodo per effettuare l'update
'sul db, riga per riga con MSXMLHTTP o con il pulsante update
'
Public Property Let UpdateOption(upOpt)
  if ((upOpt <> EXPLICIT_RECORDSET_UPDATE) and & _
	(upOpt <> ROW_UPDATE)) then
	 Call ReleaseError(vbObjectError + 1, & _
"Grid.UpdateOption: Parametro non valido " & upOpt)
	 Exit Property
  end if
  IUpdateOption = upOpt
End Property
Public Property Get UpdateOption()
  UpdateOption = IUpdateOption
End Property

'
'Imposta o restituisce il nome 
'del campo chiave della tabella
'
Public Property Let BoundColumn(strBC)
  IBoundColumn = strBC
End Property
Public Property Get BoundColumn()
  BoundColumn = IBoundColumn
End Property

'
'Imposta o restituisce un Recordset 
'esternamente alla classe
'
Public Property Set DataSource(objRS)
  set IDataSource = objRS
End Property
Public Property Get DataSource()
  set DataSource = IDataSource
End Property

'
'Imposta o restituisce una connessione 
'esternamente alla classe
'
Public Property Set Connection(objConn)
  set IConnection = objRS
End Property
Public Property Get Connection()
  set Connection = IConnection
End Property

'
'Imposta o restituisce una stringa di connessione
'
Public Property Let ConnectionString(strConn)
  IConnectionString = strConn
End Property
Public Property Get ConnectionString()
  ConnectionString = IConnectionString
End Property
Oltre al codice che implementa le proprietà della classe scriveremo tre procedure private, ossia non visibili all'esterno della classe, per svolgere dei compiti ripetitivi.
'
'Funzione privata per la formattazione
'dei numeri di riga a 4 cifre
'
Private Function FormatNumberRow(numToFormat)
  Dim strZero
  strZero = "0000"
  FormatNumberRow = left(strZero, 4 - (len(CStr & _
	 (numToFormat)))) & CStr(numToFormat)
End Function

'
'Funzione di gestione degli errori
'
Private Sub ReleaseError(errNum, ErrMsg)
  if ITraceError = ERROR_TRACE then
	 Response.Write "Errore: " & errNum & " " & ErrMsg & "
" else Call Err.Raise(errNum, ErrMsg) end if End Sub
La più importante di queste procedure si occupa di effettuare la connessione al Database ed aprire il Recordset sulla tabella. Entrambe queste operazioni vengono effettuate solo se l'oggetto corrispondente non si trova nel corretto stato:
'
'Connessione ed apertura del RecordSet
'
Private Function Connect()
  Dim ErrNum				'As Long
  Dim ErrMsg				'As String
  
  Connect = false

  if Not IsObject(IConnection) then
    if IConnectionString = "" then
     Call ReleaseError(vbObjectError + 3, & _
     "Grid.Execute: Impossibile eseguire il comando.
     La connessione e la stringa di connessione 
     non sono state impostate.")
    Exit Function
    else
     set IConnection = & _
	 Server.CreateObject("ADODB.Connection")
     IConnection.ConnectionString = IConnectionString
    end if
  end if
  if IConnection.State <> adStateOpen then
    IConnection.Open
  end if
  if Not IsObject(IDataSource) then
    Set IDataSource = Server.CreateObject("ADODB.Recordset")
  end if
  if IDataSource.State <> adStateOpen then
    if ITableName = "" then
     Call ReleaseError(vbObjectError + 5, "Grid.Connect:
     Non e' stato specificato alcun nome di tabella valido.")
     Exit Function
    end if
    On Error Resume Next
    IDataSource.Open ITableName, IConnection, & _
    adOpenKeyset, adLockOptimistic
    ErrNum = Err.number
    ErrMsg = Err.Description
    On Error Goto 0
  end if
  if ErrNum <> 0 then
    Call ReleaseError(ErrNum, ErrMsg)
    Exit Function
  end if

  Connect = true

End Function
Ora realizzeremo il metodo BuildTable() che ci consentirà di realizzare l'HTML da inviare al browser ciclando il Recordset aperto sulla tabella. Prima di addentrarci nella scrittura del codice dovremo però considerare alcune cose importanti:
  1. Al termine della scrittura del codice lato server dovremo scrivere alcune procedure lato client in JavaScript che ci consentiranno di gestire l'attività dell'utente sulle righe della tabella quindi dovremo fare in modo che ciascuna riga e ciascun campo di inserimento visualizzati nel browser siano identificabili in modo univoco.
  2. Dovremo inserire una riga vuota di default per consentire all'utente di effettuare inserimenti nella tabella
  3. Dovremo gestire la visualizzazione della riga di intestazione delle colonne
Cominciamo ora a dichiarare la procedura e le variabili che utilizzeremo
'
'Implementa il metodo BuildTable() della classe
'che genera l'HTML della griglia
'
Public Sub BuildTable()
   Dim ErrNum            'As Long
   Dim ErrMsg            'As String
   Dim NumFields         'As Long
   Dim HTMLString        'As String
   Dim ColSize           'As Long
   Dim grid_field        'As ADODB.Field
   Dim BoundColumnExist  'As Boolean
   Dim tmpString         'As String
   Dim IDPrint           'As String
   Dim tmpHTMLInput      'As String
   Dim RowCounter        'As Long
   Dim FieldCounter      'As Long
   Dim RowID             'As String
   Dim FieldID           'As String
   Dim FieldInputSize()  'As long
   Dim ImgStrXmlHTTP     'As String
   Dim cont              'As Long
   Dim AddFlag           'As Boolean
   Dim RecStart          'As Long
   Dim NumOfPage         'As Long
   Dim ContPage          'As Long
  
   [...]
poi per prima cosa richiamiamo la nostra funzione di connessione al DB
   'Richiama la procedura di connessione al DB
   if not Connect() then
      Exit Sub
   end if

   [...]
dopodichè calcoleremo il numero delle colonne e le loro dimensioni (se l'utente della griglia non l'ha impostate esplicitamente)
   'Verifica il numero di colonne e le dimensioni
   NumFields = IDataSource.Fields.CountColSize = & _
   Cint(((IGridWidth - 20) / (NumFields - 1)))
   Redim FieldInputSize(NumFields - 1)
   if (NumFields - 1) > IArrCol then
      IArrCol = NumFields - 1
      Redim Preserve IColWidth(IArrCol)
   end if

   'Se le dimensioni delle colonne non sono
   'impostate le imposta in automatico
   if not ColWidthSet then
      for cont = 0 to IArrCol
         IColWidth(cont) = ColSize
      next
   else
      'altrimenti reimposta la dimensione della griglia
      'in base alla dimensione delle colonne
       IGridWidth = 0
      for cont = 0 to IArrCol
         IGridWidth = IGridWidth + CInt(IColWidth(cont))
      next
   end if

   [...]
Prima di cominciare a stampare qualcosa dobbiamo verificare 2 cose:
  1. Che il Recordset non sia vuoto, altrimenti non riusciremo a leggere il nome delle colonne per stampare l'intestazione. In tal caso aggiungeremo un Record provvisorio che toglieremo in seguito senza effettuare Update
  2. Che esista effettivamente una colonna chiave primaria dello stesso nome di quella impostata dall'utente con la proprietà BoundColumn. Se l'utente non ha impostato questa proprietà proveremo ad utilizzare la prima colonna della tabella
   'Se la tabella e' vuota aggiunge un record provvisorio
   'per leggere il nome dei campi
   AddFlag = false
   if IDataSource.RecordCount < 1 then
      AddFlag = true
      IDataSource.AddNew
   end if

   'Verifica se presente il campo BoundColumn
   if IBoundColumn <> "" then
      On Error Resume Next
      tmpString = IDataSource.fields(IBoundColumn)
      BoundColumnExist = CBool(Err.Number = 0)
      On Error Goto 0
      if not BoundColumnExist then
         Call ReleaseError(vbObjectError + 4, "Grid.Execute:
         La colonna impostata come BoundColumn non esiste.")
         Exit Sub
      end if
   else
      BoundColumn = IDataSource.fields(0).Name
   end if

   [...]
Dato che vogliamo rendere la classe autonoma e semplice da utilizzare, inseriremo nell'HTML inviato al browser anche la dichiarazione del modulo necessario per poter inviare i dati al server web. In questo modo l'utente non dovrà minimamente preoccuparsi della trasmissione dei comandi di INSERT UPDATE e DELETE sulla griglia perché la classe li gestirà in automatico. Tutto l'HTML da inviare lo memorizzeremo temporaneamente nella variabile HTMLString:
   'Stampa l'inizio della tabella
   HTMLString = "<form action=""" & IPageName & """ 
   method=""post"" name=""gridform"" id=""gridform"">" 
   & vbcrlf
   HTMLString = HTMLString & "<div align=""center"" id=""grid"">"
   & vbcrlf & "<table width=""" & IGridWidth & """ border=""0""
   cellspacing=""0"" cellpadding=""0"" class=""GridTable"">"
   & vbcrlf

   [...]
Ora possiamo cominciare a costruire l'intestazione della griglia (sempre che l'utente la voglia visualizzare).

Se è stata scelta l'opzione ROW_UPDATE, visualizzeremo nell'angolo in alto a sinistra della griglia una piccola immagine indicante lo stato della comunicazione in background con il server web.

Per stampare l'intestazione delle colonne useremo comunque dei campi di input, questa cosa ci garantisce che la colonna non verrà dimensionata sulla base del testo immesso ma seguirà le dimensioni fisse che gli assegneremo.

Per formattare la griglia utilizzeremo delle definizione di stile in un foglio di stile collegato
   'Stampa l'intestazione con il nome delle colonne

   if IColumnHeaders then
      ImgStrXmlHTTP = ""
      if IUpdateOption = ROW_UPDATE then
         ImgStrXmlHTTP = "<img name=""ImgStatus"" 
         src=""file/operation_ok.png"" width=""16""
         height=""16"" border=""0"" id=""ImgStatus"" alt="""">"
      else
         ImgStrXmlHTTP = "<img src=""file/trasp.png"" 
         width=""16"" height=""16"" border=""0"" alt="""">"
      end if
      HTMLString = HTMLString & "<TR>" & vbcrlf
      FieldCounter = 0
      for each grid_field in IDataSource.fields
         if lcase(grid_field.Name) = lcase(IBoundColumn) then
            HTMLString = HTMLString & "<td width=""20"" 
            class=""GridCellID"" style=""width: 20px;"">" & 
            ImgStrXmlHTTP & "</td>" &
            vbcrlf 
         else
            HTMLString = HTMLString & "<td align=""center""
            style=""width: " & CInt(IColWidth(FieldCounter)) &
            "px;"" class=""GridCellID""><input style=""width: "
            & CInt(IColWidth(FieldCounter)) & "px; border: 
            none; height: 18px; text-align: center;"" 
            class=""GridCellID"" value=""" & grid_field.Name
            & """></td>" & vbcrlf
            FieldCounter = FieldCounter + 1
         end if
      next
      HTMLString = HTMLString & "</TR>" & vbcrlf
   end if

   [...]
dopo aver costruito l'HTML dell'intestazione cicleremo il Recordset, facendo attenzione alla pagina nella quale ci troviamo, per costruire l'HTML delle righe vere e proprie della griglia:
   'Cicla il recordset per stampare le righe
   IDPrint = " style=""background-color: #EBEBF1;"""
   tmpHTMLInput = "record"
   RowCounter = 0
   Dim tmpVal
   if not AddFlag then
      'Posiziona il cursore all'inizio della pagina corrente
      RecStart = ((IPageNumber - 1) * IRecordForPage)' + 1
      NumOfPage = int(IDataSource.RecordCount / IRecordForPage)
      if IDataSource.RecordCount mod IRecordForPage > 0 then
         NumOfPage = NumOfPage + 1
      end if
      IDataSource.Move RecStart
   
      While ((RowCounter < IRecordForPage) and & _
      (not IDataSource.EOF))
         HTMLString = HTMLString & "<TR>" & vbcrlf
         RowID = FormatNumberRow(RowCounter)
         HTMLString = HTMLString & "<td class=""GridCellID""
         id=""riga_" & RowID & "xROW""><input type=""hidden""
         name=""riga_" & RowID & "xID"" value=""" &
         IDataSource(IBoundColumn) & """> <input
         type=""hidden"" name=""riga_" & RowID & "xCHANGE""
         value=""""><img  OnClick=""selectRow(this, 2)""
         id=""riga_" & RowID & "xImg"" src=""file/" &
         tmpHTMLInput & ".png"" width=""16""
         height=""16"" border=""0"" alt=""""></td>"
         & vbcrlf
         FieldCounter = 0
         for each grid_field in IDataSource.fields
            if lcase(grid_field.Name) <> lcase(IBoundColumn) then
               FieldID = FormatNumberRow(FieldCounter)
               FieldInputSize(FieldCounter) = & _
               grid_field.DefinedSize
               if grid_field.type = 205 then 'adLongVarBinary 
                 tmpVal = ""
               elseif grid_field.type = 11 then
                  tmpVal = CLng(grid_field.value)
               else
                  tmpVal = grid_field.value
               end if
               HTMLString = HTMLString & "<td style=""width: " 
               & CInt(IColWidth(FieldCounter)) & "px;"" 
               class=""GridCellData"" id=""riga_" & RowID 
               & "x" & FieldID & """" & IDPrint & 
               "><input style=""width: " & 
               CInt(IColWidth(FieldCounter)) & "px;"" 
               onFocus=""selectRow(this, 1)""
               OnKeyDown=""kdField(this)""
               OnChange=""rowChanged(this)"" type=""text"" 
               name=""campo_" & RowID & "x"
               & FieldID & """ id=""campo_" & RowID & "x"
               & FieldID & """ value="""
               & tmpVal & """ maxlength=""" & 
               FieldInputSize(FieldCounter) & """
               class=""InputData""" & IDPrint & "></td>"
               & vbcrlf
               FieldCounter = FieldCounter + 1
            end if
         next
         IDataSource.MoveNext
         RowCounter = RowCounter + 1
         IDPrint = ""
         tmpHTMLInput = "trasp"
         HTMLString = HTMLString & "</TR>" & vbcrlf
      Wend
   end if

   [...]
Dopo aver ciclato il Recordset aggiungiamo l'HTML relativo alla riga di inserimento e chiudiamo la tabella:
   'Aggiunge la riga per l'inserimento
   HTMLString = HTMLString & "<TR>" & vbcrlf

   RowID = FormatNumberRow(RowCounter)
   HTMLString = HTMLString & "<td class=""GridCellID""
   id=""riga_" & RowID & "xROW""><input type=""hidden""
   name=""riga_" & RowID & "xID"" value=""-2""><input
   type=""hidden"" name=""riga_" & RowID & "xCHANGE"" 
   value=""""><img OnClick=""selectRow(this, 2)""
   id=""riga_" & RowID & "xImg"" src=""file/newrecord.png""
   width=""16"" height=""16"" border=""0""
   alt=""""></td>" & vbcrlf

   for FieldCounter = 0 to (NumFields -2)
      FieldID = FormatNumberRow(FieldCounter)
      HTMLString = HTMLString & "<td class=""GridCellData""
      id=""riga_" & RowID & "x" & FieldID & """><input
      onFocus=""selectRow(this, 1)"" OnKeyDown=""kdField(this)""
      OnChange=""rowChanged(this)"" type=""text""
      name=""campo_" & RowID & "x" & FieldID & """ id=""campo_"
      & RowID & "x" & FieldID & """ value="""" maxlength="""
     & FieldInputSize(FieldCounter)
      & """ class=""InputData""></td>" & vbcrlf
   next
   HTMLString = HTMLString & "</TR>" & vbcrlf
   HTMLString = HTMLString & "</TABLE></div>" & vbcrlf

   [...]
Ora dovremo inserire nell'HTML la dichiarazione di alcune variabili JavaScript che saranno utilizzate lato Client per gestire l'attività dell'utente sulle righe della griglia:
   'Inserisce nell'html l'inizializzazione delle variabili
   'JavaScript necessarie per il corretto funzionamento
   'delle procedure lato client
   HTMLString = HTMLString & "" & vbcrlf      

   [...]
Infine provvederemo a visualizzare un pulsante di Update se l'utente ha scelto il salvataggio esplicito dei dati con EXPLICIT_RECORDSET_UPDATE ed i link alle pagine della tabella. Prima di inviare l'HTML al browser inseriremo un campo hidden nel form per gestire sul server i comandi inviati dalla pagina e rimuoveremo l'eventuale Record inserito per accedere al nome dei campi:
   'Aggiunge il pulsante Update se e' stata
   'selezionata l'opzione di Update esplicito
   if IUpdateOption = EXPLICIT_RECORDSET_UPDATE then
      HTMLString = HTMLString & "<br><div align=""center"">"
      & vbcrlf
      HTMLString = HTMLString & "<input type=""submit""
      value=""Update""></div>" & vbcrlf
   end if

   'Aggiunge i link alle pagine
   if NumOfPage > 1 then
      HTMLString = HTMLString & "<br><div align=""center"" 
      class=""Page"">" & vbcrlf

      for ContPage = 1 to NumOfPage
         if ContPage = IPageNumber then
            HTMLString = HTMLString & ContPage
         else
            HTMLString = HTMLString & "<a class=""Page""
            href=""" & IPageName & "?page=" & ContPage & """>"
            & ContPage & "</a>"
         end if
         if ContPage < NumOfPage then
            HTMLString = HTMLString & " | "
         end if
      next
      HTMLString = HTMLString & "</div>" & vbcrlf
   end if


   'Aggiunge un campo hidden per il passaggio 
   'dei comandi verso il server
   HTMLString = HTMLString & "<input type=""hidden""
   name=""command"" value=""""></form>" & vbcrlf

   'Stampa l'HTML
   Response.Write HTMLString

   'Rimuove la riga eventualmente aggiunta per ricavare
   'il nome dei campi

   if AddFlag then
      IDataSource.Delete
   end if  

End Sub
A questo punto, prima di implementare il metodo ExecuteRemoteCommand() della griglia passiamo a scrivere il codice JavaScript necessario per gestire l'attività dell'utente sul Client.

Come prima cosa ci dichiariamo delle variabili da utilizzare come costanti aventi lo stesso valore delle corrispondenti lato server, poi inizializziamo alcune altre variabili fra le quali troviamo quelle utilizzate per gestire il cambiamento dell'immagine di attività della griglia dopo una chiamata con XmlHTTP (Quella che si vede nella figura in alto a sinistra)

Non dimenticate che il codice che segue è codice JavaScript e che viene eseguito lato Client!
// Definizione di alcune costanti per l'uso della classe
var EXPLICIT_RECORDSET_UPDATE = 0;
var ROW_UPDATE = 1;
var isMSIE = (document.all != 'undefined');

// Inizializzazione delle variabili per la gestione delle
// immagini relative allo spostamento del cursore
// e delle operazioni eseguite sul server
var imgCursorOn = new Image();
var imgCursorOff = new Image();
var imgCursorSelected = new Image();
var imgNewRecord = new Image();
var imgOpOk = new Image();
var imgOpSend = new Image();
var imgOpError = new Image();
imgCursorOn.src = "file/record.png";
imgCursorOff.src = "file/trasp.png";
imgCursorSelected.src = "file/record_selected.png";
imgNewRecord.src = "file/newrecord.png";
imgOpOk.src = "file/operation_ok.png";
imgOpSend.src = "file/operation_send.png";
imgOpError.src = "file/operation_error.png";

// Inizializzazione delle variabili
//per la gestione della selezione
var selectedRow1 = -1;
var selectedRow2 = -1;
var selPresent = 0;
var resetFocus = false;

[...]


Cominciamo a scrivere due funzioni che ci saranno utili all'interno dello script per formattare correttamente i nomi dei campi di input utilizzati nella griglia:
//
// Funzione di servizio per la formattazione di numeri
// in stringa 4 cifre
//
function formatName(numb){
   var strTemp = "0000";
   var strNumb = numb.toString();
   return(strTemp.substr(0, 4 - strNumb.length) + strNumb);
}

//
// Restituisce il numero di riga estraendolo dal nome
// in formato numerico o testo
//
function getRowNumberFromName(strName, numb, cell){
   if(cell){
      var sNtemp = strName.match(/x.+/);
   }
   else{
      var sNtemp = strName.match(/_.+x/);
   }
   if(sNtemp){
      if(numb){
         if(cell){
            var strNumber = sNtemp[0].substr(1);
         }
         else{
            var strNumber = sNtemp[0].substr(1, 
               (sNtemp[0].length -2));
         }
         if(isNaN(strNumber)){
            return(-1);
         }
         else{
            return(parseInt(strNumber, 10));
         }
      }
      else{
         return(sNtemp[0]);
      }
   }
   else{
      return(-1);
   }
}

[...]
Dopodichè scriveremo due procedure che si occupano rispettivamente di gestire una selezione sulle righe della griglia e impostare lo stile di visualizzazione in base alle operazioni effettuate dall'utente sulla griglia stessa.

La prima di queste due procedure (selectRow) è il gestore dell'evento onFocus() dei campi inviati al browser dalla procedura ASP di creazione della griglia.
//
// Funzione per la selezione delle righe
// selectType: 0 = Normal ; 1 = Edit ; 2 = Select
//
function selectRow(obj, selectType){
   if(!isMSIE){
      alert("La griglia dati e' compatibile con le
         versioni di MS Explorer 4 in poi.nImpossibile utilizzare
         la griglia con altri browser.");
      return;
   }
   var rowNumber = getRowNumberFromName(obj.id, true, false);
   var oldRowName = "riga_" + formatName(lastEditableRow) 
   + "xCHANGE";
   if((updateOption == ROW_UPDATE)
      && (rowNumber != lastEditableRow) &&
      (document.gridform.elements[oldRowName].value == "1")){
      submitRowCommand(formatName(lastEditableRow), oldRowName);
   }
   var RowName = "riga_" + formatName(rowNumber) + "x";
   var rowID = RowName + "ID";
   if(resetFocus){
      resetFocus = false;
      window.setTimeout("document.gridform.elements['" + obj.id
      + "'].focus()", 150);
   }
   if((selectType == 2)
      && (document.getElementById(RowName + "Img").src
      == imgNewRecord.src)){
      selectType = 1;
   }
   // Tolgo eventuali selezioni presenti
   if((selectedRow1 != -1) && (selectedRow2 != -1)){
      //Toglie la selezione multipla
      for(rx = selectedRow1; rx <= selectedRow2; rx++){
         setRowStyle(rx, 0);
      }
      selectedRow1 = -1;
      selectedRow2 = -1;
   }
   else{
      //Toglie la selezione singola
      if((selectedRow1 != -1) && (selectType != 2)){
         setRowStyle(selectedRow1, 0);
         selectedRow1 = -1;
      }
      if(lastEditableRow != rowNumber){
         setRowStyle(lastEditableRow, 0);
      }
   }
   selPresent = 0;
   switch(selectType){
      case 2: // Select
         if(selectedRow1 == -1){
            selectedRow1 = rowNumber;
         }
         else{
            selectedRow2 == rowNumber;
         }
         if((selectedRow1 != -1) && event.shiftKey){
            //Selezione multipla
            selectedRow2 = rowNumber;
            var appSwitch = selectedRow1;
            var useCursor = false
            if(selectedRow2 < selectedRow1){
               selectedRow1 = selectedRow2;
               selectedRow2 = appSwitch;
            }
            for(rx = selectedRow1; rx <= selectedRow2; rx++){
               useCursor = (rx == rowNumber);
               setRowStyle(rx, 2, useCursor);
            }
            selPresent = 2;
         }
         else{
            //Selezione singola in base a  selectedRow1
            selectedRow2 = -1;
            selectedRow1 = rowNumber;
            setRowStyle(selectedRow1, 2);
            selPresent = 1;
         }
         break;
      case 0: // Normal
         setRowStyle(rowNumber, 0);
         break;
      case 1: // Edit
         setRowStyle(rowNumber, 1);
         break;
   }
}

//
// Funzione per l'impostazione dello stile della riga
// selectType: 0 = Normal ; 1 = Edit ; 2 = Select
//
function setRowStyle(row, selectType, useCursor){
   var RowName = "riga_" + formatName(row) + "x";
   var FieldName = "campo_" + formatName(row) + "x";
   var cursorName = "riga_" + formatName(row) + "xROW";
   var fieldCounter = 0;
   switch(selectType){
      case 0: // Normal
         var rowBgColor = "#FFFFFF";
         var rowColor = "#000000";
         var patImg = imgCursorOff.src;
         var IDrowBgColor = "#D6D3CE";
         break;
      case 1: // Edit
         var rowBgColor = "#EBEBF1";
         var rowColor = "#000000";
         var patImg = imgCursorOn.src;
         var IDrowBgColor = "#D6D3CE";
         break;
      case 2 : //Select
         var rowBgColor = "#08246B";
         var rowColor = "#FFFFFF";
         if ((useCursor != 'undefined') && (useCursor == false)){
            var patImg = imgCursorOff.src;
         }
         else{
            var patImg = imgCursorSelected.src;
         }
         var IDrowBgColor = "#08246B";
         break;
   }
   
   var objRow = document.getElementById(RowName +
   formatName(fieldCounter));
   while((objRow != 'undefined')&& (objRow != null)){
      objRow.style.backgroundColor = rowBgColor;
      document.gridform.elements[FieldName +
         formatName(fieldCounter)].style.backgroundColor
         = rowBgColor;
      document.gridform.elements[FieldName +
         formatName(fieldCounter)].style.color = rowColor;
      fieldCounter++;
      objRow = document.getElementById(RowName +
      formatName(fieldCounter));
   }
   if((document.getElementById(RowName + "ID").value == "-2")
      && (document.getElementById(RowName + "CHANGE").value
      == "")){
      document.getElementById(RowName + "Img").src
      = imgNewRecord.src;
   }
   else{
      document.getElementById(RowName + "Img").src = patImg;
      document.getElementById(cursorName).style.backgroundColor
      = IDrowBgColor;		
   }
   lastEditableRow = row;
}

[...]
Scriviamo anche una procedura che ci consentirà di gestire lo spostamento verticale fra le righe della griglia in base alla pressione dei tasti di spostamento del cursore in modo da semplificare l'uso della tastiera.

Tale procedura è il gestore dell'evento OnKeyDown() registrato per i campi della griglia:
//
// Gestisce la pressione dei tasti del cursore per lo spostamento
// verticale fra le righe della tabella
//
function kdField(obj){
   if(isMSIE){
      var KC = event.keyCode;
      if(KC == 38 || KC == 40){
         var rowNumber = getRowNumberFromName(obj.name, true, 
         false);
         var fieldNumber = getRowNumberFromName(obj.name, false,
         true);
         var mov = 0;
         switch(KC){
            case 38:
               mov = -1;
               break;
            case 40:
               mov = 1;
               break;
         }
         rowNumber += mov;
         var fieldName = "campo_" + formatName(rowNumber)
         + fieldNumber;
         var rowName = "riga_" + formatName(rowNumber)
         + fieldNumber;
         if(document.gridform.elements[fieldName]){
            document.gridform.elements[fieldName].focus();
         }
      }
   }
}

[...]
Un altro evento di KeyDown che dobbiamo gestire è a livello del documento e ci serve per rilevare la pressione del tasto CANC sul browser ed attivare la procedura di eliminazione dei record nel caso in cui sia presente una selezione sulle righe della griglia.

La procedura di eliminazione dei Record ricarica la pagina indipendentemente dall'impostazione scelta dall'utente per la proprietà UpdateOption della griglia, tale accorgimento ci consente di non sovraccaricare il browser con procedure complesse e di avere una paginazione reale rispetto alle righe presenti nella tabella dopo l'eliminazione, dato che la paginazione viene ricalcolata sul server.
//
// Imposta il gestore di eventi per intercettare la pressione
// del tasto canc ed avviare la procedura di delete sul server
// in caso siano selezionate delle righe
//
document.onkeydown = function() {
   if((event.keyCode == 46) && (selPresent != 0)){
      if(confirm("Eliminare definitivamente i record
      selezionati?")){
         document.gridform.command.value = "delete";
         //Scorro da selectedRow1 a selectedRow2
         //ed imposto il campo change
         var tmpselectedRow2 = selectedRow2;
         if(tmpselectedRow2 == -1) tmpselectedRow2 = selectedRow1;
            for(rx = selectedRow1; rx <= tmpselectedRow2; rx++){
               var rowName = "riga_" + formatName(rx) + "xCHANGE";
               document.gridform.elements[rowName].value = "-1";
            }
         document.gridform.submit();
         }
      }
   }
}

[...]
All'evento OnChange() dei campi della griglia viene richiamata la procedura seguente che imposta il flag di cambiamento riga nel campo nascosto e verifica se il cambiamento è avvenuto nell'ultima riga della griglia: quella di inserimento. In tal caso viene impostato un timer per attivare una seconda procedura (alterHTML) che si occuperà di inserire una nuova riga vuota in coda alla griglia senza ricaricare la pagina, grazie alle funzioni DHTML di MSIExplorer.

L'uso del timer si è reso necessario per problemi di perdita del fuoco del campo.
//
// Gestisce il flag di riga cambiata per l'eventuale update
//
function rowChanged(obj){
   var rowName = "riga" + getRowNumberFromName(obj.name,
   false, false) + "CHANGE";
   document.gridform.elements[rowName].value = "1";
   var rowID = "riga" + getRowNumberFromName(obj.name,
   false, false) + "ID";
   if(document.gridform.elements[rowID].value == "-2"){
      document.gridform.elements[rowID].value = "-3";
      resetFocus = true;
      window.setTimeout("alterHTML('" + obj.name + "')", 100);
   }
}


//
// Inserisce dinamicamente una riga vuota per l'inserimento
// di un nuovo record nella tabella
//
function alterHTML(objName){
   var rowID = "riga" + getRowNumberFromName(objName,
   false, false) + "ID";
   if (document.gridform.elements[rowID].value == "-3"){
      document.gridform.elements[rowID].value = "-1";
      var oldHTML = document.getElementById("grid").innerHTML;
      oldHTML = oldHTML.substr(0, (oldHTML.length - 16));
      var newRowName = formatName(numberOfRows);
      oldHTML += "<tr>\n";
      oldHTML += "<td class=\"GridCellID\" id=\"riga_"
         + newRowName + "xROW\"><input type=\"hidden\"
         name=\"riga_" + newRowName + "xID\" value=\"-2\"
         ><input type=\"hidden\" name=\"riga_" + newRowName
         + "xCHANGE\" value=\"\"><img id=\"riga_" + newRowName
         + "xImg\" OnClick=\"selectRow(this, 2)\"
         src=\"file/newrecord.png\" width=\"16\" height=\"16\"
         border=\"0\" alt=\"\"></td>\n";
      for(cc = 0; cc < numberOfColumns; cc++){
         newFieldName = "campo_" + newRowName + "x"
         + formatName(cc);
         newRow = "riga_" + newRowName + "x" + formatName(cc);
         oldHTML += "<td id=\"" + newRow + "\"
            class=\"GridCellData\">
            <input OnFocus=\"selectRow(this, 1)\"
            OnKeyDown=\"kdField(this)\"
            OnChange=\"rowChanged(this)\"
            type=\"text\" name=\"" + newFieldName + "\"
            id=\"" + newFieldName +
            "\" value=\"\" maxlength=\"\" class=\"InputData\"
            ></td>\n";
      }
      oldHTML += "</tr></TBODY></table>";
      numberOfRows++;
      document.getElementById("grid").innerHTML = oldHTML;
   }
}

[...]
Infine scriveremo la procedura che ci consente di utilizzare il componente Microsoft.XMLHTTP per eseguire un comando sul server senza ricaricare la pagina.

La procedura che vedremo ora registra una funzione di CallBack, ossia una funzione che sarà chiamata dal componente una volta che questo avrà ricevuto qualcosa dal server, per l'uso in modo asincrono ossia senza bloccare il browser in attesa della risposta del server.

La procedura di CallBack controlla il codice di stato ricevuto ed in base a questo modifica l'immagine presente in alto a sinistra della griglia.

Se è stato restituito un codice diverso da 200 verrà visualizzata una finestra di messaggio contenente l'errore restituito dal server, nel caso in cui l'errore restituito sia maggiore di 500 caratteri la procedura provvede ad aprire una pagina nuova e a scrivervi l'errore ricevuto. Tale accortezza è necessaria per evitare la visualizzazione di finestre di messaggio giganti in caso di errori non previsti.
// Dichiarazione delle variabili pubbliche per l'uso di XmlHTTP
var objXmlHTTP;
var pRowElab;
var objNotInstalled = false;

//
// Funzione di insert o update tramite XmlHTTP
//
function submitRowCommand(RowNumber, RowName){
   try{
      objXmlHTTP = new ActiveXObject("Microsoft.XMLHTTP");
   }
   catch(er){
      if(!objNotInstalled){
         alert("Impossibile creare l'oggetto Microsoft.XmlHTTP");
         objNotInstalled = true;
      }
      return;
   }
   document.images["ImgStatus"].src = imgOpSend.src;
   var tempFN = "";
   var httpParam = "&id=" + document.gridform.elements["riga_"
   + RowNumber + "xID"].value;
   for(cc = 0; cc < numberOfColumns; cc++){
      tempFN = "campo_" + RowNumber + "x" + formatName(cc);
      httpParam += "&" + tempFN + "=" +
      document.gridform.elements[tempFN].value;
   }
   pRowElab = RowName
   objXmlHTTP.Open("GET", pageName + "?async=1" + httpParam,
   true);
   objXmlHTTP.SetRequestHeader("Content-Type:", "text/html");
   objXmlHTTP.OnReadyStateChange = callBackProcedure;
   objXmlHTTP.Send();
}

//
// Procedura di CallBack di XmlHTTP,
// riceve il risultato delle operazioni
// svolte lato server
//
function callBackProcedure(){
   if(objXmlHTTP.ReadyState == 4){
      if(objXmlHTTP.Status == 200){
         document.images["ImgStatus"].src = imgOpOk.src;
         document.gridform.elements[pRowElab].value = "";
      }
      else{
         document.images["ImgStatus"].src = imgOpError.src;
         var strErr = objXmlHTTP.ResponseText;
         if(strErr.length < 500){
            alert("Errore : " + objXmlHTTP.ResponseText);
         }
         else{
            var err_win = window.open("about:blank", "err_win");
            err_win.document.open("text/html");
            err_win.document.write(strErr);
            err_win.document.close();
         }
      }
   }
}
A questo punto non ci rimane che scrivere il codice che implementa il metodo ExecuteRemoteCommand() della griglia.

Dopo aver dichiarato la procedura e le variabili utilizzate dalla stessa, utilizziamo la nostra funzione Connect() per connetterci al Database.

Ricordatevi che le righe di codice che seguono vanno inserite all'interno della classe Grid e vengono eseguite sul server
   '
   'Esegue l'insert, l'update ed il delete nel DB
   'in base ai dati ricevuti dal client
   '
   Public Sub ExecuteRemoteCommand()
      Dim Param              'As String
      Dim FNumb              'As Long
      Dim Cont               'As Long
      Dim ArrID()            'As Long
      Dim ArrInsert()        'As Long
      Dim ArrUpdate()        'As Long
      Dim ArrDelete()        'As Long
      Dim Cicle              'As Long
      Dim ContInsert         'As Long
      Dim ContDelete         'As Long
      Dim ContUpdate         'As Long
      Dim ChangeFlag         'As Boolean
      Dim strDelete          'As String
      Dim strUpdate          'As String
      Dim strInsert          'As String
      Dim ErrNum             'As Long
      Dim ErrMsg             'As String
      Dim tmpField           'As String
      Dim ContFields         'As Long
      Dim NumF               'As Long
      Dim strInsertF         'As String
      Dim ArrFieldName()     'As String
      Dim BoundColumnExist   'As Boolean
      
      'Richiama la procedura di connessione al DB
      if not Connect() then
         Exit Sub
      end if

[...]
Ora gestiremo i due tipi di chiamata del Client, il primo tramite XmlHTTP ed il secondo tramite un ricaricamento della pagina.

Nel primo caso dovremo stare attenti a qualsiasi tipo di errore, restituendo un codice di stato differente da 200 (io ho scelto il 500 che di solito è un errore interno del server) ed un messaggio che indichi all'utente cosa è avvenuto.

Se tutto è andato liscio, invece, dovremo svuotare il buffer della pagina , restituire un codice di stato 200 ed uscire dall'esecuzione dello script. In tal caso infatti non dobbiamo assolutamente generare output HTML.
   'Chiamata di XmlHTTP
   if ((IUpdateOption = ROW_UPDATE) and
    (Request("async") = "1")) then
      'Svuota il buffer
      Response.Clear
      if request("id") < 1 then
         'INSERT
         On Error Resume Next
         IDataSource.AddNew
         if Err.Number <> 0 then
            Response.Status = 500
            Response.write Err.Description
            Response.end
            Exit sub
         end if
   
         for each param in Request.Querystring
         'Scandisce i parametri per eseguire INSERT o UPDATE
         if instr(param, "campo_") > 0 then
            FNumb = CLng(right(param,4)) + 1
            if Err.Number <> 0 then
               Response.Status = 500
               Response.write Err.Description
               Response.end
               Exit sub
            end if
            if Request(param) <> "" then
               IDataSource(FNumb) = Request(param)
            end if
            if Err.Number <> 0 then
               Response.Status = 500
               Response.write Err.Description &
               "(" & IDataSource(FNumb).Name & ")"
               Response.end
               Exit sub
            end if
         end if
      next
      IDataSource.Update
      if Err.Number <> 0 then
         Response.Status = 500
         Response.write Err.Description
         Response.end
         Exit sub
      end if
   else
      'UPDATE
      On Error Resume Next
      While Not IDataSource.EOF
         if Cstr(IDataSource(0)) = request("id") then
            for each param in Request.Querystring
               'Scandisce i parametri per eseguire INSERT o UPDATE
               if instr(param, "campo_") > 0 then
                  FNumb = CLng(right(param,4)) + 1
                  if Err.Number <> 0 then
                     Response.Status = 500
                     Response.write Err.Description
                     Response.end
                     Exit sub
                  end if
                  if Request(param) <> "" then
                     IDataSource(FNumb) = Request(param)
                  end if
                  if Err.Number <> 0 then
                     Response.Status = 500
                     Response.write Err.Description &
                     "(" & IDataSource(FNumb).Name & ")"
                     Response.end
                     Exit sub
                  end if
               end if
            next
            IDataSource.Update
            if Err.Number <> 0 then
               Response.Status = 500
               Response.write Err.Description
               Response.end
               Exit sub
            end if
         end if
         IDataSource.MoveNext
      Wend
      On Error Goto 0
   end if
   
   'Restituisce il codice 200 e
   'termina la scrittura dello stream verso il browser
   Response.Status = 200
   Response.end
   Exit Sub

[...]
Nel secondo caso aggiremo come in una normalissima pagina ASP che dopo aver effettuato le operazioni sul Database viene ricaricata dal Client
   else
      'Submit esplicito
      'Verifica se presente il campo BoundColumn
      if IBoundColumn <> "" then
         On Error Resume Next
            tmpString = IDataSource.fields(IBoundColumn)
            BoundColumnExist = CBool(Err.Number = 0)
         On Error Goto 0
         if not BoundColumnExist then
            Call ReleaseError(vbObjectError + 4, "Grid.Execute:
               La colonna impostata come BoundColumn non esiste.")
            Exit Sub
         end if
      else
         On Error Resume Next
            IBoundColumn = IDataSource.fields(0).Name
            BoundColumnExist = CBool(Err.Number = 0)
         On Error Goto 0
         if not BoundColumnExist then
            Call ReleaseError(vbObjectError + 4, "Grid.Execute:
               Impossibile impostare BoundColumn.")
            Exit Sub
            end if
      end if
      
      'Recupero Numero ID
      Cont = 0
      While Request("riga_" & FormatNumberRow(Cont) &
      "xID") <> ""
         Cont = Cont + 1
      Wend
      Cont = Cont - 1
      ReDim ArrID(Cont)
      ReDim ArrInsert(Cont)
      ReDim ArrUpdate(Cont, 1)
      ReDim ArrDelete(Cont)
      Cicle = 0
      ContInsert = 0
      ContDelete = 0
      ContUpdate = 0
     
      'Recupera i parametri inviati dal client
      for Cicle = 0 to Cont
         ArrID(Cicle) = CLng(Request("riga_" &
         FormatNumberRow(Cicle) & "xID"))
         ChangeFlag = Request("riga_" &
         FormatNumberRow(Cicle) & "xCHANGE")
         if ((ArrID(Cicle) < 1) and (ChangeFlag = "1")) then
            ArrInsert(ContInsert) = Cicle
            ContInsert = ContInsert + 1
         else
            if ChangeFlag = "-1" then
               ArrDelete(ContDelete) = ArrID(Cicle)
               ContDelete = ContDelete + 1
            elseif ChangeFlag <> "-1" and ChangeFlag <> "" then
               ArrUpdate(ContUpdate, 0) = ArrID(Cicle)
               ArrUpdate(ContUpdate, 1) = Cicle
               ContUpdate = ContUpdate + 1
            end if
         end if
      next
      
      'Delete
      if Request("command") = "delete" then
         strDelete = ""
         for Cont = 0 to (ContDelete -1)
            strDelete = strDelete & " OR " & IBoundColumn & " = "
            & ArrDelete(Cont)
         next
         if strDelete <> "" then
             strDelete = "DELETE FROM " & ITableName &
             "WHERE 1 <> 1" & strDelete
            if IDataSource.State = adStateOpen then
               IDataSource.Close
            end if
            On Error Resume Next
               IDataSource.Open strDelete, IConnection, & _
               adOpenKeyset, adLockOptimistic
               ErrNum = Err.number
               ErrMsg = Err.Description
            On Error Goto 0
            if ErrNum <> 0 then
               Call ReleaseError(ErrNum, ErrMsg)
               Exit Sub
            end if
         end if
      else
         if ContInsert > 0 then
            strInsertF = "INSERT INTO " & ITableName & " ("
            'Scorro i campi escludendo il boundcolumn
            for each tmpField in IDataSource.Fields
               if tmpField.Name <> IBoundColumn then
                  strInsertF = strInsertF & tmpField.Name & ", "
               end if
            next
            strInsertF = left(strInsertF, len(strInsertF) - 2) &
            ") VALUES (" & NumF = IDataSource.Fields.Count - 2
            for Cont = 0 to (ContInsert -1)
               strInsert = strInsertF
               for ContFields = 0 to NumF
                  'Si deve migliorare verifica tipi inserimento
                  strInsert = strInsert & "'" & Request("campo_" &
                     FormatNumberRow(ArrInsert(Cont)) & "x" &
                     FormatNumberRow(ContFields)) & "', "
               next
               strInsert = left(strInsert, len(strInsert) - 2)
               & ")"
         
               if IDataSource.State = adStateOpen then
                  IDataSource.Close
               end if
               On Error Resume Next
                  IDataSource.Open strInsert, IConnection, & _
                  adOpenKeyset, adLockOptimistic
                  ErrNum = Err.number
                  ErrMsg = Err.Description
               On Error Goto 0
               if ErrNum <> 0 then
                  Call ReleaseError(ErrNum, ErrMsg)
                  Exit Sub
               end if
            next
         end if
         if ContUpdate > 0 then
            if IDataSource.State = adStateClosed then
               'Richiama la procedura di connessione al DB
               if not Connect() then
                  Exit Sub
               end if
            end if
            NumF = IDataSource.Fields.Count - 2
            Redim ArrFieldName(NumF)
            Cicle = 0
            for ContFields = 0 to IDataSource.Fields.Count - 1
               if IDataSource.Fields(ContFields).Name <>
               IBoundColumn then
                  ArrFieldName(Cicle) =
                  IDataSource.Fields(ContFields).Name
                  Cicle = Cicle + 1
               end if
            next
            for Cont = 0 to (ContUpdate -1)
               if IDataSource.State = adStateOpen then
                  IDataSource.Close
               end if
               On Error Resume Next
                  IDataSource.Open "SELECT * FROM " & ITableName
                  & " WHERE " & IBoundColumn & " = " &
                  ArrUpdate(Cont,0), IConnection,
                  adOpenKeyset, adLockOptimistic
                  ErrNum = Err.number
                  ErrMsg = Err.Description
               On Error Goto 0
               if ErrNum <> 0 then
                  Call ReleaseError(ErrNum, ErrMsg)
                  Exit Sub
               end if
               for ContFields = 0 to ubound(ArrFieldName)
                  IDataSource(ArrFieldName(ContFields)) =
                  Request("campo_"
                  & FormatNumberRow(ArrUpdate(Cont,1)) & "x" &
                  FormatNumberRow(ContFields))
               next
               On Error Resume Next
                  IDataSource.Update
                  ErrNum = Err.number
                  ErrMsg = Err.Description
               On Error Goto 0
               if ErrNum <> 0 then
                  Call ReleaseError(ErrNum, ErrMsg)
                  Exit Sub
               end if
               if IDataSource.State = adStateOpen then
                  IDataSource.Close
               end if
               'Richiama la procedura di connessione al DB
               if not Connect() then
                  Exit Sub
                end if
            next
         end if
      end if
   end if
   
End Sub
A questo punto abbiamo terminato la scrittura della classe che salveremo opportunamente in un file di inclusione separato dalla nostra pagina ASP.

Per poter visualizzare la griglia è ora sufficiente scrivere una pagina ASP come la seguente
<%@ language=VBScript %>
<% Option Explicit %>
<% Response.Expires = -1500 %>
<!--#include file="file/adovbs.inc" -->
<!--#include file="file/kosmous_grid.inc" -->

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Griglia dati in ASP</title>
<link href="file/kosmous_grid.css" rel="stylesheet">
<script language="JavaScript" src="file/kosmous_grid.js"></script>
</head>

<body>
<%

Dim KosmousGrid    'As Grid

'Istanzia la classe
Set KosmousGrid = New Grid


'Imposta la stringa di connessione
KosmousGrid.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;
   Data Source=" & Server.MapPath("file/db_dati.mdb") & ";
   Persist Security Info=False"

'Imposta il nome della tabella
KosmousGrid.TableName = "Contatti"

'Imposta la modalita' di inserimento e update
KosmousGrid.UpdateOption = ROW_UPDATE

'Esegue eventuali comandi remoti
call KosmousGrid.ExecuteRemoteCommand()

'Renderizza la tabella

call KosmousGrid.BuildTable()

'Libera la memoria
set KosmousGrid = Nothing
%>
</body>
</html>
Commenti (2)2003-06-27

11/10/2017 - allemagne_cialis scrive:
You don’t have prescription for allemagne cialis but need to get one?
29/08/2012 - LM scrive:
Bellissima Classe. E' possibile vederla all'opera in esempio online?