Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d210c3305 | ||
|
|
a72880d205 | ||
|
|
57aed828ac | ||
|
|
63bc2af226 | ||
|
|
20e987929e | ||
|
|
90d26d641c | ||
|
|
93c0a5bb9b | ||
|
|
8718da35c6 | ||
|
|
31e1e7e405 | ||
|
|
05163d9fb5 | ||
|
|
a15c94c55a | ||
|
|
2d4df39f72 | ||
|
|
fa05e94f07 | ||
|
|
e3df2b1c3d | ||
|
|
01fb72ceeb | ||
|
|
f642eec047 | ||
|
|
cc384b9b16 | ||
|
|
f559ca2914 | ||
|
|
137322543c | ||
|
|
f7f68b2e50 | ||
|
|
ff62289683 | ||
|
|
3205596f3e | ||
|
|
3c8cd087c5 | ||
|
|
12f3be21fb | ||
|
|
b74fbc4178 | ||
|
|
8fcef04c9e | ||
|
|
feb85382f2 | ||
|
|
f196604d73 | ||
|
|
526a0883a2 | ||
|
|
486ef0e0ba | ||
|
|
6000ad5620 | ||
|
|
9845244948 | ||
|
|
e7935d9c74 | ||
|
|
e4b3952589 | ||
|
|
546b9abba3 | ||
|
|
bc2432b18c | ||
|
|
37d303ed66 | ||
|
|
b5c2120d08 | ||
|
|
ca61c4a840 | ||
|
|
cd09cc88d1 | ||
|
|
b98e3ef914 | ||
|
|
36ed7079b1 | ||
|
|
6fa51e58f8 | ||
|
|
cb9c37fcb3 | ||
|
|
92d5e09494 | ||
|
|
3bd101bb1d | ||
|
|
c0d64e41b8 | ||
|
|
e586440964 | ||
|
|
a2e6cfc0af | ||
|
|
6491e32437 | ||
|
|
d3d5ef7a11 | ||
|
|
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 | ||
|
|
01536bfbf5 | ||
|
|
722ddf2a30 | ||
|
|
34fc00c89d | ||
|
|
c02193c61b | ||
|
|
824b229158 | ||
|
|
e3d0f8cc1b | ||
|
|
8ea8ffad07 | ||
|
|
c12e4cdedd | ||
|
|
546b7e309a | ||
|
|
2f4bd760a5 | ||
|
|
477d967804 | ||
|
|
e882b8639a | ||
|
|
3d25bee131 | ||
|
|
47059bdb04 | ||
|
|
791400ed20 | ||
|
|
97c8b2d749 | ||
|
|
b156a4f389 | ||
|
|
1d292a1a6e | ||
|
|
aaa2631eb7 | ||
|
|
460dea8a9e | ||
|
|
b908621842 | ||
|
|
b20ee2261e | ||
|
|
ad6292fffb | ||
|
|
4ed4577539 | ||
|
|
6eafcf8dc6 | ||
|
|
b244a8cac4 | ||
|
|
a601e301cd | ||
|
|
c16996d2b2 | ||
|
|
af8783587c | ||
|
|
14f997c8d1 | ||
|
|
9a86044f90 | ||
|
|
e6a35f38e4 | ||
|
|
3d50ae9131 | ||
|
|
949d1be1a0 | ||
|
|
a119033870 | ||
|
|
482ba2c117 | ||
|
|
7e81f791a9 | ||
|
|
d4ba34e5ed | ||
|
|
ea7d1d2fce | ||
|
|
1d087e8b00 | ||
|
|
3bc67f245c | ||
|
|
332cd4e876 | ||
|
|
db02366324 | ||
|
|
b5bf998fc6 | ||
|
|
74e224c018 | ||
|
|
165532d43c | ||
|
|
414650113b | ||
|
|
a6c9b57647 | ||
|
|
f4e5ec254a | ||
|
|
f1976c3a10 | ||
|
|
799dd51597 | ||
|
|
76026cf8d9 | ||
|
|
b6702a5ea0 | ||
|
|
673c483ccb | ||
|
|
d1536eb228 | ||
|
|
681aa74e1d | ||
|
|
3f3fa761e1 | ||
|
|
0d6643cf7a | ||
|
|
4a4fb6fbc6 | ||
|
|
d5395c3d07 | ||
|
|
8c05692004 | ||
|
|
13b3485111 | ||
|
|
b1de220555 | ||
|
|
b4f7aceab6 | ||
|
|
1cc1aa9d04 | ||
|
|
52d7971dc5 | ||
|
|
9ac709b5d8 | ||
|
|
fa27e05606 | ||
|
|
106b14b851 | ||
|
|
2990f93a20 | ||
|
|
5644893c59 | ||
|
|
7631cc92c7 | ||
|
|
25a725c858 | ||
|
|
f93efb0c00 | ||
|
|
cac43b7c59 | ||
|
|
dd590bae28 | ||
|
|
48376db0ef |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1 +1,6 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
tests/scripts/index.html
|
||||
pjax.js
|
||||
.nyc_output/
|
||||
pjax.min.js
|
||||
|
||||
112
.jscs.json
112
.jscs.json
@@ -1,112 +0,0 @@
|
||||
{
|
||||
"excludeFiles": [
|
||||
]
|
||||
, "requireCurlyBraces": [
|
||||
"if"
|
||||
, "else"
|
||||
, "for"
|
||||
, "while"
|
||||
, "do"
|
||||
, "try"
|
||||
, "catch"
|
||||
]
|
||||
, "requireSpaceAfterKeywords": [
|
||||
"if"
|
||||
, "else"
|
||||
, "for"
|
||||
, "while"
|
||||
, "do"
|
||||
, "switch"
|
||||
, "return"
|
||||
, "try"
|
||||
, "catch"
|
||||
]
|
||||
, "requireSpacesInFunctionExpression": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
}
|
||||
, "disallowSpacesInFunctionExpression": {
|
||||
"beforeOpeningRoundBrace": true
|
||||
}
|
||||
, "disallowEmptyBlocks": true
|
||||
, "disallowSpacesInsideObjectBrackets": true
|
||||
, "disallowSpacesInsideArrayBrackets": true
|
||||
, "disallowSpacesInsideParentheses": true
|
||||
, "disallowSpaceAfterObjectKeys": true
|
||||
, "disallowCommaBeforeLineBreak": true
|
||||
, "requireOperatorBeforeLineBreak": [
|
||||
"?"
|
||||
, "+"
|
||||
, "-"
|
||||
, "/"
|
||||
, "*"
|
||||
, "="
|
||||
, "=="
|
||||
, "==="
|
||||
, "!="
|
||||
, "!=="
|
||||
, ">"
|
||||
, ">="
|
||||
, "<"
|
||||
, "<="
|
||||
]
|
||||
, "disallowSpaceAfterPrefixUnaryOperators": [
|
||||
"++"
|
||||
, "--"
|
||||
, "+"
|
||||
, "-"
|
||||
, "~"
|
||||
, "!"
|
||||
]
|
||||
, "disallowSpaceBeforePostfixUnaryOperators": [
|
||||
"++"
|
||||
, "--"
|
||||
]
|
||||
, "requireSpaceBeforeBinaryOperators": [
|
||||
"+"
|
||||
, "-"
|
||||
, "/"
|
||||
, "*"
|
||||
, "="
|
||||
, "=="
|
||||
, "==="
|
||||
, "!="
|
||||
, "!=="
|
||||
]
|
||||
, "requireSpaceAfterBinaryOperators": [
|
||||
"+"
|
||||
, "-"
|
||||
, "/"
|
||||
, "*"
|
||||
, "="
|
||||
, "=="
|
||||
, "==="
|
||||
, "!="
|
||||
, "!=="
|
||||
]
|
||||
, "disallowImplicitTypeConversion": [
|
||||
"numeric"
|
||||
, "boolean"
|
||||
, "binary"
|
||||
, "string"
|
||||
]
|
||||
, "disallowKeywords": [
|
||||
"with"
|
||||
]
|
||||
, "disallowMultipleLineStrings": true
|
||||
|
||||
, "validateQuoteMarks": "\""
|
||||
, "disallowMixedSpacesAndTabs": true
|
||||
, "disallowTrailingWhitespace": true
|
||||
|
||||
, "requireKeywordsOnNewLine": [
|
||||
]
|
||||
, "requireLineFeedAtFileEnd": true
|
||||
, "requireCapitalizedConstructors": true
|
||||
, "safeContextKeyword": "that"
|
||||
|
||||
, "validateJSDoc": {
|
||||
"checkParamNames": true
|
||||
, "checkRedundantParams": true
|
||||
, "requireParamTypes": true
|
||||
}
|
||||
}
|
||||
132
.jscsrc
Normal file
132
.jscsrc
Normal file
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"excludeFiles": [
|
||||
"node_modules/**",
|
||||
"pjax.js",
|
||||
"pjax.min.js"
|
||||
],
|
||||
"fileExtensions": [
|
||||
".js"
|
||||
],
|
||||
"requireCurlyBraces": [
|
||||
"if",
|
||||
"else",
|
||||
"for",
|
||||
"while",
|
||||
"do",
|
||||
"try",
|
||||
"catch"
|
||||
],
|
||||
"requireSpaceAfterKeywords": [
|
||||
"if",
|
||||
"else",
|
||||
"for",
|
||||
"while",
|
||||
"do",
|
||||
"switch",
|
||||
"return",
|
||||
"try",
|
||||
"catch"
|
||||
],
|
||||
"requireSpaceBeforeBlockStatements": true,
|
||||
"requireParenthesesAroundIIFE": true,
|
||||
"requireSpacesInConditionalExpression": {
|
||||
"afterTest": true,
|
||||
"beforeConsequent": true,
|
||||
"afterConsequent": true,
|
||||
"beforeAlternate": true
|
||||
},
|
||||
"requireSpacesInFunctionExpression": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
},
|
||||
"disallowSpacesInFunctionExpression": {
|
||||
"beforeOpeningRoundBrace": true
|
||||
},
|
||||
"disallowMultipleVarDecl": true,
|
||||
"requireBlocksOnNewline": 1,
|
||||
"disallowPaddingNewlinesInBlocks": true,
|
||||
"disallowEmptyBlocks": true,
|
||||
"disallowSpacesInsideObjectBrackets": true,
|
||||
"disallowSpacesInsideArrayBrackets": true,
|
||||
"disallowSpacesInsideParentheses": true,
|
||||
"disallowQuotedKeysInObjects": "allButReserved",
|
||||
"disallowSpaceAfterObjectKeys": true,
|
||||
"requireCommaBeforeLineBreak": true,
|
||||
"requireOperatorBeforeLineBreak": [
|
||||
"?",
|
||||
"+",
|
||||
"-",
|
||||
"/",
|
||||
"*",
|
||||
"=",
|
||||
"==",
|
||||
"===",
|
||||
"!=",
|
||||
"!==",
|
||||
">",
|
||||
">=",
|
||||
"<",
|
||||
"<="
|
||||
],
|
||||
"disallowSpaceAfterPrefixUnaryOperators": [
|
||||
"++",
|
||||
"--",
|
||||
"+",
|
||||
"-",
|
||||
"~",
|
||||
"!"
|
||||
],
|
||||
"disallowSpaceBeforePostfixUnaryOperators": [
|
||||
"++",
|
||||
"--"
|
||||
],
|
||||
"requireSpaceBeforeBinaryOperators": [
|
||||
"+",
|
||||
"-",
|
||||
"/",
|
||||
"*",
|
||||
"=",
|
||||
"==",
|
||||
"===",
|
||||
"!=",
|
||||
"!=="
|
||||
],
|
||||
"requireSpaceAfterBinaryOperators": [
|
||||
"+",
|
||||
"-",
|
||||
"/",
|
||||
"*",
|
||||
"=",
|
||||
"==",
|
||||
"===",
|
||||
"!=",
|
||||
"!=="
|
||||
],
|
||||
"disallowImplicitTypeConversion": [
|
||||
"numeric",
|
||||
"boolean",
|
||||
"binary",
|
||||
"string"
|
||||
],
|
||||
"requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties",
|
||||
"disallowKeywords": [
|
||||
"with"
|
||||
],
|
||||
"disallowMultipleLineStrings": true,
|
||||
"validateQuoteMarks": "\"",
|
||||
"validateIndentation": 2,
|
||||
"disallowMixedSpacesAndTabs": true,
|
||||
"disallowTrailingWhitespace": true,
|
||||
"requireKeywordsOnNewLine": [
|
||||
"else"
|
||||
],
|
||||
"requireLineFeedAtFileEnd": true,
|
||||
"requireCapitalizedConstructors": true,
|
||||
"safeContextKeyword": "that",
|
||||
"requireDotNotation": true,
|
||||
"jsDoc": {
|
||||
"checkParamNames": true,
|
||||
"checkRedundantParams": true,
|
||||
"requireParamTypes": true
|
||||
},
|
||||
"requireSpaceAfterLineComment": true
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"asi": true
|
||||
, "laxcomma": true
|
||||
"newcap": false,
|
||||
"undef": true,
|
||||
"unused": true,
|
||||
"asi": true,
|
||||
"esnext": true,
|
||||
"node": true,
|
||||
"browser": true
|
||||
}
|
||||
|
||||
7
.travis.yml
Normal file
7
.travis.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
language: "node_js"
|
||||
node_js:
|
||||
- "6"
|
||||
- "8"
|
||||
# Force Travis to use npm v5
|
||||
# https://github.com/travis-ci/travis-ci/issues/4653#issuecomment-194051953
|
||||
before_install: if [[ `npm -v` != 5* ]]; then npm i -g npm@5; fi
|
||||
96
CHANGELOG.md
96
CHANGELOG.md
@@ -1,22 +1,102 @@
|
||||
# 0.2.5 - 2018-02-02
|
||||
|
||||
- Fixed: Async switch functions now work correctly, because the DOM is now parsed after all the switches finish.
|
||||
([#79](https://github.com/MoOx/pjax/pull/79), [#110](https://github.com/MoOx/pjax/pull/110) - @oskarrough, @BehindTheMath, @robinnorth)
|
||||
- Fixed: Bug on IE11 preventing AJAX page refresh.
|
||||
([#81](https://github.com/MoOx/pjax/pull/81) - @CPTechnikVX)
|
||||
- Fixed: Default switches are now available as `Pjax.switches`.
|
||||
([#92](https://github.com/MoOx/pjax/pull/92) - @BehindTheMath)
|
||||
- Fixed: An error that was caused by a missing `switchElementsAlt`.
|
||||
([#93](https://github.com/MoOx/pjax/pull/93), [#104](https://github.com/MoOx/pjax/pull/104) - @BehindTheMath, @robinnorth)
|
||||
- Fixed: Incorrect `main` field in npm package
|
||||
([#105](https://github.com/MoOx/pjax/pull/105) - @robinnorth)
|
||||
- Fixed: A pending XHR is now aborted if the user navigates somewhere else before the request is finished.
|
||||
([#114](https://github.com/MoOx/pjax/pull/114) - @robinnorth)
|
||||
- Fixed: When rendering new content, focus will now be removed only from elements within one of the containers manipulated by Pjax.
|
||||
([#116](https://github.com/MoOx/pjax/pull/116) - @BehindTheMath)
|
||||
- Fixed: Stop dispatching extraneous `pjax:complete` events when external scripts load
|
||||
([#118](https://github.com/MoOx/pjax/pull/118) - @robinnorth)
|
||||
- Added: Send the `X-PJAX` header with XHR requests.
|
||||
([#80](https://github.com/MoOx/pjax/pull/80) - @bram1028)
|
||||
- Added: Direct download link for script tags. (@MoOx)
|
||||
- Added: Pass the element that triggered Pjax to the `pjax:send` event.
|
||||
([#94](https://github.com/MoOx/pjax/pull/94) - @BehindTheMath)
|
||||
- Added: An option to set a timeout for XHR requests.
|
||||
([#95](https://github.com/MoOx/pjax/pull/95) - @BehindTheMath)
|
||||
- Added: Checks for XHR redirects
|
||||
([#101](https://github.com/MoOx/pjax/pull/101) - @BehindTheMath)
|
||||
- Added: Save scroll position with history, and restore when navigating backwards or forwards.
|
||||
([#110](https://github.com/MoOx/pjax/pull/110), [#119](https://github.com/MoOx/pjax/pull/119) - @BehindTheMath, @robinnorth)
|
||||
- Added: Scroll to element position when URL contains a hash
|
||||
([#110](https://github.com/MoOx/pjax/pull/110) - @BehindTheMath)
|
||||
- Added: Minified version of the Pjax bundle.
|
||||
([#115](https://github.com/MoOx/pjax/pull/115) - @BehindTheMath)
|
||||
- Changed: Miscellaneous code and tests cleanup.
|
||||
([#96](https://github.com/MoOx/pjax/pull/96), [#98](https://github.com/MoOx/pjax/pull/98), [#99](https://github.com/MoOx/pjax/pull/99), [#100](https://github.com/MoOx/pjax/pull/1070), [#107](https://github.com/MoOx/pjax/pull/107), [#113](https://github.com/MoOx/pjax/pull/113), [#120](https://github.com/MoOx/pjax/pull/120) - @BehindTheMath, @MoOx, @robinnorth)
|
||||
|
||||
# 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
|
||||
|
||||
- Fixed: added back standalone version in `./pjax.js`
|
||||
([#57](https://github.com/MoOx/pjax/issues/57)
|
||||
- Fixed: error when using pjax with google analytics (``options`` was undefined)
|
||||
([#59](https://github.com/MoOx/pjax/pull/59))
|
||||
- Fixed: HierarchyRequestError error
|
||||
([#49](https://github.com/MoOx/pjax/pull/49))
|
||||
- Fixed: ``TypeError: Pjax.forEachEls is not a function``
|
||||
([#52](https://github.com/MoOx/pjax/pull/52))
|
||||
- Fixed: ``TypeError: Pjax.executeScripts is not a function``
|
||||
([#52](https://github.com/MoOx/pjax/pull/52))
|
||||
- Fixed: ``TypeError: Pjax.clone is not a function``
|
||||
([#52](https://github.com/MoOx/pjax/pull/52))
|
||||
- Added: Ignore events with prevented defaults
|
||||
([#50](https://github.com/MoOx/pjax/pull/50))
|
||||
|
||||
# 0.2.1 - 2015-02-04
|
||||
|
||||
- Fixed: it's better when a release have actual files.
|
||||
|
||||
# 0.2.0 - 2015-02-04
|
||||
|
||||
- Fixed: prevent scrollTo from being converted from false to 0 ([#33](https://github.com/MoOx/pjax/pull/33))
|
||||
- Changed: code exploded in commonjs style
|
||||
- Added: lots of tests
|
||||
- Added: `refresh` method to force update a DOM element ([#36](https://github.com/MoOx/pjax/pull/36))
|
||||
|
||||
# 0.1.4 - 2014-10-14
|
||||
|
||||
- allow to load pages without any attributes on `<html>` element (fix [#6](https://github.com/MoOx/pjax/issues/6))
|
||||
- make `Pjax.switches.sideBySide` method usable (fix [#13](https://github.com/MoOx/pjax/issues/13))
|
||||
- Fixed: allow to load pages without any attributes on `<html>` element (fix [#6](https://github.com/MoOx/pjax/issues/6))
|
||||
- Fixed: make `Pjax.switches.sideBySide` method usable (fix [#13](https://github.com/MoOx/pjax/issues/13))
|
||||
|
||||
# 0.1.3 - 2014-09-16
|
||||
|
||||
- clicking on the current url somewhere does not produce a full reload by default (see option `currentUrlFullReload`)
|
||||
- fix `document.implementation.createHTMLDocument` error (in IE10, ref [#16](https://github.com/MoOx/pjax/pull/16))
|
||||
- Fixed: clicking on the current url somewhere does not produce a full reload by default (see option `currentUrlFullReload`)
|
||||
- Fixed: `document.implementation.createHTMLDocument` error (in IE10, ref [#16](https://github.com/MoOx/pjax/pull/16))
|
||||
|
||||
# 0.1.2 - 2014-04-03
|
||||
|
||||
- pjax.js relocated in `src/`
|
||||
- <html> attributes of pjaxified document are now available
|
||||
- Changed: `pjax.js` relocated in `src/`
|
||||
- Fixed: `<html>` attributes of pjaxified document are now available
|
||||
|
||||
# 0.1.1 - 2014-04-02
|
||||
|
||||
- Safer UMD wrapper (fix concat issue)
|
||||
- Fixed: safer UMD wrapper (fix concat issue)
|
||||
|
||||
# 0.1.0 - 2014-03-24
|
||||
|
||||
Initial release
|
||||
✨ Initial release
|
||||
|
||||
366
README.md
366
README.md
@@ -1,64 +1,83 @@
|
||||
# Pjax
|
||||
|
||||
<img align="right" src="https://dl.dropboxusercontent.com/u/14108185/memes/mind-blow.gif">
|
||||
[](https://travis-ci.org/MoOx/pjax).
|
||||
|
||||
> Easily enable fast Ajax navigation on any website (using pushState + xhr)
|
||||
> Easily enable fast AJAX navigation on any website (using pushState() + XHR)
|
||||
|
||||
Pjax is ~~a jQuery plugin~~ **a standalone JavaScript module** that uses
|
||||
ajax (XmlHttpRequest) and
|
||||
AJAX (XmlHttpRequest) and
|
||||
[pushState()](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history)
|
||||
to deliver a fast browsing experience.
|
||||
|
||||
_It allow you to completely transform user experience of standard websites
|
||||
(server side generated or static ones) to make them feel they browse an app.
|
||||
Especially for user that have low bandwidth connection._
|
||||
_It allows you to completely transform the user experience of standard websites
|
||||
(server-side generated or static ones) to make them feel like they are browsing an app,
|
||||
especially for users with low bandwidth connection._
|
||||
|
||||
**No more full page reload. No more lots of HTTP request.**
|
||||
**No more full page reloads. No more multiple HTTP requests.**
|
||||
|
||||
## No tests or Demo ?
|
||||
## Demo
|
||||
|
||||
~~There is still some work to make this repo sexy with tests & simple demo.~~
|
||||
Actually there is a [WIP branch where I'm adding lot of tests](https://github.com/MoOx/pjax/tree/testling)
|
||||
[You can see this running on my website](http://moox.io), with sexy CSS animations when switching pages.
|
||||
|
||||
For now [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
|
||||
```
|
||||
|
||||
- You can also link directly to the [bundle](https://cdn.jsdelivr.net/npm/pjax/pjax.js):
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/pjax@VERSION/pjax.js"></script>
|
||||
```
|
||||
Or the [minified bundle](https://cdn.jsdelivr.net/npm/pjax/pjax.min.js):
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/pjax@VERSION/pjax.min.js"></script>
|
||||
```
|
||||
|
||||
## No dependencies
|
||||
|
||||
_Pjax does not rely on other libraries, like jQuery or similar. It is written entirely in vanilla JS._
|
||||
|
||||
## 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.
|
||||
_But under the hood, it's just ONE http request with a pushState() call._
|
||||
Obviously, for [browsers that don't support pushState()](http://caniuse.com/#search=pushstate) Pjax fully degrades (yeah, it doesn't do anything at all).
|
||||
Pjax loads pages using AJAX and updates the browser's current URL using `pushState()` without reloading your page's layout or any resources (JS, CSS), giving a fast page load.
|
||||
|
||||
It simply works with all permalinks & can update all parts of the page you
|
||||
want (including html metas, title, navigation state).
|
||||
_But under the hood, it's just ONE HTTP request with a `pushState()` call._
|
||||
|
||||
- It's not limited to one container, like jQuery-Pjax is,
|
||||
- It fully support browser history (back & forward buttons),
|
||||
- It **will** support keyboard browsing (@todo),
|
||||
- Automatically fallback to classic navigation for externals pages (thanks to Capitain Obvious help),
|
||||
- Automatically fallback to classic navigation for internals pages that will not have the appropriated DOM tree,
|
||||
Obviously, for [browsers that don't support `history.pushState()`](http://caniuse.com/#search=pushstate) Pjax gracefully degrades and does not do anything at all.
|
||||
|
||||
It simply works with all permalinks and can update all parts of the page you
|
||||
want (including HTML metas, title, and navigation state).
|
||||
|
||||
- It's not limited to one container, like jQuery-Pjax is.
|
||||
- It fully supports browser history (back and forward buttons).
|
||||
- It supports keyboard browsing.
|
||||
- Automatically falls back to standard navigation for external pages (thanks to Captain Obvious's help).
|
||||
- Automatically falls back to standard navigation for internal pages that do not have an appropriate DOM tree.
|
||||
- You can add pretty cool CSS transitions (animations) very easily.
|
||||
- It's around 3kb (minified & gzipped).
|
||||
- It's around 4kb (minified and gzipped).
|
||||
|
||||
### Under the hood
|
||||
|
||||
- It listen to every clicks on links _you want_ (by default all of them),
|
||||
- When a internal link hitted, Pjax grabs HTML from your server via ajax,
|
||||
- Pjax render pages DOM tree (without loading any resources - images, css, js...)
|
||||
- It check if all defined parts can be replaced:
|
||||
- if page doesn't suit requirement, classic navigation used,
|
||||
- if page suits requirement, Pjax does all defined DOM replacements
|
||||
- Then, it updates the browser's current url using pushState
|
||||
- It listens to every click on links _you want_ (by default all of them).
|
||||
- When an internal link is clicked, Pjax grabs HTML from your server via AJAX.
|
||||
- Pjax renders the page's DOM tree (without loading any resources - images, CSS, JS...).
|
||||
- It checks that all defined parts can be replaced:
|
||||
- If the page doesn't meet the requirements, standard navigation is used.
|
||||
- If the page meets the requirements, Pjax does all defined DOM replacements.
|
||||
- Then it updates the browser's current URL using `pushState()`.
|
||||
|
||||
## Overview
|
||||
|
||||
Pjax is fully automatic. You won't need to setup anything on the existing HTML.
|
||||
Pjax is fully automatic. You don't need to setup anything in the existing HTML.
|
||||
You just need to designate some elements on your page that will be replaced when
|
||||
you navigate your site.
|
||||
|
||||
Consider the following page.
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- metas, title, styles, ... -->
|
||||
@@ -76,7 +95,7 @@ Consider the following page.
|
||||
</html>
|
||||
```
|
||||
|
||||
We want Pjax to grab the url `/blah` then replace `.my-Content` with whatever it gets back.
|
||||
We want Pjax to intercept the URL `/blah`, and replace `.my-Content` with the results of the request.
|
||||
Oh and the `<nav>` (that contains a status marker somewhere) can be updated too (or stay the same, as you wish).
|
||||
And also the `<aside>` please.
|
||||
So we want to update `[".my-Header", ".my-Content", ".my-Sidebar"]`, **without reloading styles nor scripts**.
|
||||
@@ -87,43 +106,23 @@ We do this by telling Pjax to listen on `a` tags and use CSS selectors defined a
|
||||
new Pjax({ selectors: ["title", ".my-Header", ".my-Content", ".my-Sidebar"] })
|
||||
```
|
||||
|
||||
Now when someone in a Pjax-compatible browser clicks "blah" the content of all selectors will be replaced with the one found in the "blah" content.
|
||||
Now, when someone in a Pjax-compatible browser clicks "blah", the content of all selectors will be replaced with the one found in the "blah" content.
|
||||
|
||||
_Magic! For real!_ **There is completely no need to do anything on server side!**
|
||||
_Magic! For real!_ **There is no need to do anything server-side!**
|
||||
|
||||
## Differences with [jQuery-pjax](https://github.com/defunkt/jquery-pjax)
|
||||
|
||||
- No jQuery dependency,
|
||||
- Not limited to a container,
|
||||
- No server side requirements,
|
||||
- Works for CommonJS environment (browserify), AMD (RequireJS) or even globally,
|
||||
- Allow page transition with CSS animations,
|
||||
- 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._
|
||||
- No jQuery dependency
|
||||
- Not limited to a container
|
||||
- No server-side requirements
|
||||
- Works for CommonJS environment (Webpack/Browserify), AMD (RequireJS) or even globally
|
||||
- Allow page transition with CSS animations
|
||||
- Can be easily tweaked, since every method is public (and as a result, overridable)
|
||||
|
||||
## Compatibility
|
||||
|
||||
Pjax only works with [browsers that support the `history.pushState` API](http://caniuse.com/#search=pushstate).
|
||||
When the API isn't supported Pjax goes into fallback mode (it just does nothing).
|
||||
Pjax only works with [browsers that support the `history.pushState()` API](http://caniuse.com/#search=pushstate).
|
||||
When the API isn't supported, Pjax goes into fallback mode (and it just does nothing).
|
||||
|
||||
To see if Pjax is actually supported by your browser, use `Pjax.isSupported()`.
|
||||
|
||||
@@ -135,18 +134,18 @@ Let's talk more about the most basic way to get started:
|
||||
|
||||
```js
|
||||
new Pjax({
|
||||
elements: "a" // default is "a[href], form[action]"
|
||||
, selectors: ["title", ".my-Header", ".my-Content", ".my-Sidebar"]
|
||||
elements: "a", // default is "a[href], form[action]"
|
||||
selectors: ["title", ".my-Header", ".my-Content", ".my-Sidebar"]
|
||||
})
|
||||
```
|
||||
|
||||
This will enable Pjax on all links and designate the part to replace using CSS selectors `"title", ".my-Header", ".my-Content", ".my-Sidebar"`.
|
||||
This will enable Pjax on all links, and designate the part to replace using CSS selectors `"title", ".my-Header", ".my-Content", ".my-Sidebar"`.
|
||||
|
||||
For some reason, you might want to just target some elements to apply Pjax behavior.
|
||||
In that case, you can 2 differents things:
|
||||
In that case, you can do two different things:
|
||||
|
||||
- use a custom selector like "a.js-Pjax" or ".js-Pjax a" depending on what you want.
|
||||
- override `Pjax.prototype.getElements` that just basically `querSelectorAll` the `elements` option. In this function you just need to return a `NodeList`.
|
||||
- Use a custom selector like "a.js-Pjax" or ".js-Pjax a" depending on what you want.
|
||||
- Override `Pjax.prototype.getElements` that just basically `querySelectorAll` the `elements` option. In this function you just need to return a `NodeList`.
|
||||
|
||||
```js
|
||||
// use case 1
|
||||
@@ -161,17 +160,17 @@ Pjax.prototype.getElements = function() {
|
||||
new Pjax({})
|
||||
```
|
||||
|
||||
When instanciating a `Pjax` object, you need to pass all options as an object:
|
||||
When instantiating a `Pjax` object, you need to pass all options as an object:
|
||||
|
||||
#### Options
|
||||
|
||||
##### `elements` (String, default "a[href], form[action]")
|
||||
##### `elements` (String, default: `"a[href], form[action]"`)
|
||||
|
||||
CSS Selector to use to retrieve links to apply Pjax
|
||||
CSS selector to use to retrieve links to apply Pjax to.
|
||||
|
||||
##### `selectors` (Array, default ["title", ".js-Pjax"])
|
||||
##### `selectors` (Array, default: `["title", ".js-Pjax"]`)
|
||||
|
||||
CSS Selectors to replace. If a query returns multiples items, it will just keep the index.
|
||||
CSS selectors to replace. If a query returns multiples items, it will just keep the index.
|
||||
|
||||
Example of what you can do:
|
||||
|
||||
@@ -191,20 +190,20 @@ Example of what you can do:
|
||||
```
|
||||
|
||||
This example is correct and should work "as expected".
|
||||
_If there is not the same amount of DOM element from current page and new page,
|
||||
the Pjax behavior will fallback to normal page load._
|
||||
_If the current page and new page do not have the same amount of DOM elements,
|
||||
Pjax will fall back to normal page load._
|
||||
|
||||
##### `switches` (Object, default {})
|
||||
##### `switches` (Object, default: `{}`)
|
||||
|
||||
Objects containing callbacks that can be used to switch old element with new element.
|
||||
Keys should be one of the defined selector.
|
||||
Objects containing callbacks that can be used to switch old elements with new elements.
|
||||
Keys should be one of the defined selectors.
|
||||
|
||||
Examples:
|
||||
|
||||
```js
|
||||
new Pjax({
|
||||
selectors: ["title", ".Navbar", ".js-Pjax"]
|
||||
, switches: {
|
||||
selectors: ["title", ".Navbar", ".js-Pjax"],
|
||||
switches: {
|
||||
// "title": Pjax.switches.outerHTML // default behavior
|
||||
".Navbar": function(oldEl, newEl, options) {
|
||||
// here it's a stupid example since it's the default behavior too
|
||||
@@ -217,22 +216,22 @@ new Pjax({
|
||||
})
|
||||
```
|
||||
|
||||
Callbacks are binded to Pjax instance itself to allow you to reuse it (ex: `this.onSwitch()`)
|
||||
Callbacks are bound to Pjax instance itself to allow you to reuse it (ex: `this.onSwitch()`)
|
||||
|
||||
###### Existing switches callback
|
||||
|
||||
- `Pjax.switches.outerHTML`: default behavior, replace elements using outerHTML
|
||||
- `Pjax.switches.innerHTML`: replace elements using innerHTML & copy className too
|
||||
- `Pjax.switches.sideBySide`: smart replacement that allow you to have both elements in the same parent when you want to use CSS animations. Old elements are removed when all childs have been fully animated ([animationEnd](http://www.w3.org/TR/css3-animations/#animationend) event triggered)
|
||||
- `Pjax.switches.innerHTML`: replace elements using innerHTML and copy className too
|
||||
- `Pjax.switches.sideBySide`: smart replacement that allows you to have both elements in the same parent when you want to use CSS animations. Old elements are removed when all children have been fully animated ([animationEnd](http://www.w3.org/TR/css3-animations/#animationend) event triggered)
|
||||
|
||||
###### Create a switch callback
|
||||
|
||||
Your function can do whatever you want, but you need to:
|
||||
|
||||
- replace oldEl content by newEl content in some fashion
|
||||
- call `this.onSwitch()` to trigger attached callback.
|
||||
- replace `oldEl`'s content with `newEl`'s content in some fashion
|
||||
- call `this.onSwitch()` to trigger the attached callback.
|
||||
|
||||
Here is the default behavior as example
|
||||
Here is the default behavior as an example:
|
||||
|
||||
```js
|
||||
function(oldEl, newEl, pjaxRequestOptions, switchesClasses) {
|
||||
@@ -241,31 +240,31 @@ function(oldEl, newEl, pjaxRequestOptions, switchesClasses) {
|
||||
}
|
||||
```
|
||||
|
||||
##### `switchesOptions` (Object, default {})
|
||||
##### `switchesOptions` (Object, default: `{}`)
|
||||
|
||||
This are options that can be used during switch by switchers ( for now, only `Pjax.switches.sideBySide` use it).
|
||||
Very convenient when you use something like [Animate.css](https://github.com/daneden/animate.css)
|
||||
These are options that can be used during switch by switchers (for now, only `Pjax.switches.sideBySide` uses it).
|
||||
This is very convenient when you use something like [Animate.css](https://github.com/daneden/animate.css)
|
||||
with or without [WOW.js](https://github.com/matthieua/WOW).
|
||||
|
||||
```js
|
||||
new Pjax({
|
||||
selectors: ["title", ".js-Pjax"]
|
||||
, switches: {
|
||||
selectors: ["title", ".js-Pjax"],
|
||||
switches: {
|
||||
".js-Pjax": Pjax.switches.sideBySide
|
||||
}
|
||||
, switchesOptions: {
|
||||
},
|
||||
switchesOptions: {
|
||||
".js-Pjax": {
|
||||
classNames: {
|
||||
// class added on the element that will be removed
|
||||
remove: "Animated Animated--reverse Animate--fast Animate--noDelay"
|
||||
remove: "Animated Animated--reverse Animate--fast Animate--noDelay",
|
||||
// class added on the element that will be added
|
||||
, add: "Animated"
|
||||
add: "Animated",
|
||||
// class added on the element when it go backward
|
||||
, backward: "Animate--slideInRight"
|
||||
backward: "Animate--slideInRight",
|
||||
// class added on the element when it go forward (used for new page too)
|
||||
, forward: "Animate--slideInLeft"
|
||||
}
|
||||
, callbacks: {
|
||||
forward: "Animate--slideInLeft"
|
||||
},
|
||||
callbacks: {
|
||||
// to make a nice transition with 2 pages as the same time
|
||||
// we are playing with absolute positioning for element we are removing
|
||||
// & we need live metrics to have something great
|
||||
@@ -275,12 +274,13 @@ new Pjax({
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
_Note that remove class include `Animated--reverse` which is a simple way to not have
|
||||
to create duplicate transition for (slideIn + reverse => slideOut)._
|
||||
|
||||
The following CSS will be required to make something nice
|
||||
The following CSS will be required to make something nice:
|
||||
|
||||
```css
|
||||
/*
|
||||
@@ -338,8 +338,7 @@ The following CSS will be required to make something nice
|
||||
}
|
||||
```
|
||||
|
||||
To get understand this CSS, here is a HTML snippet
|
||||
|
||||
To give context to this CSS, here is an HTML snippet:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
@@ -367,86 +366,99 @@ To get understand this CSS, here is a HTML snippet
|
||||
</html>
|
||||
```
|
||||
|
||||
##### `history` (Boolean, default true)
|
||||
##### `history` (Boolean, default: `true`)
|
||||
|
||||
Enable pushState. Only disable if you are crazy.
|
||||
Internaly, this option is used when `popstate` is used (to not pushState again).
|
||||
You should forget that option.
|
||||
Enable the use of `pushState()`. Disabling this will prevent Pjax from updating browser history.
|
||||
However, there is almost no use case where you would want to do that.
|
||||
|
||||
##### `analytics` (Function, default to a function that push `_gaq` `trackPageview` or send `ga` `pageview`
|
||||
Internally, this option is used when a `popstate` event triggers Pjax (to not `pushState()` again).
|
||||
|
||||
Function that allow you to add behavior for analytics. By default it try to track
|
||||
a pageview with Google Analytics.
|
||||
It's called every time a page is switched, even for history buttons.
|
||||
##### `analytics` (Function|Boolean, default: a function that pushes `_gaq` `_trackPageview` or sends `ga` `pageview`
|
||||
|
||||
##### `scrollTo` (Integer, default to 0)
|
||||
Function that allows you to add behavior for analytics. By default it tries to track
|
||||
a pageview with Google Analytics (if it exists on the page).
|
||||
It's called every time a page is switched, even for history navigation.
|
||||
|
||||
Value (in px) to scrollTo when a page is switched.
|
||||
Set to `false` to disable this behavior.
|
||||
|
||||
##### `debug` (Boolean, default to false)
|
||||
##### `scrollTo` (Integer, default: `0`)
|
||||
|
||||
Enable verbose mode & doesn't use fallback when there is an error.
|
||||
Useful to debug page layout differences.
|
||||
Value (in px from the top of the page) to scroll to when a page is switched.
|
||||
|
||||
##### `currentUrlFullReload` (Boolean, default to false)
|
||||
##### `scrollRestoration` (Boolean, default: `true`)
|
||||
|
||||
When set to true, clicking on a link that point the current url trigger a full page reload.
|
||||
When set to true, attempt to restore the scroll position when navigating backwards or forwards.
|
||||
|
||||
#### Extend Pjax
|
||||
##### `cacheBust` (Boolean, default: `true`)
|
||||
|
||||
Pjax prototype & utilities methods can be used & changed so you can patch or hack
|
||||
Pjax behavior, as you wish.
|
||||
When set to true, append a timestamp query string segment to the requested URLs
|
||||
in order to skip browser cache.
|
||||
|
||||
Here is a summary of functions:
|
||||
##### `debug` (Boolean, default: `false`)
|
||||
|
||||
- `Pjax.isSupported` (`function()`): return wheter or not the browser handle pushState correctly
|
||||
- `Pjax.on` (`function(els, events, listener, useCapture)`): addEventListener, that handles NodeList & supports space separated event name
|
||||
- `Pjax.off` (`function(els, events, listener, useCapture)`): removeEventListener, that handles NodeList & supports space separated event name
|
||||
- `Pjax.trigger` (`function(els, events)`): fireEvent, that handles NodeList & supports space separated event name
|
||||
- `Pjax.clone` (`function(obj)`): clone object
|
||||
- `Pjax.executeScripts` (`function(el)`): execute scripts that are inside an element (script src or inline scripts through `Pjax.evalScript`)
|
||||
- `Pjax.evalScript` (`function(el)`): execute inline script. Don't execute a script if it contains `document.write`.
|
||||
Enables verbose mode. Useful to debug page layout differences.
|
||||
|
||||
- `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.
|
||||
##### `currentUrlFullReload` (Boolean, default: `false`)
|
||||
|
||||
When set to true, clicking on a link that points to the current URL will trigger a full page reload.
|
||||
|
||||
The default is `false`, so clicking on such a link will do nothing.
|
||||
If you want to add some custom behavior, add a click listener to the link,
|
||||
and set `preventDefault` to true, to prevent Pjax from receiving the event.
|
||||
|
||||
Here is some sample code:
|
||||
|
||||
```js
|
||||
var links = document.querySelectorAll(".js-Pjax");
|
||||
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var el = links[i]
|
||||
|
||||
el.addEventListener("click", function(e) {
|
||||
if (el.href === window.location.href.split("#")[0]) {
|
||||
e.preventDefault();
|
||||
console.log("Link to current page clicked");
|
||||
// Custom code goes here.
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
(Note that if `cacheBust` is set to true, the code that checks if the href
|
||||
is the same as the current page's URL will not work, due to the query string
|
||||
appended to force a cache bust).
|
||||
|
||||
##### `timeout` (Integer, default: `0`)
|
||||
|
||||
The timeout in milliseconds for the XHR requests. Set to 0 to disable the timeout.
|
||||
|
||||
### Events
|
||||
|
||||
Pjax fires a number of events regardless of how its invoked.
|
||||
Pjax fires a number of events regardless of how it's invoked.
|
||||
|
||||
All events are fired from the _document_, not the link was clicked.
|
||||
|
||||
#### Ajax related events
|
||||
All events are fired from the _document_, not the link that was clicked.
|
||||
|
||||
* `pjax:send` - Fired after the Pjax request begins.
|
||||
* `pjax:complete` - Fired after the Pjax request finishes.
|
||||
* `pjax:success` - Fired after the Pjax request succeeds.
|
||||
* `pjax:error` - Fired after the Pjax request fails. Returning false will prevent the the fallback redirect.
|
||||
* `pjax:error` - Fired after the Pjax request fails.
|
||||
|
||||
`send` and `complete` are a good pair of events to use if you are implementing a loading indicator (eg: [topbar](http://buunguyen.github.io/topbar/))
|
||||
|
||||
```js
|
||||
$(document).on('pjax:send', topbar.show)
|
||||
$(document).on('pjax:complete', topbar.hide)
|
||||
document.addEventListener('pjax:send', topbar.show)
|
||||
document.addEventListener('pjax:complete', topbar.hide)
|
||||
```
|
||||
|
||||
#### Note about DOM ready state
|
||||
|
||||
Most of the time, you have code attached & related to the current DOM, that you only execute when page/dom is ready.
|
||||
Since Pjax doesn't magically rexecute you previous code each time you load a page, you need to make a simple thing to rexecute appropriate code:
|
||||
Most of the time, you will have code related to the current DOM that you only execute when the DOM is ready.
|
||||
|
||||
Since Pjax doesn't magically re-execute your previous code each time you load a page, you need to add some simple code to achieve this:
|
||||
|
||||
```js
|
||||
function whenDOMReady() {
|
||||
// do you stuff
|
||||
// do your stuff
|
||||
}
|
||||
|
||||
whenDOMReady()
|
||||
@@ -456,17 +468,18 @@ new Pjax()
|
||||
document.addEventListener("pjax:success", whenDOMReady)
|
||||
```
|
||||
|
||||
_Note: Don't create the Pjax in the `whenDOMReady` function._
|
||||
_Note: Don't create the Pjax instance in the `whenDOMReady` function._
|
||||
|
||||
For my concern & usage, I `js-Pjax`ify all body children, including stuff like navigation & footer (to get navigation state easily updated).
|
||||
So attached behavior are rexecuted each time a page is loaded, like in the snippet above.
|
||||
For my concern and usage, I `js-Pjax`-ify all body children, including stuff like navigation and footer (to get navigation state easily updated).
|
||||
|
||||
The attached behavior is re-executed each time a page is loaded, like in the snippet above.
|
||||
|
||||
If you want to just update a specific part (it's totally a good idea), you can just
|
||||
add the DOM related code in a function & rexecute this function when "pjax:success" event is done.
|
||||
add the DOM-related code in a function and re-execute this function when "pjax:success" event is fired.
|
||||
|
||||
```js
|
||||
// do your global stuff
|
||||
//... dom ready blah blah
|
||||
//... DOM ready blah blah
|
||||
|
||||
function whenContainerReady() {
|
||||
// do your container related stuff
|
||||
@@ -485,19 +498,19 @@ document.addEventListener("pjax:success", whenContainerReady)
|
||||
### Q: Disqus doesn't work anymore, how can I fix that ?
|
||||
|
||||
A: There is a few things you need to do:
|
||||
- wrap your disqus snippet into a dom element that you will add to the `selector`
|
||||
arra (or just wrap it with class="js-Pjax") & be sure to have a least the empty
|
||||
- wrap your Disqus snippet into a DOM element that you will add to the `selector`
|
||||
property (or just wrap it with `class="js-Pjax"`) and be sure to have at least the empty
|
||||
wrapper on each page (to avoid differences of DOM between pages)
|
||||
- edit your disqus snippet like the one below
|
||||
- edit your Disqus snippet like the one below
|
||||
|
||||
#### Disqus snippet before pjax integration
|
||||
#### Disqus snippet before Pjax integration
|
||||
|
||||
```html
|
||||
<script>
|
||||
var disqus_shortname = 'YOURSHORTNAME'
|
||||
, disqus_identifier = 'PAGEID'
|
||||
, disqus_url = 'PAGEURL'
|
||||
, disqus_script = 'embed.js'
|
||||
var disqus_identifier = 'PAGEID'
|
||||
var disqus_url = 'PAGEURL'
|
||||
var disqus_script = 'embed.js'
|
||||
(function(d,s) {
|
||||
s = d.createElement('script');s.async=1;s.src = '//' + disqus_shortname + '.disqus.com/'+disqus_script;
|
||||
(d.getElementsByTagName('head')[0]).appendChild(s);
|
||||
@@ -508,13 +521,13 @@ wrapper on each page (to avoid differences of DOM between pages)
|
||||
#### Disqus snippet after Pjax integration
|
||||
|
||||
```html
|
||||
<div class="js-Pjax"><!-- need to be here on every pjaxified page, even if empty -->
|
||||
<!-- if (blah blah) { // eventual server side test to know wheter or not you include this script -->
|
||||
<div class="js-Pjax"><!-- needs to be here on every Pjax-ified page, even if empty -->
|
||||
<!-- if (blah blah) { // eventual server-side test to know whether or not you include this script -->
|
||||
<script>
|
||||
var disqus_shortname = 'YOURSHORTNAME'
|
||||
, disqus_identifier = 'PAGEID'
|
||||
, disqus_url = 'PAGEURL'
|
||||
, disqus_script = 'embed.js'
|
||||
var disqus_identifier = 'PAGEID'
|
||||
var disqus_url = 'PAGEURL'
|
||||
var disqus_script = 'embed.js'
|
||||
|
||||
// here we will only load the disqus <script> if not already loaded
|
||||
if (!window.DISQUS) {
|
||||
@@ -539,19 +552,22 @@ wrapper on each page (to avoid differences of DOM between pages)
|
||||
</div>
|
||||
```
|
||||
|
||||
**Note: The thing you need to understand is that Pjax handle inline `<script>` only for container you are reloading.**
|
||||
**Note: Pjax only handles inline `<script>` blocks for the container you are switching.**
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
## Examples
|
||||
|
||||
Work on a branch, install dev-dependencies, respect coding style & run tests before submitting a bug fix or a feature.
|
||||
Clone this repository and run `npm run example`, which will open the example app in your browser.
|
||||
|
||||
$ git clone https://github.com/MoOx/pjax.git
|
||||
$ git checkout -b patch-1
|
||||
$ npm install
|
||||
$ npm test
|
||||
---
|
||||
|
||||
## [Changelog](CHANGELOG.md)
|
||||
## CONTRIBUTING
|
||||
|
||||
## [License](LICENSE)
|
||||
* ⇄ 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`).
|
||||
|
||||
## [CHANGELOG](CHANGELOG.md)
|
||||
|
||||
## [LICENSE](LICENSE)
|
||||
|
||||
11
TODO.md
11
TODO.md
@@ -1,11 +0,0 @@
|
||||
## @todo
|
||||
|
||||
- Add tests, like a lot
|
||||
- handle hash in url (see https://github.com/defunkt/jquery-pjax/blob/master/jquery.pjax.js#L287-L303)
|
||||
- add keyboard support
|
||||
- add timeout option (600ms ?)
|
||||
- add form support
|
||||
- add cache
|
||||
- abort previous pjax request when several requests are made by a crazy person clicking everywhere
|
||||
- better switchFallback ?
|
||||
- handle document.write scripts ?
|
||||
31
bower.json
31
bower.json
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"name": "pjax",
|
||||
"version": "0.1.4",
|
||||
"description": "Easily enable fast Ajax navigation on any website (using pushState + xhr)",
|
||||
"keywords": [
|
||||
"pjax",
|
||||
"push",
|
||||
"state",
|
||||
"ajax",
|
||||
"navigation",
|
||||
"transition",
|
||||
"animation"
|
||||
],
|
||||
"main": "src/pjax.js",
|
||||
"homepage": "https://github.com/MoOx/pjax",
|
||||
"authors": [
|
||||
"Maxime Thirouin"
|
||||
],
|
||||
"license": "MIT",
|
||||
"moduleType": [
|
||||
"amd",
|
||||
"globals",
|
||||
"node"
|
||||
],
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"test",
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
27
example/example.js
Normal file
27
example/example.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/* global Pjax */
|
||||
console.log("Document initialized:", window.location.href)
|
||||
|
||||
document.addEventListener("pjax:send", function() {
|
||||
console.log("Event: pjax:send", arguments)
|
||||
})
|
||||
|
||||
document.addEventListener("pjax:complete", function() {
|
||||
console.log("Event: pjax:complete", arguments)
|
||||
})
|
||||
|
||||
document.addEventListener("pjax:error", function() {
|
||||
console.log("Event: pjax:error", arguments)
|
||||
})
|
||||
|
||||
document.addEventListener("pjax:success", function() {
|
||||
console.log("Event: pjax:success", arguments)
|
||||
})
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var pjax = new Pjax({
|
||||
elements: [".js-Pjax"],
|
||||
selectors: [".body"]
|
||||
// currentUrlFullReload: true,
|
||||
})
|
||||
console.log("Pjax initialized.", pjax)
|
||||
})
|
||||
17
example/index.html
Normal file
17
example/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>Hello</title>
|
||||
<script src='../pjax.js'></script>
|
||||
<script src='example.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class='body'>
|
||||
<h1>Index</h1>
|
||||
Hello.
|
||||
Go to <a href='page2.html' class="js-Pjax">Page 2</a> or <a href='page3.html' class="js-Pjax">Page 3</a> and view your console to see Pjax events.
|
||||
Clicking on <a href='index.html'>this page</a> will just reload the page entirely.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
15
example/page2.html
Normal file
15
example/page2.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>Hello</title>
|
||||
<script src='../pjax.js'></script>
|
||||
<script src='example.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class='body'>
|
||||
<h1>Page 2</h1>
|
||||
Hello. Go to <a href='index.html' class="js-Pjax">Index</a>.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
15
example/page3.html
Normal file
15
example/page3.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>Hello</title>
|
||||
<script src='../pjax.js'></script>
|
||||
<script src='example.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class='body'>
|
||||
<h1>Page 3</h1>
|
||||
Hello. Go to <a href='index.html' class="js-Pjax">Index</a>.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
332
index.js
Normal file
332
index.js
Normal file
@@ -0,0 +1,332 @@
|
||||
var clone = require("./lib/clone.js")
|
||||
var executeScripts = require("./lib/execute-scripts.js")
|
||||
var forEachEls = require("./lib/foreach-els.js")
|
||||
var switches = require("./lib/switches")
|
||||
var newUid = require("./lib/uniqueid.js")
|
||||
|
||||
var on = require("./lib/events/on.js")
|
||||
var trigger = require("./lib/events/trigger.js")
|
||||
|
||||
var contains = require("./lib/util/contains.js")
|
||||
var noop = require("./lib/util/noop")
|
||||
|
||||
var Pjax = function(options) {
|
||||
this.state = {
|
||||
numPendingSwitches: 0,
|
||||
href: null,
|
||||
options: null
|
||||
}
|
||||
|
||||
var parseOptions = require("./lib/proto/parse-options.js")
|
||||
parseOptions.call(this,options)
|
||||
this.log("Pjax options", this.options)
|
||||
|
||||
if (this.options.scrollRestoration && "scrollRestoration" in history) {
|
||||
history.scrollRestoration = "manual"
|
||||
}
|
||||
|
||||
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 = {}
|
||||
opt.scrollPos = st.state.scrollPos
|
||||
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 = switches
|
||||
|
||||
Pjax.prototype = {
|
||||
log: require("./lib/proto/log.js"),
|
||||
|
||||
getElements: function(el) {
|
||||
return el.querySelectorAll(this.options.elements)
|
||||
},
|
||||
|
||||
parseDOM: function(el) {
|
||||
var parseElement = require("./lib/proto/parse-element")
|
||||
forEachEls(this.getElements(el), parseElement, this)
|
||||
},
|
||||
|
||||
refresh: function(el) {
|
||||
this.parseDOM(el || document)
|
||||
},
|
||||
|
||||
reload: function() {
|
||||
window.location.reload()
|
||||
},
|
||||
|
||||
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)
|
||||
},
|
||||
|
||||
latestChance: function(href) {
|
||||
window.location = href
|
||||
},
|
||||
|
||||
onSwitch: function() {
|
||||
trigger(window, "resize scroll")
|
||||
|
||||
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 <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("=")
|
||||
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.
|
||||
if (document.activeElement && contains(this.options.selectors, document.activeElement)) {
|
||||
try {
|
||||
document.activeElement.blur()
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
this.switchSelectors(this.options.selectors, tmpEl, document, options)
|
||||
},
|
||||
|
||||
abortRequest: require("./lib/abort-request.js"),
|
||||
|
||||
doRequest: require("./lib/send-request.js"),
|
||||
|
||||
loadUrl: function(href, options) {
|
||||
this.log("load href", href, options)
|
||||
|
||||
// Abort any previous request
|
||||
this.abortRequest(this.request)
|
||||
|
||||
trigger(document, "pjax:send", options)
|
||||
|
||||
// Do the request
|
||||
options.requestOptions.timeout = this.options.timeout
|
||||
this.request = 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
|
||||
}
|
||||
|
||||
// push scroll position to history
|
||||
var currentState = window.history.state || {}
|
||||
window.history.replaceState({
|
||||
url: currentState.url || window.location.href,
|
||||
title: currentState.title || document.title,
|
||||
uid: currentState.uid || newUid(),
|
||||
scrollPos: [document.documentElement.scrollLeft || document.body.scrollLeft,
|
||||
document.documentElement.scrollTop || document.body.scrollTop]
|
||||
},
|
||||
document.title, window.location)
|
||||
|
||||
var oldHref = href
|
||||
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")
|
||||
}
|
||||
|
||||
// Add back the hash if it was removed
|
||||
var a = document.createElement("a")
|
||||
a.href = oldHref
|
||||
var oldHash = a.hash
|
||||
a.href = href
|
||||
if (oldHash && !a.hash) {
|
||||
a.hash = oldHash
|
||||
href = a.href
|
||||
}
|
||||
|
||||
this.state.href = href
|
||||
this.state.options = clone(options)
|
||||
|
||||
try {
|
||||
this.loadContent(html, options)
|
||||
}
|
||||
catch (e) {
|
||||
if (!this.options.debug) {
|
||||
if (console && console.error) {
|
||||
console.error("Pjax switch fail: ", e)
|
||||
}
|
||||
return this.latestChance(href)
|
||||
}
|
||||
else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
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
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
var state = this.state
|
||||
|
||||
if (state.options.history) {
|
||||
if (!window.history.state) {
|
||||
this.lastUid = this.maxUid = newUid()
|
||||
window.history.replaceState({
|
||||
url: window.location.href,
|
||||
title: document.title,
|
||||
uid: this.maxUid,
|
||||
scrollPos: [0, 0]
|
||||
},
|
||||
document.title)
|
||||
}
|
||||
|
||||
// Update browser history
|
||||
this.lastUid = this.maxUid = newUid()
|
||||
|
||||
window.history.pushState({
|
||||
url: state.href,
|
||||
title: state.options.title,
|
||||
uid: this.maxUid,
|
||||
scrollPos: [0, 0]
|
||||
},
|
||||
state.options.title,
|
||||
state.href)
|
||||
}
|
||||
|
||||
this.forEachSelectors(function(el) {
|
||||
this.parseDOM(el)
|
||||
}, this)
|
||||
|
||||
// Fire Events
|
||||
trigger(document,"pjax:complete pjax:success", state.options)
|
||||
|
||||
if (typeof state.options.analytics === "function") {
|
||||
state.options.analytics()
|
||||
}
|
||||
|
||||
if (state.options.history) {
|
||||
// First parse url and check for hash to override scroll
|
||||
var a = document.createElement("a")
|
||||
a.href = this.state.href
|
||||
if (a.hash) {
|
||||
var name = a.hash.slice(1)
|
||||
name = decodeURIComponent(name)
|
||||
|
||||
var curtop = 0
|
||||
var target = document.getElementById(name) || document.getElementsByName(name)[0]
|
||||
if (target) {
|
||||
// http://stackoverflow.com/questions/8111094/cross-browser-javascript-function-to-find-actual-position-of-an-element-in-page
|
||||
if (target.offsetParent) {
|
||||
do {
|
||||
curtop += target.offsetTop
|
||||
|
||||
target = target.offsetParent
|
||||
} while (target)
|
||||
}
|
||||
}
|
||||
window.scrollTo(0, curtop)
|
||||
}
|
||||
else if (state.options.scrollTo !== false) {
|
||||
// Scroll page to top on new page load
|
||||
if (state.options.scrollTo.length > 1) {
|
||||
window.scrollTo(state.options.scrollTo[0], state.options.scrollTo[1])
|
||||
}
|
||||
else {
|
||||
window.scrollTo(0, state.options.scrollTo)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (state.options.scrollRestoration && state.options.scrollPos) {
|
||||
window.scrollTo(state.options.scrollPos[0], state.options.scrollPos[1])
|
||||
}
|
||||
|
||||
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 = noop
|
||||
for (var key in Pjax.prototype) {
|
||||
if (Pjax.prototype.hasOwnProperty(key) && typeof Pjax.prototype[key] === "function") {
|
||||
stupidPjax[key] = noop
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = stupidPjax
|
||||
}
|
||||
8
lib/abort-request.js
Normal file
8
lib/abort-request.js
Normal file
@@ -0,0 +1,8 @@
|
||||
var noop = require("./util/noop")
|
||||
|
||||
module.exports = function(request) {
|
||||
if (request && request.readyState < 4) {
|
||||
request.onreadystatechange = noop
|
||||
request.abort()
|
||||
}
|
||||
}
|
||||
12
lib/clone.js
Normal file
12
lib/clone.js
Normal file
@@ -0,0 +1,12 @@
|
||||
module.exports = function(obj) {
|
||||
if (null === obj || "object" !== typeof obj) {
|
||||
return obj
|
||||
}
|
||||
var copy = obj.constructor()
|
||||
for (var attr in obj) {
|
||||
if (obj.hasOwnProperty(attr)) {
|
||||
copy[attr] = obj[attr]
|
||||
}
|
||||
}
|
||||
return copy
|
||||
}
|
||||
39
lib/eval-script.js
Normal file
39
lib/eval-script.js
Normal file
@@ -0,0 +1,39 @@
|
||||
module.exports = function(el) {
|
||||
var code = (el.text || el.textContent || el.innerHTML || "")
|
||||
var src = (el.src || "")
|
||||
var parent = el.parentNode || document.querySelector("head") || document.documentElement
|
||||
var script = document.createElement("script")
|
||||
|
||||
if (code.match("document.write")) {
|
||||
if (console && console.log) {
|
||||
console.log("Script contains document.write. Can’t be executed correctly. Code skipped ", el)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
script.type = "text/javascript"
|
||||
|
||||
if (src !== "") {
|
||||
script.src = src
|
||||
script.async = false // force synchronous loading of peripheral JS
|
||||
}
|
||||
|
||||
if (code !== "") {
|
||||
try {
|
||||
script.appendChild(document.createTextNode(code))
|
||||
}
|
||||
catch (e) {
|
||||
// old IEs have funky script nodes
|
||||
script.text = code
|
||||
}
|
||||
}
|
||||
|
||||
// execute
|
||||
parent.appendChild(script)
|
||||
// avoid pollution only in head or body tags
|
||||
if (["head", "body"].indexOf(parent.tagName.toLowerCase()) > 0) {
|
||||
parent.removeChild(script)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
11
lib/events/off.js
Normal file
11
lib/events/off.js
Normal file
@@ -0,0 +1,11 @@
|
||||
var forEachEls = require("../foreach-els")
|
||||
|
||||
module.exports = function(els, events, listener, useCapture) {
|
||||
events = (typeof events === "string" ? events.split(" ") : events)
|
||||
|
||||
events.forEach(function(e) {
|
||||
forEachEls(els, function(el) {
|
||||
el.removeEventListener(e, listener, useCapture)
|
||||
})
|
||||
})
|
||||
}
|
||||
11
lib/events/on.js
Normal file
11
lib/events/on.js
Normal file
@@ -0,0 +1,11 @@
|
||||
var forEachEls = require("../foreach-els")
|
||||
|
||||
module.exports = function(els, events, listener, useCapture) {
|
||||
events = (typeof events === "string" ? events.split(" ") : events)
|
||||
|
||||
events.forEach(function(e) {
|
||||
forEachEls(els, function(el) {
|
||||
el.addEventListener(e, listener, useCapture)
|
||||
})
|
||||
})
|
||||
}
|
||||
31
lib/events/trigger.js
Normal file
31
lib/events/trigger.js
Normal file
@@ -0,0 +1,31 @@
|
||||
var forEachEls = require("../foreach-els")
|
||||
|
||||
module.exports = function(els, events, opts) {
|
||||
events = (typeof events === "string" ? events.split(" ") : events)
|
||||
|
||||
events.forEach(function(e) {
|
||||
var event
|
||||
event = document.createEvent("HTMLEvents")
|
||||
event.initEvent(e, true, true)
|
||||
event.eventName = e
|
||||
if (opts) {
|
||||
Object.keys(opts).forEach(function(key) {
|
||||
event[key] = opts[key]
|
||||
})
|
||||
}
|
||||
|
||||
forEachEls(els, function(el) {
|
||||
var domFix = false
|
||||
if (!el.parentNode && el !== document && el !== window) {
|
||||
// THANK YOU IE (9/10/11)
|
||||
// dispatchEvent doesn't work if the element is not in the DOM
|
||||
domFix = true
|
||||
document.body.appendChild(el)
|
||||
}
|
||||
el.dispatchEvent(event)
|
||||
if (domFix) {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
18
lib/execute-scripts.js
Normal file
18
lib/execute-scripts.js
Normal file
@@ -0,0 +1,18 @@
|
||||
var forEachEls = require("./foreach-els")
|
||||
var evalScript = require("./eval-script")
|
||||
// Finds and executes scripts (used for newly added elements)
|
||||
// Needed since innerHTML does not run scripts
|
||||
module.exports = function(el) {
|
||||
if (el.tagName.toLowerCase() === "script") {
|
||||
evalScript(el)
|
||||
}
|
||||
|
||||
forEachEls(el.querySelectorAll("script"), function(script) {
|
||||
if (!script.type || script.type.toLowerCase() === "text/javascript") {
|
||||
if (script.parentNode) {
|
||||
script.parentNode.removeChild(script)
|
||||
}
|
||||
evalScript(script)
|
||||
}
|
||||
})
|
||||
}
|
||||
9
lib/foreach-els.js
Normal file
9
lib/foreach-els.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/* global HTMLCollection: true */
|
||||
|
||||
module.exports = function(els, fn, context) {
|
||||
if (els instanceof HTMLCollection || els instanceof NodeList || els instanceof Array) {
|
||||
return Array.prototype.forEach.call(els, fn, context)
|
||||
}
|
||||
// assume simple DOM element
|
||||
return fn.call(context, els)
|
||||
}
|
||||
8
lib/foreach-selectors.js
Normal file
8
lib/foreach-selectors.js
Normal file
@@ -0,0 +1,8 @@
|
||||
var forEachEls = require("./foreach-els")
|
||||
|
||||
module.exports = function(selectors, cb, context, DOMcontext) {
|
||||
DOMcontext = DOMcontext || document
|
||||
selectors.forEach(function(selector) {
|
||||
forEachEls(DOMcontext.querySelectorAll(selector), cb, context)
|
||||
})
|
||||
}
|
||||
8
lib/is-supported.js
Normal file
8
lib/is-supported.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = function() {
|
||||
// Borrowed wholesale from https://github.com/defunkt/jquery-pjax
|
||||
return window.history &&
|
||||
window.history.pushState &&
|
||||
window.history.replaceState &&
|
||||
// pushState isn’t reliable on iOS until 5.
|
||||
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)
|
||||
}
|
||||
95
lib/proto/attach-form.js
Normal file
95
lib/proto/attach-form.js
Normal file
@@ -0,0 +1,95 @@
|
||||
var on = require("../events/on")
|
||||
var clone = require("../clone")
|
||||
|
||||
var attrClick = "data-pjax-click-state"
|
||||
|
||||
var formAction = function(el, event) {
|
||||
// Since loadUrl modifies options and we may add our own modifications below,
|
||||
// clone it so the changes don't persist
|
||||
var options = clone(this.options)
|
||||
|
||||
// Initialize requestOptions
|
||||
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", 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 (options.currentUrlFullReload) {
|
||||
el.setAttribute(attrClick, "reload")
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
var paramObject = []
|
||||
for (var elementKey in el.elements) {
|
||||
var element = el.elements[elementKey]
|
||||
// jscs:disable disallowImplicitTypeConversion
|
||||
if (!!element.name && element.attributes !== undefined && element.tagName.toLowerCase() !== "button") {
|
||||
// jscs:enable disallowImplicitTypeConversion
|
||||
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("&")
|
||||
|
||||
options.requestOptions.requestPayload = paramObject
|
||||
options.requestOptions.requestPayloadString = paramsString
|
||||
|
||||
el.setAttribute(attrClick, "submit")
|
||||
|
||||
options.triggerElement = el
|
||||
this.loadUrl(virtLinkElement.href, 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))
|
||||
}
|
||||
96
lib/proto/attach-link.js
Normal file
96
lib/proto/attach-link.js
Normal file
@@ -0,0 +1,96 @@
|
||||
var on = require("../events/on")
|
||||
var clone = require("../clone")
|
||||
|
||||
var attrClick = "data-pjax-click-state"
|
||||
var attrKey = "data-pjax-keyup-state"
|
||||
|
||||
var linkAction = function(el, event) {
|
||||
// Since loadUrl modifies options and we may add our own modifications below,
|
||||
// clone it so the changes don't persist
|
||||
var options = clone(this.options)
|
||||
|
||||
// Initialize requestOptions since loadUrl expects it to be an object
|
||||
options.requestOptions = {}
|
||||
|
||||
// Don’t break browser special behavior on links (like page in new window)
|
||||
if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
||||
el.setAttribute(attrClick, "modifier")
|
||||
return
|
||||
}
|
||||
|
||||
// we do test on href now to prevent unexpected behavior if for some reason
|
||||
// user have href that can be dynamically updated
|
||||
|
||||
// Ignore external links.
|
||||
if (el.protocol !== window.location.protocol || el.host !== window.location.host) {
|
||||
el.setAttribute(attrClick, "external")
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore click if we are on an anchor on the same page
|
||||
if (el.pathname === window.location.pathname && el.hash.length > 0) {
|
||||
el.setAttribute(attrClick, "anchor-present")
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore anchors on the same page (keep native behavior)
|
||||
if (el.hash && el.href.replace(el.hash, "") === window.location.href.replace(location.hash, "")) {
|
||||
el.setAttribute(attrClick, "anchor")
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore empty anchor "foo.html#"
|
||||
if (el.href === window.location.href.split("#")[0] + "#") {
|
||||
el.setAttribute(attrClick, "anchor-empty")
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
// don’t do "nothing" if user try to reload the page by clicking the same link twice
|
||||
if (
|
||||
this.options.currentUrlFullReload &&
|
||||
el.href === window.location.href.split("#")[0]
|
||||
) {
|
||||
el.setAttribute(attrClick, "reload")
|
||||
this.reload()
|
||||
return
|
||||
}
|
||||
|
||||
el.setAttribute(attrClick, "load")
|
||||
|
||||
options.triggerElement = el
|
||||
this.loadUrl(el.href, options)
|
||||
}
|
||||
|
||||
var isDefaultPrevented = function(event) {
|
||||
return event.defaultPrevented || event.returnValue === false
|
||||
}
|
||||
|
||||
module.exports = function(el) {
|
||||
var that = this
|
||||
|
||||
on(el, "click", function(event) {
|
||||
if (isDefaultPrevented(event)) {
|
||||
return
|
||||
}
|
||||
|
||||
linkAction.call(that, el, event)
|
||||
})
|
||||
|
||||
on(el, "keyup", function(event) {
|
||||
if (isDefaultPrevented(event)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Don’t break browser special behavior on links (like page in new window)
|
||||
if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
||||
el.setAttribute(attrKey, "modifier")
|
||||
return
|
||||
}
|
||||
|
||||
if (event.keyCode === 13) {
|
||||
linkAction.call(that, el, event)
|
||||
}
|
||||
}.bind(this))
|
||||
}
|
||||
11
lib/proto/log.js
Normal file
11
lib/proto/log.js
Normal file
@@ -0,0 +1,11 @@
|
||||
module.exports = function() {
|
||||
if (this.options.debug && console) {
|
||||
if (typeof console.log === "function") {
|
||||
console.log.apply(console, arguments)
|
||||
}
|
||||
// IE is weird
|
||||
else if (console.log) {
|
||||
console.log(arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
20
lib/proto/parse-element.js
Normal file
20
lib/proto/parse-element.js
Normal file
@@ -0,0 +1,20 @@
|
||||
module.exports = function(el) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case "a":
|
||||
// only attach link if el does not already have link attached
|
||||
if (!el.hasAttribute("data-pjax-click-state")) {
|
||||
this.attachLink(el)
|
||||
}
|
||||
break
|
||||
|
||||
case "form":
|
||||
// only attach link if el does not already have link attached
|
||||
if (!el.hasAttribute("data-pjax-click-state")) {
|
||||
this.attachForm(el)
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
throw "Pjax can only be applied on <a> or <form> submit"
|
||||
}
|
||||
}
|
||||
40
lib/proto/parse-options.js
Normal file
40
lib/proto/parse-options.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* global _gaq: true, ga: true */
|
||||
|
||||
var defaultSwitches = require("../switches")
|
||||
|
||||
module.exports = function(options) {
|
||||
options = options || {}
|
||||
options.elements = options.elements || "a[href], form[action]"
|
||||
options.selectors = options.selectors || ["title", ".js-Pjax"]
|
||||
options.switches = options.switches || {}
|
||||
options.switchesOptions = options.switchesOptions || {}
|
||||
options.history = options.history || true
|
||||
options.analytics = (typeof options.analytics === "function" || options.analytics === false) ?
|
||||
options.analytics :
|
||||
function() {
|
||||
if (window._gaq) {
|
||||
_gaq.push(["_trackPageview"])
|
||||
}
|
||||
if (window.ga) {
|
||||
ga("send", "pageview", {page: location.pathname, title: document.title})
|
||||
}
|
||||
}
|
||||
options.scrollTo = (typeof options.scrollTo === "undefined") ? 0 : options.scrollTo
|
||||
options.scrollRestoration = (typeof options.scrollRestoration !== "undefined") ? options.scrollRestoration : true
|
||||
options.cacheBust = (typeof options.cacheBust === "undefined") ? true : options.cacheBust
|
||||
options.debug = options.debug || false
|
||||
options.timeout = options.timeout || 0
|
||||
options.currentUrlFullReload = (typeof options.currentUrlFullReload === "undefined") ? false : options.currentUrlFullReload
|
||||
|
||||
// We can’t replace body.outerHTML or head.outerHTML.
|
||||
// It creates a bug where a new body or head are created in the DOM.
|
||||
// If you set head.outerHTML, a new body tag is appended, so the DOM has 2 body nodes, and vice versa
|
||||
if (!options.switches.head) {
|
||||
options.switches.head = defaultSwitches.switchElementsAlt
|
||||
}
|
||||
if (!options.switches.body) {
|
||||
options.switches.body = defaultSwitches.switchElementsAlt
|
||||
}
|
||||
|
||||
this.options = options
|
||||
}
|
||||
46
lib/send-request.js
Normal file
46
lib/send-request.js
Normal file
@@ -0,0 +1,46 @@
|
||||
module.exports = function(location, options, callback) {
|
||||
options = options || {}
|
||||
var requestMethod = options.requestMethod || "GET"
|
||||
var requestPayload = options.requestPayloadString || null
|
||||
var request = new XMLHttpRequest()
|
||||
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState === 4) {
|
||||
if (request.status === 200) {
|
||||
callback(request.responseText, request)
|
||||
}
|
||||
else {
|
||||
callback(null, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = function(e) {
|
||||
console.log(e)
|
||||
callback(null, request)
|
||||
}
|
||||
|
||||
request.ontimeout = function() {
|
||||
callback(null, request)
|
||||
}
|
||||
|
||||
// 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.timeout = options.timeout
|
||||
request.setRequestHeader("X-Requested-With", "XMLHttpRequest")
|
||||
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
|
||||
}
|
||||
37
lib/switches-selectors.js
Normal file
37
lib/switches-selectors.js
Normal file
@@ -0,0 +1,37 @@
|
||||
var forEachEls = require("./foreach-els")
|
||||
|
||||
var defaultSwitches = require("./switches")
|
||||
|
||||
module.exports = function(switches, switchesOptions, selectors, fromEl, toEl, options) {
|
||||
var switchesQueue = []
|
||||
|
||||
selectors.forEach(function(selector) {
|
||||
var newEls = fromEl.querySelectorAll(selector)
|
||||
var oldEls = toEl.querySelectorAll(selector)
|
||||
if (this.log) {
|
||||
this.log("Pjax switch", selector, newEls, oldEls)
|
||||
}
|
||||
if (newEls.length !== oldEls.length) {
|
||||
throw "DOM doesn’t look the same on new loaded page: ’" + selector + "’ - new " + newEls.length + ", old " + oldEls.length
|
||||
}
|
||||
|
||||
forEachEls(newEls, function(newEl, i) {
|
||||
var oldEl = oldEls[i]
|
||||
if (this.log) {
|
||||
this.log("newEl", newEl, "oldEl", oldEl)
|
||||
}
|
||||
|
||||
var callback = (switches[selector]) ?
|
||||
switches[selector].bind(this, oldEl, newEl, options, switchesOptions[selector]) :
|
||||
defaultSwitches.outerHTML.bind(this, oldEl, newEl, options)
|
||||
|
||||
switchesQueue.push(callback)
|
||||
}, this)
|
||||
}, this)
|
||||
|
||||
this.state.numPendingSwitches = switchesQueue.length
|
||||
|
||||
switchesQueue.forEach(function(queuedSwitch) {
|
||||
queuedSwitch()
|
||||
})
|
||||
}
|
||||
109
lib/switches.js
Normal file
109
lib/switches.js
Normal file
@@ -0,0 +1,109 @@
|
||||
var on = require("./events/on.js")
|
||||
|
||||
module.exports = {
|
||||
outerHTML: function(oldEl, newEl) {
|
||||
oldEl.outerHTML = newEl.outerHTML
|
||||
this.onSwitch()
|
||||
},
|
||||
|
||||
innerHTML: function(oldEl, newEl) {
|
||||
oldEl.innerHTML = newEl.innerHTML
|
||||
oldEl.className = newEl.className
|
||||
this.onSwitch()
|
||||
},
|
||||
|
||||
switchElementsAlt: function(oldEl, newEl) {
|
||||
oldEl.innerHTML = newEl.innerHTML
|
||||
|
||||
// Copy attributes from the new element to the old one
|
||||
if (newEl.hasAttributes()) {
|
||||
var attrs = newEl.attributes
|
||||
for (var i = 0; i < attrs.length; i++) {
|
||||
oldEl.attributes.setNamedItem(attrs[i].cloneNode())
|
||||
}
|
||||
}
|
||||
|
||||
this.onSwitch()
|
||||
},
|
||||
|
||||
sideBySide: function(oldEl, newEl, options, switchOptions) {
|
||||
var forEach = Array.prototype.forEach
|
||||
var elsToRemove = []
|
||||
var elsToAdd = []
|
||||
var fragToAppend = document.createDocumentFragment()
|
||||
var animationEventNames = "animationend webkitAnimationEnd MSAnimationEnd oanimationend"
|
||||
var animatedElsNumber = 0
|
||||
var sexyAnimationEnd = function(e) {
|
||||
if (e.target !== e.currentTarget) {
|
||||
// end triggered by an animation on a child
|
||||
return
|
||||
}
|
||||
|
||||
animatedElsNumber--
|
||||
if (animatedElsNumber <= 0 && elsToRemove) {
|
||||
elsToRemove.forEach(function(el) {
|
||||
// browsing quickly can make the el
|
||||
// already removed by last page update ?
|
||||
if (el.parentNode) {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
})
|
||||
|
||||
elsToAdd.forEach(function(el) {
|
||||
el.className = el.className.replace(el.getAttribute("data-pjax-classes"), "")
|
||||
el.removeAttribute("data-pjax-classes")
|
||||
})
|
||||
|
||||
elsToAdd = null // free memory
|
||||
elsToRemove = null // free memory
|
||||
|
||||
// this is to trigger some repaint (example: picturefill)
|
||||
this.onSwitch()
|
||||
}
|
||||
}.bind(this)
|
||||
|
||||
switchOptions = switchOptions || {}
|
||||
|
||||
forEach.call(oldEl.childNodes, function(el) {
|
||||
elsToRemove.push(el)
|
||||
if (el.classList && !el.classList.contains("js-Pjax-remove")) {
|
||||
// for fast switch, clean element that just have been added, & not cleaned yet.
|
||||
if (el.hasAttribute("data-pjax-classes")) {
|
||||
el.className = el.className.replace(el.getAttribute("data-pjax-classes"), "")
|
||||
el.removeAttribute("data-pjax-classes")
|
||||
}
|
||||
el.classList.add("js-Pjax-remove")
|
||||
if (switchOptions.callbacks && switchOptions.callbacks.removeElement) {
|
||||
switchOptions.callbacks.removeElement(el)
|
||||
}
|
||||
if (switchOptions.classNames) {
|
||||
el.className += " " + switchOptions.classNames.remove + " " + (options.backward ? switchOptions.classNames.backward : switchOptions.classNames.forward)
|
||||
}
|
||||
animatedElsNumber++
|
||||
on(el, animationEventNames, sexyAnimationEnd, true)
|
||||
}
|
||||
})
|
||||
|
||||
forEach.call(newEl.childNodes, function(el) {
|
||||
if (el.classList) {
|
||||
var addClasses = ""
|
||||
if (switchOptions.classNames) {
|
||||
addClasses = " js-Pjax-add " + switchOptions.classNames.add + " " + (options.backward ? switchOptions.classNames.forward : switchOptions.classNames.backward)
|
||||
}
|
||||
if (switchOptions.callbacks && switchOptions.callbacks.addElement) {
|
||||
switchOptions.callbacks.addElement(el)
|
||||
}
|
||||
el.className += addClasses
|
||||
el.setAttribute("data-pjax-classes", addClasses)
|
||||
elsToAdd.push(el)
|
||||
fragToAppend.appendChild(el)
|
||||
animatedElsNumber++
|
||||
on(el, animationEventNames, sexyAnimationEnd, true)
|
||||
}
|
||||
})
|
||||
|
||||
// pass all className of the parent
|
||||
oldEl.className = newEl.className
|
||||
oldEl.appendChild(fragToAppend)
|
||||
}
|
||||
}
|
||||
8
lib/uniqueid.js
Normal file
8
lib/uniqueid.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = (function() {
|
||||
var counter = 0
|
||||
return function() {
|
||||
var id = ("pjax" + (new Date().getTime())) + "_" + counter
|
||||
counter++
|
||||
return id
|
||||
}
|
||||
})()
|
||||
12
lib/util/contains.js
Normal file
12
lib/util/contains.js
Normal file
@@ -0,0 +1,12 @@
|
||||
module.exports = function contains(doc, selectors, el) {
|
||||
for (var i = 0; i < selectors.length; i++) {
|
||||
var selectedEls = doc.querySelectorAll(selectors[i])
|
||||
for (var j = 0; j < selectedEls.length; j++) {
|
||||
if (selectedEls[j].contains(el)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
1
lib/util/noop.js
Normal file
1
lib/util/noop.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = function() {}
|
||||
56
package.json
56
package.json
@@ -1,39 +1,55 @@
|
||||
{
|
||||
"name": "pjax",
|
||||
"version": "0.1.4",
|
||||
"description": "Easily enable fast Ajax navigation on any website (using pushState + xhr)",
|
||||
"version": "0.2.5",
|
||||
"description": "Easily enable fast AJAX navigation on any website (using pushState + XHR)",
|
||||
"keywords": [
|
||||
"pjax",
|
||||
"push",
|
||||
"state",
|
||||
"pushstate",
|
||||
"ajax",
|
||||
"navigation",
|
||||
"transition",
|
||||
"animation"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MoOx/pjax.git"
|
||||
},
|
||||
"homepage": "https://github.com/MoOx/pjax",
|
||||
"bugs": {
|
||||
"url": "https://github.com/MoOx/pjax/issues"
|
||||
},
|
||||
"repository": "https://github.com/MoOx/pjax.git",
|
||||
"author": "Maxime Thirouin",
|
||||
"contributors": [
|
||||
"BehindTheMath",
|
||||
"Robin North (http://robinnorth.co.uk)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "src/pjax.js",
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
"CHANGELOG.md",
|
||||
"LICENSE",
|
||||
"src"
|
||||
"index.js",
|
||||
"lib",
|
||||
"pjax.js",
|
||||
"pjax.min.js"
|
||||
],
|
||||
"devDependencies": {
|
||||
"jscs": "^1.6.2",
|
||||
"browserify": "^15.0.0",
|
||||
"jscs": "^3.0.7",
|
||||
"jsdom": "^11.5.1",
|
||||
"jsdom-global": "^3.0.2",
|
||||
"jshint": "^2.5.6",
|
||||
"postcss": "^2.2.5"
|
||||
"npmpub": "^3.1.0",
|
||||
"nyc": "^11.4.1",
|
||||
"opn-cli": "^3.1.0",
|
||||
"serve": "^6.4.4",
|
||||
"tap-nyc": "^1.0.3",
|
||||
"tap-spec": "^4.1.1",
|
||||
"tape": "^4.8.0",
|
||||
"uglify-js": "^3.3.8"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "jscs **/*.js && jshint . --exclude-path .gitignore",
|
||||
"test": "npm run lint"
|
||||
"lint": "jscs . && jshint . --exclude-path .gitignore",
|
||||
"standalone": "browserify index.js --standalone Pjax > pjax.js",
|
||||
"build": "npm run standalone && uglifyjs pjax.js -o pjax.min.js",
|
||||
"build-debug": "browserify index.js --debug --standalone Pjax > pjax.js",
|
||||
"tests": "tape -r ./tests/setup.js \"./tests/**/*.js\"",
|
||||
"test": "npm run lint && npm run tests | tap-spec",
|
||||
"coverage-tests": "npm run tests | tap-nyc",
|
||||
"coverage": "nyc -x \"tests/**\" npm run coverage-tests",
|
||||
"example": "opn http://localhost:3000/example/ && serve -p 3000 .",
|
||||
"prepublish": "npm run build",
|
||||
"release": "npmpub"
|
||||
}
|
||||
}
|
||||
|
||||
615
src/pjax.js
615
src/pjax.js
@@ -1,615 +0,0 @@
|
||||
;(function(root, factory) {
|
||||
if (typeof exports === "object") {
|
||||
// CommonJS
|
||||
module.exports = factory()
|
||||
}
|
||||
else if (typeof define === "function" && define.amd) {
|
||||
// AMD
|
||||
define([], factory)
|
||||
}
|
||||
else {
|
||||
// Global Variables
|
||||
root.Pjax = factory()
|
||||
}
|
||||
}(this, function() {
|
||||
"use strict";
|
||||
|
||||
function newUid() {
|
||||
return (new Date().getTime())
|
||||
}
|
||||
|
||||
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.currentUrlFullReload = this.options.currentUrlFullReload || false
|
||||
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 can’t 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)
|
||||
|
||||
Pjax.on(window, "popstate", function(st) {
|
||||
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))
|
||||
|
||||
}
|
||||
|
||||
// make internal methods public
|
||||
Pjax.isSupported = function() {
|
||||
// Borrowed wholesale from https://github.com/defunkt/jquery-pjax
|
||||
return window.history &&
|
||||
window.history.pushState &&
|
||||
window.history.replaceState &&
|
||||
// pushState isn’t reliable on iOS until 5.
|
||||
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)
|
||||
}
|
||||
|
||||
Pjax.forEachEls = function(els, fn, context) {
|
||||
if (els instanceof HTMLCollection || els instanceof NodeList) {
|
||||
return Array.prototype.forEach.call(els, fn, context)
|
||||
}
|
||||
// assume simple dom element
|
||||
fn.call(context, els)
|
||||
}
|
||||
|
||||
Pjax.on = function(els, events, listener, useCapture) {
|
||||
events = (typeof events === "string" ? events.split(" ") : events)
|
||||
|
||||
events.forEach(function(e) {
|
||||
Pjax.forEachEls(els, function(el) {
|
||||
el.addEventListener(e, listener, useCapture)
|
||||
})
|
||||
}, this)
|
||||
}
|
||||
|
||||
Pjax.off = function(els, events, listener, useCapture) {
|
||||
events = (typeof events === "string" ? events.split(" ") : events)
|
||||
|
||||
events.forEach(function(e) {
|
||||
Pjax.forEachEls(els, function(el) {
|
||||
el.removeEventListener(e, listener, useCapture)
|
||||
})
|
||||
}, this)
|
||||
}
|
||||
|
||||
Pjax.trigger = function(els, events) {
|
||||
events = (typeof events === "string" ? events.split(" ") : events)
|
||||
|
||||
events.forEach(function(e) {
|
||||
var event
|
||||
if (document.createEvent) {
|
||||
event = document.createEvent("HTMLEvents")
|
||||
event.initEvent(e, true, true)
|
||||
}
|
||||
else {
|
||||
event = document.createEventObject()
|
||||
event.eventType = e
|
||||
}
|
||||
|
||||
event.eventName = e
|
||||
|
||||
if (document.createEvent) {
|
||||
Pjax.forEachEls(els, function(el) {
|
||||
el.dispatchEvent(event)
|
||||
})
|
||||
}
|
||||
else {
|
||||
Pjax.forEachEls(els, function(el) {
|
||||
el.fireEvent("on" + event.eventType, event)
|
||||
})
|
||||
}
|
||||
}, this)
|
||||
}
|
||||
|
||||
Pjax.clone = function(obj) {
|
||||
if (null === obj || "object" != typeof obj) {
|
||||
return obj
|
||||
}
|
||||
var copy = obj.constructor()
|
||||
for (var attr in obj) {
|
||||
if (obj.hasOwnProperty(attr)) {
|
||||
copy[attr] = obj[attr]
|
||||
}
|
||||
}
|
||||
return copy
|
||||
}
|
||||
|
||||
// Finds and executes scripts (used for newly added elements)
|
||||
// Needed since innerHTML does not run scripts
|
||||
Pjax.executeScripts = function(el) {
|
||||
// console.log("going to execute scripts for ", el)
|
||||
Pjax.forEachEls(el.querySelectorAll("script"), function(script) {
|
||||
if (!script.type || script.type.toLowerCase() === "text/javascript") {
|
||||
if (script.parentNode) {
|
||||
script.parentNode.removeChild(script)
|
||||
}
|
||||
Pjax.evalScript(script)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Pjax.evalScript = function(el) {
|
||||
// console.log("going to execute script", el)
|
||||
|
||||
var code = (el.text || el.textContent || el.innerHTML || "")
|
||||
, head = document.querySelector("head") || document.documentElement
|
||||
, script = document.createElement("script")
|
||||
|
||||
if (code.match("document.write")) {
|
||||
if (console && console.log) {
|
||||
console.log("Script contains document.write. Can’t be executed correctly. Code skipped ", el)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
script.type = "text/javascript"
|
||||
try {
|
||||
script.appendChild(document.createTextNode(code))
|
||||
}
|
||||
catch (e) {
|
||||
// old IEs have funky script nodes
|
||||
script.text = code
|
||||
}
|
||||
|
||||
// execute
|
||||
head.insertBefore(script, head.firstChild)
|
||||
head.removeChild(script) // avoid pollution
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
Pjax.prototype = {
|
||||
log: function() {
|
||||
if (this.options.debug && console) {
|
||||
if (typeof console.log === "function") {
|
||||
console.log.apply(console, arguments);
|
||||
}
|
||||
// ie is weird
|
||||
else if (console.log) {
|
||||
console.log(arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
, getElements: function(el) {
|
||||
return el.querySelectorAll(this.options.elements)
|
||||
}
|
||||
|
||||
, parseDOM: function(el) {
|
||||
Pjax.forEachEls(this.getElements(el), function(el) {
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case "a": this.attachLink(el)
|
||||
break
|
||||
case "form":
|
||||
// todo
|
||||
this.log("Pjax doesnt support <form> yet. TODO :)")
|
||||
break
|
||||
default:
|
||||
throw "Pjax can only be applied on <a> or <form> submit"
|
||||
}
|
||||
}, this)
|
||||
}
|
||||
|
||||
, attachLink: function(el) {
|
||||
Pjax.on(el, "click", function(event) {
|
||||
//var el = event.currentTarget
|
||||
|
||||
// Don’t break browser special behavior on links (like page in new window)
|
||||
if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Ignore external links.
|
||||
if (el.protocol !== window.location.protocol || el.host !== window.location.host) {
|
||||
return -2
|
||||
}
|
||||
|
||||
// Ignore anchors on the same page
|
||||
if (el.pathname === location.pathname && el.hash.length > 0) {
|
||||
return -3
|
||||
}
|
||||
|
||||
// Ignore anchors on the same page
|
||||
if (el.hash && el.href.replace(el.hash, "") === location.href.replace(location.hash, "")) {
|
||||
return -4
|
||||
}
|
||||
|
||||
// Ignore empty anchor "foo.html#"
|
||||
if (el.href === location.href + "#") {
|
||||
return -5
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
if (this.options.currentUrlFullReload) {
|
||||
if (el.href === window.location.href) {
|
||||
window.location.reload()
|
||||
return -6
|
||||
}
|
||||
}
|
||||
|
||||
this.loadUrl(el.href, Pjax.clone(this.options))
|
||||
}.bind(this))
|
||||
|
||||
Pjax.on(el, "keyup", function(event) {
|
||||
this.log("pjax todo")
|
||||
// todo handle a link hitted by keyboard (enter/space) when focus is on it
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
, forEachSelectors: function(cb, context, DOMcontext) {
|
||||
DOMcontext = DOMcontext || document
|
||||
this.options.selectors.forEach(function(selector) {
|
||||
Pjax.forEachEls(DOMcontext.querySelectorAll(selector), cb, context)
|
||||
})
|
||||
}
|
||||
|
||||
, switchSelectors: function(selectors, fromEl, toEl, options) {
|
||||
selectors.forEach(function(selector) {
|
||||
var newEls = fromEl.querySelectorAll(selector)
|
||||
var oldEls = toEl.querySelectorAll(selector)
|
||||
this.log("Pjax switch", selector, newEls, oldEls)
|
||||
if (newEls.length !== oldEls.length) {
|
||||
// Pjax.forEachEls(newEls, function(el) {
|
||||
// this.log("newEl", el, el.outerHTML)
|
||||
// }, this)
|
||||
// Pjax.forEachEls(oldEls, function(el) {
|
||||
// this.log("oldEl", el, el.outerHTML)
|
||||
// }, this)
|
||||
throw "DOM doesn’t look the same on new loaded page: ’" + selector + "’ - new " + newEls.length + ", old " + oldEls.length
|
||||
}
|
||||
|
||||
Pjax.forEachEls(newEls, function(newEl, i) {
|
||||
var oldEl = oldEls[i]
|
||||
this.log("newEl", newEl, "oldEl", oldEl)
|
||||
if (this.options.switches[selector]) {
|
||||
this.options.switches[selector].bind(this)(oldEl, newEl, options, this.options.switchesOptions[selector])
|
||||
}
|
||||
else {
|
||||
Pjax.switches.outerHTML.bind(this)(oldEl, newEl, options)
|
||||
}
|
||||
}, this)
|
||||
}, this)
|
||||
}
|
||||
|
||||
// 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() {
|
||||
Pjax.trigger(window, "resize scroll")
|
||||
}
|
||||
|
||||
, 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>)
|
||||
, htmlRegex = /<html[^>]+>/gi
|
||||
, htmlAttribsRegex = /\s?[a-z:]+(?:\=(?:\'|\")[^\'\">]+(?:\'|\"))*/gi
|
||||
, 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)
|
||||
// try {
|
||||
this.switchSelectors(this.options.selectors, tmpEl, document, options)
|
||||
|
||||
// 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) {
|
||||
Pjax.forEachEls(document.querySelectorAll(selector), function(el) {
|
||||
Pjax.executeScripts(el)
|
||||
})
|
||||
})
|
||||
// }
|
||||
// catch(e) {
|
||||
// if (this.options.debug) {
|
||||
// this.log("Pjax switch fail: ", e)
|
||||
// }
|
||||
// this.switchFallback(tmpEl, document)
|
||||
// }
|
||||
}
|
||||
|
||||
, doRequest: function(location, callback) {
|
||||
var request = new XMLHttpRequest()
|
||||
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState === 4 && request.status === 200) {
|
||||
callback(request.responseText)
|
||||
}
|
||||
else if (request.readyState === 4 && (request.status === 404 || request.status === 500)){
|
||||
callback(false)
|
||||
}
|
||||
}
|
||||
|
||||
request.open("GET", location + (!/[?&]/.test(location) ? "?" : "&") + (new Date().getTime()), true)
|
||||
request.setRequestHeader("X-Requested-With", "XMLHttpRequest")
|
||||
request.send(null)
|
||||
}
|
||||
|
||||
, loadUrl: function(href, options) {
|
||||
this.log("load href", href, options)
|
||||
|
||||
Pjax.trigger(document, "pjax:send", options);
|
||||
|
||||
// Do the request
|
||||
this.doRequest(href, function(html) {
|
||||
|
||||
// Fail if unable to load HTML via AJAX
|
||||
if (html === false) {
|
||||
Pjax.trigger(document,"pjax:complete pjax:error", options)
|
||||
|
||||
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({
|
||||
"url": window.location.href
|
||||
, "title": document.title
|
||||
, "uid": this.maxUid
|
||||
}
|
||||
, document.title)
|
||||
}
|
||||
|
||||
// Update browser history
|
||||
this.lastUid = this.maxUid = newUid()
|
||||
window.history.pushState({
|
||||
"url": href
|
||||
, "title": options.title
|
||||
, "uid": this.maxUid
|
||||
}
|
||||
, options.title
|
||||
, href)
|
||||
}
|
||||
|
||||
this.forEachSelectors(function(el) {
|
||||
this.parseDOM(el)
|
||||
}, this)
|
||||
|
||||
// Fire Events
|
||||
Pjax.trigger(document,"pjax:complete pjax:success", options)
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
Pjax.switches = {
|
||||
outerHTML: function(oldEl, newEl, options) {
|
||||
oldEl.outerHTML = newEl.outerHTML
|
||||
this.onSwitch()
|
||||
}
|
||||
|
||||
, innerHTML: function(oldEl, newEl, options) {
|
||||
oldEl.innerHTML = newEl.innerHTML
|
||||
oldEl.className = newEl.className
|
||||
this.onSwitch()
|
||||
}
|
||||
|
||||
, sideBySide: function(oldEl, newEl, options, switchOptions) {
|
||||
var forEach = Array.prototype.forEach
|
||||
, elsToRemove = []
|
||||
, elsToAdd = []
|
||||
, fragToAppend = document.createDocumentFragment()
|
||||
// height transition are shitty on safari
|
||||
// so commented for now (until I found something ?)
|
||||
// , relevantHeight = 0
|
||||
, animationEventNames = "animationend webkitAnimationEnd MSAnimationEnd oanimationend"
|
||||
, animatedElsNumber = 0
|
||||
, sexyAnimationEnd = function(e) {
|
||||
if (e.target != e.currentTarget) {
|
||||
// end triggered by an animation on a child
|
||||
return
|
||||
}
|
||||
|
||||
animatedElsNumber--
|
||||
if (animatedElsNumber <= 0 && elsToRemove) {
|
||||
elsToRemove.forEach(function(el) {
|
||||
// browsing quickly can make the el
|
||||
// already removed by last page update ?
|
||||
if (el.parentNode) {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
})
|
||||
|
||||
elsToAdd.forEach(function(el) {
|
||||
el.className = el.className.replace(el.getAttribute("data-pjax-classes"), "")
|
||||
el.removeAttribute("data-pjax-classes")
|
||||
// Pjax.off(el, animationEventNames, sexyAnimationEnd, true)
|
||||
})
|
||||
|
||||
elsToAdd = null // free memory
|
||||
elsToRemove = null // free memory
|
||||
|
||||
// assume the height is now useless (avoid bug since there is overflow hidden on the parent)
|
||||
// oldEl.style.height = "auto"
|
||||
|
||||
// this is to trigger some repaint (example: picturefill)
|
||||
this.onSwitch()
|
||||
//Pjax.trigger(window, "scroll")
|
||||
}
|
||||
}.bind(this)
|
||||
|
||||
// Force height to be able to trigger css animation
|
||||
// here we get the relevant height
|
||||
// oldEl.parentNode.appendChild(newEl)
|
||||
// relevantHeight = newEl.getBoundingClientRect().height
|
||||
// oldEl.parentNode.removeChild(newEl)
|
||||
// oldEl.style.height = oldEl.getBoundingClientRect().height + "px"
|
||||
|
||||
switchOptions = switchOptions || {}
|
||||
|
||||
forEach.call(oldEl.childNodes, function(el) {
|
||||
elsToRemove.push(el)
|
||||
if (el.classList && !el.classList.contains("js-Pjax-remove")) {
|
||||
// for fast switch, clean element that just have been added, & not cleaned yet.
|
||||
if (el.hasAttribute("data-pjax-classes")) {
|
||||
el.className = el.className.replace(el.getAttribute("data-pjax-classes"), "")
|
||||
el.removeAttribute("data-pjax-classes")
|
||||
}
|
||||
el.classList.add("js-Pjax-remove")
|
||||
if (switchOptions.callbacks && switchOptions.callbacks.removeElement) {
|
||||
switchOptions.callbacks.removeElement(el)
|
||||
}
|
||||
if (switchOptions.classNames) {
|
||||
el.className += " " + switchOptions.classNames.remove + " " + (options.backward ? switchOptions.classNames.backward : switchOptions.classNames.forward)
|
||||
}
|
||||
animatedElsNumber++
|
||||
Pjax.on(el, animationEventNames, sexyAnimationEnd, true)
|
||||
}
|
||||
})
|
||||
|
||||
forEach.call(newEl.childNodes, function(el) {
|
||||
if (el.classList) {
|
||||
var addClasses = ""
|
||||
if (switchOptions.classNames) {
|
||||
addClasses = " js-Pjax-add " + switchOptions.classNames.add + " " + (options.backward ? switchOptions.classNames.forward : switchOptions.classNames.backward)
|
||||
}
|
||||
if (switchOptions.callbacks && switchOptions.callbacks.addElement) {
|
||||
switchOptions.callbacks.addElement(el)
|
||||
}
|
||||
el.className += addClasses
|
||||
el.setAttribute("data-pjax-classes", addClasses)
|
||||
elsToAdd.push(el)
|
||||
fragToAppend.appendChild(el)
|
||||
animatedElsNumber++
|
||||
Pjax.on(el, animationEventNames, sexyAnimationEnd, true)
|
||||
}
|
||||
})
|
||||
|
||||
// pass all className of the parent
|
||||
oldEl.className = newEl.className
|
||||
oldEl.appendChild(fragToAppend)
|
||||
|
||||
// oldEl.style.height = relevantHeight + "px"
|
||||
}
|
||||
}
|
||||
|
||||
if (Pjax.isSupported()) {
|
||||
return 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
|
||||
}
|
||||
}
|
||||
|
||||
return stupidPjax
|
||||
}
|
||||
|
||||
}));
|
||||
56
tests/lib/abort-request.js
Normal file
56
tests/lib/abort-request.js
Normal file
@@ -0,0 +1,56 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var abortRequest = require("../../lib/abort-request.js")
|
||||
var sendRequest = require("../../lib/send-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 aborting xhr request", function(t) {
|
||||
var requestCacheBust = sendRequest.bind({
|
||||
options: {
|
||||
cacheBust: true
|
||||
}
|
||||
})
|
||||
|
||||
t.test("- pending request is aborted", function(t) {
|
||||
var r = requestCacheBust("https://httpbin.org/delay/10", {}, function() {
|
||||
t.fail("xhr was not aborted")
|
||||
})
|
||||
t.equal(r.readyState, 1, "xhr readyState is '1' (SENT)")
|
||||
abortRequest(r)
|
||||
t.equal(r.readyState, 0, "xhr readyState is '0' (ABORTED)")
|
||||
t.equal(r.status, 0, "xhr HTTP status is '0' (ABORTED)")
|
||||
t.equal(r.responseText, "", "xhr response is empty")
|
||||
t.end()
|
||||
})
|
||||
t.test("- request is not aborted if it has already completed", function(t) {
|
||||
var r = requestCacheBust("https://httpbin.org/get", {}, function() {
|
||||
abortRequest(r)
|
||||
t.equal(r.readyState, 4, "xhr readyState is '4' (DONE)")
|
||||
t.equal(r.status, 200, "xhr HTTP status is '200' (OK)")
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
t.test("- request is not aborted if it is undefined", function(t) {
|
||||
var r
|
||||
try {
|
||||
abortRequest(r)
|
||||
}
|
||||
catch (e) {
|
||||
t.fail("aborting an undefined request threw an error")
|
||||
}
|
||||
t.equal(typeof r, "undefined", "undefined xhr was ignored")
|
||||
t.end()
|
||||
})
|
||||
t.end()
|
||||
})
|
||||
17
tests/lib/clone.js
Normal file
17
tests/lib/clone.js
Normal file
@@ -0,0 +1,17 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var clone = require("../../lib/clone")
|
||||
|
||||
tape("test clone method", function(t) {
|
||||
var obj = {one: 1, two: 2}
|
||||
var cloned = clone(obj)
|
||||
|
||||
t.notEqual(obj, cloned, "cloned object isn't the object")
|
||||
|
||||
t.same(obj, cloned, "cloned object have the same values than object")
|
||||
|
||||
cloned.tree = 3
|
||||
t.notSame(obj, cloned, "modified cloned object haven't the same values than object")
|
||||
|
||||
t.end()
|
||||
})
|
||||
23
tests/lib/eval-scripts.js
Normal file
23
tests/lib/eval-scripts.js
Normal file
@@ -0,0 +1,23 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var evalScript = require("../../lib/eval-script")
|
||||
|
||||
tape("test evalScript method", function(t) {
|
||||
document.body.className = ""
|
||||
|
||||
var script = document.createElement("script")
|
||||
script.innerHTML = "document.body.className = 'executed'"
|
||||
|
||||
t.equal(document.body.className, "", "script hasn't been executed yet")
|
||||
|
||||
evalScript(script)
|
||||
t.equal(document.body.className, "executed", "script has been properly executed")
|
||||
|
||||
script.innerHTML = "document.write('failure')"
|
||||
document.body.text = "document.write hasn't been executed"
|
||||
var bodyText = document.body.text
|
||||
evalScript(script)
|
||||
t.equal(document.body.text, bodyText, "document.write hasn't been executed")
|
||||
|
||||
t.end()
|
||||
})
|
||||
110
tests/lib/events.js
Normal file
110
tests/lib/events.js
Normal file
@@ -0,0 +1,110 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var on = require("../../lib/events/on")
|
||||
var off = require("../../lib/events/off")
|
||||
var trigger = require("../../lib/events/trigger")
|
||||
|
||||
var el = document.createElement("div")
|
||||
var el2 = document.createElement("span")
|
||||
var els = [el, el2]
|
||||
|
||||
var classCb = function() {
|
||||
this.className += "on"
|
||||
}
|
||||
var attrCb = function() {
|
||||
this.setAttribute("data-state", this.getAttribute("data-state") + "ON")
|
||||
}
|
||||
|
||||
tape("test events on/off/trigger for one element, one event", function(t) {
|
||||
el.className = ""
|
||||
on(el, "click", classCb)
|
||||
trigger(el, "click")
|
||||
t.equal(el.className, "on", "attached callback has been fired properly")
|
||||
|
||||
el.className = "off"
|
||||
off(el, "click", classCb)
|
||||
trigger(el, "click")
|
||||
t.equal(el.className, "off", "triggered event didn't fire detached callback")
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tape("test events on/off/trigger for multiple elements, one event", function(t) {
|
||||
el.className = ""
|
||||
el2.className = ""
|
||||
|
||||
on(els, "click", classCb)
|
||||
trigger(els, "click")
|
||||
t.equal(el.className, "on", "attached callback has been fired properly on the first element")
|
||||
t.equal(el2.className, "on", "attached callback has been fired properly on the second element")
|
||||
|
||||
el.className = "off"
|
||||
el2.className = "off"
|
||||
off(els, "click", classCb)
|
||||
trigger(els, "click")
|
||||
t.equal(el.className, "off", "triggered event didn't fire detached callback on the first element")
|
||||
t.equal(el2.className, "off", "triggered event didn't fire detached callback on the second element")
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tape("test events on/off/trigger for one element, multiple events", function(t) {
|
||||
el.className = ""
|
||||
on(el, "click mouseover", classCb)
|
||||
trigger(el, "click mouseover")
|
||||
t.equal(el.className, "onon", "attached callbacks have been fired properly")
|
||||
|
||||
el.className = "off"
|
||||
off(el, "click mouseover", classCb)
|
||||
trigger(el, "click mouseover")
|
||||
t.equal(el.className, "off", "triggered events didn't fire detached callback")
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tape("test events on/off/trigger for multiple elements, multiple events", function(t) {
|
||||
el.className = ""
|
||||
el2.className = ""
|
||||
el.setAttribute("data-state", "")
|
||||
el2.setAttribute("data-state", "")
|
||||
on(els, "click mouseover", classCb)
|
||||
on(els, "resize scroll", attrCb)
|
||||
trigger(els, "click mouseover resize scroll")
|
||||
t.equal(el.className, "onon", "attached callbacks has been fired properly on the first element")
|
||||
t.equal(el.getAttribute("data-state"), "ONON", "attached callbacks has been fired properly on the first element")
|
||||
t.equal(el2.className, "onon", "attached callbacks has been fired properly on the second element")
|
||||
t.equal(el2.getAttribute("data-state"), "ONON", "attached callbacks has been fired properly on the second element")
|
||||
|
||||
el.className = "off"
|
||||
el2.className = "off"
|
||||
el.setAttribute("data-state", "off")
|
||||
el2.setAttribute("data-state", "off")
|
||||
off(els, "click mouseover", classCb)
|
||||
off(els, "resize scroll", attrCb)
|
||||
trigger(els, "click mouseover resize scroll")
|
||||
t.equal(el.className, "off", "triggered events didn't fire detached callbacks on the first element")
|
||||
t.equal(el.getAttribute("data-state"), "off", "triggered events didn't fire detached callbacks on the first element")
|
||||
t.equal(el2.className, "off", "triggered events didn't fire detached callbacks on the first element")
|
||||
t.equal(el2.getAttribute("data-state"), "off", "triggered events didn't fire detached callbacks on the first element")
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tape("test events on top level elements", function(t) {
|
||||
var el = document
|
||||
|
||||
el.className = ""
|
||||
on(el, "click", classCb)
|
||||
trigger(el, "click")
|
||||
t.equal(el.className, "on", "attached callback has been fired properly on document")
|
||||
|
||||
el = window
|
||||
|
||||
el.className = ""
|
||||
// With jsdom, the default this is global, not window, so we need to explicitly bind to window.
|
||||
on(el, "click", classCb.bind(window))
|
||||
trigger(el, "click")
|
||||
t.equal(el.className, "on", "attached callback has been fired properly on window")
|
||||
|
||||
t.end()
|
||||
})
|
||||
16
tests/lib/execute-scripts.js
Normal file
16
tests/lib/execute-scripts.js
Normal file
@@ -0,0 +1,16 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var executeScripts = require("../../lib/execute-scripts")
|
||||
|
||||
tape("test executeScripts method", function(t) {
|
||||
document.body.className = ""
|
||||
|
||||
var container = document.createElement("div")
|
||||
container.innerHTML = "<" + "script" + ">document.body.className = 'executed';</" + "script" + "><" + "script" + ">document.body.className += ' correctly';</" + "script" + ">"
|
||||
|
||||
t.equal(document.body.className, "", "script hasn't been executed yet")
|
||||
executeScripts(container)
|
||||
t.equal(document.body.className, "executed correctly", "script has been properly executed")
|
||||
|
||||
t.end()
|
||||
})
|
||||
45
tests/lib/foreach-els.js
Normal file
45
tests/lib/foreach-els.js
Normal file
@@ -0,0 +1,45 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var forEachEls = require("../../lib/foreach-els.js")
|
||||
|
||||
var div = document.createElement("div")
|
||||
var span = document.createElement("span")
|
||||
var cb = function(el) {
|
||||
el.innerHTML = "boom"
|
||||
}
|
||||
|
||||
tape("test forEachEls on one element", function(t) {
|
||||
div.innerHTML = "div tag"
|
||||
forEachEls(div, cb)
|
||||
|
||||
t.equal(div.innerHTML, "boom", "works correctly on one element")
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
||||
tape("test forEachEls on an array", function(t) {
|
||||
div.innerHTML = "div tag"
|
||||
span.innerHTML = "span tag"
|
||||
|
||||
forEachEls([div, span], cb)
|
||||
|
||||
t.equal(div.innerHTML, "boom", "works correctly on the first element of the array")
|
||||
t.equal(span.innerHTML, "boom", "works correctly on the last element of the array")
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tape("test forEachEls on a NodeList", function(t) {
|
||||
div.innerHTML = "div tag"
|
||||
span.innerHTML = "span tag"
|
||||
|
||||
var frag = document.createDocumentFragment()
|
||||
frag.appendChild(div)
|
||||
frag.appendChild(span)
|
||||
forEachEls(frag.childNodes, cb)
|
||||
|
||||
t.equal(div.innerHTML, "boom", "works correctly on the first element of the document fragment")
|
||||
t.equal(span.innerHTML, "boom", "works correctly on the last element of the document fragment")
|
||||
|
||||
t.end()
|
||||
})
|
||||
24
tests/lib/foreach-selectors.js
Normal file
24
tests/lib/foreach-selectors.js
Normal file
@@ -0,0 +1,24 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var forEachEls = require("../../lib/foreach-selectors.js")
|
||||
|
||||
var cb = function(el) {
|
||||
el.className = "modified"
|
||||
}
|
||||
|
||||
tape("test forEachSelector", function(t) {
|
||||
forEachEls(["html", "body"], cb)
|
||||
|
||||
t.equal(document.documentElement.className, "modified", "callback has been executed on first selector")
|
||||
t.equal(document.body.className, "modified", "callback has been executed on first selector")
|
||||
|
||||
document.documentElement.className = ""
|
||||
document.body.className = ""
|
||||
|
||||
forEachEls(["html", "body"], cb, null, document.documentElement)
|
||||
|
||||
t.equal(document.documentElement.className, "", "callback has not been executed on first selector when context is used")
|
||||
t.equal(document.body.className, "modified", "callback has been executed on first selector when context is used")
|
||||
|
||||
t.end()
|
||||
})
|
||||
8
tests/lib/is-supported.js
Normal file
8
tests/lib/is-supported.js
Normal file
@@ -0,0 +1,8 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var isSupported = require("../../lib/is-supported.js")
|
||||
|
||||
tape("test isSupported method", function(t) {
|
||||
t.true(isSupported(), "well, we run test on supported browser, so it should be ok here")
|
||||
t.end()
|
||||
})
|
||||
95
tests/lib/proto/attach-form.js
Normal file
95
tests/lib/proto/attach-form.js
Normal file
@@ -0,0 +1,95 @@
|
||||
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()
|
||||
})
|
||||
|
||||
tape("test options are not modified by attachForm", function(t) {
|
||||
var form = document.createElement("form")
|
||||
var options = {foo: "bar"}
|
||||
var loadUrl = function() {}
|
||||
|
||||
attachForm.call({options: options, loadUrl: loadUrl}, form)
|
||||
|
||||
form.action = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search
|
||||
form.method = "GET"
|
||||
trigger(form, "submit")
|
||||
|
||||
t.equal(1, Object.keys(options).length, "options object that is passed in should not be modified")
|
||||
t.equal("bar", options.foo, "options object that is passed in should not be modified")
|
||||
|
||||
t.end()
|
||||
})
|
||||
94
tests/lib/proto/attach-link.js
Normal file
94
tests/lib/proto/attach-link.js
Normal file
@@ -0,0 +1,94 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var on = require("../../../lib/events/on")
|
||||
var trigger = require("../../../lib/events/trigger")
|
||||
var attachLink = require("../../../lib/proto/attach-link")
|
||||
|
||||
var a = document.createElement("a")
|
||||
var attr = "data-pjax-click-state"
|
||||
var preventDefault = function(e) { e.preventDefault() }
|
||||
|
||||
tape("test attach link prototype method", function(t) {
|
||||
t.plan(7)
|
||||
|
||||
attachLink.call({
|
||||
options: {},
|
||||
reload: function() {
|
||||
t.equal(a.getAttribute(attr), "reload", "triggering exact same url reload the page")
|
||||
},
|
||||
loadUrl: function() {
|
||||
t.equal(a.getAttribute(attr), "load", "triggering a internal link actually load the page")
|
||||
}
|
||||
}, a)
|
||||
|
||||
var internalUri = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search
|
||||
|
||||
a.href = internalUri
|
||||
on(a, "click", preventDefault) // to avoid link to be open (break testing env)
|
||||
trigger(a, "click", {metaKey: true})
|
||||
t.equal(a.getAttribute(attr), "modifier", "event key modifier stop behavior")
|
||||
|
||||
a.href = "http://external.com/"
|
||||
trigger(a, "click")
|
||||
t.equal(a.getAttribute(attr), "external", "external url stop behavior")
|
||||
|
||||
a.href = internalUri + "#anchor"
|
||||
trigger(a, "click")
|
||||
t.equal(a.getAttribute(attr), "anchor-present", "internal anchor stop behavior")
|
||||
|
||||
window.location.hash = "#anchor"
|
||||
a.href = internalUri + "#another-anchor"
|
||||
trigger(a, "click")
|
||||
t.notEqual(a.getAttribute(attr), "anchor", "differents anchors stop behavior")
|
||||
window.location.hash = ""
|
||||
|
||||
a.href = internalUri + "#"
|
||||
trigger(a, "click")
|
||||
t.equal(a.getAttribute(attr), "anchor-empty", "empty anchor stop behavior")
|
||||
|
||||
a.href = internalUri
|
||||
trigger(a, "click")
|
||||
// see reload defined above
|
||||
|
||||
a.href = window.location.protocol + "//" + window.location.host + "/internal"
|
||||
trigger(a, "click")
|
||||
// see loadUrl defined above
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tape("test attach link preventDefaulted events", function(t) {
|
||||
var callbacked = false
|
||||
var a = document.createElement("a")
|
||||
|
||||
attachLink.call({
|
||||
options: {},
|
||||
loadUrl: function() {
|
||||
callbacked = true
|
||||
}
|
||||
}, a)
|
||||
|
||||
a.href = "#"
|
||||
on(a, "click", preventDefault)
|
||||
trigger(a, "click")
|
||||
t.equal(callbacked, false, "events that are preventDefaulted should not fire callback")
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tape("test options are not modified by attachLink", function(t) {
|
||||
var a = document.createElement("a")
|
||||
var options = {foo: "bar"}
|
||||
var loadUrl = function() {}
|
||||
|
||||
attachLink.call({options: options, loadUrl: loadUrl}, a)
|
||||
|
||||
a.href = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search
|
||||
|
||||
trigger(a, "click")
|
||||
|
||||
t.equal(1, Object.keys(options).length, "options object that is passed in should not be modified")
|
||||
t.equal("bar", options.foo, "options object that is passed in should not be modified")
|
||||
|
||||
t.end()
|
||||
})
|
||||
21
tests/lib/proto/parse-element.js
Normal file
21
tests/lib/proto/parse-element.js
Normal file
@@ -0,0 +1,21 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var parseElement = require("../../../lib/proto/parse-element")
|
||||
var protoMock = {
|
||||
attachLink: function() { return true },
|
||||
attachForm: function() { return true }
|
||||
}
|
||||
|
||||
tape("test parse element prototype method", function(t) {
|
||||
t.doesNotThrow(function() {
|
||||
var a = document.createElement("a")
|
||||
parseElement.call(protoMock, a)
|
||||
}, "<a> element can be parsed")
|
||||
|
||||
t.doesNotThrow(function() {
|
||||
var form = document.createElement("form")
|
||||
parseElement.call(protoMock, form)
|
||||
}, "<form> element can be parsed")
|
||||
|
||||
t.end()
|
||||
})
|
||||
49
tests/lib/proto/parse-options.js
Normal file
49
tests/lib/proto/parse-options.js
Normal file
@@ -0,0 +1,49 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var parseOptions = require("../../../lib/proto/parse-options.js")
|
||||
tape("test parse initalization options function", function(t) {
|
||||
t.test("- default options", function(t) {
|
||||
var pjax = {}
|
||||
parseOptions.call(pjax, {})
|
||||
|
||||
t.equal(pjax.options.elements, "a[href], form[action]")
|
||||
t.equal(pjax.options.selectors.length, 2, "selectors length")
|
||||
t.equal(pjax.options.selectors[0], "title")
|
||||
t.equal(pjax.options.selectors[1], ".js-Pjax")
|
||||
|
||||
t.equal(typeof pjax.options.switches, "object")
|
||||
t.equal(Object.keys(pjax.options.switches).length, 2)// head and body
|
||||
|
||||
t.equal(typeof pjax.options.switchesOptions, "object")
|
||||
t.equal(Object.keys(pjax.options.switchesOptions).length, 0)
|
||||
|
||||
t.equal(pjax.options.history, true)
|
||||
t.equal(typeof pjax.options.analytics, "function")
|
||||
t.equal(pjax.options.scrollTo, 0)
|
||||
t.equal(pjax.options.scrollRestoration, true)
|
||||
t.equal(pjax.options.cacheBust, true)
|
||||
t.equal(pjax.options.debug, false)
|
||||
t.equal(pjax.options.currentUrlFullReload, false)
|
||||
t.end()
|
||||
})
|
||||
|
||||
// verify analytics always ends up as a function even when passed not a function
|
||||
t.test("- analytics is a function", function(t) {
|
||||
var pjax = {}
|
||||
parseOptions.call(pjax, {analytics: "some string"})
|
||||
|
||||
t.deepEqual(typeof pjax.options.analytics, "function")
|
||||
t.end()
|
||||
})
|
||||
|
||||
// verify that the value false for scrollTo is not squashed
|
||||
t.test("- scrollTo remains false", function(t) {
|
||||
var pjax = {}
|
||||
parseOptions.call(pjax, {scrollTo: false})
|
||||
|
||||
t.deepEqual(pjax.options.scrollTo, false)
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
50
tests/lib/send-request.js
Normal file
50
tests/lib/send-request.js
Normal file
@@ -0,0 +1,50 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var sendRequest = require("../../lib/send-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) {
|
||||
var url = "https://httpbin.org/get"
|
||||
|
||||
t.test("- request is made, gets a result, and is cache-busted", function(t) {
|
||||
var requestCacheBust = sendRequest.bind({
|
||||
options: {
|
||||
cacheBust: true
|
||||
}
|
||||
})
|
||||
var r = requestCacheBust(url, {}, function(result) {
|
||||
t.equal(r.responseURL.indexOf("?"), url.length, "XHR URL is cache-busted when configured to be")
|
||||
try {
|
||||
result = JSON.parse(result)
|
||||
}
|
||||
catch (e) {
|
||||
t.fail("xhr doesn't get a JSON response")
|
||||
}
|
||||
t.same(typeof result, "object", "xhr request get a result")
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
t.test("- request is not cache-busted when configured not to be", function(t) {
|
||||
var requestNoCacheBust = sendRequest.bind({
|
||||
options: {
|
||||
cacheBust: false
|
||||
}
|
||||
})
|
||||
var r = requestNoCacheBust(url, {}, function() {
|
||||
t.equal(r.responseURL, url, "XHR URL is left untouched")
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
t.end()
|
||||
})
|
||||
42
tests/lib/switch-selectors.js
Normal file
42
tests/lib/switch-selectors.js
Normal file
@@ -0,0 +1,42 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var switchesSelectors = require("../../lib/switches-selectors.js")
|
||||
|
||||
// @author darylteo
|
||||
tape("test switchesSelectors", function(t) {
|
||||
// switchesSelectors relies on a higher level function callback
|
||||
// should really be passed in instead so I'll leave it here as a TODO:
|
||||
var pjax = {
|
||||
onSwitch: function() {
|
||||
console.log("Switched")
|
||||
},
|
||||
state: {}
|
||||
}
|
||||
|
||||
var tmpEl = document.implementation.createHTMLDocument()
|
||||
|
||||
// a div container is used because swapping the containers
|
||||
// will generate a new element, so things get weird
|
||||
// using "body" generates a lot of testling cruft that I don't
|
||||
// want so let's avoid that
|
||||
var container = document.createElement("div")
|
||||
container.innerHTML = "<p>Original Text</p><span>No Change</span>"
|
||||
document.body.appendChild(container)
|
||||
|
||||
var container2 = tmpEl.createElement("div")
|
||||
container2.innerHTML = "<p>New Text</p><span>New Span</span>"
|
||||
tmpEl.body.appendChild(container2)
|
||||
|
||||
switchesSelectors.bind(pjax)(
|
||||
{}, // switches
|
||||
{}, // switchesOptions
|
||||
["p"], // selectors,
|
||||
tmpEl, // fromEl
|
||||
document, // toEl,
|
||||
{} // options
|
||||
)
|
||||
|
||||
t.equals(container.innerHTML, "<p>New Text</p><span>No Change</span>", "Elements correctly switched")
|
||||
|
||||
t.end()
|
||||
})
|
||||
12
tests/lib/uniqueid.js
Normal file
12
tests/lib/uniqueid.js
Normal file
@@ -0,0 +1,12 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var uniqueid = require("../../lib/uniqueid.js")
|
||||
|
||||
tape("test uniqueid", function(t) {
|
||||
var a = uniqueid()
|
||||
var b = uniqueid()
|
||||
|
||||
t.notEqual(a,b,"Two calls to uniqueid produce different values")
|
||||
|
||||
t.end()
|
||||
})
|
||||
16
tests/lib/util/contains.js
Normal file
16
tests/lib/util/contains.js
Normal file
@@ -0,0 +1,16 @@
|
||||
var tape = require("tape")
|
||||
|
||||
var contains = require("../../../lib/util/contains.js")
|
||||
|
||||
tape("test contains function", function(t) {
|
||||
var tempDoc = document.implementation.createHTMLDocument()
|
||||
tempDoc.body.innerHTML = "<div><p id='el' class='js-Pjax'></p></div><span></span>"
|
||||
var selectors = ["div"]
|
||||
var el = tempDoc.body.querySelector("#el")
|
||||
t.equal(contains(tempDoc, selectors, el), true, "contains() returns true when a selector contains the element")
|
||||
|
||||
selectors = ["span"]
|
||||
t.equal(contains(tempDoc, selectors, el), false, "contains() returns false when the selectors do not contain the element")
|
||||
|
||||
t.end()
|
||||
})
|
||||
6
tests/setup.js
Normal file
6
tests/setup.js
Normal file
@@ -0,0 +1,6 @@
|
||||
var jsdomOptions = {
|
||||
url: "https://example.org/",
|
||||
runScripts: "dangerously"
|
||||
}
|
||||
|
||||
require("jsdom-global")("", jsdomOptions)
|
||||
Reference in New Issue
Block a user