[ Index ]

PHP Cross Reference of vtigercrm-6.1.0

title

Body

[close]

/libraries/jquery/defunkt-jquery-pjax/ -> jquery.pjax.js (source)

   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);


Generated: Fri Nov 28 20:08:37 2014 Cross-referenced by PHPXref 0.7.1