...
...
@@ -0,0 +1,838 @@
1
+// jquery.pjax.js
2
+// copyright chris wanstrath
3
+// https://github.com/defunkt/jquery-pjax
4
+
5
+(function($){
6
+
7
+// When called on a container with a selector, fetches the href with
8
+// ajax into the container or with the data-pjax attribute on the link
9
+// itself.
10
+//
11
+// Tries to make sure the back button and ctrl+click work the way
12
+// you'd expect.
13
+//
14
+// Exported as $.fn.pjax
15
+//
16
+// Accepts a jQuery ajax options object that may include these
17
+// pjax specific options:
18
+//
19
+//
20
+// container - Where to stick the response body. Usually a String selector.
21
+// $(container).html(xhr.responseBody)
22
+// (default: current jquery context)
23
+// push - Whether to pushState the URL. Defaults to true (of course).
24
+// replace - Want to use replaceState instead? That's cool.
25
+//
26
+// For convenience the second parameter can be either the container or
27
+// the options object.
28
+//
29
+// Returns the jQuery object
30
+function fnPjax(selector, container, options) {
31
+ var context = this
32
+ return this.on('click.pjax', selector, function(event) {
33
+ var opts = $.extend({}, optionsFor(container, options))
34
+ if (!opts.container)
35
+ opts.container = $(this).attr('data-pjax') || context
36
+ handleClick(event, opts)
37
+ })
38
+}
39
+
40
+// Public: pjax on click handler
41
+//
42
+// Exported as $.pjax.click.
43
+//
44
+// event - "click" jQuery.Event
45
+// options - pjax options
46
+//
47
+// Examples
48
+//
49
+// $(document).on('click', 'a', $.pjax.click)
50
+// // is the same as
51
+// $(document).pjax('a')
52
+//
53
+// $(document).on('click', 'a', function(event) {
54
+// var container = $(this).closest('[data-pjax-container]')
55
+// $.pjax.click(event, container)
56
+// })
57
+//
58
+// Returns nothing.
59
+function handleClick(event, container, options) {
60
+ options = optionsFor(container, options)
61
+
62
+ var link = event.currentTarget
63
+
64
+ if (link.tagName.toUpperCase() !== 'A')
65
+ throw "$.fn.pjax or $.pjax.click requires an anchor element"
66
+
67
+ // Middle click, cmd click, and ctrl click should open
68
+ // links in a new tab as normal.
69
+ if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey )
70
+ return
71
+
72
+ // Ignore cross origin links
73
+ if ( location.protocol !== link.protocol || location.hostname !== link.hostname )
74
+ return
75
+
76
+ // Ignore anchors on the same page
77
+ if (link.hash && link.href.replace(link.hash, '') ===
78
+ location.href.replace(location.hash, ''))
79
+ return
80
+
81
+ // Ignore empty anchor "foo.html#"
82
+ if (link.href === location.href + '#')
83
+ return
84
+
85
+ var defaults = {
86
+ url: link.href,
87
+ container: $(link).attr('data-pjax'),
88
+ target: link
89
+ }
90
+
91
+ var opts = $.extend({}, defaults, options)
92
+ var clickEvent = $.Event('pjax:click')
93
+ $(link).trigger(clickEvent, [opts])
94
+
95
+ if (!clickEvent.isDefaultPrevented()) {
96
+ pjax(opts)
97
+ event.preventDefault()
98
+ }
99
+}
100
+
101
+// Public: pjax on form submit handler
102
+//
103
+// Exported as $.pjax.submit
104
+//
105
+// event - "click" jQuery.Event
106
+// options - pjax options
107
+//
108
+// Examples
109
+//
110
+// $(document).on('submit', 'form', function(event) {
111
+// var container = $(this).closest('[data-pjax-container]')
112
+// $.pjax.submit(event, container)
113
+// })
114
+//
115
+// Returns nothing.
116
+function handleSubmit(event, container, options) {
117
+ options = optionsFor(container, options)
118
+
119
+ var form = event.currentTarget
120
+
121
+ if (form.tagName.toUpperCase() !== 'FORM')
122
+ throw "$.pjax.submit requires a form element"
123
+
124
+ var defaults = {
125
+ type: form.method.toUpperCase(),
126
+ url: form.action,
127
+ data: $(form).serializeArray(),
128
+ container: $(form).attr('data-pjax'),
129
+ target: form
130
+ }
131
+
132
+ pjax($.extend({}, defaults, options))
133
+
134
+ event.preventDefault()
135
+}
136
+
137
+// Loads a URL with ajax, puts the response body inside a container,
138
+// then pushState()'s the loaded URL.
139
+//
140
+// Works just like $.ajax in that it accepts a jQuery ajax
141
+// settings object (with keys like url, type, data, etc).
142
+//
143
+// Accepts these extra keys:
144
+//
145
+// container - Where to stick the response body.
146
+// $(container).html(xhr.responseBody)
147
+// push - Whether to pushState the URL. Defaults to true (of course).
148
+// replace - Want to use replaceState instead? That's cool.
149
+//
150
+// Use it just like $.ajax:
151
+//
152
+// var xhr = $.pjax({ url: this.href, container: '#main' })
153
+// console.log( xhr.readyState )
154
+//
155
+// Returns whatever $.ajax returns.
156
+function pjax(options) {
157
+ options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options)
158
+
159
+ if ($.isFunction(options.url)) {
160
+ options.url = options.url()
161
+ }
162
+
163
+ var target = options.target
164
+
165
+ var hash = parseURL(options.url).hash
166
+
167
+ var context = options.context = findContainerFor(options.container)
168
+
169
+ // We want the browser to maintain two separate internal caches: one
170
+ // for pjax'd partial page loads and one for normal page loads.
171
+ // Without adding this secret parameter, some browsers will often
172
+ // confuse the two.
173
+ if (!options.data) options.data = {}
174
+ options.data._pjax = context.selector
175
+
176
+ function fire(type, args) {
177
+ var event = $.Event(type, { relatedTarget: target })
178
+ context.trigger(event, args)
179
+ return !event.isDefaultPrevented()
180
+ }
181
+
182
+ var timeoutTimer
183
+
184
+ options.beforeSend = function(xhr, settings) {
185
+ // No timeout for non-GET requests
186
+ // Its not safe to request the resource again with a fallback method.
187
+ if (settings.type !== 'GET') {
188
+ settings.timeout = 0
189
+ }
190
+
191
+ xhr.setRequestHeader('X-PJAX', 'true')
192
+ xhr.setRequestHeader('X-PJAX-Container', context.selector)
193
+
194
+ if (!fire('pjax:beforeSend', [xhr, settings]))
195
+ return false
196
+
197
+ if (settings.timeout > 0) {
198
+ timeoutTimer = setTimeout(function() {
199
+ if (fire('pjax:timeout', [xhr, options]))
200
+ xhr.abort('timeout')
201
+ }, settings.timeout)
202
+
203
+ // Clear timeout setting so jquerys internal timeout isn't invoked
204
+ settings.timeout = 0
205
+ }
206
+
207
+ options.requestUrl = parseURL(settings.url).href
208
+ }
209
+
210
+ options.complete = function(xhr, textStatus) {
211
+ if (timeoutTimer)
212
+ clearTimeout(timeoutTimer)
213
+
214
+ fire('pjax:complete', [xhr, textStatus, options])
215
+
216
+ fire('pjax:end', [xhr, options])
217
+ }
218
+
219
+ options.error = function(xhr, textStatus, errorThrown) {
220
+ var container = extractContainer("", xhr, options)
221
+
222
+ var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options])
223
+ if (options.type == 'GET' && textStatus !== 'abort' && allowed) {
224
+ locationReplace(container.url)
225
+ }
226
+ }
227
+
228
+ options.success = function(data, status, xhr) {
229
+ // If $.pjax.defaults.version is a function, invoke it first.
230
+ // Otherwise it can be a static string.
231
+ var currentVersion = (typeof $.pjax.defaults.version === 'function') ?
232
+ $.pjax.defaults.version() :
233
+ $.pjax.defaults.version
234
+
235
+ var latestVersion = xhr.getResponseHeader('X-PJAX-Version')
236
+
237
+ var container = extractContainer(data, xhr, options)
238
+
239
+ // If there is a layout version mismatch, hard load the new url
240
+ if (currentVersion && latestVersion && currentVersion !== latestVersion) {
241
+ locationReplace(container.url)
242
+ return
243
+ }
244
+
245
+ // If the new response is missing a body, hard load the page
246
+ if (!container.contents) {
247
+ locationReplace(container.url)
248
+ return
249
+ }
250
+
251
+ pjax.state = {
252
+ id: options.id || uniqueId(),
253
+ url: container.url,
254
+ title: container.title,
255
+ container: context.selector,
256
+ fragment: options.fragment,
257
+ timeout: options.timeout
258
+ }
259
+
260
+ if (options.push || options.replace) {
261
+ window.history.replaceState(pjax.state, container.title, container.url)
262
+ }
263
+
264
+ // Clear out any focused controls before inserting new page contents.
265
+ document.activeElement.blur()
266
+
267
+ if (container.title) document.title = container.title
268
+ context.html(container.contents)
269
+
270
+ // FF bug: Won't autofocus fields that are inserted via JS.
271
+ // This behavior is incorrect. So if theres no current focus, autofocus
272
+ // the last field.
273
+ //
274
+ // http://www.w3.org/html/wg/drafts/html/master/forms.html
275
+ var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0]
276
+ if (autofocusEl && document.activeElement !== autofocusEl) {
277
+ autofocusEl.focus();
278
+ }
279
+
280
+ executeScriptTags(container.scripts)
281
+
282
+ // Scroll to top by default
283
+ if (typeof options.scrollTo === 'number')
284
+ $(window).scrollTop(options.scrollTo)
285
+
286
+ // If the URL has a hash in it, make sure the browser
287
+ // knows to navigate to the hash.
288
+ if ( hash !== '' ) {
289
+ // Avoid using simple hash set here. Will add another history
290
+ // entry. Replace the url with replaceState and scroll to target
291
+ // by hand.
292
+ //
293
+ // window.location.hash = hash
294
+ var url = parseURL(container.url)
295
+ url.hash = hash
296
+
297
+ pjax.state.url = url.href
298
+ window.history.replaceState(pjax.state, container.title, url.href)
299
+
300
+ var target = $(url.hash)
301
+ if (target.length) $(window).scrollTop(target.offset().top)
302
+ }
303
+
304
+ fire('pjax:success', [data, status, xhr, options])
305
+ }
306
+
307
+
308
+ // Initialize pjax.state for the initial page load. Assume we're
309
+ // using the container and options of the link we're loading for the
310
+ // back button to the initial page. This ensures good back button
311
+ // behavior.
312
+ if (!pjax.state) {
313
+ pjax.state = {
314
+ id: uniqueId(),
315
+ url: window.location.href,
316
+ title: document.title,
317
+ container: context.selector,
318
+ fragment: options.fragment,
319
+ timeout: options.timeout
320
+ }
321
+ window.history.replaceState(pjax.state, document.title)
322
+ }
323
+
324
+ // Cancel the current request if we're already pjaxing
325
+ var xhr = pjax.xhr
326
+ if ( xhr && xhr.readyState < 4) {
327
+ xhr.onreadystatechange = $.noop
328
+ xhr.abort()
329
+ }
330
+
331
+ pjax.options = options
332
+ var xhr = pjax.xhr = $.ajax(options)
333
+
334
+ if (xhr.readyState > 0) {
335
+ if (options.push && !options.replace) {
336
+ // Cache current container element before replacing it
337
+ cachePush(pjax.state.id, context.clone().contents())
338
+
339
+ window.history.pushState(null, "", stripPjaxParam(options.requestUrl))
340
+ }
341
+
342
+ fire('pjax:start', [xhr, options])
343
+ fire('pjax:send', [xhr, options])
344
+ }
345
+
346
+ return pjax.xhr
347
+}
348
+
349
+// Public: Reload current page with pjax.
350
+//
351
+// Returns whatever $.pjax returns.
352
+function pjaxReload(container, options) {
353
+ var defaults = {
354
+ url: window.location.href,
355
+ push: false,
356
+ replace: true,
357
+ scrollTo: false
358
+ }
359
+
360
+ return pjax($.extend(defaults, optionsFor(container, options)))
361
+}
362
+
363
+// Internal: Hard replace current state with url.
364
+//
365
+// Work for around WebKit
366
+// https://bugs.webkit.org/show_bug.cgi?id=93506
367
+//
368
+// Returns nothing.
369
+function locationReplace(url) {
370
+ window.history.replaceState(null, "", "#")
371
+ window.location.replace(url)
372
+}
373
+
374
+
375
+var initialPop = true
376
+var initialURL = window.location.href
377
+var initialState = window.history.state
378
+
379
+// Initialize $.pjax.state if possible
380
+// Happens when reloading a page and coming forward from a different
381
+// session history.
382
+if (initialState && initialState.container) {
383
+ pjax.state = initialState
384
+}
385
+
386
+// Non-webkit browsers don't fire an initial popstate event
387
+if ('state' in window.history) {
388
+ initialPop = false
389
+}
390
+
391
+// popstate handler takes care of the back and forward buttons
392
+//
393
+// You probably shouldn't use pjax on pages with other pushState
394
+// stuff yet.
395
+function onPjaxPopstate(event) {
396
+ var state = event.state
397
+
398
+ if (state && state.container) {
399
+ // When coming forward from a separate history session, will get an
400
+ // initial pop with a state we are already at. Skip reloading the current
401
+ // page.
402
+ if (initialPop && initialURL == state.url) return
403
+
404
+ // If popping back to the same state, just skip.
405
+ // Could be clicking back from hashchange rather than a pushState.
406
+ if (pjax.state.id === state.id) return
407
+
408
+ var container = $(state.container)
409
+ if (container.length) {
410
+ var direction, contents = cacheMapping[state.id]
411
+
412
+ if (pjax.state) {
413
+ // Since state ids always increase, we can deduce the history
414
+ // direction from the previous state.
415
+ direction = pjax.state.id < state.id ? 'forward' : 'back'
416
+
417
+ // Cache current container before replacement and inform the
418
+ // cache which direction the history shifted.
419
+ cachePop(direction, pjax.state.id, container.clone().contents())
420
+ }
421
+
422
+ var popstateEvent = $.Event('pjax:popstate', {
423
+ state: state,
424
+ direction: direction
425
+ })
426
+ container.trigger(popstateEvent)
427
+
428
+ var options = {
429
+ id: state.id,
430
+ url: state.url,
431
+ container: container,
432
+ push: false,
433
+ fragment: state.fragment,
434
+ timeout: state.timeout,
435
+ scrollTo: false
436
+ }
437
+
438
+ if (contents) {
439
+ container.trigger('pjax:start', [null, options])
440
+
441
+ if (state.title) document.title = state.title
442
+ container.html(contents)
443
+ pjax.state = state
444
+
445
+ container.trigger('pjax:end', [null, options])
446
+ } else {
447
+ pjax(options)
448
+ }
449
+
450
+ // Force reflow/relayout before the browser tries to restore the
451
+ // scroll position.
452
+ container[0].offsetHeight
453
+ } else {
454
+ locationReplace(location.href)
455
+ }
456
+ }
457
+ initialPop = false
458
+}
459
+
460
+// Fallback version of main pjax function for browsers that don't
461
+// support pushState.
462
+//
463
+// Returns nothing since it retriggers a hard form submission.
464
+function fallbackPjax(options) {
465
+ var url = $.isFunction(options.url) ? options.url() : options.url,
466
+ method = options.type ? options.type.toUpperCase() : 'GET'
467
+
468
+ var form = $('<form>', {
469
+ method: method === 'GET' ? 'GET' : 'POST',
470
+ action: url,
471
+ style: 'display:none'
472
+ })
473
+
474
+ if (method !== 'GET' && method !== 'POST') {
475
+ form.append($('<input>', {
476
+ type: 'hidden',
477
+ name: '_method',
478
+ value: method.toLowerCase()
479
+ }))
480
+ }
481
+
482
+ var data = options.data
483
+ if (typeof data === 'string') {
484
+ $.each(data.split('&'), function(index, value) {
485
+ var pair = value.split('=')
486
+ form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]}))
487
+ })
488
+ } else if (typeof data === 'object') {
489
+ for (key in data)
490
+ form.append($('<input>', {type: 'hidden', name: key, value: data[key]}))
491
+ }
492
+
493
+ $(document.body).append(form)
494
+ form.submit()
495
+}
496
+
497
+// Internal: Generate unique id for state object.
498
+//
499
+// Use a timestamp instead of a counter since ids should still be
500
+// unique across page loads.
501
+//
502
+// Returns Number.
503
+function uniqueId() {
504
+ return (new Date).getTime()
505
+}
506
+
507
+// Internal: Strips _pjax param from url
508
+//
509
+// url - String
510
+//
511
+// Returns String.
512
+function stripPjaxParam(url) {
513
+ return url
514
+ .replace(/\?_pjax=[^&]+&?/, '?')
515
+ .replace(/_pjax=[^&]+&?/, '')
516
+ .replace(/[\?&]$/, '')
517
+}
518
+
519
+// Internal: Parse URL components and returns a Locationish object.
520
+//
521
+// url - String URL
522
+//
523
+// Returns HTMLAnchorElement that acts like Location.
524
+function parseURL(url) {
525
+ var a = document.createElement('a')
526
+ a.href = url
527
+ return a
528
+}
529
+
530
+// Internal: Build options Object for arguments.
531
+//
532
+// For convenience the first parameter can be either the container or
533
+// the options object.
534
+//
535
+// Examples
536
+//
537
+// optionsFor('#container')
538
+// // => {container: '#container'}
539
+//
540
+// optionsFor('#container', {push: true})
541
+// // => {container: '#container', push: true}
542
+//
543
+// optionsFor({container: '#container', push: true})
544
+// // => {container: '#container', push: true}
545
+//
546
+// Returns options Object.
547
+function optionsFor(container, options) {
548
+ // Both container and options
549
+ if ( container && options )
550
+ options.container = container
551
+
552
+ // First argument is options Object
553
+ else if ( $.isPlainObject(container) )
554
+ options = container
555
+
556
+ // Only container
557
+ else
558
+ options = {container: container}
559
+
560
+ // Find and validate container
561
+ if (options.container)
562
+ options.container = findContainerFor(options.container)
563
+
564
+ return options
565
+}
566
+
567
+// Internal: Find container element for a variety of inputs.
568
+//
569
+// Because we can't persist elements using the history API, we must be
570
+// able to find a String selector that will consistently find the Element.
571
+//
572
+// container - A selector String, jQuery object, or DOM Element.
573
+//
574
+// Returns a jQuery object whose context is `document` and has a selector.
575
+function findContainerFor(container) {
576
+ container = $(container)
577
+
578
+ if ( !container.length ) {
579
+ throw "no pjax container for " + container.selector
580
+ } else if ( container.selector !== '' && container.context === document ) {
581
+ return container
582
+ } else if ( container.attr('id') ) {
583
+ return $('#' + container.attr('id'))
584
+ } else {
585
+ throw "cant get selector for pjax container!"
586
+ }
587
+}
588
+
589
+// Internal: Filter and find all elements matching the selector.
590
+//
591
+// Where $.fn.find only matches descendants, findAll will test all the
592
+// top level elements in the jQuery object as well.
593
+//
594
+// elems - jQuery object of Elements
595
+// selector - String selector to match
596
+//
597
+// Returns a jQuery object.
598
+function findAll(elems, selector) {
599
+ return elems.filter(selector).add(elems.find(selector));
600
+}
601
+
602
+function parseHTML(html) {
603
+ return $.parseHTML(html, document, true)
604
+}
605
+
606
+// Internal: Extracts container and metadata from response.
607
+//
608
+// 1. Extracts X-PJAX-URL header if set
609
+// 2. Extracts inline <title> tags
610
+// 3. Builds response Element and extracts fragment if set
611
+//
612
+// data - String response data
613
+// xhr - XHR response
614
+// options - pjax options Object
615
+//
616
+// Returns an Object with url, title, and contents keys.
617
+function extractContainer(data, xhr, options) {
618
+ var obj = {}
619
+
620
+ // Prefer X-PJAX-URL header if it was set, otherwise fallback to
621
+ // using the original requested url.
622
+ obj.url = stripPjaxParam(xhr.getResponseHeader('X-PJAX-URL') || options.requestUrl)
623
+
624
+ // Attempt to parse response html into elements
625
+ if (/<html/i.test(data)) {
626
+ var $head = $(parseHTML(data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0]))
627
+ var $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0]))
628
+ } else {
629
+ var $head = $body = $(parseHTML(data))
630
+ }
631
+
632
+ // If response data is empty, return fast
633
+ if ($body.length === 0)
634
+ return obj
635
+
636
+ // If there's a <title> tag in the header, use it as
637
+ // the page's title.
638
+ obj.title = findAll($head, 'title').last().text()
639
+
640
+ if (options.fragment) {
641
+ // If they specified a fragment, look for it in the response
642
+ // and pull it out.
643
+ if (options.fragment === 'body') {
644
+ var $fragment = $body
645
+ } else {
646
+ var $fragment = findAll($body, options.fragment).first()
647
+ }
648
+
649
+ if ($fragment.length) {
650
+ obj.contents = $fragment.contents()
651
+
652
+ // If there's no title, look for data-title and title attributes
653
+ // on the fragment
654
+ if (!obj.title)
655
+ obj.title = $fragment.attr('title') || $fragment.data('title')
656
+ }
657
+
658
+ } else if (!/<html/i.test(data)) {
659
+ obj.contents = $body
660
+ }
661
+
662
+ // Clean up any <title> tags
663
+ if (obj.contents) {
664
+ // Remove any parent title elements
665
+ obj.contents = obj.contents.not(function() { return $(this).is('title') })
666
+
667
+ // Then scrub any titles from their descendants
668
+ obj.contents.find('title').remove()
669
+
670
+ // Gather all script[src] elements
671
+ //obj.scripts = findAll(obj.contents, 'script[src]').remove()
672
+ obj.contents = obj.contents.not(obj.scripts)
673
+ }
674
+
675
+ // Trim any whitespace off the title
676
+ if (obj.title) obj.title = $.trim(obj.title)
677
+
678
+ return obj
679
+}
680
+
681
+// Load an execute scripts using standard script request.
682
+//
683
+// Avoids jQuery's traditional $.getScript which does a XHR request and
684
+// globalEval.
685
+//
686
+// scripts - jQuery object of script Elements
687
+//
688
+// Returns nothing.
689
+function executeScriptTags(scripts) {
690
+ if (!scripts) return
691
+
692
+ var existingScripts = $('script[src]')
693
+
694
+ scripts.each(function() {
695
+ var src = this.src
696
+ var matchedScripts = existingScripts.filter(function() {
697
+ return this.src === src
698
+ })
699
+ if (matchedScripts.length) return
700
+
701
+ var script = document.createElement('script')
702
+ script.type = $(this).attr('type')
703
+ script.src = $(this).attr('src')
704
+ document.head.appendChild(script)
705
+ })
706
+}
707
+
708
+// Internal: History DOM caching class.
709
+var cacheMapping = {}
710
+var cacheForwardStack = []
711
+var cacheBackStack = []
712
+
713
+// Push previous state id and container contents into the history
714
+// cache. Should be called in conjunction with `pushState` to save the
715
+// previous container contents.
716
+//
717
+// id - State ID Number
718
+// value - DOM Element to cache
719
+//
720
+// Returns nothing.
721
+function cachePush(id, value) {
722
+ cacheMapping[id] = value
723
+ cacheBackStack.push(id)
724
+
725
+ // Remove all entires in forward history stack after pushing
726
+ // a new page.
727
+ while (cacheForwardStack.length)
728
+ delete cacheMapping[cacheForwardStack.shift()]
729
+
730
+ // Trim back history stack to max cache length.
731
+ while (cacheBackStack.length > pjax.defaults.maxCacheLength)
732
+ delete cacheMapping[cacheBackStack.shift()]
733
+}
734
+
735
+// Shifts cache from directional history cache. Should be
736
+// called on `popstate` with the previous state id and container
737
+// contents.
738
+//
739
+// direction - "forward" or "back" String
740
+// id - State ID Number
741
+// value - DOM Element to cache
742
+//
743
+// Returns nothing.
744
+function cachePop(direction, id, value) {
745
+ var pushStack, popStack
746
+ cacheMapping[id] = value
747
+
748
+ if (direction === 'forward') {
749
+ pushStack = cacheBackStack
750
+ popStack = cacheForwardStack
751
+ } else {
752
+ pushStack = cacheForwardStack
753
+ popStack = cacheBackStack
754
+ }
755
+
756
+ pushStack.push(id)
757
+ if (id = popStack.pop())
758
+ delete cacheMapping[id]
759
+}
760
+
761
+// Public: Find version identifier for the initial page load.
762
+//
763
+// Returns String version or undefined.
764
+function findVersion() {
765
+ return $('meta').filter(function() {
766
+ var name = $(this).attr('http-equiv')
767
+ return name && name.toUpperCase() === 'X-PJAX-VERSION'
768
+ }).attr('content')
769
+}
770
+
771
+// Install pjax functions on $.pjax to enable pushState behavior.
772
+//
773
+// Does nothing if already enabled.
774
+//
775
+// Examples
776
+//
777
+// $.pjax.enable()
778
+//
779
+// Returns nothing.
780
+function enable() {
781
+ $.fn.pjax = fnPjax
782
+ $.pjax = pjax
783
+ $.pjax.enable = $.noop
784
+ $.pjax.disable = disable
785
+ $.pjax.click = handleClick
786
+ $.pjax.submit = handleSubmit
787
+ $.pjax.reload = pjaxReload
788
+ $.pjax.defaults = {
789
+ timeout: 650,
790
+ push: true,
791
+ replace: false,
792
+ type: 'GET',
793
+ dataType: 'html',
794
+ scrollTo: 0,
795
+ maxCacheLength: 20,
796
+ version: findVersion
797
+ }
798
+ $(window).on('popstate.pjax', onPjaxPopstate)
799
+}
800
+
801
+// Disable pushState behavior.
802
+//
803
+// This is the case when a browser doesn't support pushState. It is
804
+// sometimes useful to disable pushState for debugging on a modern
805
+// browser.
806
+//
807
+// Examples
808
+//
809
+// $.pjax.disable()
810
+//
811
+// Returns nothing.
812
+function disable() {
813
+ $.fn.pjax = function() { return this }
814
+ $.pjax = fallbackPjax
815
+ $.pjax.enable = enable
816
+ $.pjax.disable = $.noop
817
+ $.pjax.click = $.noop
818
+ $.pjax.submit = $.noop
819
+ $.pjax.reload = function() { window.location.reload() }
820
+
821
+ $(window).off('popstate.pjax', onPjaxPopstate)
822
+}
823
+
824
+
825
+// Add the state property to jQuery's event object so we can use it in
826
+// $(window).bind('popstate')
827
+if ( $.inArray('state', $.event.props) < 0 )
828
+ $.event.props.push('state')
829
+
830
+// Is pjax supported by this browser?
831
+$.support.pjax =
832
+ window.history && window.history.pushState && window.history.replaceState &&
833
+ // pushState isn't reliable on iOS until 5.
834
+ !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)
835
+
836
+$.support.pjax ? enable() : disable()
837
+
838
+})(jQuery);
...
...