Merge branch 'MDL-32509' of git://github.com/danpoltawski/moodle
[moodle.git] / lib / yui / 3.5.0 / build / pjax-base / pjax-base-debug.js
blob11492ebbc7bf65f1ec57619d1edbf0f863a41c35
1 /*
2 YUI 3.5.0 (build 5089)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('pjax-base', function(Y) {
9 /**
10 `Y.Router` extension that provides the core plumbing for enhanced navigation
11 implemented using the pjax technique (HTML5 pushState + Ajax).
13 @module pjax
14 @submodule pjax-base
15 @since 3.5.0
16 **/
18 var win = Y.config.win,
20     Lang = Y.Lang,
22     // The CSS class name used to filter link clicks from only the links which
23     // the pjax enhanced navigation should be used.
24     CLASS_PJAX = Y.ClassNameManager.getClassName('pjax'),
26     /**
27     Fired when navigating to a URL via Pjax.
29     When the `navigate()` method is called or a pjax link is clicked, this event
30     will be fired if the browser supports HTML5 history _and_ the router has a
31     route handler for the specified URL.
33     This is a useful event to listen to for adding a visual loading indicator
34     while the route handlers are busy handling the URL change.
36     @event navigate
37     @param {String} url The URL that the router will dispatch to its route
38       handlers in order to fulfill the enhanced navigation "request".
39     @param {Boolean} [force=false] Whether the enhanced navigation should occur
40       even in browsers without HTML5 history.
41     @param {String} [hash] The hash-fragment (including "#") of the `url`. This
42       will be present when the `url` differs from the current URL only by its
43       hash and `navigateOnHash` has ben set to `true`.
44     @param {Event} [originEvent] The event that caused the navigation. Usually
45       this would be a click event from a "pjax" anchor element.
46     @param {Boolean} [replace] Whether or not the current history entry will be
47       replaced, or a new entry will be created. Will default to `true` if the
48       specified `url` is the same as the current URL.
49     @since 3.5.0
50     **/
51     EVT_NAVIGATE = 'navigate';
53 /**
54 `Y.Router` extension that provides the core plumbing for enhanced navigation
55 implemented using the pjax technique (HTML5 `pushState` + Ajax).
57 This makes it easy to enhance the navigation between the URLs of an application
58 in HTML5 history capable browsers by delegating to the router to fulfill the
59 "request" and seamlessly falling-back to using standard full-page reloads in
60 older, less-capable browsers.
62 The `PjaxBase` class isn't useful on its own, but can be mixed into a
63 `Router`-based class to add Pjax functionality to that Router. For a pre-made
64 standalone Pjax router, see the `Pjax` class.
66     var MyRouter = Y.Base.create('myRouter', Y.Router, [Y.PjaxBase], {
67         // ...
68     });
70 @class PjaxBase
71 @extensionfor Router
72 @since 3.5.0
73 **/
74 function PjaxBase() {}
76 PjaxBase.prototype = {
77     // -- Protected Properties -------------------------------------------------
79     /**
80     Holds the delegated pjax-link click handler.
82     @property _pjaxEvents
83     @type EventHandle
84     @protected
85     @since 3.5.0
86     **/
88     /**
89     Regex used to break up a URL string around the URL's path.
91     Subpattern captures:
93       1. Origin, everything before the URL's path-part.
94       2. The URL's path-part.
95       3. Suffix, everything after the URL's path-part.
97     @property _regexURL
98     @type RegExp
99     @protected
100     @since 3.5.0
101     **/
102     _regexURL: /^((?:[^\/#?:]+:\/\/|\/\/)[^\/]*)?([^?#]*)(\?[^#]*)?(#.*)?$/,
104     // -- Lifecycle Methods ----------------------------------------------------
105     initializer: function () {
106         this.publish(EVT_NAVIGATE, {defaultFn: this._defNavigateFn});
108         // Pjax is all about progressively enhancing the navigation between
109         // "pages", so by default we only want to handle and route link clicks
110         // in HTML5 `pushState`-compatible browsers.
111         if (this.get('html5')) {
112             this._pjaxBindUI();
113         }
114     },
116     destructor: function () {
117         this._pjaxEvents && this._pjaxEvents.detach();
118     },
120     // -- Public Methods -------------------------------------------------------
122     /**
123     Navigates to the specified URL if there is a route handler that matches. In
124     browsers capable of using HTML5 history, the navigation will be enhanced by
125     firing the `navigate` event and having the router handle the "request".
126     Non-HTML5 browsers will navigate to the new URL via manipulation of
127     `window.location`.
129     When there is a route handler for the specified URL and it is being
130     navigated to, this method will return `true`, otherwise it will return
131     `false`.
133     **Note:** The specified URL _must_ be of the same origin as the current URL,
134     otherwise an error will be logged and navigation will not occur. This is
135     intended as both a security constraint and a purposely imposed limitation as
136     it does not make sense to tell the router to navigate to a URL on a
137     different scheme, host, or port.
139     @method navigate
140     @param {String} url The URL to navigate to. This must be of the same origin
141       as the current URL.
142     @param {Object} [options] Additional options to configure the navigation.
143       These are mixed into the `navigate` event facade.
144         @param {Boolean} [options.replace] Whether or not the current history
145           entry will be replaced, or a new entry will be created. Will default
146           to `true` if the specified `url` is the same as the current URL.
147         @param {Boolean} [options.force=false] Whether the enhanced navigation
148           should occur even in browsers without HTML5 history.
149     @return {Boolean} `true` if the URL was navigated to, `false` otherwise.
150     @since 3.5.0
151     **/
152     navigate: function (url, options) {
153         // The `_navigate()` method expects fully-resolved URLs.
154         url = this._resolveURL(url);
156         if (this._navigate(url, options)) {
157             return true;
158         }
160         if (!this._hasSameOrigin(url)) {
161             Y.error('Security error: The new URL must be of the same origin as the current URL.');
162         }
164         return false;
165     },
167     // -- Protected Methods ----------------------------------------------------
169     /**
170     Returns the current path root after popping off the last path segment,
171     making it useful for resolving other URL paths against.
173     The path root will always begin and end with a '/'.
175     @method _getRoot
176     @return {String} The URL's path root.
177     @protected
178     @since 3.5.0
179     **/
180     _getRoot: function () {
181         var slash = '/',
182             path  = Y.getLocation().pathname,
183             segments;
185         if (path.charAt(path.length - 1) === slash) {
186             return path;
187         }
189         segments = path.split(slash);
190         segments.pop();
192         return segments.join(slash) + slash;
193     },
195     /**
196     Underlying implementation for `navigate()`.
198     @method _navigate
199     @param {String} url The fully-resolved URL that the router should dispatch
200       to its route handlers to fulfill the enhanced navigation "request", or use
201       to update `window.location` in non-HTML5 history capable browsers.
202     @param {Object} [options] Additional options to configure the navigation.
203       These are mixed into the `navigate` event facade.
204         @param {Boolean} [options.replace] Whether or not the current history
205           entry will be replaced, or a new entry will be created. Will default
206           to `true` if the specified `url` is the same as the current URL.
207         @param {Boolean} [options.force=false] Whether the enhanced navigation
208           should occur even in browsers without HTML5 history.
209     @return {Boolean} `true` if the URL was navigated to, `false` otherwise.
210     @protected
211     @since 3.5.0
212     **/
213     _navigate: function (url, options) {
214         // Navigation can only be enhanced if there is a route-handler.
215         if (!this.hasRoute(url)) {
216             return false;
217         }
219         options || (options = {});
220         options.url = url;
222         var currentURL = this._getURL(),
223             hash, hashlessURL;
225         // Captures the `url`'s hash and returns a URL without that hash.
226         hashlessURL = url.replace(/(#.*)$/, function (u, h, i) {
227             hash = h;
228             return u.substring(i);
229         });
231         if (hash && hashlessURL === currentURL.replace(/#.*$/, '')) {
232             // When the specified `url` and current URL only differ by the hash,
233             // the browser should handle this in-page navigation normally.
234             if (!this.get('navigateOnHash')) {
235                 return false;
236             }
238             options.hash = hash;
239         }
241         // When navigating to the same URL as the current URL, behave like a
242         // browser and replace the history entry instead of creating a new one.
243         Lang.isValue(options.replace) || (options.replace = url === currentURL);
245         // The `navigate` event will only fire and therefore enhance the
246         // navigation to the new URL in HTML5 history enabled browsers or when
247         // forced. Otherwise it will fallback to assigning or replacing the URL
248         // on `window.location`.
249         if (this.get('html5') || options.force) {
250             this.fire(EVT_NAVIGATE, options);
251         } else {
252             if (options.replace) {
253                 win && win.location.replace(url);
254             } else {
255                 win && (win.location = url);
256             }
257         }
259         return true;
260     },
262     /**
263     Returns a normalized path, ridding it of any '..' segments and properly
264     handling leading and trailing slashes.
266     @method _normalizePath
267     @param {String} path URL path to normalize.
268     @return {String} Normalized path.
269     @protected
270     @since 3.5.0
271     **/
272     _normalizePath: function (path) {
273         var dots  = '..',
274             slash = '/',
275             i, len, normalized, segments, segment, stack;
277         if (!path || path === slash) {
278             return slash;
279         }
281         segments = path.split(slash);
282         stack    = [];
284         for (i = 0, len = segments.length; i < len; ++i) {
285             segment = segments[i];
287             if (segment === dots) {
288                 stack.pop();
289             } else if (segment) {
290                 stack.push(segment);
291             }
292         }
294         normalized = slash + stack.join(slash);
296         // Append trailing slash if necessary.
297         if (normalized !== slash && path.charAt(path.length - 1) === slash) {
298             normalized += slash;
299         }
301         return normalized;
302     },
304     /**
305     Binds the delegation of link-click events that match the `linkSelector` to
306     the `_onLinkClick()` handler.
308     By default this method will only be called if the browser is capable of
309     using HTML5 history.
311     @method _pjaxBindUI
312     @protected
313     @since 3.5.0
314     **/
315     _pjaxBindUI: function () {
316         // Only bind link if we haven't already.
317         if (!this._pjaxEvents) {
318             this._pjaxEvents = Y.one('body').delegate('click',
319                 this._onLinkClick, this.get('linkSelector'), this);
320         }
321     },
323     /**
324     Returns the normalized result of resolving the `path` against the current
325     path. Falsy values for `path` will return just the current path.
327     @method _resolvePath
328     @param {String} path URL path to resolve.
329     @return {String} Resolved path.
330     @protected
331     @since 3.5.0
332     **/
333     _resolvePath: function (path) {
334         if (!path) {
335             return this._getPath();
336         }
338         // Path is host-relative and assumed to be resolved and normalized,
339         // meaning silly paths like: '/foo/../bar/' will be returned as-is.
340         if (path.charAt(0) === '/') {
341             return this._normalizePath(path);
342         }
344         return this._normalizePath(this._getRoot() + path);
345     },
347     /**
348     Resolves the specified URL against the current URL.
350     This method resolves URLs like a browser does and will always return an
351     absolute URL. When the specified URL is already absolute, it is assumed to
352     be fully resolved and is simply returned as is. Scheme-relative URLs are
353     prefixed with the current protocol. Relative URLs are giving the current
354     URL's origin and are resolved and normalized against the current path root.
356     @method _resolveURL
357     @param {String} url URL to resolve.
358     @return {String} Resolved URL.
359     @protected
360     @since 3.5.0
361     **/
362     _resolveURL: function (url) {
363         var parts    = url && url.match(this._regexURL),
364             origin, path, query, hash, resolved;
366         if (!parts) {
367             return this._getURL();
368         }
370         origin = parts[1];
371         path   = parts[2];
372         query  = parts[3];
373         hash   = parts[4];
375         // Absolute and scheme-relative URLs are assumed to be fully-resolved.
376         if (origin) {
377             // Prepend the current scheme for scheme-relative URLs.
378             if (origin.indexOf('//') === 0) {
379                 origin = Y.getLocation().protocol + origin;
380             }
382             return origin + (path || '/') + (query || '') + (hash || '');
383         }
385         // Will default to the current origin and current path.
386         resolved = this._getOrigin() + this._resolvePath(path);
388         // A path or query for the specified URL trumps the current URL's.
389         if (path || query) {
390             return resolved + (query || '') + (hash || '');
391         }
393         query = this._getQuery();
395         return resolved + (query ? ('?' + query) : '') + (hash || '');
396     },
398     // -- Protected Event Handlers ---------------------------------------------
400     /**
401     Default handler for the `navigate` event.
403     Adds a new history entry or replaces the current entry for the specified URL
404     and will scroll the page to the top if configured to do so.
406     @method _defNavigateFn
407     @param {EventFacade} e
408     @protected
409     @since 3.5.0
410     **/
411     _defNavigateFn: function (e) {
412         this[e.replace ? 'replace' : 'save'](e.url);
414         if (win && this.get('scrollToTop')) {
415             // Scroll to the top of the page. The timeout ensures that the
416             // scroll happens after navigation begins, so that the current
417             // scroll position will be restored if the user clicks the back
418             // button.
419             setTimeout(function () {
420                 win.scroll(0, 0);
421             }, 1);
422         }
423     },
425     /**
426     Handler for delegated link-click events which match the `linkSelector`.
428     This will attempt to enhance the navigation to the link element's `href` by
429     passing the URL to the `_navigate()` method. When the navigation is being
430     enhanced, the default action is prevented.
432     If the user clicks a link with the middle/right mouse buttons, or is holding
433     down the Ctrl or Command keys, this method's behavior is not applied and
434     allows the native behavior to occur. Similarly, if the router is not capable
435     or handling the URL because no route-handlers match, the link click will
436     behave natively.
438     @method _onLinkClick
439     @param {EventFacade} e
440     @protected
441     @since 3.5.0
442     **/
443     _onLinkClick: function (e) {
444         var url;
446         // Allow the native behavior on middle/right-click, or when Ctrl or
447         // Command are pressed.
448         if (e.button !== 1 || e.ctrlKey || e.metaKey) { return; }
450         // All browsers fully resolve an anchor's `href` property.
451         url = e.currentTarget.get('href');
453         // Try and navigate to the URL via the router, and prevent the default
454         // link-click action if we do.
455         url && this._navigate(url, {originEvent: e}) && e.preventDefault();
456     }
459 PjaxBase.ATTRS = {
460     /**
461     CSS selector string used to filter link click events so that only the links
462     which match it will have the enhanced navigation behavior of Pjax applied.
464     When a link is clicked and that link matches this selector, Pjax will
465     attempt to dispatch to any route handlers matching the link's `href` URL. If
466     HTML5 history is not supported or if no route handlers match, the link click
467     will be handled by the browser just like any old link.
469     @attribute linkSelector
470     @type String|Function
471     @default "a.pjax"
472     @initOnly
473     @since 3.5.0
474     **/
475     linkSelector: {
476         value    : 'a.' + CLASS_PJAX,
477         writeOnce: 'initOnly'
478     },
480     /**
481     Whether navigating to a hash-fragment identifier on the current page should
482     be enhanced and cause the `navigate` event to fire.
484     By default Pjax allows the browser to perform its default action when a user
485     is navigating within a page by clicking in-page links
486     (e.g. `<a href="#top">Top of page</a>`) and does not attempt to interfere or
487     enhance in-page navigation.
489     @attribute navigateOnHash
490     @type Boolean
491     @default false
492     @since 3.5.0
493     **/
494     navigateOnHash: {
495         value: false
496     },
498     /**
499     Whether the page should be scrolled to the top after navigating to a URL.
501     When the user clicks the browser's back button, the previous scroll position
502     will be maintained.
504     @attribute scrollToTop
505     @type Boolean
506     @default true
507     @since 3.5.0
508     **/
509     scrollToTop: {
510         value: true
511     }
514 Y.PjaxBase = PjaxBase;
517 }, '3.5.0' ,{requires:['classnamemanager', 'node-event-delegate', 'router']});