add jstorage to codebase
[plewww.git] / plekit / jstorage / 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 Andris Reinman, andris.reinman@gmail.com
7  * Project homepage: www.jstorage.info
8  *
9  * Licensed under MIT-style license:
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a copy
12  * of this software and associated documentation files (the "Software"), to deal
13  * in the Software without restriction, including without limitation the rights
14  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15  * copies of the Software, and to permit persons to whom the Software is
16  * furnished to do so, subject to the following conditions:
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24  * SOFTWARE.
25  */
26
27 /**
28  * $.jStorage
29  * 
30  * USAGE:
31  *
32  * jStorage requires Prototype, MooTools or jQuery! If jQuery is used, then
33  * jQuery-JSON (http://code.google.com/p/jquery-json/) is also needed.
34  * (jQuery-JSON needs to be loaded BEFORE jStorage!)
35  *
36  * Methods:
37  *
38  * -set(key, value)
39  * $.jStorage.set(key, value) -> saves a value
40  *
41  * -get(key[, default])
42  * value = $.jStorage.get(key [, default]) ->
43  *    retrieves value if key exists, or default if it doesn't
44  *
45  * -deleteKey(key)
46  * $.jStorage.deleteKey(key) -> removes a key from the storage
47  *
48  * -flush()
49  * $.jStorage.flush() -> clears the cache
50  * 
51  * -storageObj()
52  * $.jStorage.storageObj() -> returns a read-ony copy of the actual storage
53  * 
54  * -storageSize()
55  * $.jStorage.storageSize() -> returns the size of the storage in bytes
56  *
57  * -index()
58  * $.jStorage.index() -> returns the used keys as an array
59  * 
60  * -storageAvailable()
61  * $.jStorage.storageAvailable() -> returns true if storage is available
62  * 
63  * -reInit()
64  * $.jStorage.reInit() -> reloads the data from browser storage
65  * 
66  * <value> can be any JSON-able value, including objects and arrays.
67  *
68  **/
69
70 (function($){
71     if(!$ || !($.toJSON || Object.toJSON || window.JSON)){
72         throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!");
73     }
74     
75     var
76         /* This is the object, that holds the cached values */ 
77         _storage = {},
78
79         /* Actual browser storage (localStorage or globalStorage['domain']) */
80         _storage_service = {jStorage:"{}"},
81
82         /* DOM element for older IE versions, holds userData behavior */
83         _storage_elm = null,
84         
85         /* How much space does the storage take */
86         _storage_size = 0,
87
88         /* function to encode objects to JSON strings */
89         json_encode = $.toJSON || Object.toJSON || (window.JSON && (JSON.encode || JSON.stringify)),
90
91         /* function to decode objects from JSON strings */
92         json_decode = $.evalJSON || (window.JSON && (JSON.decode || JSON.parse)) || function(str){
93             return String(str).evalJSON();
94         },
95         
96         /* which backend is currently used */
97         _backend = false;
98         
99         /**
100          * XML encoding and decoding as XML nodes can't be JSON'ized
101          * XML nodes are encoded and decoded if the node is the value to be saved
102          * but not if it's as a property of another object
103          * Eg. -
104          *   $.jStorage.set("key", xmlNode);        // IS OK
105          *   $.jStorage.set("key", {xml: xmlNode}); // NOT OK
106          */
107         _XMLService = {
108             
109             /**
110              * Validates a XML node to be XML
111              * based on jQuery.isXML function
112              */
113             isXML: function(elm){
114                 var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
115                 return documentElement ? documentElement.nodeName !== "HTML" : false;
116             },
117             
118             /**
119              * Encodes a XML node to string
120              * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
121              */
122             encode: function(xmlNode) {
123                 if(!this.isXML(xmlNode)){
124                     return false;
125                 }
126                 try{ // Mozilla, Webkit, Opera
127                     return new XMLSerializer().serializeToString(xmlNode);
128                 }catch(E1) {
129                     try {  // IE
130                         return xmlNode.xml;
131                     }catch(E2){}
132                 }
133                 return false;
134             },
135             
136             /**
137              * Decodes a XML node from string
138              * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
139              */
140             decode: function(xmlString){
141                 var dom_parser = ("DOMParser" in window && (new DOMParser()).parseFromString) ||
142                         (window.ActiveXObject && function(_xmlString) {
143                     var xml_doc = new ActiveXObject('Microsoft.XMLDOM');
144                     xml_doc.async = 'false';
145                     xml_doc.loadXML(_xmlString);
146                     return xml_doc;
147                 }),
148                 resultXML;
149                 if(!dom_parser){
150                     return false;
151                 }
152                 resultXML = dom_parser.call("DOMParser" in window && (new DOMParser()) || window, xmlString, 'text/xml');
153                 return this.isXML(resultXML)?resultXML:false;
154             }
155         };
156
157     ////////////////////////// PRIVATE METHODS ////////////////////////
158
159     /**
160      * Initialization function. Detects if the browser supports DOM Storage
161      * or userData behavior and behaves accordingly.
162      * @returns undefined
163      */
164     function _init(){
165         /* Check if browser supports localStorage */
166         if("localStorage" in window){
167             try {
168                 if(window.localStorage) {
169                     _storage_service = window.localStorage;
170                     _backend = "localStorage";
171                 }
172             } catch(E3) {/* Firefox fails when touching localStorage and cookies are disabled */}
173         }
174         /* Check if browser supports globalStorage */
175         else if("globalStorage" in window){
176             try {
177                 if(window.globalStorage) {
178                     _storage_service = window.globalStorage[window.location.hostname];
179                     _backend = "globalStorage";
180                 }
181             } catch(E4) {/* Firefox fails when touching localStorage and cookies are disabled */}
182         }
183         /* Check if browser supports userData behavior */
184         else {
185             _storage_elm = document.createElement('link');
186             if(_storage_elm.addBehavior){
187
188                 /* Use a DOM element to act as userData storage */
189                 _storage_elm.style.behavior = 'url(#default#userData)';
190
191                 /* userData element needs to be inserted into the DOM! */
192                 document.getElementsByTagName('head')[0].appendChild(_storage_elm);
193
194                 _storage_elm.load("jStorage");
195                 var data = "{}";
196                 try{
197                     data = _storage_elm.getAttribute("jStorage");
198                 }catch(E5){}
199                 _storage_service.jStorage = data;
200                 _backend = "userDataBehavior";
201             }else{
202                 _storage_elm = null;
203                 return;
204             }
205         }
206
207         _load_storage();
208     }
209     
210     /**
211      * Loads the data from the storage based on the supported mechanism
212      * @returns undefined
213      */
214     function _load_storage(){
215         /* if jStorage string is retrieved, then decode it */
216         if(_storage_service.jStorage){
217             try{
218                 _storage = json_decode(String(_storage_service.jStorage));
219             }catch(E6){_storage_service.jStorage = "{}";}
220         }else{
221             _storage_service.jStorage = "{}";
222         }
223         _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;    
224     }
225
226     /**
227      * This functions provides the "save" mechanism to store the jStorage object
228      * @returns undefined
229      */
230     function _save(){
231         try{
232             _storage_service.jStorage = json_encode(_storage);
233             // If userData is used as the storage engine, additional
234             if(_storage_elm) {
235                 _storage_elm.setAttribute("jStorage",_storage_service.jStorage);
236                 _storage_elm.save("jStorage");
237             }
238             _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0;
239         }catch(E7){/* probably cache is full, nothing is saved this way*/}
240     }
241
242     /**
243      * Function checks if a key is set and is string or numberic
244      */
245     function _checkKey(key){
246         if(!key || (typeof key != "string" && typeof key != "number")){
247             throw new TypeError('Key name must be string or numeric');
248         }
249         return true;
250     }
251
252     ////////////////////////// PUBLIC INTERFACE /////////////////////////
253
254     $.jStorage = {
255         /* Version number */
256         version: "0.1.5.0",
257
258         /**
259          * Sets a key's value.
260          * 
261          * @param {String} key - Key to set. If this value is not set or not
262          *              a string an exception is raised.
263          * @param value - Value to set. This can be any value that is JSON
264          *              compatible (Numbers, Strings, Objects etc.).
265          * @returns the used value
266          */
267         set: function(key, value){
268             _checkKey(key);
269             if(_XMLService.isXML(value)){
270                 value = {_is_xml:true,xml:_XMLService.encode(value)};
271             }
272             _storage[key] = value;
273             _save();
274             return value;
275         },
276         
277         /**
278          * Looks up a key in cache
279          * 
280          * @param {String} key - Key to look up.
281          * @param {mixed} def - Default value to return, if key didn't exist.
282          * @returns the key value, default value or <null>
283          */
284         get: function(key, def){
285             _checkKey(key);
286             if(key in _storage){
287                 if(typeof _storage[key] == "object" &&
288                         _storage[key]._is_xml &&
289                             _storage[key]._is_xml){
290                     return _XMLService.decode(_storage[key].xml);
291                 }else{
292                     return _storage[key];
293                 }
294             }
295             return typeof(def) == 'undefined' ? null : def;
296         },
297         
298         /**
299          * Deletes a key from cache.
300          * 
301          * @param {String} key - Key to delete.
302          * @returns true if key existed or false if it didn't
303          */
304         deleteKey: function(key){
305             _checkKey(key);
306             if(key in _storage){
307                 delete _storage[key];
308                 _save();
309                 return true;
310             }
311             return false;
312         },
313
314         /**
315          * Deletes everything in cache.
316          * 
317          * @returns true
318          */
319         flush: function(){
320             _storage = {};
321             _save();
322             /*
323              * Just to be sure - andris9/jStorage#3
324              */
325             try{
326                 window.localStorage.clear();
327             }catch(E8){}
328             return true;
329         },
330         
331         /**
332          * Returns a read-only copy of _storage
333          * 
334          * @returns Object
335         */
336         storageObj: function(){
337             function F() {}
338             F.prototype = _storage;
339             return new F();
340         },
341         
342         /**
343          * Returns an index of all used keys as an array
344          * ['key1', 'key2',..'keyN']
345          * 
346          * @returns Array
347         */
348         index: function(){
349             var index = [], i;
350             for(i in _storage){
351                 if(_storage.hasOwnProperty(i)){
352                     index.push(i);
353                 }
354             }
355             return index;
356         },
357         
358         /**
359          * How much space in bytes does the storage take?
360          * 
361          * @returns Number
362          */
363         storageSize: function(){
364             return _storage_size;
365         },
366         
367         /**
368          * Which backend is currently in use?
369          * 
370          * @returns String
371          */
372         currentBackend: function(){
373             return _backend;
374         },
375         
376         /**
377          * Test if storage is available
378          * 
379          * @returns Boolean
380          */
381         storageAvailable: function(){
382             return !!_backend;
383         },
384         
385         /**
386          * Reloads the data from browser storage
387          * 
388          * @returns undefined
389          */
390         reInit: function(){
391             var new_storage_elm, data;
392             if(_storage_elm && _storage_elm.addBehavior){
393                 new_storage_elm = document.createElement('link');
394                 
395                 _storage_elm.parentNode.replaceChild(new_storage_elm, _storage_elm);
396                 _storage_elm = new_storage_elm;
397                 
398                 /* Use a DOM element to act as userData storage */
399                 _storage_elm.style.behavior = 'url(#default#userData)';
400
401                 /* userData element needs to be inserted into the DOM! */
402                 document.getElementsByTagName('head')[0].appendChild(_storage_elm);
403
404                 _storage_elm.load("jStorage");
405                 data = "{}";
406                 try{
407                     data = _storage_elm.getAttribute("jStorage");
408                 }catch(E5){}
409                 _storage_service.jStorage = data;
410                 _backend = "userDataBehavior";
411             }
412             
413             _load_storage();
414         }
415     };
416
417     // Initialize jStorage
418     _init();
419
420 })(window.jQuery || window.$);