[ Index ] |
PHP Cross Reference of vtigercrm-6.1.0 |
[Summary view] [Print] [Text view]
1 // jquery.pjax.js 2 // copyright chris wanstrath 3 // https://github.com/defunkt/jquery-pjax 4 5 (function($){ 6 7 // When called on a link, fetches the href with ajax into the 8 // container specified as the first parameter or with the data-pjax 9 // attribute on the link itself. 10 // 11 // Tries to make sure the back button and ctrl+click work the way 12 // you'd expect. 13 // 14 // Accepts a jQuery ajax options object that may include these 15 // pjax specific options: 16 // 17 // container - Where to stick the response body. Usually a String selector. 18 // $(container).html(xhr.responseBody) 19 // push - Whether to pushState the URL. Defaults to true (of course). 20 // replace - Want to use replaceState instead? That's cool. 21 // 22 // For convenience the first parameter can be either the container or 23 // the options object. 24 // 25 // Returns the jQuery object 26 $.fn.pjax = function( container, options ) { 27 return this.live('click.pjax', function(event){ 28 handleClick(event, container, options) 29 }) 30 } 31 32 // Public: pjax on click handler 33 // 34 // Exported as $.pjax.click. 35 // 36 // event - "click" jQuery.Event 37 // options - pjax options 38 // 39 // Examples 40 // 41 // $('a').live('click', $.pjax.click) 42 // // is the same as 43 // $('a').pjax() 44 // 45 // $(document).on('click', 'a', function(event) { 46 // var container = $(this).closest('[data-pjax-container]') 47 // return $.pjax.click(event, container) 48 // }) 49 // 50 // Returns false if pjax runs, otherwise nothing. 51 function handleClick(event, container, options) { 52 options = optionsFor(container, options) 53 54 var link = event.currentTarget 55 56 if (link.tagName.toUpperCase() !== 'A') 57 throw "$.fn.pjax or $.pjax.click requires an anchor element" 58 59 // Middle click, cmd click, and ctrl click should open 60 // links in a new tab as normal. 61 if ( event.which > 1 || event.metaKey || event.ctrlKey ) 62 return 63 64 // Ignore cross origin links 65 if ( location.protocol !== link.protocol || location.host !== link.host ) 66 return 67 68 // Ignore anchors on the same page 69 if ( link.hash && link.href.replace(link.hash, '') === 70 location.href.replace(location.hash, '') ) 71 return 72 73 var defaults = { 74 url: link.href, 75 container: $(link).attr('data-pjax'), 76 target: link, 77 clickedElement: $(link), // DEPRECATED: use target 78 fragment: null 79 } 80 81 $.pjax($.extend({}, defaults, options)) 82 83 event.preventDefault() 84 } 85 86 87 // Loads a URL with ajax, puts the response body inside a container, 88 // then pushState()'s the loaded URL. 89 // 90 // Works just like $.ajax in that it accepts a jQuery ajax 91 // settings object (with keys like url, type, data, etc). 92 // 93 // Accepts these extra keys: 94 // 95 // container - Where to stick the response body. 96 // $(container).html(xhr.responseBody) 97 // push - Whether to pushState the URL. Defaults to true (of course). 98 // replace - Want to use replaceState instead? That's cool. 99 // 100 // Use it just like $.ajax: 101 // 102 // var xhr = $.pjax({ url: this.href, container: '#main' }) 103 // console.log( xhr.readyState ) 104 // 105 // Returns whatever $.ajax returns. 106 var pjax = $.pjax = function( options ) { 107 options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options) 108 109 if ($.isFunction(options.url)) { 110 options.url = options.url() 111 } 112 113 var target = options.target 114 115 // DEPRECATED: use options.target 116 if (!target && options.clickedElement) target = options.clickedElement[0] 117 118 var hash = parseURL(options.url).hash 119 120 // DEPRECATED: Save references to original event callbacks. However, 121 // listening for custom pjax:* events is prefered. 122 var oldBeforeSend = options.beforeSend, 123 oldComplete = options.complete, 124 oldSuccess = options.success, 125 oldError = options.error 126 127 var context = options.context = findContainerFor(options.container) 128 129 // We want the browser to maintain two separate internal caches: one 130 // for pjax'd partial page loads and one for normal page loads. 131 // Without adding this secret parameter, some browsers will often 132 // confuse the two. 133 if (!options.data) options.data = {} 134 options.data._pjax = context.selector 135 136 function fire(type, args) { 137 var event = $.Event(type, { relatedTarget: target }) 138 context.trigger(event, args) 139 return !event.isDefaultPrevented() 140 } 141 142 var timeoutTimer 143 144 options.beforeSend = function(xhr, settings) { 145 if (settings.timeout > 0) { 146 timeoutTimer = setTimeout(function() { 147 if (fire('pjax:timeout', [xhr, options])) 148 xhr.abort('timeout') 149 }, settings.timeout) 150 151 // Clear timeout setting so jquerys internal timeout isn't invoked 152 settings.timeout = 0 153 } 154 155 xhr.setRequestHeader('X-PJAX', 'true') 156 xhr.setRequestHeader('X-PJAX-Container', context.selector) 157 158 var result 159 160 // DEPRECATED: Invoke original `beforeSend` handler 161 if (oldBeforeSend) { 162 result = oldBeforeSend.apply(this, arguments) 163 if (result === false) return false 164 } 165 166 if (!fire('pjax:beforeSend', [xhr, settings])) 167 return false 168 169 options.requestUrl = parseURL(settings.url).href 170 } 171 172 options.complete = function(xhr, textStatus) { 173 if (timeoutTimer) 174 clearTimeout(timeoutTimer) 175 176 // DEPRECATED: Invoke original `complete` handler 177 if (oldComplete) oldComplete.apply(this, arguments) 178 179 fire('pjax:complete', [xhr, textStatus, options]) 180 181 fire('pjax:end', [xhr, options]) 182 // end.pjax is deprecated 183 fire('end.pjax', [xhr, options]) 184 } 185 186 options.error = function(xhr, textStatus, errorThrown) { 187 var container = extractContainer("", xhr, options) 188 189 // DEPRECATED: Invoke original `error` handler 190 if (oldError) oldError.apply(this, arguments) 191 192 var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options]) 193 if (textStatus !== 'abort' && allowed) 194 window.location = container.url 195 } 196 197 options.success = function(data, status, xhr) { 198 var container = extractContainer(data, xhr, options) 199 200 if (!container.contents) { 201 window.location = container.url 202 return 203 } 204 205 pjax.state = { 206 id: options.id || uniqueId(), 207 url: container.url, 208 title: container.title, 209 container: context.selector, 210 fragment: options.fragment, 211 timeout: options.timeout 212 } 213 214 if (options.push || options.replace) { 215 window.history.replaceState(pjax.state, container.title, container.url) 216 } 217 218 if (container.title) document.title = container.title 219 context.html(container.contents) 220 221 // Scroll to top by default 222 if (typeof options.scrollTo === 'number') 223 $(window).scrollTop(options.scrollTo) 224 225 // Google Analytics support 226 if ( (options.replace || options.push) && window._gaq ) 227 _gaq.push(['_trackPageview']) 228 229 // If the URL has a hash in it, make sure the browser 230 // knows to navigate to the hash. 231 if ( hash !== '' ) { 232 window.location.href = hash 233 } 234 235 // DEPRECATED: Invoke original `success` handler 236 if (oldSuccess) oldSuccess.apply(this, arguments) 237 238 fire('pjax:success', [data, status, xhr, options]) 239 } 240 241 242 // Initialize pjax.state for the initial page load. Assume we're 243 // using the container and options of the link we're loading for the 244 // back button to the initial page. This ensures good back button 245 // behavior. 246 if (!pjax.state) { 247 pjax.state = { 248 id: uniqueId(), 249 url: window.location.href, 250 title: document.title, 251 container: context.selector, 252 fragment: options.fragment, 253 timeout: options.timeout 254 } 255 window.history.replaceState(pjax.state, document.title) 256 } 257 258 // Cancel the current request if we're already pjaxing 259 var xhr = pjax.xhr 260 if ( xhr && xhr.readyState < 4) { 261 xhr.onreadystatechange = $.noop 262 xhr.abort() 263 } 264 265 pjax.options = options 266 var xhr = pjax.xhr = $.ajax(options) 267 268 if (xhr.readyState > 0) { 269 // pjax event is deprecated 270 $(document).trigger('pjax', [xhr, options]) 271 272 if (options.push && !options.replace) { 273 // Cache current container element before replacing it 274 containerCache.push(pjax.state.id, context.clone(true, true).contents()) 275 276 window.history.pushState(null, "", options.url) 277 } 278 279 fire('pjax:start', [xhr, options]) 280 // start.pjax is deprecated 281 fire('start.pjax', [xhr, options]) 282 283 fire('pjax:send', [xhr, options]) 284 } 285 286 return pjax.xhr 287 } 288 289 290 // Internal: Generate unique id for state object. 291 // 292 // Use a timestamp instead of a counter since ids should still be 293 // unique across page loads. 294 // 295 // Returns Number. 296 function uniqueId() { 297 return (new Date).getTime() 298 } 299 300 // Internal: Strips _pjax param from url 301 // 302 // url - String 303 // 304 // Returns String. 305 function stripPjaxParam(url) { 306 return url 307 .replace(/\?_pjax=[^&]+&?/, '?') 308 .replace(/_pjax=[^&]+&?/, '') 309 .replace(/[\?&]$/, '') 310 } 311 312 // Internal: Parse URL components and returns a Locationish object. 313 // 314 // url - String URL 315 // 316 // Returns HTMLAnchorElement that acts like Location. 317 function parseURL(url) { 318 var a = document.createElement('a') 319 a.href = url 320 return a 321 } 322 323 // Internal: Build options Object for arguments. 324 // 325 // For convenience the first parameter can be either the container or 326 // the options object. 327 // 328 // Examples 329 // 330 // optionsFor('#container') 331 // // => {container: '#container'} 332 // 333 // optionsFor('#container', {push: true}) 334 // // => {container: '#container', push: true} 335 // 336 // optionsFor({container: '#container', push: true}) 337 // // => {container: '#container', push: true} 338 // 339 // Returns options Object. 340 function optionsFor(container, options) { 341 // Both container and options 342 if ( container && options ) 343 options.container = container 344 345 // First argument is options Object 346 else if ( $.isPlainObject(container) ) 347 options = container 348 349 // Only container 350 else 351 options = {container: container} 352 353 // Find and validate container 354 if (options.container) 355 options.container = findContainerFor(options.container) 356 357 return options 358 } 359 360 // Internal: Find container element for a variety of inputs. 361 // 362 // Because we can't persist elements using the history API, we must be 363 // able to find a String selector that will consistently find the Element. 364 // 365 // container - A selector String, jQuery object, or DOM Element. 366 // 367 // Returns a jQuery object whose context is `document` and has a selector. 368 function findContainerFor(container) { 369 container = $(container) 370 371 if ( !container.length ) { 372 throw "no pjax container for " + container.selector 373 } else if ( container.selector !== '' && container.context === document ) { 374 return container 375 } else if ( container.attr('id') ) { 376 return $('#' + container.attr('id')) 377 } else { 378 throw "cant get selector for pjax container!" 379 } 380 } 381 382 // Internal: Filter and find all elements matching the selector. 383 // 384 // Where $.fn.find only matches descendants, findAll will test all the 385 // top level elements in the jQuery object as well. 386 // 387 // elems - jQuery object of Elements 388 // selector - String selector to match 389 // 390 // Returns a jQuery object. 391 function findAll(elems, selector) { 392 var results = $() 393 elems.each(function() { 394 if ($(this).is(selector)) 395 results = results.add(this) 396 results = results.add(selector, this) 397 }) 398 return results 399 } 400 401 // Internal: Extracts container and metadata from response. 402 // 403 // 1. Extracts X-PJAX-URL header if set 404 // 2. Extracts inline <title> tags 405 // 3. Builds response Element and extracts fragment if set 406 // 407 // data - String response data 408 // xhr - XHR response 409 // options - pjax options Object 410 // 411 // Returns an Object with url, title, and contents keys. 412 function extractContainer(data, xhr, options) { 413 var obj = {} 414 415 // Prefer X-PJAX-URL header if it was set, otherwise fallback to 416 // using the original requested url. 417 obj.url = stripPjaxParam(xhr.getResponseHeader('X-PJAX-URL') || options.requestUrl) 418 419 // Attempt to parse response html into elements 420 var $data = $(data) 421 422 // If response data is empty, return fast 423 if ($data.length === 0) 424 return obj 425 426 // If there's a <title> tag in the response, use it as 427 // the page's title. 428 obj.title = findAll($data, 'title').last().text() 429 430 if (options.fragment) { 431 // If they specified a fragment, look for it in the response 432 // and pull it out. 433 var $fragment = findAll($data, options.fragment).first() 434 435 if ($fragment.length) { 436 obj.contents = $fragment.contents() 437 438 // If there's no title, look for data-title and title attributes 439 // on the fragment 440 if (!obj.title) 441 obj.title = $fragment.attr('title') || $fragment.data('title') 442 } 443 444 } else if (!/<html/i.test(data)) { 445 obj.contents = $data 446 } 447 448 // Clean up any <title> tags 449 if (obj.contents) { 450 // Remove any parent title elements 451 obj.contents = obj.contents.not('title') 452 453 // Then scrub any titles from their descendents 454 obj.contents.find('title').remove() 455 } 456 457 // Trim any whitespace off the title 458 if (obj.title) obj.title = $.trim(obj.title) 459 460 return obj 461 } 462 463 // Public: Reload current page with pjax. 464 // 465 // Returns whatever $.pjax returns. 466 pjax.reload = function(container, options) { 467 var defaults = { 468 url: window.location.href, 469 push: false, 470 replace: true, 471 scrollTo: false 472 } 473 474 return $.pjax($.extend(defaults, optionsFor(container, options))) 475 } 476 477 478 pjax.defaults = { 479 timeout: 65000, 480 push: true, 481 replace: false, 482 type: 'GET', 483 dataType: 'html', 484 scrollTo: 0, 485 maxCacheLength: 20 486 } 487 488 // Internal: History DOM caching class. 489 function Cache() { 490 this.mapping = {} 491 this.forwardStack = [] 492 this.backStack = [] 493 } 494 // Push previous state id and container contents into the history 495 // cache. Should be called in conjunction with `pushState` to save the 496 // previous container contents. 497 // 498 // id - State ID Number 499 // value - DOM Element to cache 500 // 501 // Returns nothing. 502 Cache.prototype.push = function(id, value) { 503 this.mapping[id] = value 504 this.backStack.push(id) 505 506 // Remove all entires in forward history stack after pushing 507 // a new page. 508 while (this.forwardStack.length) 509 delete this.mapping[this.forwardStack.shift()] 510 511 // Trim back history stack to max cache length. 512 while (this.backStack.length > pjax.defaults.maxCacheLength) 513 delete this.mapping[this.backStack.shift()] 514 } 515 // Retrieve cached DOM Element for state id. 516 // 517 // id - State ID Number 518 // 519 // Returns DOM Element(s) or undefined if cache miss. 520 Cache.prototype.get = function(id) { 521 return this.mapping[id] 522 } 523 // Shifts cache from forward history cache to back stack. Should be 524 // called on `popstate` with the previous state id and container 525 // contents. 526 // 527 // id - State ID Number 528 // value - DOM Element to cache 529 // 530 // Returns nothing. 531 Cache.prototype.forward = function(id, value) { 532 this.mapping[id] = value 533 this.backStack.push(id) 534 535 if (id = this.forwardStack.pop()) 536 delete this.mapping[id] 537 } 538 // Shifts cache from back history cache to forward stack. Should be 539 // called on `popstate` with the previous state id and container 540 // contents. 541 // 542 // id - State ID Number 543 // value - DOM Element to cache 544 // 545 // Returns nothing. 546 Cache.prototype.back = function(id, value) { 547 this.mapping[id] = value 548 this.forwardStack.push(id) 549 550 if (id = this.backStack.pop()) 551 delete this.mapping[id] 552 } 553 554 var containerCache = new Cache 555 556 557 // Export $.pjax.click 558 pjax.click = handleClick 559 560 561 // Used to detect initial (useless) popstate. 562 // If history.state exists, assume browser isn't going to fire initial popstate. 563 var popped = ('state' in window.history), initialURL = location.href 564 565 566 // popstate handler takes care of the back and forward buttons 567 // 568 // You probably shouldn't use pjax on pages with other pushState 569 // stuff yet. 570 $(window).bind('popstate', function(event){ 571 // Ignore inital popstate that some browsers fire on page load 572 var initialPop = !popped && location.href == initialURL 573 popped = true 574 if ( initialPop ) return 575 576 var state = event.state 577 578 if (state && state.container) { 579 var container = $(state.container) 580 if (container.length) { 581 var contents = containerCache.get(state.id) 582 583 if (pjax.state) { 584 // Since state ids always increase, we can deduce the history 585 // direction from the previous state. 586 var direction = pjax.state.id < state.id ? 'forward' : 'back' 587 588 // Cache current container before replacement and inform the 589 // cache which direction the history shifted. 590 containerCache[direction](pjax.state.id, container.clone(true, true).contents()) 591 } 592 593 var popstateEvent = $.Event('pjax:popstate', { 594 state: state, 595 direction: direction 596 }) 597 container.trigger(popstateEvent) 598 599 var options = { 600 id: state.id, 601 url: state.url, 602 container: container, 603 push: false, 604 fragment: state.fragment, 605 timeout: state.timeout, 606 scrollTo: false 607 } 608 609 if (contents) { 610 // pjax event is deprecated 611 $(document).trigger('pjax', [null, options]) 612 container.trigger('pjax:start', [null, options]) 613 // end.pjax event is deprecated 614 container.trigger('start.pjax', [null, options]) 615 616 if (state.title) document.title = state.title 617 container.html(contents) 618 pjax.state = state 619 620 container.trigger('pjax:end', [null, options]) 621 // end.pjax event is deprecated 622 container.trigger('end.pjax', [null, options]) 623 } else { 624 $.pjax(options) 625 } 626 627 // Force reflow/relayout before the browser tries to restore the 628 // scroll position. 629 container[0].offsetHeight 630 } else { 631 window.location = location.href 632 } 633 } 634 }) 635 636 637 // Add the state property to jQuery's event object so we can use it in 638 // $(window).bind('popstate') 639 if ( $.inArray('state', $.event.props) < 0 ) 640 $.event.props.push('state') 641 642 643 // Is pjax supported by this browser? 644 $.support.pjax = 645 window.history && window.history.pushState && window.history.replaceState 646 // pushState isn't reliable on iOS until 5. 647 && !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/) 648 649 // Fall back to normalcy for older browsers. 650 if ( !$.support.pjax ) { 651 $.pjax = function( options ) { 652 var url = $.isFunction(options.url) ? options.url() : options.url, 653 method = options.type ? options.type.toUpperCase() : 'GET' 654 655 var form = $('<form>', { 656 method: method === 'GET' ? 'GET' : 'POST', 657 action: url, 658 style: 'display:none' 659 }) 660 661 if (method !== 'GET' && method !== 'POST') { 662 form.append($('<input>', { 663 type: 'hidden', 664 name: '_method', 665 value: method.toLowerCase() 666 })) 667 } 668 669 var data = options.data 670 if (typeof data === 'string') { 671 $.each(data.split('&'), function(index, value) { 672 var pair = value.split('=') 673 form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]})) 674 }) 675 } else if (typeof data === 'object') { 676 for (key in data) 677 form.append($('<input>', {type: 'hidden', name: key, value: data[key]})) 678 } 679 680 $(document.body).append(form) 681 form.submit() 682 } 683 $.pjax.click = $.noop 684 $.pjax.reload = window.location.reload 685 $.fn.pjax = function() { return this } 686 } 687 688 })(jQuery);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:08:37 2014 | Cross-referenced by PHPXref 0.7.1 |