NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / history-base / history-base-debug.js
blob5f52de62c1f2671eeda4eb1ef4a9b5b026bedddf
1 /*
2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
8 YUI.add('history-base', function (Y, NAME) {
10 /**
11  * Provides browser history management functionality using a simple
12  * add/replace/get paradigm. This can be used to ensure that the browser's back
13  * and forward buttons work as the user expects and to provide bookmarkable URLs
14  * that return the user to the current application state, even in an Ajax
15  * application that doesn't perform full-page refreshes.
16  *
17  * @module history
18  * @main history
19  * @since 3.2.0
20  */
22 /**
23  * Provides global state management backed by an object, but with no browser
24  * history integration. For actual browser history integration and back/forward
25  * support, use the history-html5 or history-hash modules.
26  *
27  * @module history
28  * @submodule history-base
29  * @class HistoryBase
30  * @uses EventTarget
31  * @constructor
32  * @param {Object} config (optional) configuration object, which may contain
33  *   zero or more of the following properties:
34  *
35  * <dl>
36  *   <dt>force (Boolean)</dt>
37  *   <dd>
38  *     If `true`, a `history:change` event will be fired whenever the URL
39  *     changes, even if there is no associated state change. Default is `false`.
40  *   </dd>
41  *
42  *   <dt>initialState (Object)</dt>
43  *   <dd>
44  *     Initial state to set, as an object hash of key/value pairs. This will be
45  *     merged into the current global state.
46  *   </dd>
47  * </dl>
48  */
50 var Lang      = Y.Lang,
51     Obj       = Y.Object,
52     GlobalEnv = YUI.namespace('Env.History'),
53     YArray    = Y.Array,
55     doc       = Y.config.doc,
56     docMode   = doc.documentMode,
57     win       = Y.config.win,
59     DEFAULT_OPTIONS = {merge: true},
60     EVT_CHANGE      = 'change',
61     SRC_ADD         = 'add',
62     SRC_REPLACE     = 'replace';
64 function HistoryBase() {
65     this._init.apply(this, arguments);
68 Y.augment(HistoryBase, Y.EventTarget, null, null, {
69     emitFacade : true,
70     prefix     : 'history',
71     preventable: false,
72     queueable  : true
73 });
75 if (!GlobalEnv._state) {
76     GlobalEnv._state = {};
79 // -- Private Methods ----------------------------------------------------------
81 /**
82  * Returns <code>true</code> if <i>value</i> is a simple object and not a
83  * function or an array.
84  *
85  * @method _isSimpleObject
86  * @param {mixed} value
87  * @return {Boolean}
88  * @private
89  */
90 function _isSimpleObject(value) {
91     return Lang.type(value) === 'object';
94 // -- Public Static Properties -------------------------------------------------
96 /**
97  * Name of this component.
98  *
99  * @property NAME
100  * @type String
101  * @static
102  */
103 HistoryBase.NAME = 'historyBase';
106  * Constant used to identify state changes originating from the
107  * <code>add()</code> method.
109  * @property SRC_ADD
110  * @type String
111  * @static
112  * @final
113  */
114 HistoryBase.SRC_ADD = SRC_ADD;
117  * Constant used to identify state changes originating from the
118  * <code>replace()</code> method.
120  * @property SRC_REPLACE
121  * @type String
122  * @static
123  * @final
124  */
125 HistoryBase.SRC_REPLACE = SRC_REPLACE;
128  * Whether or not this browser supports the HTML5 History API.
130  * @property html5
131  * @type Boolean
132  * @static
133  */
135 // All HTML5-capable browsers except Gecko 2+ (Firefox 4+) correctly return
136 // true for 'onpopstate' in win. In order to support Gecko 2, we fall back to a
137 // UA sniff for now. (current as of Firefox 4.0b2)
138 HistoryBase.html5 = !!(win.history && win.history.pushState &&
139         win.history.replaceState && ('onpopstate' in win || Y.UA.gecko >= 2) &&
140         (!Y.UA.android || Y.UA.android >= 2.4));
143  * Whether or not this browser supports the <code>window.onhashchange</code>
144  * event natively. Note that even if this is <code>true</code>, you may
145  * still want to use HistoryHash's synthetic <code>hashchange</code> event
146  * since it normalizes implementation differences and fixes spec violations
147  * across various browsers.
149  * @property nativeHashChange
150  * @type Boolean
151  * @static
152  */
154 // Most browsers that support hashchange expose it on the window. Opera 10.6+
155 // exposes it on the document (but you can still attach to it on the window).
157 // IE8 supports the hashchange event, but only in IE8 Standards
158 // Mode. However, IE8 in IE7 compatibility mode still defines the
159 // event but never fires it, so we can't just detect the event. We also can't
160 // just UA sniff for IE8, since other browsers support this event as well.
161 HistoryBase.nativeHashChange = ('onhashchange' in win || 'onhashchange' in doc) &&
162         (!docMode || docMode > 7);
164 Y.mix(HistoryBase.prototype, {
165     // -- Initialization -------------------------------------------------------
167     /**
168      * Initializes this HistoryBase instance. This method is called by the
169      * constructor.
170      *
171      * @method _init
172      * @param {Object} config configuration object
173      * @protected
174      */
175     _init: function (config) {
176         var initialState;
178         /**
179          * Configuration object provided by the user on instantiation, or an
180          * empty object if one wasn't provided.
181          *
182          * @property _config
183          * @type Object
184          * @default {}
185          * @protected
186          */
187         config = this._config = config || {};
189         /**
190          * If `true`, a `history:change` event will be fired whenever the URL
191          * changes, even if there is no associated state change.
192          *
193          * @property force
194          * @type Boolean
195          * @default false
196          */
197          this.force = !!config.force;
199         /**
200          * Resolved initial state: a merge of the user-supplied initial state
201          * (if any) and any initial state provided by a subclass. This may
202          * differ from <code>_config.initialState</code>. If neither the config
203          * nor a subclass supplies an initial state, this property will be
204          * <code>null</code>.
205          *
206          * @property _initialState
207          * @type Object|null
208          * @default {}
209          * @protected
210          */
211         initialState = this._initialState = this._initialState ||
212                 config.initialState || null;
214         /**
215          * Fired when the state changes. To be notified of all state changes
216          * regardless of the History or YUI instance that generated them,
217          * subscribe to this event on <code>Y.Global</code>. If you would rather
218          * be notified only about changes generated by this specific History
219          * instance, subscribe to this event on the instance.
220          *
221          * @event history:change
222          * @param {EventFacade} e Event facade with the following additional
223          *   properties:
224          *
225          * <dl>
226          *   <dt>changed (Object)</dt>
227          *   <dd>
228          *     Object hash of state items that have been added or changed. The
229          *     key is the item key, and the value is an object containing
230          *     <code>newVal</code> and <code>prevVal</code> properties
231          *     representing the values of the item both before and after the
232          *     change. If the item was newly added, <code>prevVal</code> will be
233          *     <code>undefined</code>.
234          *   </dd>
235          *
236          *   <dt>newVal (Object)</dt>
237          *   <dd>
238          *     Object hash of key/value pairs of all state items after the
239          *     change.
240          *   </dd>
241          *
242          *   <dt>prevVal (Object)</dt>
243          *   <dd>
244          *     Object hash of key/value pairs of all state items before the
245          *     change.
246          *   </dd>
247          *
248          *   <dt>removed (Object)</dt>
249          *   <dd>
250          *     Object hash of key/value pairs of state items that have been
251          *     removed. Values are the old values prior to removal.
252          *   </dd>
253          *
254          *   <dt>src (String)</dt>
255          *   <dd>
256          *     The source of the event. This can be used to selectively ignore
257          *     events generated by certain sources.
258          *   </dd>
259          * </dl>
260          */
261         this.publish(EVT_CHANGE, {
262             broadcast: 2,
263             defaultFn: this._defChangeFn
264         });
266         // If initialState was provided, merge it into the current state.
267         if (initialState) {
268             this.replace(initialState);
269         }
270     },
272     // -- Public Methods -------------------------------------------------------
274     /**
275      * Adds a state entry with new values for the specified keys. By default,
276      * the new state will be merged into the existing state, and new values will
277      * override existing values. Specifying a <code>null</code> or
278      * <code>undefined</code> value will cause that key to be removed from the
279      * new state entry.
280      *
281      * @method add
282      * @param {Object} state Object hash of key/value pairs.
283      * @param {Object} options (optional) Zero or more of the following options:
284      *   <dl>
285      *     <dt>merge (Boolean)</dt>
286      *     <dd>
287      *       <p>
288      *       If <code>true</code> (the default), the new state will be merged
289      *       into the existing state. New values will override existing values,
290      *       and <code>null</code> or <code>undefined</code> values will be
291      *       removed from the state.
292      *       </p>
293      *
294      *       <p>
295      *       If <code>false</code>, the existing state will be discarded as a
296      *       whole and the new state will take its place.
297      *       </p>
298      *     </dd>
299      *   </dl>
300      * @chainable
301      */
302     add: function () {
303         var args = YArray(arguments, 0, true);
304         args.unshift(SRC_ADD);
305         return this._change.apply(this, args);
306     },
308     /**
309      * Adds a state entry with a new value for a single key. By default, the new
310      * value will be merged into the existing state values, and will override an
311      * existing value with the same key if there is one. Specifying a
312      * <code>null</code> or <code>undefined</code> value will cause the key to
313      * be removed from the new state entry.
314      *
315      * @method addValue
316      * @param {String} key State parameter key.
317      * @param {String} value New value.
318      * @param {Object} options (optional) Zero or more options. See
319      *   <code>add()</code> for a list of supported options.
320      * @chainable
321      */
322     addValue: function (key, value, options) {
323         var state = {};
324         state[key] = value;
325         return this._change(SRC_ADD, state, options);
326     },
328     /**
329      * Returns the current value of the state parameter specified by <i>key</i>,
330      * or an object hash of key/value pairs for all current state parameters if
331      * no key is specified.
332      *
333      * @method get
334      * @param {String} key (optional) State parameter key.
335      * @return {Object|String} Value of the specified state parameter, or an
336      *   object hash of key/value pairs for all current state parameters.
337      */
338     get: function (key) {
339         var state    = GlobalEnv._state,
340             isObject = _isSimpleObject(state);
342         if (key) {
343             return isObject && Obj.owns(state, key) ? state[key] : undefined;
344         } else {
345             return isObject ? Y.mix({}, state, true) : state; // mix provides a fast shallow clone.
346         }
347     },
349     /**
350      * Same as <code>add()</code> except that a new browser history entry will
351      * not be created. Instead, the current history entry will be replaced with
352      * the new state.
353      *
354      * @method replace
355      * @param {Object} state Object hash of key/value pairs.
356      * @param {Object} options (optional) Zero or more options. See
357      *   <code>add()</code> for a list of supported options.
358      * @chainable
359      */
360     replace: function () {
361         var args = YArray(arguments, 0, true);
362         args.unshift(SRC_REPLACE);
363         return this._change.apply(this, args);
364     },
366     /**
367      * Same as <code>addValue()</code> except that a new browser history entry
368      * will not be created. Instead, the current history entry will be replaced
369      * with the new state.
370      *
371      * @method replaceValue
372      * @param {String} key State parameter key.
373      * @param {String} value New value.
374      * @param {Object} options (optional) Zero or more options. See
375      *   <code>add()</code> for a list of supported options.
376      * @chainable
377      */
378     replaceValue: function (key, value, options) {
379         var state = {};
380         state[key] = value;
381         return this._change(SRC_REPLACE, state, options);
382     },
384     // -- Protected Methods ----------------------------------------------------
386     /**
387      * Changes the state. This method provides a common implementation shared by
388      * the public methods for changing state.
389      *
390      * @method _change
391      * @param {String} src Source of the change, for inclusion in event facades
392      *   to facilitate filtering.
393      * @param {Object} state Object hash of key/value pairs.
394      * @param {Object} options (optional) Zero or more options. See
395      *   <code>add()</code> for a list of supported options.
396      * @protected
397      * @chainable
398      */
399     _change: function (src, state, options) {
400         options = options ? Y.merge(DEFAULT_OPTIONS, options) : DEFAULT_OPTIONS;
402         if (options.merge && _isSimpleObject(state) &&
403                 _isSimpleObject(GlobalEnv._state)) {
404             state = Y.merge(GlobalEnv._state, state);
405         }
407         this._resolveChanges(src, state, options);
408         return this;
409     },
411     /**
412      * Called by _resolveChanges() when the state has changed. This method takes
413      * care of actually firing the necessary events.
414      *
415      * @method _fireEvents
416      * @param {String} src Source of the changes, for inclusion in event facades
417      *   to facilitate filtering.
418      * @param {Object} changes Resolved changes.
419      * @param {Object} options Zero or more options. See <code>add()</code> for
420      *   a list of supported options.
421      * @protected
422      */
423     _fireEvents: function (src, changes, options) {
424         // Fire the global change event.
425         this.fire(EVT_CHANGE, {
426             _options: options,
427             changed : changes.changed,
428             newVal  : changes.newState,
429             prevVal : changes.prevState,
430             removed : changes.removed,
431             src     : src
432         });
434         // Fire change/remove events for individual items.
435         Obj.each(changes.changed, function (value, key) {
436             this._fireChangeEvent(src, key, value);
437         }, this);
439         Obj.each(changes.removed, function (value, key) {
440             this._fireRemoveEvent(src, key, value);
441         }, this);
442     },
444     /**
445      * Fires a dynamic "[key]Change" event.
446      *
447      * @method _fireChangeEvent
448      * @param {String} src source of the change, for inclusion in event facades
449      *   to facilitate filtering
450      * @param {String} key key of the item that was changed
451      * @param {Object} value object hash containing <i>newVal</i> and
452      *   <i>prevVal</i> properties for the changed item
453      * @protected
454      */
455     _fireChangeEvent: function (src, key, value) {
456         /**
457          * <p>
458          * Dynamic event fired when an individual history item is added or
459          * changed. The name of this event depends on the name of the key that
460          * changed. To listen to change events for a key named "foo", subscribe
461          * to the <code>fooChange</code> event; for a key named "bar", subscribe
462          * to <code>barChange</code>, etc.
463          * </p>
464          *
465          * <p>
466          * Key-specific events are only fired for instance-level changes; that
467          * is, changes that were made via the same History instance on which the
468          * event is subscribed. To be notified of changes made by other History
469          * instances, subscribe to the global <code>history:change</code> event.
470          * </p>
471          *
472          * @event [key]Change
473          * @param {EventFacade} e Event facade with the following additional
474          *   properties:
475          *
476          * <dl>
477          *   <dt>newVal (mixed)</dt>
478          *   <dd>
479          *     The new value of the item after the change.
480          *   </dd>
481          *
482          *   <dt>prevVal (mixed)</dt>
483          *   <dd>
484          *     The previous value of the item before the change, or
485          *     <code>undefined</code> if the item was just added and has no
486          *     previous value.
487          *   </dd>
488          *
489          *   <dt>src (String)</dt>
490          *   <dd>
491          *     The source of the event. This can be used to selectively ignore
492          *     events generated by certain sources.
493          *   </dd>
494          * </dl>
495          */
496         this.fire(key + 'Change', {
497             newVal : value.newVal,
498             prevVal: value.prevVal,
499             src    : src
500         });
501     },
503     /**
504      * Fires a dynamic "[key]Remove" event.
505      *
506      * @method _fireRemoveEvent
507      * @param {String} src source of the change, for inclusion in event facades
508      *   to facilitate filtering
509      * @param {String} key key of the item that was removed
510      * @param {mixed} value value of the item prior to its removal
511      * @protected
512      */
513     _fireRemoveEvent: function (src, key, value) {
514         /**
515          * <p>
516          * Dynamic event fired when an individual history item is removed. The
517          * name of this event depends on the name of the key that was removed.
518          * To listen to remove events for a key named "foo", subscribe to the
519          * <code>fooRemove</code> event; for a key named "bar", subscribe to
520          * <code>barRemove</code>, etc.
521          * </p>
522          *
523          * <p>
524          * Key-specific events are only fired for instance-level changes; that
525          * is, changes that were made via the same History instance on which the
526          * event is subscribed. To be notified of changes made by other History
527          * instances, subscribe to the global <code>history:change</code> event.
528          * </p>
529          *
530          * @event [key]Remove
531          * @param {EventFacade} e Event facade with the following additional
532          *   properties:
533          *
534          * <dl>
535          *   <dt>prevVal (mixed)</dt>
536          *   <dd>
537          *     The value of the item before it was removed.
538          *   </dd>
539          *
540          *   <dt>src (String)</dt>
541          *   <dd>
542          *     The source of the event. This can be used to selectively ignore
543          *     events generated by certain sources.
544          *   </dd>
545          * </dl>
546          */
547         this.fire(key + 'Remove', {
548             prevVal: value,
549             src    : src
550         });
551     },
553     /**
554      * Resolves the changes (if any) between <i>newState</i> and the current
555      * state and fires appropriate events if things have changed.
556      *
557      * @method _resolveChanges
558      * @param {String} src source of the changes, for inclusion in event facades
559      *   to facilitate filtering
560      * @param {Object} newState object hash of key/value pairs representing the
561      *   new state
562      * @param {Object} options Zero or more options. See <code>add()</code> for
563      *   a list of supported options.
564      * @protected
565      */
566     _resolveChanges: function (src, newState, options) {
567         var changed   = {},
568             isChanged,
569             prevState = GlobalEnv._state,
570             removed   = {};
572         newState || (newState = {});
573         options  || (options  = {});
575         if (_isSimpleObject(newState) && _isSimpleObject(prevState)) {
576             // Figure out what was added or changed.
577             Obj.each(newState, function (newVal, key) {
578                 var prevVal = prevState[key];
580                 if (newVal !== prevVal) {
581                     changed[key] = {
582                         newVal : newVal,
583                         prevVal: prevVal
584                     };
586                     isChanged = true;
587                 }
588             }, this);
590             // Figure out what was removed.
591             Obj.each(prevState, function (prevVal, key) {
592                 if (!Obj.owns(newState, key) || newState[key] === null) {
593                     delete newState[key];
594                     removed[key] = prevVal;
595                     isChanged = true;
596                 }
597             }, this);
598         } else {
599             isChanged = newState !== prevState;
600         }
602         if (isChanged || this.force) {
603             this._fireEvents(src, {
604                 changed  : changed,
605                 newState : newState,
606                 prevState: prevState,
607                 removed  : removed
608             }, options);
609         }
610     },
612     /**
613      * Stores the specified state. Don't call this method directly; go through
614      * _resolveChanges() to ensure that changes are resolved and all events are
615      * fired properly.
616      *
617      * @method _storeState
618      * @param {String} src source of the changes
619      * @param {Object} newState new state to store
620      * @param {Object} options Zero or more options. See <code>add()</code> for
621      *   a list of supported options.
622      * @protected
623      */
624     _storeState: function (src, newState) {
625         // Note: the src and options params aren't used here, but they are used
626         // by subclasses.
627         GlobalEnv._state = newState || {};
628     },
630     // -- Protected Event Handlers ---------------------------------------------
632     /**
633      * Default <code>history:change</code> event handler.
634      *
635      * @method _defChangeFn
636      * @param {EventFacade} e state change event facade
637      * @protected
638      */
639     _defChangeFn: function (e) {
640         this._storeState(e.src, e.newVal, e._options);
641     }
642 }, true);
644 Y.HistoryBase = HistoryBase;
647 }, '3.13.0', {"requires": ["event-custom-complex"]});