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