Skip to content

Instantly share code, notes, and snippets.

@leepfrog
Created April 10, 2013 05:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leepfrog/5352059 to your computer and use it in GitHub Desktop.
Save leepfrog/5352059 to your computer and use it in GitHub Desktop.
Current WIP of IndexedDB Adapter
# Aliases
get = Ember.get
indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB
# Indexed DB Adapter
App.IDBAdapter = DS.Adapter.extend
# Name of the database store we're going to use
dbName: 'myCoolDB'
# Version of the schema
version: 1
# Handle of the db instance
dbHandle: null
# Used to determine if we have IndexedDB capabilities
isSupported: false
# This is used to hold commands that we want to run after our database becomes ready
_beforeReadyQueue: null
###
Sets us up the bomb.
###
init: ->
@_super()
# Check to see if we have an IndexedDB variable (meaning the browser supports IndexedDB)
if indexedDB?
@_beforeReadyQueue = []
@isSupported = true
@_setupDBInstance()
###
@private
@needsTest
Used for creating our IndexDB instances and registering error handlers.
@todo Moooove the schema, version number, etc.. outta herrreee.
Maybe look at something like App.Adapter.configure as an API interface
@method _setupDBInstance
###
_setupDBInstance: ->
# Open an instance to our db
request = indexedDB.open(@dbName, @version)
# Setup handlers
request.onerror = (e) -> console.error("IDBError: #{e.target.errorCode}")
request.onsuccess = (e) =>
# Hold onto our database handle
@dbHandle = request.result
# This onerror will capture all of our bubbled up error events
@dbHandle.onerror = (e) -> console.error("IDBError: #{e.target.errorCode}")
# Finally, process anything we may have waiting for us
@_processBeforeReadyQueue()
# Our schema definition
request.onupgradeneeded = (e) ->
# We grab a local handle from the event and let our success callback set the actual handle
localDbHandle = e.target.result
# TODO: Creating object store entities (this can likely be done based on model definitions)
models = [
'books'
]
# Create an object store for each model
for model in models
localDbHandle.createObjectStore(model, { keyPath: 'id' })
###
# The `find()` method is invoked when the store is asked for a record that
# has not previously been loaded. In response to `find()` being called, you
# should query your persistence layer for a record with the given ID. Once
# found, you can asynchronously call the store's `load()` method to load
# the record.
# Here is an example `find` implementation:
@method find
@param {DS.Store} store
@param {subclass of DS.Model} type
@param {String|Number} id of record to retrieve
###
find: (store, type, id) ->
root = @rootForType(type)
plural = @pluralize(root)
request = @dbHandle.transaction(plural).objectStore(plural).get(id)
request.onsuccess = (e) => @load(store, type, e.target.result)
# !!! TODO: what happens when we don't find an id?
###
Here is an example `findAll` implementation:
@method findAll
###
findAll: (store, type) ->
root = @rootForType(type)
plural = @pluralize(root)
# It's possible that findAll can be called before we have an open handle on the
# database. As a result, we need to do the following:
#
# If we have our dbHandle, do our request
if @dbHandle?
request = @dbHandle.transaction(plural).objectStore(plural).openCursor()
# Our results that we'll use as we're iterating over our cursor
results = {}
results[plural]=[]
request.onsuccess = (e) =>
cursor = e.target.result
if cursor?
currentResult = cursor.value
results[plural].push(currentResult)
cursor.continue()
else
@didFindAll(store, type, results)
# If we don't have a handle, then we check for support / enqueue a request if we're supported
else
# If we have support, great.. enqueue our request
if @isSupported
@_enqueueBeforeReady(method: 'findAll', params: arguments)
# If we do not, then return didFindAll with no results
else
return @didFindAll(store, type, [])
###
@private
This method is used whenever we are attempting to run a request before we have a handle open to
our database.
@method _enqueueBeforeReady
###
_enqueueBeforeReady: (options) -> @_beforeReadyQueue.push(options)
###
@private
This method will be called by our dbOpen callback to process any requests that are pending.
@method _processBeforeReadyQueue
###
_processBeforeReadyQueue: ->
for func in @_beforeReadyQueue
get(@, func['method']).apply(this,func['params'])
###
@needsTest
Used for adding objects that have been created into our IndexedDB.
@note I don't believe it's necessary to wrap this in a settimeout as indexeddb is already
async
@method createRecords
@param {DS.Store} store
@param {subclass of DS.Model} type
@param {Ember.Object} Instance of the DS.Model type
###
createRecord: (store, type, record) ->
root = @rootForType(type)
plural = @pluralize(root)
json = record.serialize(includeId:true)
# This is if we do not have a json id
json['id']= @_generateRecordId() if !json['id']?
# Persist the object
request = @dbHandle.transaction(plural, 'readwrite').objectStore(plural).add(json)
request.onsuccess = (e) => @didCreateRecord(store, type, record)
request.error = (e) => @didError(store, type, record)
###
@needsTest
Used for updating objects that have already been created in our IndexedDB.
@note I don't believe it's necessary to wrap this in a settimeout as indexeddb is already
async
@method createRecords
@param {DS.Store} store
@param {subclass of DS.Model} type
@param {Ember.Object} Instance of the DS.Model type
###
updateRecord: (store, type, record) ->
id = get(record, "id")
root = @rootForType(type)
plural = @pluralize(root)
json = record.serialize(includeId:true)
# Update the object
request = @dbHandle.transaction(plural, 'readwrite').objectStore(plural).put(json)
request.onsuccess = (e) => @didUpdateRecord(store, type, record)
request.error = (e) => @didError(store, type, record)
###
@needsTest
Used for deleting objects that have already been created in our IndexedDB.
@note I don't believe it's necessary to wrap this in a settimeout as indexeddb is already
async
@method deleteRecord
@param {DS.Store} store
@param {subclass of DS.Model} type
@param {Ember.Object} Instance of the DS.Model type
###
deleteRecord: (store, type, record) ->
id = get(record, "id")
root = @rootForType(type)
plural = @pluralize(root)
# Delete the object
request = @dbHandle.transaction(plural, 'readwrite').objectStore(plural).delete(id)
request.onsuccess = (e) => @didDeleteRecord(store, type, record)
request.error = (e) => @didError(store, type, record)
###
@needsTest
Used for generating a random client ID.
This code is ganked straight out of the Ember TodoMVC code sample.
@method _generateRecordId
###
_generateRecordId: -> return Math.random().toString(32).slice(2).substr(0, 5)
###
Used to obtain a string representation of a given model type.
This code was yanked directly from DS.RESTAdapter since it doesn't
exist inside of DS.Adapter for some reason.
@method rootForType
@param {subclass of DS.Model} type
@return {String} type name
###
rootForType: (type) -> get(this, 'serializer').rootForType(type)
###
Used to obtain a pluralized representation of a passed in string.
Same as rootForType, this code was yanked directly from DS.RESTAdapter
@method pluralize
@param {String} type name
@return {String} pluralized type name
###
pluralize: (string) -> get(this, 'serializer').pluralize(string)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment