sisyphus-rails 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +35 -7
- data/lib/sisyphus-rails/version.rb +1 -1
- data/vendor/assets/javascripts/jstorage.js +960 -18
- data/vendor/assets/javascripts/sisyphus.js +143 -77
- metadata +9 -19
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 3a94486773779736842c07a216e5e8c15f907971
|
|
4
|
+
data.tar.gz: 857d8d0b2045adccdb2295d3d5567802857a4ed4
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ff8d016bf658d94008b249bfef885a8003c2a736e6677364303c6c0a5b0d72c6c649fbcd561433682376ea33ce99fa27c16f4427b68517175958292aac97ed12
|
|
7
|
+
data.tar.gz: b99ea980d4aa82c6c3588d1a6dae1c08f0ccbc4271716fc6ad597dd28140947c59cc332d916c688d07e3e0f242296906ae411194c15e71f3aeac1644d9219ed1
|
data/README.md
CHANGED
|
@@ -25,6 +25,14 @@ To support IE6/7 users include jStorage as well
|
|
|
25
25
|
//= require jstorage
|
|
26
26
|
//= require sisyphus
|
|
27
27
|
|
|
28
|
+
### Create a Configuration File
|
|
29
|
+
|
|
30
|
+
You can use the built in generator to create a basic *sisyphus.yml* configuration file.
|
|
31
|
+
|
|
32
|
+
$ rails generate sisyphus:configuration
|
|
33
|
+
|
|
34
|
+
The file will be placed in *config/*
|
|
35
|
+
|
|
28
36
|
## How it Works
|
|
29
37
|
|
|
30
38
|
Sisyphus uses an aliased form_tag method to automatically put a *<script>...</script>* tag infront of your forms to initialize Sisyphus. It also uses an aliased FormHelper form_for method to read options.
|
|
@@ -33,18 +41,36 @@ Sisyphus uses an aliased form_tag method to automatically put a *<script>.
|
|
|
33
41
|
<form id="sampleform">
|
|
34
42
|
...
|
|
35
43
|
</form>
|
|
44
|
+
|
|
36
45
|
## Usage
|
|
37
46
|
|
|
38
47
|
Sisyphus automatically works with all your forms, including custom form builders.
|
|
39
48
|
|
|
40
|
-
### Exclude form
|
|
49
|
+
### Exclude a form
|
|
41
50
|
|
|
42
|
-
To stop Sisyphus from initializing on a form include the *with_sisyphus* option in your form_for.
|
|
51
|
+
To stop Sisyphus from initializing on a form include the *with_sisyphus* option in your form_for. This switch overrides the others
|
|
43
52
|
|
|
44
53
|
<%= form_for User.new, :with_sisyphus=>false do |f| %>
|
|
45
54
|
...
|
|
46
55
|
<% end %>
|
|
47
56
|
|
|
57
|
+
### Exclude/Include a model
|
|
58
|
+
|
|
59
|
+
To exclude or include a specific model you can enter it in your ENV or the *sisyphus.yml* file. The configuration takes a comma (,) separated string of __lower case__ model names. This switch takes precedence over the global switch.
|
|
60
|
+
|
|
61
|
+
exclude_models: user
|
|
62
|
+
include_models: comment,post
|
|
63
|
+
|
|
64
|
+
### Exclude/Include everything
|
|
65
|
+
|
|
66
|
+
You can also set the *sisyphus_enabled* option to true/false in your ENV or the *sisyphus.yml* file to turn Sisyphus on or off by default. If no other switches are set this will be used.
|
|
67
|
+
|
|
68
|
+
sisyphus_enabled: false
|
|
69
|
+
|
|
70
|
+
### Putting the switches together
|
|
71
|
+
|
|
72
|
+
The switches work together to see if a form should be protected by Sisyphus or not. The *sisyphus_enabled* switch will be used if no other switches are set. If the model is specifically excluded (when *sisyphus_enabled* = true) then it will not be protected. Conversley, if the model is specifically included and *sisyphus_enabled* = false it will be protected. The final say is taken by the form option. If *with_sysphus* is set it's value will be used to determine if the form should be protected or not.
|
|
73
|
+
|
|
48
74
|
## Dependencies
|
|
49
75
|
|
|
50
76
|
- Rails >= 3.1
|
|
@@ -53,18 +79,20 @@ To stop Sisyphus from initializing on a form include the *with_sisyphus* option
|
|
|
53
79
|
## TODO
|
|
54
80
|
|
|
55
81
|
- Tests
|
|
56
|
-
|
|
82
|
+
|
|
83
|
+
- rubydoc.info hosted documentation so the rubygems.org page doesn't look like crap.
|
|
57
84
|
- conflict resolution... i.e. you have an object edit form, the fields are populated from the DB but if you have local browser changes they may get overridden. We need a way to resolve conflicts between local and remote data -> could use a jquery based modal dialog to present the diff?
|
|
58
|
-
- Model based exclusions via activerecord extension?
|
|
59
|
-
-
|
|
85
|
+
- Model based exclusions/options via activerecord extension? We have config based exclude/includes now
|
|
86
|
+
- implement all sisyphus options and any relevant ones in jStorage
|
|
87
|
+
- block based options (this would allow us to easily and neatly implement Sisyphus options). The do...end block is passed in but how do we interact with it to get options out? Is this possible in any way?
|
|
60
88
|
|
|
61
89
|
<%= form_for User.new do |f| %>
|
|
62
90
|
<% :sisyphus => flase %>
|
|
63
91
|
<% :sisyphus_customKeyPrefix => 'sisy' %>
|
|
64
92
|
...
|
|
65
93
|
<% end %>
|
|
66
|
-
|
|
67
|
-
-
|
|
94
|
+
|
|
95
|
+
- Move away from form_tag_helper, should be able to do it all from form_helper? Script tag can go at the end anyhow. Why aren't we doing that right now? We don't seem to have access to the same variables that the regular form_for does. Also the options array gets muddied by the FormHelper form_for call -> it removes the [:html] section (we need the id of the form for sisyphus). It would be nice if we could get away with this but there are certain things like the form name that we need to know that aren't easily available in the FormHelper.
|
|
68
96
|
|
|
69
97
|
## Contributing
|
|
70
98
|
|
|
@@ -1,18 +1,960 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
+
* Author: 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 <http://unlicense.org/>
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
(function(){
|
|
38
|
+
var
|
|
39
|
+
/* jStorage version */
|
|
40
|
+
JSTORAGE_VERSION = "0.4.8",
|
|
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<len; i++){
|
|
385
|
+
if(_observers[keys[i]]){
|
|
386
|
+
for(var j=0, jlen = _observers[keys[i]].length; j<jlen; j++){
|
|
387
|
+
_observers[keys[i]][j](keys[i], action);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if(_observers["*"]){
|
|
391
|
+
for(var j=0, jlen = _observers["*"].length; j<jlen; j++){
|
|
392
|
+
_observers["*"][j](keys[i], action);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Publishes key change to listeners
|
|
400
|
+
*/
|
|
401
|
+
function _publishChange(){
|
|
402
|
+
var updateTime = (+new Date()).toString();
|
|
403
|
+
|
|
404
|
+
if(_backend == "localStorage" || _backend == "globalStorage"){
|
|
405
|
+
try {
|
|
406
|
+
_storage_service.jStorage_update = updateTime;
|
|
407
|
+
} catch (E8) {
|
|
408
|
+
// safari private mode has been enabled after the jStorage initialization
|
|
409
|
+
_backend = false;
|
|
410
|
+
}
|
|
411
|
+
}else if(_backend == "userDataBehavior"){
|
|
412
|
+
_storage_elm.setAttribute("jStorage_update", updateTime);
|
|
413
|
+
_storage_elm.save("jStorage");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
_storageObserver();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Loads the data from the storage based on the supported mechanism
|
|
421
|
+
*/
|
|
422
|
+
function _load_storage(){
|
|
423
|
+
/* if jStorage string is retrieved, then decode it */
|
|
424
|
+
if(_storage_service.jStorage){
|
|
425
|
+
try{
|
|
426
|
+
_storage = JSON.parse(String(_storage_service.jStorage));
|
|
427
|
+
}catch(E6){_storage_service.jStorage = "{}";}
|
|
428
|
+
}else{
|
|
429
|
+
_storage_service.jStorage = "{}";
|
|
430
|
+
}
|
|
431
|
+
_storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
|
|
432
|
+
|
|
433
|
+
if(!_storage.__jstorage_meta){
|
|
434
|
+
_storage.__jstorage_meta = {};
|
|
435
|
+
}
|
|
436
|
+
if(!_storage.__jstorage_meta.CRC32){
|
|
437
|
+
_storage.__jstorage_meta.CRC32 = {};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* This functions provides the "save" mechanism to store the jStorage object
|
|
443
|
+
*/
|
|
444
|
+
function _save(){
|
|
445
|
+
_dropOldEvents(); // remove expired events
|
|
446
|
+
try{
|
|
447
|
+
_storage_service.jStorage = JSON.stringify(_storage);
|
|
448
|
+
// If userData is used as the storage engine, additional
|
|
449
|
+
if(_storage_elm) {
|
|
450
|
+
_storage_elm.setAttribute("jStorage",_storage_service.jStorage);
|
|
451
|
+
_storage_elm.save("jStorage");
|
|
452
|
+
}
|
|
453
|
+
_storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
|
|
454
|
+
}catch(E7){/* probably cache is full, nothing is saved this way*/}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Function checks if a key is set and is string or numberic
|
|
459
|
+
*
|
|
460
|
+
* @param {String} key Key name
|
|
461
|
+
*/
|
|
462
|
+
function _checkKey(key){
|
|
463
|
+
if(typeof key != "string" && typeof key != "number"){
|
|
464
|
+
throw new TypeError("Key name must be string or numeric");
|
|
465
|
+
}
|
|
466
|
+
if(key == "__jstorage_meta"){
|
|
467
|
+
throw new TypeError("Reserved key name");
|
|
468
|
+
}
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Removes expired keys
|
|
474
|
+
*/
|
|
475
|
+
function _handleTTL(){
|
|
476
|
+
var curtime, i, TTL, CRC32, nextExpire = Infinity, changed = false, deleted = [];
|
|
477
|
+
|
|
478
|
+
clearTimeout(_ttl_timeout);
|
|
479
|
+
|
|
480
|
+
if(!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != "object"){
|
|
481
|
+
// nothing to do here
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
curtime = +new Date();
|
|
486
|
+
TTL = _storage.__jstorage_meta.TTL;
|
|
487
|
+
|
|
488
|
+
CRC32 = _storage.__jstorage_meta.CRC32;
|
|
489
|
+
for(i in TTL){
|
|
490
|
+
if(TTL.hasOwnProperty(i)){
|
|
491
|
+
if(TTL[i] <= curtime){
|
|
492
|
+
delete TTL[i];
|
|
493
|
+
delete CRC32[i];
|
|
494
|
+
delete _storage[i];
|
|
495
|
+
changed = true;
|
|
496
|
+
deleted.push(i);
|
|
497
|
+
}else if(TTL[i] < nextExpire){
|
|
498
|
+
nextExpire = TTL[i];
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// set next check
|
|
504
|
+
if(nextExpire != Infinity){
|
|
505
|
+
_ttl_timeout = setTimeout(_handleTTL, nextExpire - curtime);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// save changes
|
|
509
|
+
if(changed){
|
|
510
|
+
_save();
|
|
511
|
+
_publishChange();
|
|
512
|
+
_fireObservers(deleted, "deleted");
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Checks if there's any events on hold to be fired to listeners
|
|
518
|
+
*/
|
|
519
|
+
function _handlePubSub(){
|
|
520
|
+
var i, len;
|
|
521
|
+
if(!_storage.__jstorage_meta.PubSub){
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
var pubelm,
|
|
525
|
+
_pubsubCurrent = _pubsub_last;
|
|
526
|
+
|
|
527
|
+
for(i=len=_storage.__jstorage_meta.PubSub.length-1; 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; i<len; i++){
|
|
547
|
+
// send immutable data that can't be modified by listeners
|
|
548
|
+
try{
|
|
549
|
+
_pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
|
|
550
|
+
}catch(E){};
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Remove old events from the publish stream (at least 2sec old)
|
|
557
|
+
*/
|
|
558
|
+
function _dropOldEvents(){
|
|
559
|
+
if(!_storage.__jstorage_meta.PubSub){
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
var retire = +new Date() - 2000;
|
|
564
|
+
|
|
565
|
+
for(var i=0, len = _storage.__jstorage_meta.PubSub.length; i<len; i++){
|
|
566
|
+
if(_storage.__jstorage_meta.PubSub[i][0] <= retire){
|
|
567
|
+
// deleteCount is needed for IE6
|
|
568
|
+
_storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if(!_storage.__jstorage_meta.PubSub.length){
|
|
574
|
+
delete _storage.__jstorage_meta.PubSub;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Publish payload to a channel
|
|
581
|
+
*
|
|
582
|
+
* @param {String} channel Channel name
|
|
583
|
+
* @param {Mixed} payload Payload to send to the subscribers
|
|
584
|
+
*/
|
|
585
|
+
function _publish(channel, payload){
|
|
586
|
+
if(!_storage.__jstorage_meta){
|
|
587
|
+
_storage.__jstorage_meta = {};
|
|
588
|
+
}
|
|
589
|
+
if(!_storage.__jstorage_meta.PubSub){
|
|
590
|
+
_storage.__jstorage_meta.PubSub = [];
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
_storage.__jstorage_meta.PubSub.unshift([+new Date, channel, payload]);
|
|
594
|
+
|
|
595
|
+
_save();
|
|
596
|
+
_publishChange();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* JS Implementation of MurmurHash2
|
|
602
|
+
*
|
|
603
|
+
* SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed)
|
|
604
|
+
*
|
|
605
|
+
* @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
|
|
606
|
+
* @see http://github.com/garycourt/murmurhash-js
|
|
607
|
+
* @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
|
|
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
|
+
})();
|