Updated packages via auto-update.js

This commit is contained in:
Thomas Davis
2014-05-01 04:30:42 -04:00
parent dbc958d7d4
commit a1ded1f981
321 changed files with 0 additions and 41329 deletions
Binary file not shown.
@@ -1,28 +0,0 @@
{
"name": "autosize",
"title": "Autosize",
"description": "Automatically adjust textarea height based on user input.",
"version": "1.17.2",
"dependencies": {
"jquery": ">=1.7"
},
"keywords": [
"form",
"textarea",
"ui",
"jQuery"
],
"author": {
"name": "Jack Moore",
"url": "http://www.jacklmoore.com",
"email": "hello@jacklmoore.com"
},
"licenses": [
{
"type": "MIT",
"url": "http://www.opensource.org/licenses/mit-license.php"
}
],
"homepage": "http://www.jacklmoore.com/autosize",
"demo": "http://www.jacklmoore.com/autosize"
}
@@ -1,29 +0,0 @@
{
"name": "jquery-autosize",
"description": "Automatically adjust textarea height based on user input.",
"version": "1.17.2",
"dependencies": {
"jquery": ">=1.7"
},
"keywords": [
"form",
"textarea",
"ui",
"jQuery"
],
"authors": [
{
"name": "Jack Moore",
"url": "http://www.jacklmoore.com",
"email": "hello@jacklmoore.com"
}
],
"licenses": [
{
"type": "MIT",
"url": "http://www.opensource.org/licenses/mit-license.php"
}
],
"homepage": "http://www.jacklmoore.com/autosize",
"main": "jquery.autosize.js"
}
-36
View File
@@ -1,36 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'/>
<title>Textarea Autosize Demo</title>
<style>
textarea {
border:2px solid #ccc;
padding: 10px;
vertical-align: top;
width: 25%;
}
.animated {
-webkit-transition: height 0.2s;
-moz-transition: height 0.2s;
transition: height 0.2s;
}
</style>
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js'></script>
<script src='jquery.autosize.js'></script>
<script>
$(function(){
$('.normal').autosize();
$('.animated').autosize({append: "\n"});
});
</script>
</head>
<body>
<textarea class='normal'>Not animated.</textarea>
<textarea class='normal'>The coconut palm (also, cocoanut), Cocos nucifera, is a member of the family Arecaceae (palm family). It is the only accepted species in the genus Cocos.[2] The term coconut can refer to the entire coconut palm, the seed, or the fruit, which, botanically, is a drupe, not a nut. The spelling cocoanut is an archaic form of the word.[3] The term is derived from 16th-century Portuguese and Spanish coco, meaning "head" or "skull",[4] from the three small holes on the coconut shell that resemble human facial features.</textarea>
<textarea class='animated'>With CSS transition.</textarea>
</body>
</html>
@@ -1,7 +0,0 @@
/*!
Autosize v1.17.2 - 2013-07-28
Automatically adjust textarea height based on user input.
(c) 2013 Jack Moore - http://www.jacklmoore.com/autosize
license: http://www.opensource.org/licenses/mit-license.php
*/
(function(e){"function"==typeof define&&define.amd?define("jquery-autosize",["jquery"],e):e(window.jQuery||window.$)})(function(e){var t,o={className:"autosizejs",append:"",callback:!1,resizeDelay:10},i='<textarea tabindex="-1" style="position:absolute; top:-999px; left:0; right:auto; bottom:auto; border:0; -moz-box-sizing:content-box; -webkit-box-sizing:content-box; box-sizing:content-box; word-wrap:break-word; height:0 !important; min-height:0 !important; overflow:hidden; transition:none; -webkit-transition:none; -moz-transition:none;"/>',n=["fontFamily","fontSize","fontWeight","fontStyle","letterSpacing","textTransform","wordSpacing","textIndent"],s=e(i).data("autosize",!0)[0];s.style.lineHeight="99px","99px"===e(s).css("lineHeight")&&n.push("lineHeight"),s.style.lineHeight="",e.fn.autosize=function(i){return i=e.extend({},o,i||{}),s.parentNode!==document.body&&e(document.body).append(s),this.each(function(){function o(){var o,a={};if(t=c,s.className=i.className,l=parseInt(h.css("maxHeight"),10),e.each(n,function(e,t){a[t]=h.css(t)}),e(s).css(a),"oninput"in c){var r=c.style.width;c.style.width="0px",o=c.offsetWidth,c.style.width=r}}function a(){var n,a,r,u;t!==c&&o(),s.value=c.value+i.append,s.style.overflowY=c.style.overflowY,a=parseInt(c.style.height,10),"getComputedStyle"in window?(u=window.getComputedStyle(c),r=c.getBoundingClientRect().width,e.each(["paddingLeft","paddingRight","borderLeftWidth","borderRightWidth"],function(e,t){r-=parseInt(u[t],10)}),s.style.width=r+"px"):s.style.width=Math.max(h.width(),0)+"px",s.scrollTop=0,s.scrollTop=9e4,n=s.scrollTop,l&&n>l?(c.style.overflowY="scroll",n=l):(c.style.overflowY="hidden",d>n&&(n=d)),n+=p,a!==n&&(c.style.height=n+"px",f&&i.callback.call(c,c))}function r(){clearTimeout(u),u=setTimeout(function(){h.width()!==z&&a()},parseInt(i.resizeDelay,10))}var l,d,u,c=this,h=e(c),p=0,f=e.isFunction(i.callback),w={height:c.style.height,overflow:c.style.overflow,overflowY:c.style.overflowY,wordWrap:c.style.wordWrap,resize:c.style.resize},z=h.width();h.data("autosize")||(h.data("autosize",!0),("border-box"===h.css("box-sizing")||"border-box"===h.css("-moz-box-sizing")||"border-box"===h.css("-webkit-box-sizing"))&&(p=h.outerHeight()-h.height()),d=Math.max(parseInt(h.css("minHeight"),10)-p||0,h.height()),h.css({overflow:"hidden",overflowY:"hidden",wordWrap:"break-word",resize:"none"===h.css("resize")||"vertical"===h.css("resize")?"none":"horizontal"}),"onpropertychange"in c?"oninput"in c?h.on("input.autosize keyup.autosize",a):h.on("propertychange.autosize",function(){"value"===event.propertyName&&a()}):h.on("input.autosize",a),i.resizeDelay!==!1&&e(window).on("resize.autosize",r),h.on("autosize.resize",a),h.on("autosize.resizeIncludeStyle",function(){t=null,a()}),h.on("autosize.destroy",function(){t=null,clearTimeout(u),e(window).off("resize",r),h.off("autosize").off(".autosize").css(w).removeData("autosize")}),a())})}});
@@ -1,242 +0,0 @@
/*!
Autosize v1.17.2 - 2013-07-28
Automatically adjust textarea height based on user input.
(c) 2013 Jack Moore - http://www.jacklmoore.com/autosize
license: http://www.opensource.org/licenses/mit-license.php
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define('jquery-autosize', ['jquery'], factory);
} else {
// Browser globals: jQuery or jQuery-like library, such as Zepto
factory(window.jQuery || window.$);
}
}(function ($) {
var
defaults = {
className: 'autosizejs',
append: '',
callback: false,
resizeDelay: 10
},
// border:0 is unnecessary, but avoids a bug in FireFox on OSX
copy = '<textarea tabindex="-1" style="position:absolute; top:-999px; left:0; right:auto; bottom:auto; border:0; -moz-box-sizing:content-box; -webkit-box-sizing:content-box; box-sizing:content-box; word-wrap:break-word; height:0 !important; min-height:0 !important; overflow:hidden; transition:none; -webkit-transition:none; -moz-transition:none;"/>',
// line-height is conditionally included because IE7/IE8/old Opera do not return the correct value.
typographyStyles = [
'fontFamily',
'fontSize',
'fontWeight',
'fontStyle',
'letterSpacing',
'textTransform',
'wordSpacing',
'textIndent'
],
// to keep track which textarea is being mirrored when adjust() is called.
mirrored,
// the mirror element, which is used to calculate what size the mirrored element should be.
mirror = $(copy).data('autosize', true)[0];
// test that line-height can be accurately copied.
mirror.style.lineHeight = '99px';
if ($(mirror).css('lineHeight') === '99px') {
typographyStyles.push('lineHeight');
}
mirror.style.lineHeight = '';
$.fn.autosize = function (options) {
options = $.extend({}, defaults, options || {});
if (mirror.parentNode !== document.body) {
$(document.body).append(mirror);
}
return this.each(function () {
var
ta = this,
$ta = $(ta),
maxHeight,
minHeight,
boxOffset = 0,
callback = $.isFunction(options.callback),
originalStyles = {
height: ta.style.height,
overflow: ta.style.overflow,
overflowY: ta.style.overflowY,
wordWrap: ta.style.wordWrap,
resize: ta.style.resize
},
timeout,
width = $ta.width();
if ($ta.data('autosize')) {
// exit if autosize has already been applied, or if the textarea is the mirror element.
return;
}
$ta.data('autosize', true);
if ($ta.css('box-sizing') === 'border-box' || $ta.css('-moz-box-sizing') === 'border-box' || $ta.css('-webkit-box-sizing') === 'border-box'){
boxOffset = $ta.outerHeight() - $ta.height();
}
// IE8 and lower return 'auto', which parses to NaN, if no min-height is set.
minHeight = Math.max(parseInt($ta.css('minHeight'), 10) - boxOffset || 0, $ta.height());
$ta.css({
overflow: 'hidden',
overflowY: 'hidden',
wordWrap: 'break-word', // horizontal overflow is hidden, so break-word is necessary for handling words longer than the textarea width
resize: ($ta.css('resize') === 'none' || $ta.css('resize') === 'vertical') ? 'none' : 'horizontal'
});
function initMirror() {
var styles = {}, ignore;
mirrored = ta;
mirror.className = options.className;
maxHeight = parseInt($ta.css('maxHeight'), 10);
// mirror is a duplicate textarea located off-screen that
// is automatically updated to contain the same text as the
// original textarea. mirror always has a height of 0.
// This gives a cross-browser supported way getting the actual
// height of the text, through the scrollTop property.
$.each(typographyStyles, function(i,val){
styles[val] = $ta.css(val);
});
$(mirror).css(styles);
// The textarea overflow is probably now hidden, but Chrome doesn't reflow the text to account for the
// new space made available by removing the scrollbars. This workaround causes Chrome to reflow the text.
if ('oninput' in ta) {
var width = ta.style.width;
ta.style.width = '0px';
ignore = ta.offsetWidth; // This value isn't used, but getting it triggers the necessary reflow
ta.style.width = width;
}
}
// Using mainly bare JS in this function because it is going
// to fire very often while typing, and needs to very efficient.
function adjust() {
var height, original, width, style;
if (mirrored !== ta) {
initMirror();
}
mirror.value = ta.value + options.append;
mirror.style.overflowY = ta.style.overflowY;
original = parseInt(ta.style.height,10);
// window.getComputedStyle, getBoundingClientRect returning a width are unsupported in IE8 and lower.
// The mirror width must exactly match the textarea width, so using getBoundingClientRect because it doesn't round the sub-pixel value.
if ('getComputedStyle' in window) {
style = window.getComputedStyle(ta);
width = ta.getBoundingClientRect().width;
$.each(['paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'], function(i,val){
width -= parseInt(style[val],10);
});
mirror.style.width = width + 'px';
}
else {
mirror.style.width = Math.max($ta.width(), 0) + 'px';
}
// Needed for IE8 and lower to reliably return the correct scrollTop
mirror.scrollTop = 0;
mirror.scrollTop = 9e4;
// Using scrollTop rather than scrollHeight because scrollHeight is non-standard and includes padding.
height = mirror.scrollTop;
if (maxHeight && height > maxHeight) {
ta.style.overflowY = 'scroll';
height = maxHeight;
} else {
ta.style.overflowY = 'hidden';
if (height < minHeight) {
height = minHeight;
}
}
height += boxOffset;
if (original !== height) {
ta.style.height = height + 'px';
if (callback) {
options.callback.call(ta,ta);
}
}
}
function resize () {
clearTimeout(timeout);
timeout = setTimeout(function(){
if ($ta.width() !== width) {
adjust();
}
}, parseInt(options.resizeDelay,10));
}
if ('onpropertychange' in ta) {
if ('oninput' in ta) {
// Detects IE9. IE9 does not fire onpropertychange or oninput for deletions,
// so binding to onkeyup to catch most of those occasions. There is no way that I
// know of to detect something like 'cut' in IE9.
$ta.on('input.autosize keyup.autosize', adjust);
} else {
// IE7 / IE8
$ta.on('propertychange.autosize', function(){
if(event.propertyName === 'value'){
adjust();
}
});
}
} else {
// Modern Browsers
$ta.on('input.autosize', adjust);
}
// Set options.resizeDelay to false if using fixed-width textarea elements.
// Uses a timeout and width check to reduce the amount of times adjust needs to be called after window resize.
if (options.resizeDelay !== false) {
$(window).on('resize.autosize', resize);
}
// Event for manual triggering if needed.
// Should only be needed when the value of the textarea is changed through JavaScript rather than user input.
$ta.on('autosize.resize', adjust);
// Event for manual triggering that also forces the styles to update as well.
// Should only be needed if one of typography styles of the textarea change, and the textarea is already the target of the adjust method.
$ta.on('autosize.resizeIncludeStyle', function() {
mirrored = null;
adjust();
});
$ta.on('autosize.destroy', function(){
mirrored = null;
clearTimeout(timeout);
$(window).off('resize', resize);
$ta
.off('autosize')
.off('.autosize')
.css(originalStyles)
.removeData('autosize');
});
// Call adjust in case the textarea already contains text.
adjust();
});
};
}));
@@ -1,27 +0,0 @@
{
"name": "jquery-autosize",
"description": "Automatically adjust textarea height based on user input.",
"version": "1.17.2",
"dependencies": {},
"keywords": [
"form",
"textarea",
"ui",
"jQuery"
],
"authors": [
{
"name": "Jack Moore",
"url": "http://www.jacklmoore.com",
"email": "hello@jacklmoore.com"
}
],
"licenses": [
{
"type": "MIT",
"url": "http://www.opensource.org/licenses/mit-license.php"
}
],
"homepage": "http://www.jacklmoore.com/autosize",
"main": "jquery.autosize.js"
}
-139
View File
@@ -1,139 +0,0 @@
## Autosize
Small jQuery plugin to allow dynamic resizing of textarea height, so that it grows as based on visitor input. To use, just call the `.autosize()` method on any textarea element. Example `$('textarea').autosize();`. See the [project page](http://jacklmoore.com/autosize/) for documentation, caveats, and a demonstration. Released under the [MIT license](http://www.opensource.org/licenses/mit-license.php).
## Changelog
### v1.17.2 - 2013/7/28
* Added support for loading as an AMD module.
* Added package.json for installing through NPM.
### v1.17.1 - 2013/6/22
* Fixed potential memory leak when using autosize.destroy.
### v1.17.0 - 2013/6/19
* Renamed 'autosize' event to 'autosize.resize'
* Renamed 'autosize.includeStyle' event to 'autosize.resizeIncludeStyle'
* Fixes problem introduced in 1.16.18 with manually triggering the 'autosize' event:
### v1.16.20 - 2013/6/18
* Minor improvement to the destroy event.
### v1.16.19 - 2013/6/18
* Added event for removing autosize from a textarea element:
$('textarea.example').trigger('autosize.destroy');
### v1.16.18 - 2013/6/18
* Added event for manually triggering resize that also accounts for typographic styles that have changed on the textarea element. Example:
$('textarea.example').css('text-indent', 25);
$('textarea.example').trigger('autosize.includeStyle');
* Minor optimization
### v1.16.17 - 2013/6/12
* Fixed a compatibility issue with jQuery versions before 1.9 introduced in the previous update.
### v1.16.16 - 2013/6/11
* Fixed an issue where the calculated height might be slightly off in modern browsers when the width of the textarea has a sub-pixel value.
### v1.16.15 - 2013/6/7
* Reduced how frequently autosize is triggered when resizing the window. Added resizeDelay property so that the frequency can be adjusted or disabled.
### v1.16.14 - 2013/6/6
* Fixed an issue with autosize working poorly if the mirror element has a transition applied to it's width.
### v1.16.13 - 2013/6/4
* Fixed a Chrome cursor position issue introduced with the reflow workaround added in 1.16.10.
### v1.16.12 - 2013/5/31
* Much better efficiency and smoothness for IE8 and lower.
### v1.16.11 - 2013/5/31
* Fixed a default height issue in IE8 and lower.
### v1.16.10 - 2013/5/30
* Dropped scrollHeight for scrollTop. This fixed a height problem relating to padding. (Fixes #70)
* Re-added workaround to get Chrome to reflow text after hiding overflow.
### v1.16.9 - 2013/5/20
* Reverted change from 1.16.8 as it caused an issue in IE8. (Fixes #69)
### v1.16.8 - 2013/5/7
* Fixed issue where autosize was creating a horizontal scrollbar for a user
### v1.16.7 - 2013/3/20
* Added workaround for a very edge-case iOS bug (Fixes #58).
### v1.16.6 - 2013/3/12
* Replaced jQuery shorthand methods with on() in anticipation of jQuery 2.0 conditional builds
### v1.16.5 - 2013/3/12
* Fixed a bug where triggering the autosize event immediately after assigning autosize had no effect.
### v1.16.4 - 2013/1/29
* Fixed a conflict with direction:ltr pages.
### v1.16.3 - 2013/1/23
* Added minified file back to repository
### v1.16.2 - 2013/1/20
* Minor box-sizing issue dealing with min-heights.
### v1.16.1 - 2013/1/20
* Added to plugins.jquery.com
### v1.15 - 2012/11/16
* Reworked to only create a single mirror element, instead of one for each textarea.
* Dropped feature detection for FF3 and Safari 4.
### v1.14 - 2012/10/6
* Added 'append' option for appending whitespace to the end of the height calculation (an extra newline improves the apperance when animating).
* Added a demonstration of animating the height change using a CSS transition.
### v1.13 - 2012/9/21
* Added optional callback that fires after resize.
### v1.12 - 2012/9/3
* Fixed a bug I introduced in the last update.
### v1.11 - 2012/8/8
* Added workaround to get Chrome to reflow default text better.
### v1.10 - 2012/4/30
* Added 'lineHeight' to the list of styles considered for size detection.
### v1.9 - 2012/6/19
* Added 'textIndent' to the list of styles considered for size detection.
* Added vender prefixes to box-sizing detection
### v1.8 - 2012/6/7
* Added conditional so that autosize cannot be applied twice to the same element
* When autosize is applied to an element, it will have a data property that links it to the mirrored textarea element. This will make it easier to keep track of and remove unneeded mirror elements. Example:
$('textarea.example').data('mirror').remove(); // delete the mirror
$('textarea.example').remove(); // delete the original
### v1.7 - 2012/5/3
* Now supports box-sizing:border-box
### v1.6 - 2012/2/11
* added binding to allow autosize to be triggered manually. Example:
$('#myTextArea').trigger('autosize');
### v1.5 - 2011/12/7
* fixed a regression in detecting FireFox support
### v1.4 - 2011/11/22
* added branching to exclude old browsers (FF3- & Safari4-)
### v1.3 - 2011/11/13
* fixed a regression in 1.1 relating to Opera.
### v1.2 - 2011/11/10
* fixed a regression in 1.1 that broke autosize for IE9.
### v1.1 - 2011/11/10
* autosize now follows the max-height of textareas. OverflowY will be set to scroll once the content height exceeds max-height.
### v1.0 - 2011/11/7
* first release
Binary file not shown.
-2
View File
@@ -1,2 +0,0 @@
.project
.idea
-464
View File
@@ -1,464 +0,0 @@
/*
* IDBWrapper - A cross-browser wrapper for IndexedDB
* Copyright (c) 2011 - 2012 Jens Arps
* http://jensarps.de/
*
* Licensed under the MIT (X11) license
*/
"use strict";
(function (name, definition, global) {
if (typeof define === 'function') {
define(definition);
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = definition();
} else {
global[name] = definition();
}
})('IDBStore', function () {
var IDBStore;
var defaults = {
storeName: 'Store',
dbVersion: 1,
keyPath: 'id',
autoIncrement: true,
onStoreReady: function () {
},
indexes: []
};
IDBStore = function (kwArgs, onStoreReady) {
function fixupConstants (object, constants) {
for (var prop in constants) {
if (!(prop in object))
object[prop] = constants[prop];
}
}
for(var key in defaults){
this[key] = typeof kwArgs[key] != 'undefined' ? kwArgs[key] : defaults[key];
}
this.dbName = 'IDBWrapper-' + this.storeName;
this.dbVersion = parseInt(this.dbVersion, 10);
onStoreReady && (this.onStoreReady = onStoreReady);
this.idb = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
this.keyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange;
this.consts = window.IDBTransaction || window.webkitIDBTransaction;
fixupConstants(this.consts, {
'READ_ONLY': 'readonly',
'READ_WRITE': 'readwrite',
'VERSION_CHANGE': 'versionchange'
});
this.cursor = window.IDBCursor || window.webkitIDBCursor;
fixupConstants(this.cursor, {
'NEXT': 'next',
'NEXT_NO_DUPLICATE': 'nextunique',
'PREV': 'prev',
'PREV_NO_DUPLICATE': 'prevunique'
});
this.openDB();
};
IDBStore.prototype = {
db: null,
dbName: null,
dbVersion: null,
store: null,
storeName: null,
keyPath: null,
autoIncrement: null,
indexes: null,
features: null,
onStoreReady: null,
openDB: function () {
this.newVersionAPI = typeof this.idb.setVersion == 'undefined';
if(!this.newVersionAPI){
throw new Error('The IndexedDB implementation in this browser is outdated. Please upgrade your browser.');
}
var features = this.features = {};
features.hasAutoIncrement = !window.mozIndexedDB; // TODO: Still, really?
var openRequest = this.idb.open(this.dbName, this.dbVersion);
openRequest.onerror = function (error) {
var gotVersionErr = false;
if ('error' in error.target) {
gotVersionErr = error.target.error.name == "VersionError";
} else if ('errorCode' in error.target) {
gotVersionErr = error.target.errorCode == 12; // TODO: Use const
}
if (gotVersionErr) {
console.error('Could not open database, version error:', error);
} else {
console.error('Could not open database, error:', error);
}
}.bind(this);
openRequest.onsuccess = function (event) {
if(this.db){
this.onStoreReady();
return;
}
this.db = event.target.result;
if(this.db.objectStoreNames.contains(this.storeName)){
if(!this.store){
var emptyTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
this.store = emptyTransaction.objectStore(this.storeName);
}
// check indexes
this.indexes.forEach(function(indexData){
var indexName = indexData.name;
// normalize and provide existing keys
indexData.keyPath = indexData.keyPath || indexName;
indexData.unique = !!indexData.unique;
indexData.multiEntry = !!indexData.multiEntry;
if(!indexName){
throw new Error('Cannot create index: No index name given.');
}
if(this.hasIndex(indexName)){
// check if it complies
var actualIndex = this.store.index(indexName);
var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
// IE10 returns undefined for no multiEntry
if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
return true;
}
return indexData[key] == actualIndex[key];
});
if(!complies){
throw new Error('Cannot modify index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
} else {
throw new Error('Cannot create new index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
}, this);
this.onStoreReady();
} else {
// We should never get here.
throw new Error('Cannot create a new store for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
}.bind(this);
openRequest.onupgradeneeded = function(/* IDBVersionChangeEvent */ event){
this.db = event.target.result;
if(this.db.objectStoreNames.contains(this.storeName)){
this.store = event.target.transaction.objectStore(this.storeName);
} else {
this.store = this.db.createObjectStore(this.storeName, { keyPath: this.keyPath, autoIncrement: this.autoIncrement});
}
this.indexes.forEach(function(indexData){
var indexName = indexData.name;
// normalize and provide existing keys
indexData.keyPath = indexData.keyPath || indexName;
indexData.unique = !!indexData.unique;
indexData.multiEntry = !!indexData.multiEntry;
if(!indexName){
throw new Error('Cannot create index: No index name given.');
}
if(this.hasIndex(indexName)){
// check if it complies
var actualIndex = this.store.index(indexName);
var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
// IE10 returns undefined for no multiEntry
if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
return true;
}
return indexData[key] == actualIndex[key];
});
if(!complies){
// index differs, need to delete and re-create
this.store.deleteIndex(indexName);
this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
}
} else {
this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
}
}, this);
}.bind(this);
},
deleteDatabase: function () {
if (this.idb.deleteDatabase) {
this.idb.deleteDatabase(this.dbName);
}
},
/*********************
* data manipulation *
*********************/
put: function (dataObj, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not write data.', error);
});
onSuccess || (onSuccess = noop);
if (typeof dataObj[this.keyPath] == 'undefined' && !this.features.hasAutoIncrement) {
dataObj[this.keyPath] = this._getUID();
}
var putTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var putRequest = putTransaction.objectStore(this.storeName).put(dataObj);
putRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
putRequest.onerror = onError;
},
get: function (key, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not read data.', error);
});
onSuccess || (onSuccess = noop);
var getTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var getRequest = getTransaction.objectStore(this.storeName).get(key);
getRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
getRequest.onerror = onError;
},
remove: function (key, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not remove data.', error);
});
onSuccess || (onSuccess = noop);
var removeTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var deleteRequest = removeTransaction.objectStore(this.storeName).delete(key);
deleteRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
deleteRequest.onerror = onError;
},
getAll: function (onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not read data.', error);
});
onSuccess || (onSuccess = noop);
var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var store = getAllTransaction.objectStore(this.storeName);
if (store.getAll) {
var getAllRequest = store.getAll();
getAllRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
getAllRequest.onerror = onError;
} else {
this._getAllCursor(getAllTransaction, onSuccess, onError);
}
},
_getAllCursor: function (tr, onSuccess, onError) {
var all = [];
var store = tr.objectStore(this.storeName);
var cursorRequest = store.openCursor();
cursorRequest.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
all.push(cursor.value);
cursor['continue']();
}
else {
onSuccess(all);
}
};
cursorRequest.onError = onError;
},
clear: function (onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not clear store.', error);
});
onSuccess || (onSuccess = noop);
var clearTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var clearRequest = clearTransaction.objectStore(this.storeName).clear();
clearRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
clearRequest.onerror = onError;
},
_getUID: function () {
// FF bails at times on non-numeric ids. So we take an even
// worse approach now, using current time as id. Sigh.
return +new Date();
},
/************
* indexing *
************/
getIndexList: function () {
return this.store.indexNames;
},
hasIndex: function (indexName) {
return this.store.indexNames.contains(indexName);
},
/**********
* cursor *
**********/
iterate: function (onItem, options) {
options = mixin({
index: null,
order: 'ASC',
filterDuplicates: false,
keyRange: null,
writeAccess: false,
onEnd: null,
onError: function (error) {
console.error('Could not open cursor.', error);
}
}, options || {});
var directionType = options.order.toLowerCase() == 'desc' ? 'PREV' : 'NEXT';
if (options.filterDuplicates) {
directionType += '_NO_DUPLICATE';
}
var cursorTransaction = this.db.transaction([this.storeName], this.consts[options.writeAccess ? 'READ_WRITE' : 'READ_ONLY']);
var cursorTarget = cursorTransaction.objectStore(this.storeName);
if (options.index) {
cursorTarget = cursorTarget.index(options.index);
}
var cursorRequest = cursorTarget.openCursor(options.keyRange, this.cursor[directionType]);
cursorRequest.onerror = options.onError;
cursorRequest.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
onItem(cursor.value, cursor, cursorTransaction);
cursor['continue']();
} else {
if(options.onEnd){
options.onEnd()
} else {
onItem(null);
}
}
};
},
count: function (onSuccess, options) {
options = mixin({
index: null,
keyRange: null
}, options || {});
var onError = options.onError || function (error) {
console.error('Could not open cursor.', error);
};
var cursorTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var cursorTarget = cursorTransaction.objectStore(this.storeName);
if (options.index) {
cursorTarget = cursorTarget.index(options.index);
}
var countRequest = cursorTarget.count(options.keyRange);
countRequest.onsuccess = function (evt) {
onSuccess(evt.target.result);
};
countRequest.onError = function (error) {
onError(error);
};
},
/**************/
/* key ranges */
/**************/
makeKeyRange: function(options){
var keyRange,
hasLower = typeof options.lower != 'undefined',
hasUpper = typeof options.upper != 'undefined';
switch(true){
case hasLower && hasUpper:
keyRange = this.keyRange.bound(options.lower, options.upper, options.excludeLower, options.excludeUpper);
break;
case hasLower:
keyRange = this.keyRange.lowerBound(options.lower, options.excludeLower);
break;
case hasUpper:
keyRange = this.keyRange.upperBound(options.upper, options.excludeUpper);
break;
default:
throw new Error('Cannot create KeyRange. Provide one or both of "lower" or "upper" value.');
break;
}
return keyRange;
}
};
/** helpers **/
var noop = function () {
};
var empty = {};
var mixin = function (target, source) {
var name, s;
for (name in source) {
s = source[name];
if (s !== empty[name] && s !== target[name]) {
target[name] = s;
}
}
return target;
};
return IDBStore;
}, this);
-19
View File
@@ -1,19 +0,0 @@
Copyright (c) 2011 - 2012 Jens Arps
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-313
View File
@@ -1,313 +0,0 @@
About
=====
This is a wrapper for indexedDB. It is meant to
a) ease the use of indexedDB and abstract away the differences between the
existing impls in Chrome, Firefox and IE10 (yes, it works in all three), and
b) show how IDB works. The code is split up into short methods, so that it's
easy to see what happens in what method.
"Showing how it works" is the main intention of this project. IndexedDB is
all the buzz, but only a few people actually know how to use it.
The code in IDBWrapper.js is not optimized for anything, nor minified or anything.
It is meant to be read and easy to understand. So, please, go ahead and check out
the source!
There are two tutorials to get you up and running:
Part 1: Setup and CRUD operations
http://jensarps.de/2011/11/25/working-with-idbwrapper-part-1/
Part 2: Running Queries against the store
http://jensarps.de/2012/11/13/working-with-idbwrapper-part-2/
##November Rewrite
I rewrote IDBWrapper to cope with all the issues, and the new version is on
master since Nov, 13th 2012. The API didn't change much, I just removed some
of the methods. Method signatures remain unchanged.
However, if you have a previous version of IDBWrapper in use, there's an
issue: The new version won't be able to access the store created with the old
version, because database names changed. In that case, you need to manually
migrate the data: Include both versions of IDBWrapper (use a different name for
them), do a getAll() on the old store and write the data to the new store.
I am very sorry about any inconveniences, but there was no other way.
The 'old' version of IDBWrapper is still available in the `legacy` branch:
https://github.com/jensarps/IDBWrapper/tree/legacy
Also, "showing how it works" is no longer the main intention behind this. Now,
it's rather "just works".
Examples
========
There are some examples to run right in your browser over here: http://jensarps.github.com/IDBWrapper/example/
The source for these examples are in the `example` folder of this repository.
Usage
=====
Including the IDBStore.js file will add an IDBStore constructor to the global scope.
Alternatively, you can use an AMD loader such as RequireJS to load the file,
and you will receive the constructor in your load callback (the constructor
will then, of course, have whatever name you call it).
You can then create an IDB store:
```javascript
var myStore = new IDBStore();
```
You may pass two parameters to the constructor: the first is an object with optional parameters,
the second is a function reference to a function that is called when the store is ready to use.
The options object may contain the following properties (default values are shown):
```javascript
{
storeName: 'Store',
dbVersion: 1,
keyPath: 'id',
autoIncrement: true,
indexes: [],
onStoreReady: function(){}
}
```
'keyPath' is the name of the property to be used as key index. If 'autoIncrement' is set to true,
the database will automatically add a unique key to the keyPath index when storing objects missing
that property. 'indexes' contains objects defining indexes (see below for details on indexes).
You can also pass a callback function to the options object. If a callback is provided both as second
parameter and inside of the options object, the function passed as second parameter will be used.
Methods
=======
Here's an overview of available methods in IDBStore:
Data Manipulation
-----------------
Use the following methods to read and write data:
___
1) The put method.
```javascript
put(/*Object*/ dataObj, /*Function?*/onSuccess, /*Function?*/onError)
```
`dataObj` is the Object to store. `onSuccess` will be called when the insertion/update was successful,
and it will receive the keyPath value (the id, so to say) of the inserted object as first and only
argument. `onError` will be called if the insertion/update failed and it will receive the error event
object as first and only argument. If the store already contains an object with the given keyPath id,
it will be overwritten by `dataObj`.
___
2) The get method.
```javascript
get(/*keyPath value*/ key, /*Function?*/onSuccess, /*Function?*/onError)
```
`key` is the keyPath property value (the id) of the object to retrieve. `onSuccess` will be called if
the get operation was successful, and it will receive the stored object as first and only argument. If
no object was found with the given keyPath value, this argument will be null. `onError` will be called
if the get operation failed and it will receive the error event object as first and only argument.
___
3) The getAll method.
```javascript
getAll: function(/*Function?*/onSuccess, /*Function?*/onError)
```
`onSuccess` will be called if the getAll operation was successful, and it will receive an Array of
all objects currently stored in the store as first and only argument. `onError` will be called if
the getAll operation failed and it will receive the error event object as first and only argument.
___
4) The remove method.
```javascript
remove: function(/*keyPath value*/ key, /*Function?*/onSuccess, /*Function?*/onError)
```
`key` is the keyPath property value (the id) of the object to remove. `onSuccess` will be called if
the remove operation was successful, and it _should_ receive `false` as first and only argument if the
object to remove was not found, and `true` if it was found and removed.
NOTE: FF 8 will pass the key to the onSuccess handler, no matter if there is an corresponding object
or not. Chrome 15 will pass `null` if removal was successful, and call the error handler if the object
wasn't found. Chrome 17 will behave as described above.
`onError` will be called if the remove operation failed and it will receive the error event object as first
and only argument.
___
5) The clear method.
```javascript
clear: function(/*Function?*/onSuccess, /*Function?*/onError)
```
`onSuccess` will be called if the clear operation was successful. `onError` will be called if the clear
operation failed and it will receive the error event object as first and only argument.
Index Operations
----------------
To create indexes, you need to pass the index information to the IDBStore()
constructor, for example:
```javascript
{
storeName: 'customers',
dbVersion: 1,
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: function(){},
indexes: [
{ name: 'lastname', keyPath: 'lastname', unique: false, multiEntry: false }
]
}
```
An entry in the index Array is an object containing the following properties:
The `name` property is the identifier of the index. If you want to work with the created index later, this name is used to identify the index. This is the only property that is mandatory.
The `keyPath` property is the name of the property in your stored data that you want to index. If you omit that, IDBWrapper will assume that it is the same as the provided name, and will use this instead.
The `unique` property tells the store whether the indexed property in your data is unique. If you set this to true, it will add a uniqueness constraint to the store which will make it throw if you try to store data that violates that constraint. If you omit that, IDBWrapper will set this to false.
The `multiEntry` property is kinda weird. You can read up on it here: http://www.w3.org/TR/IndexedDB/#dfn-multientry. However, you can live perfectly fine with setting this to false (or just omitting it, this is set to false by default).
If you want to add an index to an existing store, you need to increase the
version number of your store, as adding an index changes the structure of
the database.
To modify an index, modify the object in the indexes Array in the constructor.
Again, you need to increase the version of your store.
In addition, there are still some convenience methods available:
___
1) The hasIndex method.
```javascript
hasIndex: function(/*String*/ indexName)
```
Return true if an index with the given name exists in the store, false if not.
___
2) The getIndexList method.
```javascript
getIndexList: function()
```
Returns a `DOMStringList` with all existing indices.
Running Queries
---------------
To run queries, IDBWrapper provides an `iterate()` method. To create keyRanges,
there is the `makeKeyRange()` method. In addition to these, IDBWrapper comes
with a `count()` method.
___
1) The iterate method.
```javascript
iterate: function(/*Function*/ onItem, /*Object*/ iterateOptions)
```
The `onItem` callback will be called once for every match. It will receive three arguments: the object that matched the query, a reference to the current cursor object (IDBWrapper uses IndexedDB's Cursor internally to iterate), and a reference to the current ongoing transaction.
There's one special situation: if you didn't pass an onEnd handler in the options objects (see below), the onItem handler will be called one extra time when the transaction is over. In this case, it will receive null as only argument. So, to check when the iteration is over and you won't get any more data objects, you can either pass an onEnd handler, or check for null in the onItem handler.
The `iterateOptions` object can contain one or more of the following properties:
The `index` property contains the name of the index to operate on. If you omit this, IDBWrapper will use the store's keyPath as index.
In the `keyRange` property you can pass a keyRange.
The `order` property can be set to 'ASC' or 'DESC', and determines the ordering direction of results. If you omit this, IDBWrapper will use 'ASC'.
The `filterDuplicates` property is an interesting one: If you set this to true (it defaults to false), and have several objects that have the same value in their key, the store will only fetch the first of those. It is not about objects being the same, it's about their key being the same. For example, in the customers database are a couple of guys having 'Smith' as last name. Setting filterDuplicates to true in the above example will make `iterate()` call the onItem callback only for the first of those.
The `writeAccess` property defaults to false. If you need write access to the store during the iteration, you need to set this to true.
In the `onEnd` property you can pass a callback that gets called after the iteration is over and the transaction is closed. It does not receive any arguments.
In the `onError` property you can pass a custom error handler. In case of an error, it will be called and receives the Error object as only argument.
___
2) The makeKeyRange method.
```javascript
iterate: function(/*Object*/ keyRangeOptions)
```
Returns an IDBKeyRange.
The `keyRangeOptions` object must have one or more of the following properties:
`lower`: The lower bound of the range
`excludeLower`: Boolean, whether to exclude the lower bound itself. Default: false
`upper`: The upper bound of the range
`excludeUpper`: Boolean, whether to exclude the upper bound itself. Default: false
___
3) The count method.
```javascript
iterate: function(/*Function*/ onSuccess, /*Object*/ countOptions)
```
The onSuccess receives the result of the count as only argument.
The `countOptions` object may have one or more of the following properties:
index: The name of an index to operate on.
keyRange: A keyRange to use
@@ -1,94 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var tpls = {
row: '<tr><td>{customerid}</td><td><input id="lastname_{customerid}" value="{lastname}"></td><td><input id="firstname_{customerid}" value="{firstname}"></td><td><button onclick="app.deleteItem({customerid});">delete</button><button onclick="app.updateItem({customerid});">update</button></td></tr>',
table: '<table><tr><th>ID</th><th>Last Name</th><th>First Name</th><th></th></tr>{content}</table>'
};
var customers;
var nodeCache = {};
function init(){
// create a store ("table") for the customers
customers = new IDBStore({
storeName: 'customer',
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: refreshTable
});
// create references for some nodes we have to work with
['submit', 'customerid', 'firstname', 'lastname', 'results-container'].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit button.
nodeCache.submit.addEventListener('click', enterData);
}
function refreshTable(){
customers.getAll(listItems);
}
function listItems(data){
var content = '';
data.forEach(function(item){
content += tpls.row.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key];
});
});
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
}
function enterData(){
// read data from inputs…
var data = {};
['customerid','firstname','lastname'].forEach(function(key){
var value = nodeCache[key].value.trim();
if(value.length){
if(key == 'customerid'){ // We want the id to be numeric:
value = parseInt(value, 10);
}
data[key] = value;
}
});
// …and store them away.
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function clearForm(){
['customerid','firstname','lastname'].forEach(function(id){
nodeCache[id].value = '';
});
}
function deleteItem(id){
customers.remove(id, refreshTable);
}
function updateItem(id){
var data = {
customerid: id,
firstname: document.getElementById('firstname_' + id).value.trim(),
lastname: document.getElementById('lastname_' + id).value.trim()
};
customers.put(data, refreshTable);
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
deleteItem: deleteItem,
updateItem: updateItem
};
// go!
init();
});
@@ -1,31 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper Basic CRUD Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper Basic CRUD Example</div>
<div class="hbox flex">
<div class="vbox flex" id="results-container">
QueryResults
</div>
<div class="vbox">
<div id="query"></div>
<div id="input">
<p>
Enter some data to save. As ID, enter a numeric value or leave blank.
</p>
<div><label>ID</label><input id="customerid"></div>
<div><label>First Name</label><input id="firstname"></div>
<div><label>Last Name</label><input id="lastname"></div>
<button id="submit">Enter data</button>
</div>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,86 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
@@ -1,22 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>IndexedDB Wrapper Examples</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="head">IDBWrapper Examples</div>
<div class="container">
<h1>IDBWrapper Examples</h1>
<p>
There are a couple of examples to try out / look at:
</p>
<ul>
<li><a href="quicktest/">Quicktest</a> - Just a quick test to see if IDB opens and fool around in the console.</li>
<li><a href="basic/">Basic CRUD</a> - A basic CRUD example using an IDB store as fixed table.</li>
<li><a href="objectstore/">ObjectStore</a> - An example to show the difference between a table and an object store.</li>
<li><a href="index/">Index</a> - An example to show how to work with indexes.</li>
</ul>
</div>
</body>
</html>
@@ -1,163 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var tpls = {
row: '<tr><td>{customerid}</td><td>{lastname}</td><td>{firstname}</td><td>{age}</td><td><button onclick="app.deleteItem({customerid});">delete</button></td></tr>',
table: '<table><tr><th>ID</th><th>Last Name</th><th>First Name</th><th>Age</th><th></th></tr>{content}</table>'
};
var customers;
var nodeCache = {};
function init(){
// create a store ("table") for the customers
customers = app.customers = new IDBStore({
dbVersion: 1,
storeName: 'customer-index',
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: refreshTable,
indexes: [
{ name: 'lastname', keyPath: 'lastname', unique: false, multiEntry: false }
]
});
// create references for some nodes we have to work with
[
'submit', 'submitQuery',
'upper', 'lower', 'excludeLower', 'excludeUpper',
'sortOrder', 'index', 'filterDuplicates',
'customerid', 'firstname', 'lastname', 'age',
'results-container'
].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit buttons.
nodeCache.submit.addEventListener('click', enterData);
nodeCache.submitQuery.addEventListener('click', runQuery);
}
function refreshTable(){
customers.getAll(listItems);
}
function listItems(data){
var content = '';
data.forEach(function(item){
content += tpls.row.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key];
});
});
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
}
function enterData(){
// read data from inputs…
var data = {};
['customerid','firstname','lastname', 'age'].forEach(function(key){
var value = nodeCache[key].value.trim();
if(value.length){
if(key == 'customerid'){
value = parseInt(value, 10);
}
data[key] = value;
}
});
// …and store them away.
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function clearForm(){
['customerid','firstname','lastname', 'age'].forEach(function(id){
nodeCache[id].value = '';
});
}
function deleteItem(id){
customers.remove(id, refreshTable);
}
function makeRandomEntry(){
var lastnames = ['Smith','Miller','Doe','Frankenstein','Furter'],
firstnames = ['Peter','John','Frank', 'James', 'Jill'];
var entry = {
lastname: lastnames[Math.floor(Math.random()*5)],
firstname: firstnames[Math.floor(Math.random()*4)],
age: Math.floor(Math.random() * (100 - 20)) + 20,
customerid: parseInt( ( "" + ( Date.now() * Math.random() ) ).substring(0, 6), 10)
};
return entry;
}
function addRandomCustomer(){
var data = makeRandomEntry();
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function runQuery(){
var upper = nodeCache.upper.value,
hasUpper = upper != '',
lower = nodeCache.lower.value,
hasLower = lower != '',
indexName = nodeCache.index.value,
sortOrder = nodeCache.sortOrder.value,
filterDuplicates = nodeCache.filterDuplicates.checked,
keyRange,
content = '';
if(hasUpper || hasLower){ // create a keyRange only if bounds are given
var options = {};
if(hasUpper){
options.upper = upper;
options.excludeUpper = nodeCache.excludeUpper.checked;
}
if(hasLower){
options.lower = lower;
options.excludeLower = nodeCache.excludeLower.checked;
}
keyRange = customers.makeKeyRange(options);
}
var onItem = function (item) {
content += tpls.row.replace(/\{([^\}]+)\}/g, function (_, key) {
return item[key];
});
};
var onEnd = function () {
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
};
customers.iterate(onItem, {
index: indexName,
keyRange: keyRange,
filterDuplicates: filterDuplicates,
order: sortOrder,
onEnd: onEnd
});
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
deleteItem: deleteItem,
addRandomCustomer: addRandomCustomer
};
// go!
init();
});
@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper Basic Index Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper Basic Index Example</div>
<div class="hbox flex">
<div class="vbox flex" id="results-container">
QueryResults
</div>
<div id="query">
<h1>Query</h1>
<div>
<label>Index to use</label>
<select id="index">
<option value="" selected>None (use store's keyPath)</option>
<option value="lastname">lastname</option>
</select>
</div>
<div><label>Lower Bound</label><input id="lower"></div>
<div><label>Exclude Lower</label><input type="checkbox" id="excludeLower"></div>
<div><label>Upper Bound</label><input id="upper"></div>
<div><label>Exclude Upper</label><input type="checkbox" id="excludeUpper"></div>
<div>
<label>Sort Order</label>
<select id="sortOrder">
<option value="ASC" selected>Ascending</option>
<option value="DESC">Descending</option>
</select>
</div>
<div><label>Filter Duplicates</label><input type="checkbox" id="filterDuplicates"></div>
<button id="submitQuery">Submit Query</button>
</div>
<div id="input">
<h1>Add data</h1>
<p>
Add a random customer:
<button onclick="app.addRandomCustomer()">Add random customer data
</button>
</p>
<p>
Or, enter customer data below:
</p>
<div><label>Customer ID</label><input id="customerid"></div>
<div><label>First Name</label><input id="firstname"></div>
<div><label>Last Name</label><input id="lastname"></div>
<div><label>Age</label><input id="age"></div>
<button id="submit">Enter data</button>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,89 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input,
#query {
padding: 10px;
width: 350px;
border-left: solid 1px black;
}
#input div,
#query div{
padding: 5px;
}
#input label,
#query label{
display: inline-block;
width: 120px;
}
File diff suppressed because it is too large Load Diff
@@ -1,93 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var objStore;
var nodeCache = {};
function init(){
// create a store ("table")
objStore = new IDBStore({
storeName: 'objectstore',
keyPath: 'id',
autoIncrement: true,
onStoreReady: refreshTable
});
// create references for some nodes we have to work with
['submit', 'results-container'].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit button.
nodeCache.submit.addEventListener('click', enterData);
}
function refreshTable(){
objStore.getAll(listItems);
}
function listItems(data){
var header, tpl,
props = ['id'],
content = '';
data.forEach(function(item){
for(var prop in item){
if(props.indexOf(prop) < 0){
props.push(prop);
}
}
});
header = '<tr><th>' + props.join('</th><th>') + '</th></tr>';
tpl = '<tr><td>{' + props.join('}</td><td>{') + '}</td></tr>';
data.forEach(function(item){
content += tpl.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key] || '';
});
});
nodeCache['results-container'].innerHTML = '<table>' + header + content + '</table>';
}
function enterData(){
// read data from inputs
var propName, value, hasData,
data = {},
count = 4;
while(--count){
propName = document.getElementById('prop_' + count).value.trim();
if(propName.length){
hasData = true;
value = document.getElementById('value_' + count).value.trim();
// Don't do this at home. This is just a very dirty hack to 'guess' what
// type of data you just entered. If you do stuff like this in production
// code, UNICORNS WILL DIE. You have been warned.
data[propName] = ['{', '['].indexOf(value.substring(0,1)) !== -1 ? eval('(' + value + ')') : parseInt(value, 10) || value;
}
}
if(!hasData){
return;
}
// and store them away.
objStore.put(data, refreshTable);
}
function clear(){
objStore.clear(refreshTable);
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
clear: clear
};
// go!
init();
});
@@ -1,57 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper ObjectStore Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper ObjectStore Example</div>
<div class="hbox flex">
<div class="vbox flex container" id="results-container">
QueryResults
</div>
<div class="vbox container" id="interaction-container">
<div id="query" class="vbox"></div>
<div id="input" class="vbox">
<p>
IDB is not a relational database; it's an object store. That means you
have
no such things as fixed, defined columns.<br/>
Just enter <em>any name</em> as key and <em>anything</em> as value.<br/>
<br/>
To enter non-primitive values, use literal notaion.
</p>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><input id="prop_1"/></td>
<td><textarea id="value_1"></textarea></td>
</tr>
<tr>
<td><input id="prop_2"/></td>
<td><textarea id="value_2"></textarea></td>
</tr>
<tr>
<td><input id="prop_3"/></td>
<td><textarea id="value_3"></textarea></td>
</tr>
</tbody>
</table>
<button id="submit">Enter data</button>
</div>
<div id="clear" class="">
<button onclick="app.clear()">Clear Store</button>
</div>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,93 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
.container {
overflow: auto;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
#clear {
padding: 10px;
}
@@ -1,105 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IndexedDB Wrapper Quick Test</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body class="vbox" onload="onload();">
<div id="head">IDBWrapper Quick Test</div>
<div class="container">
<p>Open the console and click 'Open DB'. You will then see a bunch of buttons
that allow data manipulation. Click them, and check the console for
results.</p>
<p id="openWrapper" style="display: none;">
<button onclick="createStore_1_1()">Open DB</button>
</p>
<p id="dataWrapper" style="display: none;">
<button onclick="testWrite()">put</button>
<br/>
<button onclick="testRead()">get</button>
<br/>
<button onclick="testGetAll()">getAll</button>
<br/>
<button onclick="testRemove()">remove</button>
<br/>
<button onclick="testClear()">clear</button>
</p>
</div>
<script type="text/javascript" src="../../IDBStore.js"></script>
<script type="text/javascript">
function onload () {
document.getElementById('openWrapper').style.display = '';
}
/* methods for walking thorugh the data ops: */
var lastnames = ['Smith', 'Miller', 'Doe', 'Frankenstein', 'Furter'],
firstnames = ['Peter', 'John', 'Frank', 'James'],
id = 1;
function testWrite (store) {
store = store || store_1_1;
var dataObj = {
lastname: lastnames[Math.floor(Math.random() * 5)],
firstname: firstnames[Math.floor(Math.random() * 4)],
age: Math.floor(Math.random() * (100 - 20)) + 20,
id: id++
};
store.put(dataObj, function (result) {
console.log('put() call success. Result:', result);
});
}
function testRead (store) {
store = store || store_1_1;
store.get(1, function (result) {
console.log('get() call success. Result:', result);
});
}
function testGetAll (store) {
store = store || store_1_1;
store.getAll(function (result) {
console.log('getAll() call success. Result:', result);
});
}
function testRemove (store) {
store = store || store_1_1;
store.remove(1, function (result) {
console.log('remove() call success. Result:', result);
});
}
function testClear (store) {
store = store || store_1_1;
store.clear(function (result) {
console.log('clear() call success. Result:', result);
});
}
/* create a store using the wrapper and kick off: */
function createStore_1_1 () {
window['store_1_1'] = new IDBStore({
dbVersion: '1',
storeName: 'testStore_1_1',
keyPath: 'id',
autoIncrement: true,
onStoreReady: function () {
console.log('Store ready, go ahead!');
console.log('The store is accessible at window.store_1_1');
document.getElementById('openWrapper').style.display = 'none';
document.getElementById('dataWrapper').style.display = '';
}
});
}
</script>
</body>
</html>
@@ -1,94 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
.container {
overflow: auto;
padding: 10px;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
#clear {
padding: 10px;
}
@@ -1,87 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
}
.hbox {
display: -webkit-box;
display: -moz-box;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
}
.container {
overflow: auto;
padding: 10px;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
#clear {
padding: 10px;
}
@@ -1,28 +0,0 @@
{
"name": "idb-wrapper",
"version": "0.1.0",
"description": "This is a wrapper for indexedDB.",
"keywords": [],
"author": "jensarps <mail@jensarps.de>",
"repository": "git://github.com/jensarps/IDBWrapper.git",
"main": "IDBStore",
"homepage": "https://github.com/jensarps/IDBWrapper",
"contributors": [
],
"bugs": {
"url": "https://github.com/jensarps/IDBWrapper/issues",
"email": "mail@jensarps.de"
},
"dependencies": {
},
"devDependencies": {
},
"licenses": [
{
"type": "MIT",
"url": "http://github.com/jensarps/IDBWrapper/raw/master/LICENSE"
}
],
"scripts": {
}
}
Binary file not shown.
-2
View File
@@ -1,2 +0,0 @@
.project
.idea
-464
View File
@@ -1,464 +0,0 @@
/*
* IDBWrapper - A cross-browser wrapper for IndexedDB
* Copyright (c) 2011 - 2012 Jens Arps
* http://jensarps.de/
*
* Licensed under the MIT (X11) license
*/
"use strict";
(function (name, definition, global) {
if (typeof define === 'function') {
define(definition);
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = definition();
} else {
global[name] = definition();
}
})('IDBStore', function () {
var IDBStore;
var defaults = {
storeName: 'Store',
dbVersion: 1,
keyPath: 'id',
autoIncrement: true,
onStoreReady: function () {
},
indexes: []
};
IDBStore = function (kwArgs, onStoreReady) {
function fixupConstants (object, constants) {
for (var prop in constants) {
object[prop] = constants[prop];
}
}
for(var key in defaults){
this[key] = typeof kwArgs[key] != 'undefined' ? kwArgs[key] : defaults[key];
}
this.dbName = 'IDBWrapper-' + this.storeName;
this.dbVersion = parseInt(this.dbVersion, 10);
onStoreReady && (this.onStoreReady = onStoreReady);
this.idb = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
this.keyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange;
this.consts = window.IDBTransaction || window.webkitIDBTransaction;
fixupConstants(this.consts, {
'READ_ONLY': 'readonly',
'READ_WRITE': 'readwrite',
'VERSION_CHANGE': 'versionchange'
});
this.cursor = window.IDBCursor || window.webkitIDBCursor;
fixupConstants(this.cursor, {
'NEXT': 'next',
'NEXT_NO_DUPLICATE': 'nextunique',
'PREV': 'prev',
'PREV_NO_DUPLICATE': 'prevunique'
});
this.openDB();
};
IDBStore.prototype = {
db: null,
dbName: null,
dbVersion: null,
store: null,
storeName: null,
keyPath: null,
autoIncrement: null,
indexes: null,
features: null,
onStoreReady: null,
openDB: function () {
this.newVersionAPI = typeof this.idb.setVersion == 'undefined';
if(!this.newVersionAPI){
throw new Error('The IndexedDB implementation in this browser is outdated. Please upgrade your browser.');
}
var features = this.features = {};
features.hasAutoIncrement = !window.mozIndexedDB; // TODO: Still, really?
var openRequest = this.idb.open(this.dbName, this.dbVersion);
openRequest.onerror = function (error) {
var gotVersionErr = false;
if ('error' in error.target) {
gotVersionErr = error.target.error.name == "VersionError";
} else if ('errorCode' in error.target) {
gotVersionErr = error.target.errorCode == 12; // TODO: Use const
}
if (gotVersionErr) {
console.error('Could not open database, version error:', error);
} else {
console.error('Could not open database, error:', error);
}
}.bind(this);
openRequest.onsuccess = function (event) {
if(this.db){
this.onStoreReady();
return;
}
this.db = event.target.result;
if(this.db.objectStoreNames.contains(this.storeName)){
if(!this.store){
var emptyTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
this.store = emptyTransaction.objectStore(this.storeName);
}
// check indexes
this.indexes.forEach(function(indexData){
var indexName = indexData.name;
// normalize and provide existing keys
indexData.keyPath = indexData.keyPath || indexName;
indexData.unique = !!indexData.unique;
indexData.multiEntry = !!indexData.multiEntry;
if(!indexName){
throw new Error('Cannot create index: No index name given.');
}
if(this.hasIndex(indexName)){
// check if it complies
var actualIndex = this.store.index(indexName);
var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
// IE10 returns undefined for no multiEntry
if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
return true;
}
return indexData[key] == actualIndex[key];
});
if(!complies){
throw new Error('Cannot modify index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
} else {
throw new Error('Cannot create new index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
}, this);
this.onStoreReady();
} else {
// We should never get here.
throw new Error('Cannot create a new store for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
}.bind(this);
openRequest.onupgradeneeded = function(/* IDBVersionChangeEvent */ event){
this.db = event.target.result;
if(this.db.objectStoreNames.contains(this.storeName)){
this.store = event.target.transaction.objectStore(this.storeName);
} else {
this.store = this.db.createObjectStore(this.storeName, { keyPath: this.keyPath, autoIncrement: this.autoIncrement});
}
this.indexes.forEach(function(indexData){
var indexName = indexData.name;
// normalize and provide existing keys
indexData.keyPath = indexData.keyPath || indexName;
indexData.unique = !!indexData.unique;
indexData.multiEntry = !!indexData.multiEntry;
if(!indexName){
throw new Error('Cannot create index: No index name given.');
}
if(this.hasIndex(indexName)){
// check if it complies
var actualIndex = this.store.index(indexName);
var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
// IE10 returns undefined for no multiEntry
if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
return true;
}
return indexData[key] == actualIndex[key];
});
if(!complies){
// index differs, need to delete and re-create
this.store.deleteIndex(indexName);
this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
}
} else {
this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
}
}, this);
}.bind(this);
},
deleteDatabase: function () {
if (this.idb.deleteDatabase) {
this.idb.deleteDatabase(this.dbName);
}
},
/*********************
* data manipulation *
*********************/
put: function (dataObj, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not write data.', error);
});
onSuccess || (onSuccess = noop);
if (typeof dataObj[this.keyPath] == 'undefined' && !this.features.hasAutoIncrement) {
dataObj[this.keyPath] = this._getUID();
}
var putTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var putRequest = putTransaction.objectStore(this.storeName).put(dataObj);
putRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
putRequest.onerror = onError;
},
get: function (key, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not read data.', error);
});
onSuccess || (onSuccess = noop);
var getTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var getRequest = getTransaction.objectStore(this.storeName).get(key);
getRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
getRequest.onerror = onError;
},
remove: function (key, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not remove data.', error);
});
onSuccess || (onSuccess = noop);
var removeTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var deleteRequest = removeTransaction.objectStore(this.storeName).delete(key);
deleteRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
deleteRequest.onerror = onError;
},
getAll: function (onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not read data.', error);
});
onSuccess || (onSuccess = noop);
var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var store = getAllTransaction.objectStore(this.storeName);
if (store.getAll) {
var getAllRequest = store.getAll();
getAllRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
getAllRequest.onerror = onError;
} else {
this._getAllCursor(getAllTransaction, onSuccess, onError);
}
},
_getAllCursor: function (tr, onSuccess, onError) {
var all = [];
var store = tr.objectStore(this.storeName);
var cursorRequest = store.openCursor();
cursorRequest.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
all.push(cursor.value);
cursor['continue']();
}
else {
onSuccess(all);
}
};
cursorRequest.onError = onError;
},
clear: function (onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not clear store.', error);
});
onSuccess || (onSuccess = noop);
var clearTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var clearRequest = clearTransaction.objectStore(this.storeName).clear();
clearRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
clearRequest.onerror = onError;
},
_getUID: function () {
// FF bails at times on non-numeric ids. So we take an even
// worse approach now, using current time as id. Sigh.
return +new Date();
},
/************
* indexing *
************/
getIndexList: function () {
return this.store.indexNames;
},
hasIndex: function (indexName) {
return this.store.indexNames.contains(indexName);
},
/**********
* cursor *
**********/
iterate: function (onItem, options) {
options = mixin({
index: null,
order: 'ASC',
filterDuplicates: false,
keyRange: null,
writeAccess: false,
onEnd: null,
onError: function (error) {
console.error('Could not open cursor.', error);
}
}, options || {});
var directionType = options.order.toLowerCase() == 'desc' ? 'PREV' : 'NEXT';
if (options.filterDuplicates) {
directionType += '_NO_DUPLICATE';
}
var cursorTransaction = this.db.transaction([this.storeName], this.consts[options.writeAccess ? 'READ_WRITE' : 'READ_ONLY']);
var cursorTarget = cursorTransaction.objectStore(this.storeName);
if (options.index) {
cursorTarget = cursorTarget.index(options.index);
}
var cursorRequest = cursorTarget.openCursor(options.keyRange, this.cursor[directionType]);
cursorRequest.onerror = options.onError;
cursorRequest.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
onItem(cursor.value, cursor, cursorTransaction);
cursor['continue']();
} else {
if(options.onEnd){
options.onEnd()
} else {
onItem(null);
}
}
};
},
count: function (onSuccess, options) {
options = mixin({
index: null,
keyRange: null
}, options || {});
var onError = options.onError || function (error) {
console.error('Could not open cursor.', error);
};
var cursorTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var cursorTarget = cursorTransaction.objectStore(this.storeName);
if (options.index) {
cursorTarget = cursorTarget.index(options.index);
}
var countRequest = cursorTarget.count(options.keyRange);
countRequest.onsuccess = function (evt) {
onSuccess(evt.target.result);
};
countRequest.onError = function (error) {
onError(error);
};
},
/**************/
/* key ranges */
/**************/
makeKeyRange: function(options){
var keyRange,
hasLower = typeof options.lower != 'undefined',
hasUpper = typeof options.upper != 'undefined';
switch(true){
case hasLower && hasUpper:
keyRange = this.keyRange.bound(options.lower, options.upper, options.excludeLower, options.excludeUpper);
break;
case hasLower:
keyRange = this.keyRange.lowerBound(options.lower, options.excludeLower);
break;
case hasUpper:
keyRange = this.keyRange.upperBound(options.upper, options.excludeUpper);
break;
default:
throw new Error('Cannot create KeyRange. Provide one or both of "lower" or "upper" value.');
break;
}
return keyRange;
}
};
/** helpers **/
var noop = function () {
};
var empty = {};
var mixin = function (target, source) {
var name, s;
for (name in source) {
s = source[name];
if (s !== empty[name] && s !== target[name]) {
target[name] = s;
}
}
return target;
};
return IDBStore;
}, this);
-19
View File
@@ -1,19 +0,0 @@
Copyright (c) 2011 - 2012 Jens Arps
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-313
View File
@@ -1,313 +0,0 @@
About
=====
This is a wrapper for indexedDB. It is meant to
a) ease the use of indexedDB and abstract away the differences between the
existing impls in Chrome, Firefox and IE10 (yes, it works in all three), and
b) show how IDB works. The code is split up into short methods, so that it's
easy to see what happens in what method.
"Showing how it works" is the main intention of this project. IndexedDB is
all the buzz, but only a few people actually know how to use it.
The code in IDBWrapper.js is not optimized for anything, nor minified or anything.
It is meant to be read and easy to understand. So, please, go ahead and check out
the source!
There are two tutorials to get you up and running:
Part 1: Setup and CRUD operations
http://jensarps.de/2011/11/25/working-with-idbwrapper-part-1/
Part 2: Running Queries against the store
http://jensarps.de/2012/11/13/working-with-idbwrapper-part-2/
##November Rewrite
I rewrote IDBWrapper to cope with all the issues, and the new version is on
master since Nov, 13th 2012. The API didn't change much, I just removed some
of the methods. Method signatures remain unchanged.
However, if you have a previous version of IDBWrapper in use, there's an
issue: The new version won't be able to access the store created with the old
version, because database names changed. In that case, you need to manually
migrate the data: Include both versions of IDBWrapper (use a different name for
them), do a getAll() on the old store and write the data to the new store.
I am very sorry about any inconveniences, but there was no other way.
The 'old' version of IDBWrapper is still available in the `legacy` branch:
https://github.com/jensarps/IDBWrapper/tree/legacy
Also, "showing how it works" is no longer the main intention behind this. Now,
it's rather "just works".
Examples
========
There are some examples to run right in your browser over here: http://jensarps.github.com/IDBWrapper/example/
The source for these examples are in the `example` folder of this repository.
Usage
=====
Including the IDBStore.js file will add an IDBStore constructor to the global scope.
Alternatively, you can use an AMD loader such as RequireJS to load the file,
and you will receive the constructor in your load callback (the constructor
will then, of course, have whatever name you call it).
You can then create an IDB store:
```javascript
var myStore = new IDBStore();
```
You may pass two parameters to the constructor: the first is an object with optional parameters,
the second is a function reference to a function that is called when the store is ready to use.
The options object may contain the following properties (default values are shown):
```javascript
{
storeName: 'Store',
dbVersion: 1,
keyPath: 'id',
autoIncrement: true,
indexes: [],
onStoreReady: function(){}
}
```
'keyPath' is the name of the property to be used as key index. If 'autoIncrement' is set to true,
the database will automatically add a unique key to the keyPath index when storing objects missing
that property. 'indexes' contains objects defining indexes (see below for details on indexes).
You can also pass a callback function to the options object. If a callback is provided both as second
parameter and inside of the options object, the function passed as second parameter will be used.
Methods
=======
Here's an overview of available methods in IDBStore:
Data Manipulation
-----------------
Use the following methods to read and write data:
___
1) The put method.
```javascript
put(/*Object*/ dataObj, /*Function?*/onSuccess, /*Function?*/onError)
```
`dataObj` is the Object to store. `onSuccess` will be called when the insertion/update was successful,
and it will receive the keyPath value (the id, so to say) of the inserted object as first and only
argument. `onError` will be called if the insertion/update failed and it will receive the error event
object as first and only argument. If the store already contains an object with the given keyPath id,
it will be overwritten by `dataObj`.
___
2) The get method.
```javascript
get(/*keyPath value*/ key, /*Function?*/onSuccess, /*Function?*/onError)
```
`key` is the keyPath property value (the id) of the object to retrieve. `onSuccess` will be called if
the get operation was successful, and it will receive the stored object as first and only argument. If
no object was found with the given keyPath value, this argument will be null. `onError` will be called
if the get operation failed and it will receive the error event object as first and only argument.
___
3) The getAll method.
```javascript
getAll: function(/*Function?*/onSuccess, /*Function?*/onError)
```
`onSuccess` will be called if the getAll operation was successful, and it will receive an Array of
all objects currently stored in the store as first and only argument. `onError` will be called if
the getAll operation failed and it will receive the error event object as first and only argument.
___
4) The remove method.
```javascript
remove: function(/*keyPath value*/ key, /*Function?*/onSuccess, /*Function?*/onError)
```
`key` is the keyPath property value (the id) of the object to remove. `onSuccess` will be called if
the remove operation was successful, and it _should_ receive `false` as first and only argument if the
object to remove was not found, and `true` if it was found and removed.
NOTE: FF 8 will pass the key to the onSuccess handler, no matter if there is an corresponding object
or not. Chrome 15 will pass `null` if removal was successful, and call the error handler if the object
wasn't found. Chrome 17 will behave as described above.
`onError` will be called if the remove operation failed and it will receive the error event object as first
and only argument.
___
5) The clear method.
```javascript
clear: function(/*Function?*/onSuccess, /*Function?*/onError)
```
`onSuccess` will be called if the clear operation was successful. `onError` will be called if the clear
operation failed and it will receive the error event object as first and only argument.
Index Operations
----------------
To create indexes, you need to pass the index information to the IDBStore()
constructor, for example:
```javascript
{
storeName: 'customers',
dbVersion: 1,
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: function(){},
indexes: [
{ name: 'lastname', keyPath: 'lastname', unique: false, multiEntry: false }
]
}
```
An entry in the index Array is an object containing the following properties:
The `name` property is the identifier of the index. If you want to work with the created index later, this name is used to identify the index. This is the only property that is mandatory.
The `keyPath` property is the name of the property in your stored data that you want to index. If you omit that, IDBWrapper will assume that it is the same as the provided name, and will use this instead.
The `unique` property tells the store whether the indexed property in your data is unique. If you set this to true, it will add a uniqueness constraint to the store which will make it throw if you try to store data that violates that constraint. If you omit that, IDBWrapper will set this to false.
The `multiEntry` property is kinda weird. You can read up on it here: http://www.w3.org/TR/IndexedDB/#dfn-multientry. However, you can live perfectly fine with setting this to false (or just omitting it, this is set to false by default).
If you want to add an index to an existing store, you need to increase the
version number of your store, as adding an index changes the structure of
the database.
To modify an index, modify the object in the indexes Array in the constructor.
Again, you need to increase the version of your store.
In addition, there are still some convenience methods available:
___
1) The hasIndex method.
```javascript
hasIndex: function(/*String*/ indexName)
```
Return true if an index with the given name exists in the store, false if not.
___
2) The getIndexList method.
```javascript
getIndexList: function()
```
Returns a `DOMStringList` with all existing indices.
Running Queries
---------------
To run queries, IDBWrapper provides an `iterate()` method. To create keyRanges,
there is the `makeKeyRange()` method. In addition to these, IDBWrapper comes
with a `count()` method.
___
1) The iterate method.
```javascript
iterate: function(/*Function*/ onItem, /*Object*/ iterateOptions)
```
The `onItem` callback will be called once for every match. It will receive three arguments: the object that matched the query, a reference to the current cursor object (IDBWrapper uses IndexedDB's Cursor internally to iterate), and a reference to the current ongoing transaction.
There's one special situation: if you didn't pass an onEnd handler in the options objects (see below), the onItem handler will be called one extra time when the transaction is over. In this case, it will receive null as only argument. So, to check when the iteration is over and you won't get any more data objects, you can either pass an onEnd handler, or check for null in the onItem handler.
The `iterateOptions` object can contain one or more of the following properties:
The `index` property contains the name of the index to operate on. If you omit this, IDBWrapper will use the store's keyPath as index.
In the `keyRange` property you can pass a keyRange.
The `order` property can be set to 'ASC' or 'DESC', and determines the ordering direction of results. If you omit this, IDBWrapper will use 'ASC'.
The `filterDuplicates` property is an interesting one: If you set this to true (it defaults to false), and have several objects that have the same value in their key, the store will only fetch the first of those. It is not about objects being the same, it's about their key being the same. For example, in the customers database are a couple of guys having 'Smith' as last name. Setting filterDuplicates to true in the above example will make `iterate()` call the onItem callback only for the first of those.
The `writeAccess` property defaults to false. If you need write access to the store during the iteration, you need to set this to true.
In the `onEnd` property you can pass a callback that gets called after the iteration is over and the transaction is closed. It does not receive any arguments.
In the `onError` property you can pass a custom error handler. In case of an error, it will be called and receives the Error object as only argument.
___
2) The makeKeyRange method.
```javascript
iterate: function(/*Object*/ keyRangeOptions)
```
Returns an IDBKeyRange.
The `keyRangeOptions` object must have one or more of the following properties:
`lower`: The lower bound of the range
`excludeLower`: Boolean, whether to exclude the lower bound itself. Default: false
`upper`: The upper bound of the range
`excludeUpper`: Boolean, whether to exclude the upper bound itself. Default: false
___
3) The count method.
```javascript
iterate: function(/*Function*/ onSuccess, /*Object*/ countOptions)
```
The onSuccess receives the result of the count as only argument.
The `countOptions` object may have one or more of the following properties:
index: The name of an index to operate on.
keyRange: A keyRange to use
@@ -1,94 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var tpls = {
row: '<tr><td>{customerid}</td><td><input id="lastname_{customerid}" value="{lastname}"></td><td><input id="firstname_{customerid}" value="{firstname}"></td><td><button onclick="app.deleteItem({customerid});">delete</button><button onclick="app.updateItem({customerid});">update</button></td></tr>',
table: '<table><tr><th>ID</th><th>Last Name</th><th>First Name</th><th></th></tr>{content}</table>'
};
var customers;
var nodeCache = {};
function init(){
// create a store ("table") for the customers
customers = new IDBStore({
storeName: 'customer',
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: refreshTable
});
// create references for some nodes we have to work with
['submit', 'customerid', 'firstname', 'lastname', 'results-container'].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit button.
nodeCache.submit.addEventListener('click', enterData);
}
function refreshTable(){
customers.getAll(listItems);
}
function listItems(data){
var content = '';
data.forEach(function(item){
content += tpls.row.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key];
});
});
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
}
function enterData(){
// read data from inputs…
var data = {};
['customerid','firstname','lastname'].forEach(function(key){
var value = nodeCache[key].value.trim();
if(value.length){
if(key == 'customerid'){ // We want the id to be numeric:
value = parseInt(value, 10);
}
data[key] = value;
}
});
// …and store them away.
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function clearForm(){
['customerid','firstname','lastname'].forEach(function(id){
nodeCache[id].value = '';
});
}
function deleteItem(id){
customers.remove(id, refreshTable);
}
function updateItem(id){
var data = {
customerid: id,
firstname: document.getElementById('firstname_' + id).value.trim(),
lastname: document.getElementById('lastname_' + id).value.trim()
};
customers.put(data, refreshTable);
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
deleteItem: deleteItem,
updateItem: updateItem
};
// go!
init();
});
@@ -1,31 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper Basic CRUD Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper Basic CRUD Example</div>
<div class="hbox flex">
<div class="vbox flex" id="results-container">
QueryResults
</div>
<div class="vbox">
<div id="query"></div>
<div id="input">
<p>
Enter some data to save. As ID, enter a numeric value or leave blank.
</p>
<div><label>ID</label><input id="customerid"></div>
<div><label>First Name</label><input id="firstname"></div>
<div><label>Last Name</label><input id="lastname"></div>
<button id="submit">Enter data</button>
</div>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,86 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
@@ -1,22 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>IndexedDB Wrapper Examples</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="head">IDBWrapper Examples</div>
<div class="container">
<h1>IDBWrapper Examples</h1>
<p>
There are a couple of examples to try out / look at:
</p>
<ul>
<li><a href="quicktest/">Quicktest</a> - Just a quick test to see if IDB opens and fool around in the console.</li>
<li><a href="basic/">Basic CRUD</a> - A basic CRUD example using an IDB store as fixed table.</li>
<li><a href="objectstore/">ObjectStore</a> - An example to show the difference between a table and an object store.</li>
<li><a href="index/">Index</a> - An example to show how to work with indexes.</li>
</ul>
</div>
</body>
</html>
@@ -1,163 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var tpls = {
row: '<tr><td>{customerid}</td><td>{lastname}</td><td>{firstname}</td><td>{age}</td><td><button onclick="app.deleteItem({customerid});">delete</button></td></tr>',
table: '<table><tr><th>ID</th><th>Last Name</th><th>First Name</th><th>Age</th><th></th></tr>{content}</table>'
};
var customers;
var nodeCache = {};
function init(){
// create a store ("table") for the customers
customers = app.customers = new IDBStore({
dbVersion: 1,
storeName: 'customer-index',
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: refreshTable,
indexes: [
{ name: 'lastname', keyPath: 'lastname', unique: false, multiEntry: false }
]
});
// create references for some nodes we have to work with
[
'submit', 'submitQuery',
'upper', 'lower', 'excludeLower', 'excludeUpper',
'sortOrder', 'index', 'filterDuplicates',
'customerid', 'firstname', 'lastname', 'age',
'results-container'
].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit buttons.
nodeCache.submit.addEventListener('click', enterData);
nodeCache.submitQuery.addEventListener('click', runQuery);
}
function refreshTable(){
customers.getAll(listItems);
}
function listItems(data){
var content = '';
data.forEach(function(item){
content += tpls.row.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key];
});
});
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
}
function enterData(){
// read data from inputs…
var data = {};
['customerid','firstname','lastname', 'age'].forEach(function(key){
var value = nodeCache[key].value.trim();
if(value.length){
if(key == 'customerid'){
value = parseInt(value, 10);
}
data[key] = value;
}
});
// …and store them away.
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function clearForm(){
['customerid','firstname','lastname', 'age'].forEach(function(id){
nodeCache[id].value = '';
});
}
function deleteItem(id){
customers.remove(id, refreshTable);
}
function makeRandomEntry(){
var lastnames = ['Smith','Miller','Doe','Frankenstein','Furter'],
firstnames = ['Peter','John','Frank', 'James', 'Jill'];
var entry = {
lastname: lastnames[Math.floor(Math.random()*5)],
firstname: firstnames[Math.floor(Math.random()*4)],
age: Math.floor(Math.random() * (100 - 20)) + 20,
customerid: parseInt( ( "" + ( Date.now() * Math.random() ) ).substring(0, 6), 10)
};
return entry;
}
function addRandomCustomer(){
var data = makeRandomEntry();
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function runQuery(){
var upper = nodeCache.upper.value,
hasUpper = upper != '',
lower = nodeCache.lower.value,
hasLower = lower != '',
indexName = nodeCache.index.value,
sortOrder = nodeCache.sortOrder.value,
filterDuplicates = nodeCache.filterDuplicates.checked,
keyRange,
content = '';
if(hasUpper || hasLower){ // create a keyRange only if bounds are given
var options = {};
if(hasUpper){
options.upper = upper;
options.excludeUpper = nodeCache.excludeUpper.checked;
}
if(hasLower){
options.lower = lower;
options.excludeLower = nodeCache.excludeLower.checked;
}
keyRange = customers.makeKeyRange(options);
}
var onItem = function (item) {
content += tpls.row.replace(/\{([^\}]+)\}/g, function (_, key) {
return item[key];
});
};
var onEnd = function () {
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
};
customers.iterate(onItem, {
index: indexName,
keyRange: keyRange,
filterDuplicates: filterDuplicates,
order: sortOrder,
onEnd: onEnd
});
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
deleteItem: deleteItem,
addRandomCustomer: addRandomCustomer
};
// go!
init();
});
@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper Basic Index Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper Basic Index Example</div>
<div class="hbox flex">
<div class="vbox flex" id="results-container">
QueryResults
</div>
<div id="query">
<h1>Query</h1>
<div>
<label>Index to use</label>
<select id="index">
<option value="" selected>None (use store's keyPath)</option>
<option value="lastname">lastname</option>
</select>
</div>
<div><label>Lower Bound</label><input id="lower"></div>
<div><label>Exclude Lower</label><input type="checkbox" id="excludeLower"></div>
<div><label>Upper Bound</label><input id="upper"></div>
<div><label>Exclude Upper</label><input type="checkbox" id="excludeUpper"></div>
<div>
<label>Sort Order</label>
<select id="sortOrder">
<option value="ASC" selected>Ascending</option>
<option value="DESC">Descending</option>
</select>
</div>
<div><label>Filter Duplicates</label><input type="checkbox" id="filterDuplicates"></div>
<button id="submitQuery">Submit Query</button>
</div>
<div id="input">
<h1>Add data</h1>
<p>
Add a random customer:
<button onclick="app.addRandomCustomer()">Add random customer data
</button>
</p>
<p>
Or, enter customer data below:
</p>
<div><label>Customer ID</label><input id="customerid"></div>
<div><label>First Name</label><input id="firstname"></div>
<div><label>Last Name</label><input id="lastname"></div>
<div><label>Age</label><input id="age"></div>
<button id="submit">Enter data</button>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,89 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input,
#query {
padding: 10px;
width: 350px;
border-left: solid 1px black;
}
#input div,
#query div{
padding: 5px;
}
#input label,
#query label{
display: inline-block;
width: 120px;
}
File diff suppressed because it is too large Load Diff
@@ -1,93 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var objStore;
var nodeCache = {};
function init(){
// create a store ("table")
objStore = new IDBStore({
storeName: 'objectstore',
keyPath: 'id',
autoIncrement: true,
onStoreReady: refreshTable
});
// create references for some nodes we have to work with
['submit', 'results-container'].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit button.
nodeCache.submit.addEventListener('click', enterData);
}
function refreshTable(){
objStore.getAll(listItems);
}
function listItems(data){
var header, tpl,
props = ['id'],
content = '';
data.forEach(function(item){
for(var prop in item){
if(props.indexOf(prop) < 0){
props.push(prop);
}
}
});
header = '<tr><th>' + props.join('</th><th>') + '</th></tr>';
tpl = '<tr><td>{' + props.join('}</td><td>{') + '}</td></tr>';
data.forEach(function(item){
content += tpl.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key] || '';
});
});
nodeCache['results-container'].innerHTML = '<table>' + header + content + '</table>';
}
function enterData(){
// read data from inputs
var propName, value, hasData,
data = {},
count = 4;
while(--count){
propName = document.getElementById('prop_' + count).value.trim();
if(propName.length){
hasData = true;
value = document.getElementById('value_' + count).value.trim();
// Don't do this at home. This is just a very dirty hack to 'guess' what
// type of data you just entered. If you do stuff like this in production
// code, UNICORNS WILL DIE. You have been warned.
data[propName] = ['{', '['].indexOf(value.substring(0,1)) !== -1 ? eval('(' + value + ')') : parseInt(value, 10) || value;
}
}
if(!hasData){
return;
}
// and store them away.
objStore.put(data, refreshTable);
}
function clear(){
objStore.clear(refreshTable);
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
clear: clear
};
// go!
init();
});
@@ -1,57 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper ObjectStore Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper ObjectStore Example</div>
<div class="hbox flex">
<div class="vbox flex container" id="results-container">
QueryResults
</div>
<div class="vbox container" id="interaction-container">
<div id="query" class="vbox"></div>
<div id="input" class="vbox">
<p>
IDB is not a relational database; it's an object store. That means you
have
no such things as fixed, defined columns.<br/>
Just enter <em>any name</em> as key and <em>anything</em> as value.<br/>
<br/>
To enter non-primitive values, use literal notaion.
</p>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><input id="prop_1"/></td>
<td><textarea id="value_1"></textarea></td>
</tr>
<tr>
<td><input id="prop_2"/></td>
<td><textarea id="value_2"></textarea></td>
</tr>
<tr>
<td><input id="prop_3"/></td>
<td><textarea id="value_3"></textarea></td>
</tr>
</tbody>
</table>
<button id="submit">Enter data</button>
</div>
<div id="clear" class="">
<button onclick="app.clear()">Clear Store</button>
</div>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,93 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
.container {
overflow: auto;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
#clear {
padding: 10px;
}
@@ -1,105 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IndexedDB Wrapper Quick Test</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body class="vbox" onload="onload();">
<div id="head">IDBWrapper Quick Test</div>
<div class="container">
<p>Open the console and click 'Open DB'. You will then see a bunch of buttons
that allow data manipulation. Click them, and check the console for
results.</p>
<p id="openWrapper" style="display: none;">
<button onclick="createStore_1_1()">Open DB</button>
</p>
<p id="dataWrapper" style="display: none;">
<button onclick="testWrite()">put</button>
<br/>
<button onclick="testRead()">get</button>
<br/>
<button onclick="testGetAll()">getAll</button>
<br/>
<button onclick="testRemove()">remove</button>
<br/>
<button onclick="testClear()">clear</button>
</p>
</div>
<script type="text/javascript" src="../../IDBStore.js"></script>
<script type="text/javascript">
function onload () {
document.getElementById('openWrapper').style.display = '';
}
/* methods for walking thorugh the data ops: */
var lastnames = ['Smith', 'Miller', 'Doe', 'Frankenstein', 'Furter'],
firstnames = ['Peter', 'John', 'Frank', 'James'],
id = 1;
function testWrite (store) {
store = store || store_1_1;
var dataObj = {
lastname: lastnames[Math.floor(Math.random() * 5)],
firstname: firstnames[Math.floor(Math.random() * 4)],
age: Math.floor(Math.random() * (100 - 20)) + 20,
id: id++
};
store.put(dataObj, function (result) {
console.log('put() call success. Result:', result);
});
}
function testRead (store) {
store = store || store_1_1;
store.get(1, function (result) {
console.log('get() call success. Result:', result);
});
}
function testGetAll (store) {
store = store || store_1_1;
store.getAll(function (result) {
console.log('getAll() call success. Result:', result);
});
}
function testRemove (store) {
store = store || store_1_1;
store.remove(1, function (result) {
console.log('remove() call success. Result:', result);
});
}
function testClear (store) {
store = store || store_1_1;
store.clear(function (result) {
console.log('clear() call success. Result:', result);
});
}
/* create a store using the wrapper and kick off: */
function createStore_1_1 () {
window['store_1_1'] = new IDBStore({
dbVersion: '1',
storeName: 'testStore_1_1',
keyPath: 'id',
autoIncrement: true,
onStoreReady: function () {
console.log('Store ready, go ahead!');
console.log('The store is accessible at window.store_1_1');
document.getElementById('openWrapper').style.display = 'none';
document.getElementById('dataWrapper').style.display = '';
}
});
}
</script>
</body>
</html>
@@ -1,94 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
.container {
overflow: auto;
padding: 10px;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
#clear {
padding: 10px;
}
@@ -1,87 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
}
.hbox {
display: -webkit-box;
display: -moz-box;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
}
.container {
overflow: auto;
padding: 10px;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
#clear {
padding: 10px;
}
@@ -1,24 +0,0 @@
{
"name": "idb-wrapper",
"version": "0.1.1",
"description": "This is a wrapper for indexedDB.",
"keywords": [],
"author": "jensarps <mail@jensarps.de>",
"repository": "git://github.com/jensarps/IDBWrapper.git",
"main": "IDBStore",
"homepage": "https://github.com/jensarps/IDBWrapper",
"contributors": [],
"bugs": {
"url": "https://github.com/jensarps/IDBWrapper/issues",
"email": "mail@jensarps.de"
},
"dependencies": {},
"devDependencies": {},
"licenses": [
{
"type": "MIT",
"url": "http://github.com/jensarps/IDBWrapper/raw/master/LICENSE"
}
],
"scripts": {}
}
Binary file not shown.
-2
View File
@@ -1,2 +0,0 @@
.project
.idea
-463
View File
@@ -1,463 +0,0 @@
/*
* IDBWrapper - A cross-browser wrapper for IndexedDB
* Copyright (c) 2011 - 2012 Jens Arps
* http://jensarps.de/
*
* Licensed under the MIT (X11) license
*/
"use strict";
(function (name, definition, global) {
if (typeof define === 'function') {
define(definition);
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = definition();
} else {
global[name] = definition();
}
})('IDBStore', function () {
var IDBStore;
var defaults = {
storeName: 'Store',
dbVersion: 1,
keyPath: 'id',
autoIncrement: true,
onStoreReady: function () {
},
indexes: []
};
IDBStore = function (kwArgs, onStoreReady) {
function fixupConstants (object, constants) {
for (var prop in constants) {
object[prop] = constants[prop];
}
}
for(var key in defaults){
this[key] = typeof kwArgs[key] != 'undefined' ? kwArgs[key] : defaults[key];
}
this.dbName = 'IDBWrapper-' + this.storeName;
this.dbVersion = parseInt(this.dbVersion, 10);
onStoreReady && (this.onStoreReady = onStoreReady);
this.idb = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
this.keyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange;
this.consts = {
'READ_ONLY': 'readonly',
'READ_WRITE': 'readwrite',
'VERSION_CHANGE': 'versionchange'
}
this.cursor = window.IDBCursor || window.webkitIDBCursor;
fixupConstants(this.cursor, {
'NEXT': 'next',
'NEXT_NO_DUPLICATE': 'nextunique',
'PREV': 'prev',
'PREV_NO_DUPLICATE': 'prevunique'
});
this.openDB();
};
IDBStore.prototype = {
db: null,
dbName: null,
dbVersion: null,
store: null,
storeName: null,
keyPath: null,
autoIncrement: null,
indexes: null,
features: null,
onStoreReady: null,
openDB: function () {
this.newVersionAPI = typeof this.idb.setVersion == 'undefined';
if(!this.newVersionAPI){
throw new Error('The IndexedDB implementation in this browser is outdated. Please upgrade your browser.');
}
var features = this.features = {};
features.hasAutoIncrement = !window.mozIndexedDB; // TODO: Still, really?
var openRequest = this.idb.open(this.dbName, this.dbVersion);
openRequest.onerror = function (error) {
var gotVersionErr = false;
if ('error' in error.target) {
gotVersionErr = error.target.error.name == "VersionError";
} else if ('errorCode' in error.target) {
gotVersionErr = error.target.errorCode == 12; // TODO: Use const
}
if (gotVersionErr) {
console.error('Could not open database, version error:', error);
} else {
console.error('Could not open database, error:', error);
}
}.bind(this);
openRequest.onsuccess = function (event) {
if(this.db){
this.onStoreReady();
return;
}
this.db = event.target.result;
if(this.db.objectStoreNames.contains(this.storeName)){
if(!this.store){
var emptyTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
this.store = emptyTransaction.objectStore(this.storeName);
}
// check indexes
this.indexes.forEach(function(indexData){
var indexName = indexData.name;
// normalize and provide existing keys
indexData.keyPath = indexData.keyPath || indexName;
indexData.unique = !!indexData.unique;
indexData.multiEntry = !!indexData.multiEntry;
if(!indexName){
throw new Error('Cannot create index: No index name given.');
}
if(this.hasIndex(indexName)){
// check if it complies
var actualIndex = this.store.index(indexName);
var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
// IE10 returns undefined for no multiEntry
if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
return true;
}
return indexData[key] == actualIndex[key];
});
if(!complies){
throw new Error('Cannot modify index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
} else {
throw new Error('Cannot create new index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
}, this);
this.onStoreReady();
} else {
// We should never get here.
throw new Error('Cannot create a new store for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
}.bind(this);
openRequest.onupgradeneeded = function(/* IDBVersionChangeEvent */ event){
this.db = event.target.result;
if(this.db.objectStoreNames.contains(this.storeName)){
this.store = event.target.transaction.objectStore(this.storeName);
} else {
this.store = this.db.createObjectStore(this.storeName, { keyPath: this.keyPath, autoIncrement: this.autoIncrement});
}
this.indexes.forEach(function(indexData){
var indexName = indexData.name;
// normalize and provide existing keys
indexData.keyPath = indexData.keyPath || indexName;
indexData.unique = !!indexData.unique;
indexData.multiEntry = !!indexData.multiEntry;
if(!indexName){
throw new Error('Cannot create index: No index name given.');
}
if(this.hasIndex(indexName)){
// check if it complies
var actualIndex = this.store.index(indexName);
var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
// IE10 returns undefined for no multiEntry
if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
return true;
}
return indexData[key] == actualIndex[key];
});
if(!complies){
// index differs, need to delete and re-create
this.store.deleteIndex(indexName);
this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
}
} else {
this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
}
}, this);
}.bind(this);
},
deleteDatabase: function () {
if (this.idb.deleteDatabase) {
this.idb.deleteDatabase(this.dbName);
}
},
/*********************
* data manipulation *
*********************/
put: function (dataObj, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not write data.', error);
});
onSuccess || (onSuccess = noop);
if (typeof dataObj[this.keyPath] == 'undefined' && !this.features.hasAutoIncrement) {
dataObj[this.keyPath] = this._getUID();
}
var putTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var putRequest = putTransaction.objectStore(this.storeName).put(dataObj);
putRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
putRequest.onerror = onError;
},
get: function (key, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not read data.', error);
});
onSuccess || (onSuccess = noop);
var getTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var getRequest = getTransaction.objectStore(this.storeName).get(key);
getRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
getRequest.onerror = onError;
},
remove: function (key, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not remove data.', error);
});
onSuccess || (onSuccess = noop);
var removeTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var deleteRequest = removeTransaction.objectStore(this.storeName).delete(key);
deleteRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
deleteRequest.onerror = onError;
},
getAll: function (onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not read data.', error);
});
onSuccess || (onSuccess = noop);
var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var store = getAllTransaction.objectStore(this.storeName);
if (store.getAll) {
var getAllRequest = store.getAll();
getAllRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
getAllRequest.onerror = onError;
} else {
this._getAllCursor(getAllTransaction, onSuccess, onError);
}
},
_getAllCursor: function (tr, onSuccess, onError) {
var all = [];
var store = tr.objectStore(this.storeName);
var cursorRequest = store.openCursor();
cursorRequest.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
all.push(cursor.value);
cursor['continue']();
}
else {
onSuccess(all);
}
};
cursorRequest.onError = onError;
},
clear: function (onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not clear store.', error);
});
onSuccess || (onSuccess = noop);
var clearTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var clearRequest = clearTransaction.objectStore(this.storeName).clear();
clearRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
clearRequest.onerror = onError;
},
_getUID: function () {
// FF bails at times on non-numeric ids. So we take an even
// worse approach now, using current time as id. Sigh.
return +new Date();
},
/************
* indexing *
************/
getIndexList: function () {
return this.store.indexNames;
},
hasIndex: function (indexName) {
return this.store.indexNames.contains(indexName);
},
/**********
* cursor *
**********/
iterate: function (onItem, options) {
options = mixin({
index: null,
order: 'ASC',
filterDuplicates: false,
keyRange: null,
writeAccess: false,
onEnd: null,
onError: function (error) {
console.error('Could not open cursor.', error);
}
}, options || {});
var directionType = options.order.toLowerCase() == 'desc' ? 'PREV' : 'NEXT';
if (options.filterDuplicates) {
directionType += '_NO_DUPLICATE';
}
var cursorTransaction = this.db.transaction([this.storeName], this.consts[options.writeAccess ? 'READ_WRITE' : 'READ_ONLY']);
var cursorTarget = cursorTransaction.objectStore(this.storeName);
if (options.index) {
cursorTarget = cursorTarget.index(options.index);
}
var cursorRequest = cursorTarget.openCursor(options.keyRange, this.cursor[directionType]);
cursorRequest.onerror = options.onError;
cursorRequest.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
onItem(cursor.value, cursor, cursorTransaction);
cursor['continue']();
} else {
if(options.onEnd){
options.onEnd()
} else {
onItem(null);
}
}
};
},
count: function (onSuccess, options) {
options = mixin({
index: null,
keyRange: null
}, options || {});
var onError = options.onError || function (error) {
console.error('Could not open cursor.', error);
};
var cursorTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var cursorTarget = cursorTransaction.objectStore(this.storeName);
if (options.index) {
cursorTarget = cursorTarget.index(options.index);
}
var countRequest = cursorTarget.count(options.keyRange);
countRequest.onsuccess = function (evt) {
onSuccess(evt.target.result);
};
countRequest.onError = function (error) {
onError(error);
};
},
/**************/
/* key ranges */
/**************/
makeKeyRange: function(options){
var keyRange,
hasLower = typeof options.lower != 'undefined',
hasUpper = typeof options.upper != 'undefined';
switch(true){
case hasLower && hasUpper:
keyRange = this.keyRange.bound(options.lower, options.upper, options.excludeLower, options.excludeUpper);
break;
case hasLower:
keyRange = this.keyRange.lowerBound(options.lower, options.excludeLower);
break;
case hasUpper:
keyRange = this.keyRange.upperBound(options.upper, options.excludeUpper);
break;
default:
throw new Error('Cannot create KeyRange. Provide one or both of "lower" or "upper" value.');
break;
}
return keyRange;
}
};
/** helpers **/
var noop = function () {
};
var empty = {};
var mixin = function (target, source) {
var name, s;
for (name in source) {
s = source[name];
if (s !== empty[name] && s !== target[name]) {
target[name] = s;
}
}
return target;
};
return IDBStore;
}, this);
-19
View File
@@ -1,19 +0,0 @@
Copyright (c) 2011 - 2012 Jens Arps
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-313
View File
@@ -1,313 +0,0 @@
About
=====
This is a wrapper for indexedDB. It is meant to
a) ease the use of indexedDB and abstract away the differences between the
existing impls in Chrome, Firefox and IE10 (yes, it works in all three), and
b) show how IDB works. The code is split up into short methods, so that it's
easy to see what happens in what method.
"Showing how it works" is the main intention of this project. IndexedDB is
all the buzz, but only a few people actually know how to use it.
The code in IDBWrapper.js is not optimized for anything, nor minified or anything.
It is meant to be read and easy to understand. So, please, go ahead and check out
the source!
There are two tutorials to get you up and running:
Part 1: Setup and CRUD operations
http://jensarps.de/2011/11/25/working-with-idbwrapper-part-1/
Part 2: Running Queries against the store
http://jensarps.de/2012/11/13/working-with-idbwrapper-part-2/
##November Rewrite
I rewrote IDBWrapper to cope with all the issues, and the new version is on
master since Nov, 13th 2012. The API didn't change much, I just removed some
of the methods. Method signatures remain unchanged.
However, if you have a previous version of IDBWrapper in use, there's an
issue: The new version won't be able to access the store created with the old
version, because database names changed. In that case, you need to manually
migrate the data: Include both versions of IDBWrapper (use a different name for
them), do a getAll() on the old store and write the data to the new store.
I am very sorry about any inconveniences, but there was no other way.
The 'old' version of IDBWrapper is still available in the `legacy` branch:
https://github.com/jensarps/IDBWrapper/tree/legacy
Also, "showing how it works" is no longer the main intention behind this. Now,
it's rather "just works".
Examples
========
There are some examples to run right in your browser over here: http://jensarps.github.com/IDBWrapper/example/
The source for these examples are in the `example` folder of this repository.
Usage
=====
Including the IDBStore.js file will add an IDBStore constructor to the global scope.
Alternatively, you can use an AMD loader such as RequireJS to load the file,
and you will receive the constructor in your load callback (the constructor
will then, of course, have whatever name you call it).
You can then create an IDB store:
```javascript
var myStore = new IDBStore();
```
You may pass two parameters to the constructor: the first is an object with optional parameters,
the second is a function reference to a function that is called when the store is ready to use.
The options object may contain the following properties (default values are shown):
```javascript
{
storeName: 'Store',
dbVersion: 1,
keyPath: 'id',
autoIncrement: true,
indexes: [],
onStoreReady: function(){}
}
```
'keyPath' is the name of the property to be used as key index. If 'autoIncrement' is set to true,
the database will automatically add a unique key to the keyPath index when storing objects missing
that property. 'indexes' contains objects defining indexes (see below for details on indexes).
You can also pass a callback function to the options object. If a callback is provided both as second
parameter and inside of the options object, the function passed as second parameter will be used.
Methods
=======
Here's an overview of available methods in IDBStore:
Data Manipulation
-----------------
Use the following methods to read and write data:
___
1) The put method.
```javascript
put(/*Object*/ dataObj, /*Function?*/onSuccess, /*Function?*/onError)
```
`dataObj` is the Object to store. `onSuccess` will be called when the insertion/update was successful,
and it will receive the keyPath value (the id, so to say) of the inserted object as first and only
argument. `onError` will be called if the insertion/update failed and it will receive the error event
object as first and only argument. If the store already contains an object with the given keyPath id,
it will be overwritten by `dataObj`.
___
2) The get method.
```javascript
get(/*keyPath value*/ key, /*Function?*/onSuccess, /*Function?*/onError)
```
`key` is the keyPath property value (the id) of the object to retrieve. `onSuccess` will be called if
the get operation was successful, and it will receive the stored object as first and only argument. If
no object was found with the given keyPath value, this argument will be null. `onError` will be called
if the get operation failed and it will receive the error event object as first and only argument.
___
3) The getAll method.
```javascript
getAll: function(/*Function?*/onSuccess, /*Function?*/onError)
```
`onSuccess` will be called if the getAll operation was successful, and it will receive an Array of
all objects currently stored in the store as first and only argument. `onError` will be called if
the getAll operation failed and it will receive the error event object as first and only argument.
___
4) The remove method.
```javascript
remove: function(/*keyPath value*/ key, /*Function?*/onSuccess, /*Function?*/onError)
```
`key` is the keyPath property value (the id) of the object to remove. `onSuccess` will be called if
the remove operation was successful, and it _should_ receive `false` as first and only argument if the
object to remove was not found, and `true` if it was found and removed.
NOTE: FF 8 will pass the key to the onSuccess handler, no matter if there is an corresponding object
or not. Chrome 15 will pass `null` if removal was successful, and call the error handler if the object
wasn't found. Chrome 17 will behave as described above.
`onError` will be called if the remove operation failed and it will receive the error event object as first
and only argument.
___
5) The clear method.
```javascript
clear: function(/*Function?*/onSuccess, /*Function?*/onError)
```
`onSuccess` will be called if the clear operation was successful. `onError` will be called if the clear
operation failed and it will receive the error event object as first and only argument.
Index Operations
----------------
To create indexes, you need to pass the index information to the IDBStore()
constructor, for example:
```javascript
{
storeName: 'customers',
dbVersion: 1,
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: function(){},
indexes: [
{ name: 'lastname', keyPath: 'lastname', unique: false, multiEntry: false }
]
}
```
An entry in the index Array is an object containing the following properties:
The `name` property is the identifier of the index. If you want to work with the created index later, this name is used to identify the index. This is the only property that is mandatory.
The `keyPath` property is the name of the property in your stored data that you want to index. If you omit that, IDBWrapper will assume that it is the same as the provided name, and will use this instead.
The `unique` property tells the store whether the indexed property in your data is unique. If you set this to true, it will add a uniqueness constraint to the store which will make it throw if you try to store data that violates that constraint. If you omit that, IDBWrapper will set this to false.
The `multiEntry` property is kinda weird. You can read up on it here: http://www.w3.org/TR/IndexedDB/#dfn-multientry. However, you can live perfectly fine with setting this to false (or just omitting it, this is set to false by default).
If you want to add an index to an existing store, you need to increase the
version number of your store, as adding an index changes the structure of
the database.
To modify an index, modify the object in the indexes Array in the constructor.
Again, you need to increase the version of your store.
In addition, there are still some convenience methods available:
___
1) The hasIndex method.
```javascript
hasIndex: function(/*String*/ indexName)
```
Return true if an index with the given name exists in the store, false if not.
___
2) The getIndexList method.
```javascript
getIndexList: function()
```
Returns a `DOMStringList` with all existing indices.
Running Queries
---------------
To run queries, IDBWrapper provides an `iterate()` method. To create keyRanges,
there is the `makeKeyRange()` method. In addition to these, IDBWrapper comes
with a `count()` method.
___
1) The iterate method.
```javascript
iterate: function(/*Function*/ onItem, /*Object*/ iterateOptions)
```
The `onItem` callback will be called once for every match. It will receive three arguments: the object that matched the query, a reference to the current cursor object (IDBWrapper uses IndexedDB's Cursor internally to iterate), and a reference to the current ongoing transaction.
There's one special situation: if you didn't pass an onEnd handler in the options objects (see below), the onItem handler will be called one extra time when the transaction is over. In this case, it will receive null as only argument. So, to check when the iteration is over and you won't get any more data objects, you can either pass an onEnd handler, or check for null in the onItem handler.
The `iterateOptions` object can contain one or more of the following properties:
The `index` property contains the name of the index to operate on. If you omit this, IDBWrapper will use the store's keyPath as index.
In the `keyRange` property you can pass a keyRange.
The `order` property can be set to 'ASC' or 'DESC', and determines the ordering direction of results. If you omit this, IDBWrapper will use 'ASC'.
The `filterDuplicates` property is an interesting one: If you set this to true (it defaults to false), and have several objects that have the same value in their key, the store will only fetch the first of those. It is not about objects being the same, it's about their key being the same. For example, in the customers database are a couple of guys having 'Smith' as last name. Setting filterDuplicates to true in the above example will make `iterate()` call the onItem callback only for the first of those.
The `writeAccess` property defaults to false. If you need write access to the store during the iteration, you need to set this to true.
In the `onEnd` property you can pass a callback that gets called after the iteration is over and the transaction is closed. It does not receive any arguments.
In the `onError` property you can pass a custom error handler. In case of an error, it will be called and receives the Error object as only argument.
___
2) The makeKeyRange method.
```javascript
iterate: function(/*Object*/ keyRangeOptions)
```
Returns an IDBKeyRange.
The `keyRangeOptions` object must have one or more of the following properties:
`lower`: The lower bound of the range
`excludeLower`: Boolean, whether to exclude the lower bound itself. Default: false
`upper`: The upper bound of the range
`excludeUpper`: Boolean, whether to exclude the upper bound itself. Default: false
___
3) The count method.
```javascript
iterate: function(/*Function*/ onSuccess, /*Object*/ countOptions)
```
The onSuccess receives the result of the count as only argument.
The `countOptions` object may have one or more of the following properties:
index: The name of an index to operate on.
keyRange: A keyRange to use
@@ -1,94 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var tpls = {
row: '<tr><td>{customerid}</td><td><input id="lastname_{customerid}" value="{lastname}"></td><td><input id="firstname_{customerid}" value="{firstname}"></td><td><button onclick="app.deleteItem({customerid});">delete</button><button onclick="app.updateItem({customerid});">update</button></td></tr>',
table: '<table><tr><th>ID</th><th>Last Name</th><th>First Name</th><th></th></tr>{content}</table>'
};
var customers;
var nodeCache = {};
function init(){
// create a store ("table") for the customers
customers = new IDBStore({
storeName: 'customer',
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: refreshTable
});
// create references for some nodes we have to work with
['submit', 'customerid', 'firstname', 'lastname', 'results-container'].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit button.
nodeCache.submit.addEventListener('click', enterData);
}
function refreshTable(){
customers.getAll(listItems);
}
function listItems(data){
var content = '';
data.forEach(function(item){
content += tpls.row.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key];
});
});
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
}
function enterData(){
// read data from inputs…
var data = {};
['customerid','firstname','lastname'].forEach(function(key){
var value = nodeCache[key].value.trim();
if(value.length){
if(key == 'customerid'){ // We want the id to be numeric:
value = parseInt(value, 10);
}
data[key] = value;
}
});
// …and store them away.
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function clearForm(){
['customerid','firstname','lastname'].forEach(function(id){
nodeCache[id].value = '';
});
}
function deleteItem(id){
customers.remove(id, refreshTable);
}
function updateItem(id){
var data = {
customerid: id,
firstname: document.getElementById('firstname_' + id).value.trim(),
lastname: document.getElementById('lastname_' + id).value.trim()
};
customers.put(data, refreshTable);
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
deleteItem: deleteItem,
updateItem: updateItem
};
// go!
init();
});
@@ -1,31 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper Basic CRUD Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper Basic CRUD Example</div>
<div class="hbox flex">
<div class="vbox flex" id="results-container">
QueryResults
</div>
<div class="vbox">
<div id="query"></div>
<div id="input">
<p>
Enter some data to save. As ID, enter a numeric value or leave blank.
</p>
<div><label>ID</label><input id="customerid"></div>
<div><label>First Name</label><input id="firstname"></div>
<div><label>Last Name</label><input id="lastname"></div>
<button id="submit">Enter data</button>
</div>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,86 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
@@ -1,22 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>IndexedDB Wrapper Examples</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="head">IDBWrapper Examples</div>
<div class="container">
<h1>IDBWrapper Examples</h1>
<p>
There are a couple of examples to try out / look at:
</p>
<ul>
<li><a href="quicktest/">Quicktest</a> - Just a quick test to see if IDB opens and fool around in the console.</li>
<li><a href="basic/">Basic CRUD</a> - A basic CRUD example using an IDB store as fixed table.</li>
<li><a href="objectstore/">ObjectStore</a> - An example to show the difference between a table and an object store.</li>
<li><a href="index/">Index</a> - An example to show how to work with indexes.</li>
</ul>
</div>
</body>
</html>
@@ -1,163 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var tpls = {
row: '<tr><td>{customerid}</td><td>{lastname}</td><td>{firstname}</td><td>{age}</td><td><button onclick="app.deleteItem({customerid});">delete</button></td></tr>',
table: '<table><tr><th>ID</th><th>Last Name</th><th>First Name</th><th>Age</th><th></th></tr>{content}</table>'
};
var customers;
var nodeCache = {};
function init(){
// create a store ("table") for the customers
customers = app.customers = new IDBStore({
dbVersion: 1,
storeName: 'customer-index',
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: refreshTable,
indexes: [
{ name: 'lastname', keyPath: 'lastname', unique: false, multiEntry: false }
]
});
// create references for some nodes we have to work with
[
'submit', 'submitQuery',
'upper', 'lower', 'excludeLower', 'excludeUpper',
'sortOrder', 'index', 'filterDuplicates',
'customerid', 'firstname', 'lastname', 'age',
'results-container'
].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit buttons.
nodeCache.submit.addEventListener('click', enterData);
nodeCache.submitQuery.addEventListener('click', runQuery);
}
function refreshTable(){
customers.getAll(listItems);
}
function listItems(data){
var content = '';
data.forEach(function(item){
content += tpls.row.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key];
});
});
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
}
function enterData(){
// read data from inputs…
var data = {};
['customerid','firstname','lastname', 'age'].forEach(function(key){
var value = nodeCache[key].value.trim();
if(value.length){
if(key == 'customerid'){
value = parseInt(value, 10);
}
data[key] = value;
}
});
// …and store them away.
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function clearForm(){
['customerid','firstname','lastname', 'age'].forEach(function(id){
nodeCache[id].value = '';
});
}
function deleteItem(id){
customers.remove(id, refreshTable);
}
function makeRandomEntry(){
var lastnames = ['Smith','Miller','Doe','Frankenstein','Furter'],
firstnames = ['Peter','John','Frank', 'James', 'Jill'];
var entry = {
lastname: lastnames[Math.floor(Math.random()*5)],
firstname: firstnames[Math.floor(Math.random()*4)],
age: Math.floor(Math.random() * (100 - 20)) + 20,
customerid: parseInt( ( "" + ( Date.now() * Math.random() ) ).substring(0, 6), 10)
};
return entry;
}
function addRandomCustomer(){
var data = makeRandomEntry();
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function runQuery(){
var upper = nodeCache.upper.value,
hasUpper = upper != '',
lower = nodeCache.lower.value,
hasLower = lower != '',
indexName = nodeCache.index.value,
sortOrder = nodeCache.sortOrder.value,
filterDuplicates = nodeCache.filterDuplicates.checked,
keyRange,
content = '';
if(hasUpper || hasLower){ // create a keyRange only if bounds are given
var options = {};
if(hasUpper){
options.upper = upper;
options.excludeUpper = nodeCache.excludeUpper.checked;
}
if(hasLower){
options.lower = lower;
options.excludeLower = nodeCache.excludeLower.checked;
}
keyRange = customers.makeKeyRange(options);
}
var onItem = function (item) {
content += tpls.row.replace(/\{([^\}]+)\}/g, function (_, key) {
return item[key];
});
};
var onEnd = function () {
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
};
customers.iterate(onItem, {
index: indexName,
keyRange: keyRange,
filterDuplicates: filterDuplicates,
order: sortOrder,
onEnd: onEnd
});
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
deleteItem: deleteItem,
addRandomCustomer: addRandomCustomer
};
// go!
init();
});
@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper Basic Index Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper Basic Index Example</div>
<div class="hbox flex">
<div class="vbox flex" id="results-container">
QueryResults
</div>
<div id="query">
<h1>Query</h1>
<div>
<label>Index to use</label>
<select id="index">
<option value="" selected>None (use store's keyPath)</option>
<option value="lastname">lastname</option>
</select>
</div>
<div><label>Lower Bound</label><input id="lower"></div>
<div><label>Exclude Lower</label><input type="checkbox" id="excludeLower"></div>
<div><label>Upper Bound</label><input id="upper"></div>
<div><label>Exclude Upper</label><input type="checkbox" id="excludeUpper"></div>
<div>
<label>Sort Order</label>
<select id="sortOrder">
<option value="ASC" selected>Ascending</option>
<option value="DESC">Descending</option>
</select>
</div>
<div><label>Filter Duplicates</label><input type="checkbox" id="filterDuplicates"></div>
<button id="submitQuery">Submit Query</button>
</div>
<div id="input">
<h1>Add data</h1>
<p>
Add a random customer:
<button onclick="app.addRandomCustomer()">Add random customer data
</button>
</p>
<p>
Or, enter customer data below:
</p>
<div><label>Customer ID</label><input id="customerid"></div>
<div><label>First Name</label><input id="firstname"></div>
<div><label>Last Name</label><input id="lastname"></div>
<div><label>Age</label><input id="age"></div>
<button id="submit">Enter data</button>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,89 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input,
#query {
padding: 10px;
width: 350px;
border-left: solid 1px black;
}
#input div,
#query div{
padding: 5px;
}
#input label,
#query label{
display: inline-block;
width: 120px;
}
File diff suppressed because it is too large Load Diff
@@ -1,93 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var objStore;
var nodeCache = {};
function init(){
// create a store ("table")
objStore = new IDBStore({
storeName: 'objectstore',
keyPath: 'id',
autoIncrement: true,
onStoreReady: refreshTable
});
// create references for some nodes we have to work with
['submit', 'results-container'].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit button.
nodeCache.submit.addEventListener('click', enterData);
}
function refreshTable(){
objStore.getAll(listItems);
}
function listItems(data){
var header, tpl,
props = ['id'],
content = '';
data.forEach(function(item){
for(var prop in item){
if(props.indexOf(prop) < 0){
props.push(prop);
}
}
});
header = '<tr><th>' + props.join('</th><th>') + '</th></tr>';
tpl = '<tr><td>{' + props.join('}</td><td>{') + '}</td></tr>';
data.forEach(function(item){
content += tpl.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key] || '';
});
});
nodeCache['results-container'].innerHTML = '<table>' + header + content + '</table>';
}
function enterData(){
// read data from inputs
var propName, value, hasData,
data = {},
count = 4;
while(--count){
propName = document.getElementById('prop_' + count).value.trim();
if(propName.length){
hasData = true;
value = document.getElementById('value_' + count).value.trim();
// Don't do this at home. This is just a very dirty hack to 'guess' what
// type of data you just entered. If you do stuff like this in production
// code, UNICORNS WILL DIE. You have been warned.
data[propName] = ['{', '['].indexOf(value.substring(0,1)) !== -1 ? eval('(' + value + ')') : parseInt(value, 10) || value;
}
}
if(!hasData){
return;
}
// and store them away.
objStore.put(data, refreshTable);
}
function clear(){
objStore.clear(refreshTable);
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
clear: clear
};
// go!
init();
});
@@ -1,57 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper ObjectStore Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper ObjectStore Example</div>
<div class="hbox flex">
<div class="vbox flex container" id="results-container">
QueryResults
</div>
<div class="vbox container" id="interaction-container">
<div id="query" class="vbox"></div>
<div id="input" class="vbox">
<p>
IDB is not a relational database; it's an object store. That means you
have
no such things as fixed, defined columns.<br/>
Just enter <em>any name</em> as key and <em>anything</em> as value.<br/>
<br/>
To enter non-primitive values, use literal notaion.
</p>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><input id="prop_1"/></td>
<td><textarea id="value_1"></textarea></td>
</tr>
<tr>
<td><input id="prop_2"/></td>
<td><textarea id="value_2"></textarea></td>
</tr>
<tr>
<td><input id="prop_3"/></td>
<td><textarea id="value_3"></textarea></td>
</tr>
</tbody>
</table>
<button id="submit">Enter data</button>
</div>
<div id="clear" class="">
<button onclick="app.clear()">Clear Store</button>
</div>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,93 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
.container {
overflow: auto;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
#clear {
padding: 10px;
}
@@ -1,105 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IndexedDB Wrapper Quick Test</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body class="vbox" onload="onload();">
<div id="head">IDBWrapper Quick Test</div>
<div class="container">
<p>Open the console and click 'Open DB'. You will then see a bunch of buttons
that allow data manipulation. Click them, and check the console for
results.</p>
<p id="openWrapper" style="display: none;">
<button onclick="createStore_1_1()">Open DB</button>
</p>
<p id="dataWrapper" style="display: none;">
<button onclick="testWrite()">put</button>
<br/>
<button onclick="testRead()">get</button>
<br/>
<button onclick="testGetAll()">getAll</button>
<br/>
<button onclick="testRemove()">remove</button>
<br/>
<button onclick="testClear()">clear</button>
</p>
</div>
<script type="text/javascript" src="../../IDBStore.js"></script>
<script type="text/javascript">
function onload () {
document.getElementById('openWrapper').style.display = '';
}
/* methods for walking thorugh the data ops: */
var lastnames = ['Smith', 'Miller', 'Doe', 'Frankenstein', 'Furter'],
firstnames = ['Peter', 'John', 'Frank', 'James'],
id = 1;
function testWrite (store) {
store = store || store_1_1;
var dataObj = {
lastname: lastnames[Math.floor(Math.random() * 5)],
firstname: firstnames[Math.floor(Math.random() * 4)],
age: Math.floor(Math.random() * (100 - 20)) + 20,
id: id++
};
store.put(dataObj, function (result) {
console.log('put() call success. Result:', result);
});
}
function testRead (store) {
store = store || store_1_1;
store.get(1, function (result) {
console.log('get() call success. Result:', result);
});
}
function testGetAll (store) {
store = store || store_1_1;
store.getAll(function (result) {
console.log('getAll() call success. Result:', result);
});
}
function testRemove (store) {
store = store || store_1_1;
store.remove(1, function (result) {
console.log('remove() call success. Result:', result);
});
}
function testClear (store) {
store = store || store_1_1;
store.clear(function (result) {
console.log('clear() call success. Result:', result);
});
}
/* create a store using the wrapper and kick off: */
function createStore_1_1 () {
window['store_1_1'] = new IDBStore({
dbVersion: '1',
storeName: 'testStore_1_1',
keyPath: 'id',
autoIncrement: true,
onStoreReady: function () {
console.log('Store ready, go ahead!');
console.log('The store is accessible at window.store_1_1');
document.getElementById('openWrapper').style.display = 'none';
document.getElementById('dataWrapper').style.display = '';
}
});
}
</script>
</body>
</html>
@@ -1,94 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
.container {
overflow: auto;
padding: 10px;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
#clear {
padding: 10px;
}
@@ -1,87 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
}
.hbox {
display: -webkit-box;
display: -moz-box;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
}
.container {
overflow: auto;
padding: 10px;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
#clear {
padding: 10px;
}
@@ -1,24 +0,0 @@
{
"name": "idb-wrapper",
"version": "0.1.2",
"description": "This is a wrapper for indexedDB.",
"keywords": [],
"author": "jensarps <mail@jensarps.de>",
"repository": "git://github.com/jensarps/IDBWrapper.git",
"main": "IDBStore",
"homepage": "https://github.com/jensarps/IDBWrapper",
"contributors": [],
"bugs": {
"url": "https://github.com/jensarps/IDBWrapper/issues",
"email": "mail@jensarps.de"
},
"dependencies": {},
"devDependencies": {},
"licenses": [
{
"type": "MIT",
"url": "http://github.com/jensarps/IDBWrapper/raw/master/LICENSE"
}
],
"scripts": {}
}
Binary file not shown.
-2
View File
@@ -1,2 +0,0 @@
.project
.idea
-464
View File
@@ -1,464 +0,0 @@
/*
* IDBWrapper - A cross-browser wrapper for IndexedDB
* Copyright (c) 2011 - 2012 Jens Arps
* http://jensarps.de/
*
* Licensed under the MIT (X11) license
*/
"use strict";
(function (name, definition, global) {
if (typeof define === 'function') {
define(definition);
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = definition();
} else {
global[name] = definition();
}
})('IDBStore', function () {
var IDBStore;
var defaults = {
storeName: 'Store',
dbVersion: 1,
keyPath: 'id',
autoIncrement: true,
onStoreReady: function () {
},
indexes: []
};
IDBStore = function (kwArgs, onStoreReady) {
function fixupConstants (object, constants) {
for (var prop in constants) {
if (!(prop in object))
object[prop] = constants[prop];
}
}
for(var key in defaults){
this[key] = typeof kwArgs[key] != 'undefined' ? kwArgs[key] : defaults[key];
}
this.dbName = 'IDBWrapper-' + this.storeName;
this.dbVersion = parseInt(this.dbVersion, 10);
onStoreReady && (this.onStoreReady = onStoreReady);
this.idb = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
this.keyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange;
this.consts = {
'READ_ONLY': 'readonly',
'READ_WRITE': 'readwrite',
'VERSION_CHANGE': 'versionchange'
}
this.cursor = window.IDBCursor || window.webkitIDBCursor;
fixupConstants(this.cursor, {
'NEXT': 'next',
'NEXT_NO_DUPLICATE': 'nextunique',
'PREV': 'prev',
'PREV_NO_DUPLICATE': 'prevunique'
});
this.openDB();
};
IDBStore.prototype = {
db: null,
dbName: null,
dbVersion: null,
store: null,
storeName: null,
keyPath: null,
autoIncrement: null,
indexes: null,
features: null,
onStoreReady: null,
openDB: function () {
this.newVersionAPI = typeof this.idb.setVersion == 'undefined';
if(!this.newVersionAPI){
throw new Error('The IndexedDB implementation in this browser is outdated. Please upgrade your browser.');
}
var features = this.features = {};
features.hasAutoIncrement = !window.mozIndexedDB; // TODO: Still, really?
var openRequest = this.idb.open(this.dbName, this.dbVersion);
openRequest.onerror = function (error) {
var gotVersionErr = false;
if ('error' in error.target) {
gotVersionErr = error.target.error.name == "VersionError";
} else if ('errorCode' in error.target) {
gotVersionErr = error.target.errorCode == 12; // TODO: Use const
}
if (gotVersionErr) {
console.error('Could not open database, version error:', error);
} else {
console.error('Could not open database, error:', error);
}
}.bind(this);
openRequest.onsuccess = function (event) {
if(this.db){
this.onStoreReady();
return;
}
this.db = event.target.result;
if(this.db.objectStoreNames.contains(this.storeName)){
if(!this.store){
var emptyTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
this.store = emptyTransaction.objectStore(this.storeName);
}
// check indexes
this.indexes.forEach(function(indexData){
var indexName = indexData.name;
// normalize and provide existing keys
indexData.keyPath = indexData.keyPath || indexName;
indexData.unique = !!indexData.unique;
indexData.multiEntry = !!indexData.multiEntry;
if(!indexName){
throw new Error('Cannot create index: No index name given.');
}
if(this.hasIndex(indexName)){
// check if it complies
var actualIndex = this.store.index(indexName);
var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
// IE10 returns undefined for no multiEntry
if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
return true;
}
return indexData[key] == actualIndex[key];
});
if(!complies){
throw new Error('Cannot modify index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
} else {
throw new Error('Cannot create new index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
}, this);
this.onStoreReady();
} else {
// We should never get here.
throw new Error('Cannot create a new store for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
}.bind(this);
openRequest.onupgradeneeded = function(/* IDBVersionChangeEvent */ event){
this.db = event.target.result;
if(this.db.objectStoreNames.contains(this.storeName)){
this.store = event.target.transaction.objectStore(this.storeName);
} else {
this.store = this.db.createObjectStore(this.storeName, { keyPath: this.keyPath, autoIncrement: this.autoIncrement});
}
this.indexes.forEach(function(indexData){
var indexName = indexData.name;
// normalize and provide existing keys
indexData.keyPath = indexData.keyPath || indexName;
indexData.unique = !!indexData.unique;
indexData.multiEntry = !!indexData.multiEntry;
if(!indexName){
throw new Error('Cannot create index: No index name given.');
}
if(this.hasIndex(indexName)){
// check if it complies
var actualIndex = this.store.index(indexName);
var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
// IE10 returns undefined for no multiEntry
if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
return true;
}
return indexData[key] == actualIndex[key];
});
if(!complies){
// index differs, need to delete and re-create
this.store.deleteIndex(indexName);
this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
}
} else {
this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
}
}, this);
}.bind(this);
},
deleteDatabase: function () {
if (this.idb.deleteDatabase) {
this.idb.deleteDatabase(this.dbName);
}
},
/*********************
* data manipulation *
*********************/
put: function (dataObj, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not write data.', error);
});
onSuccess || (onSuccess = noop);
if (typeof dataObj[this.keyPath] == 'undefined' && !this.features.hasAutoIncrement) {
dataObj[this.keyPath] = this._getUID();
}
var putTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var putRequest = putTransaction.objectStore(this.storeName).put(dataObj);
putRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
putRequest.onerror = onError;
},
get: function (key, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not read data.', error);
});
onSuccess || (onSuccess = noop);
var getTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var getRequest = getTransaction.objectStore(this.storeName).get(key);
getRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
getRequest.onerror = onError;
},
remove: function (key, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not remove data.', error);
});
onSuccess || (onSuccess = noop);
var removeTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var deleteRequest = removeTransaction.objectStore(this.storeName).delete(key);
deleteRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
deleteRequest.onerror = onError;
},
getAll: function (onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not read data.', error);
});
onSuccess || (onSuccess = noop);
var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var store = getAllTransaction.objectStore(this.storeName);
if (store.getAll) {
var getAllRequest = store.getAll();
getAllRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
getAllRequest.onerror = onError;
} else {
this._getAllCursor(getAllTransaction, onSuccess, onError);
}
},
_getAllCursor: function (tr, onSuccess, onError) {
var all = [];
var store = tr.objectStore(this.storeName);
var cursorRequest = store.openCursor();
cursorRequest.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
all.push(cursor.value);
cursor['continue']();
}
else {
onSuccess(all);
}
};
cursorRequest.onError = onError;
},
clear: function (onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not clear store.', error);
});
onSuccess || (onSuccess = noop);
var clearTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var clearRequest = clearTransaction.objectStore(this.storeName).clear();
clearRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
clearRequest.onerror = onError;
},
_getUID: function () {
// FF bails at times on non-numeric ids. So we take an even
// worse approach now, using current time as id. Sigh.
return +new Date();
},
/************
* indexing *
************/
getIndexList: function () {
return this.store.indexNames;
},
hasIndex: function (indexName) {
return this.store.indexNames.contains(indexName);
},
/**********
* cursor *
**********/
iterate: function (onItem, options) {
options = mixin({
index: null,
order: 'ASC',
filterDuplicates: false,
keyRange: null,
writeAccess: false,
onEnd: null,
onError: function (error) {
console.error('Could not open cursor.', error);
}
}, options || {});
var directionType = options.order.toLowerCase() == 'desc' ? 'PREV' : 'NEXT';
if (options.filterDuplicates) {
directionType += '_NO_DUPLICATE';
}
var cursorTransaction = this.db.transaction([this.storeName], this.consts[options.writeAccess ? 'READ_WRITE' : 'READ_ONLY']);
var cursorTarget = cursorTransaction.objectStore(this.storeName);
if (options.index) {
cursorTarget = cursorTarget.index(options.index);
}
var cursorRequest = cursorTarget.openCursor(options.keyRange, this.cursor[directionType]);
cursorRequest.onerror = options.onError;
cursorRequest.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
onItem(cursor.value, cursor, cursorTransaction);
cursor['continue']();
} else {
if(options.onEnd){
options.onEnd()
} else {
onItem(null);
}
}
};
},
count: function (onSuccess, options) {
options = mixin({
index: null,
keyRange: null
}, options || {});
var onError = options.onError || function (error) {
console.error('Could not open cursor.', error);
};
var cursorTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var cursorTarget = cursorTransaction.objectStore(this.storeName);
if (options.index) {
cursorTarget = cursorTarget.index(options.index);
}
var countRequest = cursorTarget.count(options.keyRange);
countRequest.onsuccess = function (evt) {
onSuccess(evt.target.result);
};
countRequest.onError = function (error) {
onError(error);
};
},
/**************/
/* key ranges */
/**************/
makeKeyRange: function(options){
var keyRange,
hasLower = typeof options.lower != 'undefined',
hasUpper = typeof options.upper != 'undefined';
switch(true){
case hasLower && hasUpper:
keyRange = this.keyRange.bound(options.lower, options.upper, options.excludeLower, options.excludeUpper);
break;
case hasLower:
keyRange = this.keyRange.lowerBound(options.lower, options.excludeLower);
break;
case hasUpper:
keyRange = this.keyRange.upperBound(options.upper, options.excludeUpper);
break;
default:
throw new Error('Cannot create KeyRange. Provide one or both of "lower" or "upper" value.');
break;
}
return keyRange;
}
};
/** helpers **/
var noop = function () {
};
var empty = {};
var mixin = function (target, source) {
var name, s;
for (name in source) {
s = source[name];
if (s !== empty[name] && s !== target[name]) {
target[name] = s;
}
}
return target;
};
return IDBStore;
}, this);
-19
View File
@@ -1,19 +0,0 @@
Copyright (c) 2011 - 2012 Jens Arps
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-313
View File
@@ -1,313 +0,0 @@
About
=====
This is a wrapper for indexedDB. It is meant to
a) ease the use of indexedDB and abstract away the differences between the
existing impls in Chrome, Firefox and IE10 (yes, it works in all three), and
b) show how IDB works. The code is split up into short methods, so that it's
easy to see what happens in what method.
"Showing how it works" is the main intention of this project. IndexedDB is
all the buzz, but only a few people actually know how to use it.
The code in IDBWrapper.js is not optimized for anything, nor minified or anything.
It is meant to be read and easy to understand. So, please, go ahead and check out
the source!
There are two tutorials to get you up and running:
Part 1: Setup and CRUD operations
http://jensarps.de/2011/11/25/working-with-idbwrapper-part-1/
Part 2: Running Queries against the store
http://jensarps.de/2012/11/13/working-with-idbwrapper-part-2/
##November Rewrite
I rewrote IDBWrapper to cope with all the issues, and the new version is on
master since Nov, 13th 2012. The API didn't change much, I just removed some
of the methods. Method signatures remain unchanged.
However, if you have a previous version of IDBWrapper in use, there's an
issue: The new version won't be able to access the store created with the old
version, because database names changed. In that case, you need to manually
migrate the data: Include both versions of IDBWrapper (use a different name for
them), do a getAll() on the old store and write the data to the new store.
I am very sorry about any inconveniences, but there was no other way.
The 'old' version of IDBWrapper is still available in the `legacy` branch:
https://github.com/jensarps/IDBWrapper/tree/legacy
Also, "showing how it works" is no longer the main intention behind this. Now,
it's rather "just works".
Examples
========
There are some examples to run right in your browser over here: http://jensarps.github.com/IDBWrapper/example/
The source for these examples are in the `example` folder of this repository.
Usage
=====
Including the IDBStore.js file will add an IDBStore constructor to the global scope.
Alternatively, you can use an AMD loader such as RequireJS to load the file,
and you will receive the constructor in your load callback (the constructor
will then, of course, have whatever name you call it).
You can then create an IDB store:
```javascript
var myStore = new IDBStore();
```
You may pass two parameters to the constructor: the first is an object with optional parameters,
the second is a function reference to a function that is called when the store is ready to use.
The options object may contain the following properties (default values are shown):
```javascript
{
storeName: 'Store',
dbVersion: 1,
keyPath: 'id',
autoIncrement: true,
indexes: [],
onStoreReady: function(){}
}
```
'keyPath' is the name of the property to be used as key index. If 'autoIncrement' is set to true,
the database will automatically add a unique key to the keyPath index when storing objects missing
that property. 'indexes' contains objects defining indexes (see below for details on indexes).
You can also pass a callback function to the options object. If a callback is provided both as second
parameter and inside of the options object, the function passed as second parameter will be used.
Methods
=======
Here's an overview of available methods in IDBStore:
Data Manipulation
-----------------
Use the following methods to read and write data:
___
1) The put method.
```javascript
put(/*Object*/ dataObj, /*Function?*/onSuccess, /*Function?*/onError)
```
`dataObj` is the Object to store. `onSuccess` will be called when the insertion/update was successful,
and it will receive the keyPath value (the id, so to say) of the inserted object as first and only
argument. `onError` will be called if the insertion/update failed and it will receive the error event
object as first and only argument. If the store already contains an object with the given keyPath id,
it will be overwritten by `dataObj`.
___
2) The get method.
```javascript
get(/*keyPath value*/ key, /*Function?*/onSuccess, /*Function?*/onError)
```
`key` is the keyPath property value (the id) of the object to retrieve. `onSuccess` will be called if
the get operation was successful, and it will receive the stored object as first and only argument. If
no object was found with the given keyPath value, this argument will be null. `onError` will be called
if the get operation failed and it will receive the error event object as first and only argument.
___
3) The getAll method.
```javascript
getAll: function(/*Function?*/onSuccess, /*Function?*/onError)
```
`onSuccess` will be called if the getAll operation was successful, and it will receive an Array of
all objects currently stored in the store as first and only argument. `onError` will be called if
the getAll operation failed and it will receive the error event object as first and only argument.
___
4) The remove method.
```javascript
remove: function(/*keyPath value*/ key, /*Function?*/onSuccess, /*Function?*/onError)
```
`key` is the keyPath property value (the id) of the object to remove. `onSuccess` will be called if
the remove operation was successful, and it _should_ receive `false` as first and only argument if the
object to remove was not found, and `true` if it was found and removed.
NOTE: FF 8 will pass the key to the onSuccess handler, no matter if there is an corresponding object
or not. Chrome 15 will pass `null` if removal was successful, and call the error handler if the object
wasn't found. Chrome 17 will behave as described above.
`onError` will be called if the remove operation failed and it will receive the error event object as first
and only argument.
___
5) The clear method.
```javascript
clear: function(/*Function?*/onSuccess, /*Function?*/onError)
```
`onSuccess` will be called if the clear operation was successful. `onError` will be called if the clear
operation failed and it will receive the error event object as first and only argument.
Index Operations
----------------
To create indexes, you need to pass the index information to the IDBStore()
constructor, for example:
```javascript
{
storeName: 'customers',
dbVersion: 1,
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: function(){},
indexes: [
{ name: 'lastname', keyPath: 'lastname', unique: false, multiEntry: false }
]
}
```
An entry in the index Array is an object containing the following properties:
The `name` property is the identifier of the index. If you want to work with the created index later, this name is used to identify the index. This is the only property that is mandatory.
The `keyPath` property is the name of the property in your stored data that you want to index. If you omit that, IDBWrapper will assume that it is the same as the provided name, and will use this instead.
The `unique` property tells the store whether the indexed property in your data is unique. If you set this to true, it will add a uniqueness constraint to the store which will make it throw if you try to store data that violates that constraint. If you omit that, IDBWrapper will set this to false.
The `multiEntry` property is kinda weird. You can read up on it here: http://www.w3.org/TR/IndexedDB/#dfn-multientry. However, you can live perfectly fine with setting this to false (or just omitting it, this is set to false by default).
If you want to add an index to an existing store, you need to increase the
version number of your store, as adding an index changes the structure of
the database.
To modify an index, modify the object in the indexes Array in the constructor.
Again, you need to increase the version of your store.
In addition, there are still some convenience methods available:
___
1) The hasIndex method.
```javascript
hasIndex: function(/*String*/ indexName)
```
Return true if an index with the given name exists in the store, false if not.
___
2) The getIndexList method.
```javascript
getIndexList: function()
```
Returns a `DOMStringList` with all existing indices.
Running Queries
---------------
To run queries, IDBWrapper provides an `iterate()` method. To create keyRanges,
there is the `makeKeyRange()` method. In addition to these, IDBWrapper comes
with a `count()` method.
___
1) The iterate method.
```javascript
iterate: function(/*Function*/ onItem, /*Object*/ iterateOptions)
```
The `onItem` callback will be called once for every match. It will receive three arguments: the object that matched the query, a reference to the current cursor object (IDBWrapper uses IndexedDB's Cursor internally to iterate), and a reference to the current ongoing transaction.
There's one special situation: if you didn't pass an onEnd handler in the options objects (see below), the onItem handler will be called one extra time when the transaction is over. In this case, it will receive null as only argument. So, to check when the iteration is over and you won't get any more data objects, you can either pass an onEnd handler, or check for null in the onItem handler.
The `iterateOptions` object can contain one or more of the following properties:
The `index` property contains the name of the index to operate on. If you omit this, IDBWrapper will use the store's keyPath as index.
In the `keyRange` property you can pass a keyRange.
The `order` property can be set to 'ASC' or 'DESC', and determines the ordering direction of results. If you omit this, IDBWrapper will use 'ASC'.
The `filterDuplicates` property is an interesting one: If you set this to true (it defaults to false), and have several objects that have the same value in their key, the store will only fetch the first of those. It is not about objects being the same, it's about their key being the same. For example, in the customers database are a couple of guys having 'Smith' as last name. Setting filterDuplicates to true in the above example will make `iterate()` call the onItem callback only for the first of those.
The `writeAccess` property defaults to false. If you need write access to the store during the iteration, you need to set this to true.
In the `onEnd` property you can pass a callback that gets called after the iteration is over and the transaction is closed. It does not receive any arguments.
In the `onError` property you can pass a custom error handler. In case of an error, it will be called and receives the Error object as only argument.
___
2) The makeKeyRange method.
```javascript
iterate: function(/*Object*/ keyRangeOptions)
```
Returns an IDBKeyRange.
The `keyRangeOptions` object must have one or more of the following properties:
`lower`: The lower bound of the range
`excludeLower`: Boolean, whether to exclude the lower bound itself. Default: false
`upper`: The upper bound of the range
`excludeUpper`: Boolean, whether to exclude the upper bound itself. Default: false
___
3) The count method.
```javascript
iterate: function(/*Function*/ onSuccess, /*Object*/ countOptions)
```
The onSuccess receives the result of the count as only argument.
The `countOptions` object may have one or more of the following properties:
index: The name of an index to operate on.
keyRange: A keyRange to use
@@ -1,94 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var tpls = {
row: '<tr><td>{customerid}</td><td><input id="lastname_{customerid}" value="{lastname}"></td><td><input id="firstname_{customerid}" value="{firstname}"></td><td><button onclick="app.deleteItem({customerid});">delete</button><button onclick="app.updateItem({customerid});">update</button></td></tr>',
table: '<table><tr><th>ID</th><th>Last Name</th><th>First Name</th><th></th></tr>{content}</table>'
};
var customers;
var nodeCache = {};
function init(){
// create a store ("table") for the customers
customers = new IDBStore({
storeName: 'customer',
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: refreshTable
});
// create references for some nodes we have to work with
['submit', 'customerid', 'firstname', 'lastname', 'results-container'].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit button.
nodeCache.submit.addEventListener('click', enterData);
}
function refreshTable(){
customers.getAll(listItems);
}
function listItems(data){
var content = '';
data.forEach(function(item){
content += tpls.row.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key];
});
});
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
}
function enterData(){
// read data from inputs…
var data = {};
['customerid','firstname','lastname'].forEach(function(key){
var value = nodeCache[key].value.trim();
if(value.length){
if(key == 'customerid'){ // We want the id to be numeric:
value = parseInt(value, 10);
}
data[key] = value;
}
});
// …and store them away.
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function clearForm(){
['customerid','firstname','lastname'].forEach(function(id){
nodeCache[id].value = '';
});
}
function deleteItem(id){
customers.remove(id, refreshTable);
}
function updateItem(id){
var data = {
customerid: id,
firstname: document.getElementById('firstname_' + id).value.trim(),
lastname: document.getElementById('lastname_' + id).value.trim()
};
customers.put(data, refreshTable);
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
deleteItem: deleteItem,
updateItem: updateItem
};
// go!
init();
});
@@ -1,31 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper Basic CRUD Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper Basic CRUD Example</div>
<div class="hbox flex">
<div class="vbox flex" id="results-container">
QueryResults
</div>
<div class="vbox">
<div id="query"></div>
<div id="input">
<p>
Enter some data to save. As ID, enter a numeric value or leave blank.
</p>
<div><label>ID</label><input id="customerid"></div>
<div><label>First Name</label><input id="firstname"></div>
<div><label>Last Name</label><input id="lastname"></div>
<button id="submit">Enter data</button>
</div>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,86 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
@@ -1,22 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>IndexedDB Wrapper Examples</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="head">IDBWrapper Examples</div>
<div class="container">
<h1>IDBWrapper Examples</h1>
<p>
There are a couple of examples to try out / look at:
</p>
<ul>
<li><a href="quicktest/">Quicktest</a> - Just a quick test to see if IDB opens and fool around in the console.</li>
<li><a href="basic/">Basic CRUD</a> - A basic CRUD example using an IDB store as fixed table.</li>
<li><a href="objectstore/">ObjectStore</a> - An example to show the difference between a table and an object store.</li>
<li><a href="index/">Index</a> - An example to show how to work with indexes.</li>
</ul>
</div>
</body>
</html>
@@ -1,163 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var tpls = {
row: '<tr><td>{customerid}</td><td>{lastname}</td><td>{firstname}</td><td>{age}</td><td><button onclick="app.deleteItem({customerid});">delete</button></td></tr>',
table: '<table><tr><th>ID</th><th>Last Name</th><th>First Name</th><th>Age</th><th></th></tr>{content}</table>'
};
var customers;
var nodeCache = {};
function init(){
// create a store ("table") for the customers
customers = app.customers = new IDBStore({
dbVersion: 1,
storeName: 'customer-index',
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: refreshTable,
indexes: [
{ name: 'lastname', keyPath: 'lastname', unique: false, multiEntry: false }
]
});
// create references for some nodes we have to work with
[
'submit', 'submitQuery',
'upper', 'lower', 'excludeLower', 'excludeUpper',
'sortOrder', 'index', 'filterDuplicates',
'customerid', 'firstname', 'lastname', 'age',
'results-container'
].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit buttons.
nodeCache.submit.addEventListener('click', enterData);
nodeCache.submitQuery.addEventListener('click', runQuery);
}
function refreshTable(){
customers.getAll(listItems);
}
function listItems(data){
var content = '';
data.forEach(function(item){
content += tpls.row.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key];
});
});
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
}
function enterData(){
// read data from inputs…
var data = {};
['customerid','firstname','lastname', 'age'].forEach(function(key){
var value = nodeCache[key].value.trim();
if(value.length){
if(key == 'customerid'){
value = parseInt(value, 10);
}
data[key] = value;
}
});
// …and store them away.
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function clearForm(){
['customerid','firstname','lastname', 'age'].forEach(function(id){
nodeCache[id].value = '';
});
}
function deleteItem(id){
customers.remove(id, refreshTable);
}
function makeRandomEntry(){
var lastnames = ['Smith','Miller','Doe','Frankenstein','Furter'],
firstnames = ['Peter','John','Frank', 'James', 'Jill'];
var entry = {
lastname: lastnames[Math.floor(Math.random()*5)],
firstname: firstnames[Math.floor(Math.random()*4)],
age: Math.floor(Math.random() * (100 - 20)) + 20,
customerid: parseInt( ( "" + ( Date.now() * Math.random() ) ).substring(0, 6), 10)
};
return entry;
}
function addRandomCustomer(){
var data = makeRandomEntry();
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function runQuery(){
var upper = nodeCache.upper.value,
hasUpper = upper != '',
lower = nodeCache.lower.value,
hasLower = lower != '',
indexName = nodeCache.index.value,
sortOrder = nodeCache.sortOrder.value,
filterDuplicates = nodeCache.filterDuplicates.checked,
keyRange,
content = '';
if(hasUpper || hasLower){ // create a keyRange only if bounds are given
var options = {};
if(hasUpper){
options.upper = upper;
options.excludeUpper = nodeCache.excludeUpper.checked;
}
if(hasLower){
options.lower = lower;
options.excludeLower = nodeCache.excludeLower.checked;
}
keyRange = customers.makeKeyRange(options);
}
var onItem = function (item) {
content += tpls.row.replace(/\{([^\}]+)\}/g, function (_, key) {
return item[key];
});
};
var onEnd = function () {
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
};
customers.iterate(onItem, {
index: indexName,
keyRange: keyRange,
filterDuplicates: filterDuplicates,
order: sortOrder,
onEnd: onEnd
});
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
deleteItem: deleteItem,
addRandomCustomer: addRandomCustomer
};
// go!
init();
});
@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper Basic Index Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper Basic Index Example</div>
<div class="hbox flex">
<div class="vbox flex" id="results-container">
QueryResults
</div>
<div id="query">
<h1>Query</h1>
<div>
<label>Index to use</label>
<select id="index">
<option value="" selected>None (use store's keyPath)</option>
<option value="lastname">lastname</option>
</select>
</div>
<div><label>Lower Bound</label><input id="lower"></div>
<div><label>Exclude Lower</label><input type="checkbox" id="excludeLower"></div>
<div><label>Upper Bound</label><input id="upper"></div>
<div><label>Exclude Upper</label><input type="checkbox" id="excludeUpper"></div>
<div>
<label>Sort Order</label>
<select id="sortOrder">
<option value="ASC" selected>Ascending</option>
<option value="DESC">Descending</option>
</select>
</div>
<div><label>Filter Duplicates</label><input type="checkbox" id="filterDuplicates"></div>
<button id="submitQuery">Submit Query</button>
</div>
<div id="input">
<h1>Add data</h1>
<p>
Add a random customer:
<button onclick="app.addRandomCustomer()">Add random customer data
</button>
</p>
<p>
Or, enter customer data below:
</p>
<div><label>Customer ID</label><input id="customerid"></div>
<div><label>First Name</label><input id="firstname"></div>
<div><label>Last Name</label><input id="lastname"></div>
<div><label>Age</label><input id="age"></div>
<button id="submit">Enter data</button>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,89 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input,
#query {
padding: 10px;
width: 350px;
border-left: solid 1px black;
}
#input div,
#query div{
padding: 5px;
}
#input label,
#query label{
display: inline-block;
width: 120px;
}
File diff suppressed because it is too large Load Diff
@@ -1,93 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var objStore;
var nodeCache = {};
function init(){
// create a store ("table")
objStore = new IDBStore({
storeName: 'objectstore',
keyPath: 'id',
autoIncrement: true,
onStoreReady: refreshTable
});
// create references for some nodes we have to work with
['submit', 'results-container'].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit button.
nodeCache.submit.addEventListener('click', enterData);
}
function refreshTable(){
objStore.getAll(listItems);
}
function listItems(data){
var header, tpl,
props = ['id'],
content = '';
data.forEach(function(item){
for(var prop in item){
if(props.indexOf(prop) < 0){
props.push(prop);
}
}
});
header = '<tr><th>' + props.join('</th><th>') + '</th></tr>';
tpl = '<tr><td>{' + props.join('}</td><td>{') + '}</td></tr>';
data.forEach(function(item){
content += tpl.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key] || '';
});
});
nodeCache['results-container'].innerHTML = '<table>' + header + content + '</table>';
}
function enterData(){
// read data from inputs
var propName, value, hasData,
data = {},
count = 4;
while(--count){
propName = document.getElementById('prop_' + count).value.trim();
if(propName.length){
hasData = true;
value = document.getElementById('value_' + count).value.trim();
// Don't do this at home. This is just a very dirty hack to 'guess' what
// type of data you just entered. If you do stuff like this in production
// code, UNICORNS WILL DIE. You have been warned.
data[propName] = ['{', '['].indexOf(value.substring(0,1)) !== -1 ? eval('(' + value + ')') : parseInt(value, 10) || value;
}
}
if(!hasData){
return;
}
// and store them away.
objStore.put(data, refreshTable);
}
function clear(){
objStore.clear(refreshTable);
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
clear: clear
};
// go!
init();
});
@@ -1,57 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper ObjectStore Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper ObjectStore Example</div>
<div class="hbox flex">
<div class="vbox flex container" id="results-container">
QueryResults
</div>
<div class="vbox container" id="interaction-container">
<div id="query" class="vbox"></div>
<div id="input" class="vbox">
<p>
IDB is not a relational database; it's an object store. That means you
have
no such things as fixed, defined columns.<br/>
Just enter <em>any name</em> as key and <em>anything</em> as value.<br/>
<br/>
To enter non-primitive values, use literal notaion.
</p>
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><input id="prop_1"/></td>
<td><textarea id="value_1"></textarea></td>
</tr>
<tr>
<td><input id="prop_2"/></td>
<td><textarea id="value_2"></textarea></td>
</tr>
<tr>
<td><input id="prop_3"/></td>
<td><textarea id="value_3"></textarea></td>
</tr>
</tbody>
</table>
<button id="submit">Enter data</button>
</div>
<div id="clear" class="">
<button onclick="app.clear()">Clear Store</button>
</div>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,93 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
.container {
overflow: auto;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
#clear {
padding: 10px;
}
@@ -1,105 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IndexedDB Wrapper Quick Test</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body class="vbox" onload="onload();">
<div id="head">IDBWrapper Quick Test</div>
<div class="container">
<p>Open the console and click 'Open DB'. You will then see a bunch of buttons
that allow data manipulation. Click them, and check the console for
results.</p>
<p id="openWrapper" style="display: none;">
<button onclick="createStore_1_1()">Open DB</button>
</p>
<p id="dataWrapper" style="display: none;">
<button onclick="testWrite()">put</button>
<br/>
<button onclick="testRead()">get</button>
<br/>
<button onclick="testGetAll()">getAll</button>
<br/>
<button onclick="testRemove()">remove</button>
<br/>
<button onclick="testClear()">clear</button>
</p>
</div>
<script type="text/javascript" src="../../IDBStore.js"></script>
<script type="text/javascript">
function onload () {
document.getElementById('openWrapper').style.display = '';
}
/* methods for walking thorugh the data ops: */
var lastnames = ['Smith', 'Miller', 'Doe', 'Frankenstein', 'Furter'],
firstnames = ['Peter', 'John', 'Frank', 'James'],
id = 1;
function testWrite (store) {
store = store || store_1_1;
var dataObj = {
lastname: lastnames[Math.floor(Math.random() * 5)],
firstname: firstnames[Math.floor(Math.random() * 4)],
age: Math.floor(Math.random() * (100 - 20)) + 20,
id: id++
};
store.put(dataObj, function (result) {
console.log('put() call success. Result:', result);
});
}
function testRead (store) {
store = store || store_1_1;
store.get(1, function (result) {
console.log('get() call success. Result:', result);
});
}
function testGetAll (store) {
store = store || store_1_1;
store.getAll(function (result) {
console.log('getAll() call success. Result:', result);
});
}
function testRemove (store) {
store = store || store_1_1;
store.remove(1, function (result) {
console.log('remove() call success. Result:', result);
});
}
function testClear (store) {
store = store || store_1_1;
store.clear(function (result) {
console.log('clear() call success. Result:', result);
});
}
/* create a store using the wrapper and kick off: */
function createStore_1_1 () {
window['store_1_1'] = new IDBStore({
dbVersion: '1',
storeName: 'testStore_1_1',
keyPath: 'id',
autoIncrement: true,
onStoreReady: function () {
console.log('Store ready, go ahead!');
console.log('The store is accessible at window.store_1_1');
document.getElementById('openWrapper').style.display = 'none';
document.getElementById('dataWrapper').style.display = '';
}
});
}
</script>
</body>
</html>
@@ -1,94 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
.container {
overflow: auto;
padding: 10px;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
#clear {
padding: 10px;
}
@@ -1,87 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
}
.hbox {
display: -webkit-box;
display: -moz-box;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
}
.container {
overflow: auto;
padding: 10px;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
#clear {
padding: 10px;
}
@@ -1,24 +0,0 @@
{
"name": "idb-wrapper",
"version": "0.1.3",
"description": "This is a wrapper for indexedDB.",
"keywords": [],
"author": "jensarps <mail@jensarps.de>",
"repository": "git://github.com/jensarps/IDBWrapper.git",
"main": "IDBStore",
"homepage": "https://github.com/jensarps/IDBWrapper",
"contributors": [],
"bugs": {
"url": "https://github.com/jensarps/IDBWrapper/issues",
"email": "mail@jensarps.de"
},
"dependencies": {},
"devDependencies": {},
"licenses": [
{
"type": "MIT",
"url": "http://github.com/jensarps/IDBWrapper/raw/master/LICENSE"
}
],
"scripts": {}
}
Binary file not shown.
-2
View File
@@ -1,2 +0,0 @@
.project
.idea
-464
View File
@@ -1,464 +0,0 @@
/*
* IDBWrapper - A cross-browser wrapper for IndexedDB
* Copyright (c) 2011 - 2012 Jens Arps
* http://jensarps.de/
*
* Licensed under the MIT (X11) license
*/
"use strict";
(function (name, definition, global) {
if (typeof define === 'function') {
define(definition);
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = definition();
} else {
global[name] = definition();
}
})('IDBStore', function () {
var IDBStore;
var defaults = {
storeName: 'Store',
dbVersion: 1,
keyPath: 'id',
autoIncrement: true,
onStoreReady: function () {
},
indexes: []
};
IDBStore = function (kwArgs, onStoreReady) {
function fixupConstants (object, constants) {
for (var prop in constants) {
if (!(prop in object))
object[prop] = constants[prop];
}
}
for(var key in defaults){
this[key] = typeof kwArgs[key] != 'undefined' ? kwArgs[key] : defaults[key];
}
this.dbName = 'IDBWrapper-' + this.storeName;
this.dbVersion = parseInt(this.dbVersion, 10);
onStoreReady && (this.onStoreReady = onStoreReady);
this.idb = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
this.keyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange;
this.consts = {
'READ_ONLY': 'readonly',
'READ_WRITE': 'readwrite',
'VERSION_CHANGE': 'versionchange'
}
this.cursor = window.IDBCursor || window.webkitIDBCursor;
fixupConstants(this.cursor, {
'NEXT': 'next',
'NEXT_NO_DUPLICATE': 'nextunique',
'PREV': 'prev',
'PREV_NO_DUPLICATE': 'prevunique'
});
this.openDB();
};
IDBStore.prototype = {
db: null,
dbName: null,
dbVersion: null,
store: null,
storeName: null,
keyPath: null,
autoIncrement: null,
indexes: null,
features: null,
onStoreReady: null,
openDB: function () {
this.newVersionAPI = typeof this.idb.setVersion == 'undefined';
if(!this.newVersionAPI){
throw new Error('The IndexedDB implementation in this browser is outdated. Please upgrade your browser.');
}
var features = this.features = {};
features.hasAutoIncrement = !window.mozIndexedDB; // TODO: Still, really?
var openRequest = this.idb.open(this.dbName, this.dbVersion);
openRequest.onerror = function (error) {
var gotVersionErr = false;
if ('error' in error.target) {
gotVersionErr = error.target.error.name == "VersionError";
} else if ('errorCode' in error.target) {
gotVersionErr = error.target.errorCode == 12; // TODO: Use const
}
if (gotVersionErr) {
console.error('Could not open database, version error:', error);
} else {
console.error('Could not open database, error:', error);
}
}.bind(this);
openRequest.onsuccess = function (event) {
if(this.db){
this.onStoreReady();
return;
}
this.db = event.target.result;
if(this.db.objectStoreNames.contains(this.storeName)){
if(!this.store){
var emptyTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
this.store = emptyTransaction.objectStore(this.storeName);
}
// check indexes
this.indexes.forEach(function(indexData){
var indexName = indexData.name;
// normalize and provide existing keys
indexData.keyPath = indexData.keyPath || indexName;
indexData.unique = !!indexData.unique;
indexData.multiEntry = !!indexData.multiEntry;
if(!indexName){
throw new Error('Cannot create index: No index name given.');
}
if(this.hasIndex(indexName)){
// check if it complies
var actualIndex = this.store.index(indexName);
var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
// IE10 returns undefined for no multiEntry
if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
return true;
}
return indexData[key] == actualIndex[key];
});
if(!complies){
throw new Error('Cannot modify index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
} else {
throw new Error('Cannot create new index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
}, this);
this.onStoreReady();
} else {
// We should never get here.
throw new Error('Cannot create a new store for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
}
}.bind(this);
openRequest.onupgradeneeded = function(/* IDBVersionChangeEvent */ event){
this.db = event.target.result;
if(this.db.objectStoreNames.contains(this.storeName)){
this.store = event.target.transaction.objectStore(this.storeName);
} else {
this.store = this.db.createObjectStore(this.storeName, { keyPath: this.keyPath, autoIncrement: this.autoIncrement});
}
this.indexes.forEach(function(indexData){
var indexName = indexData.name;
// normalize and provide existing keys
indexData.keyPath = indexData.keyPath || indexName;
indexData.unique = !!indexData.unique;
indexData.multiEntry = !!indexData.multiEntry;
if(!indexName){
throw new Error('Cannot create index: No index name given.');
}
if(this.hasIndex(indexName)){
// check if it complies
var actualIndex = this.store.index(indexName);
var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
// IE10 returns undefined for no multiEntry
if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
return true;
}
return indexData[key] == actualIndex[key];
});
if(!complies){
// index differs, need to delete and re-create
this.store.deleteIndex(indexName);
this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
}
} else {
this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
}
}, this);
}.bind(this);
},
deleteDatabase: function () {
if (this.idb.deleteDatabase) {
this.idb.deleteDatabase(this.dbName);
}
},
/*********************
* data manipulation *
*********************/
put: function (dataObj, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not write data.', error);
});
onSuccess || (onSuccess = noop);
if (typeof dataObj[this.keyPath] == 'undefined' && !this.features.hasAutoIncrement) {
dataObj[this.keyPath] = this._getUID();
}
var putTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var putRequest = putTransaction.objectStore(this.storeName).put(dataObj);
putRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
putRequest.onerror = onError;
},
get: function (key, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not read data.', error);
});
onSuccess || (onSuccess = noop);
var getTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var getRequest = getTransaction.objectStore(this.storeName).get(key);
getRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
getRequest.onerror = onError;
},
remove: function (key, onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not remove data.', error);
});
onSuccess || (onSuccess = noop);
var removeTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var deleteRequest = removeTransaction.objectStore(this.storeName).delete(key);
deleteRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
deleteRequest.onerror = onError;
},
getAll: function (onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not read data.', error);
});
onSuccess || (onSuccess = noop);
var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var store = getAllTransaction.objectStore(this.storeName);
if (store.getAll) {
var getAllRequest = store.getAll();
getAllRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
getAllRequest.onerror = onError;
} else {
this._getAllCursor(getAllTransaction, onSuccess, onError);
}
},
_getAllCursor: function (tr, onSuccess, onError) {
var all = [];
var store = tr.objectStore(this.storeName);
var cursorRequest = store.openCursor();
cursorRequest.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
all.push(cursor.value);
cursor['continue']();
}
else {
onSuccess(all);
}
};
cursorRequest.onError = onError;
},
clear: function (onSuccess, onError) {
onError || (onError = function (error) {
console.error('Could not clear store.', error);
});
onSuccess || (onSuccess = noop);
var clearTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
var clearRequest = clearTransaction.objectStore(this.storeName).clear();
clearRequest.onsuccess = function (event) {
onSuccess(event.target.result);
};
clearRequest.onerror = onError;
},
_getUID: function () {
// FF bails at times on non-numeric ids. So we take an even
// worse approach now, using current time as id. Sigh.
return +new Date();
},
/************
* indexing *
************/
getIndexList: function () {
return this.store.indexNames;
},
hasIndex: function (indexName) {
return this.store.indexNames.contains(indexName);
},
/**********
* cursor *
**********/
iterate: function (onItem, options) {
options = mixin({
index: null,
order: 'ASC',
filterDuplicates: false,
keyRange: null,
writeAccess: false,
onEnd: null,
onError: function (error) {
console.error('Could not open cursor.', error);
}
}, options || {});
var directionType = options.order.toLowerCase() == 'desc' ? 'prev' : 'next';
if (options.filterDuplicates) {
directionType += 'unique';
}
var cursorTransaction = this.db.transaction([this.storeName], this.consts[options.writeAccess ? 'READ_WRITE' : 'READ_ONLY']);
var cursorTarget = cursorTransaction.objectStore(this.storeName);
if (options.index) {
cursorTarget = cursorTarget.index(options.index);
}
var cursorRequest = cursorTarget.openCursor(options.keyRange, directionType);
cursorRequest.onerror = options.onError;
cursorRequest.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
onItem(cursor.value, cursor, cursorTransaction);
cursor['continue']();
} else {
if(options.onEnd){
options.onEnd()
} else {
onItem(null);
}
}
};
},
count: function (onSuccess, options) {
options = mixin({
index: null,
keyRange: null
}, options || {});
var onError = options.onError || function (error) {
console.error('Could not open cursor.', error);
};
var cursorTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
var cursorTarget = cursorTransaction.objectStore(this.storeName);
if (options.index) {
cursorTarget = cursorTarget.index(options.index);
}
var countRequest = cursorTarget.count(options.keyRange);
countRequest.onsuccess = function (evt) {
onSuccess(evt.target.result);
};
countRequest.onError = function (error) {
onError(error);
};
},
/**************/
/* key ranges */
/**************/
makeKeyRange: function(options){
var keyRange,
hasLower = typeof options.lower != 'undefined',
hasUpper = typeof options.upper != 'undefined';
switch(true){
case hasLower && hasUpper:
keyRange = this.keyRange.bound(options.lower, options.upper, options.excludeLower, options.excludeUpper);
break;
case hasLower:
keyRange = this.keyRange.lowerBound(options.lower, options.excludeLower);
break;
case hasUpper:
keyRange = this.keyRange.upperBound(options.upper, options.excludeUpper);
break;
default:
throw new Error('Cannot create KeyRange. Provide one or both of "lower" or "upper" value.');
break;
}
return keyRange;
}
};
/** helpers **/
var noop = function () {
};
var empty = {};
var mixin = function (target, source) {
var name, s;
for (name in source) {
s = source[name];
if (s !== empty[name] && s !== target[name]) {
target[name] = s;
}
}
return target;
};
return IDBStore;
}, this);
-19
View File
@@ -1,19 +0,0 @@
Copyright (c) 2011 - 2012 Jens Arps
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-313
View File
@@ -1,313 +0,0 @@
About
=====
This is a wrapper for indexedDB. It is meant to
a) ease the use of indexedDB and abstract away the differences between the
existing impls in Chrome, Firefox and IE10 (yes, it works in all three), and
b) show how IDB works. The code is split up into short methods, so that it's
easy to see what happens in what method.
"Showing how it works" is the main intention of this project. IndexedDB is
all the buzz, but only a few people actually know how to use it.
The code in IDBWrapper.js is not optimized for anything, nor minified or anything.
It is meant to be read and easy to understand. So, please, go ahead and check out
the source!
There are two tutorials to get you up and running:
Part 1: Setup and CRUD operations
http://jensarps.de/2011/11/25/working-with-idbwrapper-part-1/
Part 2: Running Queries against the store
http://jensarps.de/2012/11/13/working-with-idbwrapper-part-2/
##November Rewrite
I rewrote IDBWrapper to cope with all the issues, and the new version is on
master since Nov, 13th 2012. The API didn't change much, I just removed some
of the methods. Method signatures remain unchanged.
However, if you have a previous version of IDBWrapper in use, there's an
issue: The new version won't be able to access the store created with the old
version, because database names changed. In that case, you need to manually
migrate the data: Include both versions of IDBWrapper (use a different name for
them), do a getAll() on the old store and write the data to the new store.
I am very sorry about any inconveniences, but there was no other way.
The 'old' version of IDBWrapper is still available in the `legacy` branch:
https://github.com/jensarps/IDBWrapper/tree/legacy
Also, "showing how it works" is no longer the main intention behind this. Now,
it's rather "just works".
Examples
========
There are some examples to run right in your browser over here: http://jensarps.github.com/IDBWrapper/example/
The source for these examples are in the `example` folder of this repository.
Usage
=====
Including the IDBStore.js file will add an IDBStore constructor to the global scope.
Alternatively, you can use an AMD loader such as RequireJS to load the file,
and you will receive the constructor in your load callback (the constructor
will then, of course, have whatever name you call it).
You can then create an IDB store:
```javascript
var myStore = new IDBStore();
```
You may pass two parameters to the constructor: the first is an object with optional parameters,
the second is a function reference to a function that is called when the store is ready to use.
The options object may contain the following properties (default values are shown):
```javascript
{
storeName: 'Store',
dbVersion: 1,
keyPath: 'id',
autoIncrement: true,
indexes: [],
onStoreReady: function(){}
}
```
'keyPath' is the name of the property to be used as key index. If 'autoIncrement' is set to true,
the database will automatically add a unique key to the keyPath index when storing objects missing
that property. 'indexes' contains objects defining indexes (see below for details on indexes).
You can also pass a callback function to the options object. If a callback is provided both as second
parameter and inside of the options object, the function passed as second parameter will be used.
Methods
=======
Here's an overview of available methods in IDBStore:
Data Manipulation
-----------------
Use the following methods to read and write data:
___
1) The put method.
```javascript
put(/*Object*/ dataObj, /*Function?*/onSuccess, /*Function?*/onError)
```
`dataObj` is the Object to store. `onSuccess` will be called when the insertion/update was successful,
and it will receive the keyPath value (the id, so to say) of the inserted object as first and only
argument. `onError` will be called if the insertion/update failed and it will receive the error event
object as first and only argument. If the store already contains an object with the given keyPath id,
it will be overwritten by `dataObj`.
___
2) The get method.
```javascript
get(/*keyPath value*/ key, /*Function?*/onSuccess, /*Function?*/onError)
```
`key` is the keyPath property value (the id) of the object to retrieve. `onSuccess` will be called if
the get operation was successful, and it will receive the stored object as first and only argument. If
no object was found with the given keyPath value, this argument will be null. `onError` will be called
if the get operation failed and it will receive the error event object as first and only argument.
___
3) The getAll method.
```javascript
getAll: function(/*Function?*/onSuccess, /*Function?*/onError)
```
`onSuccess` will be called if the getAll operation was successful, and it will receive an Array of
all objects currently stored in the store as first and only argument. `onError` will be called if
the getAll operation failed and it will receive the error event object as first and only argument.
___
4) The remove method.
```javascript
remove: function(/*keyPath value*/ key, /*Function?*/onSuccess, /*Function?*/onError)
```
`key` is the keyPath property value (the id) of the object to remove. `onSuccess` will be called if
the remove operation was successful, and it _should_ receive `false` as first and only argument if the
object to remove was not found, and `true` if it was found and removed.
NOTE: FF 8 will pass the key to the onSuccess handler, no matter if there is an corresponding object
or not. Chrome 15 will pass `null` if removal was successful, and call the error handler if the object
wasn't found. Chrome 17 will behave as described above.
`onError` will be called if the remove operation failed and it will receive the error event object as first
and only argument.
___
5) The clear method.
```javascript
clear: function(/*Function?*/onSuccess, /*Function?*/onError)
```
`onSuccess` will be called if the clear operation was successful. `onError` will be called if the clear
operation failed and it will receive the error event object as first and only argument.
Index Operations
----------------
To create indexes, you need to pass the index information to the IDBStore()
constructor, for example:
```javascript
{
storeName: 'customers',
dbVersion: 1,
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: function(){},
indexes: [
{ name: 'lastname', keyPath: 'lastname', unique: false, multiEntry: false }
]
}
```
An entry in the index Array is an object containing the following properties:
The `name` property is the identifier of the index. If you want to work with the created index later, this name is used to identify the index. This is the only property that is mandatory.
The `keyPath` property is the name of the property in your stored data that you want to index. If you omit that, IDBWrapper will assume that it is the same as the provided name, and will use this instead.
The `unique` property tells the store whether the indexed property in your data is unique. If you set this to true, it will add a uniqueness constraint to the store which will make it throw if you try to store data that violates that constraint. If you omit that, IDBWrapper will set this to false.
The `multiEntry` property is kinda weird. You can read up on it here: http://www.w3.org/TR/IndexedDB/#dfn-multientry. However, you can live perfectly fine with setting this to false (or just omitting it, this is set to false by default).
If you want to add an index to an existing store, you need to increase the
version number of your store, as adding an index changes the structure of
the database.
To modify an index, modify the object in the indexes Array in the constructor.
Again, you need to increase the version of your store.
In addition, there are still some convenience methods available:
___
1) The hasIndex method.
```javascript
hasIndex: function(/*String*/ indexName)
```
Return true if an index with the given name exists in the store, false if not.
___
2) The getIndexList method.
```javascript
getIndexList: function()
```
Returns a `DOMStringList` with all existing indices.
Running Queries
---------------
To run queries, IDBWrapper provides an `iterate()` method. To create keyRanges,
there is the `makeKeyRange()` method. In addition to these, IDBWrapper comes
with a `count()` method.
___
1) The iterate method.
```javascript
iterate: function(/*Function*/ onItem, /*Object*/ iterateOptions)
```
The `onItem` callback will be called once for every match. It will receive three arguments: the object that matched the query, a reference to the current cursor object (IDBWrapper uses IndexedDB's Cursor internally to iterate), and a reference to the current ongoing transaction.
There's one special situation: if you didn't pass an onEnd handler in the options objects (see below), the onItem handler will be called one extra time when the transaction is over. In this case, it will receive null as only argument. So, to check when the iteration is over and you won't get any more data objects, you can either pass an onEnd handler, or check for null in the onItem handler.
The `iterateOptions` object can contain one or more of the following properties:
The `index` property contains the name of the index to operate on. If you omit this, IDBWrapper will use the store's keyPath as index.
In the `keyRange` property you can pass a keyRange.
The `order` property can be set to 'ASC' or 'DESC', and determines the ordering direction of results. If you omit this, IDBWrapper will use 'ASC'.
The `filterDuplicates` property is an interesting one: If you set this to true (it defaults to false), and have several objects that have the same value in their key, the store will only fetch the first of those. It is not about objects being the same, it's about their key being the same. For example, in the customers database are a couple of guys having 'Smith' as last name. Setting filterDuplicates to true in the above example will make `iterate()` call the onItem callback only for the first of those.
The `writeAccess` property defaults to false. If you need write access to the store during the iteration, you need to set this to true.
In the `onEnd` property you can pass a callback that gets called after the iteration is over and the transaction is closed. It does not receive any arguments.
In the `onError` property you can pass a custom error handler. In case of an error, it will be called and receives the Error object as only argument.
___
2) The makeKeyRange method.
```javascript
iterate: function(/*Object*/ keyRangeOptions)
```
Returns an IDBKeyRange.
The `keyRangeOptions` object must have one or more of the following properties:
`lower`: The lower bound of the range
`excludeLower`: Boolean, whether to exclude the lower bound itself. Default: false
`upper`: The upper bound of the range
`excludeUpper`: Boolean, whether to exclude the upper bound itself. Default: false
___
3) The count method.
```javascript
iterate: function(/*Function*/ onSuccess, /*Object*/ countOptions)
```
The onSuccess receives the result of the count as only argument.
The `countOptions` object may have one or more of the following properties:
index: The name of an index to operate on.
keyRange: A keyRange to use
@@ -1,94 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var tpls = {
row: '<tr><td>{customerid}</td><td><input id="lastname_{customerid}" value="{lastname}"></td><td><input id="firstname_{customerid}" value="{firstname}"></td><td><button onclick="app.deleteItem({customerid});">delete</button><button onclick="app.updateItem({customerid});">update</button></td></tr>',
table: '<table><tr><th>ID</th><th>Last Name</th><th>First Name</th><th></th></tr>{content}</table>'
};
var customers;
var nodeCache = {};
function init(){
// create a store ("table") for the customers
customers = new IDBStore({
storeName: 'customer',
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: refreshTable
});
// create references for some nodes we have to work with
['submit', 'customerid', 'firstname', 'lastname', 'results-container'].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit button.
nodeCache.submit.addEventListener('click', enterData);
}
function refreshTable(){
customers.getAll(listItems);
}
function listItems(data){
var content = '';
data.forEach(function(item){
content += tpls.row.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key];
});
});
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
}
function enterData(){
// read data from inputs…
var data = {};
['customerid','firstname','lastname'].forEach(function(key){
var value = nodeCache[key].value.trim();
if(value.length){
if(key == 'customerid'){ // We want the id to be numeric:
value = parseInt(value, 10);
}
data[key] = value;
}
});
// …and store them away.
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function clearForm(){
['customerid','firstname','lastname'].forEach(function(id){
nodeCache[id].value = '';
});
}
function deleteItem(id){
customers.remove(id, refreshTable);
}
function updateItem(id){
var data = {
customerid: id,
firstname: document.getElementById('firstname_' + id).value.trim(),
lastname: document.getElementById('lastname_' + id).value.trim()
};
customers.put(data, refreshTable);
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
deleteItem: deleteItem,
updateItem: updateItem
};
// go!
init();
});
@@ -1,31 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper Basic CRUD Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper Basic CRUD Example</div>
<div class="hbox flex">
<div class="vbox flex" id="results-container">
QueryResults
</div>
<div class="vbox">
<div id="query"></div>
<div id="input">
<p>
Enter some data to save. As ID, enter a numeric value or leave blank.
</p>
<div><label>ID</label><input id="customerid"></div>
<div><label>First Name</label><input id="firstname"></div>
<div><label>Last Name</label><input id="lastname"></div>
<button id="submit">Enter data</button>
</div>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,86 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
border-right: solid 1px black;
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input {
padding: 10px;
width: 300px;
}
#input div {
padding: 5px;
}
#input label {
display: inline-block;
width: 100px;
}
@@ -1,22 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>IndexedDB Wrapper Examples</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="head">IDBWrapper Examples</div>
<div class="container">
<h1>IDBWrapper Examples</h1>
<p>
There are a couple of examples to try out / look at:
</p>
<ul>
<li><a href="quicktest/">Quicktest</a> - Just a quick test to see if IDB opens and fool around in the console.</li>
<li><a href="basic/">Basic CRUD</a> - A basic CRUD example using an IDB store as fixed table.</li>
<li><a href="objectstore/">ObjectStore</a> - An example to show the difference between a table and an object store.</li>
<li><a href="index/">Index</a> - An example to show how to work with indexes.</li>
</ul>
</div>
</body>
</html>
@@ -1,163 +0,0 @@
require(['../../IDBStore.js'], function(IDBStore){
var tpls = {
row: '<tr><td>{customerid}</td><td>{lastname}</td><td>{firstname}</td><td>{age}</td><td><button onclick="app.deleteItem({customerid});">delete</button></td></tr>',
table: '<table><tr><th>ID</th><th>Last Name</th><th>First Name</th><th>Age</th><th></th></tr>{content}</table>'
};
var customers;
var nodeCache = {};
function init(){
// create a store ("table") for the customers
customers = app.customers = new IDBStore({
dbVersion: 1,
storeName: 'customer-index',
keyPath: 'customerid',
autoIncrement: true,
onStoreReady: refreshTable,
indexes: [
{ name: 'lastname', keyPath: 'lastname', unique: false, multiEntry: false }
]
});
// create references for some nodes we have to work with
[
'submit', 'submitQuery',
'upper', 'lower', 'excludeLower', 'excludeUpper',
'sortOrder', 'index', 'filterDuplicates',
'customerid', 'firstname', 'lastname', 'age',
'results-container'
].forEach(function(id){
nodeCache[id] = document.getElementById(id);
});
// and listen to the form's submit buttons.
nodeCache.submit.addEventListener('click', enterData);
nodeCache.submitQuery.addEventListener('click', runQuery);
}
function refreshTable(){
customers.getAll(listItems);
}
function listItems(data){
var content = '';
data.forEach(function(item){
content += tpls.row.replace(/\{([^\}]+)\}/g, function(_, key){
return item[key];
});
});
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
}
function enterData(){
// read data from inputs…
var data = {};
['customerid','firstname','lastname', 'age'].forEach(function(key){
var value = nodeCache[key].value.trim();
if(value.length){
if(key == 'customerid'){
value = parseInt(value, 10);
}
data[key] = value;
}
});
// …and store them away.
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function clearForm(){
['customerid','firstname','lastname', 'age'].forEach(function(id){
nodeCache[id].value = '';
});
}
function deleteItem(id){
customers.remove(id, refreshTable);
}
function makeRandomEntry(){
var lastnames = ['Smith','Miller','Doe','Frankenstein','Furter'],
firstnames = ['Peter','John','Frank', 'James', 'Jill'];
var entry = {
lastname: lastnames[Math.floor(Math.random()*5)],
firstname: firstnames[Math.floor(Math.random()*4)],
age: Math.floor(Math.random() * (100 - 20)) + 20,
customerid: parseInt( ( "" + ( Date.now() * Math.random() ) ).substring(0, 6), 10)
};
return entry;
}
function addRandomCustomer(){
var data = makeRandomEntry();
customers.put(data, function(){
clearForm();
refreshTable();
});
}
function runQuery(){
var upper = nodeCache.upper.value,
hasUpper = upper != '',
lower = nodeCache.lower.value,
hasLower = lower != '',
indexName = nodeCache.index.value,
sortOrder = nodeCache.sortOrder.value,
filterDuplicates = nodeCache.filterDuplicates.checked,
keyRange,
content = '';
if(hasUpper || hasLower){ // create a keyRange only if bounds are given
var options = {};
if(hasUpper){
options.upper = upper;
options.excludeUpper = nodeCache.excludeUpper.checked;
}
if(hasLower){
options.lower = lower;
options.excludeLower = nodeCache.excludeLower.checked;
}
keyRange = customers.makeKeyRange(options);
}
var onItem = function (item) {
content += tpls.row.replace(/\{([^\}]+)\}/g, function (_, key) {
return item[key];
});
};
var onEnd = function () {
nodeCache['results-container'].innerHTML = tpls.table.replace('{content}', content);
};
customers.iterate(onItem, {
index: indexName,
keyRange: keyRange,
filterDuplicates: filterDuplicates,
order: sortOrder,
onEnd: onEnd
});
}
// export some functions to the outside to
// make the onclick="" attributes work.
window.app = {
deleteItem: deleteItem,
addRandomCustomer: addRandomCustomer
};
// go!
init();
});
@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IDBWrapper Basic Index Example</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
</head>
<body class="vbox">
<div id="head">IDBWrapper Basic Index Example</div>
<div class="hbox flex">
<div class="vbox flex" id="results-container">
QueryResults
</div>
<div id="query">
<h1>Query</h1>
<div>
<label>Index to use</label>
<select id="index">
<option value="" selected>None (use store's keyPath)</option>
<option value="lastname">lastname</option>
</select>
</div>
<div><label>Lower Bound</label><input id="lower"></div>
<div><label>Exclude Lower</label><input type="checkbox" id="excludeLower"></div>
<div><label>Upper Bound</label><input id="upper"></div>
<div><label>Exclude Upper</label><input type="checkbox" id="excludeUpper"></div>
<div>
<label>Sort Order</label>
<select id="sortOrder">
<option value="ASC" selected>Ascending</option>
<option value="DESC">Descending</option>
</select>
</div>
<div><label>Filter Duplicates</label><input type="checkbox" id="filterDuplicates"></div>
<button id="submitQuery">Submit Query</button>
</div>
<div id="input">
<h1>Add data</h1>
<p>
Add a random customer:
<button onclick="app.addRandomCustomer()">Add random customer data
</button>
</p>
<p>
Or, enter customer data below:
</p>
<div><label>Customer ID</label><input id="customerid"></div>
<div><label>First Name</label><input id="firstname"></div>
<div><label>Last Name</label><input id="lastname"></div>
<div><label>Age</label><input id="age"></div>
<button id="submit">Enter data</button>
</div>
</div>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
@@ -1,89 +0,0 @@
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
}
a {
color: black;
text-decoration: none;
}
/* box setup */
.vbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
}
.hbox {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
-ms-flex-direction: row;
}
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-flex: 1;
}
/* head */
#head {
background: -moz-linear-gradient(top, #ffffff 0%, #e5e5e5 100%);
background: -webkit-linear-gradient(top, #ffffff 0%,#e5e5e5 100%);
background: linear-gradient(to bottom, #ffffff, #e5e5e5);
padding: 10px;
border-bottom: solid 1px #9E9E9E;
}
/* table */
#results-container {
padding: 10px;
overflow: auto;
}
#results-container table {
border-collapse: collapse;
}
#results-container th {
border-bottom: solid 1px #808080;
}
#results-container th,
#results-container td {
padding: 2px 5px;
font-size: 14px;
}
#results-container input {
border: none;
border-bottom: solid 1px white;
font-size: 14px;
}
#results-container input:hover,
#results-container input:active {
border-bottom: dotted 1px black;
}
/* input */
#input,
#query {
padding: 10px;
width: 350px;
border-left: solid 1px black;
}
#input div,
#query div{
padding: 5px;
}
#input label,
#query label{
display: inline-block;
width: 120px;
}

Some files were not shown because too many files have changed in this diff Show More