diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000..0832943f --- /dev/null +++ b/.jshintrc @@ -0,0 +1,58 @@ +{ + "browser": true, + "node": true, + "maxerr": 999, + + "asi": true, + "bitwise": true, + "boss": false, + "camelcase": true, + "curly": false, + "debug": false, + "eqeqeq": true, + "eqnull": true, + "es3": true, + "es5": false, + "evil": true, + "expr": true, + "forin": true, + "freeze": true, + "funcscope": false, + "globalstrict": false, + "immed": true, + "indent": 2, + "iterator": false, + "lastsemic": true, + "latedef": false, + "laxbreak": true, + "laxcomma": true, + "loopfunc": false, + "maxcomplexity": false, + "maxdepth": 6, + "maxlen": 80, + "maxparams": 4, + "maxstatements": false, + "moz": false, + "newcap": true, + "noarg": true, + "nocomma": false, + "noempty": true, + "nonbsp": true, + "nonew": true, + "notypeof": true, + "noyield": false, + "phantom": false, + "plusplus": false, + "proto": false, + "quotmark": "single", + "scripturl": false, + "shadow": true, + "singleGroups": false, + "strict": true, + "sub": false, + "supernew": false, + "undef": true, + "unused": true, + "validthis": false, + "withstmt": false +} \ No newline at end of file diff --git a/lib/lawnchair.js b/lib/lawnchair.js index abd15948..3c78c25d 100644 --- a/lib/lawnchair.js +++ b/lib/lawnchair.js @@ -1,97 +1,109 @@ +/* exported Lawnchair */ +(function(global){ + 'use strict' + /** * Lawnchair! - * --- - * clientside json store + * --- + * clientside json store * */ var Lawnchair = function (options, callback) { - // ensure Lawnchair was called as a constructor - if (!(this instanceof Lawnchair)) return new Lawnchair(options, callback); - - // lawnchair requires json - if (!JSON) throw 'JSON unavailable! Include http://www.json.org/json2.js to fix.' - // options are optional; callback is not - if (arguments.length <= 2) { - callback = (typeof arguments[0] === 'function') ? arguments[0] : arguments[1]; - options = (typeof arguments[0] === 'function') ? {} : arguments[0] || {}; - } else { - throw 'Incorrect # of ctor args!' + // ensure Lawnchair was called as a constructor + if (!(this instanceof Lawnchair)) return new Lawnchair(options, callback); + + // lawnchair requires json + if (!JSON) throw 'JSON unavailable! Include '+ + 'http://www.json.org/json2.js to fix.' + // options are optional; callback is not + if (arguments.length <= 2) { + var functionFirst = typeof arguments[0] === 'function' + callback = functionFirst ? arguments[0] : arguments[1]; + options = functionFirst ? {} : arguments[0] || {}; + } else { + throw 'Incorrect # of ctor args!' + } + + // default configuration + this.record = options.record || 'record' // default for records + this.name = options.name || 'records' // default name for underlying store + + // mixin first valid adapter + var adapter + // if the adapter is passed in we try to load that only + if (options.adapter) { + + // the argument passed should be an array of prefered adapters + // if it is not, we convert it + if (typeof(options.adapter) === 'string') { + options.adapter = [options.adapter]; } - - // default configuration - this.record = options.record || 'record' // default for records - this.name = options.name || 'records' // default name for underlying store - - // mixin first valid adapter - var adapter - // if the adapter is passed in we try to load that only - if (options.adapter) { - - // the argument passed should be an array of prefered adapters - // if it is not, we convert it - if(typeof(options.adapter) === 'string'){ - options.adapter = [options.adapter]; - } - - // iterates over the array of passed adapters - for(var j = 0, k = options.adapter.length; j < k; j++){ - - // itirates over the array of available adapters - for (var i = Lawnchair.adapters.length-1; i >= 0; i--) { - if (Lawnchair.adapters[i].adapter === options.adapter[j]) { - adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined; - if (adapter) break - } - } - if (adapter) break - } - - // otherwise find the first valid adapter for this env - } - else { - for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) { - adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined - if (adapter) break + + // iterates over the array of passed adapters + for (var j = 0, k = options.adapter.length; j < k; j++) { + + // itirates over the array of available adapters + for (var i = Lawnchair.adapters.length-1; i >= 0; i--) { + if (Lawnchair.adapters[i].adapter === options.adapter[j]) { + adapter = Lawnchair.adapters[i].valid() + ? Lawnchair.adapters[i] + : undefined; + if (adapter) break } - } - - // we have failed - if (!adapter) throw 'No valid adapter.' - - // yay! mixin the adapter - for (var j in adapter) - this[j] = adapter[j] - - // call init for each mixed in plugin - for (var i = 0, l = Lawnchair.plugins.length; i < l; i++) - Lawnchair.plugins[i].call(this) - - // init the adapter - this.init(options, callback) + } + if (adapter) break + } + + // otherwise find the first valid adapter for this env + } + else { + for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) { + adapter = Lawnchair.adapters[i].valid() + ? Lawnchair.adapters[i] + : undefined + if (adapter) break + } + } + + // we have failed + if (!adapter) throw 'No valid adapter.' + + // yay! mixin the adapter + for (var j in adapter) if (adapter.hasOwnProperty(j)) + this[j] = adapter[j] + + // call init for each mixed in plugin + for (var i = 0, l = Lawnchair.plugins.length; i < l; i++) + Lawnchair.plugins[i].call(this) + + // init the adapter + this.init(options, callback) } -Lawnchair.adapters = [] +Lawnchair.adapters = [] -/** +/** * queues an adapter for mixin * === * - ensures an adapter conforms to a specific interface * */ Lawnchair.adapter = function (id, obj) { - // add the adapter id to the adapter obj - // ugly here for a cleaner dsl for implementing adapters - obj['adapter'] = id - // methods required to implement a lawnchair adapter - var implementing = 'adapter valid init keys save batch get exists all remove nuke'.split(' ') - , indexOf = this.prototype.indexOf - // mix in the adapter - for (var i in obj) { - if (indexOf(implementing, i) === -1) throw 'Invalid adapter! Nonstandard method: ' + i - } - // if we made it this far the adapter interface is valid - // insert the new adapter as the preferred adapter - Lawnchair.adapters.splice(0,0,obj) + // add the adapter id to the adapter obj + // ugly here for a cleaner dsl for implementing adapters + obj.adapter = id + // methods required to implement a lawnchair adapter + var implementing = 'adapter valid init keys save batch '+ + 'get exists all remove nuke'.split(' ') + , indexOf = this.prototype.indexOf + // mix in the adapter + for (var i in obj) { + if (indexOf(implementing, i) === -1) + throw 'Invalid adapter! Nonstandard method: ' + i + } + // if we made it this far the adapter interface is valid + // insert the new adapter as the preferred adapter + Lawnchair.adapters.splice(0, 0, obj) } Lawnchair.plugins = [] @@ -99,12 +111,12 @@ Lawnchair.plugins = [] /** * generic shallow extension for plugins * === - * - if an init method is found it registers it to be called when the lawnchair is inited - * - yes we could use hasOwnProp but nobody here is an asshole - */ + * - if an init method is found it registers it to be called + * when the lawnchair is inited + */ Lawnchair.plugin = function (obj) { - for (var i in obj) - i === 'init' ? Lawnchair.plugins.push(obj[i]) : this.prototype[i] = obj[i] + for (var i in obj) if (obj.hasOwnProperty(i)) + i === 'init' ? Lawnchair.plugins.push(obj[i]) : this.prototype[i] = obj[i] } /** @@ -112,53 +124,59 @@ Lawnchair.plugin = function (obj) { * */ Lawnchair.prototype = { + isArray: Array.isArray || function (o) { + return Object.prototype.toString.call(o) === '[object Array]' + }, - isArray: Array.isArray || function(o) { return Object.prototype.toString.call(o) === '[object Array]' }, - - /** - * this code exists for ie8... for more background see: - * http://www.flickr.com/photos/westcoastlogic/5955365742/in/photostream - */ - indexOf: function(ary, item, i, l) { - if (ary.indexOf) return ary.indexOf(item) - for (i = 0, l = ary.length; i < l; i++) if (ary[i] === item) return i - return -1 - }, + /** + * this code exists for ie8... for more background see: + * http://www.flickr.com/photos/westcoastlogic/5955365742/in/photostream + */ + indexOf: function (ary, item, i, l) { + if (ary.indexOf) return ary.indexOf(item) + for (i = 0, l = ary.length; i < l; i++) if (ary[i] === item) return i + return -1 + }, - // awesome shorthand callbacks as strings. this is shameless theft from dojo. - lambda: function (callback) { - return this.fn(this.record, callback) - }, + // awesome shorthand callbacks as strings. this is shameless theft from dojo. + lambda: function (callback) { + return this.fn(this.record, callback) + }, - // first stab at named parameters for terse callbacks; dojo: first != best // ;D - fn: function (name, callback) { - return typeof callback == 'string' ? new Function(name, callback) : callback - }, + // first stab at named parameters for terse callbacks; + // dojo: first != best // ;D + fn: function (name, callback) { + return typeof callback === 'string' + ? new Function(name, callback) + : callback + }, - // returns a unique identifier (by way of Backbone.localStorage.js) - // TODO investigate smaller UUIDs to cut on storage cost - uuid: function () { - var S4 = function () { - return (((1+Math.random())*0x10000)|0).toString(16).substring(1); - } - return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); - }, + // returns a unique identifier (by way of Backbone.localStorage.js) + // TODO investigate smaller UUIDs to cut on storage cost + uuid: function () { + var S4 = function () { + /* jshint bitwise:false */ + return (((1+Math.random())*0x10000)|0).toString(16).substring(1); + } + return (S4()+S4()+'-'+S4()+'-'+S4()+'-'+S4()+'-'+S4()+S4()+S4()); + }, - // a classic iterator - each: function (callback) { - var cb = this.lambda(callback) - // iterate from chain - if (this.__results) { - for (var i = 0, l = this.__results.length; i < l; i++) cb.call(this, this.__results[i], i) - } - // otherwise iterate the entire collection - else { - this.all(function(r) { - for (var i = 0, l = r.length; i < l; i++) cb.call(this, r[i], i) - }) - } - return this + // a classic iterator + each: function (callback) { + var cb = this.lambda(callback) + // iterate from chain + if (this.__results) { + for (var i = 0, l = this.__results.length; i < l; i++) + cb.call(this, this.__results[i], i) } + // otherwise iterate the entire collection + else { + this.all(function (r) { + for (var i = 0, l = r.length; i < l; i++) cb.call(this, r[i], i) + }) + } + return this + } // -- }; @@ -167,339 +185,372 @@ Lawnchair.prototype = { */ if (typeof module !== 'undefined' && module.exports) { module.exports = Lawnchair; +} else { + global.Lawnchair = Lawnchair; } -// window.name code courtesy Remy Sharp: http://24ways.org/2009/breaking-out-the-edges-of-the-browser -Lawnchair.adapter('window-name', (function() { - if (typeof window==='undefined') { - window = { top: { } }; // node/optimizer compatibility - } - // edited from the original here by elsigh - // Some sites store JSON data in window.top.name, but some folks (twitter on iPad) - // put simple strings in there - we should make sure not to cause a SyntaxError. - var data = {} - try { - data = JSON.parse(window.top.name) - } catch (e) {} - - - return { +})(this);/* global Lawnchair */ +/* global window:true */ - valid: function () { - return typeof window.top.name != 'undefined' - }, - - init: function (options, callback) { - data[this.name] = data[this.name] || {index:[],store:{}} - this.index = data[this.name].index - this.store = data[this.name].store - this.fn(this.name, callback).call(this, this) - return this - }, - - keys: function (callback) { - this.fn('keys', callback).call(this, this.index) - return this - }, - - save: function (obj, cb) { - // data[key] = value + ''; // force to string - // window.top.name = JSON.stringify(data); - var key = obj.key || this.uuid() - this.exists(key, function(exists) { - if (!exists) { - if (obj.key) delete obj.key - this.index.push(key) - } - this.store[key] = obj - - try { - window.top.name = JSON.stringify(data) // TODO wow, this is the only diff from the memory adapter - } catch(e) { - // restore index/store to previous value before JSON exception - if (!exists) { - this.index.pop(); - delete this.store[key]; - } - throw e; - } - - if (cb) { - obj.key = key - this.lambda(cb).call(this, obj) - } - }) - return this - }, - - batch: function (objs, cb) { - var r = [] - for (var i = 0, l = objs.length; i < l; i++) { - this.save(objs[i], function(record) { - r.push(record) - }) - } - if (cb) this.lambda(cb).call(this, r) - return this - }, - - get: function (keyOrArray, cb) { - var r; - if (this.isArray(keyOrArray)) { - r = [] - for (var i = 0, l = keyOrArray.length; i < l; i++) { - r.push(this.store[keyOrArray[i]]) - } - } else { - r = this.store[keyOrArray] - if (r) r.key = keyOrArray - } - if (cb) this.lambda(cb).call(this, r) - return this - }, - - exists: function (key, cb) { - this.lambda(cb).call(this, !!(this.store[key])) - return this - }, - - all: function (cb) { - var r = [] - for (var i = 0, l = this.index.length; i < l; i++) { - var obj = this.store[this.index[i]] - obj.key = this.index[i] - r.push(obj) - } - this.fn(this.name, cb).call(this, r) - return this - }, - - remove: function (keyOrArray, cb) { - var del = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray] - for (var i = 0, l = del.length; i < l; i++) { - var key = del[i].key ? del[i].key : del[i] - var where = this.indexOf(this.index, key) - if (where < 0) continue /* key not present */ - delete this.store[key] - this.index.splice(where, 1) - } - window.top.name = JSON.stringify(data) - if (cb) this.lambda(cb).call(this) - return this - }, - - nuke: function (cb) { - this.store = data[this.name].store = {} - this.index = data[this.name].index = [] - window.top.name = JSON.stringify(data) - if (cb) this.lambda(cb).call(this) - return this +// window.name code courtesy Remy Sharp +// see: http://24ways.org/2009/breaking-out-the-edges-of-the-browser +Lawnchair.adapter('window-name', (function () { + 'use strict' + + if (typeof window === 'undefined') { + window = { top: { } }; // node/optimizer compatibility + } + + // edited from the original here by elsigh. Some sites store + // JSON data in window.top.name, but some folks (twitter on + // iPad) put simple strings in there - we should make sure not + // to cause a SyntaxError. + var data = {} + try { + data = JSON.parse(window.top.name) + } catch (e) {} + + return { + + valid: function () { + return typeof window.top.name !== 'undefined' + }, + + init: function (options, callback) { + data[this.name] = data[this.name] || { index:[], store:{} } + this.index = data[this.name].index + this.store = data[this.name].store + this.fn(this.name, callback).call(this, this) + return this + }, + + keys: function (callback) { + this.fn('keys', callback).call(this, this.index) + return this + }, + + save: function (obj, cb) { + // data[key] = value + ''; // force to string + // window.top.name = JSON.stringify(data); + var key = obj.key || this.uuid() + this.exists(key, function (exists) { + if (!exists) { + if (obj.key) delete obj.key + this.index.push(key) + } + this.store[key] = obj + + try { + // TODO wow, this is the only diff from the memory adapter + window.top.name = JSON.stringify(data) + } catch (e) { + // restore index/store to previous value before JSON exception + if (!exists) { + this.index.pop(); + delete this.store[key]; + } + throw e; + } + + if (cb) { + obj.key = key + this.lambda(cb).call(this, obj) } + }) + return this + }, + + batch: function (objs, cb) { + var r = [] + for (var i = 0, l = objs.length; i < l; i++) { + /* jshint loopfunc:true */ + this.save(objs[i], function (record) { + r.push(record) + }) + /* jshint loopfunc:false */ + } + if (cb) this.lambda(cb).call(this, r) + return this + }, + + get: function (keyOrArray, cb) { + var r; + if (this.isArray(keyOrArray)) { + r = [] + for (var i = 0, l = keyOrArray.length; i < l; i++) { + r.push(this.store[keyOrArray[i]]) + } + } else { + r = this.store[keyOrArray] + if (r) r.key = keyOrArray + } + if (cb) this.lambda(cb).call(this, r) + return this + }, + + exists: function (key, cb) { + this.lambda(cb).call(this, !!(this.store[key])) + return this + }, + + all: function (cb) { + var r = [] + for (var i = 0, l = this.index.length; i < l; i++) { + var obj = this.store[this.index[i]] + obj.key = this.index[i] + r.push(obj) + } + this.fn(this.name, cb).call(this, r) + return this + }, + + remove: function (keyOrArray, cb) { + var del = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray] + for (var i = 0, l = del.length; i < l; i++) { + var key = del[i].key ? del[i].key : del[i] + var where = this.indexOf(this.index, key) + if (where < 0) continue /* key not present */ + delete this.store[key] + this.index.splice(where, 1) + } + window.top.name = JSON.stringify(data) + if (cb) this.lambda(cb).call(this) + return this + }, + + nuke: function (cb) { + this.store = data[this.name].store = {} + this.index = data[this.name].index = [] + window.top.name = JSON.stringify(data) + if (cb) this.lambda(cb).call(this) + return this } + } + ///// })()); +/* global Lawnchair */ + /** - * dom storage adapter - * === + * dom storage adapter + * === * - originally authored by Joseph Pecoraro * - */ + */ // // TODO does it make sense to be chainable all over the place? -// chainable: nuke, remove, all, get, save, all +// chainable: nuke, remove, all, get, save, all // not chainable: valid, keys // -Lawnchair.adapter('dom', (function() { - var storage = window.localStorage - // the indexer is an encapsulation of the helpers needed to keep an ordered index of the keys - var indexer = function(name) { - return { - // the key - key: name + '._index_', - // returns the index - all: function() { - var a = storage.getItem(JSON.stringify(this.key)) - if (a) { - a = JSON.parse(a) - } - if (a === null) storage.setItem(JSON.stringify(this.key), JSON.stringify([])) // lazy init - return JSON.parse(storage.getItem(JSON.stringify(this.key))) - }, - // adds a key to the index - add: function (key) { - var a = this.all() - a.push(key) - storage.setItem(JSON.stringify(this.key), JSON.stringify(a)) - }, - // deletes a key from the index - del: function (key) { - var a = this.all(), r = [] - // FIXME this is crazy inefficient but I'm in a strata meeting and half concentrating - for (var i = 0, l = a.length; i < l; i++) { - if (a[i] != key) r.push(a[i]) - } - storage.setItem(JSON.stringify(this.key), JSON.stringify(r)) - }, - // returns index for a key - find: function (key) { - var a = this.all() - for (var i = 0, l = a.length; i < l; i++) { - if (key === a[i]) return i - } - return false - } +Lawnchair.adapter('dom', (function () { + 'use strict' + + var storage = window.localStorage + + // the indexer is an encapsulation of the helpers needed to + // keep an ordered index of the keys + var indexer = function (name) { + return { + // the key + key: name + '._index_', + + // returns the index + all: function () { + var a = storage.getItem(JSON.stringify(this.key)) + if (a) { + a = JSON.parse(a) + } + // lazy init + if (a === null) storage.setItem( + JSON.stringify(this.key), + JSON.stringify([]) + ) + return JSON.parse(storage.getItem(JSON.stringify(this.key))) + }, + + // adds a key to the index + add: function (key) { + var a = this.all() + a.push(key) + storage.setItem(JSON.stringify(this.key), JSON.stringify(a)) + }, + + // deletes a key from the index + del: function (key) { + var a = this.all(), r = [] + // FIXME this is crazy inefficient but I'm in a strata + // meeting and half concentrating + for (var i = 0, l = a.length; i < l; i++) { + if (a[i] !== key) r.push(a[i]) + } + storage.setItem(JSON.stringify(this.key), JSON.stringify(r)) + }, + + // returns index for a key + find: function (key) { + var a = this.all() + for (var i = 0, l = a.length; i < l; i++) { + if (key === a[i]) return i } + return false + } } - - // adapter api - return { - - // ensure we are in an env with localStorage - valid: function () { - return !!storage && function() { - // in mobile safari if safe browsing is enabled, window.storage - // is defined but setItem calls throw exceptions. - var success = true - var value = Math.random() - try { - storage.setItem(value, value) - } catch (e) { - success = false - } - storage.removeItem(value) - return success - }() - }, - - init: function (options, callback) { - this.indexer = indexer(this.name) - if (callback) this.fn(this.name, callback).call(this, this) - }, - - save: function (obj, callback) { - var key = obj.key ? this.name + '.' + obj.key : this.name + '.' + this.uuid() - // now we kil the key and use it in the store colleciton - delete obj.key; - storage.setItem(key, JSON.stringify(obj)) - // if the key is not in the index push it on - if (this.indexer.find(key) === false) this.indexer.add(key) - obj.key = key.slice(this.name.length + 1) - if (callback) { - this.lambda(callback).call(this, obj) - } - return this - }, - - batch: function (ary, callback) { - var saved = [] - // not particularily efficient but this is more for sqlite situations - for (var i = 0, l = ary.length; i < l; i++) { - this.save(ary[i], function(r){ - saved.push(r) - }) - } - if (callback) this.lambda(callback).call(this, saved) - return this - }, - - // accepts [options], callback - keys: function(callback) { + } + + // adapter api + return { + // ensure we are in an env with localStorage + valid: function () { + return !!storage && (function () { + // in mobile safari if safe browsing is enabled, window.storage + // is defined but setItem calls throw exceptions. + var success = true + var value = Math.random() + try { + storage.setItem(value, value) + } catch (e) { + success = false + } + storage.removeItem(value) + return success + }()) + }, + + init: function (options, callback) { + this.indexer = indexer(this.name) + if (callback) this.fn(this.name, callback).call(this, this) + }, + + save: function (obj, callback) { + var key = this.name + '.' + (obj.key || this.uuid()) + // now we kil the key and use it in the store colleciton + delete obj.key; + storage.setItem(key, JSON.stringify(obj)) + // if the key is not in the index push it on + if (this.indexer.find(key) === false) this.indexer.add(key) + obj.key = key.slice(this.name.length + 1) + if (callback) { + this.lambda(callback).call(this, obj) + } + return this + }, + + batch: function (ary, callback) { + var saved = [] + // not particularily efficient but this is more for sqlite situations + for (var i = 0, l = ary.length; i < l; i++) { + /* jshint loopfunc:true */ + this.save(ary[i], function (r) { + saved.push(r) + }) + /* jshint loopfunc:false */ + } + if (callback) this.lambda(callback).call(this, saved) + return this + }, + + // accepts [options], callback + keys: function (callback) { + if (callback) { + var name = this.name + var indices = this.indexer.all(); + var keys = []; + // Checking for the support of map. + if (Array.prototype.map) { + keys = indices.map(function (r) { return r.replace(name + '.', '') }) + } else { + for (var key in indices) if (indices.hasOwnProperty(key)) { + keys.push(key.replace(name + '.', '')); + } + } + this.fn('keys', callback).call(this, keys) + } + return this // TODO options for limit/offset, return promise + }, + + get: function (key, callback) { + if (this.isArray(key)) { + var r = [] + for (var i = 0, l = key.length; i < l; i++) { + var k = this.name + '.' + key[i] + var obj = storage.getItem(k) + if (obj) { + obj = JSON.parse(obj) + obj.key = key[i] + } + r.push(obj) + } + if (callback) this.lambda(callback).call(this, r) + } else { + var k = this.name + '.' + key + var obj = storage.getItem(k) + if (obj) { + obj = JSON.parse(obj) + obj.key = key + } + if (callback) this.lambda(callback).call(this, obj) + } + return this + }, + + exists: function (key, cb) { + var exists = this.indexer.find(this.name+'.'+key) !== false + this.lambda(cb).call(this, exists); + return this; + }, + + // NOTE adapters cannot set this.__results but plugins do + // this probably should be reviewed + all: function (callback) { + var idx = this.indexer.all() + , r = [] + , o + , k + for (var i = 0, l = idx.length; i < l; i++) { + k = idx[i] // v + o = JSON.parse(storage.getItem(k)) + o.key = k.replace(this.name + '.', '') + r.push(o) + } + if (callback) this.fn(this.name, callback).call(this, r) + return this + }, + + remove: function (keyOrArray, callback) { + var self = this; + if (this.isArray(keyOrArray)) { + // batch remove + var i, done = keyOrArray.length; + var removeOne = function (i) { + self.remove(keyOrArray[i], function () { + if ((--done) > 0) { return; } if (callback) { - var name = this.name - var indices = this.indexer.all(); - var keys = []; - //Checking for the support of map. - if(Array.prototype.map) { - keys = indices.map(function(r){ return r.replace(name + '.', '') }) - } else { - for (var key in indices) { - keys.push(key.replace(name + '.', '')); - } - } - this.fn('keys', callback).call(this, keys) - } - return this // TODO options for limit/offset, return promise - }, - - get: function (key, callback) { - if (this.isArray(key)) { - var r = [] - for (var i = 0, l = key.length; i < l; i++) { - var k = this.name + '.' + key[i] - var obj = storage.getItem(k) - if (obj) { - obj = JSON.parse(obj) - obj.key = key[i] - } - r.push(obj) - } - if (callback) this.lambda(callback).call(this, r) - } else { - var k = this.name + '.' + key - var obj = storage.getItem(k) - if (obj) { - obj = JSON.parse(obj) - obj.key = key - } - if (callback) this.lambda(callback).call(this, obj) - } - return this - }, - - exists: function (key, cb) { - var exists = this.indexer.find(this.name+'.'+key) === false ? false : true ; - this.lambda(cb).call(this, exists); - return this; - }, - // NOTE adapters cannot set this.__results but plugins do - // this probably should be reviewed - all: function (callback) { - var idx = this.indexer.all() - , r = [] - , o - , k - for (var i = 0, l = idx.length; i < l; i++) { - k = idx[i] //v - o = JSON.parse(storage.getItem(k)) - o.key = k.replace(this.name + '.', '') - r.push(o) - } - if (callback) this.fn(this.name, callback).call(this, r) - return this - }, - - remove: function (keyOrArray, callback) { - var self = this; - if (this.isArray(keyOrArray)) { - // batch remove - var i, done = keyOrArray.length; - var removeOne = function(i) { - self.remove(keyOrArray[i], function() { - if ((--done) > 0) { return; } - if (callback) { - self.lambda(callback).call(self); - } - }); - }; - for (i=0; i < keyOrArray.length; i++) - removeOne(i); - return this; + self.lambda(callback).call(self); } - var key = this.name + '.' + - ((keyOrArray.key) ? keyOrArray.key : keyOrArray) - this.indexer.del(key) - storage.removeItem(key) - if (callback) this.lambda(callback).call(this) - return this - }, - - nuke: function (callback) { - this.all(function(r) { - for (var i = 0, l = r.length; i < l; i++) { - this.remove(r[i]); - } - if (callback) this.lambda(callback).call(this) - }) - return this + }); + }; + for (i = 0; i < keyOrArray.length; i++) + removeOne(i); + return this; + } + var key = this.name + '.' + + ((keyOrArray.key) ? keyOrArray.key : keyOrArray) + this.indexer.del(key) + storage.removeItem(key) + if (callback) this.lambda(callback).call(this) + return this + }, + + nuke: function (callback) { + this.all(function (r) { + for (var i = 0, l = r.length; i < l; i++) { + this.remove(r[i]); } -}})()); + if (callback) this.lambda(callback).call(this) + }) + return this + } + + } + +})()); diff --git a/makefile b/makefile index 654639c6..5251e744 100644 --- a/makefile +++ b/makefile @@ -2,10 +2,10 @@ VERSION = "0.6.1" PRIMARY_ADAPTER = "dom" SECONDARY_ADAPTER = "window-name" -default: clean build test -release: clean build min +default: clean build lint test +release: clean build lint min -clean: +clean: rm -rf ./lib rm -rf ./test/lib/lawnchair* @@ -16,17 +16,17 @@ build: cat ./src/adapters/$(SECONDARY_ADAPTER).js >> ./lib/lawnchair-$(VERSION).js cat ./src/adapters/$(PRIMARY_ADAPTER).js >> ./lib/lawnchair-$(VERSION).js cp ./lib/lawnchair-$(VERSION).js ./lib/lawnchair.js # copied for tests in site - # plugins business + # plugins business cp ./src/plugins/aggregation.js ./lib/lawnchair-aggregation-$(VERSION).js cp ./src/plugins/callbacks.js ./lib/lawnchair-callbacks-$(VERSION).js cp ./src/plugins/pagination.js ./lib/lawnchair-pagination-$(VERSION).js cp ./src/plugins/query.js ./lib/lawnchair-query-$(VERSION).js - # copy plugins in clean for tests,,, + # copy plugins in clean for tests,,, cp ./lib/lawnchair-aggregation-$(VERSION).js ./lib/lawnchair-aggregation.js cp ./lib/lawnchair-callbacks-$(VERSION).js ./lib/lawnchair-callbacks.js cp ./lib/lawnchair-pagination-$(VERSION).js ./lib/lawnchair-pagination.js cp ./lib/lawnchair-query-$(VERSION).js ./lib/lawnchair-query.js - # build adapters + # build adapters cp ./src/adapters/memory.js ./lib/lawnchair-adapter-memory-$(VERSION).js cp ./src/adapters/blackberry-persistent-storage.js ./lib/lawnchair-adapter-blackberry-persistent-storage-$(VERSION).js cp ./src/adapters/gears-sqlite.js ./lib/lawnchair-adapter-gears-sqlite-$(VERSION).js @@ -39,18 +39,21 @@ build: min: java -jar ./util/compiler.jar --js ./lib/lawnchair-$(VERSION).js > ./lib/lawnchair-$(VERSION).min.js +lint: + jshint src + test: cp ./lib/lawnchair-$(VERSION).js ./test/lib/lawnchair.js cp ./lib/lawnchair-$(VERSION).js ./test/lib/lawnchair-aggregation.js cp ./lib/lawnchair-$(VERSION).js ./test/lib/lawnchair-callbacks.js cp ./lib/lawnchair-$(VERSION).js ./test/lib/lawnchair-pagination.js cp ./lib/lawnchair-$(VERSION).js ./test/lib/lawnchair-query.js - + cat ./src/plugins/aggregation.js >> ./test/lib/lawnchair-aggregation.js cat ./src/plugins/callbacks.js >> ./test/lib/lawnchair-callbacks.js cat ./src/plugins/pagination.js >> ./test/lib/lawnchair-pagination.js cat ./src/plugins/query.js >> ./test/lib/lawnchair-query.js - + # copy in adaptors for testing... cp ./src/adapters/memory.js ./test/lib/lawnchair-adapter-memory.js cp ./src/adapters/blackberry-persistent-storage.js ./test/lib/lawnchair-adapter-blackberry-persistent-storage.js @@ -70,4 +73,4 @@ test: doc: ./util/build-docs -.PHONY: clean build min test doc +.PHONY: clean build min test lint doc diff --git a/src/Lawnchair.js b/src/Lawnchair.js index 569bbe25..9e2bb9d4 100644 --- a/src/Lawnchair.js +++ b/src/Lawnchair.js @@ -1,97 +1,109 @@ +/* exported Lawnchair */ +(function(global){ + 'use strict' + /** * Lawnchair! - * --- - * clientside json store + * --- + * clientside json store * */ var Lawnchair = function (options, callback) { - // ensure Lawnchair was called as a constructor - if (!(this instanceof Lawnchair)) return new Lawnchair(options, callback); - - // lawnchair requires json - if (!JSON) throw 'JSON unavailable! Include http://www.json.org/json2.js to fix.' - // options are optional; callback is not - if (arguments.length <= 2) { - callback = (typeof arguments[0] === 'function') ? arguments[0] : arguments[1]; - options = (typeof arguments[0] === 'function') ? {} : arguments[0] || {}; - } else { - throw 'Incorrect # of ctor args!' + // ensure Lawnchair was called as a constructor + if (!(this instanceof Lawnchair)) return new Lawnchair(options, callback); + + // lawnchair requires json + if (!JSON) throw 'JSON unavailable! Include '+ + 'http://www.json.org/json2.js to fix.' + // options are optional; callback is not + if (arguments.length <= 2) { + var functionFirst = typeof arguments[0] === 'function' + callback = functionFirst ? arguments[0] : arguments[1]; + options = functionFirst ? {} : arguments[0] || {}; + } else { + throw 'Incorrect # of ctor args!' + } + + // default configuration + this.record = options.record || 'record' // default for records + this.name = options.name || 'records' // default name for underlying store + + // mixin first valid adapter + var adapter + // if the adapter is passed in we try to load that only + if (options.adapter) { + + // the argument passed should be an array of prefered adapters + // if it is not, we convert it + if (typeof(options.adapter) === 'string') { + options.adapter = [options.adapter]; } - - // default configuration - this.record = options.record || 'record' // default for records - this.name = options.name || 'records' // default name for underlying store - - // mixin first valid adapter - var adapter - // if the adapter is passed in we try to load that only - if (options.adapter) { - - // the argument passed should be an array of prefered adapters - // if it is not, we convert it - if(typeof(options.adapter) === 'string'){ - options.adapter = [options.adapter]; - } - - // iterates over the array of passed adapters - for(var j = 0, k = options.adapter.length; j < k; j++){ - - // itirates over the array of available adapters - for (var i = Lawnchair.adapters.length-1; i >= 0; i--) { - if (Lawnchair.adapters[i].adapter === options.adapter[j]) { - adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined; - if (adapter) break - } - } - if (adapter) break - } - - // otherwise find the first valid adapter for this env - } - else { - for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) { - adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined - if (adapter) break + + // iterates over the array of passed adapters + for (var j = 0, k = options.adapter.length; j < k; j++) { + + // itirates over the array of available adapters + for (var i = Lawnchair.adapters.length-1; i >= 0; i--) { + if (Lawnchair.adapters[i].adapter === options.adapter[j]) { + adapter = Lawnchair.adapters[i].valid() + ? Lawnchair.adapters[i] + : undefined; + if (adapter) break } - } - - // we have failed - if (!adapter) throw 'No valid adapter.' - - // yay! mixin the adapter - for (var j in adapter) - this[j] = adapter[j] - - // call init for each mixed in plugin - for (var i = 0, l = Lawnchair.plugins.length; i < l; i++) - Lawnchair.plugins[i].call(this) - - // init the adapter - this.init(options, callback) + } + if (adapter) break + } + + // otherwise find the first valid adapter for this env + } + else { + for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) { + adapter = Lawnchair.adapters[i].valid() + ? Lawnchair.adapters[i] + : undefined + if (adapter) break + } + } + + // we have failed + if (!adapter) throw 'No valid adapter.' + + // yay! mixin the adapter + for (var j in adapter) if (adapter.hasOwnProperty(j)) + this[j] = adapter[j] + + // call init for each mixed in plugin + for (var i = 0, l = Lawnchair.plugins.length; i < l; i++) + Lawnchair.plugins[i].call(this) + + // init the adapter + this.init(options, callback) } -Lawnchair.adapters = [] +Lawnchair.adapters = [] -/** +/** * queues an adapter for mixin * === * - ensures an adapter conforms to a specific interface * */ Lawnchair.adapter = function (id, obj) { - // add the adapter id to the adapter obj - // ugly here for a cleaner dsl for implementing adapters - obj['adapter'] = id - // methods required to implement a lawnchair adapter - var implementing = 'adapter valid init keys save batch get exists all remove nuke'.split(' ') - , indexOf = this.prototype.indexOf - // mix in the adapter - for (var i in obj) { - if (indexOf(implementing, i) === -1) throw 'Invalid adapter! Nonstandard method: ' + i - } - // if we made it this far the adapter interface is valid - // insert the new adapter as the preferred adapter - Lawnchair.adapters.splice(0,0,obj) + // add the adapter id to the adapter obj + // ugly here for a cleaner dsl for implementing adapters + obj.adapter = id + // methods required to implement a lawnchair adapter + var implementing = 'adapter valid init keys save batch '+ + 'get exists all remove nuke'.split(' ') + , indexOf = this.prototype.indexOf + // mix in the adapter + for (var i in obj) { + if (indexOf(implementing, i) === -1) + throw 'Invalid adapter! Nonstandard method: ' + i + } + // if we made it this far the adapter interface is valid + // insert the new adapter as the preferred adapter + Lawnchair.adapters.splice(0, 0, obj) } Lawnchair.plugins = [] @@ -99,12 +111,12 @@ Lawnchair.plugins = [] /** * generic shallow extension for plugins * === - * - if an init method is found it registers it to be called when the lawnchair is inited - * - yes we could use hasOwnProp but nobody here is an asshole - */ + * - if an init method is found it registers it to be called + * when the lawnchair is inited + */ Lawnchair.plugin = function (obj) { - for (var i in obj) - i === 'init' ? Lawnchair.plugins.push(obj[i]) : this.prototype[i] = obj[i] + for (var i in obj) if (obj.hasOwnProperty(i)) + i === 'init' ? Lawnchair.plugins.push(obj[i]) : this.prototype[i] = obj[i] } /** @@ -112,53 +124,59 @@ Lawnchair.plugin = function (obj) { * */ Lawnchair.prototype = { + isArray: Array.isArray || function (o) { + return Object.prototype.toString.call(o) === '[object Array]' + }, - isArray: Array.isArray || function(o) { return Object.prototype.toString.call(o) === '[object Array]' }, - - /** - * this code exists for ie8... for more background see: - * http://www.flickr.com/photos/westcoastlogic/5955365742/in/photostream - */ - indexOf: function(ary, item, i, l) { - if (ary.indexOf) return ary.indexOf(item) - for (i = 0, l = ary.length; i < l; i++) if (ary[i] === item) return i - return -1 - }, - - // awesome shorthand callbacks as strings. this is shameless theft from dojo. - lambda: function (callback) { - return this.fn(this.record, callback) - }, - - // first stab at named parameters for terse callbacks; dojo: first != best // ;D - fn: function (name, callback) { - return typeof callback == 'string' ? new Function(name, callback) : callback - }, - - // returns a unique identifier (by way of Backbone.localStorage.js) - // TODO investigate smaller UUIDs to cut on storage cost - uuid: function () { - var S4 = function () { - return (((1+Math.random())*0x10000)|0).toString(16).substring(1); - } - return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); - }, - - // a classic iterator - each: function (callback) { - var cb = this.lambda(callback) - // iterate from chain - if (this.__results) { - for (var i = 0, l = this.__results.length; i < l; i++) cb.call(this, this.__results[i], i) - } - // otherwise iterate the entire collection - else { - this.all(function(r) { - for (var i = 0, l = r.length; i < l; i++) cb.call(this, r[i], i) - }) - } - return this + /** + * this code exists for ie8... for more background see: + * http://www.flickr.com/photos/westcoastlogic/5955365742/in/photostream + */ + indexOf: function (ary, item, i, l) { + if (ary.indexOf) return ary.indexOf(item) + for (i = 0, l = ary.length; i < l; i++) if (ary[i] === item) return i + return -1 + }, + + // awesome shorthand callbacks as strings. this is shameless theft from dojo. + lambda: function (callback) { + return this.fn(this.record, callback) + }, + + // first stab at named parameters for terse callbacks; + // dojo: first != best // ;D + fn: function (name, callback) { + return typeof callback === 'string' + ? new Function(name, callback) + : callback + }, + + // returns a unique identifier (by way of Backbone.localStorage.js) + // TODO investigate smaller UUIDs to cut on storage cost + uuid: function () { + var S4 = function () { + /* jshint bitwise:false */ + return (((1+Math.random())*0x10000)|0).toString(16).substring(1); + } + return (S4()+S4()+'-'+S4()+'-'+S4()+'-'+S4()+'-'+S4()+S4()+S4()); + }, + + // a classic iterator + each: function (callback) { + var cb = this.lambda(callback) + // iterate from chain + if (this.__results) { + for (var i = 0, l = this.__results.length; i < l; i++) + cb.call(this, this.__results[i], i) } + // otherwise iterate the entire collection + else { + this.all(function (r) { + for (var i = 0, l = r.length; i < l; i++) cb.call(this, r[i], i) + }) + } + return this + } // -- }; @@ -167,4 +185,8 @@ Lawnchair.prototype = { */ if (typeof module !== 'undefined' && module.exports) { module.exports = Lawnchair; +} else { + global.Lawnchair = Lawnchair; } + +})(this); \ No newline at end of file diff --git a/src/adapters/blackberry-persistent-storage.js b/src/adapters/blackberry-persistent-storage.js index cdfb7e8e..7696203c 100644 --- a/src/adapters/blackberry-persistent-storage.js +++ b/src/adapters/blackberry-persistent-storage.js @@ -1,80 +1,100 @@ +/* global Lawnchair */ + /** - * blackberry persistent storage adaptor + * blackberry persistent storage adaptor * === - * - Implementation that uses the BlackBerry Persistent Storage mechanism. This is only available in PhoneGap BlackBerry projects + * - Implementation that uses the BlackBerry Persistent Storage + * mechanism. This is only available in PhoneGap BlackBerry + * projects * - See http://www.github.com/phonegap/phonegap-blackberry * */ -Lawnchair.adapter('blackberry-persistent-storage', (function() { - // Private helper. - var isObjectAsString = function(value) { - return (value != null && value[0] == '{' && value[value.length-1] == '}'); - }; - return { - valid: function() { - return !!navigator.store; - }, - init:function() { - // Check for the existence of the phonegap blackberry persistent store API - if (!navigator.store) - throw('Lawnchair, "This browser does not support BlackBerry Persistent Storage; it is a PhoneGap-only implementation."'); - }, - get:function(key, callback) { - var that = this; - navigator.store.get(function(value) { // success cb - if (callback) { - // Check if BBPS returned a serialized JSON obj, if so eval it. - if (isObjectAsString(value)) { - eval('value = ' + value.substr(0,value.length-1) + ',key:\'' + key + '\'};'); - } - that.terseToVerboseCallback(callback)(value); - } - }, function() {}, // empty error cb - key); - }, - save:function(obj, callback) { - var id = obj.key || this.uuid(); - delete obj.key; - var that = this; - navigator.store.put(function(){ - if (callback) { - var cbObj = obj; - cbObj['key'] = id; - that.terseToVerboseCallback(callback)(cbObj); - } - }, function(){}, id, this.serialize(obj)); - }, - all:function(callback) { - var that = this; - navigator.store.getAll(function(json) { // success cb - if (callback) { - // BlackBerry store returns straight strings, so eval as necessary for values we deem as objects. - var arr = []; - for (var prop in json) { - if (isObjectAsString(json[prop])) { - eval('arr.push(' + json[prop].substr(0,json[prop].length-1) + ',key:\'' + prop + '\'});'); - } else { - eval('arr.push({\'' + prop + '\':\'' + json[prop] + '\'});'); - } - } - that.terseToVerboseCallback(callback)(arr); - } - }, function() {}); // empty error cb - }, - remove:function(keyOrObj, callback) { - var key = (typeof keyOrObj == 'string') ? keyOrObj : keyOrObj.key; - var that = this; - navigator.store.remove(function() { - if (callback) - that.terseToVerboseCallback(callback)(); - }, function() {}, key); - }, - nuke:function(callback) { - var that = this; - navigator.store.nuke(function(){ - if (callback) - that.terseToVerboseCallback(callback)(); - },function(){}); - } - }; +Lawnchair.adapter('blackberry-persistent-storage', (function () { + 'use strict' + + // Private helper. + var isObjectAsString = function (value) { + return (value != null && value[0] === '{' && value[value.length-1] === '}'); + }; + + return { + + valid: function () { + return !!navigator.store; + }, + + init: function () { + // Check for the existence of the phonegap blackberry persistent store API + if (!navigator.store) + throw('Lawnchair, "This browser does not support '+ + 'BlackBerry Persistent Storage; it is a '+ + 'PhoneGap-only implementation."'); + }, + + get: function (key, callback) { + var that = this; + navigator.store.get(function (value) { // success cb + if (callback) { + // Check if BBPS returned a serialized JSON obj, if so eval it. + if (isObjectAsString(value)) { + var obj = value.substr(0, value.length-1); + eval('value = ' +obj+ ', key:"'+key+'"};'); + } + that.terseToVerboseCallback(callback)(value); + } + }, function(){}, key); + }, + + save: function (obj, callback) { + var id = obj.key || this.uuid(); + delete obj.key; + var that = this; + navigator.store.put(function () { + if (callback) { + var cbObj = obj; + cbObj.key = id; + that.terseToVerboseCallback(callback)(cbObj); + } + }, function(){}, id, this.serialize(obj)); + }, + + all: function (callback) { + var that = this; + navigator.store.getAll(function (json) { // success cb + if (callback) { + // BlackBerry store returns straight strings, so eval + // as necessary for values we deem as objects. + var arr = []; + for (var prop in json) { + if (isObjectAsString(json[prop])) { + var obj = json[prop].substr(0, json[prop].length-1); + eval('arr.push(' +obj+ ', key:"'+prop+'"});'); + } else { + eval('arr.push({"' + prop + '":"' + json[prop] + '"});'); + } + } + that.terseToVerboseCallback(callback)(arr); + } + }, function(){}); // empty error cb + }, + + remove: function (keyOrObj, callback) { + var key = (typeof keyOrObj === 'string') ? keyOrObj : keyOrObj.key; + var that = this; + navigator.store.remove(function () { + if (callback) + that.terseToVerboseCallback(callback)(); + }, function(){}, key); + }, + + nuke: function (callback) { + var that = this; + navigator.store.nuke(function () { + if (callback) + that.terseToVerboseCallback(callback)(); + }, function(){}); + } + + }; + })()); diff --git a/src/adapters/chrome-storage.js b/src/adapters/chrome-storage.js index 7b5b5d45..b741377d 100644 --- a/src/adapters/chrome-storage.js +++ b/src/adapters/chrome-storage.js @@ -1,270 +1,286 @@ +/* global Lawnchair */ +/* global chrome */ + /** - * chrome.storage storage adapter - * === + * chrome.storage storage adapter + * === * - originally authored by Joseph Pecoraro * - */ -// -// Oh, what a tangled web we weave when a callback is what we use to receive - jrschifa -// -Lawnchair.adapter('chrome-storage', (function() { - var storage = chrome.storage.local - - var indexer = function(name) { - return { - // the key - key: name + '._index_', - // returns the index - all: function(callback) { - var _this = this - - var initStorage = function() { - var obj = JSON.stringify([]) - var _set = {} - _set[_this.key] = obj - storage.set(_set) - - obj = JSON.parse(obj) - - return obj - } - - storage.get(this.key, function(items) { - var obj - if (Object.keys(items).length > 0) { - for (itemKey in items) { - obj = items[itemKey] - if (obj) { - obj = JSON.parse(obj) - } - - if (obj === null || typeof obj === 'undefined') { - obj = initStorage() - } - - if (callback) { - callback(obj) - } - } - } else { - obj = initStorage() - callback(obj) - } - }) - }, - // adds a key to the index - add: function (key) { - this.all(function(a) { - a.push(key) - - var _set = {} - _set[this.key] = JSON.stringify(a) - storage.set(_set) - }) - }, - // deletes a key from the index - del: function (key) { - var r = [] - this.all(function(a) { - for (var i = 0, l = a.length; i < l; i++) { - if (a[i] != key) r.push(a[i]) - } - - var _set = {} - _set[this.key] = JSON.stringify(r) - storage.set(_set) - }) - }, - // returns index for a key - find: function (key, callback) { - this.all(function(a) { - for (var i = 0, l = a.length; i < l; i++) { - if (key === a[i]) { - if (callback) callback(i) - } - } - - if (callback) callback(false) - }) - } - } - } - - // adapter api + */ + +// Oh, what a tangled web we weave when a callback is what we +// use to receive - jrschifa +Lawnchair.adapter('chrome-storage', (function () { + 'use strict' + + var storage = chrome.storage.local + + var indexer = function (name) { return { - - // ensure we are in an env with chrome.storage - valid: function () { - return !!storage && function() { - // in mobile safari if safe browsing is enabled, window.storage - // is defined but setItem calls throw exceptions. - var success = true - var value = Math.random() - value = "" + value + "" //ensure that we are dealing with a string - try { - var _set = {} - _set[value] = value; - storage.set(_set) - } catch (e) { - success = false - } - storage.remove(value) - return success - }() - }, - - init: function (options, callback) { - this.indexer = indexer(this.name) - if (callback) this.fn(this.name, callback).call(this, this) - }, - - save: function (obj, callback) { - var key = obj.key ? this.name + '.' + obj.key : this.name + '.' + this.uuid() - // if the key is not in the index push it on - if (this.indexer.find(key) === false) this.indexer.add(key) - // now we kil the key and use it in the store colleciton - delete obj.key; - var _set = {} - _set[key] = JSON.stringify(obj) - storage.set(_set) - obj.key = key.slice(this.name.length + 1) - if (callback) { - this.lambda(callback).call(this, obj) + // the key + key: name + '._index_', + + // returns the index + all: function (callback) { + var _this = this + + var initStorage = function () { + var obj = JSON.stringify([]) + var _set = {} + _set[_this.key] = obj + storage.set(_set) + + obj = JSON.parse(obj) + + return obj + } + + storage.get(this.key, function (items) { + var obj + if (Object.keys(items).length > 0) { + for (var itemKey in items) if (items.hasOwnProperty(itemKey)) { + obj = items[itemKey] + if (obj) { + obj = JSON.parse(obj) + } + + if (obj === null || typeof obj === 'undefined') { + obj = initStorage() + } + + if (callback) { + callback(obj) + } + } + } else { + obj = initStorage() + callback(obj) + } + }) + }, + + // adds a key to the index + add: function (key) { + this.all(function (a) { + a.push(key) + + var _set = {} + _set[this.key] = JSON.stringify(a) + storage.set(_set) + }) + }, + + // deletes a key from the index + del: function (key) { + var r = [] + this.all(function (a) { + for (var i = 0, l = a.length; i < l; i++) { + if (a[i] !== key) r.push(a[i]) + } + + var _set = {} + _set[this.key] = JSON.stringify(r) + storage.set(_set) + }) + }, + + // returns index for a key + find: function (key, callback) { + this.all(function (a) { + for (var i = 0, l = a.length; i < l; i++) { + if (key === a[i]) { + if (callback) callback(i) } - return this - }, - - batch: function (ary, callback) { - var saved = [] - // not particularily efficient but this is more for sqlite situations - for (var i = 0, l = ary.length; i < l; i++) { - this.save(ary[i], function(r){ - saved.push(r) - }) + } + + if (callback) callback(false) + }) + } + } + } + + // adapter api + return { + + // ensure we are in an env with chrome.storage + valid: function () { + return !!storage && (function () { + // in mobile safari if safe browsing is enabled, window.storage + // is defined but setItem calls throw exceptions. + var success = true + var value = Math.random() + value = '' + value + '' // ensure that we are dealing with a string + try { + var _set = {} + _set[value] = value; + storage.set(_set) + } catch (e) { + success = false + } + storage.remove(value) + return success + }()) + }, + + init: function (options, callback) { + this.indexer = indexer(this.name) + if (callback) this.fn(this.name, callback).call(this, this) + }, + + save: function (obj, callback) { + var key = this.name + '.' + (obj.key || this.uuid()) + // if the key is not in the index push it on + if (this.indexer.find(key) === false) this.indexer.add(key) + // now we kil the key and use it in the store colleciton + delete obj.key; + var _set = {} + _set[key] = JSON.stringify(obj) + storage.set(_set) + obj.key = key.slice(this.name.length + 1) + if (callback) { + this.lambda(callback).call(this, obj) + } + return this + }, + + batch: function (ary, callback) { + var saved = [] + // not particularily efficient but this is more for sqlite situations + for (var i = 0, l = ary.length; i < l; i++) { + /* jshint loopfunc:true */ + this.save(ary[i], function (r) { + saved.push(r) + }) + /* jshint loopfunc:false */ + } + if (callback) this.lambda(callback).call(this, saved) + return this + }, + + // accepts [options], callback + keys: function (callback) { + if (callback) { + var _this = this + var name = this.name + var keys + + this.indexer.all(function (data) { + keys = data.map(function (r) { + return r.replace(name + '.', '') + }) + + _this.fn('keys', callback).call(_this, keys) + }) + } + return this + }, + + get: function (key, callback) { + var _this = this + var obj + + if (this.isArray(key)) { + var r = [] + for (var i = 0, l = key.length; i < l; i++) { + var k = this.name + '.' + key[i] + /* jshint loopfunc:true */ + storage.get(k, function (items) { + if (items) { + for (var itemKey in items) if (items.hasOwnProperty(itemKey)) { + obj = items[itemKey] + obj = JSON.parse(obj) + obj.key = itemKey.replace(_this.name + '.', '') + r.push(obj) + } } - if (callback) this.lambda(callback).call(this, saved) - return this - }, - - // accepts [options], callback - keys: function(callback) { - if (callback) { - var _this = this - var name = this.name - var keys - - this.indexer.all(function(data) { - keys = data.map(function(r) { - return r.replace(name + '.', '') - }) - - _this.fn('keys', callback).call(_this, keys) - }) + if (i === l) { + if (callback) _this.lambda(callback).call(_this, r) } - return this - }, - - get: function (key, callback) { - var _this = this - var obj - - if (this.isArray(key)) { - var r = [] - for (var i = 0, l = key.length; i < l; i++) { - var k = this.name + '.' + key[i] - - storage.get(k, function(items) { - if (items) { - for (itemKey in items) { - obj = items[itemKey] - obj = JSON.parse(obj) - obj.key = itemKey.replace(_this.name + '.', '') - r.push(obj) - } - } - - if (i == l) { - if (callback) _this.lambda(callback).call(_this, r) - } - }) - } - } else { - var k = this.name + '.' + key - - storage.get(k, function(items) { - if (items) { - for (itemKey in items) { - obj = items[itemKey] - obj = JSON.parse(obj) - obj.key = itemKey.replace(_this.name + '.', '') - } - } - if (callback) _this.lambda(callback).call(_this, obj) - }) + }) + /* jshint loopfunc:false */ + } + } else { + var k = this.name + '.' + key + + storage.get(k, function (items) { + if (items) { + for (var itemKey in items) if (items.hasOwnProperty(itemKey)) { + obj = items[itemKey] + obj = JSON.parse(obj) + obj.key = itemKey.replace(_this.name + '.', '') } - return this - }, - - exists: function (key, callback) { - var _this = this - this.indexer.find((this.name+'.'+key), function(response) { - response = (response === false) ? false : true; - _this.lambda(callback).call(_this, response) - }) - - return this; - }, - // NOTE adapters cannot set this.__results but plugins do - // this probably should be reviewed - all: function (callback) { - var _this = this - - this.indexer.all(function(idx) { - //console.log('adapter all'); - //console.log(idx); - var r = [] - , o - , k - - //console.log(idx); - if (idx.length > 0) { - for (var i = 0, l = idx.length; i < l; i++) { - storage.get(idx[i], function(items) { - for (k in items) { - o = JSON.parse(items[k]) - o.key = k.replace(_this.name + '.', '') - r.push(o) - } - - if (i == l) { - if (callback) _this.fn(_this.name, callback).call(_this, r) - } - }) - } - } else { - if (callback) _this.fn(_this.name, callback).call(_this, r) - } - }) - return this - }, - - remove: function (keyOrObj, callback) { - var key = this.name + '.' + ((keyOrObj.key) ? keyOrObj.key : keyOrObj) - this.indexer.del(key) - storage.remove(key) - if (callback) this.lambda(callback).call(this) - return this - }, - - nuke: function (callback) { - //could probably just use storage.clear() hear - this.all(function(r) { - for (var i = 0, l = r.length; i < l; i++) { - r[i] = "" + r[i] + "" - this.remove(r[i]); - } - if (callback) this.lambda(callback).call(this) + } + if (callback) _this.lambda(callback).call(_this, obj) + }) + } + return this + }, + + exists: function (key, callback) { + var _this = this + this.indexer.find((this.name+'.'+key), function (response) { + response = (response === false) ? false : true; + _this.lambda(callback).call(_this, response) + }) + + return this; + }, + + // NOTE adapters cannot set this.__results but plugins do + // this probably should be reviewed + all: function (callback) { + var _this = this + + this.indexer.all(function (idx) { + // console.log('adapter all'); + // console.log(idx); + var r = [] + , o + + // console.log(idx); + if (idx.length > 0) { + for (var i = 0, l = idx.length; i < l; i++) { + /* jshint loopfunc:true */ + storage.get(idx[i], function (items) { + for (var k in items) if (items.hasOwnProperty(k)) { + o = JSON.parse(items[k]) + o.key = k.replace(_this.name + '.', '') + r.push(o) + } + + if (i === l) { + if (callback) _this.fn(_this.name, callback).call(_this, r) + } }) - return this + /* jshint loopfunc:false */ + } + } else { + if (callback) _this.fn(_this.name, callback).call(_this, r) } -}})()); + }) + return this + }, + + remove: function (keyOrObj, callback) { + var key = this.name + '.' + ((keyOrObj.key) ? keyOrObj.key : keyOrObj) + this.indexer.del(key) + storage.remove(key) + if (callback) this.lambda(callback).call(this) + return this + }, + + nuke: function (callback) { + // could probably just use storage.clear() hear + this.all(function (r) { + for (var i = 0, l = r.length; i < l; i++) { + r[i] = '' + r[i] + '' + this.remove(r[i]); + } + if (callback) this.lambda(callback).call(this) + }) + return this + } + + } + +})()); diff --git a/src/adapters/dom.js b/src/adapters/dom.js index ace0b60f..8834349c 100644 --- a/src/adapters/dom.js +++ b/src/adapters/dom.js @@ -1,205 +1,225 @@ +/* global Lawnchair */ + /** - * dom storage adapter - * === + * dom storage adapter + * === * - originally authored by Joseph Pecoraro * - */ + */ // // TODO does it make sense to be chainable all over the place? -// chainable: nuke, remove, all, get, save, all +// chainable: nuke, remove, all, get, save, all // not chainable: valid, keys // -Lawnchair.adapter('dom', (function() { - var storage = window.localStorage - // the indexer is an encapsulation of the helpers needed to keep an ordered index of the keys - var indexer = function(name) { - return { - // the key - key: name + '._index_', - // returns the index - all: function() { - var a = storage.getItem(JSON.stringify(this.key)) - if (a) { - a = JSON.parse(a) - } - if (a === null) storage.setItem(JSON.stringify(this.key), JSON.stringify([])) // lazy init - return JSON.parse(storage.getItem(JSON.stringify(this.key))) - }, - // adds a key to the index - add: function (key) { - var a = this.all() - a.push(key) - storage.setItem(JSON.stringify(this.key), JSON.stringify(a)) - }, - // deletes a key from the index - del: function (key) { - var a = this.all(), r = [] - // FIXME this is crazy inefficient but I'm in a strata meeting and half concentrating - for (var i = 0, l = a.length; i < l; i++) { - if (a[i] != key) r.push(a[i]) - } - storage.setItem(JSON.stringify(this.key), JSON.stringify(r)) - }, - // returns index for a key - find: function (key) { - var a = this.all() - for (var i = 0, l = a.length; i < l; i++) { - if (key === a[i]) return i - } - return false - } +Lawnchair.adapter('dom', (function () { + 'use strict' + + var storage = window.localStorage + + // the indexer is an encapsulation of the helpers needed to + // keep an ordered index of the keys + var indexer = function (name) { + return { + // the key + key: name + '._index_', + + // returns the index + all: function () { + var a = storage.getItem(JSON.stringify(this.key)) + if (a) { + a = JSON.parse(a) + } + // lazy init + if (a === null) storage.setItem( + JSON.stringify(this.key), + JSON.stringify([]) + ) + return JSON.parse(storage.getItem(JSON.stringify(this.key))) + }, + + // adds a key to the index + add: function (key) { + var a = this.all() + a.push(key) + storage.setItem(JSON.stringify(this.key), JSON.stringify(a)) + }, + + // deletes a key from the index + del: function (key) { + var a = this.all(), r = [] + // FIXME this is crazy inefficient but I'm in a strata + // meeting and half concentrating + for (var i = 0, l = a.length; i < l; i++) { + if (a[i] !== key) r.push(a[i]) } + storage.setItem(JSON.stringify(this.key), JSON.stringify(r)) + }, + + // returns index for a key + find: function (key) { + var a = this.all() + for (var i = 0, l = a.length; i < l; i++) { + if (key === a[i]) return i + } + return false + } } - - // adapter api - return { - - // ensure we are in an env with localStorage - valid: function () { - return !!storage && function() { - // in mobile safari if safe browsing is enabled, window.storage - // is defined but setItem calls throw exceptions. - var success = true - var value = Math.random() - try { - storage.setItem(value, value) - } catch (e) { - success = false - } - storage.removeItem(value) - return success - }() - }, - - init: function (options, callback) { - this.indexer = indexer(this.name) - if (callback) this.fn(this.name, callback).call(this, this) - }, - - save: function (obj, callback) { - var key = obj.key ? this.name + '.' + obj.key : this.name + '.' + this.uuid() - // now we kil the key and use it in the store colleciton - delete obj.key; - storage.setItem(key, JSON.stringify(obj)) - // if the key is not in the index push it on - if (this.indexer.find(key) === false) this.indexer.add(key) - obj.key = key.slice(this.name.length + 1) - if (callback) { - this.lambda(callback).call(this, obj) - } - return this - }, - - batch: function (ary, callback) { - var saved = [] - // not particularily efficient but this is more for sqlite situations - for (var i = 0, l = ary.length; i < l; i++) { - this.save(ary[i], function(r){ - saved.push(r) - }) - } - if (callback) this.lambda(callback).call(this, saved) - return this - }, - - // accepts [options], callback - keys: function(callback) { + } + + // adapter api + return { + // ensure we are in an env with localStorage + valid: function () { + return !!storage && (function () { + // in mobile safari if safe browsing is enabled, window.storage + // is defined but setItem calls throw exceptions. + var success = true + var value = Math.random() + try { + storage.setItem(value, value) + } catch (e) { + success = false + } + storage.removeItem(value) + return success + }()) + }, + + init: function (options, callback) { + this.indexer = indexer(this.name) + if (callback) this.fn(this.name, callback).call(this, this) + }, + + save: function (obj, callback) { + var key = this.name + '.' + (obj.key || this.uuid()) + // now we kil the key and use it in the store colleciton + delete obj.key; + storage.setItem(key, JSON.stringify(obj)) + // if the key is not in the index push it on + if (this.indexer.find(key) === false) this.indexer.add(key) + obj.key = key.slice(this.name.length + 1) + if (callback) { + this.lambda(callback).call(this, obj) + } + return this + }, + + batch: function (ary, callback) { + var saved = [] + // not particularily efficient but this is more for sqlite situations + for (var i = 0, l = ary.length; i < l; i++) { + /* jshint loopfunc:true */ + this.save(ary[i], function (r) { + saved.push(r) + }) + /* jshint loopfunc:false */ + } + if (callback) this.lambda(callback).call(this, saved) + return this + }, + + // accepts [options], callback + keys: function (callback) { + if (callback) { + var name = this.name + var indices = this.indexer.all(); + var keys = []; + // Checking for the support of map. + if (Array.prototype.map) { + keys = indices.map(function (r) { return r.replace(name + '.', '') }) + } else { + for (var key in indices) if (indices.hasOwnProperty(key)) { + keys.push(key.replace(name + '.', '')); + } + } + this.fn('keys', callback).call(this, keys) + } + return this // TODO options for limit/offset, return promise + }, + + get: function (key, callback) { + if (this.isArray(key)) { + var r = [] + for (var i = 0, l = key.length; i < l; i++) { + var k = this.name + '.' + key[i] + var obj = storage.getItem(k) + if (obj) { + obj = JSON.parse(obj) + obj.key = key[i] + } + r.push(obj) + } + if (callback) this.lambda(callback).call(this, r) + } else { + var k = this.name + '.' + key + var obj = storage.getItem(k) + if (obj) { + obj = JSON.parse(obj) + obj.key = key + } + if (callback) this.lambda(callback).call(this, obj) + } + return this + }, + + exists: function (key, cb) { + var exists = this.indexer.find(this.name+'.'+key) !== false + this.lambda(cb).call(this, exists); + return this; + }, + + // NOTE adapters cannot set this.__results but plugins do + // this probably should be reviewed + all: function (callback) { + var idx = this.indexer.all() + , r = [] + , o + , k + for (var i = 0, l = idx.length; i < l; i++) { + k = idx[i] // v + o = JSON.parse(storage.getItem(k)) + o.key = k.replace(this.name + '.', '') + r.push(o) + } + if (callback) this.fn(this.name, callback).call(this, r) + return this + }, + + remove: function (keyOrArray, callback) { + var self = this; + if (this.isArray(keyOrArray)) { + // batch remove + var i, done = keyOrArray.length; + var removeOne = function (i) { + self.remove(keyOrArray[i], function () { + if ((--done) > 0) { return; } if (callback) { - var name = this.name - var indices = this.indexer.all(); - var keys = []; - //Checking for the support of map. - if(Array.prototype.map) { - keys = indices.map(function(r){ return r.replace(name + '.', '') }) - } else { - for (var key in indices) { - keys.push(key.replace(name + '.', '')); - } - } - this.fn('keys', callback).call(this, keys) - } - return this // TODO options for limit/offset, return promise - }, - - get: function (key, callback) { - if (this.isArray(key)) { - var r = [] - for (var i = 0, l = key.length; i < l; i++) { - var k = this.name + '.' + key[i] - var obj = storage.getItem(k) - if (obj) { - obj = JSON.parse(obj) - obj.key = key[i] - } - r.push(obj) - } - if (callback) this.lambda(callback).call(this, r) - } else { - var k = this.name + '.' + key - var obj = storage.getItem(k) - if (obj) { - obj = JSON.parse(obj) - obj.key = key - } - if (callback) this.lambda(callback).call(this, obj) - } - return this - }, - - exists: function (key, cb) { - var exists = this.indexer.find(this.name+'.'+key) === false ? false : true ; - this.lambda(cb).call(this, exists); - return this; - }, - // NOTE adapters cannot set this.__results but plugins do - // this probably should be reviewed - all: function (callback) { - var idx = this.indexer.all() - , r = [] - , o - , k - for (var i = 0, l = idx.length; i < l; i++) { - k = idx[i] //v - o = JSON.parse(storage.getItem(k)) - o.key = k.replace(this.name + '.', '') - r.push(o) - } - if (callback) this.fn(this.name, callback).call(this, r) - return this - }, - - remove: function (keyOrArray, callback) { - var self = this; - if (this.isArray(keyOrArray)) { - // batch remove - var i, done = keyOrArray.length; - var removeOne = function(i) { - self.remove(keyOrArray[i], function() { - if ((--done) > 0) { return; } - if (callback) { - self.lambda(callback).call(self); - } - }); - }; - for (i=0; i < keyOrArray.length; i++) - removeOne(i); - return this; + self.lambda(callback).call(self); } - var key = this.name + '.' + - ((keyOrArray.key) ? keyOrArray.key : keyOrArray) - this.indexer.del(key) - storage.removeItem(key) - if (callback) this.lambda(callback).call(this) - return this - }, - - nuke: function (callback) { - this.all(function(r) { - for (var i = 0, l = r.length; i < l; i++) { - this.remove(r[i]); - } - if (callback) this.lambda(callback).call(this) - }) - return this + }); + }; + for (i = 0; i < keyOrArray.length; i++) + removeOne(i); + return this; + } + var key = this.name + '.' + + ((keyOrArray.key) ? keyOrArray.key : keyOrArray) + this.indexer.del(key) + storage.removeItem(key) + if (callback) this.lambda(callback).call(this) + return this + }, + + nuke: function (callback) { + this.all(function (r) { + for (var i = 0, l = r.length; i < l; i++) { + this.remove(r[i]); } -}})()); + if (callback) this.lambda(callback).call(this) + }) + return this + } + + } + +})()); diff --git a/src/adapters/gears-sqlite.js b/src/adapters/gears-sqlite.js index e0b7f06b..2fb8d9a9 100644 --- a/src/adapters/gears-sqlite.js +++ b/src/adapters/gears-sqlite.js @@ -1,3 +1,8 @@ +/* global Lawnchair */ +/* global GearsFactory */ +/* global ActiveXObject */ +/* global google:true */ + // init.js directly included to save on include traffic // // Copyright 2007, Google Inc. @@ -5,14 +10,14 @@ // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// 3. Neither the name of Google Inc. nor the names of its contributors may be -// used to endorse or promote products derived from this software without -// specific prior written permission. +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of Google Inc. nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF @@ -34,168 +39,185 @@ // issues. Applications that use the code below will continue to work seamlessly // when that happens. -(function() { - // We are already defined. Hooray! - if (window.google && google.gears) { - return; - } - - var factory = null; - - // Firefox - if (typeof GearsFactory != 'undefined') { - factory = new GearsFactory(); - } else { - // IE - try { - factory = new ActiveXObject('Gears.Factory'); - // privateSetGlobalObject is only required and supported on IE Mobile on - // WinCE. - if (factory.getBuildInfo().indexOf('ie_mobile') != -1) { - factory.privateSetGlobalObject(this); - } - } catch (e) { - // Safari - if ((typeof navigator.mimeTypes != 'undefined') - && navigator.mimeTypes["application/x-googlegears"]) { - factory = document.createElement("object"); - factory.style.display = "none"; - factory.width = 0; - factory.height = 0; - factory.type = "application/x-googlegears"; - document.documentElement.appendChild(factory); - } - } - } - - // *Do not* define any objects if Gears is not installed. This mimics the - // behavior of Gears defining the objects in the future. - if (!factory) { - return; - } - - // Now set up the objects, being careful not to overwrite anything. - // - // Note: In Internet Explorer for Windows Mobile, you can't add properties to - // the window object. However, global objects are automatically added as - // properties of the window object in all browsers. - if (!window.google) { - google = {}; - } - - if (!google.gears) { - google.gears = {factory: factory}; - } +(function () { + 'use strict' + + // We are already defined. Hooray! + if (window.google && google.gears) { + return; + } + + var factory = null; + + // Firefox + if (typeof GearsFactory !== 'undefined') { + factory = new GearsFactory(); + } else { + // IE + try { + factory = new ActiveXObject('Gears.Factory'); + // privateSetGlobalObject is only required and supported on IE Mobile on + // WinCE. + if (factory.getBuildInfo().indexOf('ie_mobile') !== -1) { + factory.privateSetGlobalObject(this); + } + } catch (e) { + // Safari + if ((typeof navigator.mimeTypes !== 'undefined') + && navigator.mimeTypes['application/x-googlegears']) { + factory = document.createElement('object'); + factory.style.display = 'none'; + factory.width = 0; + factory.height = 0; + factory.type = 'application/x-googlegears'; + document.documentElement.appendChild(factory); + } + } + } + + // *Do not* define any objects if Gears is not installed. This mimics the + // behavior of Gears defining the objects in the future. + if (!factory) { + return; + } + + // Now set up the objects, being careful not to overwrite anything. + // + // Note: In Internet Explorer for Windows Mobile, you can't add properties to + // the window object. However, global objects are automatically added as + // properties of the window object in all browsers. + if (!window.google) { + google = {}; + } + + if (!google.gears) { + google.gears = {factory: factory}; + } })(); /** - * gears sqlite adaptor + * gears sqlite adaptor * */ -Lawnchair.adapter('gears-sqlite', { - valid: function() { - return window.google && window.google.gears; - }, - init:function(options) { - var that = this; - var merge = that.merge; - var opts = (typeof arguments[0] == 'string') ? {table:options} : options; - this.name = merge('Lawnchair', opts.name); - this.table = merge('field', opts.table); - this.db = google.gears.factory.create('beta.database'); - this.db.open(this.name); - this.db.execute('create table if not exists ' + this.table + ' (id NVARCHAR(32) UNIQUE PRIMARY KEY, value TEXT, timestamp REAL)'); - }, - save:function(obj, callback) { - var that = this; - - var insert = function(obj, callback) { - var id = (obj.key == undefined) ? that.uuid() : obj.key; - delete(obj.key); - - var rs = that.db.execute( - "INSERT INTO " + that.table + " (id, value, timestamp) VALUES (?,?,?)", - [id, that.serialize(obj), that.now()] - ); - if (callback != undefined) { - obj.key = id; - callback(obj); - } - }; - - var update = function(id, obj, callback) { - that.db.execute( - "UPDATE " + that.table + " SET value=?, timestamp=? WHERE id=?", - [that.serialize(obj), that.now(), id] - ); - if (callback != undefined) { - obj.key = id; - callback(obj); - } - }; - - if (obj.key == undefined) { - insert(obj, callback); - } else { - this.get(obj.key, function(r) { - var isUpdate = (r != null); - - if (isUpdate) { - var id = obj.key; - delete(obj.key); - update(id, obj, callback); - } else { - insert(obj, callback); - } - }); - } - - }, - get:function(key, callback) { - var rs = this.db.execute("SELECT * FROM " + this.table + " WHERE id = ?", [key]); - - if (rs.isValidRow()) { - // FIXME need to test null return / empty recordset - var o = this.deserialize(rs.field(1)); - o.key = key; - rs.close(); - callback(o); - } else { - rs.close(); - callback(null); - } - }, - all:function(callback) { - var cb = this.terseToVerboseCallback(callback); - var rs = this.db.execute("SELECT * FROM " + this.table); - var r = []; - var o; - - // FIXME need to add 0 len support - //if (results.rows.length == 0 ) { - // cb([]); - - while (rs.isValidRow()) { - o = this.deserialize(rs.field(1)); - o.key = rs.field(0); - r.push(o); - rs.next(); - } - rs.close(); - cb(r); - }, - remove:function(keyOrObj, callback) { - this.db.execute( - "DELETE FROM " + this.table + " WHERE id = ?", - [(typeof keyOrObj == 'string') ? keyOrObj : keyOrObj.key] - ); - if(callback) - callback(); - }, - nuke:function(callback) { - this.db.execute("DELETE FROM " + this.table); - if(callback) - callback(); - return this; - } -}); +Lawnchair.adapter('gears-sqlite', (function () { + 'use strict' + + return { + valid: function () { + return window.google && window.google.gears; + }, + + init: function (options) { + var that = this; + var merge = that.merge; + var opts = (typeof arguments[0] === 'string') ? {table:options} : options; + this.name = merge('Lawnchair', opts.name); + this.table = merge('field', opts.table); + this.db = google.gears.factory.create('beta.database'); + this.db.open(this.name); + this.db.execute( + 'create table if not exists ' + this.table + + ' (id NVARCHAR(32) UNIQUE PRIMARY KEY, value TEXT, timestamp REAL)' + ); + }, + + save: function (obj, callback) { + var that = this; + + var insert = function (obj, callback) { + var id = (obj.key === undefined) ? that.uuid() : obj.key; + delete(obj.key); + + that.db.execute( + 'INSERT INTO ' + that.table + + ' (id, value, timestamp) VALUES (?,?,?)', + [id, that.serialize(obj), that.now()] + ); + if (callback !== undefined) { + obj.key = id; + callback(obj); + } + }; + + var update = function (id, obj, callback) { + that.db.execute( + 'UPDATE ' + that.table + ' SET value=?, timestamp=? WHERE id=?', + [that.serialize(obj), that.now(), id] + ); + if (callback !== undefined) { + obj.key = id; + callback(obj); + } + }; + + if (obj.key === undefined) { + insert(obj, callback); + } else { + this.get(obj.key, function (r) { + var isUpdate = (r != null); + + if (isUpdate) { + var id = obj.key; + delete(obj.key); + update(id, obj, callback); + } else { + insert(obj, callback); + } + }); + } + }, + + get: function (key, callback) { + var rs = this.db.execute( + 'SELECT * FROM ' + this.table + ' WHERE id = ?', [key] + ) + + if (rs.isValidRow()) { + // FIXME need to test null return / empty recordset + var o = this.deserialize(rs.field(1)); + o.key = key; + rs.close(); + callback(o); + } else { + rs.close(); + callback(null); + } + }, + + all: function (callback) { + var cb = this.terseToVerboseCallback(callback); + var rs = this.db.execute('SELECT * FROM ' + this.table); + var r = []; + var o; + + // FIXME need to add 0 len support + // if (results.rows.length === 0) { + // cb([]); + + while (rs.isValidRow()) { + o = this.deserialize(rs.field(1)); + o.key = rs.field(0); + r.push(o); + rs.next(); + } + rs.close(); + cb(r); + }, + + remove: function (keyOrObj, callback) { + this.db.execute( + 'DELETE FROM ' + this.table + ' WHERE id = ?', + [(typeof keyOrObj === 'string') ? keyOrObj : keyOrObj.key] + ); + if (callback) + callback(); + }, + + nuke: function (callback) { + this.db.execute('DELETE FROM ' + this.table); + if (callback) + callback(); + return this; + } + } +})()); diff --git a/src/adapters/html5-filesystem.js b/src/adapters/html5-filesystem.js index 4a2779e3..e671f0ae 100644 --- a/src/adapters/html5-filesystem.js +++ b/src/adapters/html5-filesystem.js @@ -1,237 +1,286 @@ -Lawnchair.adapter('html5-filesystem', (function(global){ - - var StorageInfo = global.StorageInfo || global.webkitStorageInfo || {}; - var TEMPORARY = global.TEMPORARY || StorageInfo.TEMPORARY; - var PERSISTENT = global.PERSISTENT || StorageInfo.PERSISTENT; - var BlobBuilder = global.BlobBuilder || global.WebKitBlobBuilder; - // BlobBuilder is depricated, use Blob - if(BlobBuilder){ - console.error('this browser has not depricated BlobBuilder. you probably want to update.'); - }else{ - console.log('this modern browser has depricated BlobBuilder, use Blob instead') - // see: https://developer.mozilla.org/en-US/docs/DOM/Blob#Blob_constructor_example_usage +/* global Lawnchair */ + +Lawnchair.adapter('html5-filesystem', (function (global) { + 'use strict' + + var StorageInfo = global.StorageInfo || global.webkitStorageInfo || {}; + var TEMPORARY = global.TEMPORARY || StorageInfo.TEMPORARY; + var PERSISTENT = global.PERSISTENT || StorageInfo.PERSISTENT; + var BlobBuilder = global.BlobBuilder || global.WebKitBlobBuilder; + // BlobBuilder is depricated, use Blob + if (BlobBuilder) { + console.error('this browser has not depricated BlobBuilder. '+ + 'You probably want to update.') + } else { + console.log('This modern browser has depricated BlobBuilder, '+ + 'use Blob instead.') + // see: https://developer.mozilla.org/en-US/docs/DOM/Blob + // #Blob_constructor_example_usage + } + + /* jshint camelcase:false */ + var requestFileSystem = global.requestFileSystem + || global.webkitRequestFileSystem + || global.moz_requestFileSystem + /* jshint camelcase:true */ + + var FileError = global.FileError; + + var fail = function (e) { + var msg; + switch (e.code) { + case FileError.QUOTA_EXCEEDED_ERR: + msg = 'QUOTA_EXCEEDED_ERR'; + break; + case FileError.NOT_FOUND_ERR: + msg = 'NOT_FOUND_ERR'; + break; + case FileError.SECURITY_ERR: + msg = 'SECURITY_ERR'; + break; + case FileError.INVALID_MODIFICATION_ERR: + msg = 'INVALID_MODIFICATION_ERR'; + break; + case FileError.INVALID_STATE_ERR: + msg = 'INVALID_STATE_ERR'; + break; + default: + msg = 'Unknown Error'; + break; } - var requestFileSystem = global.requestFileSystem || global.webkitRequestFileSystem || global.moz_requestFileSystem; - var FileError = global.FileError; - - var fail = function( e ) { - var msg; - switch (e.code) { - case FileError.QUOTA_EXCEEDED_ERR: - msg = 'QUOTA_EXCEEDED_ERR'; - break; - case FileError.NOT_FOUND_ERR: - msg = 'NOT_FOUND_ERR'; - break; - case FileError.SECURITY_ERR: - msg = 'SECURITY_ERR'; - break; - case FileError.INVALID_MODIFICATION_ERR: - msg = 'INVALID_MODIFICATION_ERR'; - break; - case FileError.INVALID_STATE_ERR: - msg = 'INVALID_STATE_ERR'; - break; - default: - msg = 'Unknown Error'; - break; - }; - if ( console ) console.error( e, msg ); - }; - - var ls = function( reader, callback, entries ) { - var result = entries || []; - reader.readEntries(function( results ) { - if ( !results.length ) { - if ( callback ) callback( result.map(function(entry) { return entry.name; }) ); - } else { - ls( reader, callback, result.concat( Array.prototype.slice.call( results ) ) ); - } - }, fail ); - }; + if (console) console.error(e, msg); + }; - var filesystems = {}; + var ls = function (reader, callback, entries) { + var result = entries || []; + reader.readEntries(function (results) { + if (!results.length) { + if (callback) + callback(result.map(function (entry) { + return entry.name; + })); + } else { + ls( + reader, callback, + result.concat(Array.prototype.slice.call(results)) + ); + } + }, fail); + }; - var root = function( store, callback ) { - var directory = filesystems[store.name]; - if ( directory ) { - callback( directory ); - } else { - setTimeout(function() { - root( store, callback ); - }, 10 ); - } - }; - - return { - // boolean; true if the adapter is valid for the current environment - valid: function() { - return !!requestFileSystem; - }, - - // constructor call and callback. 'name' is the most common option - init: function( options, callback ) { - var me = this; - var error = function(e) { fail(e); if ( callback ) me.fn( me.name, callback ).call( me, me ); }; - requestFileSystem( (options.storage === 'TEMPORARY' ? TEMPORARY : PERSISTENT), (options.size || 1024*1024*1024), function( fs ) { - fs.root.getDirectory( options.name, {create:true}, function( directory ) { - filesystems[options.name] = directory; - if ( callback ) me.fn( me.name, callback ).call( me, me ); - }, error ); - }, error ); - }, - - // returns all the keys in the store - keys: function( callback ) { - var me = this; - root( this, function( store ) { - ls( store.createReader(), function( entries ) { - if ( callback ) me.fn( 'keys', callback ).call( me, entries ); - }); - }); - return this; - }, - - // save an object - save: function( obj, callback ) { - var me = this; - var key = obj.key || this.uuid(); - obj.key = key; - var error = function(e) { fail(e); if ( callback ) me.lambda( callback ).call( me ); }; - root( this, function( store ) { - store.getFile( key, {create:true}, function( file ) { - file.createWriter(function( writer ) { - writer.onerror = error; - writer.onwriteend = function() { - if ( callback ) me.lambda( callback ).call( me, obj ); - }; - // Old, depricated - if(BlobBuilder){ - var builder = new BlobBuilder(); - builder.append( JSON.stringify( obj ) ); - writer.write( builder.getBlob( 'application/json' ) ); - }else{ - // new, kinky - writer.write( new Blob([JSON.stringify(obj)] , {'type': 'application/json'}) ); - } - }, error ); - }, error ); - }); - return this; - }, - - // batch save array of objs - batch: function( objs, callback ) { - var me = this; - var saved = []; - for ( var i = 0, il = objs.length; i < il; i++ ) { - me.save( objs[i], function( obj ) { - saved.push( obj ); - if ( saved.length === il && callback ) { - me.lambda( callback ).call( me, saved ); - } - }); - } - return this; - }, - - // retrieve obj (or array of objs) and apply callback to each - get: function( key /* or array */, callback ) { - var me = this; - if ( this.isArray( key ) ) { - var values = []; - for ( var i = 0, il = key.length; i < il; i++ ) { - me.get( key[i], function( result ) { - if ( result ) values.push( result ); - if ( values.length === il && callback ) { - me.lambda( callback ).call( me, values ); - } - }); - } + var filesystems = {}; + + var root = function (store, callback) { + var directory = filesystems[store.name]; + if (directory) { + callback(directory); + } else { + setTimeout(function () { + root(store, callback); + }, 10); + } + }; + + return { + // boolean; true if the adapter is valid for the current environment + valid: function () { + return !!requestFileSystem; + }, + + // constructor call and callback. 'name' is the most common option + init: function (options, callback) { + var me = this; + var error = function (e) { + fail(e); + if (callback) me.fn(me.name, callback).call(me, me); + }; + var storage = options.storage === 'TEMPORARY' ? TEMPORARY : PERSISTENT + var size = options.size || 1024*1024*1024 + requestFileSystem(storage, size, function (fs) { + fs.root.getDirectory( + options.name, + {create: true}, + function (directory) { + filesystems[options.name] = directory; + if (callback) me.fn(me.name, callback).call(me, me); + }, error + ) + }, error); + }, + + // returns all the keys in the store + keys: function (callback) { + var me = this; + root(this, function (store) { + ls(store.createReader(), function (entries) { + if (callback) me.fn('keys', callback).call(me, entries); + }); + }); + return this; + }, + + // save an object + save: function (obj, callback) { + var me = this; + var key = obj.key || this.uuid(); + obj.key = key; + var error = function (e) { + fail(e) + if (callback) me.lambda(callback).call(me) + } + root(this, function (store) { + store.getFile(key, {create:true}, function (file) { + file.createWriter(function (writer) { + writer.onerror = error; + writer.onwriteend = function () { + if (callback) me.lambda(callback).call(me, obj); + }; + // Old, depricated + if (BlobBuilder) { + var builder = new BlobBuilder(); + builder.append(JSON.stringify(obj)); + writer.write(builder.getBlob('application/json')); } else { - var error = function(e) { fail( e ); if ( callback ) me.lambda( callback ).call( me ); }; - root( this, function( store ) { - store.getFile( key, {create:false}, function( entry ) { - entry.file(function( file ) { - var reader = new FileReader(); - - reader.onerror = error; - - reader.onload = function(e) { - if ( callback ) me.lambda( callback ).call( me, JSON.parse( e.target.result ) ); - }; - - reader.readAsText( file ); - }, error ); - }, error ); - }); + // new, kinky + writer.write(new Blob( + [JSON.stringify(obj)], + {'type':'application/json'}) + ); } - return this; - }, - - // check if an obj exists in the collection - exists: function( key, callback ) { - var me = this; - root( this, function( store ) { - store.getFile( key, {create:false}, function() { - if ( callback ) me.lambda( callback ).call( me, true ); - }, function() { - if ( callback ) me.lambda( callback ).call( me, false ); - }); - }); - return this; - }, - - // returns all the objs to the callback as an array - all: function( callback ) { - var me = this; - if ( callback ) { - this.keys(function( keys ) { - if ( !keys.length ) { - me.fn( me.name, callback ).call( me, [] ); - } else { - me.get( keys, function( values ) { - me.fn( me.name, callback ).call( me, values ); - }); - } - }); + }, error); + }, error); + }); + return this; + }, + + // batch save array of objs + batch: function (objs, callback) { + var me = this; + var saved = []; + for (var i = 0, il = objs.length; i < il; i++) { + /* jshint loopfunc:true */ + me.save(objs[i], function (obj) { + saved.push(obj); + if (saved.length === il && callback) { + me.lambda(callback).call(me, saved); + } + }); + /* jshint loopfunc:false */ + } + return this; + }, + + // retrieve obj (or array of objs) and apply callback to each + get: function (key /* or array */, callback) { + var me = this; + if (this.isArray(key)) { + var values = []; + for (var i = 0, il = key.length; i < il; i++) { + /* jshint loopfunc:true */ + me.get(key[i], function (result) { + if (result) values.push(result); + if (values.length === il && callback) { + me.lambda(callback).call(me, values); } - return this; - }, - - // remove a doc or collection of em - remove: function( key /* or object */, callback ) { - var me = this; - var error = function(e) { fail( e ); if ( callback ) me.lambda( callback ).call( me ); }; - root( this, function( store ) { - store.getFile( (typeof key === 'string' ? key : key.key ), {create:false}, function( file ) { - file.remove(function() { - if ( callback ) me.lambda( callback ).call( me ); - }, error ); - }, error ); + }); + /* jshint loopfunc:false */ + } + } else { + var error = function (e) { + fail(e) + if (callback) me.lambda(callback).call(me) + }; + root(this, function (store) { + store.getFile(key, {create:false}, function (entry) { + entry.file(function (file) { + var reader = new FileReader(); + + reader.onerror = error; + + reader.onload = function (e) { + if (callback) + me.lambda(callback).call(me, JSON.parse(e.target.result)); + }; + + reader.readAsText(file); + }, error); + }, error); + }); + } + return this; + }, + + // check if an obj exists in the collection + exists: function (key, callback) { + var me = this; + root(this, function (store) { + store.getFile(key, {create:false}, function () { + if (callback) me.lambda(callback).call(me, true); + }, function () { + if (callback) me.lambda(callback).call(me, false); + }); + }); + return this; + }, + + // returns all the objs to the callback as an array + all: function (callback) { + var me = this; + if (callback) { + this.keys(function (keys) { + if (!keys.length) { + me.fn(me.name, callback).call(me, []); + } else { + me.get(keys, function (values) { + me.fn(me.name, callback).call(me, values); }); - return this; - }, - - // destroy everything - nuke: function( callback ) { - var me = this; - var count = 0; - this.keys(function( keys ) { - if ( !keys.length ) { - if ( callback ) me.lambda( callback ).call( me ); - } else { - for ( var i = 0, il = keys.length; i < il; i++ ) { - me.remove( keys[i], function() { - count++; - if ( count === il && callback ) { - me.lambda( callback ).call( me ); - } - }); - } - } + } + }); + } + return this; + }, + + // remove a doc or collection of em + remove: function (key /* or object */, callback) { + var me = this; + var error = function (e) { + fail(e); + if (callback) me.lambda(callback).call(me); + }; + root(this, function (store) { + var key = typeof key === 'string' ? key : key.key; + store.getFile(key, {create:false}, function (file) { + file.remove(function () { + if (callback) me.lambda(callback).call(me); + }, error); + }, error); + }); + return this; + }, + + // destroy everything + nuke: function (callback) { + var me = this; + var count = 0; + this.keys(function (keys) { + if (!keys.length) { + if (callback) me.lambda(callback).call(me); + } else { + for (var i = 0, il = keys.length; i < il; i++) { + /* jshint loopfunc:true */ + me.remove(keys[i], function () { + count++; + if (count === il && callback) { + me.lambda(callback).call(me); + } }); - return this; + /* jshint loopfunc:false */ + } } - }; + }); + return this; + } + + }; + }(this))); diff --git a/src/adapters/ie-userdata.js b/src/adapters/ie-userdata.js index 9fce4e45..db2390cd 100644 --- a/src/adapters/ie-userdata.js +++ b/src/adapters/ie-userdata.js @@ -1,80 +1,85 @@ +/* global Lawnchair */ + /** - * ie userdata adaptor + * ie userdata adaptor * */ -Lawnchair.adapter('ie-userdata', { +Lawnchair.adapter('ie-userdata', (function () { + 'use strict' + + return { + valid: function () { + return typeof document.body.addBehavior !== 'undefined'; + }, - valid: function () { - return typeof(document.body.addBehavior) != 'undefined'; - }, + init: function (options, callback) { + var s = document.createElement('span'); + s.style.behavior = 'url(\'#default#userData\')'; + s.style.position = 'absolute'; + s.style.left = 10000; + document.body.appendChild(s); + this.storage = s; + this.storage.load(this.name); + this.fn(this.name, callback).call(this, this) + }, - init:function(options, callback){ - var s = document.createElement('span'); - s.style.behavior = 'url(\'#default#userData\')'; - s.style.position = 'absolute'; - s.style.left = 10000; - document.body.appendChild(s); - this.storage = s; - this.storage.load(this.name); - this.fn(this.name, callback).call(this, this) - }, + get: function (key, callback) { + var obj = JSON.parse(this.storage.getAttribute(key) || 'null'); + if (obj) { + obj.key = key; + } + if (callback) this.lambda(callback).call(this, obj) + return this; + }, - get:function(key, callback){ - - var obj = JSON.parse(this.storage.getAttribute(key) || 'null'); - if (obj) { - obj.key = key; - } - if (callback) this.lambda(callback).call(this, obj) - return this; - }, + save: function (obj, callback) { + var id = obj.key || 'lc' + this.uuid(); + delete obj.key; + this.storage.setAttribute(id, JSON.stringify(obj)); + this.storage.save(this.name); + obj.key = id; + if (callback) { + this.lambda(callback).call(this, obj) + } + return this; + }, - save:function(obj, callback){ - var id = obj.key || 'lc' + this.uuid(); - delete obj.key; - this.storage.setAttribute(id, JSON.stringify(obj)); - this.storage.save(this.name); - obj.key = id; - if (callback) { - this.lambda(callback).call(this, obj) - } - return this; - }, + all: function (callback) { + var ca = this.storage.XMLDocument.firstChild.attributes; + var yar = []; + var v, o; + // yo ho yo ho a pirates life for me + for (var i = 0, l = ca.length; i < l; i++) { + v = ca[i]; + o = JSON.parse(v.nodeValue || 'null'); + if (o) { + o.key = v.nodeName; + yar.push(o); + } + } + if (callback) this.fn(this.name, callback).call(this, yar) + return this; + }, - all:function(callback){ - var ca = this.storage.XMLDocument.firstChild.attributes; - var yar = []; - var v,o; - // yo ho yo ho a pirates life for me - for (var i = 0, l = ca.length; i < l; i++) { - v = ca[i]; - o = JSON.parse(v.nodeValue || 'null'); - if (o) { - o.key = v.nodeName; - yar.push(o); - } - } - if (callback) this.fn(this.name, callback).call(this, yar) - return this; - }, + remove: function (keyOrObj, callback) { + var key = (typeof keyOrObj === 'string') ? keyOrObj : keyOrObj.key; + this.storage.removeAttribute(key); + this.storage.save(this.name); + if (callback) this.lambda(callback).call(this) + return this; + }, - remove:function(keyOrObj,callback) { - var key = (typeof keyOrObj == 'string') ? keyOrObj : keyOrObj.key; - this.storage.removeAttribute(key); - this.storage.save(this.name); - if (callback) this.lambda(callback).call(this) - return this; - }, + nuke: function (callback) { + var that = this; + this.all(function (r) { + for (var i = 0, l = r.length; i < l; i++) { + if (r[i].key) + that.remove(r[i].key); + } + if (callback) that.lambda(callback).call(that) + }); + return this; + } + } - nuke:function(callback) { - var that = this; - this.all(function(r){ - for (var i = 0, l = r.length; i < l; i++) { - if (r[i].key) - that.remove(r[i].key); - } - if (callback) that.lambda(callback).call(that) - }); - return this; - } -}); +})()); diff --git a/src/adapters/indexed-db.js b/src/adapters/indexed-db.js index 8f06b031..dd86f651 100644 --- a/src/adapters/indexed-db.js +++ b/src/adapters/indexed-db.js @@ -1,317 +1,346 @@ +/* global Lawnchair */ + /** * indexed db adapter - * === + * === * - originally authored by Vivian Li * - */ - -Lawnchair.adapter('indexed-db', (function(){ + */ +Lawnchair.adapter('indexed-db', (function () { + 'use strict' // update the STORE_VERSION when the schema used by this adapter changes // (for example, if you change the STORE_NAME above) // NB: Causes onupgradeneeded to be fired, which erases the old database! var STORE_VERSION = 3; - var getIDB = function() { - return window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.oIndexedDB || window.msIndexedDB; + var getIDB = function () { + return window.indexedDB + || window.webkitIndexedDB + || window.mozIndexedDB + || window.oIndexedDB + || window.msIndexedDB }; - var getIDBTransaction = function() { - return window.IDBTransaction || window.webkitIDBTransaction || window.mozIDBTransaction || window.oIDBTransaction || window.msIDBTransaction; + var getIDBTransaction = function () { + return window.IDBTransaction + || window.webkitIDBTransaction + || window.mozIDBTransaction + || window.oIDBTransaction + || window.msIDBTransaction }; - var getIDBKeyRange = function() { - return window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange || window.oIDBKeyRange || window.msIDBKeyRange; + var getIDBKeyRange = function () { + return window.IDBKeyRange + || window.webkitIDBKeyRange + || window.mozIDBKeyRange + || window.oIDBKeyRange + || window.msIDBKeyRange }; - // see https://groups.google.com/a/chromium.org/forum/?fromgroups#!topic/chromium-html5/OhsoAQLj7kc - var READ_WRITE = (getIDBTransaction() && 'READ_WRITE' in getIDBTransaction()) ? getIDBTransaction().READ_WRITE : 'readwrite'; + // see: https://groups.google.com/a/chromium.org/forum/ + // ?fromgroups#!topic/chromium-html4/OhsoAQLj7kc + var READ_WRITE = (getIDBTransaction() && 'READ_WRITE' in getIDBTransaction()) + ? getIDBTransaction().READ_WRITE + : 'readwrite'; return { - valid: function() { - return !!getIDB(); + valid: function () { + return !!getIDB(); }, - init: function(options, callback) { - var self = this; + init: function (options, callback) { + var self = this; - var cb = self.fn(self.name, callback); - if (cb && typeof cb !== 'function') { - throw 'callback not valid'; - } + var cb = self.fn(self.name, callback); + if (cb && typeof cb !== 'function') { + throw 'callback not valid'; + } - // queues pending operations - self.waiting = []; - - // open idb - self.idb = getIDB(); - var request = self.idb.open(self.name, STORE_VERSION); - - // attach callback handlers - request.onerror = fail; - request.onupgradeneeded = onupgradeneeded; - request.onsuccess = onsuccess; - - // first start or indexeddb needs a version upgrade - function onupgradeneeded() { - self.db = request.result; - self.transaction = request.transaction; - - // NB! in case of a version conflict, we don't try to migrate, - // instead just throw away the old store and create a new one. - // this happens if somebody changed the - try { - self.db.deleteObjectStore(self.record); - } catch (e) { /* ignore */ } - - // create object store. - self.db.createObjectStore(self.record, { - autoIncrement: useAutoIncrement() - }); - } + // queues pending operations + self.waiting = []; - // database is ready for use - function onsuccess(event) { - // remember the db instance - self.db = event.target.result; + // open idb + self.idb = getIDB(); + var request = self.idb.open(self.name, STORE_VERSION); - // storage is now possible - self.store = true; + // attach callback handlers + request.onerror = fail; + request.onupgradeneeded = onupgradeneeded; + request.onsuccess = onsuccess; - // execute all pending operations - while (self.waiting.length) { - self.waiting.shift().call(self); - } + // first start or indexeddb needs a version upgrade + function onupgradeneeded() { + self.db = request.result; + self.transaction = request.transaction; - // we're done, fire the callback - if (cb) { - cb.call(self, self); - } + // NB! in case of a version conflict, we don't try to migrate, + // instead just throw away the old store and create a new one. + // this happens if somebody changed the + try { + self.db.deleteObjectStore(self.record); + } catch (e) { /* ignore */ } + + // create object store. + self.db.createObjectStore(self.record, { + autoIncrement: useAutoIncrement() + }); + } + + // database is ready for use + function onsuccess(event) { + // remember the db instance + self.db = event.target.result; + + // storage is now possible + self.store = true; + + // execute all pending operations + while (self.waiting.length) { + self.waiting.shift().call(self); } - }, - save:function(obj, callback) { - var self = this; - if(!this.store) { - this.waiting.push(function() { - this.save(obj, callback); - }); - return; - } - - var objs = (this.isArray(obj) ? obj : [obj]).map(function(o){if(!o.key) { o.key = self.uuid()} return o}) - - var win = function (e) { - if (callback) { self.lambda(callback).call(self, self.isArray(obj) ? objs : objs[0] ) } - }; - - var trans = this.db.transaction(this.record, READ_WRITE); - var store = trans.objectStore(this.record); - - for (var i = 0; i < objs.length; i++) { - var o = objs[i]; - store.put(o, o.key); - } - store.transaction.oncomplete = win; - store.transaction.onabort = fail; - - return this; + // we're done, fire the callback + if (cb) { + cb.call(self, self); + } + } }, - - batch: function (objs, callback) { - return this.save(objs, callback); + + save: function (obj, callback) { + var self = this; + if (!this.store) { + this.waiting.push(function () { + this.save(obj, callback); + }); + return; + } + + var objs = (this.isArray(obj) ? obj : [obj]).map(function (o) { + if (!o.key) o.key = self.uuid() + return o + }) + + var win = function (e) { + /* jshint unused:false */ + if (callback) + self.lambda(callback).call(self, self.isArray(obj) ? objs : objs[0]) + }; + + var trans = this.db.transaction(this.record, READ_WRITE); + var store = trans.objectStore(this.record); + + for (var i = 0; i < objs.length; i++) { + var o = objs[i]; + store.put(o, o.key); + } + store.transaction.oncomplete = win; + store.transaction.onabort = fail; + + return this; }, - - - get:function(key, callback) { - if(!this.store) { - this.waiting.push(function() { - this.get(key, callback); - }); - return; - } - - - var self = this; - var win = function (e) { - var r = e.target.result; - if (callback) { - if (r) { r.key = key; } - self.lambda(callback).call(self, r); - } - }; - - if (!this.isArray(key)){ - var req = this.db.transaction(this.record).objectStore(this.record).get(key); - - req.onsuccess = function(event) { - req.onsuccess = req.onerror = null; - win(event); - }; - req.onerror = function(event) { - req.onsuccess = req.onerror = null; - fail(event); - }; - - } else { - - // note: these are hosted. - var results = [] - , done = key.length - , keys = key - - var getOne = function(i) { - self.get(keys[i], function(obj) { - results[i] = obj; - if ((--done) > 0) { return; } - if (callback) { - self.lambda(callback).call(self, results); - } - }); - }; - for (var i = 0, l = keys.length; i < l; i++) - getOne(i); - } - return this; + batch: function (objs, callback) { + return this.save(objs, callback); }, - exists:function(key, callback) { - if(!this.store) { - this.waiting.push(function() { - this.exists(key, callback); - }); - return; + get: function (key, callback) { + if (!this.store) { + this.waiting.push(function () { + this.get(key, callback); + }); + return; + } + + var self = this; + var win = function (e) { + var r = e.target.result; + if (callback) { + if (r) { r.key = key; } + self.lambda(callback).call(self, r); } + }; - var self = this; + if (!this.isArray(key)) { + var req = this.db.transaction(this.record) + .objectStore(this.record) + .get(key); - var req = this.db.transaction(self.record).objectStore(this.record).openCursor(getIDBKeyRange().only(key)); - - req.onsuccess = function(event) { + req.onsuccess = function (event) { req.onsuccess = req.onerror = null; - // exists iff req.result is not null - // XXX but firefox returns undefined instead, sigh XXX - var undef; - self.lambda(callback).call(self, event.target.result !== null && - event.target.result !== undef); + win(event); }; - req.onerror = function(event) { + req.onerror = function (event) { req.onsuccess = req.onerror = null; fail(event); }; - return this; - }, + } else { - all:function(callback) { - if(!this.store) { - this.waiting.push(function() { - this.all(callback); - }); - return; - } - var cb = this.fn(this.name, callback) || undefined; - var self = this; - var objectStore = this.db.transaction(this.record).objectStore(this.record); - var toReturn = []; - objectStore.openCursor().onsuccess = function(event) { - var cursor = event.target.result; - if (cursor) { - toReturn.push(cursor.value); - cursor['continue'](); - } - else { - if (cb) cb.call(self, toReturn); - } + // note: these are hosted. + var results = [] + , done = key.length + , keys = key + + var getOne = function (i) { + self.get(keys[i], function (obj) { + results[i] = obj; + if ((--done) > 0) { return; } + if (callback) { + self.lambda(callback).call(self, results); + } + }); }; - return this; + for (var i = 0, l = keys.length; i < l; i++) + getOne(i); + } + + return this; }, - keys:function(callback) { - if(!this.store) { - this.waiting.push(function() { - this.keys(callback); - }); - return; - } - var cb = this.fn(this.name, callback) || undefined; - var self = this; - var objectStore = this.db.transaction(this.record).objectStore(this.record); - var toReturn = []; - // in theory we could use openKeyCursor() here, but no one actually - // supports it yet. - objectStore.openCursor().onsuccess = function(event) { - var cursor = event.target.result; - if (cursor) { - toReturn.push(cursor.key); - cursor['continue'](); - } - else { - if (cb) cb.call(self, toReturn); - } - }; - return this; + exists: function (key, callback) { + if (!this.store) { + this.waiting.push(function () { + this.exists(key, callback); + }); + return; + } + + var self = this; + + var req = this.db.transaction(self.record) + .objectStore(this.record) + .openCursor(getIDBKeyRange().only(key)); + + req.onsuccess = function (event) { + req.onsuccess = req.onerror = null; + // exists iff req.result is not null + // XXX but firefox returns undefined instead, sigh XXX + var undef; + self.lambda(callback).call(self, event.target.result !== null && + event.target.result !== undef); + }; + req.onerror = function (event) { + req.onsuccess = req.onerror = null; + fail(event); + }; + + return this; }, - remove:function(keyOrArray, callback) { - if(!this.store) { - this.waiting.push(function() { - this.remove(keyOrArray, callback); - }); - return; + all: function (callback) { + if (!this.store) { + this.waiting.push(function () { + this.all(callback); + }); + return; + } + var cb = this.fn(this.name, callback) || undefined; + var self = this; + var objectStore = this.db.transaction(this.record) + .objectStore(this.record); + var toReturn = []; + objectStore.openCursor().onsuccess = function (event) { + var cursor = event.target.result; + if (cursor) { + toReturn.push(cursor.value); + cursor['continue'](); } - var self = this; - - var toDelete = keyOrArray; - if (!this.isArray(keyOrArray)) { - toDelete=[keyOrArray]; + else { + if (cb) cb.call(self, toReturn); } + }; + return this; + }, + keys: function (callback) { + if (!this.store) { + this.waiting.push(function () { + this.keys(callback); + }); + return; + } + var cb = this.fn(this.name, callback) || undefined; + var self = this; + var objectStore = this.db.transaction(this.record) + .objectStore(this.record); + var toReturn = []; + // in theory we could use openKeyCursor() here, but no one actually + // supports it yet. + objectStore.openCursor().onsuccess = function (event) { + var cursor = event.target.result; + if (cursor) { + toReturn.push(cursor.key); + cursor['continue'](); + } + else { + if (cb) cb.call(self, toReturn); + } + }; + return this; + }, - var win = function () { - if (callback) self.lambda(callback).call(self) - }; - - var os = this.db.transaction(this.record, READ_WRITE).objectStore(this.record); - - var key = keyOrArray.key ? keyOrArray.key : keyOrArray; - for (var i = 0; i < toDelete.length; i++) { - var key = toDelete[i].key ? toDelete[i].key : toDelete[i]; - os['delete'](key); - }; + remove: function (keyOrArray, callback) { + if (!this.store) { + this.waiting.push(function () { + this.remove(keyOrArray, callback); + }); + return; + } + var self = this; + + var toDelete = keyOrArray; + if (!this.isArray(keyOrArray)) { + toDelete = [keyOrArray]; + } + + var win = function () { + if (callback) self.lambda(callback).call(self) + }; + + var os = this.db.transaction(this.record, READ_WRITE) + .objectStore(this.record); + + var key = keyOrArray.key ? keyOrArray.key : keyOrArray; + for (var i = 0; i < toDelete.length; i++) { + var key = toDelete[i].key ? toDelete[i].key : toDelete[i]; + os['delete'](key); + } + + os.transaction.oncomplete = win; + os.transaction.onabort = fail; + + return this; + }, + nuke: function (callback) { + if (!this.store) { + this.waiting.push(function () { + this.nuke(callback); + }); + return; + } + + var self = this + , win = callback + ? function () { self.lambda(callback).call(self) } + : function(){}; + + try { + var os = this.db.transaction(this.record, READ_WRITE) + .objectStore(this.record); + os.clear(); os.transaction.oncomplete = win; os.transaction.onabort = fail; - - return this; - }, - - nuke:function(callback) { - if(!this.store) { - this.waiting.push(function() { - this.nuke(callback); - }); - return; - } - - var self = this - , win = callback ? function() { self.lambda(callback).call(self) } : function(){}; - - try { - var os = this.db.transaction(this.record, READ_WRITE).objectStore(this.record); - os.clear(); - os.transaction.oncomplete = win; - os.transaction.onabort = fail; - } catch (e) { - if (e.name=='NotFoundError') - win() - else - fail(e); - } - return this; + } catch (e) { + if (e.name === 'NotFoundError') + win() + else + fail(e); + } + return this; } - + }; // @@ -319,13 +348,13 @@ Lawnchair.adapter('indexed-db', (function(){ // function fail(e, i) { - console.error('error in indexed-db adapter!', e, i); + console.error('error in indexed-db adapter!', e, i); } function useAutoIncrement() { - // using preliminary mozilla implementation which doesn't support - // auto-generated keys. Neither do some webkit implementations. - return !!window.indexedDB; + // using preliminary mozilla implementation which doesn't support + // auto-generated keys. Neither do some webkit implementations. + return !!window.indexedDB; } })()); diff --git a/src/adapters/memory.js b/src/adapters/memory.js index b1a18290..6a272dda 100644 --- a/src/adapters/memory.js +++ b/src/adapters/memory.js @@ -1,105 +1,111 @@ -Lawnchair.adapter('memory', (function(){ - - var data = {} - - return { - valid: function() { return true }, - - init: function (options, callback) { - data[this.name] = data[this.name] || {index:[],store:{}} - this.index = data[this.name].index - this.store = data[this.name].store - var cb = this.fn(this.name, callback) - if (cb) cb.call(this, this) - return this - }, - - keys: function (callback) { - this.fn('keys', callback).call(this, this.index) - return this - }, - - save: function(obj, cb) { - var key = obj.key || this.uuid() - - this.exists(key, function(exists) { - if (!exists) { - if (obj.key) delete obj.key - this.index.push(key) - } - - this.store[key] = obj - - if (cb) { - obj.key = key - this.lambda(cb).call(this, obj) - } - }) - - return this - }, - - batch: function (objs, cb) { - var r = [] - for (var i = 0, l = objs.length; i < l; i++) { - this.save(objs[i], function(record) { - r.push(record) - }) - } - if (cb) this.lambda(cb).call(this, r) - return this - }, - - get: function (keyOrArray, cb) { - var r; - if (this.isArray(keyOrArray)) { - r = [] - for (var i = 0, l = keyOrArray.length; i < l; i++) { - r.push(this.store[keyOrArray[i]]) - } - } else { - r = this.store[keyOrArray] - if (r) r.key = keyOrArray - } - if (cb) this.lambda(cb).call(this, r) - return this - }, - - exists: function (key, cb) { - this.lambda(cb).call(this, !!(this.store[key])) - return this - }, - - all: function (cb) { - var r = [] - for (var i = 0, l = this.index.length; i < l; i++) { - var obj = this.store[this.index[i]] - obj.key = this.index[i] - r.push(obj) - } - this.fn(this.name, cb).call(this, r) - return this - }, - - remove: function (keyOrArray, cb) { - var del = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray] - for (var i = 0, l = del.length; i < l; i++) { - var key = del[i].key ? del[i].key : del[i] - var where = this.indexOf(this.index, key) - if (where < 0) continue /* key not present */ - delete this.store[key] - this.index.splice(where, 1) - } - if (cb) this.lambda(cb).call(this) - return this - }, - - nuke: function (cb) { - this.store = data[this.name].store = {} - this.index = data[this.name].index = [] - if (cb) this.lambda(cb).call(this) - return this +/* global Lawnchair */ + +Lawnchair.adapter('memory', (function () { + 'use strict' + + var data = {} + + return { + valid: function () { return true }, + + init: function (options, callback) { + data[this.name] = data[this.name] || { index:[], store:{} } + this.index = data[this.name].index + this.store = data[this.name].store + var cb = this.fn(this.name, callback) + if (cb) cb.call(this, this) + return this + }, + + keys: function (callback) { + this.fn('keys', callback).call(this, this.index) + return this + }, + + save: function (obj, cb) { + var key = obj.key || this.uuid() + + this.exists(key, function (exists) { + if (!exists) { + if (obj.key) delete obj.key + this.index.push(key) + } + + this.store[key] = obj + + if (cb) { + obj.key = key + this.lambda(cb).call(this, obj) } + }) + + return this + }, + + batch: function (objs, cb) { + var r = [] + for (var i = 0, l = objs.length; i < l; i++) { + /* jshint loopfunc:true */ + this.save(objs[i], function (record) { + r.push(record) + }) + /* jshint loopfunc:false */ + } + if (cb) this.lambda(cb).call(this, r) + return this + }, + + get: function (keyOrArray, cb) { + var r; + if (this.isArray(keyOrArray)) { + r = [] + for (var i = 0, l = keyOrArray.length; i < l; i++) { + r.push(this.store[keyOrArray[i]]) + } + } else { + r = this.store[keyOrArray] + if (r) r.key = keyOrArray + } + if (cb) this.lambda(cb).call(this, r) + return this + }, + + exists: function (key, cb) { + this.lambda(cb).call(this, !!(this.store[key])) + return this + }, + + all: function (cb) { + var r = [] + for (var i = 0, l = this.index.length; i < l; i++) { + var obj = this.store[this.index[i]] + obj.key = this.index[i] + r.push(obj) + } + this.fn(this.name, cb).call(this, r) + return this + }, + + remove: function (keyOrArray, cb) { + var del = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray] + for (var i = 0, l = del.length; i < l; i++) { + var key = del[i].key ? del[i].key : del[i] + var where = this.indexOf(this.index, key) + if (where < 0) continue /* key not present */ + delete this.store[key] + this.index.splice(where, 1) + } + if (cb) this.lambda(cb).call(this) + return this + }, + + nuke: function (cb) { + this.store = data[this.name].store = {} + this.index = data[this.name].index = [] + if (cb) this.lambda(cb).call(this) + return this } + } + ///// })()); diff --git a/src/adapters/touchdb-couchdb.js b/src/adapters/touchdb-couchdb.js index 11fbdd3b..63c0bc23 100644 --- a/src/adapters/touchdb-couchdb.js +++ b/src/adapters/touchdb-couchdb.js @@ -1,251 +1,281 @@ +/* global Lawnchair */ +/* global $ */ + // Original author: Chris Anderson jchris@couchbase.com // Copyright 2013 Couchbase -Lawnchair.adapter('touchdb-couchdb', (function(){ - function makePath (base, path) { - var k, q, query = [], first = true, uri; - if (path) { - if ($.isArray(path)) { - if (typeof path[path.length-1] == 'object') { - q = path[path.length-1]; - path = path.slice(0, path.length-1); - for (k in q) { - if (['startkey', 'endkey', 'start_key', 'end_key', 'key'].indexOf(k) !== -1) { - v = JSON.stringify(q[k]) - } else { - v = q[k]; - } - query.push(encodeURIComponent(k)+'='+encodeURIComponent(v.toString())); - } - query = query.join('&'); - } - uri = (path.map(encodeURIComponent)).join('/'); - if (query.toString()) { - uri = uri + "?" + query.toString(); - } - return base + "/" + uri; +Lawnchair.adapter('touchdb-couchdb', (function () { + 'use strict' + + function makePath (base, path) { + var query = [] + , v, q, uri + if (path) { + if ($.isArray(path)) { + if (typeof path[path.length-1] === 'object') { + q = path[path.length-1]; + path = path.slice(0, path.length-1); + for (var k in q) if (q.hasOwnProperty(k)) { + var keyNames = ['startkey', 'endkey', 'start_key', + 'end_key', 'key'] + if (keyNames.indexOf(k) !== -1) { + v = JSON.stringify(q[k]) } else { - return base + "/" + path; + v = q[k]; } + query.push( + encodeURIComponent(k) + '=' + + encodeURIComponent(v.toString()) + ); + } + query = query.join('&'); + } + uri = (path.map(encodeURIComponent)).join('/'); + if (query.toString()) { + uri = uri + '?' + query.toString(); + } + return base + '/' + uri; + } else { + return base + '/' + path; + } + } else { + return base; + } + } + + function makeDb(dbName) { + if (location.protocol === 'file:') { + console.log('file:// url, so assume TouchDB-iOS is whitelisted '+ + 'at http://localhost.touchdb./') + dbName = 'http://localhost.touchdb./'+dbName; + } else { + console.log('assume CouchDB is reachable at '+location.origin); + dbName = '/'+dbName; + } + + function dbReq(/* path, json, cb */) { + /* jshint validthis:true */ + var cb, opts = { + contentType: 'application/json', + type: this + }; + if (typeof arguments[0] === 'function') { + // the db path + cb = arguments[0]; + opts.url = makePath(dbName); + } else if (typeof arguments[1] === 'function') { + // new path + cb = arguments[1]; + opts.url = makePath(dbName, arguments[0]); + } else { + // we have a body + cb = arguments[2]; + opts.url = makePath(dbName, arguments[0]); + opts.data = JSON.stringify(arguments[1]); + } + opts.complete = function (xhr, code) { + /* jshint unused:false */ + var body; + try { + body = JSON.parse(xhr.responseText); + } catch (e) { + body = xhr.responseText; + } + if (xhr.status >= 400) { + if (body.error) { + cb(body, xhr); + } else { + cb(xhr.status, body, xhr) + } } else { - return base; + cb(null, body); } + }; + return $.ajax(opts); } - function makeDb(dbName) { - if (location.protocol === 'file:') { - console.log("file:// url, so assume TouchDB-iOS is whitelisted at http://localhost.touchdb./") - dbName = "http://localhost.touchdb./"+dbName; + function allReq() { + return dbReq.apply('GET', arguments); + } + + 'get put post head del'.split(' ').forEach(function (v) { + allReq[v] = function () { + var type; + if (v === 'del') { + type = 'DELETE'; } else { - console.log("assume CouchDB is reachable at "+location.origin); - dbName = "/"+dbName; + type = v.toUpperCase(); + } + return dbReq.apply(type, arguments); + } + }); + return allReq; + } + + function forceSave(db, objs, cb) { + JSON.stringify(objs); // to catch cyclic errors while we can + db.post('_all_docs', { keys: objs.map(function (o) { return o._id; })}, + function (err, view) { + if (err) return cb(err); + for (var i = 0; i < view.rows.length; i++) { + if (view.rows[i].value) { + objs[i]._rev = view.rows[i].value.rev; } - function dbReq(/* path, json, cb */) { - var cb, opts = { - contentType : "application/json", - type : this - }; - if (typeof arguments[0] == 'function') { - // the db path - cb = arguments[0]; - opts.url = makePath(dbName); - } else if (typeof arguments[1] == 'function') { - // new path - cb = arguments[1]; - opts.url = makePath(dbName, arguments[0]); + } + db.post('_bulk_docs', {docs:objs}, function (err, ok) { + if (err) return cb(err); + for (var i = 0; i < ok.length; i++) { + objs[i]._rev = ok[i].rev; + } + cb(null, objs); + }); + }); + } + + return { + valid: function () { + if ($ && typeof $.ajax === 'function') { + return true; + } else { + console.log('touchdb-couchdb Lawnchair adapter requires '+ + 'Zepto or jQuery for $.ajax()'); + return false; + } + }, + + init: function (options, callback) { + var self = this; + this.name = options.name; + this.db = makeDb(this.name); + this.db.put(function (err, ok) { + /* jshint unused:false */ + if (err && err.error !== 'file_exists') { + throw(err); + } else { + self.fn(self.name, callback).call(self, self); + } + }); + return this; + }, + + keys: function (cb) { + var self = this; + this.db('_all_docs', function (err, view) { + var ids = []; + if (!err && view.rows) { + ids = view.rows.map(function (r) { return r.id }); + } + self.fn('keys', cb).call(this, ids); + }); + return this; + }, + save: function (obj, cb) { + obj._id = obj.key = obj.key || this.uuid(); + var self = this; + forceSave(self.db, [obj], function (err) { + if (err) throw(err); + if (cb) { + self.lambda(cb).call(self, obj) + } + }); + return this; + }, + + batch: function (objs, cb) { + var self = this; + objs = objs.map(function (obj) { + obj._id = obj.key = obj.key || self.uuid(); + return obj; + }); + forceSave(self.db, objs, function (err) { + if (err) throw(err); + if (cb) { + self.lambda(cb).call(self, objs) + } + }); + return this; + }, + + get: function (keyOrArray, cb) { + var self = this; + var keys = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray]; + /* jshint camelcase:false */ + this.db.post( + ['_all_docs', {include_docs:true}], + {keys:keys}, + function (err, view) { + if (err) throw (err); + var docs = []; + view.rows.forEach(function (r) { + docs.push(r.doc||null); + }); + if (cb) { + if (self.isArray(keyOrArray)) { + self.lambda(cb).call(self, docs); } else { - // we have a body - cb = arguments[2]; - opts.url = makePath(dbName, arguments[0]); - opts.data = JSON.stringify(arguments[1]); + self.lambda(cb).call(self, docs[0]); } - opts.complete = function(xhr, code) { - var body; - try { - body = JSON.parse(xhr.responseText); - } catch(e) { - body = xhr.responseText; - } - if (xhr.status >= 400) { - if (body.error) { - cb(body, xhr); - } else { - cb(xhr.status, body, xhr) - } - } else { - cb(null, body); - } - }; - return $.ajax(opts); + } } - function allReq(){ - return dbReq.apply("GET", arguments); - }; - "get put post head del".split(" ").forEach(function(v){ - allReq[v] = function() { - var type; - if (v == "del") { - type = "DELETE"; - } else { - type = v.toUpperCase(); - } - return dbReq.apply(type, arguments); - } - }); - return allReq; - } + ); + return this; + /* jshint camelcase:true */ + }, - function forceSave(db, objs, cb) { - JSON.stringify(objs); // to catch cyclic errors while we can - db.post("_all_docs", {keys:objs.map(function(o){return o._id;})}, - function(err, view) { - if (err) return cb(err); - for (var i = 0; i < view.rows.length; i++) { - if (view.rows[i].value) { - objs[i]._rev = view.rows[i].value.rev; - } - }; - db.post("_bulk_docs", {docs:objs}, function(err, ok) { - if (err) return cb(err); - for (var i = 0; i < ok.length; i++) { - objs[i]._rev = ok[i].rev; - }; - cb(null, objs); - }); - }); - }; - - return { - valid: function() { - if ($ && typeof $.ajax == "function") { - return true; - } else { - console.log("touchdb-couchdb Lawnchair adapter requires Zepto or jQuery for $.ajax()"); - return false; + exists: function (key, cb) { + this.get(key, function (doc) { + this.lambda(cb).call(this, !!doc); + }); + return this; + }, + + all: function (cb) { + var self = this; + /* jshint camelcase:false */ + this.db(['_all_docs', {include_docs:true}], function (err, view) { + var docs = []; + if (!err && view.rows) { + docs = view.rows.map(function (r) { return r.doc }); + } + self.fn(self.name, cb).call(self, docs); + }); + return this; + /* jshint camelcase:true */ + }, + + remove: function (keyOrArray, cb) { + var dels = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray]; + var self = this; + dels = dels.map(function (d) { + var id = d.key || d; + return { _id:id, _deleted:true }; + }); + forceSave(this.db, dels, function (err) { + if (err) throw (err); + if (cb) self.lambda(cb).call(self); + }); + return this; + }, + + nuke: function (cb) { + // nuke leaves us with an empty database + var self = this; + this.db.del(function (err, ok) { + /* jshint unused:false */ + if (err) { + throw(err); + } else { + self.db.put(function (err, ok) { + /* jshint unused:false */ + if (err && err.error !== 'file_exists') { + throw (err); } - }, - - init: function (options, callback) { - var self = this; - this.name = options.name; - this.db = makeDb(this.name); - this.db.put(function(err, ok) { - if (err && err.error !== "file_exists") { - throw(err); - } else { - self.fn(self.name, callback).call(self, self); - } - }); - return this; - }, - - keys: function (cb) { - var self = this; - this.db("_all_docs", function(err, view){ - var ids = []; - if (!err && view.rows) { - ids = view.rows.map(function(r){return r.id;}); - } - self.fn('keys', cb).call(this, ids); - }); - return this; - }, - save: function(obj, cb) { - obj._id = obj.key = obj.key || this.uuid(); - var self = this; - forceSave(self.db, [obj], function(err){ - if (err) throw(err); - if (cb) { - self.lambda(cb).call(self, obj) - } - }); - return this; - }, - - batch: function (objs, cb) { - var self = this; - objs = objs.map(function(obj) { - obj._id = obj.key = obj.key || self.uuid(); - return obj; - }); - forceSave(self.db, objs, function(err){ - if (err) throw(err); - if (cb) { - self.lambda(cb).call(self, objs) - } - }); - return this; - }, - - get: function (keyOrArray, cb) { - var self = this; - var keys = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray]; - this.db.post(["_all_docs", {include_docs:true}], {keys:keys}, function(err, view) { - if (err) throw (err); - var docs = []; - view.rows.forEach(function(r){ - docs.push(r.doc||null); - }); - if (cb) { - if (self.isArray(keyOrArray)) { - self.lambda(cb).call(self, docs); - } else { - self.lambda(cb).call(self, docs[0]); - } - } - }); - return this; - }, - - exists: function (key, cb) { - this.get(key, function(doc){ - this.lambda(cb).call(this, !!doc); - }); - return this; - }, - - all: function (cb) { - var self = this; - this.db(["_all_docs", {include_docs:true}], function(err, view){ - var docs = []; - if (!err && view.rows) { - docs = view.rows.map(function(r){return r.doc;}); - } - self.fn(self.name, cb).call(self, docs); - }); - return this; - }, - - remove: function (keyOrArray, cb) { - var dels = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray]; - var self = this; - dels = dels.map(function(d){ - var id = d.key || d; - return {_id : id, _deleted : true}; - }); - forceSave(this.db, dels, function(err) { - if (err) throw (err); - if (cb) self.lambda(cb).call(self); - }); - return this; - }, - - nuke: function (cb) { - // nuke leaves us with an empty database - var self = this; - this.db.del(function(err, ok) { - if (err) { - throw(err); - } else { - self.db.put(function(err, ok) { - if (err && !(err.error == "file_exists")) { - throw (err); - } - if (cb) self.lambda(cb).call(self) - }); - } - }); - return this; + if (cb) self.lambda(cb).call(self) + }); } + }); + return this; } + } + ///// })()); diff --git a/src/adapters/webkit-sqlite.js b/src/adapters/webkit-sqlite.js index 3bf50ef1..f0540336 100644 --- a/src/adapters/webkit-sqlite.js +++ b/src/adapters/webkit-sqlite.js @@ -1,187 +1,219 @@ +/* global Lawnchair */ + Lawnchair.adapter('webkit-sqlite', (function () { - // private methods - var fail = function (e, i) { console.error('error in sqlite adaptor!', e, i) } - , now = function () { return new Date() } // FIXME need to use better date fn - // not entirely sure if this is needed... - if (!Function.prototype.bind) { - Function.prototype.bind = function( obj ) { - var slice = [].slice - , args = slice.call(arguments, 1) - , self = this - , nop = function () {} - , bound = function () { - return self.apply(this instanceof nop ? this : (obj || {}), args.concat(slice.call(arguments))) - } - nop.prototype = self.prototype - bound.prototype = new nop() - return bound - } + 'use strict' + + // private methods + var fail = function (e, i) { console.error('error in sqlite adaptor!', e, i) } + , now = function(){ return new Date() } // FIXME need to use better date fn + + // not entirely sure if this is needed... + if (!Function.prototype.bind) { + /* jshint freeze:false */ + Function.prototype.bind = function (obj) { + var slice = [].slice + , args = slice.call(arguments, 1) + , self = this + , Nop = function(){} + , bound = function () { + return self.apply( + this instanceof Nop ? this : (obj || {}), + args.concat(slice.call(arguments)) + ) + } + Nop.prototype = self.prototype + bound.prototype = new Nop() + return bound } - - // public methods - return { - - valid: function() { return !!(window.openDatabase) }, - - init: function (options, callback) { - var that = this - , cb = that.fn(that.name, callback) - , create = "CREATE TABLE IF NOT EXISTS " + this.record + " (id NVARCHAR(32) UNIQUE PRIMARY KEY, value TEXT, timestamp REAL)" - , win = function(){ if(cb) return cb.call(that, that); } - - if (cb && typeof cb != 'function') throw 'callback not valid'; - - // open a connection and create the db if it doesn't exist - this.db = openDatabase(this.name, '1.0.0', this.name, 65536) - this.db.transaction(function (t) { - t.executeSql(create, []) - }, fail, win) - }, - - keys: function (callback) { - var cb = this.lambda(callback) - , that = this - , keys = "SELECT id FROM " + this.record + " ORDER BY timestamp DESC" - - this.db.readTransaction(function(t) { - var win = function (xxx, results) { - if (results.rows.length == 0 ) { - cb.call(that, []) - } else { - var r = []; - for (var i = 0, l = results.rows.length; i < l; i++) { - r.push(results.rows.item(i).id); - } - cb.call(that, r) - } - } - t.executeSql(keys, [], win, fail) - }) - return this - }, - // you think thats air you're breathing now? - save: function (obj, callback, error) { - var that = this - , objs = (this.isArray(obj) ? obj : [obj]).map(function(o){if(!o.key) { o.key = that.uuid()} return o}) - , ins = "INSERT OR REPLACE INTO " + this.record + " (value, timestamp, id) VALUES (?,?,?)" - , win = function () { if (callback) { that.lambda(callback).call(that, that.isArray(obj)?objs:objs[0]) }} - , error= error || function() {} - , insvals = [] - , ts = now() - - try { - for (var i = 0, l = objs.length; i < l; i++) { - insvals[i] = [JSON.stringify(objs[i]), ts, objs[i].key]; + /* jshint freeze:true */ + } + + // public methods + return { + + valid: function () { return !!(window.openDatabase) }, + + init: function (options, callback) { + var that = this + , cb = that.fn(that.name, callback) + , create = 'CREATE TABLE IF NOT EXISTS ' + this.record + + ' (id NVARCHAR(32) UNIQUE PRIMARY KEY,'+ + ' value TEXT, timestamp REAL)' + , win = function () { if (cb) return cb.call(that, that); } + + if (cb && typeof cb !== 'function') throw 'callback not valid'; + + // open a connection and create the db if it doesn't exist + this.db = openDatabase(this.name, '1.0.0', this.name, 65536) + this.db.transaction(function (t) { + t.executeSql(create, []) + }, fail, win) + }, + + keys: function (callback) { + var cb = this.lambda(callback) + , that = this + , keys = 'SELECT id FROM ' + this.record + ' ORDER BY timestamp DESC' + + this.db.readTransaction(function (t) { + var win = function (xxx, results) { + if (results.rows.length === 0) { + cb.call(that, []) + } else { + var r = []; + for (var i = 0, l = results.rows.length; i < l; i++) { + r.push(results.rows.item(i).id); } - } catch (e) { - fail(e) - throw e; + cb.call(that, r) } + } + t.executeSql(keys, [], win, fail) + }) + return this + }, + + // you think thats air you're breathing now? + save: function (obj, callback, error) { + var that = this + , error = error || function(){} + , insvals = [] + , ts = now() + + var objs = (this.isArray(obj) ? obj : [obj]).map(function (o) { + if (!o.key) { o.key = that.uuid() } + return o + }) + + var ins = 'INSERT OR REPLACE INTO ' + this.record + + ' (value, timestamp, id) VALUES (?,?,?)' + + var win = function () { + if (callback) { + that.lambda(callback).call(that, that.isArray(obj)? objs : objs[0]) + } + } - that.db.transaction(function(t) { - for (var i = 0, l = objs.length; i < l; i++) - t.executeSql(ins, insvals[i]) - }, function(e,i){fail(e,i)}, win) - - return this - }, - - - batch: function (objs, callback) { - return this.save(objs, callback) - }, - - get: function (keyOrArray, cb) { - var that = this - , sql = '' - , args = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray]; - // batch selects support - sql = 'SELECT id, value FROM ' + this.record + " WHERE id IN (" + - args.map(function(){return '?'}).join(",") + ")" - // FIXME - // will always loop the results but cleans it up if not a batch return at the end.. - // in other words, this could be faster - var win = function (xxx, results) { - var o - , r - , lookup = {} - // map from results to keys - for (var i = 0, l = results.rows.length; i < l; i++) { - o = JSON.parse(results.rows.item(i).value) - o.key = results.rows.item(i).id - lookup[o.key] = o; - } - r = args.map(function(key) { return lookup[key]; }); - if (!that.isArray(keyOrArray)) r = r.length ? r[0] : null - if (cb) that.lambda(cb).call(that, r) + try { + for (var i = 0, l = objs.length; i < l; i++) { + insvals[i] = [JSON.stringify(objs[i]), ts, objs[i].key]; + } + } catch (e) { + fail(e) + throw e; + } + + that.db.transaction(function (t) { + for (var i = 0, l = objs.length; i < l; i++) + t.executeSql(ins, insvals[i]) + }, function (e, i) { fail(e, i) }, win) + + return this + }, + + batch: function (objs, callback) { + return this.save(objs, callback) + }, + + get: function (keyOrArray, cb) { + var that = this + , sql = '' + , args = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray]; + // batch selects support + sql = 'SELECT id, value FROM ' + this.record + ' WHERE id IN (' + + args.map(function () { return '?' }).join(',') + ')' + // FIXME: will always loop the results but cleans it up if + // not a batch return at the end.. in other words, this + // could be faster + var win = function (xxx, results) { + var o + , r + , lookup = {} + // map from results to keys + for (var i = 0, l = results.rows.length; i < l; i++) { + o = JSON.parse(results.rows.item(i).value) + o.key = results.rows.item(i).id + lookup[o.key] = o; + } + r = args.map(function (key) { return lookup[key]; }); + if (!that.isArray(keyOrArray)) r = r.length ? r[0] : null + if (cb) that.lambda(cb).call(that, r) + } + this.db.readTransaction(function (t) { + t.executeSql(sql, args, win, fail) + }) + return this + }, + + exists: function (key, cb) { + var is = 'SELECT * FROM ' + this.record + ' WHERE id = ?' + , that = this + var win = function (xxx, results) { + if (cb) that.fn('exists', cb).call(that, (results.rows.length > 0)) + } + this.db.readTransaction(function (t) { + t.executeSql(is, [key], win, fail) + }) + return this + }, + + all: function (callback) { + var that = this + , all = 'SELECT * FROM ' + this.record + , r = [] + , cb = this.fn(this.name, callback) || undefined + , win = function (xxx, results) { + if (results.rows.length !== 0) { + for (var i = 0, l = results.rows.length; i < l; i++) { + var obj = JSON.parse(results.rows.item(i).value) + obj.key = results.rows.item(i).id + r.push(obj) + } } - this.db.readTransaction(function(t){ t.executeSql(sql, args, win, fail) }) - return this - }, - - exists: function (key, cb) { - var is = "SELECT * FROM " + this.record + " WHERE id = ?" - , that = this - , win = function(xxx, results) { if (cb) that.fn('exists', cb).call(that, (results.rows.length > 0)) } - this.db.readTransaction(function(t){ t.executeSql(is, [key], win, fail) }) - return this - }, - - all: function (callback) { - var that = this - , all = "SELECT * FROM " + this.record - , r = [] - , cb = this.fn(this.name, callback) || undefined - , win = function (xxx, results) { - if (results.rows.length != 0) { - for (var i = 0, l = results.rows.length; i < l; i++) { - var obj = JSON.parse(results.rows.item(i).value) - obj.key = results.rows.item(i).id - r.push(obj) - } - } - if (cb) cb.call(that, r) - } - - this.db.readTransaction(function (t) { - t.executeSql(all, [], win, fail) - }) - return this - }, - - remove: function (keyOrArray, cb) { - var that = this - , args - , sql = "DELETE FROM " + this.record + " WHERE id " - , win = function () { if (cb) that.lambda(cb).call(that) } - if (!this.isArray(keyOrArray)) { - sql += '= ?'; - args = [keyOrArray]; - } else { - args = keyOrArray; - sql += "IN (" + - args.map(function(){return '?'}).join(',') + - ")"; - } - args = args.map(function(obj) { - return obj.key ? obj.key : obj; - }); - - this.db.transaction( function (t) { - t.executeSql(sql, args, win, fail); - }); - - return this; - }, - - nuke: function (cb) { - var nuke = "DELETE FROM " + this.record - , that = this - , win = cb ? function() { that.lambda(cb).call(that) } : function(){} - this.db.transaction(function (t) { - t.executeSql(nuke, [], win, fail) - }) - return this - } + if (cb) cb.call(that, r) + } + + this.db.readTransaction(function (t) { + t.executeSql(all, [], win, fail) + }) + return this + }, + + remove: function (keyOrArray, cb) { + var that = this + , args + , sql = 'DELETE FROM ' + this.record + ' WHERE id ' + , win = function () { if (cb) that.lambda(cb).call(that) } + + if (!this.isArray(keyOrArray)) { + sql += '= ?'; + args = [keyOrArray]; + } else { + args = keyOrArray; + sql += 'IN (' + + args.map(function () { return '?' }).join(',') + + ')'; + } + args = args.map(function (obj) { + return obj.key ? obj.key : obj; + }); + + this.db.transaction(function (t) { + t.executeSql(sql, args, win, fail); + }); + + return this; + }, + + nuke: function (cb) { + var nuke = 'DELETE FROM ' + this.record + , that = this + , win = cb ? function () { that.lambda(cb).call(that) } : function(){} + this.db.transaction(function (t) { + t.executeSql(nuke, [], win, fail) + }) + return this + } + } + ////// -}})()); +})()); diff --git a/src/adapters/window-name.js b/src/adapters/window-name.js index cdf135b5..7f149e30 100644 --- a/src/adapters/window-name.js +++ b/src/adapters/window-name.js @@ -1,130 +1,140 @@ -// window.name code courtesy Remy Sharp: http://24ways.org/2009/breaking-out-the-edges-of-the-browser -Lawnchair.adapter('window-name', (function() { - if (typeof window==='undefined') { - window = { top: { } }; // node/optimizer compatibility - } +/* global Lawnchair */ +/* global window:true */ + +// window.name code courtesy Remy Sharp +// see: http://24ways.org/2009/breaking-out-the-edges-of-the-browser +Lawnchair.adapter('window-name', (function () { + 'use strict' + + if (typeof window === 'undefined') { + window = { top: { } }; // node/optimizer compatibility + } + + // edited from the original here by elsigh. Some sites store + // JSON data in window.top.name, but some folks (twitter on + // iPad) put simple strings in there - we should make sure not + // to cause a SyntaxError. + var data = {} + try { + data = JSON.parse(window.top.name) + } catch (e) {} + + return { + + valid: function () { + return typeof window.top.name !== 'undefined' + }, + + init: function (options, callback) { + data[this.name] = data[this.name] || { index:[], store:{} } + this.index = data[this.name].index + this.store = data[this.name].store + this.fn(this.name, callback).call(this, this) + return this + }, + + keys: function (callback) { + this.fn('keys', callback).call(this, this.index) + return this + }, + + save: function (obj, cb) { + // data[key] = value + ''; // force to string + // window.top.name = JSON.stringify(data); + var key = obj.key || this.uuid() + this.exists(key, function (exists) { + if (!exists) { + if (obj.key) delete obj.key + this.index.push(key) + } + this.store[key] = obj - // edited from the original here by elsigh - // Some sites store JSON data in window.top.name, but some folks (twitter on iPad) - // put simple strings in there - we should make sure not to cause a SyntaxError. - var data = {} - try { - data = JSON.parse(window.top.name) - } catch (e) {} - - - return { - - valid: function () { - return typeof window.top.name != 'undefined' - }, - - init: function (options, callback) { - data[this.name] = data[this.name] || {index:[],store:{}} - this.index = data[this.name].index - this.store = data[this.name].store - this.fn(this.name, callback).call(this, this) - return this - }, - - keys: function (callback) { - this.fn('keys', callback).call(this, this.index) - return this - }, - - save: function (obj, cb) { - // data[key] = value + ''; // force to string - // window.top.name = JSON.stringify(data); - var key = obj.key || this.uuid() - this.exists(key, function(exists) { - if (!exists) { - if (obj.key) delete obj.key - this.index.push(key) - } - this.store[key] = obj - - try { - window.top.name = JSON.stringify(data) // TODO wow, this is the only diff from the memory adapter - } catch(e) { - // restore index/store to previous value before JSON exception - if (!exists) { - this.index.pop(); - delete this.store[key]; - } - throw e; - } - - if (cb) { - obj.key = key - this.lambda(cb).call(this, obj) - } - }) - return this - }, - - batch: function (objs, cb) { - var r = [] - for (var i = 0, l = objs.length; i < l; i++) { - this.save(objs[i], function(record) { - r.push(record) - }) - } - if (cb) this.lambda(cb).call(this, r) - return this - }, - - get: function (keyOrArray, cb) { - var r; - if (this.isArray(keyOrArray)) { - r = [] - for (var i = 0, l = keyOrArray.length; i < l; i++) { - r.push(this.store[keyOrArray[i]]) - } - } else { - r = this.store[keyOrArray] - if (r) r.key = keyOrArray - } - if (cb) this.lambda(cb).call(this, r) - return this - }, - - exists: function (key, cb) { - this.lambda(cb).call(this, !!(this.store[key])) - return this - }, - - all: function (cb) { - var r = [] - for (var i = 0, l = this.index.length; i < l; i++) { - var obj = this.store[this.index[i]] - obj.key = this.index[i] - r.push(obj) - } - this.fn(this.name, cb).call(this, r) - return this - }, - - remove: function (keyOrArray, cb) { - var del = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray] - for (var i = 0, l = del.length; i < l; i++) { - var key = del[i].key ? del[i].key : del[i] - var where = this.indexOf(this.index, key) - if (where < 0) continue /* key not present */ - delete this.store[key] - this.index.splice(where, 1) - } - window.top.name = JSON.stringify(data) - if (cb) this.lambda(cb).call(this) - return this - }, - - nuke: function (cb) { - this.store = data[this.name].store = {} - this.index = data[this.name].index = [] - window.top.name = JSON.stringify(data) - if (cb) this.lambda(cb).call(this) - return this + try { + // TODO wow, this is the only diff from the memory adapter + window.top.name = JSON.stringify(data) + } catch (e) { + // restore index/store to previous value before JSON exception + if (!exists) { + this.index.pop(); + delete this.store[key]; + } + throw e; } + + if (cb) { + obj.key = key + this.lambda(cb).call(this, obj) + } + }) + return this + }, + + batch: function (objs, cb) { + var r = [] + for (var i = 0, l = objs.length; i < l; i++) { + /* jshint loopfunc:true */ + this.save(objs[i], function (record) { + r.push(record) + }) + /* jshint loopfunc:false */ + } + if (cb) this.lambda(cb).call(this, r) + return this + }, + + get: function (keyOrArray, cb) { + var r; + if (this.isArray(keyOrArray)) { + r = [] + for (var i = 0, l = keyOrArray.length; i < l; i++) { + r.push(this.store[keyOrArray[i]]) + } + } else { + r = this.store[keyOrArray] + if (r) r.key = keyOrArray + } + if (cb) this.lambda(cb).call(this, r) + return this + }, + + exists: function (key, cb) { + this.lambda(cb).call(this, !!(this.store[key])) + return this + }, + + all: function (cb) { + var r = [] + for (var i = 0, l = this.index.length; i < l; i++) { + var obj = this.store[this.index[i]] + obj.key = this.index[i] + r.push(obj) + } + this.fn(this.name, cb).call(this, r) + return this + }, + + remove: function (keyOrArray, cb) { + var del = this.isArray(keyOrArray) ? keyOrArray : [keyOrArray] + for (var i = 0, l = del.length; i < l; i++) { + var key = del[i].key ? del[i].key : del[i] + var where = this.indexOf(this.index, key) + if (where < 0) continue /* key not present */ + delete this.store[key] + this.index.splice(where, 1) + } + window.top.name = JSON.stringify(data) + if (cb) this.lambda(cb).call(this) + return this + }, + + nuke: function (cb) { + this.store = data[this.name].store = {} + this.index = data[this.name].index = [] + window.top.name = JSON.stringify(data) + if (cb) this.lambda(cb).call(this) + return this } + } + ///// })()); diff --git a/src/plugins/aggregation.js b/src/plugins/aggregation.js index 2d318174..d39c39ad 100644 --- a/src/plugins/aggregation.js +++ b/src/plugins/aggregation.js @@ -1,55 +1,60 @@ -Lawnchair.plugin({ - +/* global Lawnchair */ + +Lawnchair.plugin((function () { + 'use strict' + + return { // count of rows in the lawnchair collection with property count: function (property, callback) { - // if only one arg we count the collection - if ([].slice.call(arguments).length === 1) { - callback = property - property = 'key' - } - var c = 0 - this.each(function(e){ - if (e[property]) c++ - }) - this.fn('count', callback).call(this, c) + // if only one arg we count the collection + if ([].slice.call(arguments).length === 1) { + callback = property + property = 'key' + } + var c = 0 + this.each(function (e) { + if (e[property]) c++ + }) + this.fn('count', callback).call(this, c) }, - + // adds up property and returns sum sum: function (property, callback) { - var sum = 0 - this.each(function(e){ - if (e[property]) sum += e[property] - }) - this.fn('sum', callback).call(this, sum) + var sum = 0 + this.each(function (e) { + if (e[property]) sum += e[property] + }) + this.fn('sum', callback).call(this, sum) }, - // averages a property + // averages a property avg: function (property, callback) { - this.sum(property, function (sum) { - this.count(property, function (count) { - this.fn('avg', callback).call(this, sum/count) - }) + this.sum(property, function (sum) { + this.count(property, function (count) { + this.fn('avg', callback).call(this, sum/count) }) + }) }, // lowest number min: function (property, callback) { - this._minOrMax('min', property, callback) + this._minOrMax('min', property, callback) }, // highest number max: function (property, callback) { - this._minOrMax('max', property, callback) + this._minOrMax('max', property, callback) }, // helper for min/max _minOrMax: function (m, p, c) { - var r, all - this.all(function(a){ - all = a.map(function(e){ return e[p] }) - r = Math[m].apply(Math, all) - }) - this.fn(m, c).call(this, r) + var r, all + this.all(function (a) { + all = a.map(function (e) { return e[p] }) + r = Math[m].apply(Math, all) + }) + this.fn(m, c).call(this, r) } -// -- -}); + } + +})()); diff --git a/src/plugins/callbacks.js b/src/plugins/callbacks.js index a8e08600..6edaedba 100644 --- a/src/plugins/callbacks.js +++ b/src/plugins/callbacks.js @@ -1,86 +1,89 @@ +/* global Lawnchair */ + // I would mark my relationship with javascript as 'its complicated' -Lawnchair.plugin((function(){ - - // methods we want to augment with before/after callback registery capability - var methods = 'save batch get remove nuke'.split(' ') - , registry = {before:{}, after:{}} - - // fill in the blanks - for (var i = 0, l = methods.length; i < l; i++) { - registry.before[methods[i]] = [] - registry.after[methods[i]] = [] - } +Lawnchair.plugin((function () { + 'use strict' + + // methods we want to augment with before/after callback registery capability + var methods = 'save batch get remove nuke'.split(' ') + , registry = {before:{}, after:{}} + + // fill in the blanks + for (var i = 0, l = methods.length; i < l; i++) { + registry.before[methods[i]] = [] + registry.after[methods[i]] = [] + } + + return { + // start of module - return { - // start of module - - // roll thru each method we with to augment - init: function () { - for (var i = 0, l = methods.length; i < l; i++) { - this.evented(methods[i]) - } - }, - // TODO make private? - // rewrites a method with before/after callback capability - evented: function (methodName) { - var oldy = this[methodName], self = this - // overwrite the orig method - this[methodName] = function() { - var args = [].slice.call(arguments) - , beforeObj = args[0] - , oldCallback = args[args.length - 1] - , overwroteCallback = false - - // call before with obj - this.fire('before', methodName, beforeObj) - - if (typeof oldCallback === 'function') { - // overwrite final callback with after method injection - args[args.length - 1] = function(record) { - oldCallback.call(self, record) - self.fire('after', methodName, record) - } - overwroteCallback = true - } - - // finally call the orig method - oldy.apply(self, args) - - // if there was no callback to override for after we invoke here - if (!overwroteCallback) - self.fire('after', methodName, beforeObj) - } - }, - - // TODO definitely make private method - // for invoking callbacks - fire: function (when, methodName, record) { - var callbacks = registry[when][methodName] - for (var i = 0, l = callbacks.length; i < l; i++) { - callbacks[i].call(this, record) - } - }, - - // TODO cleanup duplication that starts here.. - clearBefore: function(methodName) { - registry.before[methodName] = [] - }, - - clearAfter: function(methodName) { - registry.after[methodName] = [] - }, - - // register before callback for methodName - before: function (methodName, callback) { - registry.before[methodName].push(callback) - }, - - // register after callback for methodName - after: function (methodName, callback) { - registry.after[methodName].push(callback) + // roll thru each method we with to augment + init: function () { + for (var i = 0, l = methods.length; i < l; i++) { + this.evented(methods[i]) + } + }, + + // TODO make private? + // rewrites a method with before/after callback capability + evented: function (methodName) { + var oldy = this[methodName], self = this + // overwrite the orig method + this[methodName] = function () { + var args = [].slice.call(arguments) + , beforeObj = args[0] + , oldCallback = args[args.length - 1] + , overwroteCallback = false + + // call before with obj + this.fire('before', methodName, beforeObj) + + if (typeof oldCallback === 'function') { + // overwrite final callback with after method injection + args[args.length - 1] = function (record) { + oldCallback.call(self, record) + self.fire('after', methodName, record) + } + overwroteCallback = true } - - // end module + + // finally call the orig method + oldy.apply(self, args) + + // if there was no callback to override for after we invoke here + if (!overwroteCallback) + self.fire('after', methodName, beforeObj) + } + }, + + // TODO definitely make private method + // for invoking callbacks + fire: function (when, methodName, record) { + var callbacks = registry[when][methodName] + for (var i = 0, l = callbacks.length; i < l; i++) { + callbacks[i].call(this, record) + } + }, + + // TODO cleanup duplication that starts here.. + clearBefore: function (methodName) { + registry.before[methodName] = [] + }, + + clearAfter: function (methodName) { + registry.after[methodName] = [] + }, + + // register before callback for methodName + before: function (methodName, callback) { + registry.before[methodName].push(callback) + }, + + // register after callback for methodName + after: function (methodName, callback) { + registry.after[methodName].push(callback) } -})()); + } + // end module +})()); diff --git a/src/plugins/pagination.js b/src/plugins/pagination.js index fa988f11..469b3a68 100644 --- a/src/plugins/pagination.js +++ b/src/plugins/pagination.js @@ -1,51 +1,61 @@ +/* global Lawnchair */ + /* -var p = new Lawnchair({name:'people', record:'person'}, function() { - - People = this - - People.page(2, function (page) { - // scoped iterator - this.each('console.log(person)') - // also correctly scoped callback data - console.log(page.people) - console.log(page.max) - console.log(page.next) - console.log(page.prev) - }) +var p = new Lawnchair({ name:'people', record:'person' }, function () { + + People = this + + People.page(2, function (page) { + // scoped iterator + this.each('console.log(person)') + // also correctly scoped callback data + console.log(page.people) + console.log(page.max) + console.log(page.next) + console.log(page.prev) + }) }) + // chaining friendly p.page(1, 'console.log(page.people)').each('console.log(person)') -*/ -Lawnchair.plugin({ +*/ + +Lawnchair.plugin((function () { + 'use strict' + return { page: function (page, callback) { - // some defaults - var objs = [] - , count = 5 // TODO make this configurable - , cur = ~~page || 1 - , next = cur + 1 - , prev = cur - 1 - , start = cur == 1 ? 0 : prev*count - , end = start >= count ? start+count : count - - // grab all the records - // FIXME if this was core we could use this.__results for faster queries - this.all(function(r){ - objs = r - }) - - // grab the metadata - var max = Math.ceil(objs.length/count) - , page = { max: max - , next: next > max ? max : next - , prev: prev == 0 ? 1 : prev - } - - // reassign to the working resultset - this.__results = page[this.name] = objs.slice(start, end) - - // callback / chain - if (callback) this.fn('page', callback).call(this, page) + /* jshint bitwise:false */ + // some defaults + var objs = [] + , count = 5 // TODO make this configurable + , cur = ~~page || 1 + , next = cur + 1 + , prev = cur - 1 + , start = cur === 1 ? 0 : prev*count + , end = start >= count ? start+count : count + + // grab all the records + // FIXME if this was core we could use this.__results for faster queries + this.all(function (r) { + objs = r + }) + + // grab the metadata + var max = Math.ceil(objs.length/count) + , page = { + max: max + , next: next > max ? max : next + , prev: prev === 0 ? 1 : prev + } + + // reassign to the working resultset + this.__results = page[this.name] = objs.slice(start, end) + + // callback / chain + if (callback) this.fn('page', callback).call(this, page) return this - } -}); + } + } + +})()); diff --git a/src/plugins/query.js b/src/plugins/query.js index 808566fb..812ce2ce 100644 --- a/src/plugins/query.js +++ b/src/plugins/query.js @@ -1,65 +1,76 @@ +/* global Lawnchair */ + // - NOT jsonPath or jsonQuery which are horrendously complex and fugly // - simple query syntax 'its just javascript' -// - simple string interpolation +// - simple string interpolation // - search then sorting -Lawnchair.plugin((function(){ - // - var interpolate = function(template, args) { - var parts = template.split('?').filter(function(i) { return i != ''}) - , query = '' +Lawnchair.plugin((function () { + 'use strict' - for (var i = 0, l = parts.length; i < l; i++) { - query += parts[i] + args[i] - } - return query - } - - var sorter = function(p) { - return function(a, b) { - if (a[p] < b[p]) return -1 - if (a[p] > b[p]) return 1 - return 0 - } + // + var interpolate = function (template, args) { + var parts = template.split('?').filter(function (i) { return i !== '' }) + , query = '' + + for (var i = 0, l = parts.length; i < l; i++) { + query += parts[i] + args[i] } - // - return { - // query the storage obj - where: function() { - // ever notice we do this sort thing lots? - var args = [].slice.call(arguments) - , tmpl = args.shift() - , last = args[args.length - 1] - , qs = tmpl.match(/\?/g) - , q = qs && qs.length > 0 ? interpolate(tmpl, args.slice(0, qs.length)) : tmpl - , is = new Function(this.record, 'return !!(' + q + ')') - , r = [] - , cb - // iterate the entire collection - // TODO should we allow for chained where() to filter __results? (I'm thinking no b/c creates funny behvaiors w/ callbacks) - this.all(function(all){ - for (var i = 0, l = all.length; i < l; i++) { - if (is.call(all[i], all[i])) r.push(all[i]) - } - // overwrite working results - this.__results = r - // callback / chain - if (args.length === 1) this.fn(this.name, last).call(this, this.__results) - }) - return this - }, + return query + } - // FIXME should be able to call without this.__results - // ascending sort the working storage obj on a property (or nested property) - asc: function(property, callback) { - this.fn(this.name, callback).call(this, this.__results.sort(sorter(property))) - return this - }, + var sorter = function (p) { + return function (a, b) { + if (a[p] < b[p]) return -1 + if (a[p] > b[p]) return 1 + return 0 + } + } - // descending sort on working storage object on a property - desc: function(property, callback) { - this.fn(this.name, callback).call(this, this.__results.sort(sorter(property)).reverse()) - return this + // + return { + // query the storage obj + where: function () { + // ever notice we do this sort thing lots? + var args = [].slice.call(arguments) + , tmpl = args.shift() + , last = args[args.length - 1] + , qs = tmpl.match(/\?/g) + , q = qs && qs.length > 0 + ? interpolate(tmpl, args.slice(0, qs.length)) + : tmpl + , is = new Function(this.record, 'return !!(' + q + ')') + , r = [] + // iterate the entire collection. + // TODO: should we allow for chained where() to filter__results? + // (I'm thinking no b/c creates funny behvaiors w/ callbacks) + this.all(function (all) { + for (var i = 0, l = all.length; i < l; i++) { + if (is.call(all[i], all[i])) r.push(all[i]) } - } -///// + // overwrite working results + this.__results = r + // callback / chain + if (args.length === 1) + this.fn(this.name, last).call(this, this.__results) + }) + return this + }, + + // FIXME should be able to call without this.__results + // ascending sort the working storage obj on a property (or nested property) + asc: function (property, callback) { + this.fn(this.name, callback) + .call(this, this.__results.sort(sorter(property))) + return this + }, + + // descending sort on working storage object on a property + desc: function (property, callback) { + this.fn(this.name, callback) + .call(this, this.__results.sort(sorter(property)).reverse()) + return this + } + } + +///// })());