UNPKG

@pageworks/pjax

Version:

Turns any website into a SPA using Fetch and link prefetching.

625 lines 26.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var parse_options_1 = require("./lib/parse-options"); var trigger_1 = require("./lib/events/trigger"); var parse_dom_1 = require("./lib/parse-dom"); var scroll_1 = require("./lib/util/scroll"); var clear_active_1 = require("./lib/util/clear-active"); var state_manager_1 = require("@pageworks/state-manager"); var device_manager_1 = require("@pageworks/device-manager"); var Pjax = (function () { function Pjax(options) { var _this = this; this.handleManualLoad = function (e) { var uri = e.detail.uri; if (_this.options.debug) { console.log('%c[Pjax] ' + ("%cmanually loading " + uri), 'color:#f3ff35', 'color:#eee'); } _this.doRequest(uri); }; this.handlePopstate = function (e) { if (e.state) { if (_this.options.debug) { console.log('%c[Pjax] ' + "%chijacking popstate event", 'color:#f3ff35', 'color:#eee'); } _this._scrollTo = e.state.scrollPos; _this.loadUrl(e.state.uri, 'popstate'); } }; this.handleContinue = function (e) { _this._transitionFinished = true; if (_this._cachedSwitch !== null) { if (_this.options.titleSwitch) { document.title = _this._cachedSwitch.title; } _this.handleSwitches(_this._cachedSwitch.queue); } }; this._dom = document.documentElement; if (device_manager_1.default.isIE) { console.log('%c[Pjax] ' + "%cIE 11 detected - Pjax aborted", 'color:#f3ff35', 'color:#eee'); this._dom.classList.remove('dom-is-loading'); this._dom.classList.add('dom-is-loaded'); return; } this._cache = null; this.options = parse_options_1.default(options); this._request = null; this._response = null; this._confirmed = false; this._cachedSwitch = null; this._scrollTo = { x: 0, y: 0 }; this._isPushstate = true; this._scriptsToAppend = []; this._requestId = 0; this._transitionFinished = false; this.init(); } Pjax.prototype.init = function () { if (this.options.debug) { console.group(); console.log('%c[Pjax] ' + ("%cinitializing Pjax version " + Pjax.VERSION), 'color:#f3ff35', 'color:#eee'); console.log('%c[Pjax] ' + "%cview Pjax documentation at https://github.com/Pageworks/pjax", 'color:#f3ff35', 'color:#eee'); console.log('%c[Pjax] ' + "%cloaded with the following options: ", 'color:#f3ff35', 'color:#eee'); console.log(this.options); console.groupEnd(); } this._dom.classList.add('dom-is-loaded'); this._dom.classList.remove('dom-is-loading'); new state_manager_1.default(this.options.debug, true); window.addEventListener('popstate', this.handlePopstate); if (this.options.customTransitions) { document.addEventListener('pjax:continue', this.handleContinue); } document.addEventListener('pjax:load', this.handleManualLoad); parse_dom_1.default(document.body, this); }; Pjax.prototype.loadUrl = function (href, loadType) { if (this._confirmed) { return; } this.abortRequest(); this._cache = null; this.handleLoad(href, loadType); }; Pjax.prototype.abortRequest = function () { this._request = null; this._response = null; }; Pjax.prototype.finalize = function () { if (this.options.debug) { console.log('%c[Pjax] ' + "%cpage transition completed", 'color:#f3ff35', 'color:#eee'); } scroll_1.default(this._scrollTo); if (this.options.history) { if (this._isPushstate) { state_manager_1.default.doPush(this._response.url, document.title); } else { state_manager_1.default.doReplace(this._response.url, document.title); } } trigger_1.default(document, ['pjax:complete']); if (!this._scriptsToAppend.length) { if (this.options.debug) { console.log('%c[Pjax] ' + "%cNo new scripts to load", 'color:#f3ff35', 'color:#eee'); trigger_1.default(document, ['pjax:scriptContentLoaded']); } } this._dom.classList.add('dom-is-loaded'); this._dom.classList.remove('dom-is-loading'); this._cache = null; this._request = null; this._response = null; this._cachedSwitch = null; this._isPushstate = true; this._scrollTo = { x: 0, y: 0 }; this._confirmed = false; this._transitionFinished = false; }; Pjax.prototype.handleSwitches = function (switchQueue) { for (var i = 0; i < switchQueue.length; i++) { switchQueue[i].current.innerHTML = switchQueue[i].new.innerHTML; parse_dom_1.default(switchQueue[i].current, this); } this.finalize(); }; Pjax.prototype.switchSelectors = function (selectors, tempDocument) { var _this = this; if (tempDocument === null) { if (this.options.debug) { console.log('%c[Pjax] ' + ("%ctemporary document was null, telling the browser to load " + ((this._cache !== null) ? this._cache.url : this._response.url)), 'color:#f3ff35', 'color:#eee'); } if (this._cache !== null) { this.lastChance(this._cache.url); } else { this.lastChance(this._response.url); } return; } if (!this.options.importScripts) { var newScripts = Array.from(tempDocument.querySelectorAll('script')); if (newScripts.length) { var currentScripts_1 = Array.from(document.querySelectorAll('script')); newScripts.forEach(function (newScript) { var isNewScript = true; currentScripts_1.forEach(function (currentScript) { if (newScript.src === currentScript.src) { isNewScript = false; } }); if (isNewScript) { if (_this.options.debug) { console.log('%c[Pjax] ' + "%cthe new page contains scripts", 'color:#f3ff35', 'color:#eee'); } _this.lastChance(_this._response.url); } }); } } if (!this.options.importCSS) { var newStylesheets = Array.from(tempDocument.querySelectorAll('link[rel="stylesheet"]')); if (newStylesheets.length) { var currentStylesheets_1 = Array.from(document.querySelectorAll('link[rel="stylesheet"]')); newStylesheets.forEach(function (newStylesheet) { var isNewSheet = true; currentStylesheets_1.forEach(function (currentStylesheet) { if (newStylesheet.getAttribute('href') === currentStylesheet.getAttribute('href')) { isNewSheet = false; } }); if (isNewSheet) { if (_this.options.debug) { console.log('%c[Pjax] ' + "%cthe new page contains new stylesheets", 'color:#f3ff35', 'color:#eee'); } _this.lastChance(_this._response.url); } }); } } var switchQueue = []; for (var i = 0; i < selectors.length; i++) { var newContainers = Array.from(tempDocument.querySelectorAll(selectors[i])); var currentContainers = Array.from(document.querySelectorAll(selectors[i])); if (this.options.debug) { console.log('%c[Pjax] ' + ("%cswapping content from " + selectors[i]), 'color:#f3ff35', 'color:#eee'); } if (newContainers.length !== currentContainers.length) { if (this.options.debug) { console.log('%c[Pjax] ' + "%cthe dom doesn't look the same", 'color:#f3ff35', 'color:#eee'); } this.lastChance(this._response.url); return; } for (var k = 0; k < newContainers.length; k++) { var newContainer = newContainers[k]; var currentContainer = currentContainers[k]; var switchObject = { new: newContainer, current: currentContainer }; switchQueue.push(switchObject); } } if (switchQueue.length === 0) { if (this.options.debug) { console.log('%c[Pjax] ' + "%ccouldn't find anything to switch", 'color:#f3ff35', 'color:#eee'); } this.lastChance(this._response.url); return; } if (this.options.importScripts) { this.handleScripts(tempDocument); } if (this.options.importCSS) { if (!this.options.requireCssBeforeComplete) { this.handleCSS(tempDocument); } else { this.handleSynchronousCss(tempDocument) .then(function () { if (!_this.options.customTransitions) { if (_this.options.titleSwitch) { document.title = tempDocument.title; } _this.handleSwitches(switchQueue); } else { _this._cachedSwitch = { queue: switchQueue, title: tempDocument.title }; if (_this._transitionFinished) { if (_this.options.titleSwitch) { document.title = _this._cachedSwitch.title; } _this.handleSwitches(_this._cachedSwitch.queue); } } }); } } if (this.options.importCSS && this.options.requireCssBeforeComplete) { return; } if (!this.options.customTransitions) { if (this.options.titleSwitch) { document.title = tempDocument.title; } this.handleSwitches(switchQueue); } else { this._cachedSwitch = { queue: switchQueue, title: tempDocument.title }; if (this._transitionFinished) { if (this.options.titleSwitch) { document.title = this._cachedSwitch.title; } this.handleSwitches(this._cachedSwitch.queue); } } }; Pjax.prototype.lastChance = function (uri) { if (this.options.debug) { console.log('%c[Pjax] ' + ("%csomething caused Pjax to break, native loading " + uri), 'color:#f3ff35', 'color:#eee'); } window.location.href = uri; }; Pjax.prototype.statusCheck = function () { for (var status_1 = 200; status_1 <= 206; status_1++) { if (this._cache.status === status_1) { return true; } } return false; }; Pjax.prototype.loadCachedContent = function () { if (!this.statusCheck()) { this.lastChance(this._cache.url); return; } clear_active_1.default(); state_manager_1.default.doReplace(window.location.href, document.title); this.switchSelectors(this.options.selectors, this._cache.document); }; Pjax.prototype.parseContent = function (responseText) { var tempDocument = document.implementation.createHTMLDocument('pjax-temp-document'); var contentType = this._response.headers.get('Content-Type'); if (contentType === null) { return null; } var htmlRegex = /text\/html/gi; var matches = contentType.match(htmlRegex); if (matches !== null) { tempDocument.documentElement.innerHTML = responseText; return tempDocument; } return null; }; Pjax.prototype.cacheContent = function (responseText, responseStatus, uri) { var tempDocument = this.parseContent(responseText); this._cache = { status: responseStatus, document: tempDocument, url: uri }; if (tempDocument instanceof HTMLDocument) { if (this.options.debug) { console.log('%c[Pjax] ' + "%ccaching content", 'color:#f3ff35', 'color:#eee'); } } else { if (this.options.debug) { console.log('%c[Pjax] ' + "%cresponse wan't an HTML document", 'color:#f3ff35', 'color:#eee'); } trigger_1.default(document, ['pjax:error']); } }; Pjax.prototype.loadContent = function (responseText) { var tempDocument = this.parseContent(responseText); if (tempDocument instanceof HTMLDocument) { clear_active_1.default(); state_manager_1.default.doReplace(window.location.href, document.title); this.switchSelectors(this.options.selectors, tempDocument); } else { if (this.options.debug) { console.log('%c[Pjax] ' + "%cresponse wasn't an HTML document", 'color:#f3ff35', 'color:#eee'); } trigger_1.default(document, ['pjax:error']); this.lastChance(this._response.url); return; } }; Pjax.prototype.handleScripts = function (newDocument) { var _this = this; if (newDocument instanceof HTMLDocument) { var newScripts = Array.from(newDocument.querySelectorAll('script')); var currentScripts_2 = Array.from(document.querySelectorAll('script')); newScripts.forEach(function (newScript) { var appendScript = true; var newScriptFilename = 'inline-script'; if (newScript.getAttribute('src') !== null) { newScriptFilename = newScript.getAttribute('src').match(/[^/]+$/g)[0]; } currentScripts_2.forEach(function (currentScript) { var currentScriptFilename = 'inline-script'; if (currentScript.getAttribute('src') !== null) { currentScriptFilename = currentScript.getAttribute('src').match(/[^/]+$/g)[0]; } if (newScriptFilename === currentScriptFilename && newScriptFilename !== 'inline-script') { appendScript = false; } }); if (appendScript) { _this._scriptsToAppend.push(newScript); } }); if (this._scriptsToAppend.length) { var scriptLoadCount_1 = 0; this._scriptsToAppend.forEach(function (script) { if (script.src === '') { var newScript = document.createElement('script'); newScript.dataset.src = _this._response.url; newScript.innerHTML = script.innerHTML; _this.options.scriptImportLocation.appendChild(newScript); scriptLoadCount_1++; _this.checkForScriptLoadComplete(scriptLoadCount_1); } else { fetch(script.src, { method: 'GET', credentials: 'include', headers: new Headers({ 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/javascript' }) }) .then(function (request) { return request.text(); }) .then(function (response) { var newScript = document.createElement('script'); newScript.setAttribute('src', script.src); newScript.innerHTML = response; _this.options.scriptImportLocation.appendChild(newScript); scriptLoadCount_1++; _this.checkForScriptLoadComplete(scriptLoadCount_1); }) .catch(function (error) { console.error('Failed to fetch script', script.src, error); }); } }); } } }; Pjax.prototype.checkForScriptLoadComplete = function (scriptCount) { if (scriptCount === this._scriptsToAppend.length) { if (this.options.debug) { console.log('%c[Pjax] ' + "%cAll scripts have been loaded", 'color:#f3ff35', 'color:#eee'); } this._scriptsToAppend = []; trigger_1.default(document, ['pjax:scriptContentLoaded']); } }; Pjax.prototype.handleCSS = function (newDocument) { if (newDocument instanceof HTMLDocument) { var newStyles = Array.from(newDocument.querySelectorAll('link[rel="stylesheet"]')); var currentStyles_1 = Array.from(document.querySelectorAll('link[rel="stylesheet"], style[href]')); var stylesToAppend_1 = []; newStyles.forEach(function (newStyle) { var appendStyle = true; var newStyleFile = newStyle.getAttribute('href').match(/[^/]+$/g)[0]; currentStyles_1.forEach(function (currentStyle) { var currentStyleFile = currentStyle.getAttribute('href').match(/[^/]+$/g)[0]; if (newStyleFile === currentStyleFile) { appendStyle = false; } }); if (appendStyle) { stylesToAppend_1.push(newStyle); } }); if (stylesToAppend_1.length) { stylesToAppend_1.forEach(function (style) { fetch(style.href, { method: 'GET', credentials: 'include', headers: new Headers({ 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'text/javascript' }) }) .then(function (request) { return request.text(); }) .then(function (response) { var newStyle = document.createElement('style'); newStyle.setAttribute('rel', 'stylesheet'); newStyle.setAttribute('href', style.href); newStyle.innerHTML = response; document.head.appendChild(newStyle); }) .catch(function (error) { console.error('Failed to fetch stylesheet', style.href, error); }); }); } } }; Pjax.prototype.handleSynchronousCss = function (newDocument) { return new Promise(function (resolve) { if (newDocument instanceof HTMLDocument) { var newStyles = Array.from(newDocument.querySelectorAll('link[rel="stylesheet"]')); var currentStyles_2 = Array.from(document.querySelectorAll('link[rel="stylesheet"], style[href]')); var stylesToAppend_2 = []; var fetched_1 = 0; newStyles.forEach(function (newStyle) { var appendStyle = true; var newStyleFile = newStyle.getAttribute('href').match(/[^/]+$/g)[0]; currentStyles_2.forEach(function (currentStyle) { var currentStyleFile = currentStyle.getAttribute('href').match(/[^/]+$/g)[0]; if (newStyleFile === currentStyleFile) { appendStyle = false; } }); if (appendStyle) { stylesToAppend_2.push(newStyle); } }); if (stylesToAppend_2.length) { stylesToAppend_2.forEach(function (style) { fetch(style.href, { method: 'GET', credentials: 'include', headers: new Headers({ 'X-Requested-With': 'XMLHttpRequest' }) }) .then(function (request) { return request.text(); }) .then(function (response) { var newStyle = document.createElement('style'); newStyle.setAttribute('rel', 'stylesheet'); newStyle.setAttribute('href', style.href); newStyle.innerHTML = response; document.head.appendChild(newStyle); }) .catch(function (error) { console.error('Failed to fetch stylesheet', style.href, error); }) .then(function () { fetched_1++; if (fetched_1 === stylesToAppend_2.length) { resolve(); } }); }); } else { resolve(); } } }); }; Pjax.prototype.handleResponse = function (response) { var _this = this; if (this._request === null) { return; } if (this.options.debug) { console.log('%c[Pjax] ' + ("%cRequest status: " + response.status), 'color:#f3ff35', 'color:#eee'); } if (!response.ok) { trigger_1.default(document, ['pjax:error']); return; } this._response = response; response.text().then(function (responseText) { switch (_this._request) { case 'prefetch': if (_this._confirmed) { _this.loadContent(responseText); } else { _this.cacheContent(responseText, _this._response.status, _this._response.url); } break; case 'popstate': _this._isPushstate = false; _this.loadContent(responseText); break; case 'reload': _this._isPushstate = false; _this.loadContent(responseText); break; default: _this.loadContent(responseText); break; } }); }; Pjax.prototype.doRequest = function (href) { var _this = this; this._requestId++; var idAtStartOfRequest = this._requestId; var uri = href; var queryString = href.split('?')[1]; if (this.options.cacheBust) { uri += (queryString === undefined) ? ("?cb=" + Date.now()) : ("&cb=" + Date.now()); } var fetchMethod = 'GET'; var fetchHeaders = new Headers({ 'X-Requested-With': 'XMLHttpRequest', 'X-Pjax': 'true' }); fetch(uri, { method: fetchMethod, headers: fetchHeaders }).then(function (response) { if (idAtStartOfRequest === _this._requestId) { _this.handleResponse(response); } }).catch(function (error) { if (_this.options.debug) { console.group(); console.error('%c[Pjax] ' + "%cFetch error:", 'color:#f3ff35', 'color:#eee'); console.error(error); console.groupEnd(); } }); }; Pjax.prototype.handlePrefetch = function (href) { if (this._confirmed) { return; } if (this.options.debug) { console.log('%c[Pjax] ' + ("%cprefetching " + href), 'color:#f3ff35', 'color:#eee'); } this.abortRequest(); trigger_1.default(document, ['pjax:prefetch']); this._request = 'prefetch'; this.doRequest(href); }; Pjax.prototype.handleLoad = function (href, loadType, el) { if (el === void 0) { el = null; } if (this._confirmed) { return; } trigger_1.default(document, ['pjax:send'], el); this._dom.classList.remove('dom-is-loaded'); this._dom.classList.add('dom-is-loading'); this._confirmed = true; if (this._cache !== null) { if (this.options.debug) { console.log('%c[Pjax] ' + ("%cloading cached content from " + href), 'color:#f3ff35', 'color:#eee'); } this.loadCachedContent(); } else if (this._request !== 'prefetch') { if (this.options.debug) { console.log('%c[Pjax] ' + ("%cloading " + href), 'color:#f3ff35', 'color:#eee'); } this._request = loadType; this.doRequest(href); } }; Pjax.prototype.clearPrefetch = function () { if (!this._confirmed) { this._cache = null; this.abortRequest(); trigger_1.default(document, ['pjax:cancel']); } }; Pjax.load = function (url) { var customEvent = new CustomEvent('pjax:load', { detail: { uri: url } }); document.dispatchEvent(customEvent); }; Pjax.VERSION = '2.3.2'; return Pjax; }()); exports.default = Pjax; //# sourceMappingURL=pjax.js.map