93 |
94 |
95 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2007 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 |
18 |
19 |
20 |
21 | import wsgiref.handlers
22 | from google.appengine.ext import webapp
23 | import os
24 | from google.appengine.ext.webapp import template
25 |
26 |
27 | class MainHandler(webapp.RequestHandler):
28 |
29 | def get(self):
30 | template_values = {}
31 | path = os.path.join(os.path.dirname(__file__), 'views/main.html')
32 | self.response.out.write(template.render(path, template_values))
33 |
34 |
35 | class AssTestHandler(webapp.RequestHandler):
36 |
37 | def get(self):
38 | template_values = {}
39 | path = os.path.join(os.path.dirname(__file__), 'views/test2.html')
40 | self.response.out.write(template.render(path, template_values))
41 |
42 | class PubSubHandler(webapp.RequestHandler):
43 |
44 | def get(self):
45 | template_values = {}
46 | path = os.path.join(os.path.dirname(__file__), 'views/pubsub.html')
47 | self.response.out.write(template.render(path, template_values))
48 |
49 | class IframeHandler(webapp.RequestHandler):
50 |
51 | def get(self):
52 | template_values = {}
53 | path = os.path.join(os.path.dirname(__file__), 'views/iframe.html')
54 | self.response.out.write(template.render(path, template_values))
55 |
56 | class RIIframeHandler(webapp.RequestHandler):
57 |
58 | def get(self):
59 | template_values = {}
60 | path = os.path.join(os.path.dirname(__file__), 'views/riiframe.html')
61 | self.response.out.write(template.render(path, template_values))
62 |
63 | class TestHandler(webapp.RequestHandler):
64 |
65 | def get(self):
66 | type = self.request.get("type", "prototype")
67 | if type=="prototype":
68 | curlib = "Prototype 1.6.1"
69 | if type=="mootools":
70 | curlib = "MooTools 1.2.4"
71 | if type=="jquery":
72 | curlib = "jQuery 1.4.2 + jquery-json 2.1"
73 |
74 | template_values = {
75 | "type": type,
76 | "curlib": curlib
77 | }
78 | path = os.path.join(os.path.dirname(__file__), 'views/test.html')
79 | self.response.out.write(template.render(path, template_values))
80 |
81 |
82 | def main():
83 | application = webapp.WSGIApplication([('/', MainHandler),
84 | ('/test', TestHandler),
85 | ('/objtest', AssTestHandler),
86 | ('/iframe', IframeHandler),
87 | ('/pubsub', PubSubHandler),
88 | ('/riiframe', RIIframeHandler)],
89 | debug=True)
90 | wsgiref.handlers.CGIHandler().run(application)
91 |
92 |
93 | if __name__ == '__main__':
94 | main()
95 |
--------------------------------------------------------------------------------
/views/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
58 |
59 | {% ifequal type "mootools" %}
60 |
61 | {% endifequal %}{% ifequal type "jquery" %}
62 |
63 |
64 | {% endifequal %}{% ifequal type "prototype" %}
65 |
66 | {% endifequal %}
67 |
68 |
69 |
70 |
128 |
129 |
130 |
134 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
Interactive test
145 |
Add some values and refresh the page - if your browser supports storing local data, then the values should survive the page refresh.
146 |
147 |
Currently using: {{curlib}}
148 | Click to change: Prototype, MooTools or jQuery
149 |
150 |
170 |
171 |
172 |
--------------------------------------------------------------------------------
/static/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 |
--------------------------------------------------------------------------------
/static/jquery-json.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery JSON Plugin
3 | * version: 2.1 (2009-08-14)
4 | *
5 | * This document is licensed as free software under the terms of the
6 | * MIT License: http://www.opensource.org/licenses/mit-license.php
7 | *
8 | * Brantley Harris wrote this plugin. It is based somewhat on the JSON.org
9 | * website's http://www.json.org/json2.js, which proclaims:
10 | * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that
11 | * I uphold.
12 | *
13 | * It is also influenced heavily by MochiKit's serializeJSON, which is
14 | * copyrighted 2005 by Bob Ippolito.
15 | */
16 |
17 | (function($) {
18 | /** jQuery.toJSON( json-serializble )
19 | Converts the given argument into a JSON respresentation.
20 |
21 | If an object has a "toJSON" function, that will be used to get the representation.
22 | Non-integer/string keys are skipped in the object, as are keys that point to a function.
23 |
24 | json-serializble:
25 | The *thing* to be converted.
26 | **/
27 | $.toJSON = function(o)
28 | {
29 | if (typeof(JSON) == 'object' && JSON.stringify)
30 | return JSON.stringify(o);
31 |
32 | var type = typeof(o);
33 |
34 | if (o === null)
35 | return "null";
36 |
37 | if (type == "undefined")
38 | return undefined;
39 |
40 | if (type == "number" || type == "boolean")
41 | return o + "";
42 |
43 | if (type == "string")
44 | return $.quoteString(o);
45 |
46 | if (type == 'object')
47 | {
48 | if (typeof o.toJSON == "function")
49 | return $.toJSON( o.toJSON() );
50 |
51 | if (o.constructor === Date)
52 | {
53 | var month = o.getUTCMonth() + 1;
54 | if (month < 10) month = '0' + month;
55 |
56 | var day = o.getUTCDate();
57 | if (day < 10) day = '0' + day;
58 |
59 | var year = o.getUTCFullYear();
60 |
61 | var hours = o.getUTCHours();
62 | if (hours < 10) hours = '0' + hours;
63 |
64 | var minutes = o.getUTCMinutes();
65 | if (minutes < 10) minutes = '0' + minutes;
66 |
67 | var seconds = o.getUTCSeconds();
68 | if (seconds < 10) seconds = '0' + seconds;
69 |
70 | var milli = o.getUTCMilliseconds();
71 | if (milli < 100) milli = '0' + milli;
72 | if (milli < 10) milli = '0' + milli;
73 |
74 | return '"' + year + '-' + month + '-' + day + 'T' +
75 | hours + ':' + minutes + ':' + seconds +
76 | '.' + milli + 'Z"';
77 | }
78 |
79 | if (o.constructor === Array)
80 | {
81 | var ret = [];
82 | for (var i = 0; i < o.length; i++)
83 | ret.push( $.toJSON(o[i]) || "null" );
84 |
85 | return "[" + ret.join(",") + "]";
86 | }
87 |
88 | var pairs = [];
89 | for (var k in o) {
90 | var name;
91 | var type = typeof k;
92 |
93 | if (type == "number")
94 | name = '"' + k + '"';
95 | else if (type == "string")
96 | name = $.quoteString(k);
97 | else
98 | continue; //skip non-string or number keys
99 |
100 | if (typeof o[k] == "function")
101 | continue; //skip pairs where the value is a function.
102 |
103 | var val = $.toJSON(o[k]);
104 |
105 | pairs.push(name + ":" + val);
106 | }
107 |
108 | return "{" + pairs.join(", ") + "}";
109 | }
110 | };
111 |
112 | /** jQuery.evalJSON(src)
113 | Evaluates a given piece of json source.
114 | **/
115 | $.evalJSON = function(src)
116 | {
117 | if (typeof(JSON) == 'object' && JSON.parse)
118 | return JSON.parse(src);
119 | return eval("(" + src + ")");
120 | };
121 |
122 | /** jQuery.secureEvalJSON(src)
123 | Evals JSON in a way that is *more* secure.
124 | **/
125 | $.secureEvalJSON = function(src)
126 | {
127 | if (typeof(JSON) == 'object' && JSON.parse)
128 | return JSON.parse(src);
129 |
130 | var filtered = src;
131 | filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@');
132 | filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
133 | filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
134 |
135 | if (/^[\],:{}\s]*$/.test(filtered))
136 | return eval("(" + src + ")");
137 | else
138 | throw new SyntaxError("Error parsing JSON, source is not valid.");
139 | };
140 |
141 | /** jQuery.quoteString(string)
142 | Returns a string-repr of a string, escaping quotes intelligently.
143 | Mostly a support function for toJSON.
144 |
145 | Examples:
146 | >>> jQuery.quoteString("apple")
147 | "apple"
148 |
149 | >>> jQuery.quoteString('"Where are we going?", she asked.')
150 | "\"Where are we going?\", she asked."
151 | **/
152 | $.quoteString = function(string)
153 | {
154 | if (string.match(_escapeable))
155 | {
156 | return '"' + string.replace(_escapeable, function (a)
157 | {
158 | var c = _meta[a];
159 | if (typeof c === 'string') return c;
160 | c = a.charCodeAt();
161 | return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
162 | }) + '"';
163 | }
164 | return '"' + string + '"';
165 | };
166 |
167 | var _escapeable = /["\\\x00-\x1f\x7f-\x9f]/g;
168 |
169 | var _meta = {
170 | '\b': '\\b',
171 | '\t': '\\t',
172 | '\n': '\\n',
173 | '\f': '\\f',
174 | '\r': '\\r',
175 | '"' : '\\"',
176 | '\\': '\\\\'
177 | };
178 | })(jQuery);
179 |
--------------------------------------------------------------------------------
/static/jstorage.min.js:
--------------------------------------------------------------------------------
1 | // jStorage v0.4.7
2 | (function(){function D(){var a="{}";if("userDataBehavior"==k){d.load("jStorage");try{a=d.getAttribute("jStorage")}catch(b){}try{r=d.getAttribute("jStorage_update")}catch(c){}h.jStorage=a}E();x();F()}function u(){var a;clearTimeout(G);G=setTimeout(function(){if("localStorage"==k||"globalStorage"==k)a=h.jStorage_update;else if("userDataBehavior"==k){d.load("jStorage");try{a=d.getAttribute("jStorage_update")}catch(b){}}if(a&&a!=r){r=a;var l=m.parse(m.stringify(c.__jstorage_meta.CRC32)),p;D();p=m.parse(m.stringify(c.__jstorage_meta.CRC32));
3 | var e,z=[],f=[];for(e in l)l.hasOwnProperty(e)&&(p[e]?l[e]!=p[e]&&"2."==String(l[e]).substr(0,2)&&z.push(e):f.push(e));for(e in p)p.hasOwnProperty(e)&&(l[e]||z.push(e));s(z,"updated");s(f,"deleted")}},25)}function s(a,b){a=[].concat(a||[]);if("flushed"==b){a=[];for(var c in g)g.hasOwnProperty(c)&&a.push(c);b="deleted"}c=0;for(var p=a.length;c
B){var l=b[0],d=b[1];b=b[2];if(t[d])for(var e=0,h=t[d].length;e>>16)&65535)<<16),n^=n>>>24,n=1540483477*(n&65535)+((1540483477*(n>>>16)&65535)<<16),f=1540483477*(f&65535)+((1540483477*(f>>>16)&65535)<<16)^n,k-=4,++g;switch(k){case 3:f^=(e.charCodeAt(g+2)&255)<<16;case 2:f^=(e.charCodeAt(g+1)&255)<<8;case 1:f^=e.charCodeAt(g)&255,f=1540483477*(f&65535)+((1540483477*(f>>>16)&65535)<<16)}f^=f>>>13;f=1540483477*(f&65535)+((1540483477*(f>>>16)&
11 | 65535)<<16);h[a]="2."+((f^f>>>15)>>>0);this.setTTL(a,d.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?C.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&&delete c.__jstorage_meta.TTL[a],delete c.__jstorage_meta.CRC32[a],w(),v(),s(a,"deleted"),!0):!1},setTTL:function(a,b){var d=+new Date;q(a);b=Number(b)||0;return a in
12 | c?(c.__jstorage_meta.TTL||(c.__jstorage_meta.TTL={}),0
2 |
3 |
4 | jStorage - simple JavaScript plugin to store data locally
5 |
6 |
7 |
76 |
77 |
78 |
79 |
80 |
136 |
137 |
138 |
142 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
jStorage - store data locally with JavaScript
153 |
154 |
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.
155 |
156 |
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 on the page to support older IE versions.
157 |
158 |
jStorage supports storing Strings, Numbers, JavaScript objects, Arrays and even native XML nodes. 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 or publishing/subscribing to events from the same or another tab/window, which makes jStorage also a local PubSub platform for web applications.
159 |
160 |
jStorage is pretty small, about 7kB when minified and 4kB gzipped.
161 |
162 |
Donate
163 |
164 |
Support jStorage development
165 |
166 |

167 |
168 |
Index
169 |
170 | - Basics
171 | - Download
172 | - Interactive test
173 | - Browser support
174 | - Usage
175 | - Function reference
176 | - Usage example
177 | - Issues
178 | - Contact and Copyright
179 |
180 |
181 |
182 |
190 |
191 |
192 |
193 |
1. Basics
194 |
195 |
jStorage makes use of HTML5 local storage where available and userData behavior in Internet Explorer older versions.
196 |
197 |
Current availability: jStorage supports all major browsers - Internet Explorer 6+, Firefox 2+, Safari 4+, Chrome 4+, Opera 10.50+
198 |
199 |
If the browser doesn't support data caching, then no exceptions are raised - jStorage can still be used by the script but nothing is actually stored.
200 |
201 |
jStorage is really small, just about 7kB when minified (4kB when gzipped)!
202 |
203 |
2. Download
204 |
205 |
jStorage can be downloaded at github (direct download link)
206 |
207 |
208 |
3. Interactive test
209 |
Add some values and refresh the page - if your browser supports storing local data, then the values should survive the page refresh.
210 |
211 |
234 |
235 |
Test Publish/Subscribe
236 |
Test this functionality with a specific library
237 |
Unit tests
238 |
239 |
240 |
4. Browser support
241 |
242 |
243 |
244 |
245 | | Browser |
246 | Storage support |
247 | Survives browser restart |
248 | Survives browser crash |
249 | Storage size |
250 |
251 |
252 |
253 | | Chrome 4 | + | + | + | 5 MB |
254 | | Firefox 3.6 | + | + | + | 5 MB |
255 | | Firefox 3 | + | + | + | 5 MB |
256 | | Firefox 2 | + | + | + | 5 MB |
257 | | IE8 | + | + | + | 10 MB |
258 | | IE7 | + | + | + | 128 kB |
259 | | IE6 | + | + | + | 128 kB |
260 | | Opera 10.50 | + | + | - | 5 MB |
261 | | Opera 10.10 | - | N/A | N/A | N/A |
262 | | Safari 4 | + | + | + | 5 MB |
263 | | Iphone Safari | + | + | + | 5 MB |
264 | | Safari 3 | - | N/A | N/A | N/A |
265 |
266 |
267 |
268 |
5. Usage
269 |
270 |
jStorage requires Prototype, MooTools or jQuery + jQuery-JSON or JSON2. Either way, the syntax stays the same.
271 |
272 |
Simple setup to support jStorage with JSON2
273 |
274 |
<script src="//cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js"></script>
275 | <script src="https://raw.github.com/andris9/jStorage/master/jstorage.js"></script>
276 | <script> /* $.jStorage is now available */ </script>
277 |
278 |
Setup with jQuery2
279 |
280 |
<script src="//cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js"></script>
281 | <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
282 | <script src="https://raw.github.com/andris9/jStorage/master/jstorage.js"></script>
283 | <script> /* $.jStorage is now available */ </script>
284 |
285 |
6. Function reference
286 |
287 |
set(key, value[, options])
288 |
289 |
$.jStorage.set(key, value, options)
290 |
291 |
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.
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.
292 |
293 |
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.
294 |
295 |
get(key[, default])
296 |
297 |
value = $.jStorage.get(key)
298 | value = $.jStorage.get(key, "default value")
299 |
300 |
301 |
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.
302 |
303 |
deleteKey(key)
304 |
305 |
$.jStorage.deleteKey(key)
306 |
307 |
Removes a key from the storage. key needs to be string otherwise an exception is thrown.
308 |
309 |
setTTL(key, ttl)
310 |
311 |
$.jStorage.set("mykey", "keyvalue");
312 | $.jStorage.setTTL("mykey", 3000); // expires in 3 seconds
313 |
314 |
Sets a TTL (in milliseconds) for an existing key. Use 0 or negative value to clear TTL.
315 |
316 |
getTTL(key)
317 |
318 |
ttl = $.jStorage.getTTL("mykey"); // TTL in milliseconds or 0
319 |
320 |
Gets remaining TTL (in milliseconds) for a key or 0 if not TTL has been set.
321 |
322 |
flush()
323 |
324 |
$.jStorage.flush()
325 |
326 |
Clears the cache.
327 |
328 |
index()
329 |
330 |
$.jStorage.index()
331 |
332 |
Returns all the keys currently in use as an array.
333 | var index = $.jStorage.index();
console.log(index); // ["key1","key2","key3"]
334 |
335 |
storageSize()
336 |
337 |
$.jStorage.storageSize()
338 |
339 |
Returns the size of the stored data in bytes
340 |
341 |
currentBackend()
342 |
343 |
$.jStorage.currentBackend()
344 |
345 |
Returns the storage engine currently in use or false if none
346 |
347 |
reInit()
348 |
349 |
$.jStorage.reInit()
350 |
351 |
Reloads the data from browser storage
352 |
353 |
storageAvailable()
354 |
355 |
$.jStorage.storageAvailable()
356 |
357 |
Returns true if storage is available
358 |
359 |
subscribe(channel, callback)
360 |
361 |
$.jStorage.subscribe("ch1", function(channel, payload){
362 | console.log(payload+ " from " + channel);
363 | });
364 |
365 |
Subscribes to a Publish/Subscribe channel (see demo)
366 |
367 |
publish(channel, payload)
368 |
369 |
$.jStorage.publish("ch1", "data");
370 |
371 |
Publishes payload to a Publish/Subscribe channel (see demo)
372 |
373 |
listenKeyChange(key, callback)
374 |
375 |
$.jStorage.listenKeyChange("mykey", function(key, action){
376 | console.log(key + " has been " + action);
377 | });
378 |
379 |
Listens for updates for selected key. NB! even updates made in other
380 | windows/tabs are reflected, so this feature can also be used for some kind
381 | of publish/subscribe service.
382 |
383 |
stopListening(key[, callback])
384 |
385 |
$.jStorage.stopListening("mykey"); // cancel all listeners for "mykey" change
386 |
387 |
Stops listening for key change. If callback is set, only the used callback will be cleared, otherwise all listeners will be dropped.
388 |
389 |
7. Usage example
390 |
391 |
jQuery
392 |
393 |
jQuery doesn't come with built in JSON encoder/decoder so if you need to support IE6/IE7 you should include one yourselt like in the example
394 |
395 |
<script src="//cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js"></script>
396 | <script src="//code.jquery.com/jquery-1.9.1.min.js"></script>
397 | <script src="jstorage.js"></script>
398 | <script>
399 | // Check if "key" exists in the storage
400 | var value = $.jStorage.get("key");
401 | if(!value){
402 | // if not - load the data from the server
403 | value = load_data_from_server()
404 | // and save it
405 | $.jStorage.set("key",value);
406 | }
407 | </script>
408 |
409 |
Prototype
410 |
411 |
<script src="//ajax.googleapis.com/ajax/libs/prototype/1.7.1.0/prototype.js"></script>
412 | <script src="jstorage.js"></script>
413 | <script>
414 | // Check if "key" exists in the storage
415 | var value = $.jStorage.get("key");
416 | if(!value){
417 | // if not - load the data from the server
418 | value = load_data_from_server()
419 | // and save it
420 | $.jStorage.set("key",value);
421 | }
422 | </script>
423 |
424 |
8. Issues
425 |
426 | - Why would you want to use jStorage when cookies work already in every browser?
427 | Cookies are not meant to save large quantities of data locally - they are meant to pass around ID values to keep track of users. Internet Explorer allows to use up to 20 cookies per domain with the payload of 4kB per cookie. It isn't very much, especially considering the need to chunk larger data into smaller bits. The fact that the data (max. 80 kB) is sent to the server with *every* call doesn't sound much of a good idea either.
428 |
429 |
430 |
431 |
432 |
433 |
© 2010 - 2012 Andris Reinman, andris.reinman@gmail.com
jStorage is licensed under Unlicense, so basically you can do whatever you want to do with it.
434 |
435 |
436 |
437 |
438 |
439 |
443 |
449 |
450 |
--------------------------------------------------------------------------------
/static/jstorage.js:
--------------------------------------------------------------------------------
1 | /*
2 | * ----------------------------- JSTORAGE -------------------------------------
3 | * Simple local storage wrapper to save data on the browser side, supporting
4 | * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+
5 | *
6 | * Copyright (c) 2010 - 2012 Andris Reinman, andris.reinman@gmail.com
7 | * Project homepage: www.jstorage.info
8 | *
9 | * Licensed under Unlicense:
10 | *
11 | * This is free and unencumbered software released into the public domain.
12 | *
13 | * Anyone is free to copy, modify, publish, use, compile, sell, or
14 | * distribute this software, either in source code form or as a compiled
15 | * binary, for any purpose, commercial or non-commercial, and by any
16 | * means.
17 | *
18 | * In jurisdictions that recognize copyright laws, the author or authors
19 | * of this software dedicate any and all copyright interest in the
20 | * software to the public domain. We make this dedication for the benefit
21 | * of the public at large and to the detriment of our heirs and
22 | * successors. We intend this dedication to be an overt act of
23 | * relinquishment in perpetuity of all present and future rights to this
24 | * software under copyright law.
25 | *
26 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
29 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
30 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
31 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
32 | * OTHER DEALINGS IN THE SOFTWARE.
33 | *
34 | * For more information, please refer to
35 | */
36 |
37 | (function(){
38 | var
39 | /* jStorage version */
40 | JSTORAGE_VERSION = "0.4.7",
41 |
42 | /* detect a dollar object or create one if not found */
43 | $ = window.jQuery || window.$ || (window.$ = {}),
44 |
45 | /* check for a JSON handling support */
46 | JSON = {
47 | parse:
48 | window.JSON && (window.JSON.parse || window.JSON.decode) ||
49 | String.prototype.evalJSON && function(str){return String(str).evalJSON();} ||
50 | $.parseJSON ||
51 | $.evalJSON,
52 | stringify:
53 | Object.toJSON ||
54 | window.JSON && (window.JSON.stringify || window.JSON.encode) ||
55 | $.toJSON
56 | };
57 |
58 | // Break if no JSON support was found
59 | if(!("parse" in JSON) || !("stringify" in JSON)){
60 | throw new Error("No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page");
61 | }
62 |
63 | var
64 | /* This is the object, that holds the cached values */
65 | _storage = {__jstorage_meta:{CRC32:{}}},
66 |
67 | /* Actual browser storage (localStorage or globalStorage["domain"]) */
68 | _storage_service = {jStorage:"{}"},
69 |
70 | /* DOM element for older IE versions, holds userData behavior */
71 | _storage_elm = null,
72 |
73 | /* How much space does the storage take */
74 | _storage_size = 0,
75 |
76 | /* which backend is currently used */
77 | _backend = false,
78 |
79 | /* onchange observers */
80 | _observers = {},
81 |
82 | /* timeout to wait after onchange event */
83 | _observer_timeout = false,
84 |
85 | /* last update time */
86 | _observer_update = 0,
87 |
88 | /* pubsub observers */
89 | _pubsub_observers = {},
90 |
91 | /* skip published items older than current timestamp */
92 | _pubsub_last = +new Date(),
93 |
94 | /* Next check for TTL */
95 | _ttl_timeout,
96 |
97 | /**
98 | * XML encoding and decoding as XML nodes can't be JSON'ized
99 | * XML nodes are encoded and decoded if the node is the value to be saved
100 | * but not if it's as a property of another object
101 | * Eg. -
102 | * $.jStorage.set("key", xmlNode); // IS OK
103 | * $.jStorage.set("key", {xml: xmlNode}); // NOT OK
104 | */
105 | _XMLService = {
106 |
107 | /**
108 | * Validates a XML node to be XML
109 | * based on jQuery.isXML function
110 | */
111 | isXML: function(elm){
112 | var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
113 | return documentElement ? documentElement.nodeName !== "HTML" : false;
114 | },
115 |
116 | /**
117 | * Encodes a XML node to string
118 | * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
119 | */
120 | encode: function(xmlNode) {
121 | if(!this.isXML(xmlNode)){
122 | return false;
123 | }
124 | try{ // Mozilla, Webkit, Opera
125 | return new XMLSerializer().serializeToString(xmlNode);
126 | }catch(E1) {
127 | try { // IE
128 | return xmlNode.xml;
129 | }catch(E2){}
130 | }
131 | return false;
132 | },
133 |
134 | /**
135 | * Decodes a XML node from string
136 | * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
137 | */
138 | decode: function(xmlString){
139 | var dom_parser = ("DOMParser" in window && (new DOMParser()).parseFromString) ||
140 | (window.ActiveXObject && function(_xmlString) {
141 | var xml_doc = new ActiveXObject("Microsoft.XMLDOM");
142 | xml_doc.async = "false";
143 | xml_doc.loadXML(_xmlString);
144 | return xml_doc;
145 | }),
146 | resultXML;
147 | if(!dom_parser){
148 | return false;
149 | }
150 | resultXML = dom_parser.call("DOMParser" in window && (new DOMParser()) || window, xmlString, "text/xml");
151 | return this.isXML(resultXML)?resultXML:false;
152 | }
153 | };
154 |
155 |
156 | ////////////////////////// PRIVATE METHODS ////////////////////////
157 |
158 | /**
159 | * Initialization function. Detects if the browser supports DOM Storage
160 | * or userData behavior and behaves accordingly.
161 | */
162 | function _init(){
163 | /* Check if browser supports localStorage */
164 | var localStorageReallyWorks = false;
165 | if("localStorage" in window){
166 | try {
167 | window.localStorage.setItem("_tmptest", "tmpval");
168 | localStorageReallyWorks = true;
169 | window.localStorage.removeItem("_tmptest");
170 | } catch(BogusQuotaExceededErrorOnIos5) {
171 | // Thanks be to iOS5 Private Browsing mode which throws
172 | // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
173 | }
174 | }
175 |
176 | if(localStorageReallyWorks){
177 | try {
178 | if(window.localStorage) {
179 | _storage_service = window.localStorage;
180 | _backend = "localStorage";
181 | _observer_update = _storage_service.jStorage_update;
182 | }
183 | } catch(E3) {/* Firefox fails when touching localStorage and cookies are disabled */}
184 | }
185 | /* Check if browser supports globalStorage */
186 | else if("globalStorage" in window){
187 | try {
188 | if(window.globalStorage) {
189 | if(window.location.hostname == "localhost"){
190 | _storage_service = window.globalStorage["localhost.localdomain"];
191 | }
192 | else{
193 | _storage_service = window.globalStorage[window.location.hostname];
194 | }
195 | _backend = "globalStorage";
196 | _observer_update = _storage_service.jStorage_update;
197 | }
198 | } catch(E4) {/* Firefox fails when touching localStorage and cookies are disabled */}
199 | }
200 | /* Check if browser supports userData behavior */
201 | else {
202 | _storage_elm = document.createElement("link");
203 | if(_storage_elm.addBehavior){
204 |
205 | /* Use a DOM element to act as userData storage */
206 | _storage_elm.style.behavior = "url(#default#userData)";
207 |
208 | /* userData element needs to be inserted into the DOM! */
209 | document.getElementsByTagName("head")[0].appendChild(_storage_elm);
210 |
211 | try{
212 | _storage_elm.load("jStorage");
213 | }catch(E){
214 | // try to reset cache
215 | _storage_elm.setAttribute("jStorage", "{}");
216 | _storage_elm.save("jStorage");
217 | _storage_elm.load("jStorage");
218 | }
219 |
220 | var data = "{}";
221 | try{
222 | data = _storage_elm.getAttribute("jStorage");
223 | }catch(E5){}
224 |
225 | try{
226 | _observer_update = _storage_elm.getAttribute("jStorage_update");
227 | }catch(E6){}
228 |
229 | _storage_service.jStorage = data;
230 | _backend = "userDataBehavior";
231 | }else{
232 | _storage_elm = null;
233 | return;
234 | }
235 | }
236 |
237 | // Load data from storage
238 | _load_storage();
239 |
240 | // remove dead keys
241 | _handleTTL();
242 |
243 | // start listening for changes
244 | _setupObserver();
245 |
246 | // initialize publish-subscribe service
247 | _handlePubSub();
248 |
249 | // handle cached navigation
250 | if("addEventListener" in window){
251 | window.addEventListener("pageshow", function(event){
252 | if(event.persisted){
253 | _storageObserver();
254 | }
255 | }, false);
256 | }
257 | }
258 |
259 | /**
260 | * Reload data from storage when needed
261 | */
262 | function _reloadData(){
263 | var data = "{}";
264 |
265 | if(_backend == "userDataBehavior"){
266 | _storage_elm.load("jStorage");
267 |
268 | try{
269 | data = _storage_elm.getAttribute("jStorage");
270 | }catch(E5){}
271 |
272 | try{
273 | _observer_update = _storage_elm.getAttribute("jStorage_update");
274 | }catch(E6){}
275 |
276 | _storage_service.jStorage = data;
277 | }
278 |
279 | _load_storage();
280 |
281 | // remove dead keys
282 | _handleTTL();
283 |
284 | _handlePubSub();
285 | }
286 |
287 | /**
288 | * Sets up a storage change observer
289 | */
290 | function _setupObserver(){
291 | if(_backend == "localStorage" || _backend == "globalStorage"){
292 | if("addEventListener" in window){
293 | window.addEventListener("storage", _storageObserver, false);
294 | }else{
295 | document.attachEvent("onstorage", _storageObserver);
296 | }
297 | }else if(_backend == "userDataBehavior"){
298 | setInterval(_storageObserver, 1000);
299 | }
300 | }
301 |
302 | /**
303 | * Fired on any kind of data change, needs to check if anything has
304 | * really been changed
305 | */
306 | function _storageObserver(){
307 | var updateTime;
308 | // cumulate change notifications with timeout
309 | clearTimeout(_observer_timeout);
310 | _observer_timeout = setTimeout(function(){
311 |
312 | if(_backend == "localStorage" || _backend == "globalStorage"){
313 | updateTime = _storage_service.jStorage_update;
314 | }else if(_backend == "userDataBehavior"){
315 | _storage_elm.load("jStorage");
316 | try{
317 | updateTime = _storage_elm.getAttribute("jStorage_update");
318 | }catch(E5){}
319 | }
320 |
321 | if(updateTime && updateTime != _observer_update){
322 | _observer_update = updateTime;
323 | _checkUpdatedKeys();
324 | }
325 |
326 | }, 25);
327 | }
328 |
329 | /**
330 | * Reloads the data and checks if any keys are changed
331 | */
332 | function _checkUpdatedKeys(){
333 | var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)),
334 | newCrc32List;
335 |
336 | _reloadData();
337 | newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32));
338 |
339 | var key,
340 | updated = [],
341 | removed = [];
342 |
343 | for(key in oldCrc32List){
344 | if(oldCrc32List.hasOwnProperty(key)){
345 | if(!newCrc32List[key]){
346 | removed.push(key);
347 | continue;
348 | }
349 | if(oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0,2) == "2."){
350 | updated.push(key);
351 | }
352 | }
353 | }
354 |
355 | for(key in newCrc32List){
356 | if(newCrc32List.hasOwnProperty(key)){
357 | if(!oldCrc32List[key]){
358 | updated.push(key);
359 | }
360 | }
361 | }
362 |
363 | _fireObservers(updated, "updated");
364 | _fireObservers(removed, "deleted");
365 | }
366 |
367 | /**
368 | * Fires observers for updated keys
369 | *
370 | * @param {Array|String} keys Array of key names or a key
371 | * @param {String} action What happened with the value (updated, deleted, flushed)
372 | */
373 | function _fireObservers(keys, action){
374 | keys = [].concat(keys || []);
375 | if(action == "flushed"){
376 | keys = [];
377 | for(var key in _observers){
378 | if(_observers.hasOwnProperty(key)){
379 | keys.push(key);
380 | }
381 | }
382 | action = "deleted";
383 | }
384 | for(var i=0, len = keys.length; i=0; i--){
528 | pubelm = _storage.__jstorage_meta.PubSub[i];
529 | if(pubelm[0] > _pubsub_last){
530 | _pubsubCurrent = pubelm[0];
531 | _fireSubscribers(pubelm[1], pubelm[2]);
532 | }
533 | }
534 |
535 | _pubsub_last = _pubsubCurrent;
536 | }
537 |
538 | /**
539 | * Fires all subscriber listeners for a pubsub channel
540 | *
541 | * @param {String} channel Channel name
542 | * @param {Mixed} payload Payload data to deliver
543 | */
544 | function _fireSubscribers(channel, payload){
545 | if(_pubsub_observers[channel]){
546 | for(var i=0, len = _pubsub_observers[channel].length; iGary Court
606 | * @see http://github.com/garycourt/murmurhash-js
607 | * @author Austin Appleby
608 | * @see http://sites.google.com/site/murmurhash/
609 | *
610 | * @param {string} str ASCII only
611 | * @param {number} seed Positive integer only
612 | * @return {number} 32-bit positive integer hash
613 | */
614 |
615 | function murmurhash2_32_gc(str, seed) {
616 | var
617 | l = str.length,
618 | h = seed ^ l,
619 | i = 0,
620 | k;
621 |
622 | while (l >= 4) {
623 | k =
624 | ((str.charCodeAt(i) & 0xff)) |
625 | ((str.charCodeAt(++i) & 0xff) << 8) |
626 | ((str.charCodeAt(++i) & 0xff) << 16) |
627 | ((str.charCodeAt(++i) & 0xff) << 24);
628 |
629 | k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
630 | k ^= k >>> 24;
631 | k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
632 |
633 | h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
634 |
635 | l -= 4;
636 | ++i;
637 | }
638 |
639 | switch (l) {
640 | case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
641 | case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
642 | case 1: h ^= (str.charCodeAt(i) & 0xff);
643 | h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
644 | }
645 |
646 | h ^= h >>> 13;
647 | h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
648 | h ^= h >>> 15;
649 |
650 | return h >>> 0;
651 | }
652 |
653 | ////////////////////////// PUBLIC INTERFACE /////////////////////////
654 |
655 | $.jStorage = {
656 | /* Version number */
657 | version: JSTORAGE_VERSION,
658 |
659 | /**
660 | * Sets a key's value.
661 | *
662 | * @param {String} key Key to set. If this value is not set or not
663 | * a string an exception is raised.
664 | * @param {Mixed} value Value to set. This can be any value that is JSON
665 | * compatible (Numbers, Strings, Objects etc.).
666 | * @param {Object} [options] - possible options to use
667 | * @param {Number} [options.TTL] - optional TTL value
668 | * @return {Mixed} the used value
669 | */
670 | set: function(key, value, options){
671 | _checkKey(key);
672 |
673 | options = options || {};
674 |
675 | // undefined values are deleted automatically
676 | if(typeof value == "undefined"){
677 | this.deleteKey(key);
678 | return value;
679 | }
680 |
681 | if(_XMLService.isXML(value)){
682 | value = {_is_xml:true,xml:_XMLService.encode(value)};
683 | }else if(typeof value == "function"){
684 | return undefined; // functions can't be saved!
685 | }else if(value && typeof value == "object"){
686 | // clone the object before saving to _storage tree
687 | value = JSON.parse(JSON.stringify(value));
688 | }
689 |
690 | _storage[key] = value;
691 |
692 | _storage.__jstorage_meta.CRC32[key] = "2." + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c);
693 |
694 | this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange
695 |
696 | _fireObservers(key, "updated");
697 | return value;
698 | },
699 |
700 | /**
701 | * Looks up a key in cache
702 | *
703 | * @param {String} key - Key to look up.
704 | * @param {mixed} def - Default value to return, if key didn't exist.
705 | * @return {Mixed} the key value, default value or null
706 | */
707 | get: function(key, def){
708 | _checkKey(key);
709 | if(key in _storage){
710 | if(_storage[key] && typeof _storage[key] == "object" && _storage[key]._is_xml) {
711 | return _XMLService.decode(_storage[key].xml);
712 | }else{
713 | return _storage[key];
714 | }
715 | }
716 | return typeof(def) == "undefined" ? null : def;
717 | },
718 |
719 | /**
720 | * Deletes a key from cache.
721 | *
722 | * @param {String} key - Key to delete.
723 | * @return {Boolean} true if key existed or false if it didn't
724 | */
725 | deleteKey: function(key){
726 | _checkKey(key);
727 | if(key in _storage){
728 | delete _storage[key];
729 | // remove from TTL list
730 | if(typeof _storage.__jstorage_meta.TTL == "object" &&
731 | key in _storage.__jstorage_meta.TTL){
732 | delete _storage.__jstorage_meta.TTL[key];
733 | }
734 |
735 | delete _storage.__jstorage_meta.CRC32[key];
736 |
737 | _save();
738 | _publishChange();
739 | _fireObservers(key, "deleted");
740 | return true;
741 | }
742 | return false;
743 | },
744 |
745 | /**
746 | * Sets a TTL for a key, or remove it if ttl value is 0 or below
747 | *
748 | * @param {String} key - key to set the TTL for
749 | * @param {Number} ttl - TTL timeout in milliseconds
750 | * @return {Boolean} true if key existed or false if it didn't
751 | */
752 | setTTL: function(key, ttl){
753 | var curtime = +new Date();
754 | _checkKey(key);
755 | ttl = Number(ttl) || 0;
756 | if(key in _storage){
757 |
758 | if(!_storage.__jstorage_meta.TTL){
759 | _storage.__jstorage_meta.TTL = {};
760 | }
761 |
762 | // Set TTL value for the key
763 | if(ttl>0){
764 | _storage.__jstorage_meta.TTL[key] = curtime + ttl;
765 | }else{
766 | delete _storage.__jstorage_meta.TTL[key];
767 | }
768 |
769 | _save();
770 |
771 | _handleTTL();
772 |
773 | _publishChange();
774 | return true;
775 | }
776 | return false;
777 | },
778 |
779 | /**
780 | * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set
781 | *
782 | * @param {String} key Key to check
783 | * @return {Number} Remaining TTL in milliseconds
784 | */
785 | getTTL: function(key){
786 | var curtime = +new Date(), ttl;
787 | _checkKey(key);
788 | if(key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]){
789 | ttl = _storage.__jstorage_meta.TTL[key] - curtime;
790 | return ttl || 0;
791 | }
792 | return 0;
793 | },
794 |
795 | /**
796 | * Deletes everything in cache.
797 | *
798 | * @return {Boolean} Always true
799 | */
800 | flush: function(){
801 | _storage = {__jstorage_meta:{CRC32:{}}};
802 | _save();
803 | _publishChange();
804 | _fireObservers(null, "flushed");
805 | return true;
806 | },
807 |
808 | /**
809 | * Returns a read-only copy of _storage
810 | *
811 | * @return {Object} Read-only copy of _storage
812 | */
813 | storageObj: function(){
814 | function F() {}
815 | F.prototype = _storage;
816 | return new F();
817 | },
818 |
819 | /**
820 | * Returns an index of all used keys as an array
821 | * ["key1", "key2",.."keyN"]
822 | *
823 | * @return {Array} Used keys
824 | */
825 | index: function(){
826 | var index = [], i;
827 | for(i in _storage){
828 | if(_storage.hasOwnProperty(i) && i != "__jstorage_meta"){
829 | index.push(i);
830 | }
831 | }
832 | return index;
833 | },
834 |
835 | /**
836 | * How much space in bytes does the storage take?
837 | *
838 | * @return {Number} Storage size in chars (not the same as in bytes,
839 | * since some chars may take several bytes)
840 | */
841 | storageSize: function(){
842 | return _storage_size;
843 | },
844 |
845 | /**
846 | * Which backend is currently in use?
847 | *
848 | * @return {String} Backend name
849 | */
850 | currentBackend: function(){
851 | return _backend;
852 | },
853 |
854 | /**
855 | * Test if storage is available
856 | *
857 | * @return {Boolean} True if storage can be used
858 | */
859 | storageAvailable: function(){
860 | return !!_backend;
861 | },
862 |
863 | /**
864 | * Register change listeners
865 | *
866 | * @param {String} key Key name
867 | * @param {Function} callback Function to run when the key changes
868 | */
869 | listenKeyChange: function(key, callback){
870 | _checkKey(key);
871 | if(!_observers[key]){
872 | _observers[key] = [];
873 | }
874 | _observers[key].push(callback);
875 | },
876 |
877 | /**
878 | * Remove change listeners
879 | *
880 | * @param {String} key Key name to unregister listeners against
881 | * @param {Function} [callback] If set, unregister the callback, if not - unregister all
882 | */
883 | stopListening: function(key, callback){
884 | _checkKey(key);
885 |
886 | if(!_observers[key]){
887 | return;
888 | }
889 |
890 | if(!callback){
891 | delete _observers[key];
892 | return;
893 | }
894 |
895 | for(var i = _observers[key].length - 1; i>=0; i--){
896 | if(_observers[key][i] == callback){
897 | _observers[key].splice(i,1);
898 | }
899 | }
900 | },
901 |
902 | /**
903 | * Subscribe to a Publish/Subscribe event stream
904 | *
905 | * @param {String} channel Channel name
906 | * @param {Function} callback Function to run when the something is published to the channel
907 | */
908 | subscribe: function(channel, callback){
909 | channel = (channel || "").toString();
910 | if(!channel){
911 | throw new TypeError("Channel not defined");
912 | }
913 | if(!_pubsub_observers[channel]){
914 | _pubsub_observers[channel] = [];
915 | }
916 | _pubsub_observers[channel].push(callback);
917 | },
918 |
919 | /**
920 | * Publish data to an event stream
921 | *
922 | * @param {String} channel Channel name
923 | * @param {Mixed} payload Payload to deliver
924 | */
925 | publish: function(channel, payload){
926 | channel = (channel || "").toString();
927 | if(!channel){
928 | throw new TypeError("Channel not defined");
929 | }
930 |
931 | _publish(channel, payload);
932 | },
933 |
934 | /**
935 | * Reloads the data from browser storage
936 | */
937 | reInit: function(){
938 | _reloadData();
939 | },
940 |
941 | /**
942 | * Removes reference from global objects and saves it as jStorage
943 | *
944 | * @param {Boolean} option if needed to save object as simple "jStorage" in windows context
945 | */
946 | noConflict: function( saveInGlobal ) {
947 | delete window.$.jStorage
948 |
949 | if ( saveInGlobal ) {
950 | window.jStorage = this;
951 | }
952 |
953 | return this;
954 | }
955 | };
956 |
957 | // Initialize jStorage
958 | _init();
959 |
960 | })();
961 |
--------------------------------------------------------------------------------
/static/mootools.js:
--------------------------------------------------------------------------------
1 | //MooTools, , My Object Oriented (JavaScript) Tools. Copyright (c) 2006-2009 Valerio Proietti, , MIT Style License.
2 |
3 | var MooTools={version:"1.2.4",build:"0d9113241a90b9cd5643b926795852a2026710d4"};var Native=function(k){k=k||{};var a=k.name;var i=k.legacy;var b=k.protect;
4 | var c=k.implement;var h=k.generics;var f=k.initialize;var g=k.afterImplement||function(){};var d=f||i;h=h!==false;d.constructor=Native;d.$family={name:"native"};
5 | if(i&&f){d.prototype=i.prototype;}d.prototype.constructor=d;if(a){var e=a.toLowerCase();d.prototype.$family={name:e};Native.typize(d,e);}var j=function(n,l,o,m){if(!b||m||!n.prototype[l]){n.prototype[l]=o;
6 | }if(h){Native.genericize(n,l,b);}g.call(n,l,o);return n;};d.alias=function(n,l,p){if(typeof n=="string"){var o=this.prototype[n];if((n=o)){return j(this,l,n,p);
7 | }}for(var m in n){this.alias(m,n[m],l);}return this;};d.implement=function(m,l,o){if(typeof m=="string"){return j(this,m,l,o);}for(var n in m){j(this,n,m[n],l);
8 | }return this;};if(c){d.implement(c);}return d;};Native.genericize=function(b,c,a){if((!a||!b[c])&&typeof b.prototype[c]=="function"){b[c]=function(){var d=Array.prototype.slice.call(arguments);
9 | return b.prototype[c].apply(d.shift(),d);};}};Native.implement=function(d,c){for(var b=0,a=d.length;b-1:this.indexOf(a)>-1;},trim:function(){return this.replace(/^\s+|\s+$/g,"");},clean:function(){return this.replace(/\s+/g," ").trim();
66 | },camelCase:function(){return this.replace(/-\D/g,function(a){return a.charAt(1).toUpperCase();});},hyphenate:function(){return this.replace(/[A-Z]/g,function(a){return("-"+a.charAt(0).toLowerCase());
67 | });},capitalize:function(){return this.replace(/\b[a-z]/g,function(a){return a.toUpperCase();});},escapeRegExp:function(){return this.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1");
68 | },toInt:function(a){return parseInt(this,a||10);},toFloat:function(){return parseFloat(this);},hexToRgb:function(b){var a=this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
69 | return(a)?a.slice(1).hexToRgb(b):null;},rgbToHex:function(b){var a=this.match(/\d{1,3}/g);return(a)?a.rgbToHex(b):null;},stripScripts:function(b){var a="";
70 | var c=this.replace(/