2019-02-13 22:26:57 -05:00
|
|
|
|
var executeScripts = require("./lib/execute-scripts");
|
|
|
|
|
|
var forEachEls = require("./lib/foreach-els");
|
|
|
|
|
|
var parseOptions = require("./lib/parse-options");
|
|
|
|
|
|
var switches = require("./lib/switches");
|
|
|
|
|
|
var newUid = require("./lib/uniqueid");
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
var on = require("./lib/events/on");
|
|
|
|
|
|
var trigger = require("./lib/events/trigger");
|
2014-10-14 11:42:36 +02:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
var clone = require("./lib/util/clone");
|
|
|
|
|
|
var contains = require("./lib/util/contains");
|
|
|
|
|
|
var extend = require("./lib/util/extend");
|
|
|
|
|
|
var noop = require("./lib/util/noop");
|
2016-01-05 14:30:09 +11:00
|
|
|
|
|
2014-10-14 08:13:50 +02:00
|
|
|
|
var Pjax = function(options) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.state = {
|
|
|
|
|
|
numPendingSwitches: 0,
|
|
|
|
|
|
href: null,
|
|
|
|
|
|
options: null
|
|
|
|
|
|
};
|
2018-03-06 10:06:38 +00:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.options = parseOptions(options);
|
|
|
|
|
|
this.log("Pjax options", this.options);
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
if (this.options.scrollRestoration && "scrollRestoration" in history) {
|
|
|
|
|
|
history.scrollRestoration = "manual";
|
|
|
|
|
|
}
|
2018-01-28 14:35:25 +00:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.maxUid = this.lastUid = newUid();
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.parseDOM(document);
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
on(
|
|
|
|
|
|
window,
|
|
|
|
|
|
"popstate",
|
|
|
|
|
|
function(st) {
|
2014-10-14 08:13:50 +02:00
|
|
|
|
if (st.state) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
var opt = clone(this.options);
|
|
|
|
|
|
opt.url = st.state.url;
|
|
|
|
|
|
opt.title = st.state.title;
|
2018-07-23 20:46:13 -04:00
|
|
|
|
// Since state already exists, prevent it from being pushed again
|
2019-02-13 22:26:57 -05:00
|
|
|
|
opt.history = false;
|
|
|
|
|
|
opt.scrollPos = st.state.scrollPos;
|
2014-10-14 08:13:50 +02:00
|
|
|
|
if (st.state.uid < this.lastUid) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
opt.backward = true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
opt.forward = true;
|
2014-10-14 08:13:50 +02:00
|
|
|
|
}
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.lastUid = st.state.uid;
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
|
|
|
|
|
// @todo implement history cache here, based on uid
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.loadUrl(st.state.url, opt);
|
2014-10-14 08:13:50 +02:00
|
|
|
|
}
|
2019-02-13 22:26:57 -05:00
|
|
|
|
}.bind(this)
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
Pjax.switches = switches;
|
2017-12-19 07:49:26 -05:00
|
|
|
|
|
2014-10-14 08:13:50 +02:00
|
|
|
|
Pjax.prototype = {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
log: require("./lib/proto/log"),
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2018-02-02 09:52:44 -05:00
|
|
|
|
getElements: function(el) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
return el.querySelectorAll(this.options.elements);
|
2018-02-02 09:52:44 -05:00
|
|
|
|
},
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2018-02-02 09:52:44 -05:00
|
|
|
|
parseDOM: function(el) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
var parseElement = require("./lib/proto/parse-element");
|
|
|
|
|
|
forEachEls(this.getElements(el), parseElement, this);
|
2018-02-02 09:52:44 -05:00
|
|
|
|
},
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2018-02-02 09:52:44 -05:00
|
|
|
|
refresh: function(el) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.parseDOM(el || document);
|
2018-02-02 09:52:44 -05:00
|
|
|
|
},
|
2015-01-21 00:11:59 -08:00
|
|
|
|
|
2018-02-02 09:52:44 -05:00
|
|
|
|
reload: function() {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
window.location.reload();
|
2018-02-02 09:52:44 -05:00
|
|
|
|
},
|
2016-03-24 12:38:15 +01:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
attachLink: require("./lib/proto/attach-link"),
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
attachForm: require("./lib/proto/attach-form"),
|
2017-09-18 14:13:39 +02:00
|
|
|
|
|
2014-10-14 08:13:50 +02:00
|
|
|
|
forEachSelectors: function(cb, context, DOMcontext) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
return require("./lib/foreach-selectors").bind(this)(
|
|
|
|
|
|
this.options.selectors,
|
|
|
|
|
|
cb,
|
|
|
|
|
|
context,
|
|
|
|
|
|
DOMcontext
|
|
|
|
|
|
);
|
2014-10-14 08:13:50 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
switchSelectors: function(selectors, fromEl, toEl, options) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
return require("./lib/switches-selectors").bind(this)(
|
|
|
|
|
|
this.options.switches,
|
|
|
|
|
|
this.options.switchesOptions,
|
|
|
|
|
|
selectors,
|
|
|
|
|
|
fromEl,
|
|
|
|
|
|
toEl,
|
|
|
|
|
|
options
|
|
|
|
|
|
);
|
2014-10-14 08:13:50 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
latestChance: function(href) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
window.location = href;
|
2014-10-14 08:13:50 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onSwitch: function() {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
trigger(window, "resize scroll");
|
2018-01-22 20:42:57 -05:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.state.numPendingSwitches--;
|
2018-01-22 10:55:29 -05:00
|
|
|
|
|
|
|
|
|
|
// debounce calls, so we only run this once after all switches are finished.
|
|
|
|
|
|
if (this.state.numPendingSwitches === 0) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.afterAllSwitches();
|
2018-01-22 10:55:29 -05:00
|
|
|
|
}
|
2014-10-14 08:13:50 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
loadContent: function(html, options) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
var tmpEl = document.implementation.createHTMLDocument("pjax");
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
|
|
|
|
|
// parse HTML attributes to copy them
|
|
|
|
|
|
// since we are forced to use documentElement.innerHTML (outerHTML can't be used for <html>)
|
2019-02-13 22:26:57 -05:00
|
|
|
|
var htmlRegex = /<html[^>]+>/gi;
|
|
|
|
|
|
var htmlAttribsRegex = /\s?[a-z:]+(?:=['"][^'">]+['"])*/gi;
|
|
|
|
|
|
var matches = html.match(htmlRegex);
|
2014-10-14 08:13:50 +02:00
|
|
|
|
if (matches && matches.length) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
matches = matches[0].match(htmlAttribsRegex);
|
2014-10-14 08:13:50 +02:00
|
|
|
|
if (matches.length) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
matches.shift();
|
2014-10-14 08:13:50 +02:00
|
|
|
|
matches.forEach(function(htmlAttrib) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
var attr = htmlAttrib.trim().split("=");
|
2016-04-19 12:15:39 -04:00
|
|
|
|
if (attr.length === 1) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
tmpEl.documentElement.setAttribute(attr[0], true);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
tmpEl.documentElement.setAttribute(attr[0], attr[1].slice(1, -1));
|
2016-04-19 12:15:39 -04:00
|
|
|
|
}
|
2019-02-13 22:26:57 -05:00
|
|
|
|
});
|
2014-10-14 08:13:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
tmpEl.documentElement.innerHTML = html;
|
|
|
|
|
|
this.log(
|
|
|
|
|
|
"load content",
|
|
|
|
|
|
tmpEl.documentElement.attributes,
|
|
|
|
|
|
tmpEl.documentElement.innerHTML.length
|
|
|
|
|
|
);
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
|
|
|
|
|
// Clear out any focused controls before inserting new page contents.
|
2019-02-13 22:26:57 -05:00
|
|
|
|
if (
|
|
|
|
|
|
document.activeElement &&
|
|
|
|
|
|
contains(document, this.options.selectors, document.activeElement)
|
|
|
|
|
|
) {
|
2014-10-14 08:13:50 +02:00
|
|
|
|
try {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
document.activeElement.blur();
|
|
|
|
|
|
} catch (e) {} // eslint-disable-line no-empty
|
2014-10-14 08:13:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.switchSelectors(this.options.selectors, tmpEl, document, options);
|
2014-10-14 08:13:50 +02:00
|
|
|
|
},
|
|
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
abortRequest: require("./lib/abort-request"),
|
2018-01-23 15:09:57 +00:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
doRequest: require("./lib/send-request"),
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
handleResponse: require("./lib/proto/handle-response"),
|
2018-03-15 16:12:32 -04:00
|
|
|
|
|
2014-10-14 08:13:50 +02:00
|
|
|
|
loadUrl: function(href, options) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
options =
|
|
|
|
|
|
typeof options === "object"
|
|
|
|
|
|
? extend({}, this.options, options)
|
|
|
|
|
|
: clone(this.options);
|
2018-03-06 10:06:38 +00:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.log("load href", href, options);
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2018-01-23 15:09:57 +00:00
|
|
|
|
// Abort any previous request
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.abortRequest(this.request);
|
2018-01-23 15:09:57 +00:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
trigger(document, "pjax:send", options);
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
|
|
|
|
|
// Do the request
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.request = this.doRequest(
|
|
|
|
|
|
href,
|
|
|
|
|
|
options,
|
|
|
|
|
|
this.handleResponse.bind(this)
|
|
|
|
|
|
);
|
2018-01-22 10:55:29 -05:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
afterAllSwitches: function() {
|
|
|
|
|
|
// 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
|
2019-02-13 22:26:57 -05:00
|
|
|
|
var autofocusEl = Array.prototype.slice
|
|
|
|
|
|
.call(document.querySelectorAll("[autofocus]"))
|
|
|
|
|
|
.pop();
|
2018-01-22 10:55:29 -05:00
|
|
|
|
if (autofocusEl && document.activeElement !== autofocusEl) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
autofocusEl.focus();
|
2018-01-22 10:55:29 -05:00
|
|
|
|
}
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2018-01-22 10:55:29 -05:00
|
|
|
|
// execute scripts when DOM have been completely updated
|
|
|
|
|
|
this.options.selectors.forEach(function(selector) {
|
|
|
|
|
|
forEachEls(document.querySelectorAll(selector), function(el) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
executeScripts(el);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
2018-01-22 10:55:29 -05:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
var state = this.state;
|
2018-01-22 10:55:29 -05:00
|
|
|
|
|
|
|
|
|
|
if (state.options.history) {
|
2018-01-22 11:27:13 -05:00
|
|
|
|
if (!window.history.state) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.lastUid = this.maxUid = newUid();
|
|
|
|
|
|
window.history.replaceState(
|
|
|
|
|
|
{
|
2014-10-14 11:42:36 +02:00
|
|
|
|
url: window.location.href,
|
|
|
|
|
|
title: document.title,
|
2018-01-22 11:27:13 -05:00
|
|
|
|
uid: this.maxUid,
|
2018-01-22 17:32:59 -05:00
|
|
|
|
scrollPos: [0, 0]
|
2014-10-14 11:42:36 +02:00
|
|
|
|
},
|
2019-02-13 22:26:57 -05:00
|
|
|
|
document.title
|
|
|
|
|
|
);
|
2018-01-22 10:55:29 -05:00
|
|
|
|
}
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2018-01-22 10:55:29 -05:00
|
|
|
|
// Update browser history
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.lastUid = this.maxUid = newUid();
|
2018-01-22 10:55:29 -05:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
window.history.pushState(
|
|
|
|
|
|
{
|
2018-01-22 10:55:29 -05:00
|
|
|
|
url: state.href,
|
|
|
|
|
|
title: state.options.title,
|
2018-01-22 11:27:13 -05:00
|
|
|
|
uid: this.maxUid,
|
2018-01-22 17:32:59 -05:00
|
|
|
|
scrollPos: [0, 0]
|
2014-10-14 11:42:36 +02:00
|
|
|
|
},
|
2018-01-22 10:55:29 -05:00
|
|
|
|
state.options.title,
|
2019-02-13 22:26:57 -05:00
|
|
|
|
state.href
|
|
|
|
|
|
);
|
2018-01-22 10:55:29 -05:00
|
|
|
|
}
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2018-01-22 10:55:29 -05:00
|
|
|
|
this.forEachSelectors(function(el) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
this.parseDOM(el);
|
|
|
|
|
|
}, this);
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2018-01-22 10:55:29 -05:00
|
|
|
|
// Fire Events
|
2019-02-13 22:26:57 -05:00
|
|
|
|
trigger(document, "pjax:complete pjax:success", state.options);
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2018-01-22 19:06:45 +00:00
|
|
|
|
if (typeof state.options.analytics === "function") {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
state.options.analytics();
|
2018-01-22 19:06:45 +00:00
|
|
|
|
}
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2018-01-22 11:27:13 -05:00
|
|
|
|
if (state.options.history) {
|
2018-01-21 19:04:50 -05:00
|
|
|
|
// First parse url and check for hash to override scroll
|
2019-02-13 22:26:57 -05:00
|
|
|
|
var a = document.createElement("a");
|
|
|
|
|
|
a.href = this.state.href;
|
2018-01-21 19:04:50 -05:00
|
|
|
|
if (a.hash) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
var name = a.hash.slice(1);
|
|
|
|
|
|
name = decodeURIComponent(name);
|
2018-01-21 19:04:50 -05:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
var curtop = 0;
|
|
|
|
|
|
var target =
|
|
|
|
|
|
document.getElementById(name) || document.getElementsByName(name)[0];
|
2018-01-21 19:04:50 -05:00
|
|
|
|
if (target) {
|
|
|
|
|
|
// http://stackoverflow.com/questions/8111094/cross-browser-javascript-function-to-find-actual-position-of-an-element-in-page
|
|
|
|
|
|
if (target.offsetParent) {
|
|
|
|
|
|
do {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
curtop += target.offsetTop;
|
2018-01-21 19:04:50 -05:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
target = target.offsetParent;
|
|
|
|
|
|
} while (target);
|
2018-01-21 19:04:50 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2019-02-13 22:26:57 -05:00
|
|
|
|
window.scrollTo(0, curtop);
|
|
|
|
|
|
} else if (state.options.scrollTo !== false) {
|
2018-01-21 19:04:50 -05:00
|
|
|
|
// Scroll page to top on new page load
|
2018-01-22 11:27:13 -05:00
|
|
|
|
if (state.options.scrollTo.length > 1) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
window.scrollTo(state.options.scrollTo[0], state.options.scrollTo[1]);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
window.scrollTo(0, state.options.scrollTo);
|
2018-01-22 11:27:13 -05:00
|
|
|
|
}
|
2018-01-22 10:55:29 -05:00
|
|
|
|
}
|
2019-02-13 22:26:57 -05:00
|
|
|
|
} else if (state.options.scrollRestoration && state.options.scrollPos) {
|
|
|
|
|
|
window.scrollTo(state.options.scrollPos[0], state.options.scrollPos[1]);
|
2018-01-22 11:27:13 -05:00
|
|
|
|
}
|
2018-01-22 10:55:29 -05:00
|
|
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
|
|
numPendingSwitches: 0,
|
|
|
|
|
|
href: null,
|
|
|
|
|
|
options: null
|
2019-02-13 22:26:57 -05:00
|
|
|
|
};
|
2014-10-14 08:13:50 +02:00
|
|
|
|
}
|
2019-02-13 22:26:57 -05:00
|
|
|
|
};
|
2014-10-14 08:13:50 +02:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
Pjax.isSupported = require("./lib/is-supported");
|
2015-01-16 15:44:57 -08:00
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
// arguably could do `if( require("./lib/is-supported")()) {` but that might be a little to simple
|
2014-10-14 08:13:50 +02:00
|
|
|
|
if (Pjax.isSupported()) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
module.exports = Pjax;
|
2014-10-14 08:13:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
// if there isn’t required browser functions, returning stupid api
|
|
|
|
|
|
else {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
var stupidPjax = noop;
|
2014-10-14 08:13:50 +02:00
|
|
|
|
for (var key in Pjax.prototype) {
|
2019-02-13 22:26:57 -05:00
|
|
|
|
if (
|
|
|
|
|
|
Pjax.prototype.hasOwnProperty(key) &&
|
|
|
|
|
|
typeof Pjax.prototype[key] === "function"
|
|
|
|
|
|
) {
|
|
|
|
|
|
stupidPjax[key] = noop;
|
2014-10-14 08:13:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-02-13 22:26:57 -05:00
|
|
|
|
module.exports = stupidPjax;
|
2014-10-14 08:13:50 +02:00
|
|
|
|
}
|