|
| 1 | +/// <reference path="dexie.d.ts" /> |
| 2 | + |
| 3 | +import Dexie = require("Dexie"); |
| 4 | + |
| 5 | +module Utils { |
| 6 | + |
| 7 | + export class Console { |
| 8 | + textarea: HTMLTextAreaElement; |
| 9 | + |
| 10 | + constructor() { |
| 11 | + this.textarea = document.createElement('textarea'); |
| 12 | + } |
| 13 | + |
| 14 | + log(txt: string, type?: string) { |
| 15 | + if (type) this.textarea.value += type + " "; |
| 16 | + this.textarea.value += txt + "\n"; |
| 17 | + } |
| 18 | + error = function (txt: string) { |
| 19 | + this.log(txt, "ERROR!"); |
| 20 | + } |
| 21 | + } |
| 22 | + |
| 23 | +} |
| 24 | + |
| 25 | +module AppDb { |
| 26 | + |
| 27 | + export class AppDatabase extends Dexie { |
| 28 | + |
| 29 | + contacts: Dexie.Table<Contact, number>; |
| 30 | + emails: Dexie.Table<IEmailAddress, number>; |
| 31 | + phones: Dexie.Table<IPhoneNumber, number>; |
| 32 | + |
| 33 | + constructor() { |
| 34 | + |
| 35 | + super("MyTypeScriptAppDb"); |
| 36 | + |
| 37 | + var db = this; |
| 38 | + |
| 39 | + // |
| 40 | + // Define tables and indexes |
| 41 | + // |
| 42 | + db.version(1).stores({ |
| 43 | + contacts: '++id, first, last', |
| 44 | + emails: '++id, contactId, type, email', |
| 45 | + phones: '++id, contactId, type, phone', |
| 46 | + }); |
| 47 | + |
| 48 | + // Let's physically map Contact class to contacts table. |
| 49 | + // This will make it possible to call loadEmailsAndPhones() |
| 50 | + // directly on retrieved database objects. |
| 51 | + db.contacts.mapToClass(Contact); |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + /* Just for code completion and compilation - defines |
| 56 | + * the interface of objects stored in the emails table. |
| 57 | + */ |
| 58 | + export interface IEmailAddress { |
| 59 | + id?: number; |
| 60 | + contactId: number; |
| 61 | + type: string; |
| 62 | + email: string; |
| 63 | + } |
| 64 | + |
| 65 | + /* Just for code completion and compilation - defines |
| 66 | + * the interface of objects stored in the phones table. |
| 67 | + */ |
| 68 | + export interface IPhoneNumber { |
| 69 | + id?: number; |
| 70 | + contactId: number; |
| 71 | + type: string; |
| 72 | + phone: string; |
| 73 | + } |
| 74 | + |
| 75 | + /* This is a 'physical' class that is mapped to |
| 76 | + * the contacts table. We can have methods on it that |
| 77 | + * we could call on retrieved database objects. |
| 78 | + */ |
| 79 | + export class Contact { |
| 80 | + id: number; |
| 81 | + first: string; |
| 82 | + last: string; |
| 83 | + emails: IEmailAddress[]; |
| 84 | + phones: IPhoneNumber[]; |
| 85 | + |
| 86 | + constructor(first: string, last: string, id?: number) { |
| 87 | + this.first = first; |
| 88 | + this.last = last; |
| 89 | + if (id) this.id = id; |
| 90 | + } |
| 91 | + |
| 92 | + loadEmailsAndPhones() : Dexie.Promise<Contact> { |
| 93 | + return Dexie.Promise.all<any>( |
| 94 | + db.emails |
| 95 | + .where('contactId').equals(this.id) |
| 96 | + .toArray(emails => this.emails = emails) |
| 97 | + , |
| 98 | + db.phones |
| 99 | + .where('contactId').equals(this.id) |
| 100 | + .toArray(phones => this.phones = phones) |
| 101 | + |
| 102 | + ).then(() => this); |
| 103 | + } |
| 104 | + |
| 105 | + save() { |
| 106 | + return db.transaction('rw', db.contacts, db.emails, db.phones, () => { |
| 107 | + Dexie.Promise.all( |
| 108 | + // Save existing arrays |
| 109 | + Dexie.Promise.all(this.emails.map(email => db.emails.put(email))), |
| 110 | + Dexie.Promise.all(this.phones.map(phone => db.phones.put(phone)))) |
| 111 | + .then(results => { |
| 112 | + // Remove items from DB that is was not saved here: |
| 113 | + var emailIds = results[0], // array of resulting primary keys |
| 114 | + phoneIds = results[1]; // array of resulting primary keys |
| 115 | + |
| 116 | + db.emails.where('contactId').equals(this.id) |
| 117 | + .and(email => emailIds.indexOf(email.id) === -1) |
| 118 | + .delete(); |
| 119 | + |
| 120 | + db.phones.where('contactId').equals(this.id) |
| 121 | + .and(phone => phoneIds.indexOf(phone.id) === -1) |
| 122 | + .delete(); |
| 123 | + |
| 124 | + // At last, save our own properties. |
| 125 | + // (Must not do put(this) because we would get |
| 126 | + // reduntant emails/phones arrays saved into db) |
| 127 | + db.contacts.put( |
| 128 | + new Contact(this.first, this.last, this.id)) |
| 129 | + .then(id => this.id = id); |
| 130 | + }); |
| 131 | + }); |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + export var db = new AppDatabase(); |
| 136 | + db.open(); |
| 137 | +} |
| 138 | + |
| 139 | + |
| 140 | +import Console = Utils.Console; |
| 141 | +import db = AppDb.db; |
| 142 | + |
| 143 | +document.addEventListener('DOMContentLoaded', () => { |
| 144 | + |
| 145 | + // Initialize our Console widget - it will log browser window. |
| 146 | + var console = new Console(); |
| 147 | + document.getElementById('consoleArea').appendChild(console.textarea); |
| 148 | + |
| 149 | + // Test it: |
| 150 | + console.log("Hello world!"); |
| 151 | + |
| 152 | + // Make sure to never miss any unexpected error: |
| 153 | + Dexie.Promise.on.error.subscribe(e => { |
| 154 | + // Log any uncatched error: |
| 155 | + console.error(e); |
| 156 | + }); |
| 157 | + |
| 158 | + // |
| 159 | + // Let's clear and re-seed the database: |
| 160 | + // |
| 161 | + clearDatabase() |
| 162 | + .then(seedDatabase) |
| 163 | + .then(playALittle_add_phone_to_adam) |
| 164 | + .then(printContacts); |
| 165 | + |
| 166 | + function clearDatabase() { |
| 167 | + console.log("Clearing database..."); |
| 168 | + return Dexie.Promise.all( |
| 169 | + db.contacts.clear(), |
| 170 | + db.emails.clear(), |
| 171 | + db.phones.clear()); |
| 172 | + } |
| 173 | + |
| 174 | + function seedDatabase() { |
| 175 | + console.log("Seeding database with some contacts..."); |
| 176 | + return db.transaction('rw', db.contacts, db.emails, db.phones, () => { |
| 177 | + // Populate a contact |
| 178 | + db.contacts.add(new AppDb.Contact('Arnold', 'Fitzgerald')).then(id => { |
| 179 | + // Populate some emails and phone numbers for the contact |
| 180 | + db.emails.add({ contactId: id, type: 'home', email: '[email protected]' }); |
| 181 | + db.emails.add({ contactId: id, type: 'work', email: '[email protected]' }); |
| 182 | + db.phones.add({ contactId: id, type: 'home', phone: '12345678' }); |
| 183 | + db.phones.add({ contactId: id, type: 'work', phone: '987654321' }); |
| 184 | + }); |
| 185 | + |
| 186 | + // ... and another one... |
| 187 | + db.contacts.add(new AppDb.Contact('Adam', 'Tensta')).then(id => { |
| 188 | + // Populate some emails and phone numbers for the contact |
| 189 | + db.emails.add({ contactId: id, type: 'home', email: '[email protected]' }); |
| 190 | + db.phones.add({ contactId: id, type: 'work', phone: '88888888' }); |
| 191 | + }); |
| 192 | + }); |
| 193 | + } |
| 194 | + |
| 195 | + function playALittle_add_phone_to_adam() { |
| 196 | + // Now, just to examplify how to use the save() method as an alternative |
| 197 | + // to db.phones.add(), we will add yet another phone number |
| 198 | + // to an existing contact and then re-save it: |
| 199 | + console.log("Playing a little: adding another phone entry for Adam Tensta..."); |
| 200 | + return db.contacts |
| 201 | + .where('last').equals('Tensta').first(c => c.loadEmailsAndPhones()) |
| 202 | + .then(contact => { |
| 203 | + // Also add another phone number to Adam Tensta: |
| 204 | + contact.phones.push({ |
| 205 | + contactId: contact.id, |
| 206 | + type: 'custom', |
| 207 | + phone: '112' |
| 208 | + }); |
| 209 | + contact.save(); |
| 210 | + }); |
| 211 | + } |
| 212 | + |
| 213 | + function printContacts() { |
| 214 | + |
| 215 | + // Now we're gonna list all contacts starting with letter 'A' |
| 216 | + // and print them out. |
| 217 | + // For each contact, also resolve its collection of |
| 218 | + // phone number entries and email addresses by reverse-quering |
| 219 | + // the foreign tables. |
| 220 | + |
| 221 | + // For atomicity and speed, use a single transaction for the |
| 222 | + // queries to make: |
| 223 | + db.transaction('r', [db.contacts, db.phones, db.emails], () => { |
| 224 | + |
| 225 | + // Query some contacts |
| 226 | + return db.contacts |
| 227 | + .where('first').startsWithIgnoreCase('a') |
| 228 | + .sortBy('id') |
| 229 | + .then(contacts => |
| 230 | + |
| 231 | + // Resolve array properties 'emails' and 'phones' |
| 232 | + // on each and every contact: |
| 233 | + Dexie.Promise.all( |
| 234 | + contacts.map(contact => |
| 235 | + contact.loadEmailsAndPhones())) |
| 236 | + ); |
| 237 | + |
| 238 | + }).then(contacts => { |
| 239 | + |
| 240 | + // Print result |
| 241 | + console.log("Database contains the following contacts:"); |
| 242 | + contacts.forEach(contact => { |
| 243 | + console.log(contact.id + ". " + contact.first + " " + contact.last); |
| 244 | + console.log(" Phone numbers: "); |
| 245 | + contact.phones.forEach(phone => { |
| 246 | + console.log(" " + phone.phone + "(" + phone.type + ")"); |
| 247 | + }); |
| 248 | + console.log(" Emails: "); |
| 249 | + contact.emails.forEach(email => { |
| 250 | + console.log(" " + email.email + "(" + email.type + ")"); |
| 251 | + }); |
| 252 | + }); |
| 253 | + }); |
| 254 | + } |
| 255 | +}); |
| 256 | + |
0 commit comments