The lccache library is a simple library that emulates memcache
21 | functions using Lawnchair,
22 | so that you can store items with an expiration date. These demos show its
23 | current functionality - set, get, remove.
24 |
25 |
Set memcache entries (most with expiration of 2 minutes):
26 | (Check "Resources->Local Storage" in Chrome Dev Tools or type
27 | "localStorage" into the Firebug Console to see what is stored.)
28 |
29 |
33 |
34 |
35 |
Retrieve lccache entries:
36 |
40 |
41 |
42 |
Delete lccache entries and try to retrieve them:
43 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/js/libs/adapters/memory.js:
--------------------------------------------------------------------------------
1 | Lawnchair.adapter('memory', (function(){
2 |
3 | var storage = {}, index = []
4 |
5 | return {
6 | valid: function() { return true },
7 |
8 | init: function(opts, cb) {
9 | this.fn(this.name, cb).call(this, this)
10 | return this
11 | },
12 |
13 | keys: function() { return index },
14 |
15 | save: function(obj, cb) {
16 | var key = obj.key || this.uuid()
17 |
18 | if (obj.key) delete obj.key
19 |
20 | this.exists(key, function(exists) {
21 | if (!exists) index.push(key)
22 |
23 | storage[key] = obj
24 |
25 | if (cb) {
26 | obj.key = key
27 | this.lambda(cb).call(this, obj)
28 | }
29 | })
30 |
31 | return this
32 | },
33 |
34 | batch: function (objs, cb) {
35 | var r = []
36 | for (var i = 0, l = objs.length; i < l; i++) {
37 | this.save(objs[i], function(record) {
38 | r.push(record)
39 | })
40 | }
41 | if (cb) this.lambda(cb).call(this, r)
42 | return this
43 | },
44 |
45 | get: function (keyOrArray, cb) {
46 | var r;
47 | if (this.isArray(keyOrArray)) {
48 | r = []
49 | for (var i = 0, l = keyOrArray.length; i < l; i++) {
50 | r.push(storage[keyOrArray[i]])
51 | }
52 | } else {
53 | r = storage[keyOrArray]
54 | if (r) r.key = keyOrArray
55 | }
56 | if (cb) this.lambda(cb).call(this, r)
57 | return this
58 | },
59 |
60 | exists: function (key, cb) {
61 | this.lambda(cb).call(this, !!(storage[key]))
62 | return this
63 | },
64 |
65 | all: function (cb) {
66 | var r = []
67 | for (var i = 0, l = index.length; i < l; i++) {
68 | var obj = storage[index[i]]
69 | obj.key = index[i]
70 | r.push(obj)
71 | }
72 | this.fn(this.name, cb).call(this, r)
73 | return this
74 | },
75 |
76 | remove: function (keyOrArray, cb) {
77 | var del = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray]
78 | for (var i = 0, l = del.length; i < l; i++) {
79 | delete storage[del[i]]
80 | index.splice(index.indexOf(del[i]), 1)
81 | }
82 | if (cb) this.lambda(cb).call(this)
83 | return this
84 | },
85 |
86 | nuke: function (cb) {
87 | storage = {}
88 | index = []
89 | if (cb) this.lambda(cb).call(this)
90 | return this
91 | }
92 | }
93 | /////
94 | })())
95 |
--------------------------------------------------------------------------------
/js/libs/adapters/window-name.js:
--------------------------------------------------------------------------------
1 | // window.name code courtesy Remy Sharp: http://24ways.org/2009/breaking-out-the-edges-of-the-browser
2 | Lawnchair.adapter('window-name', (function(index, store) {
3 |
4 | var data = window.top.name ? JSON.parse(window.top.name) : {}
5 |
6 | return {
7 |
8 | valid: function () {
9 | return typeof window.top.name != 'undefined'
10 | },
11 |
12 | init: function (options, callback) {
13 | data[this.name] = {index:[],store:{}}
14 | index = data[this.name].index
15 | store = data[this.name].store
16 | this.fn(this.name, callback).call(this, this)
17 | },
18 |
19 | keys: function (callback) {
20 | this.fn('keys', callback).call(this, index)
21 | return this
22 | },
23 |
24 | save: function (obj, cb) {
25 | // data[key] = value + ''; // force to string
26 | // window.top.name = JSON.stringify(data);
27 | var key = obj.key || this.uuid()
28 | if (obj.key) delete obj.key
29 | this.exists(key, function(exists) {
30 | if (!exists) index.push(key)
31 | store[key] = obj
32 | window.top.name = JSON.stringify(data) // TODO wow, this is the only diff from the memory adapter
33 | if (cb) {
34 | obj.key = key
35 | this.lambda(cb).call(this, obj)
36 | }
37 | })
38 | return this
39 | },
40 |
41 | batch: function (objs, cb) {
42 | var r = []
43 | for (var i = 0, l = objs.length; i < l; i++) {
44 | this.save(objs[i], function(record) {
45 | r.push(record)
46 | })
47 | }
48 | if (cb) this.lambda(cb).call(this, r)
49 | return this
50 | },
51 |
52 | get: function (keyOrArray, cb) {
53 | var r;
54 | if (this.isArray(keyOrArray)) {
55 | r = []
56 | for (var i = 0, l = keyOrArray.length; i < l; i++) {
57 | r.push(store[keyOrArray[i]])
58 | }
59 | } else {
60 | r = store[keyOrArray]
61 | if (r) r.key = keyOrArray
62 | }
63 | if (cb) this.lambda(cb).call(this, r)
64 | return this
65 | },
66 |
67 | exists: function (key, cb) {
68 | this.lambda(cb).call(this, !!(store[key]))
69 | return this
70 | },
71 |
72 | all: function (cb) {
73 | var r = []
74 | for (var i = 0, l = index.length; i < l; i++) {
75 | var obj = store[index[i]]
76 | obj.key = index[i]
77 | r.push(obj)
78 | }
79 | this.fn(this.name, cb).call(this, r)
80 | return this
81 | },
82 |
83 | remove: function (keyOrArray, cb) {
84 | var del = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray]
85 | for (var i = 0, l = del.length; i < l; i++) {
86 | delete store[del[i]]
87 | index.splice(this.indexOf(index, del[i]), 1)
88 | }
89 | window.top.name = JSON.stringify(data)
90 | if (cb) this.lambda(cb).call(this)
91 | return this
92 | },
93 |
94 | nuke: function (cb) {
95 | storage = {}
96 | index = []
97 | window.top.name = JSON.stringify(data)
98 | if (cb) this.lambda(cb).call(this)
99 | return this
100 | }
101 | }
102 | /////
103 | })())
104 |
--------------------------------------------------------------------------------
/js/libs/Lawnchair.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Lawnchair!
3 | * ---
4 | * clientside json store
5 | *
6 | */
7 | var Lawnchair = function () {
8 | // lawnchair requires json
9 | if (!JSON) throw 'JSON unavailable! Include http://www.json.org/json2.js to fix.'
10 | // options are optional; callback is not
11 | if (arguments.length <= 2 && arguments.length > 0) {
12 | var callback = (typeof arguments[0] === 'function') ? arguments[0] : arguments[1]
13 | , options = (typeof arguments[0] === 'function') ? {} : arguments[0]
14 | } else {
15 | throw 'Incorrect # of ctor args!'
16 | }
17 | // TODO perhaps allow for pub/sub instead?
18 | if (typeof callback !== 'function') throw 'No callback was provided';
19 |
20 | // ensure we init with this set to the Lawnchair prototype
21 | var self = (!(this instanceof Lawnchair))
22 | ? new Lawnchair(options, callback)
23 | : this
24 |
25 | // default configuration
26 | self.record = options.record || 'record' // default for records
27 | self.name = options.name || 'records' // default name for underlying store
28 |
29 | // mixin first valid adapter
30 | var adapter
31 | // if the adapter is passed in we try to load that only
32 | if (options.adapter) {
33 | adapter = Lawnchair.adapters[self.indexOf(Lawnchair.adapters, options.adapter)]
34 | adapter = adapter.valid() ? adapter : undefined
35 | // otherwise find the first valid adapter for this env
36 | }
37 | else {
38 | for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) {
39 | adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined
40 | if (adapter) break
41 | }
42 | }
43 |
44 | // we have failed
45 | if (!adapter) throw 'No valid adapter.'
46 |
47 | // yay! mixin the adapter
48 | for (var j in adapter)
49 | self[j] = adapter[j]
50 |
51 | // call init for each mixed in plugin
52 | for (var i = 0, l = Lawnchair.plugins.length; i < l; i++)
53 | Lawnchair.plugins[i].call(self)
54 |
55 | // init the adapter
56 | self.init(options, callback)
57 |
58 | // called as a function or as a ctor with new always return an instance
59 | return self
60 | }
61 |
62 | Lawnchair.adapters = []
63 |
64 | /**
65 | * queues an adapter for mixin
66 | * ===
67 | * - ensures an adapter conforms to a specific interface
68 | *
69 | */
70 | Lawnchair.adapter = function (id, obj) {
71 | // add the adapter id to the adapter obj
72 | // ugly here for a cleaner dsl for implementing adapters
73 | obj['adapter'] = id
74 | // methods required to implement a lawnchair adapter
75 | var implementing = 'adapter valid init keys save batch get exists all remove nuke'.split(' ')
76 | , indexOf = this.prototype.indexOf
77 | // mix in the adapter
78 | for (var i in obj) {
79 | if (indexOf(implementing, i) === -1) throw 'Invalid adapter! Nonstandard method: ' + i
80 | }
81 | // if we made it this far the adapter interface is valid
82 | Lawnchair.adapters.push(obj)
83 | }
84 |
85 | Lawnchair.plugins = []
86 |
87 | /**
88 | * generic shallow extension for plugins
89 | * ===
90 | * - if an init method is found it registers it to be called when the lawnchair is inited
91 | * - yes we could use hasOwnProp but nobody here is an asshole
92 | */
93 | Lawnchair.plugin = function (obj) {
94 | for (var i in obj)
95 | i === 'init' ? Lawnchair.plugins.push(obj[i]) : this.prototype[i] = obj[i]
96 | }
97 |
98 | /**
99 | * helpers
100 | *
101 | */
102 | Lawnchair.prototype = {
103 |
104 | isArray: Array.isArray || function(o) { return Object.prototype.toString.call(o) === '[object Array]' },
105 |
106 | /**
107 | * this code exists for ie8... for more background see:
108 | * http://www.flickr.com/photos/westcoastlogic/5955365742/in/photostream
109 | */
110 | indexOf: function(ary, item, i, l) {
111 | if (ary.indexOf) return ary.indexOf(item)
112 | for (i = 0, l = ary.length; i < l; i++) if (ary[i] === item) return i
113 | return -1
114 | },
115 |
116 | // awesome shorthand callbacks as strings. this is shameless theft from dojo.
117 | lambda: function (callback) {
118 | return this.fn(this.record, callback)
119 | },
120 |
121 | // first stab at named parameters for terse callbacks; dojo: first != best // ;D
122 | fn: function (name, callback) {
123 | return typeof callback == 'string' ? new Function(name, callback) : callback
124 | },
125 |
126 | // returns a unique identifier (by way of Backbone.localStorage.js)
127 | // TODO investigate smaller UUIDs to cut on storage cost
128 | uuid: function () {
129 | var S4 = function () {
130 | return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
131 | }
132 | return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
133 | },
134 |
135 | // a classic iterator
136 | each: function (callback) {
137 | var cb = this.lambda(callback)
138 | // iterate from chain
139 | if (this.__results) {
140 | for (var i = 0, l = this.__results.length; i < l; i++) cb.call(this, this.__results[i], i)
141 | }
142 | // otherwise iterate the entire collection
143 | else {
144 | this.all(function(r) {
145 | for (var i = 0, l = r.length; i < l; i++) cb.call(this, r[i], i)
146 | })
147 | }
148 | return this
149 | }
150 | // --
151 | };
152 |
--------------------------------------------------------------------------------
/js/libs/adapters/dom.js:
--------------------------------------------------------------------------------
1 | /**
2 | * dom storage adapter
3 | * ===
4 | * - originally authored by Joseph Pecoraro
5 | *
6 | */
7 | //
8 | // TODO does it make sense to be chainable all over the place?
9 | // chainable: nuke, remove, all, get, save, all
10 | // not chainable: valid, keys
11 | //
12 | Lawnchair.adapter('dom', (function() {
13 | var storage = window.localStorage
14 | // the indexer is an encapsulation of the helpers needed to keep an ordered index of the keys
15 | var indexer = function(name) {
16 | return {
17 | // the key
18 | key: name + '._index_',
19 | // returns the index
20 | all: function() {
21 | var a = JSON.parse(storage.getItem(this.key))
22 | if (a === null) storage.setItem(this.key, JSON.stringify([])) // lazy init
23 | return JSON.parse(storage.getItem(this.key))
24 | },
25 | // adds a key to the index
26 | add: function (key) {
27 | var a = this.all()
28 | a.push(key)
29 | storage.setItem(this.key, JSON.stringify(a))
30 | },
31 | // deletes a key from the index
32 | del: function (key) {
33 | var a = this.all(), r = []
34 | // FIXME this is crazy inefficient but I'm in a strata meeting and half concentrating
35 | for (var i = 0, l = a.length; i < l; i++) {
36 | if (a[i] != key) r.push(a[i])
37 | }
38 | storage.setItem(this.key, JSON.stringify(r))
39 | },
40 | // returns index for a key
41 | find: function (key) {
42 | var a = this.all()
43 | for (var i = 0, l = a.length; i < l; i++) {
44 | if (key === a[i]) return i
45 | }
46 | return false
47 | }
48 | }
49 | }
50 |
51 | // adapter api
52 | return {
53 |
54 | // ensure we are in an env with localStorage
55 | valid: function () {
56 | return !!storage
57 | },
58 |
59 | init: function (options, callback) {
60 | this.indexer = indexer(this.name)
61 | if (callback) this.fn(this.name, callback).call(this, this)
62 | },
63 |
64 | save: function (obj, callback) {
65 | var key = obj.key ? this.name + '.' + obj.key : this.name + '.' + this.uuid()
66 | // if the key is not in the index push it on
67 | if (this.indexer.find(key) === false) this.indexer.add(key)
68 | // now we kil the key and use it in the store colleciton
69 | delete obj.key;
70 | storage.setItem(key, JSON.stringify(obj))
71 | if (callback) {
72 | obj.key = key.replace(this.name + '.', '')
73 | this.lambda(callback).call(this, obj)
74 | }
75 | return this
76 | },
77 |
78 | batch: function (ary, callback) {
79 | var saved = []
80 | // not particularily efficient but this is more for sqlite situations
81 | for (var i = 0, l = ary.length; i < l; i++) {
82 | this.save(ary[i], function(r){
83 | saved.push(r)
84 | })
85 | }
86 | if (callback) this.lambda(callback).call(this, saved)
87 | return this
88 | },
89 |
90 | // accepts [options], callback
91 | keys: function(callback) {
92 | if (callback) {
93 | var name = this.name
94 | , keys = this.indexer.all().map(function(r){ return r.replace(name + '.', '') })
95 | this.fn('keys', callback).call(this, keys)
96 | }
97 | return this // TODO options for limit/offset, return promise
98 | },
99 |
100 | get: function (key, callback) {
101 | if (this.isArray(key)) {
102 | var r = []
103 | for (var i = 0, l = key.length; i < l; i++) {
104 | var k = this.name + '.' + key[i]
105 | , obj = JSON.parse(storage.getItem(k))
106 | if (obj) {
107 | obj.key = k
108 | r.push(obj)
109 | }
110 | }
111 | if (callback) this.lambda(callback).call(this, r)
112 | } else {
113 | var k = this.name + '.' + key
114 | , obj = JSON.parse(storage.getItem(k))
115 | if (obj) obj.key = k
116 | if (callback) this.lambda(callback).call(this, obj)
117 | }
118 | return this
119 | },
120 | // NOTE adapters cannot set this.__results but plugins do
121 | // this probably should be reviewed
122 | all: function (callback) {
123 | var idx = this.indexer.all()
124 | , r = []
125 | , o
126 | , k
127 | for (var i = 0, l = idx.length; i < l; i++) {
128 | k = idx[i] //v
129 | o = JSON.parse(storage.getItem(k))
130 | o.key = k.replace(this.name + '.', '')
131 | r.push(o)
132 | }
133 | if (callback) this.fn(this.name, callback).call(this, r)
134 | return this
135 | },
136 |
137 | remove: function (keyOrObj, callback) {
138 | var key = this.name + '.' + (typeof keyOrObj === 'string' ? keyOrObj : keyOrObj.key)
139 | this.indexer.del(key)
140 | storage.removeItem(key)
141 | if (callback) this.lambda(callback).call(this)
142 | return this
143 | },
144 |
145 | nuke: function (callback) {
146 | this.all(function(r) {
147 | for (var i = 0, l = r.length; i < l; i++) {
148 | this.remove(r[i]);
149 | }
150 | if (callback) this.lambda(callback).call(this)
151 | })
152 | return this
153 | }
154 | }})());
155 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | lccache
2 | ===============================
3 | This is a port of Pamela Fox's nifty little library
4 | [lscache](https://github.com/pamelafox/lscache) to use
5 | [Lawnchair](https://github.com/brianleroux/lawnchair) for
6 | storage rather than HTML5 localStorage.
7 |
8 | In either case, if what you need is a simple library that emulates `memcache`
9 | functions so that you can cache data on the client and associate an expiration
10 | time with each piece of data, then you want one of these libraries. The question
11 | you need to answer is, "Do I only need to support newer browsers that have HTML5
12 | localStorage, or will I need to support older browsers as well?" If the former,
13 | lscache is your ticket, if the latter, please check out this version.
14 |
15 | Methods
16 | -------
17 |
18 | The library exposes 3 methods: `set()`, `get()`, and `remove()`.
19 |
20 | * * *
21 |
22 | ### lccache.set
23 | Stores the value in Lawnchair. Expires after specified number of minutes.
24 | #### Arguments
25 | 1. `key` (**string**)
26 | 2. `value` (**Object|string**)
27 | 3. `time` (**number: optional**)
28 |
29 | * * *
30 |
31 | ### lccache.get
32 | Retrieves specified value from Lawnchair, if not expired.
33 | #### Arguments
34 | 1. `key` (**string**)
35 | #### Returns
36 | **string | Object** : The stored value.
37 |
38 | * * *
39 |
40 | ### lccache.remove
41 | Removes a value from Lawnchair.
42 | #### Arguments
43 | 1. `key` (**string**)
44 |
45 |
46 | Usage
47 | -------
48 |
49 | The interface should be familiar to those of you who have used `memcache`, and
50 | should be easy to understand for those of you who haven't.
51 |
52 | For example, you can store a string for 2 minutes using `lccache.set()`:
53 |
54 | ```js
55 | lccache.set('greeting', 'Hello World!', 2);
56 | ```
57 |
58 | You can then retrieve that string with `lccache.get()`:
59 |
60 | ```js
61 | alert(lccache.get('greeting'));
62 | ```
63 |
64 | You can remove that string from the cache entirely with `lccache.remove()`:
65 |
66 | ```js
67 | lccache.remove('greeting');
68 | ```
69 |
70 | The library handles JSON objects so you aren't restricted to only storing strings:
71 |
72 | ```js
73 | lccache.set('data', {'name': 'Pamela', 'age': 26}, 2);
74 | ```
75 |
76 | And then when you retrieve it, you will get it back as an object:
77 |
78 | ```js
79 | alert(lccache.get('data').name);
80 | ```
81 |
82 | For more live examples, play around with the demo here:
83 | http://johnmunsch.github.com/lccache/demo.html
84 |
85 | or check out the QUnit tests here:
86 | http://johnmunsch.github.com/lccache/test.html
87 |
88 | Installation
89 | ----------
90 |
91 | Add lccache.js to your project and make sure you include it in any pages that
92 | need it (see demo.html or test.html for examples). Even though I've included a
93 | copy of Lawnchair in the repository so the demo and test would work, I don't
94 | really recommend using it. It's Lawnchair 0.6.3 and a better/newer version may
95 | be available by the time you read this. If not, or if newer versions prove
96 | incompatible, know that this code worked with that version and get the whole
97 | package from his project.
98 |
99 | *NOTE: localStorage on Firefox (and maybe on other browsers as well) is disabled
100 | when you run your web pages by just opening them in the browser. So if you think
101 | you'll just double click on a file and it'll work, it won't. I've read it's for
102 | security reasons, though I don't know from what it protects you. So just serve your
103 | web pages up from some kind of server (Tomcat, Apache, or whatever) even when you're
104 | testing them locally.*
105 |
106 | Real-World Usage
107 | ----------
108 | *These are the original examples given by Pamela, note that I've not changed any
109 | of the references from lscache to lccache because she used lscache for all of
110 | her work. However, the examples of why you want to use something like this are
111 | just as relevant to both libraries.*
112 |
113 | This library was originally developed with the use case of caching results of
114 | JSON API queries to speed up my webapps and give them better protection against
115 | flaky APIs. (More on that in this [blog post](http://blog.pamelafox.org/2010/10/lscache-localstorage-based-memcache.html))
116 |
117 | For example, [RageTube](http://ragetube.net) uses `lscache` to fetch Youtube API
118 | results for 10 minutes:
119 |
120 | ```js
121 | var key = 'youtube:' + query;
122 | var json = lscache.get(key);
123 | if (json) {
124 | processJSON(json);
125 | } else {
126 | fetchJSON(query);
127 | }
128 |
129 | function processJSON(json) {
130 | // ..
131 | }
132 |
133 | function fetchJSON() {
134 | var searchUrl = 'http://gdata.youtube.com/feeds/api/videos';
135 | var params = {
136 | 'v': '2', 'alt': 'jsonc', 'q': encodeURIComponent(query)
137 | }
138 | JSONP.get(searchUrl, params, null, function(json) {
139 | processJSON(json);
140 | lscache.set(key, json, 60*10);
141 | });
142 | }
143 | ```
144 |
145 | It does not have to be used for only expiration-based caching, however. It can
146 | also be used as just a wrapper for `localStorage`, as it provides the benefit
147 | of handling JS object (de-)serialization.
148 |
149 | For example, the [QuizCards](http://quizcards.info) Chrome extensions use
150 | `lscache` to store the user statistics for each user bucket, and those stats are
151 | an array of objects.
152 |
153 | ```js
154 | function initBuckets() {
155 | var bucket1 = [];
156 | for (var i = 0; i < CARDS_DATA.length; i++) {
157 | var datum = CARDS_DATA[i];
158 | bucket1.push({'id': datum.id, 'lastAsked': 0});
159 | }
160 | lscache.set(LS_BUCKET + 1, bucket1);
161 | lscache.set(LS_BUCKET + 2, []);
162 | lscache.set(LS_BUCKET + 3, []);
163 | lscache.set(LS_BUCKET + 4, []);
164 | lscache.set(LS_BUCKET + 5, []);
165 | lscache.set(LS_INIT, 'true')
166 | }
167 | ```
168 |
169 | Browser Support
170 | ----------------
171 |
172 | The `lccache` library should work in all browsers for which a Lawnchair adapter
173 | exists. At the time of writing there were adapters for storing data in
174 | localStorage, the Blackberry persistent store, window name, Google Gears SQLite,
175 | IE userdata, Indexed DB, and memory.
176 |
177 | The current list of those is here:
178 | http://brian.io/lawnchair/adapters/
179 |
180 |
--------------------------------------------------------------------------------
/lccache.js:
--------------------------------------------------------------------------------
1 | /**
2 | * lccache library
3 | * Copyright (c) 2011, Pamela Fox, John Munsch
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | /**
19 | * Creates a namespace for the lccache functions.
20 | */
21 | var lccache = function() {
22 | // Prefixes the key name on the expiration items in localStorage
23 | var CACHESUFFIX = '-cacheexpiration';
24 |
25 | var supportsStorage = function () {
26 | // This is a temporary kludge. The old code performed its own tests but it
27 | // was looking specifically for localStorage. What we need to know is that
28 | // Lawnchair found some adapter that worked within the current environment.
29 | return true;
30 | }();
31 |
32 | // The old code checked to see if JSON was supported. Since we're using
33 | // Lawnchair, we can just pass through and assume that it will take JSON and
34 | // will give JSON back.
35 | //
36 | // Note: If there's no native support for JSON in the browser, Lawnchair will
37 | // thrown an error instructing you to add json2.js to polyfill the
38 | // functionality.
39 |
40 | /**
41 | * Returns the full string for the localStorage expiration item.
42 | * @param {String} key
43 | * @return {string}
44 | */
45 | function expirationKey(key) {
46 | return key + CACHESUFFIX;
47 | }
48 |
49 | /**
50 | * Returns the number of minutes since the epoch.
51 | * @return {number}
52 | */
53 | function currentTime() {
54 | return Math.floor((new Date().getTime())/60000);
55 | }
56 |
57 | return {
58 |
59 | /**
60 | * Stores the value in Lawnchair. Expires after specified number of minutes.
61 | * @param {string} key
62 | * @param {Object|string} value
63 | * @param {number} time
64 | */
65 | set: function(key, value, time) {
66 | if (!supportsStorage) return;
67 |
68 | // The lscache code turned anything non JSON into a string at this point.
69 | // We kind of need to go the other way. Anything non-JSON needs to become
70 | // JSON because that's what Lawnchair expects.
71 | if (typeof value != "object") {
72 | // We didn't get a JSON value, let's do something to create one.
73 | value = { "lccacheNonJSONValue": value };
74 | }
75 |
76 | try {
77 | Lawnchair(function () {
78 | var storedValue = {"key": key, "value": value};
79 |
80 | // If a time is specified, store expiration info as part of the JSON.
81 | if (time) {
82 | storedValue.expiration = currentTime() + time;
83 | }
84 |
85 | this.save(storedValue);
86 | });
87 | } catch (e) {
88 | if (e.name === 'QUOTA_EXCEEDED_ERR' || e.name == 'NS_ERROR_DOM_QUOTA_REACHED') {
89 | // If we exceeded the quota, then we will sort
90 | // by the expire time, and then remove the N oldest
91 | // var storedKey, storedKeys = [];
92 | // for (var i = 0; i < localStorage.length; i++) {
93 | // storedKey = localStorage.key(i);
94 | // if (storedKey.indexOf(CACHESUFFIX) > -1) {
95 | // var mainKey = storedKey.split(CACHESUFFIX)[0];
96 | // storedKeys.push({key: mainKey, expiration: parseInt(localStorage[storedKey], 10)});
97 | // }
98 | // }
99 | // storedKeys.sort(function(a, b) { return (a.expiration-b.expiration); });
100 | //
101 | // for (var i = 0, len = Math.min(30, storedKeys.length); i < len; i++) {
102 | // localStorage.removeItem(storedKeys[i].key);
103 | // localStorage.removeItem(expirationKey(storedKeys[i].key));
104 | // }
105 | // // TODO: This could still error if the items we removed were small and this is large
106 | // localStorage.setItem(key, value);
107 | } else {
108 | // If it was some other error, just give up.
109 | return;
110 | }
111 | }
112 | },
113 |
114 | /**
115 | * Retrieves specified value from localStorage, if not expired.
116 | * @param {string} key
117 | * @return {string|Object}
118 | */
119 | get: function(key) {
120 | if (!supportsStorage) return null;
121 |
122 | var retVal = null;
123 |
124 | Lawnchair(function () {
125 | this.get(key, function (obj) {
126 | if (obj != null) {
127 | // Return the found item if not expired.
128 | if (obj.expiration != null) {
129 | // Check if we should actually kick item out of Lawnchair.
130 | if (currentTime() >= obj.expiration) {
131 | Lawnchair(function () {
132 | this.remove(key);
133 | });
134 | } else {
135 | if (obj.value.lccacheNonJSONValue != null) {
136 | retVal = obj.value.lccacheNonJSONValue;
137 | } else {
138 | retVal = obj.value;
139 | }
140 | }
141 | } else {
142 | // No expiration was specified. Just return what we found.
143 | if (obj.value.lccacheNonJSONValue != null) {
144 | retVal = obj.value.lccacheNonJSONValue;
145 | } else {
146 | retVal = obj.value;
147 | }
148 | }
149 | }
150 | });
151 | });
152 |
153 | return retVal;
154 | },
155 |
156 | /**
157 | * Removes a value from Lawnchair.
158 | * Equivalent to 'delete' in memcache, but that's a keyword in JS.
159 | * @param {string} key
160 | */
161 | remove: function(key) {
162 | if (!supportsStorage) return null;
163 |
164 | Lawnchair(function () {
165 | this.remove(key);
166 | });
167 | }
168 | };
169 | }();
170 |
--------------------------------------------------------------------------------
/js/libs/adapters/gears-sqlite.js:
--------------------------------------------------------------------------------
1 | // init.js directly included to save on include traffic
2 | //
3 | // Copyright 2007, Google Inc.
4 | //
5 | // Redistribution and use in source and binary forms, with or without
6 | // modification, are permitted provided that the following conditions are met:
7 | //
8 | // 1. Redistributions of source code must retain the above copyright notice,
9 | // this list of conditions and the following disclaimer.
10 | // 2. Redistributions in binary form must reproduce the above copyright notice,
11 | // this list of conditions and the following disclaimer in the documentation
12 | // and/or other materials provided with the distribution.
13 | // 3. Neither the name of Google Inc. nor the names of its contributors may be
14 | // used to endorse or promote products derived from this software without
15 | // specific prior written permission.
16 | //
17 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
18 | // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
20 | // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 | // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
23 | // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25 | // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
26 | // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | //
28 | // Sets up google.gears.*, which is *the only* supported way to access Gears.
29 | //
30 | // Circumvent this file at your own risk!
31 | //
32 | // In the future, Gears may automatically define google.gears.* without this
33 | // file. Gears may use these objects to transparently fix bugs and compatibility
34 | // issues. Applications that use the code below will continue to work seamlessly
35 | // when that happens.
36 |
37 | (function() {
38 | // We are already defined. Hooray!
39 | if (window.google && google.gears) {
40 | return;
41 | }
42 |
43 | var factory = null;
44 |
45 | // Firefox
46 | if (typeof GearsFactory != 'undefined') {
47 | factory = new GearsFactory();
48 | } else {
49 | // IE
50 | try {
51 | factory = new ActiveXObject('Gears.Factory');
52 | // privateSetGlobalObject is only required and supported on IE Mobile on
53 | // WinCE.
54 | if (factory.getBuildInfo().indexOf('ie_mobile') != -1) {
55 | factory.privateSetGlobalObject(this);
56 | }
57 | } catch (e) {
58 | // Safari
59 | if ((typeof navigator.mimeTypes != 'undefined')
60 | && navigator.mimeTypes["application/x-googlegears"]) {
61 | factory = document.createElement("object");
62 | factory.style.display = "none";
63 | factory.width = 0;
64 | factory.height = 0;
65 | factory.type = "application/x-googlegears";
66 | document.documentElement.appendChild(factory);
67 | }
68 | }
69 | }
70 |
71 | // *Do not* define any objects if Gears is not installed. This mimics the
72 | // behavior of Gears defining the objects in the future.
73 | if (!factory) {
74 | return;
75 | }
76 |
77 | // Now set up the objects, being careful not to overwrite anything.
78 | //
79 | // Note: In Internet Explorer for Windows Mobile, you can't add properties to
80 | // the window object. However, global objects are automatically added as
81 | // properties of the window object in all browsers.
82 | if (!window.google) {
83 | google = {};
84 | }
85 |
86 | if (!google.gears) {
87 | google.gears = {factory: factory};
88 | }
89 | })();
90 |
91 | /**
92 | * gears sqlite adaptor
93 | *
94 | */
95 | Lawnchair.extend({
96 | init:function(options) {
97 | var that = this;
98 | var merge = that.merge;
99 | var opts = (typeof arguments[0] == 'string') ? {table:options} : options;
100 | this.name = merge('Lawnchair', opts.name);
101 | this.table = merge('field', opts.table);
102 | this.db = google.gears.factory.create('beta.database');
103 | this.db.open(this.name);
104 | this.db.execute('create table if not exists ' + this.table + ' (id NVARCHAR(32) UNIQUE PRIMARY KEY, value TEXT, timestamp REAL)');
105 | },
106 | save:function(obj, callback) {
107 | var that = this;
108 |
109 | var insert = function(obj, callback) {
110 | var id = (obj.key == undefined) ? that.uuid() : obj.key;
111 | delete(obj.key);
112 |
113 | var rs = that.db.execute(
114 | "INSERT INTO " + that.table + " (id, value, timestamp) VALUES (?,?,?)",
115 | [id, that.serialize(obj), that.now()]
116 | );
117 | if (callback != undefined) {
118 | obj.key = id;
119 | callback(obj);
120 | }
121 | };
122 |
123 | var update = function(id, obj, callback) {
124 | that.db.execute(
125 | "UPDATE " + that.table + " SET value=?, timestamp=? WHERE id=?",
126 | [that.serialize(obj), that.now(), id]
127 | );
128 | if (callback != undefined) {
129 | obj.key = id;
130 | callback(obj);
131 | }
132 | };
133 |
134 | if (obj.key == undefined) {
135 | insert(obj, callback);
136 | } else {
137 | this.get(obj.key, function(r) {
138 | var isUpdate = (r != null);
139 |
140 | if (isUpdate) {
141 | var id = obj.key;
142 | delete(obj.key);
143 | update(id, obj, callback);
144 | } else {
145 | insert(obj, callback);
146 | }
147 | });
148 | }
149 |
150 | },
151 | get:function(key, callback) {
152 | var rs = this.db.execute("SELECT * FROM " + this.table + " WHERE id = ?", [key]);
153 |
154 | if (rs.isValidRow()) {
155 | // FIXME need to test null return / empty recordset
156 | var o = this.deserialize(rs.field(1));
157 | o.key = key;
158 | rs.close();
159 | callback(o);
160 | } else {
161 | rs.close();
162 | callback(null);
163 | }
164 | },
165 | all:function(callback) {
166 | var cb = this.terseToVerboseCallback(callback);
167 | var rs = this.db.execute("SELECT * FROM " + this.table);
168 | var r = [];
169 | var o;
170 |
171 | // FIXME need to add 0 len support
172 | //if (results.rows.length == 0 ) {
173 | // cb([]);
174 |
175 | while (rs.isValidRow()) {
176 | o = this.deserialize(rs.field(1));
177 | o.key = rs.field(0);
178 | r.push(o);
179 | rs.next();
180 | }
181 | rs.close();
182 | cb(r);
183 | },
184 | remove:function(keyOrObj, callback) {
185 | this.db.execute(
186 | "DELETE FROM " + this.table + " WHERE id = ?",
187 | [(typeof keyOrObj == 'string') ? keyOrObj : keyOrObj.key]
188 | );
189 | if(callback)
190 | callback();
191 | },
192 | nuke:function(callback) {
193 | this.db.execute("DELETE FROM " + this.table);
194 | if(callback)
195 | callback();
196 | return this;
197 | }
198 | });
199 |
--------------------------------------------------------------------------------
/js/libs/adapters/webkit-sqlite.js:
--------------------------------------------------------------------------------
1 | Lawnchair.adapter('webkit-sqlite', (function () {
2 | // private methods
3 | var fail = function (e, i) { console.log('error in sqlite adaptor!', e, i) }
4 | , now = function () { return new Date() } // FIXME need to use better date fn
5 | // not entirely sure if this is needed...
6 | if (!Function.prototype.bind) {
7 | Function.prototype.bind = function( obj ) {
8 | var slice = [].slice
9 | , args = slice.call(arguments, 1)
10 | , self = this
11 | , nop = function () {}
12 | , bound = function () {
13 | return self.apply(this instanceof nop ? this : (obj || {}), args.concat(slice.call(arguments)))
14 | }
15 | nop.prototype = self.prototype
16 | bound.prototype = new nop()
17 | return bound
18 | }
19 | }
20 |
21 | // public methods
22 | return {
23 |
24 | valid: function() { return !!(window.openDatabase) },
25 |
26 | init: function (options, callback) {
27 | var that = this
28 | , cb = that.fn(that.name, callback)
29 | , create = "CREATE TABLE IF NOT EXISTS " + this.name + " (id NVARCHAR(32) UNIQUE PRIMARY KEY, value TEXT, timestamp REAL)"
30 | , win = function(){ return cb.call(that, that); }
31 | // open a connection and create the db if it doesn't exist
32 | this.db = openDatabase(this.name, '1.0.0', this.name, 65536)
33 | this.db.transaction(function (t) {
34 | t.executeSql(create, [], win, fail)
35 | })
36 | },
37 |
38 | keys: function (callback) {
39 | var cb = this.lambda(callback)
40 | , that = this
41 | , keys = "SELECT id FROM " + this.name + " ORDER BY timestamp DESC"
42 |
43 | this.db.transaction(function(t) {
44 | var win = function (xxx, results) {
45 | if (results.rows.length == 0 ) {
46 | cb.call(that, [])
47 | } else {
48 | var r = [];
49 | for (var i = 0, l = results.rows.length; i < l; i++) {
50 | r.push(results.rows.item(i).id);
51 | }
52 | cb.call(that, r)
53 | }
54 | }
55 | t.executeSql(keys, [], win, fail)
56 | })
57 | return this
58 | },
59 | // you think thats air you're breathing now?
60 | save: function (obj, callback) {
61 | var that = this
62 | , id = obj.key || that.uuid()
63 | , ins = "INSERT INTO " + this.name + " (value, timestamp, id) VALUES (?,?,?)"
64 | , up = "UPDATE " + this.name + " SET value=?, timestamp=? WHERE id=?"
65 | , win = function () { if (callback) { obj.key = id; that.lambda(callback).call(that, obj) }}
66 | , val = [now(), id]
67 | // existential
68 | that.exists(obj.key, function(exists) {
69 | // transactions are like condoms
70 | that.db.transaction(function(t) {
71 | // TODO move timestamp to a plugin
72 | var insert = function (obj) {
73 | val.unshift(JSON.stringify(obj))
74 | t.executeSql(ins, val, win, fail)
75 | }
76 | // TODO move timestamp to a plugin
77 | var update = function (obj) {
78 | delete(obj.key)
79 | val.unshift(JSON.stringify(obj))
80 | t.executeSql(up, val, win, fail)
81 | }
82 | // pretty
83 | exists ? update(obj) : insert(obj)
84 | })
85 | });
86 | return this
87 | },
88 |
89 | // FIXME this should be a batch insert / just getting the test to pass...
90 | batch: function (objs, cb) {
91 |
92 | var results = []
93 | , done = false
94 | , that = this
95 |
96 | var updateProgress = function(obj) {
97 | results.push(obj)
98 | done = results.length === objs.length
99 | }
100 |
101 | var checkProgress = setInterval(function() {
102 | if (done) {
103 | if (cb) that.lambda(cb).call(that, results)
104 | clearInterval(checkProgress)
105 | }
106 | }, 200)
107 |
108 | for (var i = 0, l = objs.length; i < l; i++)
109 | this.save(objs[i], updateProgress)
110 |
111 | return this
112 | },
113 |
114 | get: function (keyOrArray, cb) {
115 | var that = this
116 | , sql = ''
117 | // batch selects support
118 | if (this.isArray(keyOrArray)) {
119 | sql = 'SELECT id, value FROM ' + this.name + " WHERE id IN ('" + keyOrArray.join("','") + "')"
120 | } else {
121 | sql = 'SELECT id, value FROM ' + this.name + " WHERE id = '" + keyOrArray + "'"
122 | }
123 | // FIXME
124 | // will always loop the results but cleans it up if not a batch return at the end..
125 | // in other words, this could be faster
126 | var win = function (xxx, results) {
127 | var o = null
128 | , r = []
129 | if (results.rows.length) {
130 | for (var i = 0, l = results.rows.length; i < l; i++) {
131 | o = JSON.parse(results.rows.item(i).value)
132 | o.key = results.rows.item(i).id
133 | r.push(o)
134 | }
135 | }
136 | if (!that.isArray(keyOrArray)) r = r.length ? r[0] : null
137 | if (cb) that.lambda(cb).call(that, r)
138 | }
139 | this.db.transaction(function(t){ t.executeSql(sql, [], win, fail) })
140 | return this
141 | },
142 |
143 | exists: function (key, cb) {
144 | var is = "SELECT * FROM " + this.name + " WHERE id = ?"
145 | , that = this
146 | , win = function(xxx, results) { if (cb) that.fn('exists', cb).call(that, (results.rows.length > 0)) }
147 | this.db.transaction(function(t){ t.executeSql(is, [key], win, fail) })
148 | return this
149 | },
150 |
151 | all: function (callback) {
152 | var that = this
153 | , all = "SELECT * FROM " + this.name
154 | , r = []
155 | , cb = this.fn(this.name, callback) || undefined
156 | , win = function (xxx, results) {
157 | if (results.rows.length != 0) {
158 | for (var i = 0, l = results.rows.length; i < l; i++) {
159 | var obj = JSON.parse(results.rows.item(i).value)
160 | obj.key = results.rows.item(i).id
161 | r.push(obj)
162 | }
163 | }
164 | if (cb) cb.call(that, r)
165 | }
166 |
167 | this.db.transaction(function (t) {
168 | t.executeSql(all, [], win, fail)
169 | })
170 | return this
171 | },
172 |
173 | remove: function (keyOrObj, cb) {
174 | var that = this
175 | , key = typeof keyOrObj === 'string' ? keyOrObj : keyOrObj.key
176 | , del = "DELETE FROM " + this.name + " WHERE id = ?"
177 | , win = function () { if (cb) that.lambda(cb).call(that) }
178 |
179 | this.db.transaction( function (t) {
180 | t.executeSql(del, [key], win, fail);
181 | });
182 |
183 | return this;
184 | },
185 |
186 | nuke: function (cb) {
187 | var nuke = "DELETE FROM " + this.name
188 | , that = this
189 | , win = cb ? function() { that.lambda(cb).call(that) } : function(){}
190 | this.db.transaction(function (t) {
191 | t.executeSql(nuke, [], win, fail)
192 | })
193 | return this
194 | }
195 | //////
196 | }})())
197 |
--------------------------------------------------------------------------------
/js/libs/adapters/indexed-db.js:
--------------------------------------------------------------------------------
1 | /**
2 | * indexed db adapter
3 | * ===
4 | * - originally authored by Vivian Li
5 | *
6 | */
7 |
8 | Lawnchair.adapter('indexed-db', (function(){
9 |
10 | function fail(e, i) { console.log('error in indexed-db adapter!', e, i); debugger; } ;
11 |
12 | function getIDB(){
13 | return window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.oIndexedDB || window.msIndexedDB;
14 | };
15 |
16 |
17 |
18 | return {
19 |
20 | valid: function() { return !!getIDB(); },
21 |
22 | init:function(options, callback) {
23 | this.idb = getIDB();
24 | this.waiting = [];
25 | var request = this.idb.open(this.name);
26 | var self = this;
27 | var cb = self.fn(self.name, callback);
28 | var win = function(){ return cb.call(self, self); }
29 |
30 | request.onsuccess = function(event) {
31 | self.db = request.result;
32 |
33 | if(self.db.version != "1.0") {
34 | var setVrequest = self.db.setVersion("1.0");
35 | // onsuccess is the only place we can create Object Stores
36 | setVrequest.onsuccess = function(e) {
37 | self.store = self.db.createObjectStore("teststore", { autoIncrement: true} );
38 | for (var i = 0; i < self.waiting.length; i++) {
39 | self.waiting[i].call(self);
40 | }
41 | self.waiting = [];
42 | win();
43 | };
44 | setVrequest.onerror = function(e) {
45 | console.log("Failed to create objectstore " + e);
46 | fail(e);
47 | }
48 | } else {
49 | self.store = {};
50 | for (var i = 0; i < self.waiting.length; i++) {
51 | self.waiting[i].call(self);
52 | }
53 | self.waiting = [];
54 | win();
55 | }
56 | }
57 | request.onerror = fail;
58 | },
59 |
60 | save:function(obj, callback) {
61 | if(!this.store) {
62 | this.waiting.push(function() {
63 | this.save(obj, callback);
64 | });
65 | return;
66 | }
67 |
68 | var self = this;
69 | var win = function (e) { if (callback) { obj.key = e.target.result; self.lambda(callback).call(self, obj) }};
70 |
71 | var trans = this.db.transaction(["teststore"], webkitIDBTransaction.READ_WRITE, 0);
72 | var store = trans.objectStore("teststore");
73 | var request = obj.key ? store.put(obj, obj.key) : store.put(obj);
74 |
75 | request.onsuccess = win;
76 | request.onerror = fail;
77 |
78 | return this;
79 | },
80 |
81 | // FIXME this should be a batch insert / just getting the test to pass...
82 | batch: function (objs, cb) {
83 |
84 | var results = []
85 | , done = false
86 | , self = this
87 |
88 | var updateProgress = function(obj) {
89 | results.push(obj)
90 | done = results.length === objs.length
91 | }
92 |
93 | var checkProgress = setInterval(function() {
94 | if (done) {
95 | if (cb) self.lambda(cb).call(self, results)
96 | clearInterval(checkProgress)
97 | }
98 | }, 200)
99 |
100 | for (var i = 0, l = objs.length; i < l; i++)
101 | this.save(objs[i], updateProgress)
102 |
103 | return this
104 | },
105 |
106 |
107 | get:function(key, callback) {
108 | if(!this.store) {
109 | this.waiting.push(function() {
110 | this.get(key, callback);
111 | });
112 | return;
113 | }
114 |
115 |
116 | var self = this;
117 | var win = function (e) { if (callback) { self.lambda(callback).call(self, e.target.result) }};
118 |
119 |
120 | if (!this.isArray(key)){
121 | var req = this.db.transaction("teststore").objectStore("teststore").get(key);
122 |
123 | req.onsuccess = win;
124 | req.onerror = function(event) {
125 | console.log("Failed to find " + key);
126 | fail(event);
127 | };
128 |
129 | // FIXME: again the setInterval solution to async callbacks..
130 | } else {
131 |
132 | // note: these are hosted.
133 | var results = []
134 | , done = false
135 | , keys = key
136 |
137 | var updateProgress = function(obj) {
138 | results.push(obj)
139 | done = results.length === keys.length
140 | }
141 |
142 | var checkProgress = setInterval(function() {
143 | if (done) {
144 | if (callback) self.lambda(callback).call(self, results)
145 | clearInterval(checkProgress)
146 | }
147 | }, 200)
148 |
149 | for (var i = 0, l = keys.length; i < l; i++)
150 | this.get(keys[i], updateProgress)
151 |
152 | }
153 |
154 | return this;
155 | },
156 |
157 | all:function(callback) {
158 | if(!this.store) {
159 | this.waiting.push(function() {
160 | this.all(callback);
161 | });
162 | return;
163 | }
164 | var cb = this.fn(this.name, callback) || undefined;
165 | var self = this;
166 | var objectStore = this.db.transaction("teststore").objectStore("teststore");
167 | var toReturn = [];
168 | objectStore.openCursor().onsuccess = function(event) {
169 | var cursor = event.target.result;
170 | if (cursor) {
171 | toReturn.push(cursor.value);
172 | cursor.continue();
173 | }
174 | else {
175 | if (cb) cb.call(self, toReturn);
176 | }
177 | };
178 | return this;
179 | },
180 |
181 | remove:function(keyOrObj, callback) {
182 | if(!this.store) {
183 | this.waiting.push(function() {
184 | this.remove(keyOrObj, callback);
185 | });
186 | return;
187 | }
188 | if (typeof keyOrObj == "object") {
189 | keyOrObj = keyOrObj.key;
190 | }
191 | var self = this;
192 | var win = function () { if (callback) self.lambda(callback).call(self) };
193 |
194 | var request = this.db.transaction(["teststore"], webkitIDBTransaction.READ_WRITE).objectStore("teststore").delete(keyOrObj);
195 | request.onsuccess = win;
196 | request.onerror = fail;
197 | return this;
198 | },
199 |
200 | nuke:function(callback) {
201 | if(!this.store) {
202 | this.waiting.push(function() {
203 | this.nuke(callback);
204 | });
205 | return;
206 | }
207 |
208 | var self = this
209 | , win = callback ? function() { self.lambda(callback).call(self) } : function(){};
210 |
211 | try {
212 | this.db
213 | .transaction(["teststore"], webkitIDBTransaction.READ_WRITE)
214 | .objectStore("teststore").clear().onsuccess = win;
215 |
216 | } catch(e) {
217 | fail();
218 | }
219 | return this;
220 | }
221 |
222 | };
223 |
224 | })());
--------------------------------------------------------------------------------