Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0916c74171 | ||
|
|
0781f820ee | ||
|
|
a7b584c469 | ||
|
|
be5d58d550 | ||
|
|
1e40a0d70b | ||
|
|
75eddfcab6 | ||
|
|
352e7114b6 | ||
|
|
af57adaafb | ||
|
|
09f14fc86c | ||
|
|
b17457f5a2 | ||
|
|
86e5a2281a | ||
|
|
109e78347f | ||
|
|
cb3b6b8a5d | ||
|
|
6dffeba21a | ||
|
|
acdd87ef3d | ||
|
|
917c6f6bcb | ||
|
|
b201b96a37 | ||
|
|
eff7cfd452 | ||
|
|
7976f06043 | ||
|
|
ee530f4c0a | ||
|
|
bf71894395 | ||
|
|
beaa21fb3a | ||
|
|
b96b0f41a7 | ||
|
|
6e113b2d06 | ||
|
|
46912e6797 | ||
|
|
4877bac2ae | ||
|
|
afe0ddb6b9 | ||
|
|
a5d36d28f8 | ||
|
|
7a4056fd77 | ||
|
|
ba6ef126c0 |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,3 +1,19 @@
|
|||||||
|
# 0.2.4 - 2016-06-28
|
||||||
|
|
||||||
|
- Fixed: ``refresh`` should now work (use `this.parseDOM` for refresh)
|
||||||
|
([#67](https://github.com/MoOx/pjax/pull/67) - @compressed)
|
||||||
|
- Fixed: Some attributes, such as `itemscope` have no corresponding value.
|
||||||
|
This change allows them to still be set.
|
||||||
|
([#67](https://github.com/MoOx/pjax/pull/67) - @compressed)
|
||||||
|
- Added: ``cacheBust`` option
|
||||||
|
([#71](https://github.com/MoOx/pjax/pull/71) - @tremby)
|
||||||
|
|
||||||
|
# 0.2.3 - 2016-03-24
|
||||||
|
|
||||||
|
- Fixed: ``currentUrlFullReload`` option now works
|
||||||
|
- Fixed: ``this.reload`` is now a Function
|
||||||
|
([#65](https://github.com/MoOx/pjax/issues/65))
|
||||||
|
|
||||||
# 0.2.2 - 2016-03-12
|
# 0.2.2 - 2016-03-12
|
||||||
|
|
||||||
- Fixed: added back standalone version in `./pjax.js`
|
- Fixed: added back standalone version in `./pjax.js`
|
||||||
|
|||||||
85
README.md
85
README.md
@@ -1,7 +1,6 @@
|
|||||||
# Pjax [](https://travis-ci.org/MoOx/pjax)
|
# Pjax
|
||||||
|
|
||||||
|
[](https://travis-ci.org/MoOx/pjax) [@todo fix CI](https://github.com/MoOx/pjax/issues/63).
|
||||||
[](https://ci.testling.com/MoOx/pjax)
|
|
||||||
|
|
||||||
> Easily enable fast Ajax navigation on any website (using pushState + xhr)
|
> Easily enable fast Ajax navigation on any website (using pushState + xhr)
|
||||||
|
|
||||||
@@ -16,14 +15,26 @@ Especially for user that have low bandwidth connection._
|
|||||||
|
|
||||||
**No more full page reload. No more lots of HTTP request.**
|
**No more full page reload. No more lots of HTTP request.**
|
||||||
|
|
||||||
# Note: current master is WIP.
|
|
||||||
|
|
||||||
Checkout [v0.1.4](https://github.com/MoOx/pjax/tree/a17a6b90bebefd8f5209e6a6f7d8c5d59296232a) for latest "stable" (no tests so kind of funny to call that stable).
|
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
[You can see this running on my website](http://moox.io), with sexy CSS animations when switching pages.
|
[You can see this running on my website](http://moox.io), with sexy CSS animations when switching pages.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
You can install pjax from **npm**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ npm install pjax
|
||||||
|
```
|
||||||
|
|
||||||
|
Pjax can obviously be downloaded directly.
|
||||||
|
|
||||||
|
[https://unpkg.com/pjax/pjax.js](https://unpkg.com/pjax/pjax.js)
|
||||||
|
|
||||||
|
## No dependencies
|
||||||
|
|
||||||
|
_There is nothing you need. No jQuery or something._
|
||||||
|
|
||||||
## How Pjax works
|
## How Pjax works
|
||||||
|
|
||||||
Pjax loads page using ajax & updates the browser's current url using pushState without reloading your page's layout or any resources (js, css), giving a fast page load.
|
Pjax loads page using ajax & updates the browser's current url using pushState without reloading your page's layout or any resources (js, css), giving a fast page load.
|
||||||
@@ -102,26 +113,6 @@ _Magic! For real!_ **There is completely no need to do anything on server side!*
|
|||||||
- Allow page transition with CSS animations,
|
- Allow page transition with CSS animations,
|
||||||
- Can be easily hacked since every method is public (so overridable)
|
- Can be easily hacked since every method is public (so overridable)
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
You can install pjax from **npm**
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ npm install pjax
|
|
||||||
```
|
|
||||||
|
|
||||||
Or using **bower**
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ bower install pjax
|
|
||||||
```
|
|
||||||
|
|
||||||
Pjax can obviously be downloaded directly.
|
|
||||||
|
|
||||||
## No dependencies
|
|
||||||
|
|
||||||
_There is nothing you need. No jQuery or something._
|
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
Pjax only works with [browsers that support the `history.pushState` API](http://caniuse.com/#search=pushstate).
|
Pjax only works with [browsers that support the `history.pushState` API](http://caniuse.com/#search=pushstate).
|
||||||
@@ -277,6 +268,7 @@ new Pjax({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
_Note that remove class include `Animated--reverse` which is a simple way to not have
|
_Note that remove class include `Animated--reverse` which is a simple way to not have
|
||||||
@@ -385,6 +377,12 @@ It's called every time a page is switched, even for history buttons.
|
|||||||
|
|
||||||
Value (in px) to scrollTo when a page is switched.
|
Value (in px) to scrollTo when a page is switched.
|
||||||
|
|
||||||
|
##### `cacheBust` (Boolean, default true)
|
||||||
|
|
||||||
|
When set to true,
|
||||||
|
append a timestamp query string segment to the requested URLs
|
||||||
|
in order to skip browser cache.
|
||||||
|
|
||||||
##### `debug` (Boolean, default to false)
|
##### `debug` (Boolean, default to false)
|
||||||
|
|
||||||
Enable verbose mode & doesn't use fallback when there is an error.
|
Enable verbose mode & doesn't use fallback when there is an error.
|
||||||
@@ -394,25 +392,6 @@ Useful to debug page layout differences.
|
|||||||
|
|
||||||
When set to true, clicking on a link that point the current url trigger a full page reload.
|
When set to true, clicking on a link that point the current url trigger a full page reload.
|
||||||
|
|
||||||
#### Extend Pjax
|
|
||||||
|
|
||||||
Pjax prototype & utilities methods can be used & changed so you can patch or hack
|
|
||||||
Pjax behavior, as you wish.
|
|
||||||
|
|
||||||
Here is a summary of functions:
|
|
||||||
|
|
||||||
- `Pjax.prototype.log` (`function()`): console.log function that is enable/disabled by `debug` option
|
|
||||||
- `Pjax.prototype.getElements` (`function(el)`): retrieve elements to attach Pjax behavior
|
|
||||||
- `Pjax.prototype.parseDOM` (`function(el)`): parse DOM to attach behavior using `Pjax.prototype.getElements` & `Pjax.prototype.attachLink`
|
|
||||||
- `Pjax.prototype.attachLink` (`function(el)`): attach Pjax behavior to a link
|
|
||||||
- `Pjax.prototype.forEachSelectors` (`function(cb, context, DOMcontext)`): call a function for each selectors defined
|
|
||||||
- `Pjax.prototype.switchSelectors` (`function(selectors, fromEl, toEl, options)`): loop on selectors to switch elements
|
|
||||||
- `Pjax.prototype.latestChance` (`function(href)`): when everything is fucked up, it's our only hope (just call `window.location = href`)
|
|
||||||
- `Pjax.prototype.onSwitch` (`function()`): callback triggered when elements are switched, for now it's just trigger a window resize event (lots of lib are listening to this event to draw stuff)
|
|
||||||
- `Pjax.prototype.loadContent` (`function(html, options)`): switch elements for each selectors
|
|
||||||
- `Pjax.prototype.doRequest` (`function(location, callback)`): make the ajax request to grab page from the server
|
|
||||||
- `Pjax.prototype.loadUrl` (`function(href, options)`): do the ajax request, handle html results & eventually handle browser history, analytics & scroll.
|
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
Pjax fires a number of events regardless of how its invoked.
|
Pjax fires a number of events regardless of how its invoked.
|
||||||
@@ -543,15 +522,13 @@ Clone this repository and run `npm run example`, then open `http://localhost:300
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Contributing
|
## CONTRIBUTING
|
||||||
|
|
||||||
Work on a branch, install dev-dependencies, respect coding style & run tests before submitting a bug fix or a feature.
|
* ⇄ Pull requests and ★ Stars are always welcome.
|
||||||
|
* For bugs and feature requests, please create an issue.
|
||||||
|
* Pull requests must be accompanied by passing automated tests (`$ npm test`).
|
||||||
|
|
||||||
$ git clone https://github.com/MoOx/pjax.git
|
## [CHANGELOG](CHANGELOG.md)
|
||||||
$ git checkout -b patch-1
|
|
||||||
$ npm install
|
|
||||||
$ npm test
|
|
||||||
|
|
||||||
## [Changelog](CHANGELOG.md)
|
## [LICENSE](LICENSE)
|
||||||
|
|
||||||
## [License](LICENSE)
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pjax",
|
"name": "pjax",
|
||||||
"version": "0.2.2",
|
"version": "0.2.4",
|
||||||
"description": "Easily enable fast Ajax navigation on any website (using pushState + xhr)",
|
"description": "Easily enable fast Ajax navigation on any website (using pushState + xhr)",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"pjax",
|
"pjax",
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ document.addEventListener("pjax:success", function() {
|
|||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
var pjax = new Pjax({
|
var pjax = new Pjax({
|
||||||
|
elements: [".js-Pjax"],
|
||||||
selectors: [".body"]
|
selectors: [".body"]
|
||||||
|
// currentUrlFullReload: true,
|
||||||
})
|
})
|
||||||
console.log("Pjax initialized.", pjax)
|
console.log("Pjax initialized.", pjax)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class='body'>
|
<div class='body'>
|
||||||
<h1>Index</h1>
|
<h1>Index</h1>
|
||||||
hello. Go to <a href='page2.html'>Page 2</a> and view your console to see Pjax events.
|
Hello.
|
||||||
|
Go to <a href='page2.html' class="js-Pjax">Page 2</a> and view your console to see Pjax events.
|
||||||
|
Clicking on <a href='index.html'>this page</a> will just reload the page entierly.
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class='body'>
|
<div class='body'>
|
||||||
<h1>Page 2</h1>
|
<h1>Page 2</h1>
|
||||||
Hello. Go to <a href='index.html'>Index</a>.
|
Hello. Go to <a href='index.html' class="js-Pjax">Index</a>.
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
20
index.js
20
index.js
@@ -9,6 +9,8 @@ var on = require("./lib/events/on.js")
|
|||||||
// var off = require("./lib/events/on.js")
|
// var off = require("./lib/events/on.js")
|
||||||
var trigger = require("./lib/events/trigger.js")
|
var trigger = require("./lib/events/trigger.js")
|
||||||
|
|
||||||
|
var defaultSwitches = require("./lib/switches")
|
||||||
|
|
||||||
|
|
||||||
var Pjax = function(options) {
|
var Pjax = function(options) {
|
||||||
this.firstrun = true
|
this.firstrun = true
|
||||||
@@ -27,7 +29,7 @@ var Pjax = function(options) {
|
|||||||
opt.url = st.state.url
|
opt.url = st.state.url
|
||||||
opt.title = st.state.title
|
opt.title = st.state.title
|
||||||
opt.history = false
|
opt.history = false
|
||||||
|
opt.requestOptions = {};
|
||||||
if (st.state.uid < this.lastUid) {
|
if (st.state.uid < this.lastUid) {
|
||||||
opt.backward = true
|
opt.backward = true
|
||||||
}
|
}
|
||||||
@@ -42,6 +44,8 @@ var Pjax = function(options) {
|
|||||||
}.bind(this))
|
}.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Pjax.switches = defaultSwitches
|
||||||
|
|
||||||
Pjax.prototype = {
|
Pjax.prototype = {
|
||||||
log: require("./lib/proto/log.js"),
|
log: require("./lib/proto/log.js"),
|
||||||
|
|
||||||
@@ -51,8 +55,12 @@ Pjax.prototype = {
|
|||||||
|
|
||||||
refresh: require("./lib/proto/refresh.js"),
|
refresh: require("./lib/proto/refresh.js"),
|
||||||
|
|
||||||
|
reload: require("./lib/reload.js"),
|
||||||
|
|
||||||
attachLink: require("./lib/proto/attach-link.js"),
|
attachLink: require("./lib/proto/attach-link.js"),
|
||||||
|
|
||||||
|
attachForm: require("./lib/proto/attach-form.js"),
|
||||||
|
|
||||||
forEachSelectors: function(cb, context, DOMcontext) {
|
forEachSelectors: function(cb, context, DOMcontext) {
|
||||||
return require("./lib/foreach-selectors.js").bind(this)(this.options.selectors, cb, context, DOMcontext)
|
return require("./lib/foreach-selectors.js").bind(this)(this.options.selectors, cb, context, DOMcontext)
|
||||||
},
|
},
|
||||||
@@ -75,11 +83,12 @@ Pjax.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onSwitch: function() {
|
onSwitch: function() {
|
||||||
|
this.parseDOM(document)
|
||||||
trigger(window, "resize scroll")
|
trigger(window, "resize scroll")
|
||||||
},
|
},
|
||||||
|
|
||||||
loadContent: function(html, options) {
|
loadContent: function(html, options) {
|
||||||
var tmpEl = document.implementation.createHTMLDocument()
|
var tmpEl = document.implementation.createHTMLDocument("pjax")
|
||||||
|
|
||||||
// parse HTML attributes to copy them
|
// parse HTML attributes to copy them
|
||||||
// since we are forced to use documentElement.innerHTML (outerHTML can't be used for <html>)
|
// since we are forced to use documentElement.innerHTML (outerHTML can't be used for <html>)
|
||||||
@@ -92,7 +101,12 @@ Pjax.prototype = {
|
|||||||
matches.shift()
|
matches.shift()
|
||||||
matches.forEach(function(htmlAttrib) {
|
matches.forEach(function(htmlAttrib) {
|
||||||
var attr = htmlAttrib.trim().split("=")
|
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.setAttribute(attr[0], attr[1].slice(1, -1))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,7 +158,7 @@ Pjax.prototype = {
|
|||||||
trigger(document, "pjax:send", options);
|
trigger(document, "pjax:send", options);
|
||||||
|
|
||||||
// Do the request
|
// Do the request
|
||||||
this.doRequest(href, function(html) {
|
this.doRequest(href, options.requestOptions, function(html) {
|
||||||
// Fail if unable to load HTML via AJAX
|
// Fail if unable to load HTML via AJAX
|
||||||
if (html === false) {
|
if (html === false) {
|
||||||
trigger(document,"pjax:complete pjax:error", options)
|
trigger(document,"pjax:complete pjax:error", options)
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ module.exports = function(el) {
|
|||||||
// console.log("going to execute script", el)
|
// console.log("going to execute script", el)
|
||||||
|
|
||||||
var code = (el.text || el.textContent || el.innerHTML || "")
|
var code = (el.text || el.textContent || el.innerHTML || "")
|
||||||
var head = document.querySelector("head") || document.documentElement
|
var src = (el.src || "");
|
||||||
|
var parent = el.parentNode || document.querySelector("head") || document.documentElement
|
||||||
var script = document.createElement("script")
|
var script = document.createElement("script")
|
||||||
|
|
||||||
if (code.match("document.write")) {
|
if (code.match("document.write")) {
|
||||||
@@ -13,6 +14,14 @@ module.exports = function(el) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
script.type = "text/javascript"
|
script.type = "text/javascript"
|
||||||
|
|
||||||
|
if (src != "") {
|
||||||
|
script.src = src;
|
||||||
|
script.onload = function() { document.dispatchEvent((new Event("pjax:complete"))); }
|
||||||
|
script.async = false; // force asynchronous loading of peripheral js
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code != "") {
|
||||||
try {
|
try {
|
||||||
script.appendChild(document.createTextNode(code))
|
script.appendChild(document.createTextNode(code))
|
||||||
}
|
}
|
||||||
@@ -20,10 +29,14 @@ module.exports = function(el) {
|
|||||||
// old IEs have funky script nodes
|
// old IEs have funky script nodes
|
||||||
script.text = code
|
script.text = code
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// execute
|
// execute
|
||||||
head.insertBefore(script, head.firstChild)
|
parent.appendChild(script);
|
||||||
head.removeChild(script) // avoid pollution
|
// avoid pollution only in head or body tags
|
||||||
|
if (["head","body"].indexOf(parent.tagName.toLowerCase()) > 0) {
|
||||||
return true
|
parent.removeChild(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,17 @@ var evalScript = require("./eval-script")
|
|||||||
// Needed since innerHTML does not run scripts
|
// Needed since innerHTML does not run scripts
|
||||||
module.exports = function(el) {
|
module.exports = function(el) {
|
||||||
// console.log("going to execute scripts for ", el)
|
// console.log("going to execute scripts for ", el)
|
||||||
|
|
||||||
|
if (el.tagName.toLowerCase() === "script") {
|
||||||
|
evalScript(el);
|
||||||
|
}
|
||||||
|
|
||||||
forEachEls(el.querySelectorAll("script"), function(script) {
|
forEachEls(el.querySelectorAll("script"), function(script) {
|
||||||
if (!script.type || script.type.toLowerCase() === "text/javascript") {
|
if (!script.type || script.type.toLowerCase() === "text/javascript") {
|
||||||
if (script.parentNode) {
|
if (script.parentNode) {
|
||||||
script.parentNode.removeChild(script)
|
script.parentNode.removeChild(script)
|
||||||
}
|
}
|
||||||
evalScript(script)
|
evalScript(script);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
93
lib/proto/attach-form.js
Normal file
93
lib/proto/attach-form.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
require("../polyfills/Function.prototype.bind")
|
||||||
|
|
||||||
|
var on = require("../events/on")
|
||||||
|
var clone = require("../clone")
|
||||||
|
|
||||||
|
var attrClick = "data-pjax-click-state"
|
||||||
|
|
||||||
|
var formAction = function(el, event){
|
||||||
|
|
||||||
|
this.options.requestOptions = {
|
||||||
|
requestUrl : el.getAttribute('action') || window.location.href,
|
||||||
|
requestMethod : el.getAttribute('method') || 'GET',
|
||||||
|
}
|
||||||
|
|
||||||
|
//create a testable virtual link of the form action
|
||||||
|
var virtLinkElement = document.createElement('a');
|
||||||
|
virtLinkElement.setAttribute('href', this.options.requestOptions.requestUrl);
|
||||||
|
|
||||||
|
// Ignore external links.
|
||||||
|
if (virtLinkElement.protocol !== window.location.protocol || virtLinkElement.host !== window.location.host) {
|
||||||
|
el.setAttribute(attrClick, "external");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore click if we are on an anchor on the same page
|
||||||
|
if (virtLinkElement.pathname === window.location.pathname && virtLinkElement.hash.length > 0) {
|
||||||
|
el.setAttribute(attrClick, "anchor-present");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore empty anchor "foo.html#"
|
||||||
|
if (virtLinkElement.href === window.location.href.split("#")[0] + "#") {
|
||||||
|
el.setAttribute(attrClick, "anchor-empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if declared as a full reload, just normally submit the form
|
||||||
|
if ( this.options.currentUrlFullReload) {
|
||||||
|
el.setAttribute(attrClick, "reload");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
var paramObject = [];
|
||||||
|
for(var elementKey in el.elements) {
|
||||||
|
var element = el.elements[elementKey];
|
||||||
|
if (!!element.name && element.attributes !== undefined && element.tagName.toLowerCase() !== 'button'){
|
||||||
|
if ((element.attributes.type !== 'checkbox' && element.attributes.type !== 'radio') || element.checked) {
|
||||||
|
paramObject.push({ name: encodeURIComponent(element.name), value: encodeURIComponent(element.value)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Creating a getString
|
||||||
|
var paramsString = (paramObject.map(function(value){return value.name+"="+value.value;})).join('&');
|
||||||
|
|
||||||
|
this.options.requestOptions.requestPayload = paramObject;
|
||||||
|
this.options.requestOptions.requestPayloadString = paramsString;
|
||||||
|
|
||||||
|
el.setAttribute(attrClick, "submit");
|
||||||
|
|
||||||
|
this.loadUrl(virtLinkElement.href, clone(this.options))
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
var isDefaultPrevented = function(event) {
|
||||||
|
return event.defaultPrevented || event.returnValue === false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = function(el) {
|
||||||
|
var that = this
|
||||||
|
|
||||||
|
on(el, "submit", function(event) {
|
||||||
|
if (isDefaultPrevented(event)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
formAction.call(that, el, event)
|
||||||
|
})
|
||||||
|
|
||||||
|
on(el, "keyup", function(event) {
|
||||||
|
if (isDefaultPrevented(event)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (event.keyCode == 13) {
|
||||||
|
formAction.call(that, el, event)
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
|
}
|
||||||
@@ -43,12 +43,15 @@ var linkAction = function(el, event) {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
// don’t do "nothing" if user try to reload the page by clicking the same link twice
|
// don’t do "nothing" if user try to reload the page by clicking the same link twice
|
||||||
if (el.href === window.location.href.split("#")[0]) {
|
if (
|
||||||
|
this.options.currentUrlFullReload &&
|
||||||
|
el.href === window.location.href.split("#")[0]
|
||||||
|
) {
|
||||||
el.setAttribute(attrClick, "reload")
|
el.setAttribute(attrClick, "reload")
|
||||||
this.reload()
|
this.reload()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
this.options.requestOptions = this.options.requestOptions || {};
|
||||||
el.setAttribute(attrClick, "load")
|
el.setAttribute(attrClick, "load")
|
||||||
this.loadUrl(el.href, clone(this.options))
|
this.loadUrl(el.href, clone(this.options))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ module.exports = function(el) {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case "form":
|
case "form":
|
||||||
throw "Pjax doesnt support <form> yet."
|
// only attach link if el does not already have link attached
|
||||||
|
if (!el.hasAttribute('data-pjax-click-state')) {
|
||||||
|
this.attachForm(el)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
/* global _gaq: true, ga: true */
|
/* global _gaq: true, ga: true */
|
||||||
|
|
||||||
|
var defaultSwitches = require("../switches")
|
||||||
|
|
||||||
module.exports = function(options){
|
module.exports = function(options){
|
||||||
this.options = options
|
this.options = options
|
||||||
this.options.elements = this.options.elements || "a[href], form[action]"
|
this.options.elements = this.options.elements || "a[href], form[action]"
|
||||||
@@ -19,6 +21,7 @@ module.exports = function(options){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.options.scrollTo = (typeof this.options.scrollTo === 'undefined') ? 0 : this.options.scrollTo;
|
this.options.scrollTo = (typeof this.options.scrollTo === 'undefined') ? 0 : this.options.scrollTo;
|
||||||
|
this.options.cacheBust = (typeof this.options.cacheBust === 'undefined') ? true : this.options.cacheBust
|
||||||
this.options.debug = this.options.debug || false
|
this.options.debug = this.options.debug || false
|
||||||
|
|
||||||
// we can’t replace body.outerHTML or head.outerHTML
|
// we can’t replace body.outerHTML or head.outerHTML
|
||||||
@@ -26,10 +29,10 @@ module.exports = function(options){
|
|||||||
// if you set head.outerHTML, a new body tag is appended, so the dom get 2 body
|
// 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
|
// & it break the switchFallback which replace head & body
|
||||||
if (!this.options.switches.head) {
|
if (!this.options.switches.head) {
|
||||||
this.options.switches.head = this.switchElementsAlt
|
this.options.switches.head = defaultSwitches.switchElementsAlt
|
||||||
}
|
}
|
||||||
if (!this.options.switches.body) {
|
if (!this.options.switches.body) {
|
||||||
this.options.switches.body = this.switchElementsAlt
|
this.options.switches.body = defaultSwitches.switchElementsAlt
|
||||||
}
|
}
|
||||||
if (typeof options.analytics !== "function") {
|
if (typeof options.analytics !== "function") {
|
||||||
options.analytics = function() {}
|
options.analytics = function() {}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
|
|
||||||
var parseDom = require("./parse-dom")
|
|
||||||
|
|
||||||
module.exports = function(el) {
|
module.exports = function(el) {
|
||||||
parseDom(el || document)
|
this.parseDOM(el || document)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
module.exports = function(location, callback) {
|
module.exports = function(location, options, callback) {
|
||||||
|
options = options || {};
|
||||||
|
var requestMethod = options.requestMethod || "GET";
|
||||||
|
var requestPayload = options.requestPayloadString || null;
|
||||||
var request = new XMLHttpRequest()
|
var request = new XMLHttpRequest()
|
||||||
|
|
||||||
request.onreadystatechange = function() {
|
request.onreadystatechange = function() {
|
||||||
@@ -12,8 +15,22 @@ module.exports = function(location, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request.open("GET", location + (!/[?&]/.test(location) ? "?" : "&") + (new Date().getTime()), true)
|
// Add a timestamp as part of the query string if cache busting is enabled
|
||||||
|
if (this.options.cacheBust) {
|
||||||
|
location += (!/[?&]/.test(location) ? "?" : "&") + new Date().getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
request.open(requestMethod.toUpperCase(), location, true)
|
||||||
request.setRequestHeader("X-Requested-With", "XMLHttpRequest")
|
request.setRequestHeader("X-Requested-With", "XMLHttpRequest")
|
||||||
request.send(null)
|
request.setRequestHeader("X-PJAX", "true")
|
||||||
|
|
||||||
|
// Add the request payload if available
|
||||||
|
if (options.requestPayloadString != undefined && options.requestPayloadString != "") {
|
||||||
|
// Send the proper header information along with the request
|
||||||
|
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||||
|
}
|
||||||
|
|
||||||
|
request.send(requestPayload)
|
||||||
|
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,20 @@ module.exports = {
|
|||||||
this.onSwitch()
|
this.onSwitch()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
switchElementsAlt: function(oldEl, newEl) {
|
||||||
|
oldEl.innerHTML = newEl.innerHTML
|
||||||
|
|
||||||
|
// Copy attributes from the new element to the old one
|
||||||
|
if (newEl.hasAttributes()) {
|
||||||
|
const attrs = newEl.attributes;
|
||||||
|
for (var i = 0; i < attrs.length; i++) {
|
||||||
|
oldEl.attributes.setNamedItem(attrs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onSwitch()
|
||||||
|
},
|
||||||
|
|
||||||
sideBySide: function(oldEl, newEl, options, switchOptions) {
|
sideBySide: function(oldEl, newEl, options, switchOptions) {
|
||||||
var forEach = Array.prototype.forEach
|
var forEach = Array.prototype.forEach
|
||||||
var elsToRemove = []
|
var elsToRemove = []
|
||||||
|
|||||||
11
package.json
11
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pjax",
|
"name": "pjax",
|
||||||
"version": "0.2.2",
|
"version": "0.2.4",
|
||||||
"description": "Easily enable fast Ajax navigation on any website (using pushState + xhr)",
|
"description": "Easily enable fast Ajax navigation on any website (using pushState + xhr)",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"pjax",
|
"pjax",
|
||||||
@@ -25,6 +25,8 @@
|
|||||||
"coverify": "^1.0.6",
|
"coverify": "^1.0.6",
|
||||||
"jscs": "^1.6.2",
|
"jscs": "^1.6.2",
|
||||||
"jshint": "^2.5.6",
|
"jshint": "^2.5.6",
|
||||||
|
"npmpub": "^3.1.0",
|
||||||
|
"opn-cli": "^3.1.0",
|
||||||
"serve": "1.4.0",
|
"serve": "1.4.0",
|
||||||
"tape": "^3.0.0",
|
"tape": "^3.0.0",
|
||||||
"testling": "^1.6.1"
|
"testling": "^1.6.1"
|
||||||
@@ -32,12 +34,15 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "jscs **/*.js && jshint . --exclude-path .gitignore",
|
"lint": "jscs **/*.js && jshint . --exclude-path .gitignore",
|
||||||
"standalone": "browserify index.js --standalone Pjax > pjax.js",
|
"standalone": "browserify index.js --standalone Pjax > pjax.js",
|
||||||
|
"build-debug": "browserify index.js --debug --standalone Pjax > pjax.js",
|
||||||
"tests": "testling",
|
"tests": "testling",
|
||||||
"test": "npm run lint && npm run standalone && npm run tests",
|
"test": "npm run lint && npm run standalone && npm run tests",
|
||||||
"test--html": "testling --html > tests/scripts/index.html",
|
"test--html": "testling --html > tests/scripts/index.html",
|
||||||
"coverage": "browserify -t coverify tests/**/*.js | testling | coverify",
|
"coverage": "browserify -t coverify tests/**/*.js | testling | coverify",
|
||||||
"example": "echo '\n==> Open http://localhost:3000/example in your browser.'; serve .",
|
"example": "opn http://localhost:3000/example/; serve .",
|
||||||
"prepublish": "npm run standalone"
|
"prepublish": "npm run standalone",
|
||||||
|
"#release": "testling does not work in a process launch by npm... :facepalm:",
|
||||||
|
"release": "echo \"npmpub --skip-test --dry && npm test && npmpub --skip-test --skip-cleanup\""
|
||||||
},
|
},
|
||||||
"testling": {
|
"testling": {
|
||||||
"files": "tests/**/*.js",
|
"files": "tests/**/*.js",
|
||||||
|
|||||||
78
tests/lib/proto/attach-form.js
Normal file
78
tests/lib/proto/attach-form.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
var tape = require("tape")
|
||||||
|
|
||||||
|
var on = require("../../../lib/events/on")
|
||||||
|
var trigger = require("../../../lib/events/trigger")
|
||||||
|
var attachForm = require("../../../lib/proto/attach-form")
|
||||||
|
|
||||||
|
var form = document.createElement("form")
|
||||||
|
var attr = "data-pjax-click-state"
|
||||||
|
var preventDefault = function(e) { e.preventDefault() }
|
||||||
|
|
||||||
|
tape("test attach form prototype method", function(t) {
|
||||||
|
t.plan(7)
|
||||||
|
|
||||||
|
attachForm.call({
|
||||||
|
options: {},
|
||||||
|
reload: function() {
|
||||||
|
t.equal(form.getAttribute(attr), "reload", "triggering a simple reload will just submit the form")
|
||||||
|
},
|
||||||
|
loadUrl: function() {
|
||||||
|
t.equal(form.getAttribute(attr), "submit", "triggering a post to the next page")
|
||||||
|
}
|
||||||
|
}, form)
|
||||||
|
|
||||||
|
var internalUri = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search
|
||||||
|
|
||||||
|
form.action = "http://external.com/"
|
||||||
|
trigger(form, "submit")
|
||||||
|
t.equal(form.getAttribute(attr), "external", "external url stop behavior")
|
||||||
|
|
||||||
|
form.action = internalUri + "#anchor"
|
||||||
|
trigger(form, "submit")
|
||||||
|
t.equal(form.getAttribute(attr), "anchor-present", "internal anchor stop behavior")
|
||||||
|
|
||||||
|
window.location.hash = "#anchor"
|
||||||
|
form.action = internalUri + "#another-anchor"
|
||||||
|
trigger(form, "submit")
|
||||||
|
t.notEqual(form.getAttribute(attr), "anchor", "differents anchors stop behavior")
|
||||||
|
window.location.hash = ""
|
||||||
|
|
||||||
|
form.action = internalUri + "#"
|
||||||
|
trigger(form, "submit")
|
||||||
|
t.equal(form.getAttribute(attr), "anchor-empty", "empty anchor stop behavior")
|
||||||
|
|
||||||
|
form.action = internalUri
|
||||||
|
trigger(form, "submit")
|
||||||
|
// see reload defined above
|
||||||
|
|
||||||
|
form.action = window.location.protocol + "//" + window.location.host + "/internal"
|
||||||
|
form.method = 'POST'
|
||||||
|
trigger(form, "submit")
|
||||||
|
// see post defined above
|
||||||
|
|
||||||
|
form.action = window.location.protocol + "//" + window.location.host + "/internal"
|
||||||
|
form.method = 'GET'
|
||||||
|
trigger(form, "submit")
|
||||||
|
// see post defined above
|
||||||
|
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
tape("test attach form preventDefaulted events", function(t) {
|
||||||
|
var callbacked = false
|
||||||
|
var form = document.createElement("form")
|
||||||
|
|
||||||
|
attachForm.call({
|
||||||
|
options: {},
|
||||||
|
loadUrl: function() {
|
||||||
|
callbacked = true
|
||||||
|
}
|
||||||
|
}, form)
|
||||||
|
|
||||||
|
form.action = "#"
|
||||||
|
on(form, "submit", preventDefault)
|
||||||
|
trigger(form, "submit")
|
||||||
|
t.equal(callbacked, false, "events that are preventDefaulted should not fire callback")
|
||||||
|
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
@@ -1,17 +1,21 @@
|
|||||||
var tape = require("tape")
|
var tape = require("tape")
|
||||||
|
|
||||||
var parseElement = require("../../../lib/proto/parse-element")
|
var parseElement = require("../../../lib/proto/parse-element")
|
||||||
var protoMock = {attachLink: function() { return true}}
|
var protoMock = {
|
||||||
|
attachLink: function() { return true },
|
||||||
|
attachForm: function() { return true }
|
||||||
|
}
|
||||||
|
|
||||||
tape("test parse element prototype method", function(t) {
|
tape("test parse element prototype method", function(t) {
|
||||||
t.doesNotThrow(function() {
|
t.doesNotThrow(function() {
|
||||||
var a = document.createElement("a")
|
var a = document.createElement("a")
|
||||||
parseElement.call(protoMock, a)
|
parseElement.call(protoMock, a)
|
||||||
}, "<a> element can be parsed")
|
}, "<a> element can be parsed")
|
||||||
|
|
||||||
t.throws(function() {
|
t.doesNotThrow(function() {
|
||||||
var form = document.createElement("form")
|
var form = document.createElement("form")
|
||||||
parseElement.call(protoMock, form)
|
parseElement.call(protoMock, form)
|
||||||
}, "<form> cannot be used (for now)")
|
}, "<form> element can be parsed")
|
||||||
|
|
||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ tape("test parse initalization options function", function(t) {
|
|||||||
t.deepEqual(typeof body_1.options.analytics,"function");
|
t.deepEqual(typeof body_1.options.analytics,"function");
|
||||||
|
|
||||||
t.deepEqual(body_1.options.scrollTo,0);
|
t.deepEqual(body_1.options.scrollTo,0);
|
||||||
|
t.deepEqual(body_1.options.cacheBust,true);
|
||||||
t.deepEqual(body_1.options.debug,false);
|
t.deepEqual(body_1.options.debug,false);
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,8 +2,27 @@ var tape = require("tape")
|
|||||||
|
|
||||||
var request = require("../../lib/request.js")
|
var request = require("../../lib/request.js")
|
||||||
|
|
||||||
|
// Polyfill responseURL property into XMLHttpRequest if it doesn't exist,
|
||||||
|
// just for the purposes of this test
|
||||||
|
// This polyfill is not complete; it won't show the updated location if a
|
||||||
|
// redirection occurred, but it's fine for our purposes.
|
||||||
|
if (!('responseURL' in XMLHttpRequest.prototype)) {
|
||||||
|
var nativeOpen = XMLHttpRequest.prototype.open
|
||||||
|
XMLHttpRequest.prototype.open = function (method, url) {
|
||||||
|
this.responseURL = url
|
||||||
|
return nativeOpen.apply(this, arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tape("test xhr request", function(t) {
|
tape("test xhr request", function(t) {
|
||||||
request("https://api.github.com/", function(result) {
|
t.test("- request is made, gets a result, and is cache-busted", function(t) {
|
||||||
|
var requestCacheBust = request.bind({
|
||||||
|
options: {
|
||||||
|
cacheBust: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
var r = requestCacheBust("https://api.github.com/", {}, function(result) {
|
||||||
|
t.equal(r.responseURL.indexOf("?"), 23, "XHR URL is cache-busted when configured to be")
|
||||||
try {
|
try {
|
||||||
result = JSON.parse(result)
|
result = JSON.parse(result)
|
||||||
}
|
}
|
||||||
@@ -14,3 +33,16 @@ tape("test xhr request", function(t) {
|
|||||||
t.end()
|
t.end()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
t.test("- request is not cache-busted when configured not to be", function(t) {
|
||||||
|
var requestNoCacheBust = request.bind({
|
||||||
|
options: {
|
||||||
|
cacheBust: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
var r = requestNoCacheBust("https://api.github.com/", {}, function() {
|
||||||
|
t.equal(r.responseURL, "https://api.github.com/", "XHR URL is left untouched")
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.end()
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user