Neue Webtechnologien wie HTML5 & CSS3 üben einen geradezu magischen Reiz für Webentwickler aus. In Internet Explorer 9 baut Microsoft stabile Elemente aus den sich noch in Entwicklung befindlichen W3C-Spezifikationen ein. Für experimentelle Teile, die trotz des frühen Entwicklungstandes schon großes Interesse hervorrufen, stellen wir Prototypen auf unseren HTML5 Labs als Diskussions- und Feedbackgrundlage zur Verfügung.
Viele Webentwickler wollen hier ganz vorn mit dabei sein und bauen eifrig Beispielseiten zur Veranschaulichung der neuen Funktionen. Dabei wird manchmal übersehen, dass HTML5 & Co. noch lange nicht fertig sind, sondern die einzelnen Bestandteile der Spezifikationen unterschiedliche Entwicklungstände aufweisen. Die Weiterentwicklung führt zu teils gravierenden Änderungen, bei denen Abwärtskompatibilität nicht garantiert wird.
Für das heutige Posting greife ich einmal ein Beispiel von Constant Dullaart auf. Auf therevolvinginternet.com animiert er (offensichtlich inspiriert von Chris Collins Uneven Google) die Startseite von Google mit Hilfe von CSS Transforms. Es ist gar nicht so leicht, eine Suche einzugeben, wenn sich das Fenster die ganze Zeit dreht ;-)
Was fiel mir nun daran auf? Als erstes ist das Ergebnis je nach verwendetem Browser unterschiedlich. In Chrome, Firefox, Safari und Opera bekommt man die Demo angezeigt, wobei Firefox ohne Ton läuft (zu dem Warum? komme ich später). Nutzer des Internet Explorers werden jedoch mit der Meldung “This website does not function properly in microsoft's internet explorer browser (internet explorer is not fully compliant with css 3 and new developments in this field).” ausgegrenzt:
Zusätzlich erfordert die Browsercheck-Seite Adobe Flash, da ein Video eingebunden ist, welches dem Anwender die eigentliche Funktion der Webseite illustrieren soll. Das ist nur konsequent: Anwendern, deren Browser CSS Transforms nicht unterstützt, werden wohl auch kein HTML5 Videotag verarbeiten können.
Gewundert habe ich mich allerdings, als ich die Seite mit einer neueren IE9 Platform Preview aufrief. Wir unterstützen seit IE9 PP6 auch CSS Transforms – allerdings funktioniert trotzdem die Demo nicht. Auch IE9 PP7 wird als Browser ausgegrenzt:
Da läuft doch etwas schief. Was ist hier los? Der Entwickler der Seite unterscheidet, mit welchem Browser Anwender die Seite besuchen und liefert unterschiedliche Inhalte aus. Jedoch ermittelt er dabei nicht die Fähigkeiten des Browsers, sondern grenzt mit einer schlichten Browserweiche alle Versionen von Microsoft Internet Explorer aus:
<script language="JavaScript" type="text/JavaScript"> if(navigator.appName == "Microsoft Internet Explorer") { window.location = "browsercheck.html" } </script>
Genau diese Art des Einsatzes von Browserweichen verhindert jedoch die Weiterentwicklung des Webs. Was im Juli 2010 noch stimmte (IE9 unterstützte zu diesem Zeitpunkt CSS Transforms noch nicht), hat sich wenige Wochen später schon geändert. IE9 ≥PP6 verfügt über die notwendige Funktionalität. Hätte die Prüfung auf die Funktion abgehoben und nicht auf den Browsernamen, müsste man nichts verändern. Die Seite würde mit neueren Browserversionen einfach funktionieren.
Auf dieses Problem stoße ich immer wieder bei Beschwerden, dass eine Seite nicht mit IE9 funktionieren würde. Vor allem ist es nicht wirklich neu – schon bei IE8 hatten viele Webmaster Probleme mit ihren Browserweichen. Leider entschieden sich viele, ihre kaputte Weiche einfach um einen Test auf IE8 hin zu erweitern und laufen nun bei IE9 in die gleiche Falle.
Macht kaputte Browserweichen nicht noch kaputter! Prüft lieber auf Funktionen und nicht auf Browsernamen! Tools wie zum Beispiel Modernizr können dabei helfen, wenn einem Funktionstests zu kompliziert sind. Das Ergebnis ist viel besser für alle Anwender im Web.
Ich nehme mal das obige Beispiel und schaue, wie es sich verbessern lässt. Im ersten Schritt kommentiere ich die kaputte Browserweiche aus:
<!-- <script language="JavaScript" type="text/JavaScript"> if(navigator.appName == "Microsoft Internet Explorer") { window.location = "browsercheck.html" } </script> -->
Als nächstes “repariere” ich die Zeile mit den CSS Transforms, so dass neben dem eigentlichen CSS-Kommando 'transform' (welches noch kein Browser unterstützt) und den Prefixversionen für Chrome & Safari ('WebkitTransform'), Firefox ('MozTransform') und Opera ('OTransform') auch 'msTransform' für Internet Explorer mit aufgenommen wird:
var properties = ['transform', 'msTransform', 'WebkitTransform', 'MozTransform' , 'OTransform'];
Diese kleine Änderung reicht aus, damit das Beispiel auch in IE9 ≥PP6 läuft:
Was macht man nun aber mit Browsern, die CSS Transforms nicht unterstützen? Zum Beispiel kann man einfach an das if ein else anhängen, welches dann eine andere Seite präsentiert:
if (property) { var d = 0; setInterval(function () { div.style[property] = 'rotate(' + (d++ % 360) + 'deg)';}, 100); } else { window.location = "browserweiche.html" }
Kommen wir zum Schluss noch auf das Problem, dass mit Firefox kein Ton zu hören ist. Man mag es kaum glauben, aber diese Webstandard-Demo funktioniert nur, wenn man Flash installiert hat. Habe ich etwas verpasst? Ist Flash jetzt auch vom W3C als Webstandard anerkannt worden? Oder hat Constant Dullaart Workarounds für alle Browser eingebaut, die nach seinen Worten “not fully compliant with … new developments in this field” sind und nicht Microsoft Internet Explorer heißen?
Die Hintergrundmusik ist The Windmills Of Your Mind interpretiert von Dusty Springfield. Meines Wissens ist der Song nicht wirklich frei aber Constant Dullaart hat ihn auf seinem Webserver liegen und nutzt ihn als MP3-Datei in seiner Demo. Wie kommt jetzt Flash hier ins Spiel? In der Webstandard-Demo (!) wird zum Abspielen der Musik nicht das HTML5 <audio> Tag genutzt, sondern ein Flashplayer als Wrapper um die MP3-Datei:
<script type="text/javascript" src="swfobject.js"></script> <div id="player"></div> <script type="text/javascript"> var so = new SWFObject('player.swf','mpl','0','0','9'); so.addParam('allowscriptaccess','always'); so.addParam('allowfullscreen','true'); so.addParam('flashvars','&duration=149&file=Dusty_Springfield_The Windmills_Of_Your_Mind.mp3&autostart=true&repeat=always'); so.write('player'); </script>
Ich vermute, dass Constant sich mit diesem Workaround behilft, da nicht alle modernen Webbrowser MP3-Dateien in HTML5 <audio> Tags abspielen können. Zum Beispiel unterstützt Firefox hier nur “offene” Standards. Mozilla will anscheinend, dass Anwender die überwältigende Mehrzahl an Musikstücken nicht nutzen können, wenn sie Firefox einsetzen. Genauso wie Google zukünftig H.264 ausgrenzt.
Es sei denn, man setzt das nicht gerade “offene” Flash ein, welches Google in Chrome weiterhin unterstützt. Ich musste schon ein wenig schmunzeln, als ich diese Inkonsequenz sah. Was bei Microsoft nicht implementiert wird, wird ausgegrenzt. Was bei anderen fehlt, da baut man einen Workaround ;-)
Ist das wirklich im Sinne aller Anwender? Weniger Wahl statt mehr? Wir sprechen doch auch nicht alle Esperanto, wie mein Kollege Tim Sneath in seinem Blogpost An Open Letter from the President of the United States of Google scherzhaft vorschlägt.
Also ändere ich zum Schluss noch den Teil für die Hintergrundmusik. Es ist im Grunde ganz einfach. Als erstes wieder den Flashplayer auskommentieren:
<!-- <script type="text/javascript" src="swfobject.js"></script> <div id="player"></div> <script type="text/javascript"> var so = new SWFObject('player.swf','mpl','0','0','9'); so.addParam('allowscriptaccess','always'); so.addParam('allowfullscreen','true'); so.addParam('flashvars','&duration=149&file=Dusty_Springfield_The Windmills_Of_Your_Mind.mp3&autostart=true&repeat=always'); so.write('player'); </script> -->
Dann ein <audio>-Tag hinzufügen und für Firefox noch eine Kopie des Songs im OGG-Format ablegen:
<audio controls="none" autoplay="true" loop="true"> <source src="Dusty_Springfield_The Windmills_Of_Your_Mind.mp3" /> <source src="Dusty_Springfield_The Windmills_Of_Your_Mind.ogg" /> This browser does not support HTML5 MP3 or OGG audio files. </audio>
Schon funktioniert das Ganze prima auch ohne Flash und ist eine wahrhaftige Webstandard-Demo. Wer die von mir veränderte Version einmal ausprobieren möchte, kann diese unter der URL techfiles.de/therevolvinginternet/html5.html aufrufen. Da ich aber die Verwendung des Songs von Dusty Springfield rechtlich nicht bewerten kann, habe ich eine eigene Demo mit einem Song, der unter CC Attribution steht, gebaut: techfiles.de/therevolvinginternet
Have fun! Daniel
Bei meinem Kollegen Oliver Scheer gerade gesehen: Leisure-Suit Larry gibt es jetzt in HTML5:
http://www.sarien.net/larry
Es ist schon beeindruckend, was mit den neuen Webtechnologien so alles machbar ist. Schon mal ein iPad, ein NES oder einen GameBoy im Webbrowser gesehen? Hoffentlich gleichen sich die Implementierungen der neuen Technologien in den modernen Browsern zukünftig soweit an, dass solche Demos wirklich in allen Browsern laufen.
Von meinem Kollegen Oliver Scheer (Developer Evangelist & Technology Fan) bekam ich gerade Infos zu folgendem interessanten Wettbewerb rein:
An alle Windows 7 Maschinisten und solche die es werden wollen. Gewinn eine unvergessliche Reise nach Las Vegas!
Dein Auftrag
Du bist der Maschinist und nur du kennst die Maschine wirklich. Welche Funktion suchst du in deinem System vergeblich? Die Mäusejagd, das Schneetreiben oder etwas ganz Anderes? Stell dich der Herausforderung und baue deine eigene verrückte Wunsch-Anwendung. Dabei sollte mindestens eine der vielen neuen Windows 7 Funktionen verwendet werden. Lade dir dein Werkzeug kostenlos herunter und zeig wie kreativ du bist. Wie dein Windows 7 aussehen soll – das kannst du jetzt selbst bestimmen.
Die Anforderungen:
Dein Lohn
Wir wissen wie viel Sorgfalt und Herzblut in so einem kurzen Code stecken kann, deshalb möchten wir deinen Einsatz entsprechend belohnen. Den drei Gewinnern mit den kreativsten Desktop-Anwendungen schenken wir die perfekte Auszeit. Mit diesen Hauptpreisen können alle hart arbeitenden Maschinisten richtig abschalten:
Die Hauptgewinne der ersten drei Plätze sind je ein:
Großer Las Vegas Gambling-Trip mit Besuch der MIX und Helikopterflug.
Dieser Trip besteht aus:
Unsere Jury prüft alle eingehenden Applikationen ausführlich und stellt die besten Arbeiten online vor. Anschließend geben sie die Gewinner der drei Hauptpreise sowie die der anderen tollen Gewinne bekannt.
Deine Werkbank
Wir haben dir deine Werkbank schon eingerichtet – jetzt heißt es nur noch: downloaden, in die Hände spucken und sofort loslegen. Auf der kostenfreien Werkbank findest du das Codeplex Projekt Windows 7 To Go mit über 60 Beispielen zu Funktionen von Windows 7, Internet Explorer und Silverlight. Nutze die Sourcecodes und Bibliotheken um einzelne Funktionen direkt in deine Anwendung einzubauen. Hier findest du alle Werkzeuge, die du brauchst – ganz einfach und kostenlos zum Downloaden.
Dein Werkstück
Deine Arbeit sollte aus möglichst wenigen Zeilen Code bestehen – denn kurze Codes bekommen eine höhere Chance zu gewinnen. Darüber hinaus muss deine Desktop Anwendung folgende Merkmale aufweisen:
Programmiert für Windows 7
Wir wünschen dir viel Spaß beim Bauen und freuen uns schon auf verrückte Ideen und kreative Arbeiten.
Einsendeschluss
28.02.2011
Weitere Informationen und Teilnahmebedingungen
http://www.windows7.de/maschine
Die nächste Generation von Microsoft Windows wird System-on-a-Chip (SoC)-Architekturen von Intel und AMD wie auch ARM-basierte Systeme von NVIDIA, Qualcomm und Texas Instruments unterstützen. Dies hat Microsoft am späten Mittwochabend deutscher Zeit auf einer Pressekonferenz im Rahmen der CES in Las Vegas angekündigt. Die herkömmliche .x86 Architektur der Intel und AMD Chips kommt primär in PCs und Laptops zum Einsatz. ARM Chips werden dahingegen hauptsächlich in Smartphones und Tablet-PCs benutzt.
Zielsetzung ist es, Windows zukünftig in seinem vollen Leistungsumfang auf möglichst vielen Endgeräten verfügbar zu machen. Insbesondere mobile Geräte müssen heute auf Leistungsmerkmale eines modernen Betriebssystems verzichten. Microsoft präsentierte im Rahmen der Pressekonferenz bereits Anwendungsbeispiele mit SoC Endgeräten: Hardwarebeschleunigung für Grafik oder Webbrowsing mit dem Internet Explorer 9, Unterstützung von USB-Schnittstellen oder das Drucken von mobilen Geräten aus – die Leistungsfähigkeit des PCs steht zukünftig auch mobilen Geräten zur Verfügung. Als weiteres Beispiel zeigte Microsoft ARM-basierte Geräte, die das Office Paket im vollen Leistungsumfang verfügbar machen.
Steve Ballmer, CEO von Microsoft, erklärt: „Windows 7 läuft heute bereits auf vielen verschiedenen Hardwareformen: von neuen Tablet-PCs bis hin zu High-End Spielekonsolen. Wir stehen nun am Anfang einer neuen, spannenden Technologie-Ära, in der Windows auf allen Geräten, von kleinen mobilen Endgeräten bis hin zu Big Screens läuft. Dank der SoC-Unterstützung wird Windows Anwendern uneingeschränkt alles bieten, was sie sich wünschen: Spiele, TV, Kino, Musik, Produktivität und Social Networking.”
Weitere Informationen sowie ein Q&A:
Transkript der CES Keynote von Steve Ballmer:
Gastposting Dariusz Parys, Developer Evangelist für .NET 3.0 Technologie, C++, High Performance Computing, Windows Communication Foundation, Workflow Foundation und Visual Studio Team System bei der Microsoft Deutschland GmbH.
Mit den HTML5 Labs stellen wir für interessierte Entwickler Prototyp-Implementierungen von noch nicht ausgereiften W3C HTML5 Spezifikationen bereit. Ich habe mir mal die IndexedDB Implementierung angeschaut und dabei die Frage aufgegriffen, wie man die JavaScript-Funktionalität des Internet Explorers erweitern kann. Die Antwort lautet Component Object Model oder kurz COM.
Die Implementierung kommt mit zwei COM-Objekten daher, welche unter der Haube den Microsoft SQL-Server Compact 4 mit einer synchronen und mit einer asynchronen Schnittstelle benutzen. Die Installation des Prototypen ist sehr einfach. Zuerst lädt man sich das Paket herunter. Danach registriert man in einer Eingabeaufforderung als Administrator die entsprechende COM-DLL mittels regsvr32:
regsvr32 sqlcejse40.dll
Nach der Registrierung muss man den IE9 allerdings auch priviligiert starten, um mit dem Prototyp zu arbeiten.
Zum Start benötigt man immer ein Initialisierungsskript. Schließlich ist die Funktionalität in der COM-Komponente enthalten und wir möchten das ganze aus JavaScript heraus nutzen. Das Paket kommt mit einem solchen Skript und nennt sich bootidb.js. Ein Blick in die entsprechende Codezeile verrät uns, dass auf dem window-Element entsprechend eine Eigenschaft indexedDB hinzugefügt wird:
1: if (!window.indexedDB) { 2: window.indexedDB = new ActiveXObject("SQLCE.Factory.4.0"); 3: window.indexedDBSync = new ActiveXObject("SQLCE.FactorySync.4.0");
1: if (!window.indexedDB) {
2: window.indexedDB = new ActiveXObject("SQLCE.Factory.4.0");
3: window.indexedDBSync = new ActiveXObject("SQLCE.FactorySync.4.0");
Die Möglichkeit, jedes IDispatch-fähige Objekt in das HTML-DOM des Browser zu stecken, war mir so nie bewusst. Schaut man sich einmal die Typelibrary der COM-Objekte an, so sieht man die Methoden, welche man schließlich aus JavaScript heraus direkt benutzen kann:
1: [ 2: odl, 3: uuid(567270C8-E61A-4A8D-9C2E-2D2EC47D8704), 4: helpstring("ObjectStore - Asynchronous interface"), 5: dual, 6: nonextensible, 7: oleautomation 8: ] 9: interface IDBObjectStoreRequest : IDBObjectStore { 10: [id(0x60040000)] 11: HRESULT put( 12: [in] VARIANT value, 13: [in, optional] VARIANT key, 14: [out, retval] IDBRequest** ppWebRequest); 15: [id(0x60040001)] 16: HRESULT add( 17: [in] VARIANT value, 18: [in, optional] VARIANT key, 19: [out, retval] IDBRequest** ppWebRequest); 20: [id(0x60040002)] 21: HRESULT get( 22: [in] VARIANT key, 23: [out, retval] IDBRequest** ppWebRequest); 24: [id(0x60040003)] 25: HRESULT remove( 26: [in] VARIANT key, 27: [out, retval] IDBRequest** ppWebRequest); 28: [id(0x60040004)] 29: HRESULT createIndex( 30: [in] BSTR name, 31: [in] BSTR keyPath, 32: [in, optional] VARIANT unique, 33: [out, retval] IDBRequest** ppWebRequest); 34: [id(0x60040005)] 35: HRESULT index( 36: [in] BSTR indexName, 37: [out, retval] IDBRequest** ppWebRequest); 38: [id(0x60040006)] 39: HRESULT deleteIndex( 40: [in] BSTR indexName, 41: [out, retval] IDBRequest** ppWebRequest); 42: [id(0x60040007)] 43: HRESULT openCursor( 44: [in, optional] VARIANT range, 45: [in, optional] VARIANT direction, 46: [out, retval] IDBRequest** ppWebRequest); 47: }; 48:
1: [
2: odl,
3: uuid(567270C8-E61A-4A8D-9C2E-2D2EC47D8704),
4: helpstring("ObjectStore - Asynchronous interface"),
5: dual,
6: nonextensible,
7: oleautomation
8: ]
9: interface IDBObjectStoreRequest : IDBObjectStore {
10: [id(0x60040000)]
11: HRESULT put(
12: [in] VARIANT value,
13: [in, optional] VARIANT key,
14: [out, retval] IDBRequest** ppWebRequest);
15: [id(0x60040001)]
16: HRESULT add(
17: [in] VARIANT value,
18: [in, optional] VARIANT key,
19: [out, retval] IDBRequest** ppWebRequest);
20: [id(0x60040002)]
21: HRESULT get(
22: [in] VARIANT key,
23: [out, retval] IDBRequest** ppWebRequest);
24: [id(0x60040003)]
25: HRESULT remove(
26: [in] VARIANT key,
27: [out, retval] IDBRequest** ppWebRequest);
28: [id(0x60040004)]
29: HRESULT createIndex(
30: [in] BSTR name,
31: [in] BSTR keyPath,
32: [in, optional] VARIANT unique,
33: [out, retval] IDBRequest** ppWebRequest);
34: [id(0x60040005)]
35: HRESULT index(
36: [in] BSTR indexName,
37: [out, retval] IDBRequest** ppWebRequest);
38: [id(0x60040006)]
39: HRESULT deleteIndex(
40: [in] BSTR indexName,
41: [out, retval] IDBRequest** ppWebRequest);
42: [id(0x60040007)]
43: HRESULT openCursor(
44: [in, optional] VARIANT range,
45: [in, optional] VARIANT direction,
46: [out, retval] IDBRequest** ppWebRequest);
47: };
48:
Wichtig: Diese Registrierung ist nur notwendig für die Prototyp-Implementierung. Wenn die IndexedDB-Spezifikation einen stabilen Zustand erreicht und die Funktionalität in den Browser aufgenommen wird, so wird die Implementierung Bestandteil des Browsers und kann somit auch direkt aus JavaScript heraus genutzt werden.
Nach der Installation können wir die Funktionalität direkt nutzen. Im Paket sind neben den Readme’s und der DLL auch Beispiele enthalten. Eines der Beispiele ist ein BugTracking-System. Lädt man die Seite nun lokal in den Browser, so kann man direkt Bugs erfassen und danach auch wieder suchen:
Das untere Bild zeigt, wie man nach dem “Test Projekt” sucht und die Ergebnisse angezeigt bekommt:
Die Daten werden letztlich in einer SQL CE Datebank als serialisiertes JSON (JavaScript Object Notation) abgelegt. Mittels eines SQL-Server Management Studios kann man auch einen Blick in die Datenbank werfen. Diese findet man unter %localappdata%\Microsoft\IndexedDatabase. Man öffnet diese einfach über den Dialog des Management Studios und schon hat man Zugriff auf die Datenbank:
Aufgrund der Binärfelder, die im Schema benutzt werden, kann man allerdings ohne Konvertierungen die Werte nicht direkt einsehen:
Die Funktionalität der BugTracker-Beispielanwendung ist schnell ausprobiert, doch jetzt möchte ich selbst mal eine Datenbank anlegen und Daten speichern. Zuerst einmal entscheide ich mich für die asynchrone Variante der API, da jeder synchrone Aufruf die Browseranfrage blocken kann. Das möchte man in der Regel nicht, da die Webseite sonst - wie sagt man so schön - nicht “responsive” ist. Eine einfache TODO Liste soll hier als Beispiel dienen. Zuerst definiere ich das UI:
1: <!DOCTYPE html> 2: 3: <html lang="en"> 4: <head> 5: <meta charset="utf-8" /> 6: <title></title> 7: </head> 8: <body> 9: <input type=text id=todoitem /> 10: <input type=submit value=add id=actionAdd /> 11: <div id=todos></div> 12: </body> 13: </html>
1: <!DOCTYPE html>
2:
3: <html lang="en">
4: <head>
5: <meta charset="utf-8" />
6: <title></title>
7: </head>
8: <body>
9: <input type=text id=todoitem />
10: <input type=submit value=add id=actionAdd />
11: <div id=todos></div>
12: </body>
13: </html>
Um IndexedDB zu benutzen, brauche ich das Init-Script. Zudem setze ich einen Handler auf den Submit-Button, der aufgerufen wird, sobald dieser gedrückt wird:
1: <script type="text/javascript" src="bootidb.js"></script> 2: <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.4.min.js"></script> 3: <script type="text/javascript"> 4: $(document).ready( function() 5: { 6: $("#actionAdd").click(function() { 7: }); 8: }); 9: </script>
1: <script type="text/javascript" src="bootidb.js"></script>
2: <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.4.min.js"></script>
3: <script type="text/javascript">
4: $(document).ready( function()
5: {
6: $("#actionAdd").click(function() {
7: });
8: });
9: </script>
Der Rahmen ist gelegt; nun geht es um die Daten. In dem mitgeliefertem Beispiel wird ein JavaScript-Objekt erzeugt, welches Variablen und Funktionen vorhält. Ich habe mich an diese Vorgehensweise gehalten, da es bei asynchronen Aufrufen durchaus sinnvoll ist, alles in einander wiederzuverwenden.
Die eigentliche Implementierung bedient sich auch ein paar Hilfsfunktionen, aber im Großen und Ganzen läuft es auf diesen Code zum Erstellen der Datenbank hinaus:
1: requestDatabase = window.indexedDB.open(todoDB.db, 'Todo Database');
Das Öffnen der Datenbank legt auch gleichzeitig diese an, falls sie noch nicht vorhanden ist. Nun muss man prüfen, ob die Tabelle, mit der man arbeiten möchte, schon erzeugt ist. Die Prüfung ist im Gesamtcode zum Blogpost enthalten. Ich möchte hier nur noch aufzeigen, wie man letztlich die Tabelle anlegt und gleich noch einen Index hinzufügt:
1: requestObjectStore = database.createObjectStore( 2: todoDB.objectStore, 3: todoDB.keyPath, 4: true ); 5: requestObjectStore.onsuccess = function(evt) 6: { 7: store = evt.result; 8: requestIndex = store.createIndex( 9: todoDB.indexTodo, 10: todoDB.indexTodoKeyPath, 11: false); 12: requestIndex.onsuccess = function(evt) 13: { 14: whenReady(); 15: } 16: }
1: requestObjectStore = database.createObjectStore(
2: todoDB.objectStore,
3: todoDB.keyPath,
4: true );
5: requestObjectStore.onsuccess = function(evt)
6: {
7: store = evt.result;
8: requestIndex = store.createIndex(
9: todoDB.indexTodo,
10: todoDB.indexTodoKeyPath,
11: false);
12: requestIndex.onsuccess = function(evt)
13: {
14: whenReady();
15: }
16: }
Zur Vereinfachung habe ich die onerror-Methoden entfernt. Zeile 1 legt die Tabelle an, Zeile 8 den entsprechenden Index auf ein Tabellenfeld. Benutzt man einen Index, hat man die Möglichkeit, mit dem Cursor gezielt zu arbeiten und bei einer Suche auch nur Teilbereiche zu ermitteln. Anders ist es, wenn man keinen Index hat. Da macht man einfach einen Tablescan, bis man den gesuchten Datensatz hat.
Auf der anderen Seite bietet die IndexedDB auch die Möglichkeit, immer über den Primärschlüssel direkt auf einen Datensatz zuzugreifen. Zum Beispiel ein Datensatz mit dem Primary Key ID 723 kann mittels
store.get(723)
geholt werden. ISAM Datenbanken lassen grüßen. Nun der vollständige Code einer einfachen HTML5 TODO List:
1: <!DOCTYPE html> 2: 3: <html lang="en"> 4: <head> 5: <meta charset="utf-8" /> 6: <title></title> 7: <meta http-equiv="X-UA-Compatible" content="IE=8" /> 8: <script type="text/javascript" src="bootidb.js"></script> 9: <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.4.min.js"></script> 10: <script type="text/javascript"> 11: 12: function commitTransaction(txn) 13: { 14: try 15: { 16: if (txn) 17: { 18: txn.oncomplete = function () 19: { 20: if (txn.db) 21: txn.db.close(); 22: } 23: txn.commit(); 24: } 25: } 26: catch (e) 27: { 28: output_trace("Error in commitTransaction(): " + e.message); 29: } 30: } 31: 32: function abortTransaction(txn) 33: { 34: try 35: { 36: if (txn) 37: { 38: txn.onabort = function () 39: { 40: if (txn.db) 41: txn.db.close(); 42: } 43: txn.abort(); 44: alert('transaction aborted'); 45: } 46: } 47: catch (e) 48: { 49: output_trace("Error in abortTransaction(): " + e.message); 50: } 51: } 52: 53: function output_trace(message) 54: { 55: var trace = $("#trace"); 56: if ((trace != null ) || (trace != undefined)) 57: trace.html(message); 58: } 59: 60: Array.prototype.contains = function(obj) { 61: try 62: { 63: var i = this.length; 64: while (i--) { 65: if (this[i] === obj) { 66: return true; 67: } 68: } 69: return false; 70: } 71: catch (e) 72: { 73: output_error("Error in Array.prototype.contains(): " + e.message); 74: } 75: }; 76: 77: 78: var todoDB = {}; 79: 80: todoDB.db = "todolist"; 81: todoDB.objectStore = "todos"; 82: todoDB.keyPath = "id"; 83: todoDB.indexTodo = "todo"; 84: todoDB.indexTodoKeyPath = "todo"; 85: todoDB.searchResults = []; 86: 87: todoDB.addTodo = function( todoitem ) 88: { 89: var database = null; 90: try 91: { 92: var requestDatabase = null; 93: var requestObjectStore = null; 94: var requestPut = null; 95: var requestIndex = null; 96: var store = null; 97: var transaction = null; 98: 99: requestDatabase = window.indexedDB.open(todoDB.db, 'Todo Database'); 100: requestDatabase.onsuccess = function( evt ) 101: { 102: database = evt.result; 103: var whenReady = function() 104: { 105: transaction = database.transaction(); 106: var objectStore = transaction.objectStore(todoDB.objectStore); 107: var newEntry = { todo: todoitem }; 108: requestPut = objectStore.put(newEntry); 109: 110: requestPut.onsuccess = function(evt) 111: { 112: commitTransaction(transaction); 113: } 114: requestPut.onerror = function(evt) 115: { 116: output_trace("requestPut.onerror = " + evt.message); 117: abortTransaction(transaction); 118: } 119: }; 120: 121: if( database.objectStoreNames.contains(todoDB.objectStore) ) 122: { 123: whenReady(); 124: } 125: else 126: { 127: requestObjectStore = database.createObjectStore( 128: todoDB.objectStore, 129: todoDB.keyPath, 130: true ); 131: requestObjectStore.onsuccess = function(evt) 132: { 133: store = evt.result; 134: requestIndex = store.createIndex( 135: todoDB.indexTodo, 136: todoDB.indexTodoKeyPath, 137: false); 138: requestIndex.onsuccess = function(evt) 139: { 140: whenReady(); 141: } 142: requestIndex.onerror = function(evt) 143: { 144: output_trace("requestIndex.onerror = " + evt.message); 145: abortTransaction(transaction); 146: } 147: } 148: requestObjectStore.onerror = function(evt) 149: { 150: output_trace("requestObjectStore.onerror = " + evt.message); 151: abortTransaction(transaction); 152: } 153: } 154: } 155: requestDatabase.onerror = function(evt) 156: { 157: output_trace("requestDatabase.onerror = " + evt.message); 158: abortTransaction(transaction); 159: } 160: } 161: catch (e) 162: { 163: output_trace("catch(e) = " + e.message); 164: if(database) 165: { 166: database.close(); 167: } 168: } 169: } 170: 171: todoDB.findTodos = function () { 172: 173: var database = null; 174: try 175: { 176: var requestDatabase = null; 177: var requestObjectStore = null; 178: var requestIndex = null; 179: var requestCursor = null; 180: var requestMove = null; 181: var store = null; 182: var cursor = null; 183: var transaction = null; 184: var index = 1; 185: 186: requestDatabase = window.indexedDB.open(todoDB.db, 'Todo Database'); 187: requestDatabase.onsuccess = function (evt) 188: { 189: database = evt.result; 190: transaction = database.transaction(); 191: 192: if (database.objectStoreNames.contains(todoDB.objectStore)) 193: { 194: store = transaction.objectStore(todoDB.objectStore); 195: requestCursor = store.openCursor(); 196: requestCursor.onsuccess = function(evt) 197: { 198: var addRecord = function() 199: { 200: requestIndex = store.get(index); 201: requestIndex.onsuccess = function(evt) 202: { 203: var record = evt.result; 204: todoDB.searchResults.push(record.todo); 205: index++; 206: addRecord(); 207: } 208: requestIndex.onerror = function(evt) 209: { 210: commitTransaction(transaction); 211: displayTodos(); 212: } 213: } 214: 215: var cursor = evt.result; 216: addRecord(); 217: } 218: requestCursor.onerror = function(evt) 219: { 220: trace_output("requestCursor.onerror = " + evt.message); 221: abortTransaction(transaction); 222: } 223: } 224: } 225: } 226: catch (e) 227: { 228: output_trace("Error in bugDB.findProjectSearchResults(): " + e.message); 229: if (db) 230: db.close(); 231: 232: } 233: }; 234: 235: function displayTodos() 236: { 237: var result = "<ul>"; 238: $.each(todoDB.searchResults, function() 239: { 240: result += "<li>" + this + "</li>"; 241: }); 242: result += "</ul>"; 243: $("#todos").html(result); 244: } 245: 246: $(document).ready( function() 247: { 248: $("#actionAdd").click(function() { 249: var todoItem = $("#todoitem").val(); 250: todoDB.addTodo(todoItem); 251: }); 252: 253: $("#actionFind").click(function() { 254: todoDB.searchResults = []; 255: todoDB.findTodos(); 256: }); 257: 258: }); 259: </script> 260: </head> 261: <body> 262: <input type=text id="todoitem" /> 263: <input type=submit value=add id="actionAdd" /> 264: <input type=submit value=refresh id="actionFind" /> 265: <h2>Todos:</h2> 266: <div id="todos"></div> 267: <div id="trace"></div> 268: </body> 269: </html>
7: <meta http-equiv="X-UA-Compatible" content="IE=8" />
8: <script type="text/javascript" src="bootidb.js"></script>
9: <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.4.min.js"></script>
10: <script type="text/javascript">
11:
12: function commitTransaction(txn)
14: try
15: {
16: if (txn)
17: {
18: txn.oncomplete = function ()
19: {
20: if (txn.db)
21: txn.db.close();
22: }
23: txn.commit();
24: }
25: }
26: catch (e)
27: {
28: output_trace("Error in commitTransaction(): " + e.message);
29: }
30: }
31:
32: function abortTransaction(txn)
33: {
34: try
35: {
36: if (txn)
37: {
38: txn.onabort = function ()
39: {
40: if (txn.db)
41: txn.db.close();
42: }
43: txn.abort();
44: alert('transaction aborted');
45: }
46: }
47: catch (e)
48: {
49: output_trace("Error in abortTransaction(): " + e.message);
50: }
51: }
52:
53: function output_trace(message)
54: {
55: var trace = $("#trace");
56: if ((trace != null ) || (trace != undefined))
57: trace.html(message);
58: }
59:
60: Array.prototype.contains = function(obj) {
61: try
62: {
63: var i = this.length;
64: while (i--) {
65: if (this[i] === obj) {
66: return true;
67: }
68: }
69: return false;
70: }
71: catch (e)
72: {
73: output_error("Error in Array.prototype.contains(): " + e.message);
74: }
75: };
76:
77:
78: var todoDB = {};
79:
80: todoDB.db = "todolist";
81: todoDB.objectStore = "todos";
82: todoDB.keyPath = "id";
83: todoDB.indexTodo = "todo";
84: todoDB.indexTodoKeyPath = "todo";
85: todoDB.searchResults = [];
86:
87: todoDB.addTodo = function( todoitem )
88: {
89: var database = null;
90: try
91: {
92: var requestDatabase = null;
93: var requestObjectStore = null;
94: var requestPut = null;
95: var requestIndex = null;
96: var store = null;
97: var transaction = null;
98:
99: requestDatabase = window.indexedDB.open(todoDB.db, 'Todo Database');
100: requestDatabase.onsuccess = function( evt )
101: {
102: database = evt.result;
103: var whenReady = function()
104: {
105: transaction = database.transaction();
106: var objectStore = transaction.objectStore(todoDB.objectStore);
107: var newEntry = { todo: todoitem };
108: requestPut = objectStore.put(newEntry);
109:
110: requestPut.onsuccess = function(evt)
111: {
112: commitTransaction(transaction);
113: }
114: requestPut.onerror = function(evt)
115: {
116: output_trace("requestPut.onerror = " + evt.message);
117: abortTransaction(transaction);
118: }
119: };
120:
121: if( database.objectStoreNames.contains(todoDB.objectStore) )
122: {
123: whenReady();
124: }
125: else
126: {
127: requestObjectStore = database.createObjectStore(
128: todoDB.objectStore,
129: todoDB.keyPath,
130: true );
131: requestObjectStore.onsuccess = function(evt)
132: {
133: store = evt.result;
134: requestIndex = store.createIndex(
135: todoDB.indexTodo,
136: todoDB.indexTodoKeyPath,
137: false);
138: requestIndex.onsuccess = function(evt)
139: {
140: whenReady();
141: }
142: requestIndex.onerror = function(evt)
143: {
144: output_trace("requestIndex.onerror = " + evt.message);
145: abortTransaction(transaction);
146: }
147: }
148: requestObjectStore.onerror = function(evt)
149: {
150: output_trace("requestObjectStore.onerror = " + evt.message);
151: abortTransaction(transaction);
152: }
153: }
154: }
155: requestDatabase.onerror = function(evt)
156: {
157: output_trace("requestDatabase.onerror = " + evt.message);
158: abortTransaction(transaction);
159: }
160: }
161: catch (e)
162: {
163: output_trace("catch(e) = " + e.message);
164: if(database)
165: {
166: database.close();
167: }
168: }
169: }
170:
171: todoDB.findTodos = function () {
172:
173: var database = null;
174: try
175: {
176: var requestDatabase = null;
177: var requestObjectStore = null;
178: var requestIndex = null;
179: var requestCursor = null;
180: var requestMove = null;
181: var store = null;
182: var cursor = null;
183: var transaction = null;
184: var index = 1;
185:
186: requestDatabase = window.indexedDB.open(todoDB.db, 'Todo Database');
187: requestDatabase.onsuccess = function (evt)
188: {
189: database = evt.result;
190: transaction = database.transaction();
191:
192: if (database.objectStoreNames.contains(todoDB.objectStore))
193: {
194: store = transaction.objectStore(todoDB.objectStore);
195: requestCursor = store.openCursor();
196: requestCursor.onsuccess = function(evt)
197: {
198: var addRecord = function()
199: {
200: requestIndex = store.get(index);
201: requestIndex.onsuccess = function(evt)
202: {
203: var record = evt.result;
204: todoDB.searchResults.push(record.todo);
205: index++;
206: addRecord();
207: }
208: requestIndex.onerror = function(evt)
209: {
210: commitTransaction(transaction);
211: displayTodos();
212: }
213: }
214:
215: var cursor = evt.result;
216: addRecord();
217: }
218: requestCursor.onerror = function(evt)
219: {
220: trace_output("requestCursor.onerror = " + evt.message);
221: abortTransaction(transaction);
222: }
223: }
224: }
225: }
226: catch (e)
227: {
228: output_trace("Error in bugDB.findProjectSearchResults(): " + e.message);
229: if (db)
230: db.close();
231:
232: }
233: };
234:
235: function displayTodos()
236: {
237: var result = "<ul>";
238: $.each(todoDB.searchResults, function()
239: {
240: result += "<li>" + this + "</li>";
241: });
242: result += "</ul>";
243: $("#todos").html(result);
244: }
245:
246: $(document).ready( function()
247: {
248: $("#actionAdd").click(function() {
249: var todoItem = $("#todoitem").val();
250: todoDB.addTodo(todoItem);
251: });
252:
253: $("#actionFind").click(function() {
254: todoDB.searchResults = [];
255: todoDB.findTodos();
256: });
257:
258: });
259: </script>
260: </head>
261: <body>
262: <input type=text id="todoitem" />
263: <input type=submit value=add id="actionAdd" />
264: <input type=submit value=refresh id="actionFind" />
265: <h2>Todos:</h2>
266: <div id="todos"></div>
267: <div id="trace"></div>
268: </body>
269: </html>
Das UI ist nicht sonderlich hübsch, aber es geht ja erst einmal nur um die Funktionalität:
Eine indexbasierte Datenbank im Browser des Clients zu haben, kann für manche Anwendungsszenarien sinnvoll sein. So können zukünftige HTML 5-Anwendungen offline betrieben werden (ja, auch dafür gibt es Specs), die zum Beispiel auch Referenzdaten benötigen. Die Verwendung der API ist Stand heute noch ein wenig mühselig, auch direktes Tooling im Browser fehlt noch komplett. Trotzdem ist es ein erster funktionaler Ansatz, der auf Feedback wartet.