Merge branch 'MDL-32509' of git://github.com/danpoltawski/moodle
[moodle.git] / lib / yui / 3.5.0 / build / app-base / app-base.js
blob65f07ae023d18bbb781af376a946eb8802d96ff2
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('app-base', function(Y) {
9 /**
10 The App Framework provides simple MVC-like building blocks (models, model lists,
11 views, and URL-based routing) for writing single-page JavaScript applications.
13 @main app
14 @module app
15 @since 3.4.0
16 **/
18 /**
19 Provides a top-level application component which manages navigation and views.
21 @module app
22 @submodule app-base
23 @since 3.5.0
24 **/
26 // TODO: Better handling of lifecycle for registered views:
28 //   * Seems like any view created via `createView` should listen for the view's
29 //     `destroy` event and use that to remove it from the `_viewsInfoMap`. I
30 //     should look at what ModelList does for Models as a reference.
32 //   * Should we have a companion `destroyView()` method? Maybe this wouldn't be
33 //     needed if we have a `getView(name, create)` method, and already doing the
34 //     above? We could do `app.getView('foo').destroy()` and it would be removed
35 //     from the `_viewsInfoMap` as well.
37 //   * Should named views support a collection of instances instead of just one?
40 var Lang    = Y.Lang,
41     YObject = Y.Object,
43     PjaxBase = Y.PjaxBase,
44     Router   = Y.Router,
45     View     = Y.View,
47     getClassName = Y.ClassNameManager.getClassName,
49     win = Y.config.win,
51     App;
53 /**
54 Provides a top-level application component which manages navigation and views.
56 This gives you a foundation and structure on which to build your application; it
57 combines robust URL navigation with powerful routing and flexible view
58 management.
60 @class App.Base
61 @param {Object} [config] The following are configuration properties that can be
62     specified _in addition_ to default attribute values and the non-attribute
63     properties provided by `Y.Base`:
64   @param {Object} [config.views] Hash of view-name to metadata used to
65     declaratively describe an application's views and their relationship with
66     the app and other views. The views specified here will override any defaults
67     provided by the `views` object on the `prototype`.
68 @constructor
69 @extends Base
70 @uses View
71 @uses Router
72 @uses PjaxBase
73 @since 3.5.0
74 **/
75 App = Y.Base.create('app', Y.Base, [View, Router, PjaxBase], {
76     // -- Public Properties ----------------------------------------------------
78     /**
79     Hash of view-name to metadata used to declaratively describe an
80     application's views and their relationship with the app and its other views.
82     The view metadata is composed of Objects keyed to a view-name that can have
83     any or all of the following properties:
85       * `type`: Function or a string representing the view constructor to use to
86         create view instances. If a string is used, the constructor function is
87         assumed to be on the `Y` object; e.g. `"SomeView"` -> `Y.SomeView`.
89       * `preserve`: Boolean for whether the view instance should be retained. By
90         default, the view instance will be destroyed when it is no longer the
91         `activeView`. If `true` the view instance will simply be `removed()`
92         from the DOM when it is no longer active. This is useful when the view
93         is frequently used and may be expensive to re-create.
95       * `parent`: String to another named view in this hash that represents the
96         parent view within the application's view hierarchy; e.g. a `"photo"`
97         view could have `"album"` has its `parent` view. This parent/child
98         relationship is a useful cue for things like transitions.
100       * `instance`: Used internally to manage the current instance of this named
101         view. This can be used if your view instance is created up-front, or if
102         you would rather manage the View lifecycle, but you probably should just
103         let this be handled for you.
105     If `views` are specified at instantiation time, the metadata in the `views`
106     Object here will be used as defaults when creating the instance's `views`.
108     Every `Y.App` instance gets its own copy of a `views` object so this Object
109     on the prototype will not be polluted.
111     @example
112         // Imagine that `Y.UsersView` and `Y.UserView` have been defined.
113         var app = new Y.App({
114             views: {
115                 users: {
116                     type    : Y.UsersView,
117                     preserve: true
118                 },
120                 user: {
121                     type  : Y.UserView,
122                     parent: 'users'
123                 }
124             }
125         });
127     @property views
128     @type Object
129     @default {}
130     @since 3.5.0
131     **/
132     views: {},
134     // -- Protected Properties -------------------------------------------------
136     /**
137     Map of view instance id (via `Y.stamp()`) to view-info object in `views`.
139     This mapping is used to tie a specific view instance back to its metadata by
140     adding a reference to the the related view info on the `views` object.
142     @property _viewInfoMap
143     @type Object
144     @default {}
145     @protected
146     @since 3.5.0
147     **/
149     // -- Lifecycle Methods ----------------------------------------------------
150     initializer: function (config) {
151         config || (config = {});
153         var views = {};
155         // Merges-in specified view metadata into local `views` object.
156         function mergeViewConfig(view, name) {
157             views[name] = Y.merge(views[name], view);
158         }
160         // First, each view in the `views` prototype object gets its metadata
161         // merged-in, providing the defaults.
162         YObject.each(this.views, mergeViewConfig);
164         // Then, each view in the specified `config.views` object gets its
165         // metadata merged-in.
166         YObject.each(config.views, mergeViewConfig);
168         // The resulting hodgepodge of metadata is then stored as the instance's
169         // `views` object, and no one's objects were harmed in the making.
170         this.views        = views;
171         this._viewInfoMap = {};
173         // Using `bind()` to aid extensibility.
174         this.after('activeViewChange', Y.bind('_afterActiveViewChange', this));
176         // PjaxBase will bind click events when `html5` is `true`, so this just
177         // forces the binding when `serverRouting` and `html5` are both falsy.
178         if (!this.get('serverRouting')) {
179             this._pjaxBindUI();
180         }
181     },
183     // TODO: `destructor` to destroy the `activeView`?
185     // -- Public Methods -------------------------------------------------------
187     /**
188     Creates and returns a new view instance using the provided `name` to look up
189     the view info metadata defined in the `views` object. The passed-in `config`
190     object is passed to the view constructor function.
192     This function also maps a view instance back to its view info metadata.
194     @method createView
195     @param {String} name The name of a view defined on the `views` object.
196     @param {Object} [config] The configuration object passed to the view
197       constructor function when creating the new view instance.
198     @return {View} The new view instance.
199     @since 3.5.0
200     **/
201     createView: function (name, config) {
202         var viewInfo = this.getViewInfo(name),
203             type     = (viewInfo && viewInfo.type) || View,
204             ViewConstructor, view;
206         // Looks for a namespaced constructor function on `Y`.
207         ViewConstructor = Lang.isString(type) ?
208                 YObject.getValue(Y, type.split('.')) : type;
210         // Create the view instance and map it with its metadata.
211         view = new ViewConstructor(config);
212         this._viewInfoMap[Y.stamp(view, true)] = viewInfo;
214         return view;
215     },
217     /**
218     Returns the metadata associated with a view instance or view name defined on
219     the `views` object.
221     @method getViewInfo
222     @param {View|String} view View instance, or name of a view defined on the
223       `views` object.
224     @return {Object} The metadata for the view, or `undefined` if the view is
225       not registered.
226     @since 3.5.0
227     **/
228     getViewInfo: function (view) {
229         if (Lang.isString(view)) {
230             return this.views[view];
231         }
233         return view && this._viewInfoMap[Y.stamp(view, true)];
234     },
236     /**
237     Navigates to the specified URL if there is a route handler that matches. In
238     browsers capable of using HTML5 history or when `serverRouting` is falsy,
239     the navigation will be enhanced by firing the `navigate` event and having
240     the app handle the "request". When `serverRouting` is `true`, non-HTML5
241     browsers will navigate to the new URL via a full page reload.
243     When there is a route handler for the specified URL and it is being
244     navigated to, this method will return `true`, otherwise it will return
245     `false`.
247     **Note:** The specified URL _must_ be of the same origin as the current URL,
248     otherwise an error will be logged and navigation will not occur. This is
249     intended as both a security constraint and a purposely imposed limitation as
250     it does not make sense to tell the app to navigate to a URL on a
251     different scheme, host, or port.
253     @method navigate
254     @param {String} url The URL to navigate to. This must be of the same origin
255       as the current URL.
256     @param {Object} [options] Additional options to configure the navigation.
257       These are mixed into the `navigate` event facade.
258         @param {Boolean} [options.replace] Whether or not the current history
259           entry will be replaced, or a new entry will be created. Will default
260           to `true` if the specified `url` is the same as the current URL.
261         @param {Boolean} [options.force] Whether the enhanced navigation
262           should occur even in browsers without HTML5 history. Will default to
263           `true` when `serverRouting` is falsy.
264     @see PjaxBase.navigate()
265     **/
266     // Does not override `navigate()` but does use extra `options`.
268     /**
269     Renders this application by appending the `viewContainer` node to the
270     `container` node if it isn't already a child of the container, and the
271     `activeView` will be appended the view container, if it isn't already.
273     You should call this method at least once, usually after the initialization
274     of your app instance so the proper DOM structure is setup and optionally
275     append the container to the DOM if it's not there already.
277     You may override this method to customize the app's rendering, but you
278     should expect that the `viewContainer`'s contents will be modified by the
279     app for the purpose of rendering the `activeView` when it changes.
281     @method render
282     @chainable
283     @see View.render()
284     **/
285     render: function () {
286         var container           = this.get('container'),
287             viewContainer       = this.get('viewContainer'),
288             activeView          = this.get('activeView'),
289             activeViewContainer = activeView && activeView.get('container'),
290             areSame             = container.compareTo(viewContainer);
292         container.addClass(App.CSS_CLASS);
293         viewContainer.addClass(App.VIEWS_CSS_CLASS);
295         // Prevents needless shuffling around of nodes and maintains DOM order.
296         if (activeView && !viewContainer.contains(activeViewContainer)) {
297             viewContainer.appendChild(activeViewContainer);
298         }
300         // Prevents needless shuffling around of nodes and maintains DOM order.
301         if (!container.contains(viewContainer) && !areSame) {
302             container.appendChild(viewContainer);
303         }
305         return this;
306     },
308     /**
309     Sets which view is active/visible for the application. This will set the
310     app's `activeView` attribute to the specified `view`.
312     When a string-name is provided for a view which has been registered on this
313     app's `views` object, the referenced metadata will be used and the
314     `activeView` will be set to either a preserved view instance, or a new
315     instance of the registered view will be created using the specified `config`
316     object passed-into this method.
318     A callback function can be specified as either the third or fourth argument,
319     and this function will be called after the new `view` becomes the
320     `activeView`, is rendered to the `viewContainer`, and is ready to use.
322     @example
323         var app = new Y.App({
324             views: {
325                 users: {
326                     // Imagine that `Y.UsersView` has been defined.
327                     type: Y.UsersView
328                 }
329             }
330         });
332         app.route('/users/', function () {
333             this.showView('users');
334         });
336         app.render();
337         app.navigate('/uses/'); // => Creates a new `Y.UsersView` and shows it.
339     @method showView
340     @param {String|View} view The name of a view defined in the `views` object,
341         or a view instance.
342     @param {Object} [config] Optional configuration to use when creating a new
343         view instance.
344     @param {Object} [options] Optional object containing any of the following
345         properties:
346       @param {Function} [options.callback] Optional callback function to call
347         after new `activeView` is ready to use, the function will be passed:
348           @param {View} options.callback.view A reference to the new
349             `activeView`.
350       @param {Boolean} [options.prepend] Whether the new view should be
351         prepended instead of appended to the `viewContainer`.
352     @param {Function} [callback] Optional callback Function to call after the
353         new `activeView` is ready to use. **Note:** this will override
354         `options.callback`. The function will be passed the following:
355       @param {View} callback.view A reference to the new `activeView`.
356     @chainable
357     @since 3.5.0
358     **/
359     showView: function (view, config, options, callback) {
360         var viewInfo;
362         if (Lang.isString(view)) {
363             viewInfo = this.getViewInfo(view);
365             // Use the preserved view instance, or create a new view.
366             // TODO: Maybe we can remove the strict check for `preserve` and
367             // assume we'll use a View instance if it is there, and just check
368             // `preserve` when detaching?
369             if (viewInfo && viewInfo.preserve && viewInfo.instance) {
370                 view = viewInfo.instance;
371                 // Make sure there's a mapping back to the view metadata.
372                 this._viewInfoMap[Y.stamp(view, true)] = viewInfo;
373             } else {
374                 view = this.createView(view, config);
375                 view.render();
376             }
377         }
379         // TODO: Add `options.update` to update to view with the `config`, if
380         // needed. This could also call `setAttrs()` when the specified `view`
381         // already a View instance. Is this be too much overloading of the API?
383         // TODO: Add `options.render` to provide a way to control whether a view
384         // is rendered or not; by default, `render()` will only be called if
385         // this method created the View.
387         options || (options = {});
389         if (callback) {
390             options.callback = callback;
391         } else if (Lang.isFunction(options)) {
392             options = {callback: options};
393         }
395         // TODO: Should the `callback` _always_ be called, even when the
396         // `activeView` does not change?
398         return this._set('activeView', view, {options: options});
399     },
401     // -- Protected Methods ----------------------------------------------------
403     /**
404     Helper method to attach the view instance to the application by making the
405     app a bubble target of the view, append the view to the `viewContainer`, and
406     assign it to the `instance` property of the associated view info metadata.
408     @method _attachView
409     @param {View} view View to attach.
410     @param {Boolean} prepend Whether the view should be prepended instead of
411       appended to the `viewContainer`.
412     @protected
413     @since 3.5.0
414     **/
415     _attachView: function (view, prepend) {
416         if (!view) {
417             return;
418         }
420         var viewInfo      = this.getViewInfo(view),
421             viewContainer = this.get('viewContainer');
423         view.addTarget(this);
424         viewInfo && (viewInfo.instance = view);
426         // TODO: Attach events here for persevered Views?
427         // See related TODO in `_detachView`.
429         // Insert view into the DOM.
430         viewContainer[prepend ? 'prepend' : 'append'](view.get('container'));
431     },
433     /**
434     Overrides View's container destruction to deal with the `viewContainer` and
435     checks to make sure not to remove and purge the `<body>`.
437     @method _destroyContainer
438     @protected
439     @see View._destroyContainer()
440     **/
441     _destroyContainer: function () {
442         var container     = this.get('container'),
443             viewContainer = this.get('viewContainer'),
444             areSame       = container.compareTo(viewContainer);
446         // We do not want to remove or destroy the `<body>`.
447         if (Y.one('body').compareTo(container)) {
448             // Just clean-up our events listeners.
449             this.detachEvents();
451             // Clean-up `yui3-app` CSS class on the `container`.
452             container && container.removeClass(App.CSS_CLASS);
454             if (areSame) {
455                 // Clean-up `yui3-app-views` CSS class on the `container`.
456                 container && container.removeClass(App.VIEWS_CSS_CLASS);
457             } else {
458                 // Destroy and purge the `viewContainer`.
459                 viewContainer && viewContainer.remove(true);
460             }
462             return;
463         }
465         // Remove and purge events from both containers.
466         viewContainer && viewContainer.remove(true);
467         !areSame && container && container.remove(true);
468     },
470     /**
471     Helper method to detach the view instance from the application by removing
472     the application as a bubble target of the view, and either just removing the
473     view if it is intended to be preserved, or destroying the instance
474     completely.
476     @method _detachView
477     @param {View} view View to detach.
478     @protected
479     @since 3.5.0
480     **/
481     _detachView: function (view) {
482         if (!view) {
483             return;
484         }
486         var viewInfo = this.getViewInfo(view) || {};
488         if (viewInfo.preserve) {
489             view.remove();
490             // TODO: Detach events here for preserved Views? It is possible that
491             // some event subscriptions are made on elements other than the
492             // View's `container`.
493         } else {
494             view.destroy({remove: true});
496             // TODO: The following should probably happen automagically from
497             // `destroy()` being called! Possibly `removeTarget()` as well.
499             // Remove from view to view-info map.
500             delete this._viewInfoMap[Y.stamp(view, true)];
502             // Remove from view-info instance property.
503             if (view === viewInfo.instance) {
504                 delete viewInfo.instance;
505             }
506         }
508         view.removeTarget(this);
509     },
511     /**
512     Getter for the `viewContainer` attribute.
514     @method _getViewContainer
515     @param {Node|null} value Current attribute value.
516     @return {Node} View container node.
517     @protected
518     @since 3.5.0
519     **/
520     _getViewContainer: function (value) {
521         // This wackiness is necessary to enable fully lazy creation of the
522         // container node both when no container is specified and when one is
523         // specified via a valueFn.
525         if (!value && !this._viewContainer) {
526             // Create a default container and set that as the new attribute
527             // value. The `this._viewContainer` property prevents infinite
528             // recursion.
529             value = this._viewContainer = this.create();
530             this._set('viewContainer', value);
531         }
533         return value;
534     },
536     /**
537     Gets the current full URL. When `html5` is false, the URL will first be
538     upgraded before it's returned.
540     @method _getURL
541     @return {String} URL.
542     @protected
543     @see Router._getURL()
544     **/
545     _getURL: function () {
546         var url = Y.getLocation().toString();
547         return this._html5 ? url : this._upgradeURL(url);
548     },
550     /**
551     Provides the default value for the `html5` attribute.
553     The value returned is dependent on the value of the `serverRouting`
554     attribute. When `serverRouting` is explicit set to `false` (not just falsy),
555     the default value for `html5` will be set to `false` for *all* browsers.
557     When `serverRouting` is `true` or `undefined` the returned value will be
558     dependent on the browser's capability of using HTML5 history.
560     @method _initHtml5
561     @return {Boolean} Whether or not HTML5 history should be used.
562     @protected
563     @since 3.5.0
564     **/
565     _initHtml5: function () {
566         // When `serverRouting` is explicitly set to `false` (not just falsy),
567         // forcing hash-based URLs in all browsers.
568         if (this.get('serverRouting') === false) {
569             return false;
570         } else {
571             return Router.html5;
572         }
573     },
575     /**
576     Determines if the specified `view` is configured as a child of the specified
577     `parent` view. This requires both views to be either named-views, or view
578     instances created using configuration data that exists in the `views`
579     object, e.g. created by the `createView()` or `showView()` method.
581     @method _isChildView
582     @param {View|String} view The name of a view defined in the `views` object,
583       or a view instance.
584     @param {View|String} parent The name of a view defined in the `views`
585       object, or a view instance.
586     @return {Boolean} Whether the view is configured as a child of the parent.
587     @protected
588     @since 3.5.0
589     **/
590     _isChildView: function (view, parent) {
591         var viewInfo   = this.getViewInfo(view),
592             parentInfo = this.getViewInfo(parent);
594         if (viewInfo && parentInfo) {
595             return this.getViewInfo(viewInfo.parent) === parentInfo;
596         }
598         return false;
599     },
601     /**
602     Determines if the specified `view` is configured as the parent of the
603     specified `child` view. This requires both views to be either named-views,
604     or view instances created using configuration data that exists in the
605     `views` object, e.g. created by the `createView()` or `showView()` method.
607     @method _isParentView
608     @param {View|String} view The name of a view defined in the `views` object,
609       or a view instance.
610     @param {View|String} parent The name of a view defined in the `views`
611       object, or a view instance.
612     @return {Boolean} Whether the view is configured as the parent of the child.
613     @protected
614     @since 3.5.0
615     **/
616     _isParentView: function (view, child) {
617         var viewInfo  = this.getViewInfo(view),
618             childInfo = this.getViewInfo(child);
620         if (viewInfo && childInfo) {
621             return this.getViewInfo(childInfo.parent) === viewInfo;
622         }
624         return false;
625     },
627     /**
628     Underlying implementation for `navigate()`.
630     @method _navigate
631     @param {String} url The fully-resolved URL that the app should dispatch to
632       its route handlers to fulfill the enhanced navigation "request", or use to
633       update `window.location` in non-HTML5 history capable browsers when
634       `serverRouting` is `true`.
635     @param {Object} [options] Additional options to configure the navigation.
636       These are mixed into the `navigate` event facade.
637         @param {Boolean} [options.replace] Whether or not the current history
638           entry will be replaced, or a new entry will be created. Will default
639           to `true` if the specified `url` is the same as the current URL.
640         @param {Boolean} [options.force] Whether the enhanced navigation
641           should occur even in browsers without HTML5 history. Will default to
642           `true` when `serverRouting` is falsy.
643     @protected
644     @see PjaxBase._navigate()
645     **/
646     _navigate: function (url, options) {
647         url = this._upgradeURL(url);
649         options || (options = {});
651         if (!this.get('serverRouting')) {
652             // Force navigation to be enhanced and handled by the app when
653             // `serverRouting` is falsy because the server might not be able to
654             // properly handle the request.
655             Lang.isValue(options.force) || (options.force = true);
656         }
658         return PjaxBase.prototype._navigate.call(this, url, options);
659     },
661     /**
662     Will either save a history entry using `pushState()` or the location hash,
663     or gracefully-degrade to sending a request to the server causing a full-page
664     reload.
666     Overrides Router's `_save()` method to preform graceful-degradation when the
667     app's `serverRouting` is `true` and `html5` is `false` by updating the full
668     URL via standard assignment to `window.location` or by calling
669     `window.location.replace()`; both of which will cause a request to the
670     server resulting in a full-page reload.
672     Otherwise this will just delegate off to Router's `_save()` method allowing
673     the client-side enhanced routing to occur.
675     @method _save
676     @param {String} [url] URL for the history entry.
677     @param {Boolean} [replace=false] If `true`, the current history entry will
678       be replaced instead of a new one being added.
679     @chainable
680     @protected
681     @see Router._save()
682     **/
683     _save: function (url, replace) {
684         // Forces full-path URLs to always be used by modifying
685         // `window.location` in non-HTML5 history capable browsers.
686         if (this.get('serverRouting') && !this.get('html5')) {
687             // Perform same-origin check on the specified URL.
688             if (!this._hasSameOrigin(url)) {
689                 Y.error('Security error: The new URL must be of the same origin as the current URL.');
690                 return this;
691             }
693             // Results in the URL's full path starting with '/'.
694             url = this._joinURL(url || '');
696             // Either replace the current history entry or create a new one
697             // while navigating to the `url`.
698             if (replace) {
699                 win && win.location.replace(url);
700             } else {
701                 win && (win.location = url);
702             }
704             return this;
705         }
707         return Router.prototype._save.apply(this, arguments);
708     },
710     /**
711     Performs the actual change of this app's `activeView` by attaching the
712     `newView` to this app, and detaching the `oldView` from this app using any
713     specified `options`.
715     The `newView` is attached to the app by rendering it to the `viewContainer`,
716     and making this app a bubble target of its events.
718     The `oldView` is detached from the app by removing it from the
719     `viewContainer`, and removing this app as a bubble target for its events.
720     The `oldView` will either be preserved or properly destroyed.
722     **Note:** The `activeView` attribute is read-only and can be changed by
723     calling the `showView()` method.
725     @method _uiSetActiveView
726     @param {View} newView The View which is now this app's `activeView`.
727     @param {View} [oldView] The View which was this app's `activeView`.
728     @param {Object} [options] Optional object containing any of the following
729         properties:
730       @param {Function} [options.callback] Optional callback function to call
731         after new `activeView` is ready to use, the function will be passed:
732           @param {View} options.callback.view A reference to the new
733             `activeView`.
734       @param {Boolean} [options.prepend] Whether the new view should be
735         prepended instead of appended to the `viewContainer`.
736     @protected
737     @since 3.5.0
738     **/
739     _uiSetActiveView: function (newView, oldView, options) {
740         options || (options = {});
742         var callback = options.callback,
743             isChild  = this._isChildView(newView, oldView),
744             isParent = !isChild && this._isParentView(newView, oldView),
745             prepend  = !!options.prepend || isParent;
747         // Prevent detaching (thus removing) the view we want to show. Also hard
748         // to animate out and in, the same view.
749         if (newView === oldView) {
750             return callback && callback.call(this, newView);
751         }
753         this._attachView(newView, prepend);
754         this._detachView(oldView);
756         callback && callback.call(this, newView);
757     },
759     /**
760     Upgrades a hash-based URL to a full-path URL, if necessary.
762     The specified `url` will be upgraded if its of the same origin as the
763     current URL and has a path-like hash. URLs that don't need upgrading will be
764     returned as-is.
766     @example
767         app._upgradeURL('http://example.com/#/foo/'); // => 'http://example.com/foo/';
769     @method _upgradeURL
770     @param {String} url The URL to upgrade from hash-based to full-path.
771     @return {String} The upgraded URL, or the specified URL untouched.
772     @protected
773     @since 3.5.0
774     **/
775     _upgradeURL: function (url) {
776         // We should not try to upgrade paths for external URLs.
777         if (!this._hasSameOrigin(url)) {
778             return url;
779         }
781         // TODO: Should the `root` be removed first, and the hash only
782         // considered if in the form of '/#/'?
783         var hash       = (url.match(/#(.*)$/) || [])[1] || '',
784             hashPrefix = Y.HistoryHash.hashPrefix;
786         // Strip any hash prefix, like hash-bangs.
787         if (hashPrefix && hash.indexOf(hashPrefix) === 0) {
788             hash = hash.replace(hashPrefix, '');
789         }
791         // If the hash looks like a URL path, assume it is, and upgrade it!
792         if (hash && hash.charAt(0) === '/') {
793             // Re-join with configured `root` before resolving.
794             url = this._resolveURL(this._joinURL(hash));
795         }
797         return url;
798     },
800     // -- Protected Event Handlers ---------------------------------------------
802     /**
803     Handles the application's `activeViewChange` event (which is fired when the
804     `activeView` attribute changes) by detaching the old view, attaching the new
805     view.
807     The `activeView` attribute is read-only, so the public API to change its
808     value is through the `showView()` method.
810     @method _afterActiveViewChange
811     @param {EventFacade} e
812     @protected
813     @since 3.5.0
814     **/
815     _afterActiveViewChange: function (e) {
816         this._uiSetActiveView(e.newVal, e.prevVal, e.options);
817     }
818 }, {
819     ATTRS: {
820         /**
821         The application's active/visible view.
823         This attribute is read-only, to set the `activeView` use the
824         `showView()` method.
826         @attribute activeView
827         @type View
828         @default null
829         @readOnly
830         @see AppBase.showView()
831         @since 3.5.0
832         **/
833         activeView: {
834             value   : null,
835             readOnly: true
836         },
838         /**
839         Container node which represents the application's bounding-box, into
840         which this app's content will be rendered.
842         The container node serves as the host for all DOM events attached by the
843         app. Delegation is used to handle events on children of the container,
844         allowing the container's contents to be re-rendered at any time without
845         losing event subscriptions.
847         The default container is the `<body>` Node, but you can override this in
848         a subclass, or by passing in a custom `container` config value at
849         instantiation time.
851         When `container` is overridden by a subclass or passed as a config
852         option at instantiation time, it may be provided as a selector string, a
853         DOM element, or a `Y.Node` instance. During initialization, this app's
854         `create()` method will be called to convert the container into a
855         `Y.Node` instance if it isn't one already and stamp it with the CSS
856         class: `"yui3-app"`.
858         The container is not added to the page automatically. This allows you to
859         have full control over how and when your app is actually rendered to
860         the page.
862         @attribute container
863         @type HTMLElement|Node|String
864         @default Y.one('body')
865         @initOnly
866         **/
867         container: {
868             valueFn: function () {
869                 return Y.one('body');
870             }
871         },
873         /**
874         Whether or not this browser is capable of using HTML5 history.
876         This value is dependent on the value of `serverRouting` and will default
877         accordingly.
879         Setting this to `false` will force the use of hash-based history even on
880         HTML5 browsers, but please don't do this unless you understand the
881         consequences.
883         @attribute html5
884         @type Boolean
885         @initOnly
886         @see serverRouting
887         **/
888         html5: {
889             valueFn: '_initHtml5'
890         },
892         /**
893         CSS selector string used to filter link click events so that only the
894         links which match it will have the enhanced-navigation behavior of pjax
895         applied.
897         When a link is clicked and that link matches this selector, navigating
898         to the link's `href` URL using the enhanced, pjax, behavior will be
899         attempted; and the browser's default way to navigate to new pages will
900         be the fallback.
902         By default this selector will match _all_ links on the page.
904         @attribute linkSelector
905         @type String|Function
906         @default "a"
907         **/
908         linkSelector: {
909             value: 'a'
910         },
912         /**
913         Whether or not this application's server is capable of properly routing
914         all requests and rendering the initial state in the HTML responses.
916         This can have three different values, each having particular
917         implications on how the app will handle routing and navigation:
919           * `undefined`: The best form of URLs will be chosen based on the
920             capabilities of the browser. Given no information about the server
921             environmentm a balanced approach to routing and navigation is
922             chosen.
924             The server should be capable of handling full-path requests, since
925             full-URLs will be generated by browsers using HTML5 history. If this
926             is a client-side-only app the server could handle full-URL requests
927             by sending a redirect back to the root with a hash-based URL, e.g:
929                 Request:     http://example.com/users/1
930                 Redirect to: http://example.com/#/users/1
932           * `true`: The server is *fully* capable of properly handling requests
933             to all full-path URLs the app can produce.
935             This is the best option for progressive-enhancement because it will
936             cause **all URLs to always have full-paths**, which means the server
937             will be able to accurately handle all URLs this app produces. e.g.
939                 http://example.com/users/1
941             To meet this strict full-URL requirement, browsers which are not
942             capable of using HTML5 history will make requests to the server
943             resulting in full-page reloads.
945           * `false`: The server is *not* capable of properly handling requests
946             to all full-path URLs the app can produce, therefore all routing
947             will be handled by this App instance.
949             Be aware that this will cause **all URLs to always be hash-based**,
950             even in browsers that are capable of using HTML5 history. e.g.
952                 http://example.com/#/users/1
954             A single-page or client-side-only app where the server sends a
955             "shell" page with JavaScript to the client might have this
956             restriction. If you're setting this to `false`, read the following:
958         **Note:** When this is set to `false`, the server will *never* receive
959         the full URL because browsers do not send the fragment-part to the
960         server, that is everything after and including the "#".
962         Consider the following example:
964             URL shown in browser: http://example.com/#/users/1
965             URL sent to server:   http://example.com/
967         You should feel bad about hurting our precious web if you forcefully set
968         either `serverRouting` or `html5` to `false`, because you're basically
969         punching the web in the face here with your lossy URLs! Please make sure
970         you know what you're doing and that you understand the implications.
972         Ideally you should always prefer full-path URLs (not /#/foo/), and want
973         full-page reloads when the client's browser is not capable of enhancing
974         the experience using the HTML5 history APIs. Setting this to `true` is
975         the best option for progressive-enhancement (and graceful-degradation).
977         @attribute serverRouting
978         @type Boolean
979         @default undefined
980         @initOnly
981         @since 3.5.0
982         **/
983         serverRouting: {
984             value    : undefined,
985             writeOnce: 'initOnly'
986         },
988         /**
989         The node into which this app's `views` will be rendered when they become
990         the `activeView`.
992         The view container node serves as the container to hold the app's
993         `activeView`. Each time the `activeView` is set via `showView()`, the
994         previous view will be removed from this node, and the new active view's
995         `container` node will be appended.
997         The default view container is a `<div>` Node, but you can override this
998         in a subclass, or by passing in a custom `viewContainer` config value at
999         instantiation time. The `viewContainer` may be provided as a selector
1000         string, DOM element, or a `Y.Node` instance (having the `viewContainer`
1001         and the `container` be the same node is also supported).
1003         The app's `render()` method will stamp the view container with the CSS
1004         class `"yui3-app-views"` and append it to the app's `container` node if
1005         it isn't already, and any `activeView` will be appended to this node if
1006         it isn't already.
1008         @attribute viewContainer
1009         @type HTMLElement|Node|String
1010         @default Y.Node.create(this.containerTemplate)
1011         @initOnly
1012         @since 3.5.0
1013         **/
1014         viewContainer: {
1015             getter   : '_getViewContainer',
1016             setter   : Y.one,
1017             writeOnce: true
1018         }
1019     },
1021     // TODO: Should these go on the `prototype`?
1022     // TODO: These should also just go in a `CLASS_NAMES` object.
1024     /**
1025     CSS class added to an app's `container` node.
1027     @property CSS_CLASS
1028     @type String
1029     @default "yui3-app"
1030     @static
1031     @since 3.5.0
1032     **/
1033     CSS_CLASS: getClassName('app'),
1035     /**
1036     CSS class added to an app's `viewContainer` node.
1038     @property VIEWS_CSS_CLASS
1039     @type String
1040     @default "yui3-app-views"
1041     @static
1042     @since 3.5.0
1043     **/
1044     VIEWS_CSS_CLASS: getClassName('app', 'views'),
1046     /**
1047     Properties that shouldn't be turned into ad-hoc attributes when passed to
1048     App's constructor.
1050     @property _NON_ATTRS_CFG
1051     @type Array
1052     @static
1053     @protected
1054     @since 3.5.0
1055     **/
1056     _NON_ATTRS_CFG: ['views']
1059 // -- Namespace ----------------------------------------------------------------
1060 Y.namespace('App').Base = App;
1063 Provides a top-level application component which manages navigation and views.
1065 This gives you a foundation and structure on which to build your application; it
1066 combines robust URL navigation with powerful routing and flexible view
1067 management.
1069 `Y.App` is both a namespace and constructor function. The `Y.App` class is
1070 special in that any `Y.App` class extensions that are included in the YUI
1071 instance will be **auto-mixed** on to the `Y.App` class. Consider this example:
1073     YUI().use('app-base', 'app-transitions', function (Y) {
1074         // This will create two YUI Apps, `basicApp` will not have transitions,
1075         // but `fancyApp` will have transitions support included and turn it on.
1076         var basicApp = new Y.App.Base(),
1077             fancyApp = new Y.App({transitions: true});
1078     });
1080 @class App
1081 @param {Object} [config] The following are configuration properties that can be
1082     specified _in addition_ to default attribute values and the non-attribute
1083     properties provided by `Y.Base`:
1084   @param {Object} [config.views] Hash of view-name to metadata used to
1085     declaratively describe an application's views and their relationship with
1086     the app and other views. The views specified here will override any defaults
1087     provided by the `views` object on the `prototype`.
1088 @constructor
1089 @extends App.Base
1090 @uses App.Transitions
1091 @since 3.5.0
1093 Y.App = Y.mix(Y.Base.create('app', Y.App.Base, []), Y.App, true);
1096 }, '3.5.0' ,{requires:['classnamemanager', 'pjax-base', 'router', 'view']});