aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'deprecated/jinweiclarkchao.github.io/assets/pjax.js')
-rw-r--r--deprecated/jinweiclarkchao.github.io/assets/pjax.js838
1 files changed, 838 insertions, 0 deletions
diff --git a/deprecated/jinweiclarkchao.github.io/assets/pjax.js b/deprecated/jinweiclarkchao.github.io/assets/pjax.js
new file mode 100644
index 0000000..a51124f
--- /dev/null
+++ b/deprecated/jinweiclarkchao.github.io/assets/pjax.js
@@ -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
30function 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.
59function 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.
116function 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.
156function 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.
352function 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.
369function locationReplace(url) {
370 window.history.replaceState(null, "", "#")
371 window.location.replace(url)
372}
373
374
375var initialPop = true
376var initialURL = window.location.href
377var 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.
382if (initialState && initialState.container) {
383 pjax.state = initialState
384}
385
386// Non-webkit browsers don't fire an initial popstate event
387if ('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.
395function 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.
464function 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.
503function uniqueId() {
504 return (new Date).getTime()
505}
506
507// Internal: Strips _pjax param from url
508//
509// url - String
510//
511// Returns String.
512function 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.
524function 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.
547function 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.
575function 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.
598function findAll(elems, selector) {
599 return elems.filter(selector).add(elems.find(selector));
600}
601
602function 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.
617function 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.
689function 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.
709var cacheMapping = {}
710var cacheForwardStack = []
711var 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.
721function 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.
744function 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.
764function 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.
780function 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.
812function 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')
827if ( $.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); \ No newline at end of file
Powered by cgit v1.2.3 (git 2.41.0)