~bac/juju-gui/trunkcopy

« back to all changes in this revision

Viewing changes to lib/yui/build/history-hash/history-hash-debug.js

  • Committer: kapil.foss at gmail
  • Date: 2012-07-13 18:45:59 UTC
  • Revision ID: kapil.foss@gmail.com-20120713184559-2xl7be17egsrz0c9
reshape

Show diffs side-by-side

added added

removed removed

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