Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('pjax-base', function (Y, NAME) {
2
 
3
/**
4
`Y.Router` extension that provides the core plumbing for enhanced navigation
5
implemented using the pjax technique (HTML5 pushState + Ajax).
6
 
7
@module pjax
8
@submodule pjax-base
9
@since 3.5.0
10
**/
11
 
12
var win = Y.config.win,
13
 
14
    // The CSS class name used to filter link clicks from only the links which
15
    // the pjax enhanced navigation should be used.
16
    CLASS_PJAX = Y.ClassNameManager.getClassName('pjax'),
17
 
18
    /**
19
    Fired when navigating to a URL via Pjax.
20
 
21
    When the `navigate()` method is called or a pjax link is clicked, this event
22
    will be fired if the browser supports HTML5 history _and_ the router has a
23
    route handler for the specified URL.
24
 
25
    This is a useful event to listen to for adding a visual loading indicator
26
    while the route handlers are busy handling the URL change.
27
 
28
    @event navigate
29
    @param {String} url The URL that the router will dispatch to its route
30
      handlers in order to fulfill the enhanced navigation "request".
31
    @param {Boolean} [force=false] Whether the enhanced navigation should occur
32
      even in browsers without HTML5 history.
33
    @param {String} [hash] The hash-fragment (including "#") of the `url`. This
34
      will be present when the `url` differs from the current URL only by its
35
      hash and `navigateOnHash` has been set to `true`.
36
    @param {Event} [originEvent] The event that caused the navigation. Usually
37
      this would be a click event from a "pjax" anchor element.
38
    @param {Boolean} [replace] Whether or not the current history entry will be
39
      replaced, or a new entry will be created. Will default to `true` if the
40
      specified `url` is the same as the current URL.
41
    @since 3.5.0
42
    **/
43
    EVT_NAVIGATE = 'navigate';
44
 
45
/**
46
`Y.Router` extension that provides the core plumbing for enhanced navigation
47
implemented using the pjax technique (HTML5 `pushState` + Ajax).
48
 
49
This makes it easy to enhance the navigation between the URLs of an application
50
in HTML5 history capable browsers by delegating to the router to fulfill the
51
"request" and seamlessly falling-back to using standard full-page reloads in
52
older, less-capable browsers.
53
 
54
The `PjaxBase` class isn't useful on its own, but can be mixed into a
55
`Router`-based class to add Pjax functionality to that Router. For a pre-made
56
standalone Pjax router, see the `Pjax` class.
57
 
58
    var MyRouter = Y.Base.create('myRouter', Y.Router, [Y.PjaxBase], {
59
        // ...
60
    });
61
 
62
@class PjaxBase
63
@extensionfor Router
64
@since 3.5.0
65
**/
66
function PjaxBase() {}
67
 
68
PjaxBase.prototype = {
69
    // -- Protected Properties -------------------------------------------------
70
 
71
    /**
72
    Holds the delegated pjax-link click handler.
73
 
74
    @property _pjaxEvents
75
    @type EventHandle
76
    @protected
77
    @since 3.5.0
78
    **/
79
 
80
    // -- Lifecycle Methods ----------------------------------------------------
81
    initializer: function () {
82
        this.publish(EVT_NAVIGATE, {defaultFn: this._defNavigateFn});
83
 
84
        // Pjax is all about progressively enhancing the navigation between
85
        // "pages", so by default we only want to handle and route link clicks
86
        // in HTML5 `pushState`-compatible browsers.
87
        if (this.get('html5')) {
88
            this._pjaxBindUI();
89
        }
90
    },
91
 
92
    destructor: function () {
93
        if (this._pjaxEvents) {
94
            this._pjaxEvents.detach();
95
        }
96
    },
97
 
98
    // -- Public Methods -------------------------------------------------------
99
 
100
    /**
101
    Navigates to the specified URL if there is a route handler that matches. In
102
    browsers capable of using HTML5 history, the navigation will be enhanced by
103
    firing the `navigate` event and having the router handle the "request".
104
    Non-HTML5 browsers will navigate to the new URL via manipulation of
105
    `window.location`.
106
 
107
    When there is a route handler for the specified URL and it is being
108
    navigated to, this method will return `true`, otherwise it will return
109
    `false`.
110
 
111
    **Note:** The specified URL _must_ be of the same origin as the current URL,
112
    otherwise an error will be logged and navigation will not occur. This is
113
    intended as both a security constraint and a purposely imposed limitation as
114
    it does not make sense to tell the router to navigate to a URL on a
115
    different scheme, host, or port.
116
 
117
    @method navigate
118
    @param {String} url The URL to navigate to. This must be of the same origin
119
      as the current URL.
120
    @param {Object} [options] Additional options to configure the navigation.
121
      These are mixed into the `navigate` event facade.
122
        @param {Boolean} [options.replace] Whether or not the current history
123
          entry will be replaced, or a new entry will be created. Will default
124
          to `true` if the specified `url` is the same as the current URL.
125
        @param {Boolean} [options.force=false] Whether the enhanced navigation
126
          should occur even in browsers without HTML5 history.
127
    @return {Boolean} `true` if the URL was navigated to, `false` otherwise.
128
    @since 3.5.0
129
    **/
130
    navigate: function (url, options) {
131
        // The `_navigate()` method expects fully-resolved URLs.
132
        url = this._resolveURL(url);
133
 
134
        if (this._navigate(url, options)) {
135
            return true;
136
        }
137
 
138
        if (!this._hasSameOrigin(url)) {
139
            Y.error('Security error: The new URL must be of the same origin as the current URL.');
140
            return false;
141
        }
142
 
143
        if (this.get('allowFallThrough')) {
144
            // Send paths with the same origin but no matching routes to window.location if specified.
145
            win.location = url;
146
            return true;
147
        }
148
 
149
        return false;
150
    },
151
 
152
    // -- Protected Methods ----------------------------------------------------
153
 
154
    /**
155
    Utility method to test whether a specified link/anchor node's `href` is of
156
    the same origin as the page's current location.
157
 
158
    This normalize browser inconsistencies with how the `port` is reported for
159
    anchor elements (IE reports a value for the default port, e.g. "80").
160
 
161
    @method _isLinkSameOrigin
162
    @param {Node} link The anchor element to test whether its `href` is of the
163
        same origin as the page's current location.
164
    @return {Boolean} Whether or not the link's `href` is of the same origin as
165
        the page's current location.
166
    @protected
167
    @since 3.6.0
168
    **/
169
    _isLinkSameOrigin: function (link) {
170
        var location = Y.getLocation(),
171
            protocol = location.protocol,
172
            hostname = location.hostname,
173
            port     = parseInt(location.port, 10) || null,
174
            linkPort;
175
 
176
        // Link must have the same `protocol` and `hostname` as the page's
177
        // currrent location.
178
        if (link.get('protocol') !== protocol ||
179
                link.get('hostname') !== hostname) {
180
 
181
            return false;
182
        }
183
 
184
        linkPort = parseInt(link.get('port'), 10) || null;
185
 
186
        // Normalize ports. In most cases browsers use an empty string when the
187
        // port is the default port, but IE does weird things with anchor
188
        // elements, so to be sure, this will re-assign the default ports before
189
        // they are compared.
190
        if (protocol === 'http:') {
191
            port     || (port     = 80);
192
            linkPort || (linkPort = 80);
193
        } else if (protocol === 'https:') {
194
            port     || (port     = 443);
195
            linkPort || (linkPort = 443);
196
        }
197
 
198
        // Finally, to be from the same origin, the link's `port` must match the
199
        // page's current `port`.
200
        return linkPort === port;
201
    },
202
 
203
    /**
204
    Underlying implementation for `navigate()`.
205
 
206
    @method _navigate
207
    @param {String} url The fully-resolved URL that the router should dispatch
208
      to its route handlers to fulfill the enhanced navigation "request", or use
209
      to update `window.location` in non-HTML5 history capable browsers.
210
    @param {Object} [options] Additional options to configure the navigation.
211
      These are mixed into the `navigate` event facade.
212
        @param {Boolean} [options.replace] Whether or not the current history
213
          entry will be replaced, or a new entry will be created. Will default
214
          to `true` if the specified `url` is the same as the current URL.
215
        @param {Boolean} [options.force=false] Whether the enhanced navigation
216
          should occur even in browsers without HTML5 history.
217
    @return {Boolean} `true` if the URL was navigated to, `false` otherwise.
218
    @protected
219
    @since 3.5.0
220
    **/
221
    _navigate: function (url, options) {
222
        url = this._upgradeURL(url);
223
 
224
        // Navigation can only be enhanced if there is a route-handler.
225
        if (!this.hasRoute(url)) {
226
            return false;
227
        }
228
 
229
        // Make a copy of `options` before modifying it.
230
        options = Y.merge(options, {url: url});
231
 
232
        var currentURL = this._getURL(),
233
            hash, hashlessURL;
234
 
235
        // Captures the `url`'s hash and returns a URL without that hash.
236
        hashlessURL = url.replace(/(#.*)$/, function (u, h, i) {
237
            hash = h;
238
            return u.substring(i);
239
        });
240
 
241
        if (hash && hashlessURL === currentURL.replace(/#.*$/, '')) {
242
            // When the specified `url` and current URL only differ by the hash,
243
            // the browser should handle this in-page navigation normally.
244
            if (!this.get('navigateOnHash')) {
245
                return false;
246
            }
247
 
248
            options.hash = hash;
249
        }
250
 
251
        // When navigating to the same URL as the current URL, behave like a
252
        // browser and replace the history entry instead of creating a new one.
253
        'replace' in options || (options.replace = url === currentURL);
254
 
255
        // The `navigate` event will only fire and therefore enhance the
256
        // navigation to the new URL in HTML5 history enabled browsers or when
257
        // forced. Otherwise it will fallback to assigning or replacing the URL
258
        // on `window.location`.
259
        if (this.get('html5') || options.force) {
260
            this.fire(EVT_NAVIGATE, options);
261
        } else if (win) {
262
            if (options.replace) {
263
                win.location.replace(url);
264
            } else {
265
                win.location = url;
266
            }
267
        }
268
 
269
        return true;
270
    },
271
 
272
    /**
273
    Binds the delegation of link-click events that match the `linkSelector` to
274
    the `_onLinkClick()` handler.
275
 
276
    By default this method will only be called if the browser is capable of
277
    using HTML5 history.
278
 
279
    @method _pjaxBindUI
280
    @protected
281
    @since 3.5.0
282
    **/
283
    _pjaxBindUI: function () {
284
        // Only bind link if we haven't already.
285
        if (!this._pjaxEvents) {
286
            this._pjaxEvents = Y.one('body').delegate('click',
287
                this._onLinkClick, this.get('linkSelector'), this);
288
        }
289
    },
290
 
291
    // -- Protected Event Handlers ---------------------------------------------
292
 
293
    /**
294
    Default handler for the `navigate` event.
295
 
296
    Adds a new history entry or replaces the current entry for the specified URL
297
    and will scroll the page to the top if configured to do so.
298
 
299
    @method _defNavigateFn
300
    @param {EventFacade} e
301
    @protected
302
    @since 3.5.0
303
    **/
304
    _defNavigateFn: function (e) {
305
        this[e.replace ? 'replace' : 'save'](e.url);
306
 
307
        if (win && this.get('scrollToTop')) {
308
            // Scroll to the top of the page. The timeout ensures that the
309
            // scroll happens after navigation begins, so that the current
310
            // scroll position will be restored if the user clicks the back
311
            // button.
312
            setTimeout(function () {
313
                win.scroll(0, 0);
314
            }, 1);
315
        }
316
    },
317
 
318
    /**
319
    Handler for delegated link-click events which match the `linkSelector`.
320
 
321
    This will attempt to enhance the navigation to the link element's `href` by
322
    passing the URL to the `_navigate()` method. When the navigation is being
323
    enhanced, the default action is prevented.
324
 
325
    If the user clicks a link with the middle/right mouse buttons, or is holding
326
    down the Ctrl or Command keys, this method's behavior is not applied and
327
    allows the native behavior to occur. Similarly, if the router is not capable
328
    or handling the URL because no route-handlers match, the link click will
329
    behave natively.
330
 
331
    @method _onLinkClick
332
    @param {EventFacade} e
333
    @protected
334
    @since 3.5.0
335
    **/
336
    _onLinkClick: function (e) {
337
        var link, url, navigated;
338
 
339
        // Allow the native behavior on middle/right-click, or when Ctrl or
340
        // Command are pressed.
341
        if (e.button !== 1 || e.ctrlKey || e.metaKey) { return; }
342
 
343
        link = e.currentTarget;
344
 
345
        // Only allow anchor elements because we need access to its `protocol`,
346
        // `host`, and `href` attributes.
347
        if (link.get('tagName').toUpperCase() !== 'A') {
348
            Y.log('pjax link-click navigation requires an anchor element.', 'warn', 'PjaxBase');
349
            return;
350
        }
351
 
352
        // Same origin check to prevent trying to navigate to URLs from other
353
        // sites or things like mailto links.
354
        if (!this._isLinkSameOrigin(link)) {
355
            return;
356
        }
357
 
358
        // All browsers fully resolve an anchor's `href` property.
359
        url = link.get('href');
360
 
361
        // Try and navigate to the URL via the router, and prevent the default
362
        // link-click action if we do.
363
        if (url) {
364
            navigated = this._navigate(url, {originEvent: e});
365
 
366
            if (navigated) {
367
                e.preventDefault();
368
            }
369
        }
370
    }
371
};
372
 
373
PjaxBase.ATTRS = {
374
    /**
375
    CSS selector string used to filter link click events so that only the links
376
    which match it will have the enhanced navigation behavior of Pjax applied.
377
 
378
    When a link is clicked and that link matches this selector, Pjax will
379
    attempt to dispatch to any route handlers matching the link's `href` URL. If
380
    HTML5 history is not supported or if no route handlers match, the link click
381
    will be handled by the browser just like any old link.
382
 
383
    @attribute linkSelector
384
    @type String|Function
385
    @default "a.yui3-pjax"
386
    @initOnly
387
    @since 3.5.0
388
    **/
389
    linkSelector: {
390
        value    : 'a.' + CLASS_PJAX,
391
        writeOnce: 'initOnly'
392
    },
393
 
394
    /**
395
    Whether navigating to a hash-fragment identifier on the current page should
396
    be enhanced and cause the `navigate` event to fire.
397
 
398
    By default Pjax allows the browser to perform its default action when a user
399
    is navigating within a page by clicking in-page links
400
    (e.g. `<a href="#top">Top of page</a>`) and does not attempt to interfere or
401
    enhance in-page navigation.
402
 
403
    @attribute navigateOnHash
404
    @type Boolean
405
    @default false
406
    @since 3.5.0
407
    **/
408
    navigateOnHash: {
409
        value: false
410
    },
411
 
412
    /**
413
    Whether the page should be scrolled to the top after navigating to a URL.
414
 
415
    When the user clicks the browser's back button, the previous scroll position
416
    will be maintained.
417
 
418
    @attribute scrollToTop
419
    @type Boolean
420
    @default true
421
    @since 3.5.0
422
    **/
423
    scrollToTop: {
424
        value: true
425
    },
426
 
427
    /**
428
    Whether to set `window.location` when calling `navigate()`
429
    if no routes match the specified URL.
430
 
431
    @attribute allowFallThrough
432
    @type Boolean
433
    @default true
434
    @since 3.18.0
435
    **/
436
    allowFallThrough: {
437
        value: true
438
    }
439
};
440
 
441
Y.PjaxBase = PjaxBase;
442
 
443
}, '3.18.1', {"requires": ["classnamemanager", "node-event-delegate", "router"]});