[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/resources/lib/jquery/ -> jquery.jStorage.js (source)

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


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1