Lokale Datenspeicherung mit HTML5

von Veit Weber

1. Standards

Mit dem kommenden HTML5-Standard ist es möglich, Daten die von einem Webserver generiert wurden, offline im Webclient zu speichern. Damit können vom Webserver abgerufene Inhalte im Browser gespeichert und später auch ohne Internetverbindung dargestellt werden. Der Browser Safari von Apple (und alle anderen WebKit-basierten Browser, z.B. Google Chrome und der iPhone-Browser) kennen diese Möglichkeit schon länger. Ein populäres Beispiel wäre Google Mail. Mit dieser Webanwendung können E-Mails im Browser entworfen und gespeichert werden. Sobald dann eine Internetverbindung besteht und der Nutzer in den Online-Modus wechselt, wird die Mail automatisch verschickt.

Da der HTML5-Standard noch nicht verabschiedet wurde, steht auch ein endgültiger Standard für die Offlinespeicherung (Web Storage oder Indexed DB) noch nicht fest. Ich beziehe mich daher auf die WebSQL-Spezifikation, die ursprünglich von Apple in WebKit eingeführt wurde. Verbreitet ist WebSQL vor allem im Bereich von Webanwendungen, die speziell für das iPhone entwickelt wurden. Die Spezifikation wird von Apple und auch Opera voll unterstützt und weiterentwickelt. Es besteht die Tendenz, dass diese Spezifikation mit der Freigabe von HTML5 im Jahr 2014 eventuell abgelöst wird. Der Browser Firefox unterstützt, genau wie der Internet Explorer, die konkurrierende Indexed DB-Technologie.

Im folgenden Artikel möchte ich das Zusammenspiel von dokumentbasierten Domino-Daten und einer relationalen Datenbank für die Offlinefähigkeit im Browser näher beleuchten. Sollten Sie also Funktionalitäten nachbauen wollen, verwenden Sie einen der folgenden Browser:

  • Google Chrome ab Version 4
  • Apple Safari
  • Opera ab Version 10

2. Was ist WebSQL?

Die WebSQL-Spezifikation erlaubt es eine SQLite-Datenbank in den Browser zu integrieren, die dann via JavaScript und SQL abgefragt werden kann. Die Daten dieser SQL-Datenbank werden im Cache des Browser gehalten. Wenn der Cache gelöscht wird, gehen also auch die Daten der Datenbank verloren. WebSQL erlaubt eine maximale Datenbankgröße von 5 MB. Fast alle SQLite-Befehle funktionieren auch mit WebSQL.

3. Unser Beispiel

In diesem Artikel möchte ich die Termine eines Nutzers aus dem ULC.Gruppenkalender offline im Browser speichern. Dazu benötige ich eine Ansicht, die nach Nutzer kategorisierte Termine enthält. Diese Ansicht wird mit einer JavaBean eingelesen und liefert die Termine des entsprechenden Nutzers in einem JSON-Array zurück. Ich verwende dafür das freie json-simple, da ich damit gute Erfahrungen gemacht habe. Natürlich können Sie die JSON-Daten auch direkt aus der Ansicht auslesen. Dieses JSON-Array speichere ich in einer XPage mit folgendem Layout:

Die ersten beiden Felder sind für die Authentifizierung am Domino-Server gedacht. In Feld Nummer drei kann der Nutzer auswählen, welche Termineinträge er haben möchte. Mit dem "Get Data"-Button wird der JSON-String ausgelesen. Der dahinter liegende Code sollte also ungefähr so aussehen:

importPackage(com.ulc.groupcal.webservice);

var wsBean = new GroupCalWsConsumerBean();
var appValues = wsBean.getUserAppointmentData(getComponent("itUsername").getValue(), getComponent("itPwd").getValue(), getComponent("itPersonApp").getValue());

getComponent("itApps").setValue(appValues);

Nun sollte das versteckte Feld „itApps“ das JSON-Array enthalten. Dieses JSON-Array wird jetzt durchlaufen und in der Datenbank gespeichert. Vorher jedoch muss die Datenbank vorbereitet werden. Dafür bietet sich der clientseitige onClientLoad-Event der XPage an. In diesem Event wird auch gleich die Referenz auf das Datenbankobjekt initialisiert.

var  sqliteDb = null; // globale Referenz

/**
 *  Pruefen ob DB-Funktionalität möglich, wenn ja: DB initialsiieren
 */
function  init()
{
 	// browser kann daten in sql-lite db speichern?
 	try
	{
 		if (!window.openDatabase)
		{
 			sysout("Browser does not support WebSQL" + e, "ERROR");
 		} else
		{
			initSqliteDb();
			createTables();
			displayOfflineData();
		}
 	} catch (e)
	{
 		sysout("Database Error: " + e, "ERORR");
 		return;
	}
}

Schauen wir uns als erstes die Methode „initSqliteDb()“ an. In dieser Methode wird die Referenz auf die Datenbank initialisiert.

/**
 * sqlite-DB initialisieren und Referenz übergeben
 */
function  initSqliteDb()
{
  var  shortName = 'ULC.GroupCalDb';
  var  version = '1.0';
  var  displayName = 'ULC.Gruppenkalender - Offline DB';
  var  maxSize = 1048576; // in bytes
  sqliteDb = window.openDatabase(shortName, version, displayName, maxSize);
}

Sollte es die Datenbank mit dem übergebenen Kurznamen nicht geben, wird der Browser diese Datenbank automatisch erstellen. Jetzt, da die Referenz vorhanden ist, können die Tabellen in der Datenbank erstellt werden. Dies wird in der Funktion „createTables()“ realisiert.

/**
 * Tabellen erzeugen
 */
function  createTables()
{
  var  sqlCommand = ""

  try
  {
     sqlCommand += "CREATE TABLE IF NOT EXISTS groupcal";
    sqlCommand += "(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,";
    sqlCommand += "subject VARCHAR NOT NULL,";
    sqlCommand += "StartDateTime VARCHAR NOT NULL);";
    sqliteDb.transaction(function(transaction)
    {
      transaction.executeSql(sqlCommand, [], nullDataHandler, errorHandler);
      sysout("Tables successfully created");
    } );
  } catch  (e)
  {
     sysout("Could not create tables:" + e, "ERROR");
    return
  }
}

Im OnClick-Event des „Start Sync“-Buttons werden Tabellen mit den Daten aus dem JSON-Array gefüllt. Dazu wird das Array durchlaufen, die einzelnen Werte ausgelesen und über einen SQL-Statement in die Datenbank geschrieben.

/**
 * Notesdaten in die Tabelle uebertragen
 */
function  insertTableData(appValues)
{
  var  data;
  var  sqlCommand = "";

  try
  {
     sqliteDb.transaction(function(transaction)
    {
      data = parseJson(appValues.value);

      for  (i = 0; (i < data.length && i < 20); i++)
      {
        var  subject = data[i].subject;
        var  EndDateTime = data[i].EndDateTime;

        sqlCommand = "insert into groupcal (StartDateTime, subject) VALUES ('" + EndDateTime + "', '" + subject + "'); ";
        transaction.executeSql(sqlCommand, [], function(transaction, results)
        {
          if  (!results.rowsAffected)
          {
             sysout("No rows affected");
          }
        } , errorHandler);
      }
    } );
  } catch  (e)
  {
     document.getElementById('status').innerHTML = "[ERROR] Unable to perform an INSERT " + e + ".";
  }
}

Nun können die Daten aus den Tabellen ausgelesen werden. Dies wird über die Funktion „displayOfflineData()“ realisiert. Diese Funktion wird nach dem Event für das Füllen der Tabellen aufgerufen. Damit aber eventuell schon vorhandene Daten in der Datenbank nach dem Aufruf der Seite gerendert werden, kann diese Funktion auch gleich in der „init()“-Funktion aufgerufen werden.

function displayOfflineData()
{
 	var query;
 	query = "SELECT * FROM groupcal";

 	try
	{
 		sqliteDb.transaction(function(transaction)
		{
 			transaction.executeSql(query, [], function(transaction, results)
			{
 				for ( var i = 0; i < results.rows.length; i++)
				{

 					var row = results.rows.item(i);
 					var tr = document.createElement("tr");

 					var tdId = document.createElement("td");
 					var idText = document.createTextNode(row['id']);
					tdId.appendChild(idText);
					tr.appendChild(tdId);

 					var tdFn = document.createElement("td");
 					var fnText = document.createTextNode(row['subject']);
					tdFn.appendChild(fnText);
					tr.appendChild(tdFn);

 					var tdLn = document.createElement("td");
 					var lnText = document.createTextNode(row['StartDateTime']);
					tdLn.appendChild(lnText);
					tr.appendChild(tdLn);

 					document.getElementById("appData").appendChild(tr);
				}
 			}, function(transaction, error)
			{
 				// todo:sysout
			});
		});
 	} catch (e)
	{
 		// todo:sysout
	}
}

Die Funktion selbst ist recht schnell entwickelt. Als erstes wird das SQL-Statement vorbereitet, mit dem alle Datensätze ausgelesen werden. Diese werden im Resultset iteriert und bei jedem Durchlauf eine Tabellenzeile gerendert. Das Ergebnis ist dann Folgendes:

erstellt am 13.07.2011 - bewertet mit 5 von 5
Abgelegt unter: Datenbank, JavaScript, Web, Webservices, Xpages, HTML5, JSON

Bewerten Sie diesen Artikel:


Senden Sie einen Kommentar

Name
E-Mail
Kommentar
FrageGATY-9828N5 (bitte diesen Wert in unten stehendes Feld eintragen)
Antwort