[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/history-hash/ -> history-hash-debug.js (source)

   1  /*
   2  YUI 3.17.2 (build 9c3c78e)
   3  Copyright 2014 Yahoo! Inc. All rights reserved.
   4  Licensed under the BSD License.
   5  http://yuilibrary.com/license/
   6  */
   7  
   8  YUI.add('history-hash', function (Y, NAME) {
   9  
  10  /**
  11   * Provides browser history management backed by
  12   * <code>window.location.hash</code>, as well as convenience methods for working
  13   * with the location hash and a synthetic <code>hashchange</code> event that
  14   * normalizes differences across browsers.
  15   *
  16   * @module history
  17   * @submodule history-hash
  18   * @since 3.2.0
  19   * @class HistoryHash
  20   * @extends HistoryBase
  21   * @constructor
  22   * @param {Object} config (optional) Configuration object. See the HistoryBase
  23   *   documentation for details.
  24   */
  25  
  26  var HistoryBase = Y.HistoryBase,
  27      Lang        = Y.Lang,
  28      YArray      = Y.Array,
  29      YObject     = Y.Object,
  30      GlobalEnv   = YUI.namespace('Env.HistoryHash'),
  31  
  32      SRC_HASH    = 'hash',
  33  
  34      hashNotifiers,
  35      oldHash,
  36      oldUrl,
  37      win             = Y.config.win,
  38      useHistoryHTML5 = Y.config.useHistoryHTML5;
  39  
  40  function HistoryHash() {
  41      HistoryHash.superclass.constructor.apply(this, arguments);
  42  }
  43  
  44  Y.extend(HistoryHash, HistoryBase, {
  45      // -- Initialization -------------------------------------------------------
  46      _init: function (config) {
  47          var bookmarkedState = HistoryHash.parseHash();
  48  
  49          // If an initialState was provided, merge the bookmarked state into it
  50          // (the bookmarked state wins).
  51          config = config || {};
  52  
  53          this._initialState = config.initialState ?
  54                  Y.merge(config.initialState, bookmarkedState) : bookmarkedState;
  55  
  56          // Subscribe to the synthetic hashchange event (defined below) to handle
  57          // changes.
  58          Y.after('hashchange', Y.bind(this._afterHashChange, this), win);
  59  
  60          HistoryHash.superclass._init.apply(this, arguments);
  61      },
  62  
  63      // -- Protected Methods ----------------------------------------------------
  64      _change: function (src, state, options) {
  65          // Stringify all values to ensure that comparisons don't fail after
  66          // they're coerced to strings in the location hash.
  67          YObject.each(state, function (value, key) {
  68              if (Lang.isValue(value)) {
  69                  state[key] = value.toString();
  70              }
  71          });
  72  
  73          return HistoryHash.superclass._change.call(this, src, state, options);
  74      },
  75  
  76      _storeState: function (src, newState) {
  77          var decode  = HistoryHash.decode,
  78              newHash = HistoryHash.createHash(newState);
  79  
  80          HistoryHash.superclass._storeState.apply(this, arguments);
  81  
  82          // Update the location hash with the changes, but only if the new hash
  83          // actually differs from the current hash (this avoids creating multiple
  84          // history entries for a single state).
  85          //
  86          // We always compare decoded hashes, since it's possible that the hash
  87          // could be set incorrectly to a non-encoded value outside of
  88          // HistoryHash.
  89          if (src !== SRC_HASH && decode(HistoryHash.getHash()) !== decode(newHash)) {
  90              HistoryHash[src === HistoryBase.SRC_REPLACE ? 'replaceHash' : 'setHash'](newHash);
  91          }
  92      },
  93  
  94      // -- Protected Event Handlers ---------------------------------------------
  95  
  96      /**
  97       * Handler for hashchange events.
  98       *
  99       * @method _afterHashChange
 100       * @param {Event} e
 101       * @protected
 102       */
 103      _afterHashChange: function (e) {
 104          this._resolveChanges(SRC_HASH, HistoryHash.parseHash(e.newHash), {});
 105      }
 106  }, {
 107      // -- Public Static Properties ---------------------------------------------
 108      NAME: 'historyHash',
 109  
 110      /**
 111       * Constant used to identify state changes originating from
 112       * <code>hashchange</code> events.
 113       *
 114       * @property SRC_HASH
 115       * @type String
 116       * @static
 117       * @final
 118       */
 119      SRC_HASH: SRC_HASH,
 120  
 121      /**
 122       * <p>
 123       * Prefix to prepend when setting the hash fragment. For example, if the
 124       * prefix is <code>!</code> and the hash fragment is set to
 125       * <code>#foo=bar&baz=quux</code>, the final hash fragment in the URL will
 126       * become <code>#!foo=bar&baz=quux</code>. This can be used to help make an
 127       * Ajax application crawlable in accordance with Google's guidelines at
 128       * <a href="http://code.google.com/web/ajaxcrawling/">http://code.google.com/web/ajaxcrawling/</a>.
 129       * </p>
 130       *
 131       * <p>
 132       * Note that this prefix applies to all HistoryHash instances. It's not
 133       * possible for individual instances to use their own prefixes since they
 134       * all operate on the same URL.
 135       * </p>
 136       *
 137       * @property hashPrefix
 138       * @type String
 139       * @default ''
 140       * @static
 141       */
 142      hashPrefix: '',
 143  
 144      // -- Protected Static Properties ------------------------------------------
 145  
 146      /**
 147       * Regular expression used to parse location hash/query strings.
 148       *
 149       * @property _REGEX_HASH
 150       * @type RegExp
 151       * @protected
 152       * @static
 153       * @final
 154       */
 155      _REGEX_HASH: /([^\?#&=]+)=?([^&=]*)/g,
 156  
 157      // -- Public Static Methods ------------------------------------------------
 158  
 159      /**
 160       * Creates a location hash string from the specified object of key/value
 161       * pairs.
 162       *
 163       * @method createHash
 164       * @param {Object} params object of key/value parameter pairs
 165       * @return {String} location hash string
 166       * @static
 167       */
 168      createHash: function (params) {
 169          var encode = HistoryHash.encode,
 170              hash   = [];
 171  
 172          YObject.each(params, function (value, key) {
 173              if (Lang.isValue(value)) {
 174                  hash.push(encode(key) + '=' + encode(value));
 175              }
 176          });
 177  
 178          return hash.join('&');
 179      },
 180  
 181      /**
 182       * Wrapper around <code>decodeURIComponent()</code> that also converts +
 183       * chars into spaces.
 184       *
 185       * @method decode
 186       * @param {String} string string to decode
 187       * @return {String} decoded string
 188       * @static
 189       */
 190      decode: function (string) {
 191          return decodeURIComponent(string.replace(/\+/g, ' '));
 192      },
 193  
 194      /**
 195       * Wrapper around <code>encodeURIComponent()</code> that converts spaces to
 196       * + chars.
 197       *
 198       * @method encode
 199       * @param {String} string string to encode
 200       * @return {String} encoded string
 201       * @static
 202       */
 203      encode: function (string) {
 204          return encodeURIComponent(string).replace(/%20/g, '+');
 205      },
 206  
 207      /**
 208       * Gets the raw (not decoded) current location hash, minus the preceding '#'
 209       * character and the hashPrefix (if one is set).
 210       *
 211       * @method getHash
 212       * @return {String} current location hash
 213       * @static
 214       */
 215      getHash: (Y.UA.gecko ? function () {
 216          // Gecko's window.location.hash returns a decoded string and we want all
 217          // encoding untouched, so we need to get the hash value from
 218          // window.location.href instead. We have to use UA sniffing rather than
 219          // feature detection, since the only way to detect this would be to
 220          // actually change the hash.
 221          var location = Y.getLocation(),
 222              matches  = /#(.*)$/.exec(location.href),
 223              hash     = matches && matches[1] || '',
 224              prefix   = HistoryHash.hashPrefix;
 225  
 226          return prefix && hash.indexOf(prefix) === 0 ?
 227                      hash.replace(prefix, '') : hash;
 228      } : function () {
 229          var location = Y.getLocation(),
 230              hash     = location.hash.substring(1),
 231              prefix   = HistoryHash.hashPrefix;
 232  
 233          // Slight code duplication here, but execution speed is of the essence
 234          // since getHash() is called every 50ms to poll for changes in browsers
 235          // that don't support native onhashchange. An additional function call
 236          // would add unnecessary overhead.
 237          return prefix && hash.indexOf(prefix) === 0 ?
 238                      hash.replace(prefix, '') : hash;
 239      }),
 240  
 241      /**
 242       * Gets the current bookmarkable URL.
 243       *
 244       * @method getUrl
 245       * @return {String} current bookmarkable URL
 246       * @static
 247       */
 248      getUrl: function () {
 249          return location.href;
 250      },
 251  
 252      /**
 253       * Parses a location hash string into an object of key/value parameter
 254       * pairs. If <i>hash</i> is not specified, the current location hash will
 255       * be used.
 256       *
 257       * @method parseHash
 258       * @param {String} hash (optional) location hash string
 259       * @return {Object} object of parsed key/value parameter pairs
 260       * @static
 261       */
 262      parseHash: function (hash) {
 263          var decode = HistoryHash.decode,
 264              i,
 265              len,
 266              match,
 267              matches,
 268              param,
 269              params = {},
 270              prefix = HistoryHash.hashPrefix,
 271              prefixIndex;
 272  
 273          hash = Lang.isValue(hash) ? hash : HistoryHash.getHash();
 274  
 275          if (prefix) {
 276              prefixIndex = hash.indexOf(prefix);
 277  
 278              if (prefixIndex === 0 || (prefixIndex === 1 && hash.charAt(0) === '#')) {
 279                  hash = hash.replace(prefix, '');
 280              }
 281          }
 282  
 283          matches = hash.match(HistoryHash._REGEX_HASH) || [];
 284  
 285          for (i = 0, len = matches.length; i < len; ++i) {
 286              match = matches[i];
 287  
 288              param = match.split('=');
 289  
 290              if (param.length > 1) {
 291                  params[decode(param[0])] = decode(param[1]);
 292              } else {
 293                  params[decode(match)] = '';
 294              }
 295          }
 296  
 297          return params;
 298      },
 299  
 300      /**
 301       * Replaces the browser's current location hash with the specified hash
 302       * and removes all forward navigation states, without creating a new browser
 303       * history entry. Automatically prepends the <code>hashPrefix</code> if one
 304       * is set.
 305       *
 306       * @method replaceHash
 307       * @param {String} hash new location hash
 308       * @static
 309       */
 310      replaceHash: function (hash) {
 311          var location = Y.getLocation(),
 312              base     = location.href.replace(/#.*$/, '');
 313  
 314          if (hash.charAt(0) === '#') {
 315              hash = hash.substring(1);
 316          }
 317  
 318          location.replace(base + '#' + (HistoryHash.hashPrefix || '') + hash);
 319      },
 320  
 321      /**
 322       * Sets the browser's location hash to the specified string. Automatically
 323       * prepends the <code>hashPrefix</code> if one is set.
 324       *
 325       * @method setHash
 326       * @param {String} hash new location hash
 327       * @static
 328       */
 329      setHash: function (hash) {
 330          var location = Y.getLocation();
 331  
 332          if (hash.charAt(0) === '#') {
 333              hash = hash.substring(1);
 334          }
 335  
 336          location.hash = (HistoryHash.hashPrefix || '') + hash;
 337      }
 338  });
 339  
 340  // -- Synthetic hashchange Event -----------------------------------------------
 341  
 342  // TODO: YUIDoc currently doesn't provide a good way to document synthetic DOM
 343  // events. For now, we're just documenting the hashchange event on the YUI
 344  // object, which is about the best we can do until enhancements are made to
 345  // YUIDoc.
 346  
 347  /**
 348  Synthetic <code>window.onhashchange</code> event that normalizes differences
 349  across browsers and provides support for browsers that don't natively support
 350  <code>onhashchange</code>.
 351  
 352  This event is provided by the <code>history-hash</code> module.
 353  
 354  @example
 355  
 356      YUI().use('history-hash', function (Y) {
 357        Y.on('hashchange', function (e) {
 358          // Handle hashchange events on the current window.
 359        }, Y.config.win);
 360      });
 361  
 362  @event hashchange
 363  @param {EventFacade} e Event facade with the following additional
 364    properties:
 365  
 366  <dl>
 367    <dt>oldHash</dt>
 368    <dd>
 369      Previous hash fragment value before the change.
 370    </dd>
 371  
 372    <dt>oldUrl</dt>
 373    <dd>
 374      Previous URL (including the hash fragment) before the change.
 375    </dd>
 376  
 377    <dt>newHash</dt>
 378    <dd>
 379      New hash fragment value after the change.
 380    </dd>
 381  
 382    <dt>newUrl</dt>
 383    <dd>
 384      New URL (including the hash fragment) after the change.
 385    </dd>
 386  </dl>
 387  @for YUI
 388  @since 3.2.0
 389  **/
 390  
 391  hashNotifiers = GlobalEnv._notifiers;
 392  
 393  if (!hashNotifiers) {
 394      hashNotifiers = GlobalEnv._notifiers = [];
 395  }
 396  
 397  Y.Event.define('hashchange', {
 398      on: function (node, subscriber, notifier) {
 399          // Ignore this subscription if the node is anything other than the
 400          // window or document body, since those are the only elements that
 401          // should support the hashchange event. Note that the body could also be
 402          // a frameset, but that's okay since framesets support hashchange too.
 403          if (node.compareTo(win) || node.compareTo(Y.config.doc.body)) {
 404              hashNotifiers.push(notifier);
 405          }
 406      },
 407  
 408      detach: function (node, subscriber, notifier) {
 409          var index = YArray.indexOf(hashNotifiers, notifier);
 410  
 411          if (index !== -1) {
 412              hashNotifiers.splice(index, 1);
 413          }
 414      }
 415  });
 416  
 417  oldHash = HistoryHash.getHash();
 418  oldUrl  = HistoryHash.getUrl();
 419  
 420  if (HistoryBase.nativeHashChange) {
 421      // Wrap the browser's native hashchange event if there's not already a
 422      // global listener.
 423      if (!GlobalEnv._hashHandle) {
 424          GlobalEnv._hashHandle = Y.Event.attach('hashchange', function (e) {
 425              var newHash = HistoryHash.getHash(),
 426                  newUrl  = HistoryHash.getUrl();
 427  
 428              // Iterate over a copy of the hashNotifiers array since a subscriber
 429              // could detach during iteration and cause the array to be re-indexed.
 430              YArray.each(hashNotifiers.concat(), function (notifier) {
 431                  notifier.fire({
 432                      _event : e,
 433                      oldHash: oldHash,
 434                      oldUrl : oldUrl,
 435                      newHash: newHash,
 436                      newUrl : newUrl
 437                  });
 438              });
 439  
 440              oldHash = newHash;
 441              oldUrl  = newUrl;
 442          }, win);
 443      }
 444  } else {
 445      // Begin polling for location hash changes if there's not already a global
 446      // poll running.
 447      if (!GlobalEnv._hashPoll) {
 448          GlobalEnv._hashPoll = Y.later(50, null, function () {
 449              var newHash = HistoryHash.getHash(),
 450                  facade, newUrl;
 451  
 452              if (oldHash !== newHash) {
 453                  newUrl = HistoryHash.getUrl();
 454  
 455                  facade = {
 456                      oldHash: oldHash,
 457                      oldUrl : oldUrl,
 458                      newHash: newHash,
 459                      newUrl : newUrl
 460                  };
 461  
 462                  oldHash = newHash;
 463                  oldUrl  = newUrl;
 464  
 465                  YArray.each(hashNotifiers.concat(), function (notifier) {
 466                      notifier.fire(facade);
 467                  });
 468              }
 469          }, null, true);
 470      }
 471  }
 472  
 473  Y.HistoryHash = HistoryHash;
 474  
 475  // HistoryHash will never win over HistoryHTML5 unless useHistoryHTML5 is false.
 476  if (useHistoryHTML5 === false || (!Y.History && useHistoryHTML5 !== true &&
 477          (!HistoryBase.html5 || !Y.HistoryHTML5))) {
 478      Y.History = HistoryHash;
 479  }
 480  
 481  
 482  }, '3.17.2', {"requires": ["event-synthetic", "history-base", "yui-later"]});


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1