Files
pjax/index.js

260 lines
7.7 KiB
JavaScript
Raw Normal View History

2014-10-14 11:42:36 +02:00
/* global _gaq: true, ga: true */
2014-10-14 08:13:50 +02:00
var newUid = require("./lib/uniqueid.js")
2014-10-14 11:42:36 +02:00
var on = require("./lib/events/on.js")
// var off = require("./lib/events/on.js")
var trigger = require("./lib/events/trigger.js")
2014-10-14 08:13:50 +02:00
var Pjax = function(options) {
this.firstrun = true
this.options = options
this.options.elements = this.options.elements || "a[href], form[action]"
this.options.selectors = this.options.selectors || ["title", ".js-Pjax"]
this.options.switches = this.options.switches || {}
this.options.switchesOptions = this.options.switchesOptions || {}
this.options.history = this.options.history || true
this.options.analytics = this.options.analytics || function(options) {
// options.backward or options.foward can be true or undefined
// by default, we do track back/foward hit
// https://productforums.google.com/forum/#!topic/analytics/WVwMDjLhXYk
if (window._gaq) {
_gaq.push(["_trackPageview"])
}
if (window.ga) {
ga("send", "pageview", {page: options.url, title: options.title})
}
}
this.options.scrollTo = this.options.scrollTo || 0
this.options.debug = this.options.debug || false
this.maxUid = this.lastUid = newUid()
// we cant replace body.outerHTML or head.outerHTML
// it create a bug where new body or new head are created in the dom
// if you set head.outerHTML, a new body tag is appended, so the dom get 2 body
// & it break the switchFallback which replace head & body
if (!this.options.switches.head) {
this.options.switches.head = this.switchElementsAlt
}
if (!this.options.switches.body) {
this.options.switches.body = this.switchElementsAlt
}
this.log("Pjax options", this.options)
if (typeof options.analytics !== "function") {
options.analytics = function() {}
}
this.parseDOM(document)
2014-10-14 11:42:36 +02:00
on(window, "popstate", function(st) {
2014-10-14 08:13:50 +02:00
if (st.state) {
var opt = Pjax.clone(this.options)
opt.url = st.state.url
opt.title = st.state.title
opt.history = false
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.prototype = {
log: require("./lib/proto/log.js"),
getElements: require("./lib/proto/get-elements.js"),
parseDOM: require("./lib/proto/parse-dom.js"),
attachLink: require("./lib/proto/attach-link.js"),
forEachSelectors: function(cb, context, DOMcontext) {
return require("./lib/foreach-selectors.js")(this.options.selectors, cb, context, DOMcontext)
},
switchSelectors: function(selectors, fromEl, toEl, options) {
return require("./lib/switch-selectors.js")(this.options.switches, this.options.switchesOptions, selectors, fromEl, toEl, options)
},
// too much problem with the code below
// + its 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() {
2014-10-14 11:42:36 +02:00
trigger(window, "resize scroll")
2014-10-14 08:13:50 +02:00
},
loadContent: function(html, options) {
var tmpEl = document.implementation.createHTMLDocument()
// parse HTML attributes to copy them
// since we are forced to use documentElement.innerHTML (outerHTML can't be used for <html>)
var htmlRegex = /<html[^>]+>/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("=")
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 {
2014-10-14 11:42:36 +02:00
this.switchSelectors(this.options.selectors, tmpEl, document, options)
// FF bug: Wont 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();
}
2014-10-14 08:13:50 +02:00
2014-10-14 11:42:36 +02:00
// execute scripts when DOM have been completely updated
this.options.selectors.forEach(function(selector) {
Pjax.forEachEls(document.querySelectorAll(selector), function(el) {
Pjax.executeScripts(el)
2014-10-14 08:13:50 +02:00
})
2014-10-14 11:42:36 +02:00
})
2014-10-14 08:13:50 +02:00
// }
// 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)
2014-10-14 11:42:36 +02:00
trigger(document, "pjax:send", options);
2014-10-14 08:13:50 +02:00
// Do the request
this.doRequest(href, function(html) {
// Fail if unable to load HTML via AJAX
if (html === false) {
2014-10-14 11:42:36 +02:00
trigger(document,"pjax:complete pjax:error", options)
2014-10-14 08:13:50 +02:00
return
}
// Clear out any focused controls before inserting new page contents.
document.activeElement.blur()
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
}
}
if (options.history) {
if (this.firstrun) {
this.lastUid = this.maxUid = newUid()
this.firstrun = false
window.history.replaceState({
2014-10-14 11:42:36 +02:00
url: window.location.href,
title: document.title,
uid: this.maxUid
},
document.title)
2014-10-14 08:13:50 +02:00
}
// Update browser history
this.lastUid = this.maxUid = newUid()
window.history.pushState({
2014-10-14 11:42:36 +02:00
url: href,
title: options.title,
uid: this.maxUid
},
2014-10-14 08:13:50 +02:00
options.title,
href)
}
this.forEachSelectors(function(el) {
this.parseDOM(el)
}, this)
// Fire Events
2014-10-14 11:42:36 +02:00
trigger(document,"pjax:complete pjax:success", options)
2014-10-14 08:13:50 +02:00
options.analytics()
// Scroll page to top on new page load
if (options.scrollTo !== false) {
if (options.scrollTo.length > 1) {
window.scrollTo(options.scrollTo[0], options.scrollTo[1])
}
else {
window.scrollTo(0, options.scrollTo)
}
}
}.bind(this))
}
}
if (Pjax.isSupported()) {
module.exports = Pjax
}
// if there isnt 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
}