var clone = require("./lib/clone.js") var executeScripts = require("./lib/execute-scripts.js") var forEachEls = require("./lib/foreach-els.js") var newUid = require("./lib/uniqueid.js") var on = require("./lib/events/on.js") // var off = require("./lib/events/on.js") var trigger = require("./lib/events/trigger.js") var defaultSwitches = require("./lib/switches") var Pjax = function(options) { this.firstrun = true this.state = { numPendingSwitches: 0, href: null, options: null } var parseOptions = require("./lib/proto/parse-options.js"); parseOptions.apply(this,[options]) this.log("Pjax options", this.options) this.maxUid = this.lastUid = newUid() this.parseDOM(document) on(window, "popstate", function(st) { if (st.state) { var opt = clone(this.options) opt.url = st.state.url opt.title = st.state.title opt.history = false opt.requestOptions = {}; if (st.state.uid < this.lastUid) { opt.backward = true } else { opt.forward = true } this.lastUid = st.state.uid // @todo implement history cache here, based on uid this.loadUrl(st.state.url, opt) } }.bind(this)) } Pjax.switches = defaultSwitches Pjax.prototype = { log: require("./lib/proto/log.js"), getElements: require("./lib/proto/get-elements.js"), parseDOM: require("./lib/proto/parse-dom.js"), refresh: require("./lib/proto/refresh.js"), reload: require("./lib/reload.js"), attachLink: require("./lib/proto/attach-link.js"), attachForm: require("./lib/proto/attach-form.js"), forEachSelectors: function(cb, context, DOMcontext) { return require("./lib/foreach-selectors.js").bind(this)(this.options.selectors, cb, context, DOMcontext) }, switchSelectors: function(selectors, fromEl, toEl, options) { return require("./lib/switches-selectors.js").bind(this)(this.options.switches, this.options.switchesOptions, selectors, fromEl, toEl, options) }, // too much problem with the code below // + it’s too dangerous // switchFallback: function(fromEl, toEl) { // this.switchSelectors(["head", "body"], fromEl, toEl) // // execute script when DOM is like it should be // Pjax.executeScripts(document.querySelector("head")) // Pjax.executeScripts(document.querySelector("body")) // } latestChance: function(href) { window.location = href }, onSwitch: function() { this.state.numPendingSwitches-- // debounce calls, so we only run this once after all switches are finished. if (this.state.numPendingSwitches === 0) { this.afterAllSwitches() } }, loadContent: function(html, options) { var tmpEl = document.implementation.createHTMLDocument("pjax") // parse HTML attributes to copy them // since we are forced to use documentElement.innerHTML (outerHTML can't be used for ) var htmlRegex = /]+>/gi var htmlAttribsRegex = /\s?[a-z:]+(?:\=(?:\'|\")[^\'\">]+(?:\'|\"))*/gi var matches = html.match(htmlRegex) if (matches && matches.length) { matches = matches[0].match(htmlAttribsRegex) if (matches.length) { matches.shift() matches.forEach(function(htmlAttrib) { var attr = htmlAttrib.trim().split("=") if (attr.length === 1) { tmpEl.documentElement.setAttribute(attr[0], true) } else { tmpEl.documentElement.setAttribute(attr[0], attr[1].slice(1, -1)) } }) } } tmpEl.documentElement.innerHTML = html this.log("load content", tmpEl.documentElement.attributes, tmpEl.documentElement.innerHTML.length) // Clear out any focused controls before inserting new page contents. // we clear focus on non form elements if (document.activeElement && !document.activeElement.value) { try { document.activeElement.blur() } catch (e) { } } // try { this.switchSelectors(this.options.selectors, tmpEl, document, options) // } // catch(e) { // if (this.options.debug) { // this.log("Pjax switch fail: ", e) // } // this.switchFallback(tmpEl, document) // } }, doRequest: require("./lib/request.js"), loadUrl: function(href, options) { this.log("load href", href, options) trigger(document, "pjax:send", options); // Do the request options.requestOptions.timeout = this.options.timeout this.doRequest(href, options.requestOptions, function(html, request) { // Fail if unable to load HTML via AJAX if (html === false) { trigger(document,"pjax:complete pjax:error", options) return } // Clear out any focused controls before inserting new page contents. document.activeElement.blur() if (request.responseURL) { if (href !== request.responseURL) { href = request.responseURL } } else if (request.getResponseHeader("X-PJAX-URL")) { href = request.getResponseHeader("X-PJAX-URL") } else if (request.getResponseHeader("X-XHR-Redirected-To")) { href = request.getResponseHeader("X-XHR-Redirected-To") } this.state.href = href this.state.options = options try { this.loadContent(html, options) } catch (e) { if (!this.options.debug) { if (console && console.error) { console.error("Pjax switch fail: ", e) } this.latestChance(href) return } else { throw e } } }.bind(this)) }, afterAllSwitches: function() { trigger(window, "resize scroll") // 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 = Array.prototype.slice.call(document.querySelectorAll("[autofocus]")).pop() if (autofocusEl && document.activeElement !== autofocusEl) { autofocusEl.focus(); } // execute scripts when DOM have been completely updated this.options.selectors.forEach(function(selector) { forEachEls(document.querySelectorAll(selector), function(el) { executeScripts(el) }) }) if (this.state.options.history) { if (this.firstrun) { this.lastUid = this.maxUid = newUid() this.firstrun = false window.history.replaceState({ url: window.location.href, title: document.title, uid: this.maxUid }, document.title) } // Update browser history this.lastUid = this.maxUid = newUid() var state = this.state window.history.pushState({ url: state.href, title: state.options.title, uid: this.maxUid }, state.options.title, state.href) } this.forEachSelectors(function(el) { this.parseDOM(el) }, this) // Fire Events trigger(document,"pjax:complete pjax:success", this.state.options) this.state.options.analytics() // Scroll page to top on new page load if (this.state.options.scrollTo !== false) { if (this.state.options.scrollTo.length > 1) { window.scrollTo(this.state.options.scrollTo[0], this.state.options.scrollTo[1]) } else { window.scrollTo(0, this.state.options.scrollTo) } } this.state = { numPendingSwitches: 0, href: null, options: null } } } Pjax.isSupported = require("./lib/is-supported.js"); // arguably could do `if( require("./lib/is-supported.js")()) {` but that might be a little to simple if (Pjax.isSupported()) { module.exports = Pjax } // if there isn’t required browser functions, returning stupid api else { var stupidPjax = function() {} for (var key in Pjax.prototype) { if (Pjax.prototype.hasOwnProperty(key) && typeof Pjax.prototype[key] === "function") { stupidPjax[key] = stupidPjax } } module.exports = stupidPjax }