diff options
Diffstat (limited to 'src/wwwroot/libraries/jquery-pjax')
| -rw-r--r-- | src/wwwroot/libraries/jquery-pjax/LICENSE | 20 | ||||
| -rw-r--r-- | src/wwwroot/libraries/jquery-pjax/README.md | 431 | ||||
| -rw-r--r-- | src/wwwroot/libraries/jquery-pjax/jquery.pjax.js | 903 | ||||
| -rw-r--r-- | src/wwwroot/libraries/jquery-pjax/package.json | 51 |
4 files changed, 1405 insertions, 0 deletions
diff --git a/src/wwwroot/libraries/jquery-pjax/LICENSE b/src/wwwroot/libraries/jquery-pjax/LICENSE new file mode 100644 index 0000000..c8e37ff --- /dev/null +++ b/src/wwwroot/libraries/jquery-pjax/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) Chris Wanstrath + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +Software), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/wwwroot/libraries/jquery-pjax/README.md b/src/wwwroot/libraries/jquery-pjax/README.md new file mode 100644 index 0000000..9aaf76b --- /dev/null +++ b/src/wwwroot/libraries/jquery-pjax/README.md @@ -0,0 +1,431 @@ +# pjax = pushState + ajax + + + .--. + / \ + ## a a + ( '._) + |'-- | + _.\___/_ ___pjax___ + ."\> \Y/|<'. '._.-' + / \ \_\/ / '-' / + | --'\_/|/ | _/ + |___.-' | |`'` + | | | + | / './ + /__./` | | + \ | | + \ | | + ; | | + / | | + jgs |___\_.\_ + `-"--'---' + +## Introduction + +pjax is a jQuery plugin that uses ajax and pushState to deliver a fast browsing experience with real permalinks, page titles, and a working back button. + +pjax works by grabbing html from your server via ajax and replacing the content +of a container on your page with the ajax'd html. It then updates the browser's +current URL using pushState without reloading your page's layout or any +resources (JS, CSS), giving the appearance of a fast, full page load. But really +it's just ajax and pushState. + +For [browsers that don't support pushState][compat] pjax fully degrades. + +## Overview + +pjax is not fully automatic. You'll need to setup and designate a containing element on your page that will be replaced when you navigate your site. + +Consider the following page. + +``` html +<!DOCTYPE html> +<html> +<head> + <!-- styles, scripts, etc --> +</head> +<body> + <h1>My Site</h1> + <div class="container" id="pjax-container"> + Go to <a href="/page/2">next page</a>. + </div> +</body> +</html> +``` + +We want pjax to grab the URL `/page/2` then replace `#pjax-container` with +whatever it gets back. No styles or scripts will be reloaded and even the `<h1>` +can stay the same - we just want to change the `#pjax-container` element. + +We do this by telling pjax to listen on `a` tags and use `#pjax-container` as the target container: + +``` javascript +$(document).pjax('a', '#pjax-container') +``` + +Now when someone in a pjax-compatible browser clicks "next page" the content of `#pjax-container` will be replaced with the body of `/page/2`. + +Magic! Almost. You still need to configure your server to look for pjax requests and send back pjax-specific content. + +The pjax ajax request sends an `X-PJAX` header so in this example (and in most cases) we want to return just the content of the page without any layout for any requests with that header. + +Here's what it might look like in Rails: + +``` ruby +def index + if request.headers['X-PJAX'] + render :layout => false + end +end +``` + +If you'd like a more automatic solution than pjax for Rails check out [Turbolinks][]. + +Also check out [RailsCasts #294: Playing with PJAX][railscasts]. + +## Installation + +### bower + +Via [Bower][]: + +``` +$ bower install jquery-pjax +``` + +Or, add `jquery-pjax` to your app's `bower.json`. + +``` json + "dependencies": { + "jquery-pjax": "latest" + } +``` + +### standalone + +pjax can be downloaded directly into your app's public directory - just be sure you've loaded jQuery first. + +``` +curl -LO https://raw.github.com/defunkt/jquery-pjax/master/jquery.pjax.js +``` + +**WARNING** Do not hotlink the raw script url. GitHub is not a CDN. + +## Dependencies + +Requires jQuery 1.8.x or higher. + +## Compatibility + +pjax only works with [browsers that support the `history.pushState` +API][compat]. When the API isn't supported pjax goes into fallback mode: +`$.fn.pjax` calls will be a no-op and `$.pjax` will hard load the given URL. + +For debugging purposes, you can intentionally disable pjax even if the browser supports `pushState`. Just call `$.pjax.disable()`. To see if pjax is actually supports `pushState`, check `$.support.pjax`. + +## Usage + +### `$.fn.pjax` + +Let's talk more about the most basic way to get started: + +``` javascript +$(document).pjax('a', '#pjax-container') +``` + +This will enable pjax on all links and designate the container as `#pjax-container`. + +If you are migrating an existing site you probably don't want to enable pjax everywhere just yet. Instead of using a global selector like `a` try annotating pjaxable links with `data-pjax`, then use `'a[data-pjax]'` as your selector. + +Or try this selector that matches any `<a data-pjax href=>` links inside a `<div data-pjax>` container. + +``` javascript +$(document).pjax('[data-pjax] a, a[data-pjax]', '#pjax-container') +``` + +#### Arguments + +The synopsis for the `$.fn.pjax` function is: + +``` javascript +$(document).pjax(selector, [container], options) +``` + +1. `selector` is a string to be used for click [event delegation][$.fn.on]. +2. `container` is a string selector that uniquely identifies the pjax container. +3. `options` is an object with keys described below. + +##### pjax options + +key | default | description +----|---------|------------ +`timeout` | 650 | ajax timeout in milliseconds after which a full refresh is forced +`push` | true | use [pushState][] to add a browser history entry upon navigation +`replace` | false | replace URL without adding browser history entry +`maxCacheLength` | 20 | maximum cache size for previous container contents +`version` | | a string or function returning the current pjax version +`scrollTo` | 0 | vertical position to scroll to after navigation. To avoid changing scroll position, pass `false`. +`type` | `"GET"` | see [$.ajax][] +`dataType` | `"html"` | see [$.ajax][] +`container` | | CSS selector for the element where content should be replaced +`url` | link.href | a string or function that returns the URL for the ajax request +`target` | link | eventually the `relatedTarget` value for [pjax events](#events) +`fragment` | | CSS selector for the fragment to extract from ajax response + +You can change the defaults globally by writing to the `$.pjax.defaults` object: + +``` javascript +$.pjax.defaults.timeout = 1200 +``` + +### `$.pjax.click` + +This is a lower level function used by `$.fn.pjax` itself. It allows you to get a little more control over the pjax event handling. + +This example uses the current click context to set an ancestor as the container: + +``` javascript +if ($.support.pjax) { + $(document).on('click', 'a[data-pjax]', function(event) { + var container = $(this).closest('[data-pjax-container]') + $.pjax.click(event, {container: container}) + }) +} +``` + +**NOTE** Use the explicit `$.support.pjax` guard. We aren't using `$.fn.pjax` so we should avoid binding this event handler unless the browser is actually going to use pjax. + +### `$.pjax.submit` + +Submits a form via pjax. + +``` javascript +$(document).on('submit', 'form[data-pjax]', function(event) { + $.pjax.submit(event, '#pjax-container') +}) +``` + +### `$.pjax.reload` + +Initiates a request for the current URL to the server using pjax mechanism and replaces the container with the response. Does not add a browser history entry. + +``` javascript +$.pjax.reload('#pjax-container', options) +``` + +### `$.pjax` + +Manual pjax invocation. Used mainly when you want to start a pjax request in a handler that didn't originate from a click. If you can get access to a click `event`, consider `$.pjax.click(event)` instead. + +``` javascript +function applyFilters() { + var url = urlForFilters() + $.pjax({url: url, container: '#pjax-container'}) +} +``` + +### Events + +All pjax events except `pjax:click` & `pjax:clicked` are fired from the pjax +container, not the link that was clicked. + +<table> +<tr> + <th>event</th> + <th>cancel</th> + <th>arguments</th> + <th>notes</th> +</tr> +<tr> + <th colspan=4>event lifecycle upon following a pjaxed link</th> +</tr> +<tr> + <td><code>pjax:click</code></td> + <td>✔︎</td> + <td><code>options</code></td> + <td>fires from a link that got activated; cancel to prevent pjax</td> +</tr> +<tr> + <td><code>pjax:beforeSend</code></td> + <td>✔︎</td> + <td><code>xhr, options</code></td> + <td>can set XHR headers</td> +</tr> +<tr> + <td><code>pjax:start</code></td> + <td></td> + <td><code>xhr, options</code></td> + <td></td> +</tr> +<tr> + <td><code>pjax:send</code></td> + <td></td> + <td><code>xhr, options</code></td> + <td></td> +</tr> +<tr> + <td><code>pjax:clicked</code></td> + <td></td> + <td><code>options</code></td> + <td>fires after pjax has started from a link that got clicked</td> +</tr> +<tr> + <td><code>pjax:beforeReplace</code></td> + <td></td> + <td><code>contents, options</code></td> + <td>before replacing HTML with content loaded from the server</td> +</tr> +<tr> + <td><code>pjax:success</code></td> + <td></td> + <td><code>data, status, xhr, options</code></td> + <td>after replacing HTML content loaded from the server</td> +</tr> +<tr> + <td><code>pjax:timeout</code></td> + <td>✔︎</td> + <td><code>xhr, options</code></td> + <td>fires after <code>options.timeout</code>; will hard refresh unless canceled</td> +</tr> +<tr> + <td><code>pjax:error</code></td> + <td>✔︎</td> + <td><code>xhr, textStatus, error, options</code></td> + <td>on ajax error; will hard refresh unless canceled</td> +</tr> +<tr> + <td><code>pjax:complete</code></td> + <td></td> + <td><code>xhr, textStatus, options</code></td> + <td>always fires after ajax, regardless of result</td> +</tr> +<tr> + <td><code>pjax:end</code></td> + <td></td> + <td><code>xhr, options</code></td> + <td></td> +</tr> +<tr> + <th colspan=4>event lifecycle on browser Back/Forward navigation</th> +</tr> +<tr> + <td><code>pjax:popstate</code></td> + <td></td> + <td></td> + <td>event <code>direction</code> property: "back"/"forward"</td> +</tr> +<tr> + <td><code>pjax:start</code></td> + <td></td> + <td><code>null, options</code></td> + <td>before replacing content</td> +</tr> +<tr> + <td><code>pjax:beforeReplace</code></td> + <td></td> + <td><code>contents, options</code></td> + <td>right before replacing HTML with content from cache</td> +</tr> +<tr> + <td><code>pjax:end</code></td> + <td></td> + <td><code>null, options</code></td> + <td>after replacing content</td> +</tr> +</table> + +`pjax:send` & `pjax:complete` are a good pair of events to use if you are implementing a +loading indicator. They'll only be triggered if an actual XHR request is made, +not if the content is loaded from cache: + +``` javascript +$(document).on('pjax:send', function() { + $('#loading').show() +}) +$(document).on('pjax:complete', function() { + $('#loading').hide() +}) +``` + +An example of canceling a `pjax:timeout` event would be to disable the fallback +timeout behavior if a spinner is being shown: + +``` javascript +$(document).on('pjax:timeout', function(event) { + // Prevent default timeout redirection behavior + event.preventDefault() +}) +``` + +### Server side + +Server configuration will vary between languages and frameworks. The following example shows how you might configure Rails. + +``` ruby +def index + if request.headers['X-PJAX'] + render :layout => false + end +end +``` + +An `X-PJAX` request header is set to differentiate a pjax request from normal XHR requests. In this case, if the request is pjax, we skip the layout html and just render the inner contents of the container. + +[Check if there is a pjax plugin][plugins] for your favorite server framework. + +#### Response types that force a reload + +By default, pjax will force a full reload of the page if it receives one of the +following responses from the server: + +* Page content that includes `<html>` when `fragment` selector wasn't explicitly + configured. Pjax presumes that the server's response hasn't been properly + configured for pjax. If `fragment` pjax option is given, pjax will simply + extract the content to insert into the DOM based on that selector. + +* Page content that is blank. Pjax assumes that the server is unable to deliver + proper pjax contents. + +* HTTP response code that is 4xx or 5xx, indicating some server error. + +#### Affecting the browser URL + +If the server needs to affect the URL which will appear in the browser URL after +pjax navigation (like HTTP redirects work for normal requests), it can set the +`X-PJAX-URL` header: + +``` ruby +def index + request.headers['X-PJAX-URL'] = "http://example.com/hello" +end +``` + +#### Layout Reloading + +Layouts can be forced to do a hard reload when assets or html changes. + +First set the initial layout version in your header with a custom meta tag. + +``` html +<meta http-equiv="x-pjax-version" content="v123"> +``` + +Then from the server side, set the `X-PJAX-Version` header to the same. + +``` ruby +if request.headers['X-PJAX'] + response.headers['X-PJAX-Version'] = "v123" +end +``` + +Deploying a deploy, bumping the version constant to force clients to do a full reload the next request getting the new layout and assets. + +[compat]: http://caniuse.com/#search=pushstate +[$.fn.on]: http://api.jquery.com/on/ +[$.ajax]: http://api.jquery.com/jQuery.ajax/ +[pushState]: https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history#Adding_and_modifying_history_entries +[plugins]: https://gist.github.com/4283721 +[turbolinks]: https://github.com/rails/turbolinks +[railscasts]: http://railscasts.com/episodes/294-playing-with-pjax +[bower]: https://github.com/twitter/bower diff --git a/src/wwwroot/libraries/jquery-pjax/jquery.pjax.js b/src/wwwroot/libraries/jquery-pjax/jquery.pjax.js new file mode 100644 index 0000000..d57269d --- /dev/null +++ b/src/wwwroot/libraries/jquery-pjax/jquery.pjax.js @@ -0,0 +1,903 @@ +/*! + * Copyright 2012, Chris Wanstrath + * Released under the MIT License + * https://github.com/defunkt/jquery-pjax + */ + +(function($){ + +// When called on a container with a selector, fetches the href with +// ajax into the container or with the data-pjax attribute on the link +// itself. +// +// Tries to make sure the back button and ctrl+click work the way +// you'd expect. +// +// Exported as $.fn.pjax +// +// Accepts a jQuery ajax options object that may include these +// pjax specific options: +// +// +// container - String selector for the element where to place the response body. +// push - Whether to pushState the URL. Defaults to true (of course). +// replace - Want to use replaceState instead? That's cool. +// +// For convenience the second parameter can be either the container or +// the options object. +// +// Returns the jQuery object +function fnPjax(selector, container, options) { + options = optionsFor(container, options) + return this.on('click.pjax', selector, function(event) { + var opts = options + if (!opts.container) { + opts = $.extend({}, options) + opts.container = $(this).attr('data-pjax') + } + handleClick(event, opts) + }) +} + +// Public: pjax on click handler +// +// Exported as $.pjax.click. +// +// event - "click" jQuery.Event +// options - pjax options +// +// Examples +// +// $(document).on('click', 'a', $.pjax.click) +// // is the same as +// $(document).pjax('a') +// +// Returns nothing. +function handleClick(event, container, options) { + options = optionsFor(container, options) + + var link = event.currentTarget + var $link = $(link) + + if (link.tagName.toUpperCase() !== 'A') + throw "$.fn.pjax or $.pjax.click requires an anchor element" + + // Middle click, cmd click, and ctrl click should open + // links in a new tab as normal. + if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) + return + + // Ignore cross origin links + if ( location.protocol !== link.protocol || location.hostname !== link.hostname ) + return + + // Ignore case when a hash is being tacked on the current URL + if ( link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location) ) + return + + // Ignore event with default prevented + if (event.isDefaultPrevented()) + return + + var defaults = { + url: link.href, + container: $link.attr('data-pjax'), + target: link + } + + var opts = $.extend({}, defaults, options) + var clickEvent = $.Event('pjax:click') + $link.trigger(clickEvent, [opts]) + + if (!clickEvent.isDefaultPrevented()) { + pjax(opts) + event.preventDefault() + $link.trigger('pjax:clicked', [opts]) + } +} + +// Public: pjax on form submit handler +// +// Exported as $.pjax.submit +// +// event - "click" jQuery.Event +// options - pjax options +// +// Examples +// +// $(document).on('submit', 'form', function(event) { +// $.pjax.submit(event, '[data-pjax-container]') +// }) +// +// Returns nothing. +function handleSubmit(event, container, options) { + options = optionsFor(container, options) + + var form = event.currentTarget + var $form = $(form) + + if (form.tagName.toUpperCase() !== 'FORM') + throw "$.pjax.submit requires a form element" + + var defaults = { + type: ($form.attr('method') || 'GET').toUpperCase(), + url: $form.attr('action'), + container: $form.attr('data-pjax'), + target: form + } + + if (defaults.type !== 'GET' && window.FormData !== undefined) { + defaults.data = new FormData(form) + defaults.processData = false + defaults.contentType = false + } else { + // Can't handle file uploads, exit + if ($form.find(':file').length) { + return + } + + // Fallback to manually serializing the fields + defaults.data = $form.serializeArray() + } + + pjax($.extend({}, defaults, options)) + + event.preventDefault() +} + +// Loads a URL with ajax, puts the response body inside a container, +// then pushState()'s the loaded URL. +// +// Works just like $.ajax in that it accepts a jQuery ajax +// settings object (with keys like url, type, data, etc). +// +// Accepts these extra keys: +// +// container - String selector for where to stick the response body. +// push - Whether to pushState the URL. Defaults to true (of course). +// replace - Want to use replaceState instead? That's cool. +// +// Use it just like $.ajax: +// +// var xhr = $.pjax({ url: this.href, container: '#main' }) +// console.log( xhr.readyState ) +// +// Returns whatever $.ajax returns. +function pjax(options) { + options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options) + + if ($.isFunction(options.url)) { + options.url = options.url() + } + + var hash = parseURL(options.url).hash + + var containerType = $.type(options.container) + if (containerType !== 'string') { + throw "expected string value for 'container' option; got " + containerType + } + var context = options.context = $(options.container) + if (!context.length) { + throw "the container selector '" + options.container + "' did not match anything" + } + + // We want the browser to maintain two separate internal caches: one + // for pjax'd partial page loads and one for normal page loads. + // Without adding this secret parameter, some browsers will often + // confuse the two. + if (!options.data) options.data = {} + if ($.isArray(options.data)) { + options.data.push({name: '_pjax', value: options.container}) + } else { + options.data._pjax = options.container + } + + function fire(type, args, props) { + if (!props) props = {} + props.relatedTarget = options.target + var event = $.Event(type, props) + context.trigger(event, args) + return !event.isDefaultPrevented() + } + + var timeoutTimer + + options.beforeSend = function(xhr, settings) { + // No timeout for non-GET requests + // Its not safe to request the resource again with a fallback method. + if (settings.type !== 'GET') { + settings.timeout = 0 + } + + xhr.setRequestHeader('X-PJAX', 'true') + xhr.setRequestHeader('X-PJAX-Container', options.container) + + if (!fire('pjax:beforeSend', [xhr, settings])) + return false + + if (settings.timeout > 0) { + timeoutTimer = setTimeout(function() { + if (fire('pjax:timeout', [xhr, options])) + xhr.abort('timeout') + }, settings.timeout) + + // Clear timeout setting so jquerys internal timeout isn't invoked + settings.timeout = 0 + } + + var url = parseURL(settings.url) + if (hash) url.hash = hash + options.requestUrl = stripInternalParams(url) + } + + options.complete = function(xhr, textStatus) { + if (timeoutTimer) + clearTimeout(timeoutTimer) + + fire('pjax:complete', [xhr, textStatus, options]) + + fire('pjax:end', [xhr, options]) + } + + options.error = function(xhr, textStatus, errorThrown) { + var container = extractContainer("", xhr, options) + + var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options]) + if (options.type == 'GET' && textStatus !== 'abort' && allowed) { + locationReplace(container.url) + } + } + + options.success = function(data, status, xhr) { + var previousState = pjax.state + + // If $.pjax.defaults.version is a function, invoke it first. + // Otherwise it can be a static string. + var currentVersion = typeof $.pjax.defaults.version === 'function' ? + $.pjax.defaults.version() : + $.pjax.defaults.version + + var latestVersion = xhr.getResponseHeader('X-PJAX-Version') + + var container = extractContainer(data, xhr, options) + + var url = parseURL(container.url) + if (hash) { + url.hash = hash + container.url = url.href + } + + // If there is a layout version mismatch, hard load the new url + if (currentVersion && latestVersion && currentVersion !== latestVersion) { + locationReplace(container.url) + return + } + + // If the new response is missing a body, hard load the page + if (!container.contents) { + locationReplace(container.url) + return + } + + pjax.state = { + id: options.id || uniqueId(), + url: container.url, + title: container.title, + container: options.container, + fragment: options.fragment, + timeout: options.timeout + } + + if (options.push || options.replace) { + window.history.replaceState(pjax.state, container.title, container.url) + } + + // Only blur the focus if the focused element is within the container. + var blurFocus = $.contains(context, document.activeElement) + + // Clear out any focused controls before inserting new page contents. + if (blurFocus) { + try { + document.activeElement.blur() + } catch (e) { /* ignore */ } + } + + if (container.title) document.title = container.title + + fire('pjax:beforeReplace', [container.contents, options], { + state: pjax.state, + previousState: previousState + }) + context.html(container.contents) + + // FF bug: Won't autofocus fields that are inserted via JS. + // This behavior is incorrect. So if theres no current focus, autofocus + // the last field. + // + // http://www.w3.org/html/wg/drafts/html/master/forms.html + var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0] + if (autofocusEl && document.activeElement !== autofocusEl) { + autofocusEl.focus() + } + + executeScriptTags(container.scripts) + + var scrollTo = options.scrollTo + + // Ensure browser scrolls to the element referenced by the URL anchor + if (hash) { + var name = decodeURIComponent(hash.slice(1)) + var target = document.getElementById(name) || document.getElementsByName(name)[0] + if (target) scrollTo = $(target).offset().top + } + + if (typeof scrollTo == 'number') $(window).scrollTop(scrollTo) + + fire('pjax:success', [data, status, xhr, options]) + } + + + // Initialize pjax.state for the initial page load. Assume we're + // using the container and options of the link we're loading for the + // back button to the initial page. This ensures good back button + // behavior. + if (!pjax.state) { + pjax.state = { + id: uniqueId(), + url: window.location.href, + title: document.title, + container: options.container, + fragment: options.fragment, + timeout: options.timeout + } + window.history.replaceState(pjax.state, document.title) + } + + // Cancel the current request if we're already pjaxing + abortXHR(pjax.xhr) + + pjax.options = options + var xhr = pjax.xhr = $.ajax(options) + + if (xhr.readyState > 0) { + if (options.push && !options.replace) { + // Cache current container element before replacing it + cachePush(pjax.state.id, [options.container, cloneContents(context)]) + + window.history.pushState(null, "", options.requestUrl) + } + + fire('pjax:start', [xhr, options]) + fire('pjax:send', [xhr, options]) + } + + return pjax.xhr +} + +// Public: Reload current page with pjax. +// +// Returns whatever $.pjax returns. +function pjaxReload(container, options) { + var defaults = { + url: window.location.href, + push: false, + replace: true, + scrollTo: false + } + + return pjax($.extend(defaults, optionsFor(container, options))) +} + +// Internal: Hard replace current state with url. +// +// Work for around WebKit +// https://bugs.webkit.org/show_bug.cgi?id=93506 +// +// Returns nothing. +function locationReplace(url) { + window.history.replaceState(null, "", pjax.state.url) + window.location.replace(url) +} + + +var initialPop = true +var initialURL = window.location.href +var initialState = window.history.state + +// Initialize $.pjax.state if possible +// Happens when reloading a page and coming forward from a different +// session history. +if (initialState && initialState.container) { + pjax.state = initialState +} + +// Non-webkit browsers don't fire an initial popstate event +if ('state' in window.history) { + initialPop = false +} + +// popstate handler takes care of the back and forward buttons +// +// You probably shouldn't use pjax on pages with other pushState +// stuff yet. +function onPjaxPopstate(event) { + + // Hitting back or forward should override any pending PJAX request. + if (!initialPop) { + abortXHR(pjax.xhr) + } + + var previousState = pjax.state + var state = event.state + var direction + + if (state && state.container) { + // When coming forward from a separate history session, will get an + // initial pop with a state we are already at. Skip reloading the current + // page. + if (initialPop && initialURL == state.url) return + + if (previousState) { + // If popping back to the same state, just skip. + // Could be clicking back from hashchange rather than a pushState. + if (previousState.id === state.id) return + + // Since state IDs always increase, we can deduce the navigation direction + direction = previousState.id < state.id ? 'forward' : 'back' + } + + var cache = cacheMapping[state.id] || [] + var containerSelector = cache[0] || state.container + var container = $(containerSelector), contents = cache[1] + + if (container.length) { + if (previousState) { + // Cache current container before replacement and inform the + // cache which direction the history shifted. + cachePop(direction, previousState.id, [containerSelector, cloneContents(container)]) + } + + var popstateEvent = $.Event('pjax:popstate', { + state: state, + direction: direction + }) + container.trigger(popstateEvent) + + var options = { + id: state.id, + url: state.url, + container: containerSelector, + push: false, + fragment: state.fragment, + timeout: state.timeout, + scrollTo: false + } + + if (contents) { + container.trigger('pjax:start', [null, options]) + + pjax.state = state + if (state.title) document.title = state.title + var beforeReplaceEvent = $.Event('pjax:beforeReplace', { + state: state, + previousState: previousState + }) + container.trigger(beforeReplaceEvent, [contents, options]) + container.html(contents) + + container.trigger('pjax:end', [null, options]) + } else { + pjax(options) + } + + // Force reflow/relayout before the browser tries to restore the + // scroll position. + container[0].offsetHeight // eslint-disable-line no-unused-expressions + } else { + locationReplace(location.href) + } + } + initialPop = false +} + +// Fallback version of main pjax function for browsers that don't +// support pushState. +// +// Returns nothing since it retriggers a hard form submission. +function fallbackPjax(options) { + var url = $.isFunction(options.url) ? options.url() : options.url, + method = options.type ? options.type.toUpperCase() : 'GET' + + var form = $('<form>', { + method: method === 'GET' ? 'GET' : 'POST', + action: url, + style: 'display:none' + }) + + if (method !== 'GET' && method !== 'POST') { + form.append($('<input>', { + type: 'hidden', + name: '_method', + value: method.toLowerCase() + })) + } + + var data = options.data + if (typeof data === 'string') { + $.each(data.split('&'), function(index, value) { + var pair = value.split('=') + form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]})) + }) + } else if ($.isArray(data)) { + $.each(data, function(index, value) { + form.append($('<input>', {type: 'hidden', name: value.name, value: value.value})) + }) + } else if (typeof data === 'object') { + var key + for (key in data) + form.append($('<input>', {type: 'hidden', name: key, value: data[key]})) + } + + $(document.body).append(form) + form.submit() +} + +// Internal: Abort an XmlHttpRequest if it hasn't been completed, +// also removing its event handlers. +function abortXHR(xhr) { + if ( xhr && xhr.readyState < 4) { + xhr.onreadystatechange = $.noop + xhr.abort() + } +} + +// Internal: Generate unique id for state object. +// +// Use a timestamp instead of a counter since ids should still be +// unique across page loads. +// +// Returns Number. +function uniqueId() { + return (new Date).getTime() +} + +function cloneContents(container) { + var cloned = container.clone() + // Unmark script tags as already being eval'd so they can get executed again + // when restored from cache. HAXX: Uses jQuery internal method. + cloned.find('script').each(function(){ + if (!this.src) $._data(this, 'globalEval', false) + }) + return cloned.contents() +} + +// Internal: Strip internal query params from parsed URL. +// +// Returns sanitized url.href String. +function stripInternalParams(url) { + url.search = url.search.replace(/([?&])(_pjax|_)=[^&]*/g, '').replace(/^&/, '') + return url.href.replace(/\?($|#)/, '$1') +} + +// Internal: Parse URL components and returns a Locationish object. +// +// url - String URL +// +// Returns HTMLAnchorElement that acts like Location. +function parseURL(url) { + var a = document.createElement('a') + a.href = url + return a +} + +// Internal: Return the `href` component of given URL object with the hash +// portion removed. +// +// location - Location or HTMLAnchorElement +// +// Returns String +function stripHash(location) { + return location.href.replace(/#.*/, '') +} + +// Internal: Build options Object for arguments. +// +// For convenience the first parameter can be either the container or +// the options object. +// +// Examples +// +// optionsFor('#container') +// // => {container: '#container'} +// +// optionsFor('#container', {push: true}) +// // => {container: '#container', push: true} +// +// optionsFor({container: '#container', push: true}) +// // => {container: '#container', push: true} +// +// Returns options Object. +function optionsFor(container, options) { + if (container && options) { + options = $.extend({}, options) + options.container = container + return options + } else if ($.isPlainObject(container)) { + return container + } else { + return {container: container} + } +} + +// Internal: Filter and find all elements matching the selector. +// +// Where $.fn.find only matches descendants, findAll will test all the +// top level elements in the jQuery object as well. +// +// elems - jQuery object of Elements +// selector - String selector to match +// +// Returns a jQuery object. +function findAll(elems, selector) { + return elems.filter(selector).add(elems.find(selector)) +} + +function parseHTML(html) { + return $.parseHTML(html, document, true) +} + +// Internal: Extracts container and metadata from response. +// +// 1. Extracts X-PJAX-URL header if set +// 2. Extracts inline <title> tags +// 3. Builds response Element and extracts fragment if set +// +// data - String response data +// xhr - XHR response +// options - pjax options Object +// +// Returns an Object with url, title, and contents keys. +function extractContainer(data, xhr, options) { + var obj = {}, fullDocument = /<html/i.test(data) + + // Prefer X-PJAX-URL header if it was set, otherwise fallback to + // using the original requested url. + var serverUrl = xhr.getResponseHeader('X-PJAX-URL') + obj.url = serverUrl ? stripInternalParams(parseURL(serverUrl)) : options.requestUrl + + var $head, $body + // Attempt to parse response html into elements + if (fullDocument) { + $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0])) + var head = data.match(/<head[^>]*>([\s\S.]*)<\/head>/i) + $head = head != null ? $(parseHTML(head[0])) : $body + } else { + $head = $body = $(parseHTML(data)) + } + + // If response data is empty, return fast + if ($body.length === 0) + return obj + + // If there's a <title> tag in the header, use it as + // the page's title. + obj.title = findAll($head, 'title').last().text() + + if (options.fragment) { + var $fragment = $body + // If they specified a fragment, look for it in the response + // and pull it out. + if (options.fragment !== 'body') { + $fragment = findAll($fragment, options.fragment).first() + } + + if ($fragment.length) { + obj.contents = options.fragment === 'body' ? $fragment : $fragment.contents() + + // If there's no title, look for data-title and title attributes + // on the fragment + if (!obj.title) + obj.title = $fragment.attr('title') || $fragment.data('title') + } + + } else if (!fullDocument) { + obj.contents = $body + } + + // Clean up any <title> tags + if (obj.contents) { + // Remove any parent title elements + obj.contents = obj.contents.not(function() { return $(this).is('title') }) + + // Then scrub any titles from their descendants + obj.contents.find('title').remove() + + // Gather all script[src] elements + obj.scripts = findAll(obj.contents, 'script[src]').remove() + obj.contents = obj.contents.not(obj.scripts) + } + + // Trim any whitespace off the title + if (obj.title) obj.title = $.trim(obj.title) + + return obj +} + +// Load an execute scripts using standard script request. +// +// Avoids jQuery's traditional $.getScript which does a XHR request and +// globalEval. +// +// scripts - jQuery object of script Elements +// +// Returns nothing. +function executeScriptTags(scripts) { + if (!scripts) return + + var existingScripts = $('script[src]') + + scripts.each(function() { + var src = this.src + var matchedScripts = existingScripts.filter(function() { + return this.src === src + }) + if (matchedScripts.length) return + + var script = document.createElement('script') + var type = $(this).attr('type') + if (type) script.type = type + script.src = $(this).attr('src') + document.head.appendChild(script) + }) +} + +// Internal: History DOM caching class. +var cacheMapping = {} +var cacheForwardStack = [] +var cacheBackStack = [] + +// Push previous state id and container contents into the history +// cache. Should be called in conjunction with `pushState` to save the +// previous container contents. +// +// id - State ID Number +// value - DOM Element to cache +// +// Returns nothing. +function cachePush(id, value) { + cacheMapping[id] = value + cacheBackStack.push(id) + + // Remove all entries in forward history stack after pushing a new page. + trimCacheStack(cacheForwardStack, 0) + + // Trim back history stack to max cache length. + trimCacheStack(cacheBackStack, pjax.defaults.maxCacheLength) +} + +// Shifts cache from directional history cache. Should be +// called on `popstate` with the previous state id and container +// contents. +// +// direction - "forward" or "back" String +// id - State ID Number +// value - DOM Element to cache +// +// Returns nothing. +function cachePop(direction, id, value) { + var pushStack, popStack + cacheMapping[id] = value + + if (direction === 'forward') { + pushStack = cacheBackStack + popStack = cacheForwardStack + } else { + pushStack = cacheForwardStack + popStack = cacheBackStack + } + + pushStack.push(id) + id = popStack.pop() + if (id) delete cacheMapping[id] + + // Trim whichever stack we just pushed to to max cache length. + trimCacheStack(pushStack, pjax.defaults.maxCacheLength) +} + +// Trim a cache stack (either cacheBackStack or cacheForwardStack) to be no +// longer than the specified length, deleting cached DOM elements as necessary. +// +// stack - Array of state IDs +// length - Maximum length to trim to +// +// Returns nothing. +function trimCacheStack(stack, length) { + while (stack.length > length) + delete cacheMapping[stack.shift()] +} + +// Public: Find version identifier for the initial page load. +// +// Returns String version or undefined. +function findVersion() { + return $('meta').filter(function() { + var name = $(this).attr('http-equiv') + return name && name.toUpperCase() === 'X-PJAX-VERSION' + }).attr('content') +} + +// Install pjax functions on $.pjax to enable pushState behavior. +// +// Does nothing if already enabled. +// +// Examples +// +// $.pjax.enable() +// +// Returns nothing. +function enable() { + $.fn.pjax = fnPjax + $.pjax = pjax + $.pjax.enable = $.noop + $.pjax.disable = disable + $.pjax.click = handleClick + $.pjax.submit = handleSubmit + $.pjax.reload = pjaxReload + $.pjax.defaults = { + timeout: 650, + push: true, + replace: false, + type: 'GET', + dataType: 'html', + scrollTo: 0, + maxCacheLength: 20, + version: findVersion + } + $(window).on('popstate.pjax', onPjaxPopstate) +} + +// Disable pushState behavior. +// +// This is the case when a browser doesn't support pushState. It is +// sometimes useful to disable pushState for debugging on a modern +// browser. +// +// Examples +// +// $.pjax.disable() +// +// Returns nothing. +function disable() { + $.fn.pjax = function() { return this } + $.pjax = fallbackPjax + $.pjax.enable = enable + $.pjax.disable = $.noop + $.pjax.click = $.noop + $.pjax.submit = $.noop + $.pjax.reload = function() { window.location.reload() } + + $(window).off('popstate.pjax', onPjaxPopstate) +} + + +// Add the state property to jQuery's event object so we can use it in +// $(window).bind('popstate') +if ($.event.props && $.inArray('state', $.event.props) < 0) { + $.event.props.push('state') +} else if (!('state' in $.Event.prototype)) { + $.event.addProp('state') +} + +// Is pjax supported by this browser? +$.support.pjax = + window.history && window.history.pushState && window.history.replaceState && + // pushState isn't reliable on iOS until 5. + !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/) + +if ($.support.pjax) { + enable() +} else { + disable() +} + +})(jQuery) diff --git a/src/wwwroot/libraries/jquery-pjax/package.json b/src/wwwroot/libraries/jquery-pjax/package.json new file mode 100644 index 0000000..ad9dc26 --- /dev/null +++ b/src/wwwroot/libraries/jquery-pjax/package.json @@ -0,0 +1,51 @@ +{ + "_from": "jquery-pjax", + "_id": "jquery-pjax@2.0.1", + "_inBundle": false, + "_integrity": "sha1-azoboW5kTmJL3P5y62s9lqhG9fI=", + "_location": "/jquery-pjax", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "jquery-pjax", + "name": "jquery-pjax", + "escapedName": "jquery-pjax", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/jquery-pjax/-/jquery-pjax-2.0.1.tgz", + "_shasum": "6b3a1ba16e644e624bdcfe72eb6b3d96a846f5f2", + "_spec": "jquery-pjax", + "_where": "/Users/ivarlovlie/Development/repos/git/pit/Argus/source/Argus.Server/wwwroot/lib", + "bugs": { + "url": "https://github.com/defunkt/jquery-pjax/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "jQuery plugin for ajax + pushState navigation", + "devDependencies": { + "eslint": "^3.19.0" + }, + "files": [ + "LICENSE", + "jquery.pjax.js" + ], + "homepage": "https://github.com/defunkt/jquery-pjax#readme", + "license": "MIT", + "main": "jquery.pjax.js", + "name": "jquery-pjax", + "repository": { + "type": "git", + "url": "git+https://github.com/defunkt/jquery-pjax.git" + }, + "scripts": { + "test": "./script/test" + }, + "version": "2.0.1" +} |
