├── debian ├── compat ├── source │ └── format ├── libjs-jstorage.docs ├── libjs-jstorage.dirs ├── changelog ├── libjs-jstorage.links ├── libjs-jstorage.install ├── control ├── rules └── copyright ├── .gitignore ├── component.json ├── tests ├── data.xml ├── index.html └── tests.js ├── package.json ├── LICENSE ├── CONTRIBUTING.md ├── example └── index.html ├── README.md ├── jstorage.min.js └── jstorage.js /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/libjs-jstorage.docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .vscode 4 | -------------------------------------------------------------------------------- /debian/libjs-jstorage.dirs: -------------------------------------------------------------------------------- 1 | /usr/share/javascript/jstorage/ 2 | /usr/share/docs/libjs-jstorage/examples/ 3 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jStorage", 3 | "version": "0.4.13", 4 | "main": "./jstorage.js", 5 | "author": "Andris Reinman ", 6 | "dependencies": {} 7 | } 8 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | libjs-jstorage (0.3.1-1) unstable; urgency=low 2 | 3 | * Initial release, closes: #696432. 4 | 5 | -- Dmitry E. Oboukhov Thu, 20 Dec 2012 20:35:00 +0400 6 | -------------------------------------------------------------------------------- /debian/libjs-jstorage.links: -------------------------------------------------------------------------------- 1 | /usr/share/javascript/jstorage/jstorage.js /usr/share/docs/libjs-jstorage/examples/jstorage.js 2 | /usr/share/javascript/jstorage/jstorage.min.js /usr/share/docs/libjs-jstorage/examples/jstorage.min.js 3 | -------------------------------------------------------------------------------- /tests/data.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pealkiri 6 | Lingi nimi 7 | Kirjeldus 8 | et 9 | 10 | -------------------------------------------------------------------------------- /debian/libjs-jstorage.install: -------------------------------------------------------------------------------- 1 | build/jstorage.min.js /usr/share/javascript/jstorage/ 2 | build/jstorage.js /usr/share/javascript/jstorage/ 3 | test.html /usr/share/doc/libjs-jstorage/examples 4 | build/test.min.html /usr/share/doc/libjs-jstorage/examples 5 | tests /usr/share/doc/libjs-jstorage/examples 6 | build/testrunner.min.html /usr/share/doc/libjs-jstorage/examples/tests/ 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jstorage", 3 | "version": "0.4.13", 4 | "description": "jStorage is a cross-browser key-value store database to store data locally in the browser ", 5 | "keywords": [ 6 | "jStorage" 7 | ], 8 | "homepage": "http://www.jstorage.info", 9 | "bugs": "https://github.com/andris9/jStorage/issues", 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/andris9/jStorage.git" 13 | }, 14 | "main": "./jstorage.js", 15 | "license": "Unlicense", 16 | "devDependencies": { 17 | "bump-version": "^0.5.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jStorage » QUnit test runner 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: libjs-jstorage 2 | Maintainer: Dmitry E. Oboukhov 3 | Section: web 4 | Priority: optional 5 | Build-Depends: debhelper (>= 8), 6 | cdbs, 7 | yui-compressor 8 | Standards-Version: 3.9.3 9 | Homepage: http://www.jstorage.info/ 10 | 11 | Package: libjs-jstorage 12 | Architecture: all 13 | Depends: ${misc:Depends}, ${shlibs:Depends} 14 | Recommends: libjs-jquery, libjs-prototype 15 | Description: store data locally with JavaScript 16 | Storage is a cross-browser key-value store database to store data locally 17 | in the browser - jStorage supports all major browsers, both in desktop 18 | and in mobile. 19 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | include /usr/share/cdbs/1/rules/debhelper.mk 4 | 5 | package := $(shell dpkg-parsechangelog|grep ^Source|awk '{print $$2}') 6 | version := $(shell dpkg-parsechangelog|grep ^Version|awk '{print $$2}' \ 7 | |sed 's/-[[:digit:]]\+$$//') 8 | 9 | clean:: 10 | rm -fr build 11 | 12 | build/stamp: 13 | mkdir -p build 14 | touch $@ 15 | 16 | build/libjs-jstorage:: build/stamp 17 | install -m 0644 jstorage.js build/ 18 | yui-compressor --type js \ 19 | --charset utf-8 \ 20 | --nomunge \ 21 | --disable-optimizations \ 22 | --preserve-semi \ 23 | build/jstorage.js -o build/jstorage.min.js 24 | sed 's/"jstorage.js"/"jstorage.min.js"/' test.html > build/test.min.html 25 | sed 's/jstorage.js"/jstorage.min.js"/' tests/testrunner.html \ 26 | > build/testrunner.min.html 27 | 28 | tarball: clean 29 | test -d ../$(package)-$(version) 30 | cd .. && \ 31 | tar --exclude=.git --exclude=debian \ 32 | -czvf $(package)_$(version).orig.tar.gz \ 33 | $(package)-$(version) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135 2 | Maintainer: Andris Reinman 3 | Source: http://www.jstorage.info/ 4 | Name: jstorage 5 | 6 | Files: debian/* 7 | Copyright: 2012, Dmitry E. Oboukhov 8 | License: MIT 9 | 10 | Files: * 11 | Copyright: 2010-2012, Andris Reinman 12 | License: MIT 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | . 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | I consider jStorage complete, so only bug fixes are accepted but no new features please. jStorage has an extremely 4 | permissive license, you can do whatever you like with the code with no kind of attribution required. If you want a 5 | version of jStorage that has additional features or has some existing features stripped, you can fork or clone the 6 | code of jStorage and treat it as you like. You can even republish it under another name and license if you like, 7 | no constraints about that. 8 | 9 | ## Alternatives 10 | 11 | If you do not need to support IE6 and IE7, you don't even need something like jStorage. You can accomplish all 12 | your storage needs with the folowing simple functions with no dependencies whatsoever 13 | 14 | /** 15 | * Stores a value to the persistent browser storage 16 | * 17 | * @param {String} key The key name of stored object 18 | * @param {Mixed} value A value to be stored, can be anything that is JSON compatible 19 | */ 20 | function store(key, value){ 21 | window.localStorage[key] = JSON.stringify(value); 22 | } 23 | 24 | /** 25 | * Loads a value from the persistent browser storage by a key 26 | * 27 | * @param {String} key The key name of stored object 28 | * @return {Mixed} Stored value, can be anything that is JSON compatible 29 | */ 30 | function retrieve(key){ 31 | var value; 32 | try{ 33 | value = JSON.parse(window.localStorage[key]); 34 | }catch(E){} 35 | return value; 36 | } 37 | 38 | The usage of JSON is required to support storing other values than strings which is the "native" storage type 39 | for using localStorage API. 40 | 41 | ## Formatting 42 | 43 | Use 4 spaces instead of tabs. Commas last. Use double quotes instead of single quotes where possible. 44 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | test( "backend" , function(){ 2 | ok(!!$.jStorage.currentBackend(), $.jStorage.currentBackend()) 3 | }); 4 | 5 | test( "flush/index", function() { 6 | ok($.jStorage.flush()); 7 | $.jStorage.set("test", "value"); 8 | deepEqual($.jStorage.index(), ["test"]); 9 | ok($.jStorage.flush()); 10 | deepEqual($.jStorage.index(), []); 11 | ok(!$.jStorage.get("test")); 12 | }); 13 | 14 | module( "set" ); 15 | 16 | test("missing", function() { 17 | ok($.jStorage.get("test") === null); 18 | $.jStorage.flush(); 19 | }); 20 | 21 | test("use default", function() { 22 | $.jStorage.set("value exists", "value"); 23 | ok($.jStorage.get("no value", "def") === "def"); 24 | ok($.jStorage.get("value exists", "def") === "value"); 25 | $.jStorage.flush(); 26 | }); 27 | 28 | test("string", function() { 29 | ok($.jStorage.set("test", "value") == "value"); 30 | ok($.jStorage.get("test") == "value"); 31 | $.jStorage.flush(); 32 | }); 33 | 34 | test("boolean", function() { 35 | ok($.jStorage.set("test true", true) === true); 36 | ok($.jStorage.get("test true") === true); 37 | ok($.jStorage.set("test false", false) === false); 38 | ok($.jStorage.get("test false") === false); 39 | $.jStorage.flush(); 40 | }); 41 | 42 | test("number", function() { 43 | ok($.jStorage.set("test", 10.01) === 10.01); 44 | ok($.jStorage.get("test") === 10.01); 45 | $.jStorage.flush(); 46 | }); 47 | 48 | test("obejct", function() { 49 | var testObj = {arr:[1,2,3]}; 50 | deepEqual($.jStorage.set("test", testObj), testObj); 51 | deepEqual($.jStorage.get("test"), testObj); 52 | ok($.jStorage.get("test") != testObj); 53 | $.jStorage.flush(); 54 | }); 55 | 56 | asyncTest( "XML", function() { 57 | var xmlhttp; 58 | 59 | expect(3); 60 | 61 | if (window.XMLHttpRequest){ 62 | xmlhttp = new XMLHttpRequest(); 63 | }else{ 64 | xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); 65 | } 66 | 67 | xmlhttp.onreadystatechange=function(){ 68 | if(xmlhttp.readyState==4 && xmlhttp.status==200){ 69 | ok($.jStorage.set("jskey_xml", xmlhttp.responseXML)); 70 | ok($.jStorage.get("jskey_xml") != xmlhttp.responseXML); 71 | ok($.jStorage.get("jskey_xml").getElementsByTagName("title")[0].firstChild.nodeValue == "Pealkiri"); 72 | $.jStorage.flush(); 73 | start(); 74 | } 75 | } 76 | xmlhttp.open("GET","data.xml",true); 77 | xmlhttp.send(); 78 | }); 79 | 80 | asyncTest("TTL", function() { 81 | expect(2); 82 | $.jStorage.set("ttlkey", "value", {TTL:500}); 83 | setTimeout(function(){ 84 | ok($.jStorage.get("ttlkey") == "value"); 85 | setTimeout(function(){ 86 | ok($.jStorage.get("ttlkey") === null); 87 | $.jStorage.flush(); 88 | start(); 89 | }, 500); 90 | }, 250); 91 | }); 92 | 93 | module(); 94 | 95 | asyncTest("setTTL", function() { 96 | expect(2); 97 | $.jStorage.set("ttlkey", "value"); 98 | $.jStorage.setTTL("ttlkey", 500); 99 | setTimeout(function(){ 100 | ok($.jStorage.get("ttlkey") == "value"); 101 | setTimeout(function(){ 102 | ok($.jStorage.get("ttlkey") === null); 103 | $.jStorage.flush(); 104 | start(); 105 | }, 500); 106 | }, 250); 107 | }); 108 | 109 | asyncTest("getTTL", function() { 110 | expect(2); 111 | $.jStorage.set("ttlkey", "value", {TTL: 500}); 112 | setTimeout(function(){ 113 | ok($.jStorage.getTTL("ttlkey") > 0); 114 | setTimeout(function(){ 115 | ok($.jStorage.getTTL("ttlkey") === 0); 116 | $.jStorage.flush(); 117 | start(); 118 | }, 500); 119 | }, 250); 120 | }); 121 | 122 | test("deleteKey", function() { 123 | deepEqual($.jStorage.index(), []); 124 | $.jStorage.set("test", "value"); 125 | deepEqual($.jStorage.index(), ["test"]); 126 | ok($.jStorage.deleteKey("test")); 127 | ok(!$.jStorage.deleteKey("test")); 128 | deepEqual($.jStorage.index(), []); 129 | $.jStorage.flush(); 130 | }); 131 | 132 | asyncTest("publish/subscribe", function() { 133 | expect(2); 134 | $.jStorage.subscribe("testchannel", function(channel, payload){ 135 | ok(channel == "testchannel"); 136 | deepEqual(payload, {arr: [1,2,3]}); 137 | $.jStorage.flush(); 138 | start(); 139 | }); 140 | 141 | setTimeout(function(){ 142 | $.jStorage.publish("testchannel", {arr: [1,2,3]}); 143 | }, 100); 144 | }); 145 | 146 | module("listenKeyChange"); 147 | 148 | asyncTest("specific key - updated", function() { 149 | $.jStorage.listenKeyChange("testkey", function(key, action){ 150 | ok(key == "testkey"); 151 | ok(action == "updated"); 152 | $.jStorage.stopListening("testkey"); 153 | start(); 154 | }); 155 | 156 | setTimeout(function(){ 157 | $.jStorage.set("testkey", "value"); 158 | }, 100); 159 | }); 160 | 161 | asyncTest("specific key - deleted", function() { 162 | $.jStorage.listenKeyChange("testkey", function(key, action){ 163 | ok(key == "testkey"); 164 | ok(action == "deleted"); 165 | $.jStorage.stopListening("testkey"); 166 | $.jStorage.flush(); 167 | start(); 168 | }); 169 | 170 | setTimeout(function(){ 171 | $.jStorage.deleteKey("testkey"); 172 | }, 100); 173 | }); 174 | 175 | asyncTest("all keys - updated", function() { 176 | $.jStorage.listenKeyChange("*", function(key, action){ 177 | ok(key == "testkey"); 178 | ok(action == "updated"); 179 | $.jStorage.stopListening("*"); 180 | start(); 181 | }); 182 | 183 | setTimeout(function(){ 184 | $.jStorage.set("testkey", "value"); 185 | }, 100); 186 | }); 187 | 188 | asyncTest("specific key - deleted", function() { 189 | $.jStorage.listenKeyChange("*", function(key, action){ 190 | ok(key == "testkey"); 191 | ok(action == "deleted"); 192 | $.jStorage.stopListening("*"); 193 | $.jStorage.flush(); 194 | start(); 195 | }); 196 | 197 | setTimeout(function(){ 198 | $.jStorage.deleteKey("testkey"); 199 | }, 100); 200 | }); 201 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jStorage - simple JavaScript plugin to store data locally 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 108 | 109 | 110 | 111 |
112 | 113 |

jStorage - store data locally with JavaScript

114 | 115 |

jStorage is a simple wrapper plugin for Prototype, MooTools and jQuery to cache data on browser side.

116 | 117 |

Add some values and refresh the page - if your browser supports storing local data, then the values should survive the page refresh.

118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 |
KEYVALUETTL (ms) 
ADD
Clicking "GET" alerts the value for provided key with the method $.jStorage.get(key)GET
 Clear all saved dataFLUSH
 Refresh to clear expired keysREFRESH
153 | 154 |

NB! Key "test" has a special meaning - if it is updated or deleted, all open windows of the same page will alert the change.

155 | 156 |

157 | 158 | 159 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## NB! 2 | 3 | > This project is in a frozen state. No more API changes. Pull requests for bug fixes are welcomed, anything else gets most probably ignored. A bug is something that breaks the application, outdated package file is not a bug. 4 | 5 | ---- 6 | 7 | # jStorage 8 | 9 | **jStorage** is a cross-browser key-value store database to store data locally in the browser - jStorage supports all major browsers, both in **desktop** (yes - even Internet Explorer 6) and in **mobile**. 10 | 11 | Additionally jStorage is library agnostic, it works well with any other JavaScript library on the same webpage, be it jQuery, Prototype, MooTools or something else. Though you still need to have either a third party library (Prototype, MooTools) or [JSON2](https://github.com/douglascrockford/JSON-js/blob/master/json2.js) on the page to support older IE versions. 12 | 13 | jStorage supports storing Strings, Numbers, JavaScript objects, Arrays and even native XML nodes which kind of makes it a JSON storage. jStorage also supports setting TTL values for auto expiring stored keys and - best of all - notifying other tabs/windows when a key has been changed, which makes jStorage also a local PubSub platform for web applications. 14 | 15 | jStorage is pretty small, about 7kB when minified, 3kB gzipped. 16 | 17 | ## Function reference 18 | 19 | ### set(key, value[, options]) 20 | 21 | ```javascript 22 | $.jStorage.set(key, value, options) 23 | ``` 24 | 25 | Saves a value to local storage. key needs to be string otherwise an exception is thrown. value can be any JSONeable value, including objects and arrays or a XML node. 26 | Currently XML nodes can't be nested inside other objects: `$.jStorage.set("xml", xml_node)` is OK but `$.jStorage.set("xml", {xml: xml_node})` is not. 27 | 28 | Options is an optional options object. Currently only available option is options.TTL which can be used to set the TTL value to the key `$.jStorage.set(key, value, {TTL: 1000})`. NB - if no TTL option value has been set, any currently used TTL value for the key will be removed. 29 | 30 | ### get(key[, default]) 31 | 32 | ```javascript 33 | value = $.jStorage.get(key) 34 | value = $.jStorage.get(key, "default value") 35 | ``` 36 | 37 | get retrieves the value if key exists, or default if it doesn't. key needs to be string otherwise an exception is thrown. default can be any value. 38 | 39 | ### deleteKey(key) 40 | 41 | ```javascript 42 | $.jStorage.deleteKey(key) 43 | ``` 44 | 45 | Removes a key from the storage. key needs to be string otherwise an exception is thrown. 46 | 47 | ### setTTL(key, ttl) 48 | 49 | ```javascript 50 | $.jStorage.set("mykey", "keyvalue"); 51 | $.jStorage.setTTL("mykey", 3000); // expires in 3 seconds 52 | ``` 53 | 54 | Sets a TTL (in milliseconds) for an existing key. Use 0 or negative value to clear TTL. 55 | 56 | ### getTTL(key) 57 | 58 | ```javascript 59 | ttl = $.jStorage.getTTL("mykey"); // TTL in milliseconds or 0 60 | ``` 61 | 62 | Gets remaining TTL (in milliseconds) for a key or 0 if not TTL has been set. 63 | 64 | ### flush() 65 | 66 | ```javascript 67 | $.jStorage.flush() 68 | ``` 69 | 70 | Clears the cache. 71 | 72 | ### index() 73 | 74 | ```javascript 75 | $.jStorage.index() 76 | ``` 77 | 78 | Returns all the keys currently in use as an array. 79 | 80 | ```javascript 81 | var index = $.jStorage.index(); 82 | console.log(index); // ["key1","key2","key3"] 83 | ``` 84 | 85 | ### storageSize() 86 | 87 | ```javascript 88 | $.jStorage.storageSize() 89 | ``` 90 | 91 | Returns the size of the stored data in bytes 92 | 93 | ### currentBackend() 94 | 95 | ```javascript 96 | $.jStorage.currentBackend() 97 | ``` 98 | 99 | Returns the storage engine currently in use or false if none 100 | 101 | ### reInit() 102 | 103 | ```javascript 104 | $.jStorage.reInit() 105 | ``` 106 | 107 | Reloads the data from browser storage 108 | 109 | ### storageAvailable() 110 | 111 | ```javascript 112 | $.jStorage.storageAvailable() 113 | ``` 114 | 115 | Returns true if storage is available 116 | 117 | ### subscribe(channel, callback) 118 | 119 | ```javascript 120 | $.jStorage.subscribe("ch1", function(channel, payload){ 121 | console.log(payload+ " from " + channel); 122 | }); 123 | ``` 124 | 125 | Subscribes to a Publish/Subscribe channel (see demo) 126 | 127 | ### publish(channel, payload) 128 | 129 | ```javascript 130 | $.jStorage.publish("ch1", "data"); 131 | ``` 132 | 133 | Publishes payload to a Publish/Subscribe channel (see demo) 134 | 135 | ### listenKeyChange(key, callback) 136 | 137 | ```javascript 138 | $.jStorage.listenKeyChange("mykey", function(key, action){ 139 | console.log(key + " has been " + action); 140 | }); 141 | ``` 142 | 143 | Listens for updates for selected key. NB! even updates made in other windows/tabs are reflected, so this feature can also be used for some kind of publish/subscribe service. 144 | 145 | If you want to listen for any key change, use `"*"` as the key name 146 | 147 | ```javascript 148 | $.jStorage.listenKeyChange("*", function(key, action){ 149 | console.log(key + " has been " + action); 150 | }); 151 | ``` 152 | 153 | ### stopListening(key[, callback]) 154 | 155 | ```javascript 156 | $.jStorage.stopListening("mykey"); // cancel all listeners for "mykey" change 157 | ``` 158 | 159 | Stops listening for key change. If callback is set, only the used callback will be cleared, otherwise all listeners will be dropped. 160 | 161 | ## Donate 162 | 163 | Support jStorage development 164 | 165 | [![Donate to author](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DB26KWR2BQX5W) 166 | 167 | ## Features 168 | 169 | jStorage supports the following features: 170 | 171 | * store and retrieve data from browser storage using any JSON compatible data format (+ native XML nodes) 172 | * set TTL values to stored keys for auto expiring 173 | * publish and subscribe to cross-window/tab events 174 | * listen for key changes (update, delete) from the current or any other browser window 175 | * use any browser since IE6, both in desktop and in mobile 176 | 177 | ## Browser support 178 | 179 | Current availability: jStorage supports all major browsers - Internet Explorer 6+, Firefox 2+, 180 | Safari 4+, Chrome 4+, Opera 10.50+ 181 | 182 | If the browser doesn't support data caching, then no exceptions are raised - jStorage can still 183 | be used by the script but nothing is actually stored. 184 | 185 | ## License 186 | 187 | [Unlicense](http://unlicense.org/) Since version 0.4.7 188 | 189 | **MIT** (versions up to 0.4.6) 190 | -------------------------------------------------------------------------------- /jstorage.min.js: -------------------------------------------------------------------------------- 1 | (function(){function C(){var a="{}";if("userDataBehavior"==f){g.load("jStorage");try{a=g.getAttribute("jStorage")}catch(b){}try{r=g.getAttribute("jStorage_update")}catch(c){}h.jStorage=a}D();x();E()}function u(){var a;clearTimeout(F);F=setTimeout(function(){if("localStorage"==f||"globalStorage"==f)a=h.jStorage_update;else if("userDataBehavior"==f){g.load("jStorage");try{a=g.getAttribute("jStorage_update")}catch(b){}}if(a&&a!=r){r=a;var l=p.parse(p.stringify(c.__jstorage_meta.CRC32)),k;C();k=p.parse(p.stringify(c.__jstorage_meta.CRC32)); 2 | var d,n=[],e=[];for(d in l)l.hasOwnProperty(d)&&(k[d]?l[d]!=k[d]&&"2."==String(l[d]).substr(0,2)&&n.push(d):e.push(d));for(d in k)k.hasOwnProperty(d)&&(l[d]||n.push(d));s(n,"updated");s(e,"deleted")}},25)}function s(a,b){a=[].concat(a||[]);var c,k,d,n;if("flushed"==b){a=[];for(c in m)m.hasOwnProperty(c)&&a.push(c);b="deleted"}c=0;for(d=a.length;cA&&(l=b[0],k.unshift(b));for(a=k.length-1;0<=a;a--){b=k[a][1];var d=k[a][2];if(t[b])for(var n=0,e=t[b].length;n>>16)&65535)<<16),f^=f>>>24,f=1540483477*(f&65535)+((1540483477*(f>>>16)&65535)<<16),e=1540483477*(e&65535)+((1540483477*(e>>>16)&65535)<<16)^f,g-=4,++h;switch(g){case 3:e^=(d.charCodeAt(h+2)&255)<<16;case 2:e^= 10 | (d.charCodeAt(h+1)&255)<<8;case 1:e^=d.charCodeAt(h)&255,e=1540483477*(e&65535)+((1540483477*(e>>>16)&65535)<<16)}e^=e>>>13;e=1540483477*(e&65535)+((1540483477*(e>>>16)&65535)<<16);k[a]="2."+((e^e>>>15)>>>0);this.setTTL(a,l.TTL||0);s(a,"updated");return b},get:function(a,b){q(a);return a in c?c[a]&&"object"==typeof c[a]&&c[a]._is_xml?B.decode(c[a].xml):c[a]:"undefined"==typeof b?null:b},deleteKey:function(a){q(a);return a in c?(delete c[a],"object"==typeof c.__jstorage_meta.TTL&&a in c.__jstorage_meta.TTL&& 11 | delete c.__jstorage_meta.TTL[a],delete c.__jstorage_meta.CRC32[a],w(),v(),s(a,"deleted"),!0):!1},setTTL:function(a,b){var l=+new Date;q(a);b=Number(b)||0;return a in c?(c.__jstorage_meta.TTL||(c.__jstorage_meta.TTL={}),0 35 | */ 36 | 37 | /* global ActiveXObject: false */ 38 | /* jshint browser: true */ 39 | 40 | (function() { 41 | 'use strict'; 42 | 43 | var 44 | /* jStorage version */ 45 | JSTORAGE_VERSION = '0.4.13', 46 | 47 | /* detect a dollar object or create one if not found */ 48 | $ = window.jQuery || window.$ || (window.$ = {}), 49 | 50 | /* check for a JSON handling support */ 51 | JSON = { 52 | parse: window.JSON && (window.JSON.parse || window.JSON.decode) || 53 | String.prototype.evalJSON && function(str) { 54 | return String(str).evalJSON(); 55 | } || 56 | $.parseJSON || 57 | $.evalJSON, 58 | stringify: Object.toJSON || 59 | window.JSON && (window.JSON.stringify || window.JSON.encode) || 60 | $.toJSON 61 | }; 62 | 63 | // Break if no JSON support was found 64 | if (typeof JSON.parse !== 'function' || typeof JSON.stringify !== 'function') { 65 | throw new Error('No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page'); 66 | } 67 | 68 | var 69 | /* This is the object, that holds the cached values */ 70 | _storage = { 71 | __jstorage_meta: { 72 | CRC32: {} 73 | } 74 | }, 75 | 76 | /* Actual browser storage (localStorage or globalStorage['domain']) */ 77 | _storage_service = { 78 | jStorage: '{}' 79 | }, 80 | 81 | /* DOM element for older IE versions, holds userData behavior */ 82 | _storage_elm = null, 83 | 84 | /* How much space does the storage take */ 85 | _storage_size = 0, 86 | 87 | /* which backend is currently used */ 88 | _backend = false, 89 | 90 | /* onchange observers */ 91 | _observers = {}, 92 | 93 | /* timeout to wait after onchange event */ 94 | _observer_timeout = false, 95 | 96 | /* last update time */ 97 | _observer_update = 0, 98 | 99 | /* pubsub observers */ 100 | _pubsub_observers = {}, 101 | 102 | /* skip published items older than current timestamp */ 103 | _pubsub_last = +new Date(), 104 | 105 | /* Next check for TTL */ 106 | _ttl_timeout, 107 | 108 | /** 109 | * XML encoding and decoding as XML nodes can't be JSON'ized 110 | * XML nodes are encoded and decoded if the node is the value to be saved 111 | * but not if it's as a property of another object 112 | * Eg. - 113 | * $.jStorage.set('key', xmlNode); // IS OK 114 | * $.jStorage.set('key', {xml: xmlNode}); // NOT OK 115 | */ 116 | _XMLService = { 117 | 118 | /** 119 | * Validates a XML node to be XML 120 | * based on jQuery.isXML function 121 | */ 122 | isXML: function(elm) { 123 | var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement; 124 | return documentElement ? documentElement.nodeName !== 'HTML' : false; 125 | }, 126 | 127 | /** 128 | * Encodes a XML node to string 129 | * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/ 130 | */ 131 | encode: function(xmlNode) { 132 | if (!this.isXML(xmlNode)) { 133 | return false; 134 | } 135 | try { // Mozilla, Webkit, Opera 136 | return new XMLSerializer().serializeToString(xmlNode); 137 | } catch (E1) { 138 | try { // IE 139 | return xmlNode.xml; 140 | } catch (E2) {} 141 | } 142 | return false; 143 | }, 144 | 145 | /** 146 | * Decodes a XML node from string 147 | * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/ 148 | */ 149 | decode: function(xmlString) { 150 | var dom_parser = ('DOMParser' in window && (new DOMParser()).parseFromString) || 151 | (window.ActiveXObject && function(_xmlString) { 152 | var xml_doc = new ActiveXObject('Microsoft.XMLDOM'); 153 | xml_doc.async = 'false'; 154 | xml_doc.loadXML(_xmlString); 155 | return xml_doc; 156 | }), 157 | resultXML; 158 | if (!dom_parser) { 159 | return false; 160 | } 161 | resultXML = dom_parser.call('DOMParser' in window && (new DOMParser()) || window, xmlString, 'text/xml'); 162 | return this.isXML(resultXML) ? resultXML : false; 163 | } 164 | }; 165 | 166 | 167 | ////////////////////////// PRIVATE METHODS //////////////////////// 168 | 169 | /** 170 | * Initialization function. Detects if the browser supports DOM Storage 171 | * or userData behavior and behaves accordingly. 172 | */ 173 | function _init() { 174 | /* Check if browser supports localStorage */ 175 | var localStorageReallyWorks = false; 176 | if ('localStorage' in window) { 177 | try { 178 | window.localStorage.setItem('_tmptest', 'tmpval'); 179 | localStorageReallyWorks = true; 180 | window.localStorage.removeItem('_tmptest'); 181 | } catch (BogusQuotaExceededErrorOnIos5) { 182 | // Thanks be to iOS5 Private Browsing mode which throws 183 | // QUOTA_EXCEEDED_ERRROR DOM Exception 22. 184 | } 185 | } 186 | 187 | if (localStorageReallyWorks) { 188 | try { 189 | if (window.localStorage) { 190 | _storage_service = window.localStorage; 191 | _backend = 'localStorage'; 192 | _observer_update = _storage_service.jStorage_update; 193 | } 194 | } catch (E3) { /* Firefox fails when touching localStorage and cookies are disabled */ } 195 | } 196 | /* Check if browser supports globalStorage */ 197 | else if ('globalStorage' in window) { 198 | try { 199 | if (window.globalStorage) { 200 | if (window.location.hostname == 'localhost') { 201 | _storage_service = window.globalStorage['localhost.localdomain']; 202 | } else { 203 | _storage_service = window.globalStorage[window.location.hostname]; 204 | } 205 | _backend = 'globalStorage'; 206 | _observer_update = _storage_service.jStorage_update; 207 | } 208 | } catch (E4) { /* Firefox fails when touching localStorage and cookies are disabled */ } 209 | } 210 | /* Check if browser supports userData behavior */ 211 | else { 212 | _storage_elm = document.createElement('link'); 213 | if (_storage_elm.addBehavior) { 214 | 215 | /* Use a DOM element to act as userData storage */ 216 | _storage_elm.style.behavior = 'url(#default#userData)'; 217 | 218 | /* userData element needs to be inserted into the DOM! */ 219 | document.getElementsByTagName('head')[0].appendChild(_storage_elm); 220 | 221 | try { 222 | _storage_elm.load('jStorage'); 223 | } catch (E) { 224 | // try to reset cache 225 | _storage_elm.setAttribute('jStorage', '{}'); 226 | _storage_elm.save('jStorage'); 227 | _storage_elm.load('jStorage'); 228 | } 229 | 230 | var data = '{}'; 231 | try { 232 | data = _storage_elm.getAttribute('jStorage'); 233 | } catch (E5) {} 234 | 235 | try { 236 | _observer_update = _storage_elm.getAttribute('jStorage_update'); 237 | } catch (E6) {} 238 | 239 | _storage_service.jStorage = data; 240 | _backend = 'userDataBehavior'; 241 | } else { 242 | _storage_elm = null; 243 | return; 244 | } 245 | } 246 | 247 | // Load data from storage 248 | _load_storage(); 249 | 250 | // remove dead keys 251 | _handleTTL(); 252 | 253 | // start listening for changes 254 | _setupObserver(); 255 | 256 | // initialize publish-subscribe service 257 | _handlePubSub(); 258 | 259 | // handle cached navigation 260 | if ('addEventListener' in window) { 261 | window.addEventListener('pageshow', function(event) { 262 | if (event.persisted) { 263 | _storageObserver(); 264 | } 265 | }, false); 266 | } 267 | } 268 | 269 | /** 270 | * Reload data from storage when needed 271 | */ 272 | function _reloadData() { 273 | var data = '{}'; 274 | 275 | if (_backend == 'userDataBehavior') { 276 | _storage_elm.load('jStorage'); 277 | 278 | try { 279 | data = _storage_elm.getAttribute('jStorage'); 280 | } catch (E5) {} 281 | 282 | try { 283 | _observer_update = _storage_elm.getAttribute('jStorage_update'); 284 | } catch (E6) {} 285 | 286 | _storage_service.jStorage = data; 287 | } 288 | 289 | _load_storage(); 290 | 291 | // remove dead keys 292 | _handleTTL(); 293 | 294 | _handlePubSub(); 295 | } 296 | 297 | /** 298 | * Sets up a storage change observer 299 | */ 300 | function _setupObserver() { 301 | if (_backend == 'localStorage' || _backend == 'globalStorage') { 302 | if ('addEventListener' in window) { 303 | window.addEventListener('storage', _storageObserver, false); 304 | } else { 305 | document.attachEvent('onstorage', _storageObserver); 306 | } 307 | } else if (_backend == 'userDataBehavior') { 308 | setInterval(_storageObserver, 1000); 309 | } 310 | } 311 | 312 | /** 313 | * Fired on any kind of data change, needs to check if anything has 314 | * really been changed 315 | */ 316 | function _storageObserver() { 317 | var updateTime; 318 | // cumulate change notifications with timeout 319 | clearTimeout(_observer_timeout); 320 | _observer_timeout = setTimeout(function() { 321 | 322 | if (_backend == 'localStorage' || _backend == 'globalStorage') { 323 | updateTime = _storage_service.jStorage_update; 324 | } else if (_backend == 'userDataBehavior') { 325 | _storage_elm.load('jStorage'); 326 | try { 327 | updateTime = _storage_elm.getAttribute('jStorage_update'); 328 | } catch (E5) {} 329 | } 330 | 331 | if (updateTime && updateTime != _observer_update) { 332 | _observer_update = updateTime; 333 | _checkUpdatedKeys(); 334 | } 335 | 336 | }, 25); 337 | } 338 | 339 | /** 340 | * Reloads the data and checks if any keys are changed 341 | */ 342 | function _checkUpdatedKeys() { 343 | var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)), 344 | newCrc32List; 345 | 346 | _reloadData(); 347 | newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)); 348 | 349 | var key, 350 | updated = [], 351 | removed = []; 352 | 353 | for (key in oldCrc32List) { 354 | if (oldCrc32List.hasOwnProperty(key)) { 355 | if (!newCrc32List[key]) { 356 | removed.push(key); 357 | continue; 358 | } 359 | if (oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0, 2) == '2.') { 360 | updated.push(key); 361 | } 362 | } 363 | } 364 | 365 | for (key in newCrc32List) { 366 | if (newCrc32List.hasOwnProperty(key)) { 367 | if (!oldCrc32List[key]) { 368 | updated.push(key); 369 | } 370 | } 371 | } 372 | 373 | _fireObservers(updated, 'updated'); 374 | _fireObservers(removed, 'deleted'); 375 | } 376 | 377 | /** 378 | * Fires observers for updated keys 379 | * 380 | * @param {Array|String} keys Array of key names or a key 381 | * @param {String} action What happened with the value (updated, deleted, flushed) 382 | */ 383 | function _fireObservers(keys, action) { 384 | keys = [].concat(keys || []); 385 | 386 | var i, j, len, jlen; 387 | 388 | if (action == 'flushed') { 389 | keys = []; 390 | for (var key in _observers) { 391 | if (_observers.hasOwnProperty(key)) { 392 | keys.push(key); 393 | } 394 | } 395 | action = 'deleted'; 396 | } 397 | for (i = 0, len = keys.length; i < len; i++) { 398 | if (_observers[keys[i]]) { 399 | for (j = 0, jlen = _observers[keys[i]].length; j < jlen; j++) { 400 | _observers[keys[i]][j](keys[i], action); 401 | } 402 | } 403 | if (_observers['*']) { 404 | for (j = 0, jlen = _observers['*'].length; j < jlen; j++) { 405 | _observers['*'][j](keys[i], action); 406 | } 407 | } 408 | } 409 | } 410 | 411 | /** 412 | * Publishes key change to listeners 413 | */ 414 | function _publishChange() { 415 | var updateTime = (+new Date()).toString(); 416 | 417 | if (_backend == 'localStorage' || _backend == 'globalStorage') { 418 | try { 419 | _storage_service.jStorage_update = updateTime; 420 | } catch (E8) { 421 | // safari private mode has been enabled after the jStorage initialization 422 | _backend = false; 423 | } 424 | } else if (_backend == 'userDataBehavior') { 425 | _storage_elm.setAttribute('jStorage_update', updateTime); 426 | _storage_elm.save('jStorage'); 427 | } 428 | 429 | _storageObserver(); 430 | } 431 | 432 | /** 433 | * Loads the data from the storage based on the supported mechanism 434 | */ 435 | function _load_storage() { 436 | /* if jStorage string is retrieved, then decode it */ 437 | if (_storage_service.jStorage) { 438 | try { 439 | _storage = JSON.parse(String(_storage_service.jStorage)); 440 | } catch (E6) { 441 | _storage_service.jStorage = '{}'; 442 | } 443 | } else { 444 | _storage_service.jStorage = '{}'; 445 | } 446 | _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0; 447 | 448 | if (!_storage.__jstorage_meta) { 449 | _storage.__jstorage_meta = {}; 450 | } 451 | if (!_storage.__jstorage_meta.CRC32) { 452 | _storage.__jstorage_meta.CRC32 = {}; 453 | } 454 | } 455 | 456 | /** 457 | * This functions provides the 'save' mechanism to store the jStorage object 458 | */ 459 | function _save() { 460 | _dropOldEvents(); // remove expired events 461 | try { 462 | _storage_service.jStorage = JSON.stringify(_storage); 463 | // If userData is used as the storage engine, additional 464 | if (_storage_elm) { 465 | _storage_elm.setAttribute('jStorage', _storage_service.jStorage); 466 | _storage_elm.save('jStorage'); 467 | } 468 | _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0; 469 | } catch (E7) { /* probably cache is full, nothing is saved this way*/ } 470 | } 471 | 472 | /** 473 | * Function checks if a key is set and is string or numberic 474 | * 475 | * @param {String} key Key name 476 | */ 477 | function _checkKey(key) { 478 | if (typeof key != 'string' && typeof key != 'number') { 479 | throw new TypeError('Key name must be string or numeric'); 480 | } 481 | if (key == '__jstorage_meta') { 482 | throw new TypeError('Reserved key name'); 483 | } 484 | return true; 485 | } 486 | 487 | /** 488 | * Removes expired keys 489 | */ 490 | function _handleTTL() { 491 | var curtime, i, TTL, CRC32, nextExpire = Infinity, 492 | changed = false, 493 | deleted = []; 494 | 495 | clearTimeout(_ttl_timeout); 496 | 497 | if (!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != 'object') { 498 | // nothing to do here 499 | return; 500 | } 501 | 502 | curtime = +new Date(); 503 | TTL = _storage.__jstorage_meta.TTL; 504 | 505 | CRC32 = _storage.__jstorage_meta.CRC32; 506 | for (i in TTL) { 507 | if (TTL.hasOwnProperty(i)) { 508 | if (TTL[i] <= curtime) { 509 | delete TTL[i]; 510 | delete CRC32[i]; 511 | delete _storage[i]; 512 | changed = true; 513 | deleted.push(i); 514 | } else if (TTL[i] < nextExpire) { 515 | nextExpire = TTL[i]; 516 | } 517 | } 518 | } 519 | 520 | // set next check 521 | if (nextExpire != Infinity) { 522 | _ttl_timeout = setTimeout(_handleTTL, Math.min(nextExpire - curtime, 0x7FFFFFFF)); 523 | } 524 | 525 | // save changes 526 | if (changed) { 527 | _save(); 528 | _publishChange(); 529 | _fireObservers(deleted, 'deleted'); 530 | } 531 | } 532 | 533 | /** 534 | * Checks if there's any events on hold to be fired to listeners 535 | */ 536 | function _handlePubSub() { 537 | var i, len; 538 | if (!_storage.__jstorage_meta.PubSub) { 539 | return; 540 | } 541 | var pubelm, 542 | _pubsubCurrent = _pubsub_last, 543 | needFired = []; 544 | 545 | for (i = len = _storage.__jstorage_meta.PubSub.length - 1; i >= 0; i--) { 546 | pubelm = _storage.__jstorage_meta.PubSub[i]; 547 | if (pubelm[0] > _pubsub_last) { 548 | _pubsubCurrent = pubelm[0]; 549 | needFired.unshift(pubelm); 550 | } 551 | } 552 | 553 | for (i = needFired.length - 1; i >= 0; i--) { 554 | _fireSubscribers(needFired[i][1], needFired[i][2]); 555 | } 556 | 557 | _pubsub_last = _pubsubCurrent; 558 | } 559 | 560 | /** 561 | * Fires all subscriber listeners for a pubsub channel 562 | * 563 | * @param {String} channel Channel name 564 | * @param {Mixed} payload Payload data to deliver 565 | */ 566 | function _fireSubscribers(channel, payload) { 567 | if (_pubsub_observers[channel]) { 568 | for (var i = 0, len = _pubsub_observers[channel].length; i < len; i++) { 569 | // send immutable data that can't be modified by listeners 570 | try { 571 | _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload))); 572 | } catch (E) {} 573 | } 574 | } 575 | } 576 | 577 | /** 578 | * Remove old events from the publish stream (at least 2sec old) 579 | */ 580 | function _dropOldEvents() { 581 | if (!_storage.__jstorage_meta.PubSub) { 582 | return; 583 | } 584 | 585 | var retire = +new Date() - 2000; 586 | 587 | for (var i = 0, len = _storage.__jstorage_meta.PubSub.length; i < len; i++) { 588 | if (_storage.__jstorage_meta.PubSub[i][0] <= retire) { 589 | // deleteCount is needed for IE6 590 | _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i); 591 | break; 592 | } 593 | } 594 | 595 | if (!_storage.__jstorage_meta.PubSub.length) { 596 | delete _storage.__jstorage_meta.PubSub; 597 | } 598 | 599 | } 600 | 601 | /** 602 | * Publish payload to a channel 603 | * 604 | * @param {String} channel Channel name 605 | * @param {Mixed} payload Payload to send to the subscribers 606 | */ 607 | function _publish(channel, payload) { 608 | if (!_storage.__jstorage_meta) { 609 | _storage.__jstorage_meta = {}; 610 | } 611 | if (!_storage.__jstorage_meta.PubSub) { 612 | _storage.__jstorage_meta.PubSub = []; 613 | } 614 | 615 | _storage.__jstorage_meta.PubSub.unshift([+new Date(), channel, payload]); 616 | 617 | _save(); 618 | _publishChange(); 619 | } 620 | 621 | 622 | /** 623 | * JS Implementation of MurmurHash2 624 | * 625 | * SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed) 626 | * 627 | * @author Gary Court 628 | * @see http://github.com/garycourt/murmurhash-js 629 | * @author Austin Appleby 630 | * @see http://sites.google.com/site/murmurhash/ 631 | * 632 | * @param {string} str ASCII only 633 | * @param {number} seed Positive integer only 634 | * @return {number} 32-bit positive integer hash 635 | */ 636 | 637 | function murmurhash2_32_gc(str, seed) { 638 | var 639 | l = str.length, 640 | h = seed ^ l, 641 | i = 0, 642 | k; 643 | 644 | while (l >= 4) { 645 | k = 646 | ((str.charCodeAt(i) & 0xff)) | 647 | ((str.charCodeAt(++i) & 0xff) << 8) | 648 | ((str.charCodeAt(++i) & 0xff) << 16) | 649 | ((str.charCodeAt(++i) & 0xff) << 24); 650 | 651 | k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)); 652 | k ^= k >>> 24; 653 | k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)); 654 | 655 | h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k; 656 | 657 | l -= 4; 658 | ++i; 659 | } 660 | 661 | switch (l) { 662 | case 3: 663 | h ^= (str.charCodeAt(i + 2) & 0xff) << 16; 664 | /* falls through */ 665 | case 2: 666 | h ^= (str.charCodeAt(i + 1) & 0xff) << 8; 667 | /* falls through */ 668 | case 1: 669 | h ^= (str.charCodeAt(i) & 0xff); 670 | h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)); 671 | } 672 | 673 | h ^= h >>> 13; 674 | h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)); 675 | h ^= h >>> 15; 676 | 677 | return h >>> 0; 678 | } 679 | 680 | ////////////////////////// PUBLIC INTERFACE ///////////////////////// 681 | 682 | $.jStorage = { 683 | /* Version number */ 684 | version: JSTORAGE_VERSION, 685 | 686 | /** 687 | * Sets a key's value. 688 | * 689 | * @param {String} key Key to set. If this value is not set or not 690 | * a string an exception is raised. 691 | * @param {Mixed} value Value to set. This can be any value that is JSON 692 | * compatible (Numbers, Strings, Objects etc.). 693 | * @param {Object} [options] - possible options to use 694 | * @param {Number} [options.TTL] - optional TTL value, in milliseconds 695 | * @return {Mixed} the used value 696 | */ 697 | set: function(key, value, options) { 698 | _checkKey(key); 699 | 700 | options = options || {}; 701 | 702 | // undefined values are deleted automatically 703 | if (typeof value == 'undefined') { 704 | this.deleteKey(key); 705 | return value; 706 | } 707 | 708 | if (_XMLService.isXML(value)) { 709 | value = { 710 | _is_xml: true, 711 | xml: _XMLService.encode(value) 712 | }; 713 | } else if (typeof value == 'function') { 714 | return undefined; // functions can't be saved! 715 | } else if (value && typeof value == 'object') { 716 | // clone the object before saving to _storage tree 717 | value = JSON.parse(JSON.stringify(value)); 718 | } 719 | 720 | _storage[key] = value; 721 | 722 | _storage.__jstorage_meta.CRC32[key] = '2.' + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c); 723 | 724 | this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange 725 | 726 | _fireObservers(key, 'updated'); 727 | return value; 728 | }, 729 | 730 | /** 731 | * Looks up a key in cache 732 | * 733 | * @param {String} key - Key to look up. 734 | * @param {mixed} def - Default value to return, if key didn't exist. 735 | * @return {Mixed} the key value, default value or null 736 | */ 737 | get: function(key, def) { 738 | _checkKey(key); 739 | if (key in _storage) { 740 | if (_storage[key] && typeof _storage[key] == 'object' && _storage[key]._is_xml) { 741 | return _XMLService.decode(_storage[key].xml); 742 | } else { 743 | return _storage[key]; 744 | } 745 | } 746 | return typeof(def) == 'undefined' ? null : def; 747 | }, 748 | 749 | /** 750 | * Deletes a key from cache. 751 | * 752 | * @param {String} key - Key to delete. 753 | * @return {Boolean} true if key existed or false if it didn't 754 | */ 755 | deleteKey: function(key) { 756 | _checkKey(key); 757 | if (key in _storage) { 758 | delete _storage[key]; 759 | // remove from TTL list 760 | if (typeof _storage.__jstorage_meta.TTL == 'object' && 761 | key in _storage.__jstorage_meta.TTL) { 762 | delete _storage.__jstorage_meta.TTL[key]; 763 | } 764 | 765 | delete _storage.__jstorage_meta.CRC32[key]; 766 | 767 | _save(); 768 | _publishChange(); 769 | _fireObservers(key, 'deleted'); 770 | return true; 771 | } 772 | return false; 773 | }, 774 | 775 | /** 776 | * Sets a TTL for a key, or remove it if ttl value is 0 or below 777 | * 778 | * @param {String} key - key to set the TTL for 779 | * @param {Number} ttl - TTL timeout in milliseconds 780 | * @return {Boolean} true if key existed or false if it didn't 781 | */ 782 | setTTL: function(key, ttl) { 783 | var curtime = +new Date(); 784 | _checkKey(key); 785 | ttl = Number(ttl) || 0; 786 | if (key in _storage) { 787 | 788 | if (!_storage.__jstorage_meta.TTL) { 789 | _storage.__jstorage_meta.TTL = {}; 790 | } 791 | 792 | // Set TTL value for the key 793 | if (ttl > 0) { 794 | _storage.__jstorage_meta.TTL[key] = curtime + ttl; 795 | } else { 796 | delete _storage.__jstorage_meta.TTL[key]; 797 | } 798 | 799 | _save(); 800 | 801 | _handleTTL(); 802 | 803 | _publishChange(); 804 | return true; 805 | } 806 | return false; 807 | }, 808 | 809 | /** 810 | * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set 811 | * 812 | * @param {String} key Key to check 813 | * @return {Number} Remaining TTL in milliseconds 814 | */ 815 | getTTL: function(key) { 816 | var curtime = +new Date(), 817 | ttl; 818 | _checkKey(key); 819 | if (key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]) { 820 | ttl = _storage.__jstorage_meta.TTL[key] - curtime; 821 | return ttl || 0; 822 | } 823 | return 0; 824 | }, 825 | 826 | /** 827 | * Deletes everything in cache. 828 | * 829 | * @return {Boolean} Always true 830 | */ 831 | flush: function() { 832 | _storage = { 833 | __jstorage_meta: { 834 | CRC32: {} 835 | } 836 | }; 837 | _save(); 838 | _publishChange(); 839 | _fireObservers(null, 'flushed'); 840 | return true; 841 | }, 842 | 843 | /** 844 | * Returns a read-only copy of _storage 845 | * 846 | * @return {Object} Read-only copy of _storage 847 | */ 848 | storageObj: function() { 849 | function F() {} 850 | F.prototype = _storage; 851 | return new F(); 852 | }, 853 | 854 | /** 855 | * Returns an index of all used keys as an array 856 | * ['key1', 'key2',..'keyN'] 857 | * 858 | * @return {Array} Used keys 859 | */ 860 | index: function() { 861 | var index = [], 862 | i; 863 | for (i in _storage) { 864 | if (_storage.hasOwnProperty(i) && i != '__jstorage_meta') { 865 | index.push(i); 866 | } 867 | } 868 | return index; 869 | }, 870 | 871 | /** 872 | * How much space in bytes does the storage take? 873 | * 874 | * @return {Number} Storage size in chars (not the same as in bytes, 875 | * since some chars may take several bytes) 876 | */ 877 | storageSize: function() { 878 | return _storage_size; 879 | }, 880 | 881 | /** 882 | * Which backend is currently in use? 883 | * 884 | * @return {String} Backend name 885 | */ 886 | currentBackend: function() { 887 | return _backend; 888 | }, 889 | 890 | /** 891 | * Test if storage is available 892 | * 893 | * @return {Boolean} True if storage can be used 894 | */ 895 | storageAvailable: function() { 896 | return !!_backend; 897 | }, 898 | 899 | /** 900 | * Register change listeners 901 | * 902 | * @param {String} key Key name 903 | * @param {Function} callback Function to run when the key changes 904 | */ 905 | listenKeyChange: function(key, callback) { 906 | _checkKey(key); 907 | if (!_observers[key]) { 908 | _observers[key] = []; 909 | } 910 | _observers[key].push(callback); 911 | }, 912 | 913 | /** 914 | * Remove change listeners 915 | * 916 | * @param {String} key Key name to unregister listeners against 917 | * @param {Function} [callback] If set, unregister the callback, if not - unregister all 918 | */ 919 | stopListening: function(key, callback) { 920 | _checkKey(key); 921 | 922 | if (!_observers[key]) { 923 | return; 924 | } 925 | 926 | if (!callback) { 927 | delete _observers[key]; 928 | return; 929 | } 930 | 931 | for (var i = _observers[key].length - 1; i >= 0; i--) { 932 | if (_observers[key][i] == callback) { 933 | _observers[key].splice(i, 1); 934 | } 935 | } 936 | }, 937 | 938 | /** 939 | * Subscribe to a Publish/Subscribe event stream 940 | * 941 | * @param {String} channel Channel name 942 | * @param {Function} callback Function to run when the something is published to the channel 943 | */ 944 | subscribe: function(channel, callback) { 945 | channel = (channel || '').toString(); 946 | if (!channel) { 947 | throw new TypeError('Channel not defined'); 948 | } 949 | if (!_pubsub_observers[channel]) { 950 | _pubsub_observers[channel] = []; 951 | } 952 | _pubsub_observers[channel].push(callback); 953 | }, 954 | 955 | /** 956 | * Publish data to an event stream 957 | * 958 | * @param {String} channel Channel name 959 | * @param {Mixed} payload Payload to deliver 960 | */ 961 | publish: function(channel, payload) { 962 | channel = (channel || '').toString(); 963 | if (!channel) { 964 | throw new TypeError('Channel not defined'); 965 | } 966 | 967 | _publish(channel, payload); 968 | }, 969 | 970 | /** 971 | * Reloads the data from browser storage 972 | */ 973 | reInit: function() { 974 | _reloadData(); 975 | }, 976 | 977 | /** 978 | * Removes reference from global objects and saves it as jStorage 979 | * 980 | * @param {Boolean} option if needed to save object as simple 'jStorage' in windows context 981 | */ 982 | noConflict: function(saveInGlobal) { 983 | delete window.$.jStorage; 984 | 985 | if (saveInGlobal) { 986 | window.jStorage = this; 987 | } 988 | 989 | return this; 990 | } 991 | }; 992 | 993 | // Initialize jStorage 994 | _init(); 995 | 996 | })(); --------------------------------------------------------------------------------