From ee2486b76a0a4d503ab92b08fb04523d8e26ab1c Mon Sep 17 00:00:00 2001 From: Max Lynch Date: Thu, 3 Sep 2015 23:16:50 -0500 Subject: [PATCH] feat(storage): new Local and WebSQL/SQLite key value storage service --- ionic/components/app/test/storage/index.ts | 47 +++++++ ionic/components/app/test/storage/main.html | 13 ++ ionic/ionic.ts | 4 + ionic/storage/local-storage.ts | 37 ++++++ ionic/storage/sql.ts | 128 ++++++++++++++++++++ ionic/storage/storage.ts | 30 +++++ 6 files changed, 259 insertions(+) create mode 100644 ionic/components/app/test/storage/index.ts create mode 100644 ionic/components/app/test/storage/main.html create mode 100644 ionic/storage/local-storage.ts create mode 100644 ionic/storage/sql.ts create mode 100644 ionic/storage/storage.ts diff --git a/ionic/components/app/test/storage/index.ts b/ionic/components/app/test/storage/index.ts new file mode 100644 index 0000000000..d4a2eb9d1e --- /dev/null +++ b/ionic/components/app/test/storage/index.ts @@ -0,0 +1,47 @@ +import {Component} from 'angular2/angular2'; +import {Control, ControlGroup} from 'angular2/forms'; + +import {App, Http, Storage, LocalStorage, SQLStorage} from 'ionic/ionic'; + +let testUrl = 'https://ionic-api-tester.herokuapp.com/json'; +let testUrl404 = 'https://ionic-api-tester.herokuapp.com/404'; + + +@App({ + templateUrl: 'main.html' +}) +class IonicApp { + constructor() { + this.local = new Storage(LocalStorage); + this.sql = new Storage(SQLStorage); + } + getLocal() { + this.local.get('name').then(value => { + alert('Your name is: ' + value); + }); + } + setLocal() { + let name = prompt('Your name?'); + + this.local.set('name', name); + } + removeLocal() { + this.local.remove('name'); + } + + getSql() { + this.sql.get('name').then(value => { + alert('Your name is: ' + value); + }, (errResult) => { + console.error('Unable to get item from SQL db:', errResult); + }); + } + setSql() { + let name = prompt('Your name?'); + + this.sql.set('name', name); + } + removeSql() { + this.sql.remove('name'); + } +} diff --git a/ionic/components/app/test/storage/main.html b/ionic/components/app/test/storage/main.html new file mode 100644 index 0000000000..8e4111657c --- /dev/null +++ b/ionic/components/app/test/storage/main.html @@ -0,0 +1,13 @@ + + +

Local Storage

+ + + + +

SQL Storage

+ + + +
+
diff --git a/ionic/ionic.ts b/ionic/ionic.ts index 72709fc48c..4766290aeb 100644 --- a/ionic/ionic.ts +++ b/ionic/ionic.ts @@ -10,6 +10,10 @@ export * from './components' export * from './platform/platform' export * from './platform/registry' +export * from './storage/storage' +export * from './storage/local-storage' +export * from './storage/sql' + export * from './util/click-block' export * from './util/focus' diff --git a/ionic/storage/local-storage.ts b/ionic/storage/local-storage.ts new file mode 100644 index 0000000000..d61ef12f32 --- /dev/null +++ b/ionic/storage/local-storage.ts @@ -0,0 +1,37 @@ +import {StorageStrategy} from './storage'; + +export class LocalStorage extends StorageStrategy { + constructor() { + super(); + } + get(key) { + return new Promise((resolve, reject) => { + try { + let value = window.localStorage.getItem(key); + resolve(value); + } catch(e) { + reject(e); + } + }); + } + set(key, value) { + return new Promise((resolve, reject) => { + try { + window.localStorage.setItem(key, value); + resolve(); + } catch(e) { + reject(e); + } + }); + } + remove(key) { + return new Promise((resolve, reject) => { + try { + window.localStorage.removeItem(key); + resolve(); + } catch(e) { + reject(e); + } + }); + } +} diff --git a/ionic/storage/sql.ts b/ionic/storage/sql.ts new file mode 100644 index 0000000000..9b1b97f01d --- /dev/null +++ b/ionic/storage/sql.ts @@ -0,0 +1,128 @@ +import {StorageStrategy} from './storage'; + +import * as util from 'ionic/util'; + +const DB_NAME = '__ionicstorage'; + +export class SQLStorage extends StorageStrategy { + static BACKUP_LOCAL = 2 + static BACKUP_LIBRARY = 1 + static BACKUP_DOCUMENTS = 0 + + constructor(options) { + super(); + + let dbOptions = util.defaults({ + name: DB_NAME, + backupFlag: SQLStorage.BACKUP_NONE, + existingDatabase: false + }, options); + + + if(window.sqlitePlugin) { + let location = this._getBackupLocation(dbOptions); + + this._db = window.sqlitePlugin.openDatabase(util.extend({ + name: dbOptions.name, + location: location, + createFromLocation: dbOptions.existingDatabase ? 1 : 0 + }, dbOptions)); + } else { + console.warn('Storage: SQLite plugin not installed, falling back to WebSQL. Make sure to install cordova-sqlite-storage in production!'); + + this._db = window.openDatabase(dbOptions.name, '1.0', 'database', 5 * 1024 * 1024); + } + this._tryInit(); + } + + _getBackupLocation(dbFlag) { + switch(dbFlag) { + case SQLStorage.BACKUP_LOCAL: + return 2; + case SQLStorage.BACKUP_LIBRARY: + return 1; + case SQLStorage.BACKUP_DOCUMENTS: + return 0; + default: + throw Error('Invalid backup flag: ' + dbFlag); + } + } + + // Initialize the DB with our required tables + _tryInit() { + this._db.transaction((tx) => { + tx.executeSql('CREATE TABLE IF NOT EXISTS kv (key text primary key, value text)', [], (tx, res) => { + }, (tx, err) => { + console.error('Storage: Unable to create initial storage tables', tx, err); + }); + }); + } + + get(key) { + return new Promise((resolve, reject) => { + try { + + this._db.transaction(tx => { + tx.executeSql("select key, value from kv where key = ? limit 1", [key], (tx, res) => { + if(res.rows.length > 0) { + let item = res.rows.item(0); + resolve(item.value); + } + resolve(null); + }, (tx, err) => { + reject({ + tx: tx, + err: err + }); + }) + }, err => { + reject(err); + }); + + } catch(e) { + reject(e); + } + }); + } + set(key, value) { + return new Promise((resolve, reject) => { + try { + this._db.transaction(tx => { + tx.executeSql('insert or replace into kv(key, value) values (?, ?)', [key, value], (tx, res) => { + resolve(); + }, (tx, err) => { + reject({ + tx: tx, + err: err + }); + }) + }, err => { + reject(err); + }); + } catch(e) { + reject(e); + } + }); + } + remove(key) { + return new Promise((resolve, reject) => { + try { + this._db.transaction(tx => { + tx.executeSql('delete from kv where key = ?', [key], (tx, res) => { + resolve(); + }, (tx, err) => { + reject({ + tx: tx, + err: err + }); + }) + }, err => { + reject(err); + }); + } catch(e) { + reject(e); + } + }); + + } +} diff --git a/ionic/storage/storage.ts b/ionic/storage/storage.ts new file mode 100644 index 0000000000..9ba626a887 --- /dev/null +++ b/ionic/storage/storage.ts @@ -0,0 +1,30 @@ +/** + * Storage is an easy way to store key/value pairs and other complicated + * data in a way that uses the best possible storage layer underneath. + */ +export class Storage { + constructor(strategyCls: StorageStrategy) { + this._strategy = new strategyCls(); + } + get(key) { + return this._strategy.get(key); + } + set(key, value) { + return this._strategy.set(key, value); + } + remove(key) { + return this._strategy.remove(key); + } +} + +export class StorageStrategy { + get(key, value) { + throw Error("Not implemented"); + } + set(key, value) { + throw Error("Not implemented"); + } + remove(key) { + throw Error("Not implemented"); + } +}