Compare commits
209 Commits
v0.1.2
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43195c3266 | ||
|
|
480334b182 | ||
|
|
c26c223a65 | ||
|
|
3b3f4d7794 | ||
|
|
7940a6e3e5 | ||
|
|
493d56c2d0 | ||
|
|
c13149626b | ||
|
|
3c1a4b2e18 | ||
|
|
2c6506af65 | ||
|
|
fefb63ae87 | ||
|
|
52fb3bf938 | ||
|
|
6b648a7c90 | ||
|
|
03ebc657f0 | ||
|
|
6f39767cf9 | ||
|
|
03d64863c8 | ||
|
|
c36225a24c | ||
|
|
c589ab9c25 | ||
|
|
8abb21e1e9 | ||
|
|
8dbe7553b9 | ||
|
|
f639a8eae1 | ||
|
|
e49d8947f7 | ||
|
|
7d26a75fdf | ||
|
|
358b6f6836 | ||
|
|
d3447a95aa | ||
|
|
d6bf21ed22 | ||
|
|
17d8262025 | ||
|
|
75eb83dbc2 | ||
|
|
5e41a32cf4 | ||
|
|
a2982cfcba | ||
|
|
2166866967 | ||
|
|
c1e5bf9c78 | ||
|
|
333ee344f4 | ||
|
|
05fa833169 | ||
|
|
f98f2dd63b | ||
|
|
07baae8e4d | ||
|
|
0c7af354fd | ||
|
|
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 | ||
|
|
a17a6b90be | ||
|
|
7541d82095 | ||
|
|
e0d33c9e18 | ||
|
|
ce28c1adc3 | ||
|
|
fa72f25fd5 | ||
|
|
743196fe7e | ||
|
|
0a14fd9e70 | ||
|
|
02fabcca8d | ||
|
|
c65507ec50 | ||
|
|
075cc43b68 | ||
|
|
2b4c614890 | ||
|
|
f9c5929e2d | ||
|
|
54ed7a276c | ||
|
|
d72e2a852c | ||
|
|
1320dae352 |
3
.eslintignore
Normal file
3
.eslintignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pjax.js
|
||||||
|
pjax.min.js
|
||||||
|
*.json
|
||||||
8
.eslintrc.json
Normal file
8
.eslintrc.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": ["eslint-config-i-am-meticulous/es5"],
|
||||||
|
"rules": {
|
||||||
|
"import/order": "off",
|
||||||
|
"import/max-dependencies": "off",
|
||||||
|
"import/extensions": ["error", "never"]
|
||||||
|
}
|
||||||
|
}
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1 +1,9 @@
|
|||||||
|
.DS_Store
|
||||||
node_modules/
|
node_modules/
|
||||||
|
.nyc_output/
|
||||||
|
.idea/
|
||||||
|
dist/
|
||||||
|
coverage/
|
||||||
|
tests/scripts/index.html
|
||||||
|
pjax.js
|
||||||
|
pjax.min.js
|
||||||
|
|||||||
147
.jscs.json
147
.jscs.json
@@ -1,147 +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": [
|
|
||||||
"?"
|
|
||||||
, "+"
|
|
||||||
, "-"
|
|
||||||
, "/"
|
|
||||||
, "*"
|
|
||||||
, "="
|
|
||||||
, "=="
|
|
||||||
, "==="
|
|
||||||
, "!="
|
|
||||||
, "!=="
|
|
||||||
, ">"
|
|
||||||
, ">="
|
|
||||||
, "<"
|
|
||||||
, "<="
|
|
||||||
]
|
|
||||||
, "disallowLeftStickedOperators": [
|
|
||||||
"?"
|
|
||||||
, "+"
|
|
||||||
, "-"
|
|
||||||
, "/"
|
|
||||||
, "*"
|
|
||||||
, "="
|
|
||||||
, "=="
|
|
||||||
, "==="
|
|
||||||
, "!="
|
|
||||||
, "!=="
|
|
||||||
, ">"
|
|
||||||
, ">="
|
|
||||||
, "<"
|
|
||||||
, "<="
|
|
||||||
]
|
|
||||||
, "requireRightStickedOperators": [
|
|
||||||
"!"
|
|
||||||
]
|
|
||||||
, "disallowRightStickedOperators": [
|
|
||||||
"?"
|
|
||||||
, "+"
|
|
||||||
, "/"
|
|
||||||
, "*"
|
|
||||||
, ":"
|
|
||||||
, "="
|
|
||||||
, "=="
|
|
||||||
, "==="
|
|
||||||
, "!="
|
|
||||||
, "!=="
|
|
||||||
, ">"
|
|
||||||
, ">="
|
|
||||||
, "<"
|
|
||||||
, "<="
|
|
||||||
]
|
|
||||||
, "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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pjax.js
|
||||||
|
pjax.min.js
|
||||||
|
*.json
|
||||||
4
.travis.yml
Normal file
4
.travis.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
language: "node_js"
|
||||||
|
node_js:
|
||||||
|
- "10"
|
||||||
|
|
||||||
155
CHANGELOG.md
155
CHANGELOG.md
@@ -1,14 +1,153 @@
|
|||||||
# Changelog
|
# 0.2.8 - 2019-03-09
|
||||||
|
|
||||||
## 0.1.2 - 2014-04-03
|
- Fixed: Edge form support.
|
||||||
|
([#178](https://github.com/MoOx/pjax/pull/178) - @robinnorth)
|
||||||
|
- Fixed: Removed keyup event listener for forms.
|
||||||
|
([#184](https://github.com/MoOx/pjax/pull/184) - @BehindTheMath)
|
||||||
|
- Fixed: Bugs in evalScripts().
|
||||||
|
([#186](https://github.com/MoOx/pjax/pull/186) - @BehindTheMath)
|
||||||
|
- Fixed: Handle non-string HTML passed to loadContent().
|
||||||
|
([#200](https://github.com/MoOx/pjax/pull/200) - @BehindTheMath)
|
||||||
|
- Tooling: Switch linting to ESLint and Prettier.
|
||||||
|
([#191](https://github.com/MoOx/pjax/pull/191) - @BehindTheMath)
|
||||||
|
|
||||||
- pjax.js relocated in `src/`
|
# 0.2.7 - 2018-08-15
|
||||||
- <html> attributes of pjaxified document are now available
|
|
||||||
|
|
||||||
## 0.1.1 - 2014-04-02
|
- Fixed: Parsing values of option elements in forms.
|
||||||
|
([#162](https://github.com/MoOx/pjax/pull/162) - @BehindTheMath)
|
||||||
|
- Fixed: Added index.d.ts to package.json so it will be installed by npm.
|
||||||
|
([c589ab9](https://github.com/MoOx/pjax/commit/c589ab9c25bee6161bf3e557eaca44e51c14fb89) - @BehindTheMath)
|
||||||
|
- Fixed: `options.history` to correctly parse being set to false.
|
||||||
|
([#165](https://github.com/MoOx/pjax/pull/165) - @BehindTheMath).
|
||||||
|
- Fixed: Pass the current `options` object to `loadContent()`.
|
||||||
|
([#171](https://github.com/MoOx/pjax/pull/171) - @BehindTheMath)
|
||||||
|
- Fixed: Ensure correct XHR encoding for multipart/form-data forms
|
||||||
|
([#174](https://github.com/MoOx/pjax/pull/174) - @BehindTheMath)
|
||||||
|
- Added: More documentation.
|
||||||
|
([#160](https://github.com/MoOx/pjax/pull/160), [#171](https://github.com/MoOx/pjax/pull/171) - @robinnorth, @BehindTheMath)
|
||||||
|
|
||||||
- Safer UMD wrapper (fix concat issue)
|
# 0.2.6 - 2018-04-30
|
||||||
|
|
||||||
## 0.1.0 - 2014-03-24
|
- Fixed: Form submission for GET requests.
|
||||||
|
([#129](https://github.com/MoOx/pjax/pull/129) - @robinnorth)
|
||||||
|
- Fixed: Refactor `loadUrl()` to make manually calling simpler.
|
||||||
|
([#134](https://github.com/MoOx/pjax/pull/134) - @robinnorth)
|
||||||
|
- Fixed: Support multiple select fields in form submissions.
|
||||||
|
([#147](https://github.com/MoOx/pjax/pull/147) - @robinnorth)
|
||||||
|
- Fixed: Use the same options object in `handle-response` as in `send-request`. This way, `pjax.state.options` will also have the request options.
|
||||||
|
([#148](https://github.com/MoOx/pjax/pull/148) - @BehindTheMath)
|
||||||
|
- Added: Move the XHR callback to a separate method, and trigger an error event if the response cannot be parsed.
|
||||||
|
([#137](https://github.com/MoOx/pjax/pull/137) - @BehindTheMath)
|
||||||
|
- Added: TypeScript definitions.
|
||||||
|
([#138](https://github.com/MoOx/pjax/pull/138) - @BehindTheMath)
|
||||||
|
- Added: `replaceNode` switch, as an alternative to the `outerHTML` switch.
|
||||||
|
([#141](https://github.com/MoOx/pjax/pull/141) - @BehindTheMath)
|
||||||
|
- Added: `X-PJAX-Selectors` HTTP header. This is a serialized JSON array of selectors, taken from `options.selectors`. You can use this to send back only the elements that Pjax will use to switch, instead of sending the whole page.
|
||||||
|
([#144](https://github.com/MoOx/pjax/pull/144) - @BehindTheMath)
|
||||||
|
- Added: An option to use `FormData` to submit forms.
|
||||||
|
([#153](https://github.com/MoOx/pjax/pull/153) - @BehindTheMath)
|
||||||
|
- Added: Tests.
|
||||||
|
([f98f2dd](https://github.com/MoOx/pjax/commit/f98f2dd63b48113ff91b6bd8808257bfc723ef6b), [#145](https://github.com/MoOx/pjax/pull/145) - @robinnorth, @BehindTheMath)
|
||||||
|
|
||||||
Initial release
|
# 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
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- Changed: `pjax.js` relocated in `src/`
|
||||||
|
- Fixed: `<html>` attributes of pjaxified document are now available
|
||||||
|
|
||||||
|
# 0.1.1 - 2014-04-02
|
||||||
|
|
||||||
|
- Fixed: safer UMD wrapper (fix concat issue)
|
||||||
|
|
||||||
|
# 0.1.0 - 2014-03-24
|
||||||
|
|
||||||
|
✨ Initial release
|
||||||
|
|||||||
185
CONTRIBUTING.md
185
CONTRIBUTING.md
@@ -1,185 +0,0 @@
|
|||||||
# Contributing Guide
|
|
||||||
|
|
||||||
Please take a moment to review this document in order to make the contribution
|
|
||||||
process easy and effective for everyone involved.
|
|
||||||
|
|
||||||
Following these guidelines helps to communicate that you respect the time of
|
|
||||||
the developers managing and developing this open source project. In return,
|
|
||||||
they should reciprocate that respect in addressing your issue, assessing
|
|
||||||
changes, and helping you finalize your pull requests.
|
|
||||||
|
|
||||||
|
|
||||||
## Using the issue tracker
|
|
||||||
|
|
||||||
The issue tracker is the preferred channel for [bug reports](#bugs),
|
|
||||||
[features requests](#features) and [submitting pull
|
|
||||||
requests](#pull-requests).
|
|
||||||
|
|
||||||
|
|
||||||
<a name="bugs"></a>
|
|
||||||
## Bug reports
|
|
||||||
|
|
||||||
A bug is a _demonstrable problem_ that is caused by the code in the repository.
|
|
||||||
Good bug reports are extremely helpful - thank you!
|
|
||||||
|
|
||||||
Guidelines for bug reports:
|
|
||||||
|
|
||||||
1. **Use the GitHub issue search** — check if the issue has already been
|
|
||||||
reported.
|
|
||||||
|
|
||||||
2. **Check if the issue has been fixed** — try to reproduce it using the
|
|
||||||
latest `master` or development branch in the repository.
|
|
||||||
|
|
||||||
3. **Isolate the problem** — ideally create a [reduced test
|
|
||||||
case](http://css-tricks.com/6263-reduced-test-cases/).
|
|
||||||
|
|
||||||
A good bug report shouldn't leave others needing to chase you up for more
|
|
||||||
information. Please try to be as detailed as possible in your report. What is
|
|
||||||
your environment? What steps will reproduce the issue? What OS experiences the
|
|
||||||
problem? What would you expect to be the outcome? All these details will help
|
|
||||||
people to fix any potential bugs.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
> Short and descriptive example bug report title
|
|
||||||
>
|
|
||||||
> A summary of the issue and the browser/OS environment in which it occurs. If
|
|
||||||
> suitable, include the steps required to reproduce the bug.
|
|
||||||
>
|
|
||||||
> 1. This is the first step
|
|
||||||
> 2. This is the second step
|
|
||||||
> 3. Further steps, etc.
|
|
||||||
>
|
|
||||||
> `<url>` - a link to the reduced test case
|
|
||||||
>
|
|
||||||
> Any other information you want to share that is relevant to the issue being
|
|
||||||
> reported. This might include the lines of code that you have identified as
|
|
||||||
> causing the bug, and potential solutions (and your opinions on their
|
|
||||||
> merits).
|
|
||||||
|
|
||||||
|
|
||||||
<a name="features"></a>
|
|
||||||
## Feature requests
|
|
||||||
|
|
||||||
Feature requests are welcome. But take a moment to find out whether your idea
|
|
||||||
fits with the scope and aims of the project. It's up to *you* to make a strong
|
|
||||||
case to convince the project's developers of the merits of this feature. Please
|
|
||||||
provide as much detail and context as possible.
|
|
||||||
|
|
||||||
|
|
||||||
<a name="pull-requests"></a>
|
|
||||||
## Pull requests
|
|
||||||
|
|
||||||
Good pull requests - patches, improvements, new features - are a fantastic
|
|
||||||
help. They should remain focused in scope and avoid containing unrelated
|
|
||||||
commits.
|
|
||||||
|
|
||||||
**Please ask first** before embarking on any significant pull request (e.g.
|
|
||||||
implementing features, refactoring code), otherwise you risk spending a lot of
|
|
||||||
time working on something that the project's developers might not want to merge
|
|
||||||
into the project.
|
|
||||||
|
|
||||||
Please adhere to the coding conventions used throughout a project (indentation,
|
|
||||||
accurate comments, etc.) and any other requirements (such as test coverage).
|
|
||||||
|
|
||||||
Adhering to the following this process is the best way to get your work
|
|
||||||
included in the project:
|
|
||||||
|
|
||||||
1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
|
|
||||||
and configure the remotes:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone your fork of the repo into the current directory
|
|
||||||
git clone https://github.com/<your-username>/happyplan
|
|
||||||
# Navigate to the newly cloned directory
|
|
||||||
cd happyplan
|
|
||||||
# Assign the original repo to a remote called "upstream"
|
|
||||||
git remote add upstream https://github.com/happyplan/happyplan
|
|
||||||
```
|
|
||||||
|
|
||||||
2. If you cloned a while ago, get the latest changes from upstream:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout master
|
|
||||||
git pull upstream master
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Create a new topic branch (off the main project development branch) to
|
|
||||||
contain your feature, change, or fix:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout -b <topic-branch-name>
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Make sure to update, or add to the tests when appropriate. Patches and
|
|
||||||
features will not be accepted without tests. Run `npm test` to check that
|
|
||||||
all tests pass after you've made changes.
|
|
||||||
|
|
||||||
5. Commit your changes in logical chunks. Please adhere to these [git commit
|
|
||||||
message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
|
||||||
or your code is unlikely be merged into the main project. Use Git's
|
|
||||||
[interactive rebase](https://help.github.com/articles/interactive-rebase)
|
|
||||||
feature to tidy up your commits before making them public.
|
|
||||||
|
|
||||||
6. Locally merge (or rebase) the upstream development branch into your topic branch:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git pull [--rebase] upstream master
|
|
||||||
```
|
|
||||||
|
|
||||||
7. Push your topic branch up to your fork:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git push origin <topic-branch-name>
|
|
||||||
```
|
|
||||||
|
|
||||||
8. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
|
|
||||||
with a clear title and description.
|
|
||||||
|
|
||||||
9. If you are asked to amend your changes before they can be merged in, please
|
|
||||||
use `git commit --amend` (or rebasing for multi-commit Pull Requests) and
|
|
||||||
force push to your remote feature branch. You may also be asked to squash
|
|
||||||
commits.
|
|
||||||
|
|
||||||
**IMPORTANT**: By submitting a patch, you agree to license your work under the
|
|
||||||
same license as that used by the project.
|
|
||||||
|
|
||||||
|
|
||||||
<a name="maintainers"></a>
|
|
||||||
## Maintainers
|
|
||||||
|
|
||||||
If you have commit access, please follow this process for merging patches and cutting new releases.
|
|
||||||
|
|
||||||
### Reviewing changes
|
|
||||||
|
|
||||||
1. Check that a change is within the scope and philosophy of the project.
|
|
||||||
2. Check that a change has any necessary tests and a proper, descriptive commit message.
|
|
||||||
3. Checkout the change and test it locally.
|
|
||||||
4. If the change is good, and authored by someone who cannot commit to
|
|
||||||
`master`, please try to avoid using GitHub's merge button. Apply the change
|
|
||||||
to `master` locally (feel free to amend any minor problems in the author's
|
|
||||||
original commit if necessary).
|
|
||||||
5. If the change is good, and authored by another maintainer/collaborator, give
|
|
||||||
them a "Ship it!" comment and let them handle the merge.
|
|
||||||
|
|
||||||
### Submitting changes
|
|
||||||
|
|
||||||
1. All non-trivial changes should be put up for review using GitHub Pull
|
|
||||||
Requests.
|
|
||||||
2. Your change should not be merged into `master` (or another feature branch),
|
|
||||||
without at least one "Ship it!" comment from another maintainer/collaborator
|
|
||||||
on the project. "Looks good to me" is not the same as "Ship it!".
|
|
||||||
3. Try to avoid using GitHub's merge button. Locally rebase your change onto
|
|
||||||
`master` and then push to GitHub.
|
|
||||||
4. Once a feature branch has been merged into its target branch, please delete
|
|
||||||
the feature branch from the remote repository.
|
|
||||||
|
|
||||||
### Releasing a new version
|
|
||||||
|
|
||||||
1. Include all new functional changes in the CHANGELOG.
|
|
||||||
2. Use a dedicated commit to increment the version. The version needs to be
|
|
||||||
added to the `CHANGELOG.md` (inc. date) and the `package.json`.
|
|
||||||
3. The commit message must be of `v0.0.0` format.
|
|
||||||
4. Create an annotated tag for the version: `git tag -m "v0.0.0" v0.0.0`.
|
|
||||||
5. Push the changes and tags to GitHub: `git push --tags origin master`.
|
|
||||||
6. Publish the new version to npm: `npm publish`.
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2014 "MoOx" Maxime Thirouin
|
Copyright (c) 2014 Maxime Thirouin
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
740
README.md
740
README.md
@@ -1,125 +1,172 @@
|
|||||||
# Pjax
|
# Pjax
|
||||||
|
|
||||||
<img align="right" src="https://dl.dropboxusercontent.com/u/14108185/memes/mind-blow.gif">
|
[](https://travis-ci.org/MoOx/pjax).
|
||||||
|
|
||||||
> When Ajax navigation meets Push State
|
> Easily enable fast AJAX navigation on any website (using pushState() + XHR)
|
||||||
|
|
||||||
Pjax is ~~a jQuery plugin~~ **a standalone JavaScript module** that uses
|
Pjax is **a standalone JavaScript module** that uses [AJAX](https://developer.mozilla.org/en-US/docs/Web/Guide/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.
|
||||||
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
|
_It allows you to completely transform the user experience of standard websites (server-side generated or static ones) to make users feel like they are browsing an app, especially for those with low bandwidth connections._
|
||||||
(server side generated or static ones) to make them feel they browse an app._
|
|
||||||
|
|
||||||
## No tests or Demo ?
|
**No more full page reloads. No more multiple HTTP requests.**
|
||||||
|
|
||||||
There is still some work to make this repo sexy with tests & simple demo.
|
_Pjax does not rely on other libraries, like jQuery or similar. It is written entirely in vanilla JS._
|
||||||
|
|
||||||
For now [you can see this running on my website](http://moox.io), with sexy CSS animations when switching pages.
|
## Installation
|
||||||
|
|
||||||
## How Pjax works
|
- You can 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>
|
||||||
|
```
|
||||||
|
|
||||||
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.
|
- Or the [minified bundle](https://cdn.jsdelivr.net/npm/pjax/pjax.min.js):
|
||||||
_But under the hood, it's just ONE http request with a pushState() call._
|
```html
|
||||||
Obviously, for [browsers that don't support pushState()](http://caniuse.com/#search=pushstate) Pjax fully degrades (yeah, it doesn't do anything at all).
|
<script src="https://cdn.jsdelivr.net/npm/pjax@VERSION/pjax.min.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
It simply works with all permalinks & can update all parts of the page you
|
- You can also install Pjax from **npm**:
|
||||||
want (including html metas, title, navigation state).
|
```shell
|
||||||
|
npm install pjax
|
||||||
|
```
|
||||||
|
**Note**: If you use this option, you will need to do one of the following:
|
||||||
|
- Link a script tag to either `pjax.js` or `pjax.min.js`. E.g.:
|
||||||
|
```html
|
||||||
|
<script src="./node_modules/pjax/pjax.js"></script>
|
||||||
|
```
|
||||||
|
- Use a bundler like Webpack. (`index.js` cannot be used in the browser without a bundler).
|
||||||
|
|
||||||
- It's not limited to one container, like jQuery-Pjax is,
|
- Or you can clone the repo and build the bundle from the source using npm:
|
||||||
- It fully support browser history (back & forward buttons),
|
```shell
|
||||||
- It **will** support keyboard browsing (@todo),
|
git clone https://github.com/MoOx/pjax.git
|
||||||
- Automatically fallback to classic navigation for externals pages (thanks to Capitain Obvious help),
|
cd pjax
|
||||||
- Automatically fallback to classic navigation for internals pages that will not have the appropriated DOM tree,
|
npm install
|
||||||
- You can add pretty cool CSS transitions (animations) very easily.
|
npm run build
|
||||||
- It's around 3kb (minified & gzipped).
|
```
|
||||||
|
and then link a script tag to either `pjax.js` or `pjax.min.js`. E.g.:
|
||||||
|
```html
|
||||||
|
<script src="./pjax.min.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
### Under the hood
|
---
|
||||||
|
|
||||||
- It listen to every clicks on links _you want_ (by default all of them),
|
## What Pjax Does
|
||||||
- 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...)
|
_Under the hood, it's just ONE HTTP request with a `pushState()` call._
|
||||||
- It check if all defined parts can be replaced:
|
|
||||||
- if page doesn't suit requirement, classic navigation used,
|
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.
|
||||||
- if page suits requirement, Pjax does all defined DOM replacements
|
|
||||||
- Then, it updates the browser's current url using pushState
|
It works with all permalinks and can update all the parts of the page you want (including HTML metas, title, and navigation state).
|
||||||
|
|
||||||
|
In the case of [browsers that don't support `history.pushState()`](http://caniuse.com/#search=pushstate), Pjax gracefully degrades and does not do anything at all.
|
||||||
|
|
||||||
|
Additionally, Pjax:
|
||||||
|
|
||||||
|
- Is not limited to one container, like jQuery-Pjax is.
|
||||||
|
- Fully supports browser history (back and forward buttons).
|
||||||
|
- 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.
|
||||||
|
- Allows for CSS transitions (animations) very easily.
|
||||||
|
- Is only around 6kb (minified and gzipped).
|
||||||
|
|
||||||
|
## How Pjax Works
|
||||||
|
|
||||||
|
- It listens to every click on links _you want_ (by default all of them).
|
||||||
|
- When an internal link is clicked, Pjax fetches the page's HTML via AJAX.
|
||||||
|
- Pjax renders the page's DOM tree (without loading any resources - images, CSS, JS, etc).
|
||||||
|
- 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
|
## 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 change anything about your existing HTML,
|
||||||
You just need to designate some elements on your page that will be replaced when
|
you just need to designate which elements on your page that you want to be replaced when your site is navigated.
|
||||||
you navigate your site.
|
|
||||||
|
|
||||||
Consider the following page.
|
Consider the following page.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!-- metas, title, styles, ... -->
|
<!-- metas, title, styles, etc -->
|
||||||
|
<title>My Cool Blog</title>
|
||||||
|
<meta name="description" content="Welcome to My Cool Blog">
|
||||||
|
<link href="/styles.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header class="my-Header"><nav><!-- a .is-active is in there --></nav></header>
|
<header class="the-header">
|
||||||
<section class="my-Content">
|
<nav>
|
||||||
Sha blah <a href="/blah ">blah</a>.
|
<a href="/" class="is-active">Home</a>
|
||||||
|
<a href="/about">About</a>
|
||||||
|
<a href="/contact">Contact</a>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="the-content">
|
||||||
|
<h1>My Cool Blog</h1>
|
||||||
|
<p>
|
||||||
|
Thanks for stopping by!
|
||||||
|
|
||||||
|
<a href="/about">Click Here to find out more about me.</a>
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<aside class="my-Sidebar">Sidebar stuff</aside>
|
|
||||||
<footer class="my-Footer"></footer>
|
<aside class="the-sidebar">
|
||||||
|
<h3>Recent Posts</h3>
|
||||||
|
<!-- sidebar content -->
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<footer class="the-footer">
|
||||||
|
© My Cool Blog
|
||||||
|
</footer>
|
||||||
|
|
||||||
<script src="onDomReadystuff.js"></script>
|
<script src="onDomReadystuff.js"></script>
|
||||||
<script><!-- analytics --></script>
|
<script>
|
||||||
|
// analytics
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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 `/about`, and replace `.the-content` with the resulting content 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**.
|
|
||||||
|
|
||||||
We do this by telling Pjax to listen on `a` tags and use CSS selectors defined above (without forgetting minimal meta):
|
It would also be nice if we could replace the `<nav>` to show that the `/about` link is active, as well as update our page meta and the `<aside>` sidebar.
|
||||||
|
|
||||||
|
So all in all we want to update the page title and meta, header, content area, and sidebar, **without reloading styles or scripts**.
|
||||||
|
|
||||||
|
We can easily do this by telling Pjax to listen on all `a` tags (which is the default) and use CSS selectors defined above (without forgetting minimal meta):
|
||||||
|
|
||||||
``` javascript
|
``` javascript
|
||||||
new Pjax({ selectors: ["title", ".my-Header", ".my-Content", ".my-Sidebar"] })
|
var pjax = new Pjax({
|
||||||
|
selectors: [
|
||||||
|
"title",
|
||||||
|
"meta[name=description]",
|
||||||
|
".the-header",
|
||||||
|
".the-content",
|
||||||
|
".the-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 an internal link on the page, the content of each of the selectors will be replaced with the specific content pieces found in the next page's 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)
|
## Differences with [jQuery-pjax](https://github.com/defunkt/jquery-pjax)
|
||||||
|
|
||||||
- No jQuery dependency,
|
- No jQuery dependency.
|
||||||
- Not limited to a container,
|
- Not limited to a container.
|
||||||
- No server side requirements,
|
- No server-side requirements.
|
||||||
- Works for CommonJS environment (browserify), AMD (RequireJS) or even globally,
|
- Works for CommonJS environment (Webpack/Browserify), AMD (RequireJS) or even globally.
|
||||||
- Allow page transition with CSS animations,
|
- Allows page transitions with CSS animations.
|
||||||
- Can be easily hacked since every method is public (so overridable)
|
- Can be easily tweaked, since every method is public (and as a result, overridable).
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
You can install pjax from **npm**
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ npm install pjax
|
|
||||||
```
|
|
||||||
|
|
||||||
Or using **bower**
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ bower install pjax
|
|
||||||
```
|
|
||||||
|
|
||||||
Pjax can obviously be downloaded directly.
|
|
||||||
|
|
||||||
## No dependencies
|
|
||||||
|
|
||||||
_There is nothing you need. No jQuery or something._
|
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
Pjax only works with [browsers that support the `history.pushState` API](http://caniuse.com/#search=pushstate).
|
Pjax only works with [browsers that support the `history.pushState()` API](http://caniuse.com/#search=pushstate). When the API isn't supported, Pjax goes into fallback mode (and it just does nothing).
|
||||||
When the API isn't supported Pjax goes into fallback mode (it just does nothing).
|
|
||||||
|
|
||||||
To see if Pjax is actually supported by your browser, use `Pjax.isSupported()`.
|
To see if Pjax is actually supported by your browser, use `Pjax.isSupported()`.
|
||||||
|
|
||||||
@@ -127,143 +174,235 @@ To see if Pjax is actually supported by your browser, use `Pjax.isSupported()`.
|
|||||||
|
|
||||||
### `new Pjax()`
|
### `new Pjax()`
|
||||||
|
|
||||||
Let's talk more about the most basic way to get started:
|
Let's talk more about the most basic way to get started.
|
||||||
|
|
||||||
|
When instantiating `Pjax`, you can pass options into the constructor as an object:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
new Pjax({
|
var pjax = new Pjax({
|
||||||
elements: "a" // default is "a[href], form[action]"
|
elements: "a", // default is "a[href], form[action]"
|
||||||
, selectors: ["title", ".my-Header", ".my-Content", ".my-Sidebar"]
|
selectors: ["title", ".the-header", ".the-content", ".the-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", ".the-header", ".the-content", ".the-sidebar"`.
|
||||||
|
|
||||||
For some reason, you might want to just target some elements to apply Pjax behavior.
|
In some cases, you might want to only target some specific elements to apply Pjax behavior. In that case, you can do two different things:
|
||||||
In that case, you can 2 differents things:
|
|
||||||
|
|
||||||
- use a custom selector like "a.js-Pjax" or ".js-Pjax a" depending on what you want.
|
1. Use a custom CSS selector( such as `"a.js-Pjax"` or `".js-Pjax a"`, etc).
|
||||||
- override `Pjax.prototype.getElements` that just basically `querSelectorAll` the `elements` option. In this function you just need to return a `NodeList`.
|
2. Override `Pjax.prototype.getElements`.
|
||||||
|
- **Note**: If doing this, make sure to return a `NodeList`.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// use case 1
|
// use case 1
|
||||||
new Pjax({ elements: "a.js-Pjax" })
|
var pjax = new Pjax({ elements: "a.js-Pjax" })
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
// use case 2
|
// use case 2
|
||||||
Pjax.prototype.getElements = function() {
|
Pjax.prototype.getElements = function() {
|
||||||
return document.getElementsByClassName(".js-Pjax")
|
return document.getElementsByClassName(".js-Pjax")
|
||||||
}
|
}
|
||||||
|
|
||||||
new Pjax({})
|
var pjax = new Pjax()
|
||||||
```
|
```
|
||||||
|
|
||||||
When instanciating a `Pjax` object, you need to pass all options as an object:
|
### `loadUrl(href, [options])`
|
||||||
|
|
||||||
#### Options
|
With this method, you can manually trigger the loading of a URL:
|
||||||
|
|
||||||
##### `elements` (String, default "a[href], form[action]")
|
```js
|
||||||
|
var pjax = new Pjax()
|
||||||
|
|
||||||
CSS Selector to use to retrieve links to apply Pjax
|
// use case 1
|
||||||
|
pjax.loadUrl("/your-url")
|
||||||
|
|
||||||
##### `selectors` (Array, default ["title", ".js-Pjax"])
|
// use case 2 (with options override)
|
||||||
|
pjax.loadUrl("/your-other-url", { timeout: 10 })
|
||||||
|
```
|
||||||
|
|
||||||
CSS Selectors to replace. If a query returns multiples items, it will just keep the index.
|
### `handleResponse(responseText, request, href, options)`
|
||||||
|
|
||||||
|
This method takes the raw response, processes the URL, then calls `pjax.loadContent()` to actually load it into the DOM.
|
||||||
|
|
||||||
|
It is passed the following arguments:
|
||||||
|
|
||||||
|
* **responseText** (string): This is the raw response text. This is equivalent to `request.responseText`.
|
||||||
|
* **request** (XMLHttpRequest): This is the XHR object.
|
||||||
|
* **href** (string): This is the URL that was passed to `loadUrl()`.
|
||||||
|
* **options** (object): This is an object with the options for this request. The structure basically matches the regular options object, with a few extra internal properties.
|
||||||
|
|
||||||
|
You can override this if you want to process the data before, or instead of, it being loaded into the DOM.
|
||||||
|
|
||||||
|
For example, if you want to check for a non-HTML response, you could do the following:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var pjax = new Pjax();
|
||||||
|
|
||||||
|
pjax._handleResponse = pjax.handleResponse;
|
||||||
|
|
||||||
|
pjax.handleResponse = function(responseText, request, href, options) {
|
||||||
|
if (request.responseText.match("<html")) {
|
||||||
|
pjax._handleResponse(responseText, request, href, options);
|
||||||
|
} else {
|
||||||
|
// handle non-HTML response here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `refresh([el])`
|
||||||
|
|
||||||
|
Use this method to bind Pjax to children of a DOM element that didn't exist when Pjax was initialised e.g. content inserted dynamically by another library or script. If called with no arguments, Pjax will parse the entire document again to look for newly-inserted elements.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Inside a callback or Promise that runs after new DOM content has been inserted
|
||||||
|
var newContent = document.querySelector(".new-content");
|
||||||
|
|
||||||
|
pjax.refresh(newContent);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### `reload()`
|
||||||
|
|
||||||
|
A helper shortcut for `window.location.reload()`. Used to force a page reload.
|
||||||
|
|
||||||
|
```js
|
||||||
|
pjax.reload()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### `elements` (String, default: `"a[href], form[action]"`)
|
||||||
|
|
||||||
|
CSS selector(s) used to find links to apply Pjax to. If needing multiple specific selectors, separate them by a comma.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Single element
|
||||||
|
var pjax = new Pjax({
|
||||||
|
elements: ".ajax"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Multiple elements
|
||||||
|
var pjax = new Pjax({
|
||||||
|
elements: ".pjax, .ajax",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### `selectors` (Array, default: `["title", ".js-Pjax"]`)
|
||||||
|
|
||||||
|
CSS selectors used to find which content to replace.
|
||||||
|
|
||||||
|
```js
|
||||||
|
var pjax = new Pjax({
|
||||||
|
selectors: [
|
||||||
|
"title",
|
||||||
|
"the-content",
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
If a query returns multiples items, it will just keep the index.
|
||||||
|
|
||||||
Example of what you can do:
|
Example of what you can do:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Page title</title>
|
<title>Page title</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="js-Pjax"></header>
|
<header class="js-Pjax">...</header>
|
||||||
<section class="js-Pjax">...</section>
|
<section class="js-Pjax">...</section>
|
||||||
<footer class="my-Footer"></footer>
|
<footer class="the-footer">...</footer>
|
||||||
<script>...</script>
|
<script>...</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
This example is correct and should work "as expected".
|
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._
|
|
||||||
|
|
||||||
##### `switches` (Object, default {})
|
**NOTE:** _If the current page and new page do not have the same amount of DOM elements, Pjax will fall back to normal page load._
|
||||||
|
|
||||||
Objects containing callbacks that can be used to switch old element with new element.
|
### `switches` (Object, default: `{}`)
|
||||||
Keys should be one of the defined selector.
|
|
||||||
|
This is an object containing callbacks that can be used to switch old elements with new elements.
|
||||||
|
|
||||||
|
The object keys should be one of the defined selectors (from the `selectors` option).
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
new Pjax({
|
var pjax = new Pjax({
|
||||||
selectors: ["title", ".Navbar", ".js-Pjax"]
|
selectors: ["title", ".Navbar", ".js-Pjax"],
|
||||||
, switches: {
|
switches: {
|
||||||
// "title": Pjax.switches.outerHTML // default behavior
|
"title": Pjax.switches.outerHTML, // default behavior
|
||||||
".Navbar": function(oldEl, newEl, options) {
|
".the-content": function(oldEl, newEl, options) {
|
||||||
// here it's a stupid example since it's the default behavior too
|
// this is identical to the default behavior
|
||||||
oldEl.outerHTML = newEl.outerHTML
|
oldEl.outerHTML = newEl.outerHTML
|
||||||
this.onSwitch()
|
this.onSwitch()
|
||||||
},
|
},
|
||||||
|
|
||||||
".js-Pjax": Pjax.switches.sideBySide
|
".js-Pjax": Pjax.switches.sideBySide
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
Callbacks are binded to Pjax instance itself to allow you to reuse it (ex: `this.onSwitch()`)
|
Callbacks are bound to the Pjax instance itself to allow you to reuse it (ex: `this.onSwitch()`)
|
||||||
|
|
||||||
###### Existing switches callback
|
### Existing Switch Callbacks
|
||||||
|
|
||||||
- `Pjax.switches.outerHTML`: default behavior, replace elements using outerHTML
|
- `Pjax.switches.outerHTML`:
|
||||||
- `Pjax.switches.innerHTML`: replace elements using innerHTML & copy className too
|
The default behavior, replaces elements using `outerHTML`.
|
||||||
- `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`:
|
||||||
|
Replaces elements using `innerHTML` and copies `className`.
|
||||||
|
- `Pjax.switches.replaceNode`:
|
||||||
|
Replaces elements using `replaceChild`
|
||||||
|
- `Pjax.switches.sideBySide`:
|
||||||
|
Smart replacing 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 (an [animationEnd](http://www.w3.org/TR/css3-animations/#animationend) event is triggered).
|
||||||
|
|
||||||
###### Create a switch callback
|
### Creating a Switch Callback
|
||||||
|
|
||||||
Your function can do whatever you want, be should
|
Your callback function can do whatever you want, but you need to:
|
||||||
|
|
||||||
- replace oldEl content by newEl content in some fashion
|
1. Replace the `oldEl`'s content with the `newEl`'s content in some fashion.
|
||||||
- call `this.onSwitch()` to trigger attached callback.
|
2. Call `this.onSwitch()` to trigger the attached callback.
|
||||||
|
|
||||||
Here is the default behavior as example
|
Here is the default behavior as an example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function(oldEl, newEl, pjaxRequestOptions, switchesClasses) {
|
function(oldEl, newEl, pjaxOptions) {
|
||||||
oldEl.outerHTML = newEl.outerHTML
|
oldEl.outerHTML = newEl.outerHTML
|
||||||
this.onSwitch()
|
this.onSwitch()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `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).
|
These are options that can be used during content replacement by switches. 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).
|
||||||
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
|
```js
|
||||||
new Pjax({
|
var pjax = new Pjax({
|
||||||
selectors: ["title", ".js-Pjax"]
|
selectors: ["title", ".js-Pjax"],
|
||||||
, switches: {
|
switches: {
|
||||||
".js-Pjax": Pjax.switches.sideBySide
|
".js-Pjax": Pjax.switches.sideBySide
|
||||||
}
|
},
|
||||||
, switchesClasses: {
|
switchesOptions: {
|
||||||
".js-Pjax": {
|
".js-Pjax": {
|
||||||
classNames: {
|
classNames: {
|
||||||
// class added on the element that will be removed
|
// class added to the old element being replaced, e.g. a fade out
|
||||||
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
|
// class added to the new element that is replacing the old one, e.g. a fade in
|
||||||
, add: "Animated"
|
add: "Animated",
|
||||||
// class added on the element when it go backward
|
// class added on the element when navigating back
|
||||||
, backward: "Animate--slideInRight"
|
backward: "Animate--slideInRight",
|
||||||
// class added on the element when it go forward (used for new page too)
|
// class added on the element when navigating forward (used for new page too)
|
||||||
, forward: "Animate--slideInLeft"
|
forward: "Animate--slideInLeft"
|
||||||
}
|
},
|
||||||
, callbacks: {
|
callbacks: {
|
||||||
// to make a nice transition with 2 pages as the same time
|
// to make a nice transition with 2 pages at the same time
|
||||||
// we are playing with absolute positioning for element we are removing
|
// we are playing with absolute positioning for the element we are removing
|
||||||
// & we need live metrics to have something great
|
// & we need live metrics to have something great
|
||||||
// see associated CSS below
|
// see associated CSS below
|
||||||
removeElement: function(el) {
|
removeElement: function(el) {
|
||||||
@@ -271,32 +410,32 @@ 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
|
_Note that `remove` includes `Animated--reverse` which is a simple way to not have to have a duplicate transition (slideIn + reverse => slideOut)._
|
||||||
|
|
||||||
|
Here is some css that works well with the above configuration:
|
||||||
|
|
||||||
```css
|
```css
|
||||||
/*
|
/*
|
||||||
if your content elements doesn't have a fixed width,
|
Note: If your content elements don't have a fixed width it can cause
|
||||||
you can get issue when absolute positioning will be used
|
an issue when positioning absolutely
|
||||||
so you will need that rules
|
|
||||||
*/
|
*/
|
||||||
.js-Pjax { position: relative } /* parent element where switch will be made */
|
.js-Pjax { position: relative } /* parent element where switch will be made */
|
||||||
|
|
||||||
.js-Pjax-child { width: 100% }
|
.js-Pjax-child { width: 100% }
|
||||||
|
|
||||||
/* position for the elements that will be removed */
|
/* position for the elements that will be removed */
|
||||||
.js-Pjax-remove {
|
.js-Pjax-remove {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
/* transform: translateX(-50%) */
|
/* transform: translateX(-50%) */
|
||||||
/* transform can't be used since we already use generic translate for the remove effect (eg animate.css) */
|
/* transform can't be used since we already use generic translate for the remove effect (eg animate.css) */
|
||||||
/* margin-left: -width/2; // made with js */
|
/* margin-left: -width/2; // made with js */
|
||||||
/* you can totally drop the margin-left thing from switchesOptions if you use custom animations */
|
/* you can totally drop the margin-left thing from switchesOptions if you use custom animations */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CSS animations */
|
/* CSS animations */
|
||||||
.Animated {
|
.Animated {
|
||||||
@@ -304,44 +443,45 @@ The following CSS will be required to make something nice
|
|||||||
animation-duration: 1s;
|
animation-duration: 1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Animated--reverse { animation-direction: reverse }
|
.Animated--reverse { animation-direction: reverse }
|
||||||
|
|
||||||
.Animate--fast { animation-duration: .5s }
|
.Animate--fast { animation-duration: .5s }
|
||||||
.Animate--noDelay { animation-delay: 0s !important; }
|
.Animate--noDelay { animation-delay: 0s !important; }
|
||||||
|
|
||||||
.Animate--slideInRight { animation-name: Animation-slideInRight }
|
.Animate--slideInRight { animation-name: Animation-slideInRight }
|
||||||
@keyframes Animation-slideInRight {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(100rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
@keyframes Animation-slideInRight {
|
||||||
transform: translateX(0);
|
0% {
|
||||||
}
|
opacity: 0;
|
||||||
|
transform: translateX(100rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Animate--slideInLeft { animation-name: Animation-slideInLeft }
|
100% {
|
||||||
@keyframes Animation-slideInLeft {
|
transform: translateX(0);
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(-100rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Animate--slideInLeft { animation-name: Animation-slideInLeft }
|
||||||
|
|
||||||
|
@keyframes Animation-slideInLeft {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-100rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To get understand this CSS, here is a HTML snippet
|
To give context to this CSS, here is an HTML snippet:
|
||||||
|
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Page title</title>
|
<title>Page Title</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<section class="js-Pjax">
|
<section class="js-Pjax">
|
||||||
@@ -349,13 +489,15 @@ To get understand this CSS, here is a HTML snippet
|
|||||||
Your content here
|
Your content here
|
||||||
</div>
|
</div>
|
||||||
<!--
|
<!--
|
||||||
when switching will be made you will have the following tree
|
During the replacement process, you'll have the following tree:
|
||||||
|
|
||||||
<div class="js-Pjax-child js-Pjax-remove Animate...">
|
<div class="js-Pjax-child js-Pjax-remove Animate...">
|
||||||
Your OLD content here
|
Your OLD content here
|
||||||
</div>
|
</div>
|
||||||
<div class="js-Pjax-child js-Pjax-add Animate...">
|
<div class="js-Pjax-child js-Pjax-add Animate...">
|
||||||
Your NEW content here
|
Your NEW content here
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
-->
|
-->
|
||||||
</section>
|
</section>
|
||||||
<script>...</script>
|
<script>...</script>
|
||||||
@@ -363,132 +505,222 @@ To get understand this CSS, here is a HTML snippet
|
|||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `history` (Boolean, default true)
|
### `history` (Boolean, default: `true`)
|
||||||
|
|
||||||
Enable pushState. Only disable if you are crazy.
|
Enable the use of `pushState()`. Disabling this will prevent Pjax from updating browser history. While possible, there is almost no use case where you would want to do this.
|
||||||
Internaly, this option is used when `popstate` is used (to not pushState again).
|
|
||||||
You should forget that option.
|
|
||||||
|
|
||||||
##### `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
|
### `analytics` (Function | Boolean, default: a function that pushes `_gaq` `_trackPageview` or sends `ga` `pageview`
|
||||||
a pageview with Google Analytics.
|
|
||||||
It's called every time a page is switched, even for history buttons.
|
|
||||||
|
|
||||||
##### `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 | \[Integer, Integer\] | False, default: `0`)
|
||||||
|
|
||||||
Enable verbose mode & doesn't use fallback when there is an error.
|
When set to an integer, this is the value (in px from the top of the page) to scroll to when a page is switched.
|
||||||
Useful to debug page layout differences.
|
|
||||||
|
|
||||||
#### Extend Pjax
|
When set to an array of 2 integers (\[x, y\]), this is the value to scroll both horizontally and vertically.
|
||||||
|
|
||||||
Pjax prototype & utilities methods can be used & changed so you can patch or hack
|
Set this to `false` to disable scrolling, which will mean the page will stay in that same position it was before loading the new elements.
|
||||||
Pjax behavior, as you wish.
|
|
||||||
|
|
||||||
Here is a summary of functions:
|
### `scrollRestoration` (Boolean, default: `true`)
|
||||||
|
|
||||||
- `Pjax.isSupported` (`function()`): return wheter or not the browser handle pushState correctly
|
When set to `true`, Pjax will attempt to restore the scroll position when navigating backwards or forwards.
|
||||||
- `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`.
|
|
||||||
|
|
||||||
- `Pjax.prototype.log` (`function()`): console.log function that is enable/disabled by `debug` option
|
### `cacheBust` (Boolean, default: `true`)
|
||||||
- `Pjax.prototype.getElements` (`function(el)`): retrieve elements to attach Pjax behavior
|
|
||||||
- `Pjax.prototype.parseDOM` (`function(el)`): parse DOM to attach behavior using `Pjax.prototype.getElements` & `Pjax.prototype.attachLink`
|
|
||||||
- `Pjax.prototype.attachLink` (`function(el)`): attach Pjax behavior to a link
|
|
||||||
- `Pjax.prototype.forEachSelectors` (`function(cb, context, DOMcontext)`): call a function for each selectors defined
|
|
||||||
- `Pjax.prototype.switchSelectors` (`function(selectors, fromEl, toEl, options)`): loop on selectors to switch elements
|
|
||||||
- `Pjax.prototype.latestChance` (`function(href)`): when everything is fucked up, it's our only hope (just call `window.location = href`)
|
|
||||||
- `Pjax.prototype.onSwitch` (`function()`): callback triggered when elements are switched, for now it's just trigger a window resize event (lots of lib are listening to this event to draw stuff)
|
|
||||||
- `Pjax.prototype.loadContent` (`function(html, options)`): switch elements for each selectors
|
|
||||||
- `Pjax.prototype.doRequest` (`function(location, callback)`): make the ajax request to grab page from the server
|
|
||||||
- `Pjax.prototype.loadUrl` (`function(href, options)`): do the ajax request, handle html results & eventually handle browser history, analytics & scroll.
|
|
||||||
|
|
||||||
### Events
|
When set to `true`, Pjax appends a timestamp query string segment to the requested URL in order to skip the browser cache.
|
||||||
|
|
||||||
Pjax fires a number of events regardless of how its invoked.
|
### `debug` (Boolean, default: `false`)
|
||||||
|
|
||||||
All events are fired from the _document_, not the link was clicked.
|
Enables verbose mode. Useful to debug page layout differences.
|
||||||
|
|
||||||
#### Ajax related events
|
### `currentUrlFullReload` (Boolean, default: `false`)
|
||||||
|
|
||||||
|
When set to `true`, clicking on a link that points to the current URL will trigger a full page reload.
|
||||||
|
|
||||||
|
When set to `false`, clicking on such a link will cause Pjax to load the current page without a full page reload. If you want to add some custom behavior, add a click listener to the link and call `preventDefault()`. This will prevent Pjax from receiving the event.
|
||||||
|
|
||||||
|
**Note**: This must be done before Pjax is instantiated, otherwise Pjax's event handler will be called first, and `preventDefault()` won't have been called yet.
|
||||||
|
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var pjax = new Pjax()
|
||||||
|
```
|
||||||
|
|
||||||
|
(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 it's invoked.
|
||||||
|
|
||||||
|
All events are fired from the _document_, not the link that was clicked.
|
||||||
|
|
||||||
* `pjax:send` - Fired after the Pjax request begins.
|
* `pjax:send` - Fired after the Pjax request begins.
|
||||||
* `pjax:complete` - Fired after the Pjax request finishes.
|
* `pjax:complete` - Fired after the Pjax request finishes.
|
||||||
* `pjax:success` - Fired after the Pjax request succeeds.
|
* `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. The request object will be passed along as `event.options.request`.
|
||||||
|
|
||||||
`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/))
|
`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
|
```js
|
||||||
$(document).on('pjax:send', topbar.show)
|
document.addEventListener('pjax:send', topbar.show)
|
||||||
$(document).on('pjax:complete', topbar.hide)
|
document.addEventListener('pjax:complete', topbar.hide)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Note about DOM ready state
|
## HTTP Headers
|
||||||
|
|
||||||
Most of the time, you have code attached & related to the current DOM, that you only execute when page/dom is ready.
|
Pjax uses several custom headers when it makes and receives HTTP requests. If the requests are going to your server, you can use those headers for some meta information about the response.
|
||||||
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:
|
|
||||||
|
### Request Headers
|
||||||
|
|
||||||
|
Pjax sends the following headers with every request:
|
||||||
|
|
||||||
|
- `X-Requested-With: "XMLHttpRequest"`
|
||||||
|
- `X-PJAX: "true"`
|
||||||
|
- `X-PJAX-Selectors`:
|
||||||
|
A serialized JSON array of selectors, taken from `options.selectors`. You can use this to send back only the elements that Pjax will use to switch, instead of sending the whole page. Note that you'll need to deserialize this on the server (Such as by using `JSON.parse()`)
|
||||||
|
|
||||||
|
### Response Headers
|
||||||
|
|
||||||
|
Pjax looks for the following headers on the response:
|
||||||
|
|
||||||
|
- `X-PJAX-URL` or `X-XHR-Redirected-To`
|
||||||
|
|
||||||
|
Pjax first checks the `responseURL` property on the XHR object to see if the request was redirected by the server. Most browsers support this, but not all. To ensure Pjax will be able to tell when the request is redirected, you can include one of these headers with the response, set to the final URL.
|
||||||
|
|
||||||
|
## DOM Ready State
|
||||||
|
|
||||||
|
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 automatically re-execute your previous code each time you load a page, you'll need to add code to re-trigger the DOM ready code. Here's a simple example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function whenDOMReady() {
|
function whenDOMReady() {
|
||||||
// do you stuff
|
// do your stuff
|
||||||
}
|
}
|
||||||
|
|
||||||
whenDOMReady()
|
whenDOMReady()
|
||||||
|
|
||||||
new Pjax()
|
var pjax = new Pjax()
|
||||||
|
|
||||||
document.addEventListener("pjax:success", whenDOMReady)
|
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).
|
If you want to just update a specific part (which is a good idea), you can add the DOM-related code in a function and re-execute this function when the `pjax:success` event is fired.
|
||||||
So attached behavior are rexecuted 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.
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// do your global stuff
|
// do your global stuff
|
||||||
//... dom ready blah blah
|
//... DOM ready code
|
||||||
|
|
||||||
function whenContainerReady() {
|
function whenContainerReady() {
|
||||||
// do your container related stuff
|
// do your container related stuff
|
||||||
}
|
}
|
||||||
|
|
||||||
whenContainerReady()
|
whenContainerReady()
|
||||||
|
|
||||||
new Pjax()
|
var pjax = new Pjax()
|
||||||
|
|
||||||
document.addEventListener("pjax:success", whenContainerReady)
|
document.addEventListener("pjax:success", whenContainerReady)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## FAQ
|
||||||
|
|
||||||
## [Changelog](CHANGELOG.md)
|
### Q: Disqus doesn't work anymore, how can I fix that ?
|
||||||
|
|
||||||
## Contributing
|
#### A: There are a few things you need to do:
|
||||||
|
|
||||||
Please read the file nobody reads (make me lie) [CONTRIBUTING.md](CONTRIBUTING.md)
|
- 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 an empty wrapper on each page (to avoid differences of DOM between pages).
|
||||||
|
|
||||||
### tl;dr;
|
- Edit your Disqus snippet like the one below.
|
||||||
|
|
||||||
Fork, clone, then
|
#### Disqus snippet _before_ Pjax integration
|
||||||
|
|
||||||
```shell
|
```html
|
||||||
$ npm i -g gulp
|
<script>
|
||||||
$ npm i
|
var disqus_shortname = 'YOURSHORTNAME'
|
||||||
$ gulp
|
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);
|
||||||
|
})(document)
|
||||||
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you can work on the file, then make a commit and a push something when gulp doesn't show any error.
|
#### Disqus snippet _after_ Pjax integration
|
||||||
Thanks.
|
|
||||||
|
|
||||||
## [License](LICENSE-MIT)
|
```html
|
||||||
|
<div class="js-Pjax"><!-- needs to be here on every Pjax-ified page, even if empty -->
|
||||||
|
<!-- if (some condition) { // eventual server-side test to know whether or not you include this script -->
|
||||||
|
<script>
|
||||||
|
var disqus_shortname = 'YOURSHORTNAME'
|
||||||
|
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) {
|
||||||
|
(function(d,s) {
|
||||||
|
s = d.createElement('script');s.async=1;s.src = '//' + disqus_shortname + '.disqus.com/'+disqus_script;
|
||||||
|
(d.getElementsByTagName('head')[0]).appendChild(s);
|
||||||
|
})(document)
|
||||||
|
}
|
||||||
|
// if disqus <script> is already loaded, we just reset disqus the right way
|
||||||
|
// see https://help.disqus.com/developer/using-disqus-on-ajax-sites
|
||||||
|
else {
|
||||||
|
DISQUS.reset({
|
||||||
|
reload: true,
|
||||||
|
config: function () {
|
||||||
|
this.page.identifier = disqus_identifier
|
||||||
|
this.page.url = disqus_url
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- } -->
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note: Pjax only handles inline `<script>` blocks for the container you are switching.**
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Clone this repository and run `npm run example`, which will open the example app in your browser.
|
||||||
|
|
||||||
|
## CONTRIBUTING
|
||||||
|
|
||||||
|
- ⇄ 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`). If the API is changed, please update the Typescript definitions as well (`pjax.d.ts`).
|
||||||
|
|
||||||
|
## [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 ?
|
|
||||||
32
bower.json
32
bower.json
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "pjax",
|
|
||||||
"version": "0.1.2",
|
|
||||||
"description": "Boost browsing experience using Ajax navigation (+ Push state)",
|
|
||||||
"main": "src/pjax.js",
|
|
||||||
"homepage": "https://github.com/MoOx/pjax",
|
|
||||||
"authors": [
|
|
||||||
"Maxime Thirouin <m@moox.io>"
|
|
||||||
],
|
|
||||||
"moduleType": [
|
|
||||||
"amd",
|
|
||||||
"globals",
|
|
||||||
"node"
|
|
||||||
],
|
|
||||||
"keywords": [
|
|
||||||
"pjax",
|
|
||||||
"push",
|
|
||||||
"state",
|
|
||||||
"ajax",
|
|
||||||
"navigation",
|
|
||||||
"transition",
|
|
||||||
"animation"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"ignore": [
|
|
||||||
"**/.*",
|
|
||||||
"node_modules",
|
|
||||||
"bower_components",
|
|
||||||
"test",
|
|
||||||
"tests"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
59
example/example.js
Normal file
59
example/example.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/* global Pjax */
|
||||||
|
var pjax;
|
||||||
|
var initButtons = function() {
|
||||||
|
var buttons = document.querySelectorAll("button[data-manual-trigger]");
|
||||||
|
|
||||||
|
if (!buttons) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// jshint -W083
|
||||||
|
for (var i = 0; i < buttons.length; i++) {
|
||||||
|
buttons[i].addEventListener("click", function(e) {
|
||||||
|
var el = e.currentTarget;
|
||||||
|
|
||||||
|
if (el.getAttribute("data-manual-trigger-override") === "true") {
|
||||||
|
// Manually load URL with overridden Pjax instance options
|
||||||
|
pjax.loadUrl("/example/page2.html", { cacheBust: false });
|
||||||
|
} else {
|
||||||
|
// Manually load URL with current Pjax instance options
|
||||||
|
pjax.loadUrl("/example/page2.html");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// jshint +W083
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Init page content
|
||||||
|
initButtons();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
// Init Pjax instance
|
||||||
|
pjax = new Pjax({
|
||||||
|
elements: [".js-Pjax"],
|
||||||
|
selectors: [".body", "title"],
|
||||||
|
cacheBust: true
|
||||||
|
});
|
||||||
|
console.log("Pjax initialized.", pjax);
|
||||||
|
|
||||||
|
// Init page content
|
||||||
|
initButtons();
|
||||||
|
});
|
||||||
138
example/forms.html
Normal file
138
example/forms.html
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Forms</title>
|
||||||
|
<script src="../pjax.js"></script>
|
||||||
|
<script src="example.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="body">
|
||||||
|
<h1>Forms</h1>
|
||||||
|
Hello. Try out the examples below and inspect the results in your browser's developer tools, or go to the <a href="index.html" class="js-Pjax">Index</a>.
|
||||||
|
|
||||||
|
<h3>GET form</h3>
|
||||||
|
|
||||||
|
<form action="" method="get" class="js-Pjax" id="get-form">
|
||||||
|
<label for="get-form-text">Text input:</label>
|
||||||
|
<input type="text" name="textInput" id="get-form-text" value="Foobar" />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<label for="get-form-number">Number input:</label>
|
||||||
|
<input type="number" name="numberInput" id="get-form-number" value="1" />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<label for="get-form-email">Email input:</label>
|
||||||
|
<input type="email" name="emailInput" id="get-form-email" value="example@example.com" />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<label for="get-form-textarea">Textarea:</label>
|
||||||
|
<textarea name="textarea" id="get-form-textarea">This is some text</textarea>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<label for="get-form-radio-1">Radio input:</label>
|
||||||
|
<input type="radio" name="radioInput" value="radio-1" checked id="get-form-radio-1" />
|
||||||
|
|
||||||
|
<label for="get-form-radio-2">Radio input alt:</label>
|
||||||
|
<input type="radio" name="radioInput" value="radio-2" id="get-form-radio-2" />
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<label for="get-form-checkbox">Checkbox input:</label>
|
||||||
|
<input type="checkbox" name="checkboxInput" checked id="get-form-checkbox" />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<label for="get-form-select">Select list:</label>
|
||||||
|
<select multiple name="select" id="get-form-select">
|
||||||
|
<option>
|
||||||
|
Option 1
|
||||||
|
</option>
|
||||||
|
<option>
|
||||||
|
Option 2
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<input type="submit" value="Submit" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<h3>POST form</h3>
|
||||||
|
|
||||||
|
<form action="" method="post" class="js-Pjax" id="post-form">
|
||||||
|
<label for="post-form-text">Text input:</label>
|
||||||
|
<input type="text" name="textInput" id="post-form-text" value="Foobar" />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<label for="post-form-number">Number input:</label>
|
||||||
|
<input type="number" name="numberInput" id="post-form-number" value="1" />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<label for="post-form-email">Email input:</label>
|
||||||
|
<input type="email" name="emailInput" id="post-form-email" value="example@example.com" />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<label for="post-form-textarea">Textarea:</label>
|
||||||
|
<textarea name="textarea" id="post-form-textarea">This is some text</textarea>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<label for="post-form-radio-1">Radio input:</label>
|
||||||
|
<input type="radio" name="radioInput" value="radio-1" checked id="post-form-radio-1" />
|
||||||
|
|
||||||
|
<label for="post-form-radio-2">Radio input alt:</label>
|
||||||
|
<input type="radio" name="radioInput" value="radio-2" id="post-form-radio-2" />
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<label for="post-form-checkbox">Checkbox input:</label>
|
||||||
|
<input type="checkbox" name="checkboxInput" checked id="post-form-checkbox" />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<label for="post-form-select">Select list:</label>
|
||||||
|
<select multiple name="select" id="post-form-select">
|
||||||
|
<option>
|
||||||
|
Option 1
|
||||||
|
</option>
|
||||||
|
<option>
|
||||||
|
Option 2
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<input type="submit" value="Submit" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
29
example/index.html
Normal file
29
example/index.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<!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 do nothing.
|
||||||
|
|
||||||
|
<h2>Manual URL loading</h2>
|
||||||
|
|
||||||
|
You can use Pjax's <i>loadUrl</i> method to manually load a URL. Click the buttons below to try it out!<br /><br />
|
||||||
|
|
||||||
|
<button data-manual-trigger>loadUrl with current options</button><br /><br />
|
||||||
|
<button data-manual-trigger data-manual-trigger-override="true">loadUrl with overridden options (no cache busting)</button>
|
||||||
|
|
||||||
|
<h2>Forms</h2>
|
||||||
|
|
||||||
|
You can submit GET or POST forms with Pjax! Go to the <a href="forms.html">form examples</a> to try it out.
|
||||||
|
|
||||||
|
</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>Page 2</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>Page 3</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>
|
||||||
56
gulpfile.js
56
gulpfile.js
@@ -1,56 +0,0 @@
|
|||||||
var pkg = require("./package.json")
|
|
||||||
, gulp = require("gulp")
|
|
||||||
, plumber = require("gulp-plumber")
|
|
||||||
|
|
||||||
///
|
|
||||||
// JS Lint
|
|
||||||
///
|
|
||||||
var jshint = require("gulp-jshint")
|
|
||||||
, jsonFiles = [".jshintrc", "*.json"]
|
|
||||||
, jsFiles = ["*.js", "src/**/*.js"]
|
|
||||||
gulp.task("scripts.lint", function() {
|
|
||||||
gulp.src([].concat(jsonFiles).concat(jsFiles))
|
|
||||||
.pipe(plumber())
|
|
||||||
.pipe(jshint(".jshintrc"))
|
|
||||||
.pipe(jshint.reporter("jshint-stylish"))
|
|
||||||
})
|
|
||||||
|
|
||||||
///
|
|
||||||
// JS Code Sniffing
|
|
||||||
///
|
|
||||||
var jscs = require("gulp-jscs")
|
|
||||||
gulp.task("scripts.cs", function() {
|
|
||||||
gulp.src(jsFiles)
|
|
||||||
.pipe(plumber())
|
|
||||||
.pipe(jscs())
|
|
||||||
})
|
|
||||||
|
|
||||||
// JS Alias
|
|
||||||
gulp.task("scripts", ["scripts.lint", "scripts.cs"])
|
|
||||||
|
|
||||||
///
|
|
||||||
// Watch
|
|
||||||
///
|
|
||||||
gulp.task("watch", function() {
|
|
||||||
gulp.watch(jsFiles, ["scripts"])
|
|
||||||
})
|
|
||||||
|
|
||||||
///
|
|
||||||
// Publish gh-branch
|
|
||||||
///
|
|
||||||
var buildBranch = require("buildbranch")
|
|
||||||
gulp.task("publish", ["test"], function(cb) {
|
|
||||||
buildBranch({folder: "src"}
|
|
||||||
, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
console.log(pkg.name + " published.")
|
|
||||||
cb()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Aliases
|
|
||||||
gulp.task("build", ["scripts"])
|
|
||||||
gulp.task("test", ["build"])
|
|
||||||
gulp.task("default", ["test", "watch"])
|
|
||||||
205
index.d.ts
vendored
Normal file
205
index.d.ts
vendored
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
declare class Pjax {
|
||||||
|
options: Pjax.IOptions;
|
||||||
|
|
||||||
|
constructor(options?: Partial<Pjax.IOptions>);
|
||||||
|
|
||||||
|
static switches: {
|
||||||
|
[key in DefaultSwitches]: Pjax.Switch
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if Pjax is supported by the environment.
|
||||||
|
*/
|
||||||
|
static isSupported: () => boolean;
|
||||||
|
|
||||||
|
log: VoidFunction;
|
||||||
|
|
||||||
|
getElements(el: Element | Document): NodeListOf<Element>;
|
||||||
|
|
||||||
|
parseDOM(el: Element | Document): void;
|
||||||
|
|
||||||
|
refresh: ElementFunction;
|
||||||
|
|
||||||
|
reload: VoidFunction;
|
||||||
|
|
||||||
|
attachLink(el: HTMLAnchorElement): void;
|
||||||
|
|
||||||
|
attachForm(el: HTMLFormElement): void;
|
||||||
|
|
||||||
|
forEachSelectors(cb: ElementFunction, context: Pjax, DOMcontext?: Element | Document): void;
|
||||||
|
|
||||||
|
switchesSelectors(selectors: string[], fromEl: Element | Document, toEl: Element | Document, options: Pjax.IOptions): void;
|
||||||
|
|
||||||
|
latestChance(href: string): void;
|
||||||
|
|
||||||
|
onSwitch: VoidFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the HTML from the response into the DOM.
|
||||||
|
*
|
||||||
|
* @param {string} html
|
||||||
|
* @param {Pjax.IOptions} options
|
||||||
|
*/
|
||||||
|
loadContent(html: string, options: Pjax.IOptions): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aborts an ongoing XHR request.
|
||||||
|
*
|
||||||
|
* @param {XMLHttpRequest} request
|
||||||
|
*/
|
||||||
|
abortRequest(request: XMLHttpRequest): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the XHR request.
|
||||||
|
*
|
||||||
|
* @param {string} location The URI for the request.
|
||||||
|
* @param {Pjax.IOptions | null} options
|
||||||
|
* @param {(requestText: string, request: XMLHttpRequest, href: string) => void} callback The callback to call when
|
||||||
|
* the response is received. The signature should match that of <code>handleResponse</code>.
|
||||||
|
* @returns {XMLHttpRequest}
|
||||||
|
*/
|
||||||
|
doRequest(location: string, options: Pjax.IOptions | null,
|
||||||
|
callback: (requestText: string, request: XMLHttpRequest, href: string) => void): XMLHttpRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the state, updates the URL if there were any redirects, then calls loadContent().
|
||||||
|
*
|
||||||
|
* @param {string} requestText The raw text of the response. Same as <code>request.responseText</code>.
|
||||||
|
* @param {XMLHttpRequest} request The XHR object.
|
||||||
|
* @param {string} href The original URI used to initiate the request.
|
||||||
|
* @param options The Pjax options object used for the request
|
||||||
|
*/
|
||||||
|
handleResponse(requestText: string, request: XMLHttpRequest, href: string, options?: Pjax.IOptions): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates the request by calling <code>doRequest()</code>.
|
||||||
|
* @param {string} href
|
||||||
|
* @param {Pjax.IOptions} options
|
||||||
|
*/
|
||||||
|
loadUrl(href: string, options?: Pjax.IOptions): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after all switches complete (even async).
|
||||||
|
*/
|
||||||
|
afterAllSwitches: VoidFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows reassignment of existing prototype functions to be able to do something before calling the original function.
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* pjax._handleResponse = pjax.handleResponse;
|
||||||
|
* pjax.handleResponse = (requestText: string, request: XMLHttpRequest, href: string) => {
|
||||||
|
* return pjax._handleResponse(requestText, request, href);
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
[key: string]: Function | Pjax.IOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare namespace Pjax {
|
||||||
|
export interface IOptions {
|
||||||
|
/**
|
||||||
|
* CSS selector to use to retrieve links to apply Pjax to.
|
||||||
|
*/
|
||||||
|
elements: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSS selectors for the elements to replace.
|
||||||
|
*/
|
||||||
|
selectors: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Objects containing callbacks that can be used to switch old elements with new elements.
|
||||||
|
* Keys should be one of the defined selectors.
|
||||||
|
*/
|
||||||
|
switches: StringKeyedObject<Switch>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are options that can be used during switches.
|
||||||
|
* Keys should be one of the defined selectors.
|
||||||
|
*/
|
||||||
|
switchesOptions: StringKeyedObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the use of pushState(). Disabling this will prevent Pjax from updating browser history.
|
||||||
|
* Internally, this option is used when a popstate event triggers Pjax (to not pushState() again).
|
||||||
|
*/
|
||||||
|
history: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* Set to false to disable this behavior.
|
||||||
|
*/
|
||||||
|
analytics: Function | false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set to an integer, this is the value (in px from the top of the page) to scroll to when a page is switched.
|
||||||
|
* When set to an array of 2 integers ([x, y]), this is the value to scroll both horizontally and vertically.
|
||||||
|
* Set this to false to disable scrolling, which will mean the page will stay in that same position it was before
|
||||||
|
* loading the new elements.
|
||||||
|
*/
|
||||||
|
scrollTo: number | [number, number] | false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set to true, attempt to restore the scroll position when navigating backwards or forwards.
|
||||||
|
*/
|
||||||
|
scrollRestoration: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set to true, append a timestamp query string segment to the requested URLs in order to skip browser cache.
|
||||||
|
*/
|
||||||
|
cacheBust: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables verbose mode.
|
||||||
|
*/
|
||||||
|
debug: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timeout in milliseconds for the XHR requests. Set to 0 to disable the timeout.
|
||||||
|
*/
|
||||||
|
timeout: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set to true, clicking on a link that points to the current URL will trigger a full page reload.
|
||||||
|
* (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).
|
||||||
|
*/
|
||||||
|
currentUrlFullReload: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hold the information to make an XHR request.
|
||||||
|
*/
|
||||||
|
requestOptions?: {
|
||||||
|
requestUrl?: string;
|
||||||
|
requestMethod?: string;
|
||||||
|
requestParams?: IRequestParams[];
|
||||||
|
formData?: FormData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Switch = (oldEl: Element, newEl: Element, options?: IOptions, switchesOptions?: StringKeyedObject) => void;
|
||||||
|
|
||||||
|
export interface IRequestParams {
|
||||||
|
name: string,
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StringKeyedObject<T = any> {
|
||||||
|
[key: string]: T
|
||||||
|
}
|
||||||
|
|
||||||
|
type ElementFunction = (el: Element) => void;
|
||||||
|
|
||||||
|
declare enum DefaultSwitches {
|
||||||
|
innerHTML = "innerHTML",
|
||||||
|
ouetrHTML = "outerHTML",
|
||||||
|
sideBySide = "sideBySide",
|
||||||
|
replaceNode = "replaceNode"
|
||||||
|
}
|
||||||
|
|
||||||
|
export = Pjax;
|
||||||
318
index.js
Normal file
318
index.js
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
var executeScripts = require("./lib/execute-scripts");
|
||||||
|
var forEachEls = require("./lib/foreach-els");
|
||||||
|
var parseOptions = require("./lib/parse-options");
|
||||||
|
var switches = require("./lib/switches");
|
||||||
|
var newUid = require("./lib/uniqueid");
|
||||||
|
|
||||||
|
var on = require("./lib/events/on");
|
||||||
|
var trigger = require("./lib/events/trigger");
|
||||||
|
|
||||||
|
var clone = require("./lib/util/clone");
|
||||||
|
var contains = require("./lib/util/contains");
|
||||||
|
var extend = require("./lib/util/extend");
|
||||||
|
var noop = require("./lib/util/noop");
|
||||||
|
|
||||||
|
var Pjax = function(options) {
|
||||||
|
this.state = {
|
||||||
|
numPendingSwitches: 0,
|
||||||
|
href: null,
|
||||||
|
options: null
|
||||||
|
};
|
||||||
|
|
||||||
|
this.options = parseOptions(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;
|
||||||
|
// Since state already exists, prevent it from being pushed again
|
||||||
|
opt.history = false;
|
||||||
|
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"),
|
||||||
|
|
||||||
|
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"),
|
||||||
|
|
||||||
|
attachForm: require("./lib/proto/attach-form"),
|
||||||
|
|
||||||
|
forEachSelectors: function(cb, context, DOMcontext) {
|
||||||
|
return require("./lib/foreach-selectors").bind(this)(
|
||||||
|
this.options.selectors,
|
||||||
|
cb,
|
||||||
|
context,
|
||||||
|
DOMcontext
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
switchSelectors: function(selectors, fromEl, toEl, options) {
|
||||||
|
return require("./lib/switches-selectors").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) {
|
||||||
|
if (typeof html !== "string") {
|
||||||
|
trigger(document, "pjax:complete pjax:error", options);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(document, this.options.selectors, document.activeElement)
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
document.activeElement.blur();
|
||||||
|
} catch (e) {} // eslint-disable-line no-empty
|
||||||
|
}
|
||||||
|
|
||||||
|
this.switchSelectors(this.options.selectors, tmpEl, document, options);
|
||||||
|
},
|
||||||
|
|
||||||
|
abortRequest: require("./lib/abort-request"),
|
||||||
|
|
||||||
|
doRequest: require("./lib/send-request"),
|
||||||
|
|
||||||
|
handleResponse: require("./lib/proto/handle-response"),
|
||||||
|
|
||||||
|
loadUrl: function(href, options) {
|
||||||
|
options =
|
||||||
|
typeof options === "object"
|
||||||
|
? extend({}, this.options, options)
|
||||||
|
: clone(this.options);
|
||||||
|
|
||||||
|
this.log("load href", href, options);
|
||||||
|
|
||||||
|
// Abort any previous request
|
||||||
|
this.abortRequest(this.request);
|
||||||
|
|
||||||
|
trigger(document, "pjax:send", options);
|
||||||
|
|
||||||
|
// Do the request
|
||||||
|
this.request = this.doRequest(
|
||||||
|
href,
|
||||||
|
options,
|
||||||
|
this.handleResponse.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");
|
||||||
|
|
||||||
|
// arguably could do `if( require("./lib/is-supported")()) {` 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
48
lib/eval-script.js
Normal file
48
lib/eval-script.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
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";
|
||||||
|
script.id = el.id;
|
||||||
|
|
||||||
|
/* istanbul ignore if */
|
||||||
|
if (src !== "") {
|
||||||
|
script.src = src;
|
||||||
|
script.async = false; // force synchronous loading of peripheral JS
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code !== "") {
|
||||||
|
try {
|
||||||
|
script.appendChild(document.createTextNode(code));
|
||||||
|
} catch (e) {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
// old IEs have funky script nodes
|
||||||
|
script.text = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute
|
||||||
|
parent.appendChild(script);
|
||||||
|
// avoid pollution only in head or body tags
|
||||||
|
if (
|
||||||
|
(parent instanceof HTMLHeadElement || parent instanceof HTMLBodyElement) &&
|
||||||
|
parent.contains(script)
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
13
lib/foreach-els.js
Normal file
13
lib/foreach-els.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/* 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);
|
||||||
|
});
|
||||||
|
};
|
||||||
12
lib/is-supported.js
Normal file
12
lib/is-supported.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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)/
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
53
lib/parse-options.js
Normal file
53
lib/parse-options.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/* 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 =
|
||||||
|
typeof options.history === "undefined" ? true : options.history;
|
||||||
|
options.analytics =
|
||||||
|
typeof options.analytics === "function" || options.analytics === false
|
||||||
|
? options.analytics
|
||||||
|
: defaultAnalytics;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
function defaultAnalytics() {
|
||||||
|
if (window._gaq) {
|
||||||
|
_gaq.push(["_trackPageview"]);
|
||||||
|
}
|
||||||
|
if (window.ga) {
|
||||||
|
ga("send", "pageview", { page: location.pathname, title: document.title });
|
||||||
|
}
|
||||||
|
}
|
||||||
139
lib/proto/attach-form.js
Normal file
139
lib/proto/attach-form.js
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
var on = require("../events/on");
|
||||||
|
var clone = require("../util/clone");
|
||||||
|
|
||||||
|
var attrState = "data-pjax-state";
|
||||||
|
|
||||||
|
var formAction = function(el, event) {
|
||||||
|
if (isDefaultPrevented(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
var attrValue = checkIfShouldAbort(virtLinkElement, options);
|
||||||
|
if (attrValue) {
|
||||||
|
el.setAttribute(attrState, attrValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (el.enctype === "multipart/form-data") {
|
||||||
|
options.requestOptions.formData = new FormData(el);
|
||||||
|
} else {
|
||||||
|
options.requestOptions.requestParams = parseFormElements(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
el.setAttribute(attrState, "submit");
|
||||||
|
|
||||||
|
options.triggerElement = el;
|
||||||
|
this.loadUrl(virtLinkElement.href, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseFormElements(el) {
|
||||||
|
var requestParams = [];
|
||||||
|
var formElements = el.elements;
|
||||||
|
|
||||||
|
for (var i = 0; i < formElements.length; i++) {
|
||||||
|
var element = formElements[i];
|
||||||
|
var tagName = element.tagName.toLowerCase();
|
||||||
|
// jscs:disable disallowImplicitTypeConversion
|
||||||
|
if (
|
||||||
|
!!element.name &&
|
||||||
|
element.attributes !== undefined &&
|
||||||
|
tagName !== "button"
|
||||||
|
) {
|
||||||
|
// jscs:enable disallowImplicitTypeConversion
|
||||||
|
var type = element.attributes.type;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!type ||
|
||||||
|
(type.value !== "checkbox" && type.value !== "radio") ||
|
||||||
|
element.checked
|
||||||
|
) {
|
||||||
|
// Build array of values to submit
|
||||||
|
var values = [];
|
||||||
|
|
||||||
|
if (tagName === "select") {
|
||||||
|
var opt;
|
||||||
|
|
||||||
|
for (var j = 0; j < element.options.length; j++) {
|
||||||
|
opt = element.options[j];
|
||||||
|
if (opt.selected && !opt.disabled) {
|
||||||
|
values.push(opt.hasAttribute("value") ? opt.value : opt.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
values.push(element.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var k = 0; k < values.length; k++) {
|
||||||
|
requestParams.push({
|
||||||
|
name: encodeURIComponent(element.name),
|
||||||
|
value: encodeURIComponent(values[k])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkIfShouldAbort(virtLinkElement, options) {
|
||||||
|
// Ignore external links.
|
||||||
|
if (
|
||||||
|
virtLinkElement.protocol !== window.location.protocol ||
|
||||||
|
virtLinkElement.host !== window.location.host
|
||||||
|
) {
|
||||||
|
return "external";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore click if we are on an anchor on the same page
|
||||||
|
if (
|
||||||
|
virtLinkElement.hash &&
|
||||||
|
virtLinkElement.href.replace(virtLinkElement.hash, "") ===
|
||||||
|
window.location.href.replace(location.hash, "")
|
||||||
|
) {
|
||||||
|
return "anchor";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore empty anchor "foo.html#"
|
||||||
|
if (virtLinkElement.href === window.location.href.split("#")[0] + "#") {
|
||||||
|
return "anchor-empty";
|
||||||
|
}
|
||||||
|
|
||||||
|
// if declared as a full reload, just normally submit the form
|
||||||
|
if (
|
||||||
|
options.currentUrlFullReload &&
|
||||||
|
virtLinkElement.href === window.location.href.split("#")[0]
|
||||||
|
) {
|
||||||
|
return "reload";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isDefaultPrevented = function(event) {
|
||||||
|
return event.defaultPrevented || event.returnValue === false;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = function(el) {
|
||||||
|
var that = this;
|
||||||
|
|
||||||
|
el.setAttribute(attrState, "");
|
||||||
|
|
||||||
|
on(el, "submit", function(event) {
|
||||||
|
formAction.call(that, el, event);
|
||||||
|
});
|
||||||
|
};
|
||||||
99
lib/proto/attach-link.js
Normal file
99
lib/proto/attach-link.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
var on = require("../events/on");
|
||||||
|
var clone = require("../util/clone");
|
||||||
|
|
||||||
|
var attrState = "data-pjax-state";
|
||||||
|
|
||||||
|
var linkAction = function(el, event) {
|
||||||
|
if (isDefaultPrevented(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
var attrValue = checkIfShouldAbort(el, event);
|
||||||
|
if (attrValue) {
|
||||||
|
el.setAttribute(attrState, attrValue);
|
||||||
|
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(attrState, "reload");
|
||||||
|
this.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
el.setAttribute(attrState, "load");
|
||||||
|
|
||||||
|
options.triggerElement = el;
|
||||||
|
this.loadUrl(el.href, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
function checkIfShouldAbort(el, event) {
|
||||||
|
// 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 "modifier";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
) {
|
||||||
|
return "external";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore anchors on the same page (keep native behavior)
|
||||||
|
if (
|
||||||
|
el.hash &&
|
||||||
|
el.href.replace(el.hash, "") ===
|
||||||
|
window.location.href.replace(location.hash, "")
|
||||||
|
) {
|
||||||
|
return "anchor";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore empty anchor "foo.html#"
|
||||||
|
if (el.href === window.location.href.split("#")[0] + "#") {
|
||||||
|
return "anchor-empty";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isDefaultPrevented = function(event) {
|
||||||
|
return event.defaultPrevented || event.returnValue === false;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = function(el) {
|
||||||
|
var that = this;
|
||||||
|
|
||||||
|
el.setAttribute(attrState, "");
|
||||||
|
|
||||||
|
on(el, "click", function(event) {
|
||||||
|
linkAction.call(that, el, event);
|
||||||
|
});
|
||||||
|
|
||||||
|
on(
|
||||||
|
el,
|
||||||
|
"keyup",
|
||||||
|
function(event) {
|
||||||
|
if (event.keyCode === 13) {
|
||||||
|
linkAction.call(that, el, event);
|
||||||
|
}
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
};
|
||||||
71
lib/proto/handle-response.js
Normal file
71
lib/proto/handle-response.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
var clone = require("../util/clone");
|
||||||
|
var newUid = require("../uniqueid");
|
||||||
|
var trigger = require("../events/trigger");
|
||||||
|
|
||||||
|
module.exports = function(responseText, request, href, options) {
|
||||||
|
options = clone(options || this.options);
|
||||||
|
options.request = request;
|
||||||
|
|
||||||
|
// Fail if unable to load HTML via AJAX
|
||||||
|
if (responseText === 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.href
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for redirects
|
||||||
|
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 = options;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.loadContent(responseText, options);
|
||||||
|
} catch (e) {
|
||||||
|
trigger(document, "pjax:error", options);
|
||||||
|
|
||||||
|
if (!this.options.debug) {
|
||||||
|
if (console && console.error) {
|
||||||
|
console.error("Pjax switch fail: ", e);
|
||||||
|
}
|
||||||
|
return this.latestChance(href);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
22
lib/proto/parse-element.js
Normal file
22
lib/proto/parse-element.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
var attrState = "data-pjax-state";
|
||||||
|
|
||||||
|
module.exports = function(el) {
|
||||||
|
switch (el.tagName.toLowerCase()) {
|
||||||
|
case "a":
|
||||||
|
// only attach link if el does not already have link attached
|
||||||
|
if (!el.hasAttribute(attrState)) {
|
||||||
|
this.attachLink(el);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "form":
|
||||||
|
// only attach link if el does not already have link attached
|
||||||
|
if (!el.hasAttribute(attrState)) {
|
||||||
|
this.attachForm(el);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw "Pjax can only be applied on <a> or <form> submit";
|
||||||
|
}
|
||||||
|
};
|
||||||
86
lib/send-request.js
Normal file
86
lib/send-request.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
var updateQueryString = require("./util/update-query-string");
|
||||||
|
|
||||||
|
module.exports = function(location, options, callback) {
|
||||||
|
options = options || {};
|
||||||
|
var queryString;
|
||||||
|
var requestOptions = options.requestOptions || {};
|
||||||
|
var requestMethod = (requestOptions.requestMethod || "GET").toUpperCase();
|
||||||
|
var requestParams = requestOptions.requestParams || null;
|
||||||
|
var formData = requestOptions.formData || null;
|
||||||
|
var requestPayload = null;
|
||||||
|
var request = new XMLHttpRequest();
|
||||||
|
var timeout = options.timeout || 0;
|
||||||
|
|
||||||
|
request.onreadystatechange = function() {
|
||||||
|
if (request.readyState === 4) {
|
||||||
|
if (request.status === 200) {
|
||||||
|
callback(request.responseText, request, location, options);
|
||||||
|
} else if (request.status !== 0) {
|
||||||
|
callback(null, request, location, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onerror = function(e) {
|
||||||
|
console.log(e);
|
||||||
|
callback(null, request, location, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
request.ontimeout = function() {
|
||||||
|
callback(null, request, location, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prepare the request payload for forms, if available
|
||||||
|
if (requestParams && requestParams.length) {
|
||||||
|
// Build query string
|
||||||
|
queryString = requestParams
|
||||||
|
.map(function(param) {
|
||||||
|
return param.name + "=" + param.value;
|
||||||
|
})
|
||||||
|
.join("&");
|
||||||
|
|
||||||
|
switch (requestMethod) {
|
||||||
|
case "GET":
|
||||||
|
// Reset query string to avoid an issue with repeat submissions where checkboxes that were
|
||||||
|
// previously checked are incorrectly preserved
|
||||||
|
location = location.split("?")[0];
|
||||||
|
|
||||||
|
// Append new query string
|
||||||
|
location += "?" + queryString;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "POST":
|
||||||
|
// Send query string as request payload
|
||||||
|
requestPayload = queryString;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (formData) {
|
||||||
|
requestPayload = formData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a timestamp as part of the query string if cache busting is enabled
|
||||||
|
if (options.cacheBust) {
|
||||||
|
location = updateQueryString(location, "t", Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
request.open(requestMethod, location, true);
|
||||||
|
request.timeout = timeout;
|
||||||
|
request.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
||||||
|
request.setRequestHeader("X-PJAX", "true");
|
||||||
|
request.setRequestHeader(
|
||||||
|
"X-PJAX-Selectors",
|
||||||
|
JSON.stringify(options.selectors)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Send the proper header information for POST forms
|
||||||
|
if (requestPayload && requestMethod === "POST" && !formData) {
|
||||||
|
request.setRequestHeader(
|
||||||
|
"Content-Type",
|
||||||
|
"application/x-www-form-urlencoded"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.send(requestPayload);
|
||||||
|
|
||||||
|
return request;
|
||||||
|
};
|
||||||
59
lib/switches-selectors.js
Normal file
59
lib/switches-selectors.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
};
|
||||||
140
lib/switches.js
Normal file
140
lib/switches.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
var on = require("./events/on");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
outerHTML: function(oldEl, newEl) {
|
||||||
|
oldEl.outerHTML = newEl.outerHTML;
|
||||||
|
this.onSwitch();
|
||||||
|
},
|
||||||
|
|
||||||
|
innerHTML: function(oldEl, newEl) {
|
||||||
|
oldEl.innerHTML = newEl.innerHTML;
|
||||||
|
|
||||||
|
if (newEl.className === "") {
|
||||||
|
oldEl.removeAttribute("class");
|
||||||
|
} else {
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Equivalent to outerHTML(), but doesn't require switchElementsAlt() for <head> and <body>
|
||||||
|
replaceNode: function(oldEl, newEl) {
|
||||||
|
oldEl.parentNode.replaceChild(newEl, oldEl);
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
})();
|
||||||
13
lib/util/clone.js
Normal file
13
lib/util/clone.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module.exports = function(obj) {
|
||||||
|
/* istanbul ignore if */
|
||||||
|
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;
|
||||||
|
};
|
||||||
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;
|
||||||
|
};
|
||||||
21
lib/util/extend.js
Normal file
21
lib/util/extend.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
module.exports = function(target) {
|
||||||
|
if (target == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var to = Object(target);
|
||||||
|
|
||||||
|
for (var i = 1; i < arguments.length; i++) {
|
||||||
|
var source = arguments[i];
|
||||||
|
|
||||||
|
if (source != null) {
|
||||||
|
for (var key in source) {
|
||||||
|
// Avoid bugs when hasOwnProperty is shadowed
|
||||||
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||||
|
to[key] = source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return to;
|
||||||
|
};
|
||||||
1
lib/util/noop.js
Normal file
1
lib/util/noop.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = function() {};
|
||||||
9
lib/util/update-query-string.js
Normal file
9
lib/util/update-query-string.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module.exports = function(uri, key, value) {
|
||||||
|
var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
|
||||||
|
var separator = uri.indexOf("?") !== -1 ? "&" : "?";
|
||||||
|
if (uri.match(re)) {
|
||||||
|
return uri.replace(re, "$1" + key + "=" + value + "$2");
|
||||||
|
} else {
|
||||||
|
return uri + separator + key + "=" + value;
|
||||||
|
}
|
||||||
|
};
|
||||||
9354
package-lock.json
generated
Normal file
9354
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
82
package.json
82
package.json
@@ -1,36 +1,72 @@
|
|||||||
{
|
{
|
||||||
"name": "pjax",
|
"name": "pjax",
|
||||||
"version": "0.1.2",
|
"version": "0.2.8",
|
||||||
"description": "Boost browsing experience using Ajax navigation (+ Push state)",
|
"description": "Easily enable fast AJAX navigation on any website (using pushState + XHR)",
|
||||||
"main": "src/pjax.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/MoOx/pjax.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"pjax",
|
"pjax",
|
||||||
"push",
|
"pushstate",
|
||||||
"state",
|
|
||||||
"ajax",
|
"ajax",
|
||||||
"navigation",
|
"navigation",
|
||||||
"transition",
|
"transition",
|
||||||
"animation"
|
"animation"
|
||||||
],
|
],
|
||||||
"author": "MoOx <m@moox.io>",
|
"repository": "https://github.com/MoOx/pjax.git",
|
||||||
|
"author": "Maxime Thirouin",
|
||||||
|
"contributors": [
|
||||||
|
"BehindTheMath",
|
||||||
|
"Robin North (http://robinnorth.co.uk)"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"main": "index.js",
|
||||||
"url": "https://github.com/MoOx/pjax/issues"
|
"files": [
|
||||||
},
|
"index.js",
|
||||||
"homepage": "https://github.com/MoOx/pjax",
|
"lib",
|
||||||
|
"pjax.js",
|
||||||
|
"pjax.min.js",
|
||||||
|
"index.d.ts"
|
||||||
|
],
|
||||||
|
"types": "index.d.ts",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jshint-stylish": "^0.1.5",
|
"browserify": "^15.0.0",
|
||||||
"gulp-jscs": "^0.3.2",
|
"eslint": "^5.7.0",
|
||||||
"gulp-plumber": "^0.5.6",
|
"eslint-config-i-am-meticulous": "^11.0.0",
|
||||||
"gulp": "^3.5.6",
|
"husky": "^1.2.0",
|
||||||
"gulp-jshint": "^1.5.1",
|
"jsdom": "^11.5.1",
|
||||||
"buildbranch": "0.0.1"
|
"jsdom-global": "^3.0.2",
|
||||||
|
"lint-staged": "^8.1.0",
|
||||||
|
"npmpub": "^3.1.0",
|
||||||
|
"nyc": "^11.4.1",
|
||||||
|
"opn-cli": "^3.1.0",
|
||||||
|
"prettier": "^1.14.3",
|
||||||
|
"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": "eslint .",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"pre-commit": "npm test && lint-staged"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.js": [
|
||||||
|
"eslint --fix",
|
||||||
|
"prettier --write",
|
||||||
|
"git add"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
605
src/pjax.js
605
src/pjax.js
@@ -1,605 +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.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]|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()
|
|
||||||
|
|
||||||
// don’t do "nothing" if user try to reload the page
|
|
||||||
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.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 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"
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
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 = " 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
|
|
||||||
}
|
|
||||||
|
|
||||||
}));
|
|
||||||
55
tests/lib/abort-request.js
Normal file
55
tests/lib/abort-request.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
49
tests/lib/eval-scripts.js
Normal file
49
tests/lib/eval-scripts.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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')";
|
||||||
|
var bodyText = "document.write hasn't been executed";
|
||||||
|
document.body.text = bodyText;
|
||||||
|
evalScript(script);
|
||||||
|
t.equal(document.body.text, bodyText, "document.write hasn't been executed");
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape(
|
||||||
|
"evalScript should not throw an error if the script removed itself",
|
||||||
|
function(t) {
|
||||||
|
var script = document.createElement("script");
|
||||||
|
script.id = "myScript";
|
||||||
|
script.innerHTML =
|
||||||
|
"const script = document.querySelector('#myScript');" +
|
||||||
|
"script.parentNode.removeChild(script);";
|
||||||
|
|
||||||
|
try {
|
||||||
|
evalScript(script);
|
||||||
|
|
||||||
|
t.pass("Missing script tested successfully");
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
t.fail("Attempted to remove missing script");
|
||||||
|
}
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
}
|
||||||
|
);
|
||||||
177
tests/lib/events.js
Normal file
177
tests/lib/events.js
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
49
tests/lib/execute-scripts.js
Normal file
49
tests/lib/execute-scripts.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
var tape = require("tape");
|
||||||
|
|
||||||
|
var executeScripts = require("../../lib/execute-scripts");
|
||||||
|
|
||||||
|
tape(
|
||||||
|
"test executeScripts method when the script tag is inside a container",
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
tape("test executeScripts method with just a script tag", function(t) {
|
||||||
|
document.body.className = "";
|
||||||
|
|
||||||
|
var script = document.createElement("script");
|
||||||
|
script.innerHTML = "document.body.className = 'executed correctly';";
|
||||||
|
|
||||||
|
t.equal(document.body.className, "", "script hasn't been executed yet");
|
||||||
|
executeScripts(script);
|
||||||
|
t.equal(
|
||||||
|
document.body.className,
|
||||||
|
"executed correctly",
|
||||||
|
"script has been properly executed"
|
||||||
|
);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
60
tests/lib/foreach-els.js
Normal file
60
tests/lib/foreach-els.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
40
tests/lib/foreach-selectors.js
Normal file
40
tests/lib/foreach-selectors.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
11
tests/lib/is-supported.js
Normal file
11
tests/lib/is-supported.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
50
tests/lib/parse-options.js
Normal file
50
tests/lib/parse-options.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
var tape = require("tape");
|
||||||
|
|
||||||
|
var parseOptions = require("../../lib/parse-options.js");
|
||||||
|
|
||||||
|
tape("test parse initalization options function", function(t) {
|
||||||
|
t.test("- default options", function(t) {
|
||||||
|
var pjax = {};
|
||||||
|
pjax.options = parseOptions({});
|
||||||
|
|
||||||
|
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 = {};
|
||||||
|
pjax.options = parseOptions({ 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 = {};
|
||||||
|
pjax.options = parseOptions({ scrollTo: false });
|
||||||
|
|
||||||
|
t.deepEqual(pjax.options.scrollTo, false);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
231
tests/lib/proto/attach-form.js
Normal file
231
tests/lib/proto/attach-form.js
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
var tape = require("tape");
|
||||||
|
|
||||||
|
var on = require("../../../lib/events/on");
|
||||||
|
var trigger = require("../../../lib/events/trigger");
|
||||||
|
var attachForm = require("../../../lib/proto/attach-form");
|
||||||
|
|
||||||
|
var attr = "data-pjax-state";
|
||||||
|
|
||||||
|
tape("test attach form prototype method", function(t) {
|
||||||
|
var form = document.createElement("form");
|
||||||
|
var loadUrlCalled = false;
|
||||||
|
|
||||||
|
attachForm.call(
|
||||||
|
{
|
||||||
|
options: {
|
||||||
|
currentUrlFullReload: true
|
||||||
|
},
|
||||||
|
loadUrl: function() {
|
||||||
|
loadUrlCalled = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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", "internal anchor stop behavior");
|
||||||
|
|
||||||
|
window.location.hash = "#anchor";
|
||||||
|
form.action = internalUri + "#another-anchor";
|
||||||
|
trigger(form, "submit");
|
||||||
|
t.equal(form.getAttribute(attr), "anchor", "different 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 = window.location.href;
|
||||||
|
trigger(form, "submit");
|
||||||
|
t.equal(
|
||||||
|
form.getAttribute(attr),
|
||||||
|
"reload",
|
||||||
|
"submitting when currentUrlFullReload is true will submit normally, without XHR"
|
||||||
|
);
|
||||||
|
t.equal(loadUrlCalled, false, "loadUrl() not called");
|
||||||
|
|
||||||
|
form.action =
|
||||||
|
window.location.protocol + "//" + window.location.host + "/internal";
|
||||||
|
form.method = "POST";
|
||||||
|
trigger(form, "submit");
|
||||||
|
t.equal(
|
||||||
|
form.getAttribute(attr),
|
||||||
|
"submit",
|
||||||
|
"triggering a POST request to the next page"
|
||||||
|
);
|
||||||
|
t.equal(loadUrlCalled, true, "loadUrl() called correctly");
|
||||||
|
|
||||||
|
loadUrlCalled = false;
|
||||||
|
form.setAttribute(attr, "");
|
||||||
|
form.action =
|
||||||
|
window.location.protocol + "//" + window.location.host + "/internal";
|
||||||
|
form.method = "GET";
|
||||||
|
trigger(form, "submit");
|
||||||
|
t.equal(
|
||||||
|
form.getAttribute(attr),
|
||||||
|
"submit",
|
||||||
|
"triggering a GET request to the next page"
|
||||||
|
);
|
||||||
|
t.equal(loadUrlCalled, true, "loadUrl() called correctly");
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("test attach form preventDefaulted events", function(t) {
|
||||||
|
var loadUrlCalled = false;
|
||||||
|
var form = document.createElement("form");
|
||||||
|
|
||||||
|
// This needs to be before the call to attachForm()
|
||||||
|
on(form, "submit", function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
attachForm.call(
|
||||||
|
{
|
||||||
|
options: {},
|
||||||
|
loadUrl: function() {
|
||||||
|
loadUrlCalled = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
form
|
||||||
|
);
|
||||||
|
|
||||||
|
form.action = "#";
|
||||||
|
trigger(form, "submit");
|
||||||
|
t.equal(
|
||||||
|
loadUrlCalled,
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("test form elements parsed correctly", function(t) {
|
||||||
|
t.plan(1);
|
||||||
|
|
||||||
|
var form = document.createElement("form");
|
||||||
|
var input = document.createElement("input");
|
||||||
|
input.name = "input";
|
||||||
|
input.value = "value";
|
||||||
|
form.appendChild(input);
|
||||||
|
|
||||||
|
var params = [
|
||||||
|
{
|
||||||
|
name: "input",
|
||||||
|
value: "value"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
var pjax = {
|
||||||
|
options: {},
|
||||||
|
loadUrl: function(href, options) {
|
||||||
|
t.same(
|
||||||
|
options.requestOptions.requestParams,
|
||||||
|
params,
|
||||||
|
"form elements parsed correctly"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
attachForm.call(pjax, form);
|
||||||
|
|
||||||
|
form.action =
|
||||||
|
window.location.protocol + "//" + window.location.host + "/internal";
|
||||||
|
|
||||||
|
trigger(form, "submit");
|
||||||
|
// see loadUrl defined above
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape('test form.enctype="multipart/form-data"', function(t) {
|
||||||
|
t.plan(4);
|
||||||
|
|
||||||
|
var form = document.createElement("form");
|
||||||
|
form.enctype = "multipart/form-data";
|
||||||
|
var input = document.createElement("input");
|
||||||
|
input.name = "input";
|
||||||
|
input.value = "value";
|
||||||
|
form.appendChild(input);
|
||||||
|
|
||||||
|
var pjax = {
|
||||||
|
options: {},
|
||||||
|
loadUrl: function(href, options) {
|
||||||
|
t.equals(
|
||||||
|
options.requestOptions.requestParams,
|
||||||
|
undefined,
|
||||||
|
"form elements not parsed manually"
|
||||||
|
);
|
||||||
|
t.true(
|
||||||
|
options.requestOptions.formData instanceof FormData,
|
||||||
|
"requestOptions.formData is a FormData"
|
||||||
|
);
|
||||||
|
t.equals(
|
||||||
|
Array.from(options.requestOptions.formData.entries()).length,
|
||||||
|
1,
|
||||||
|
"correct number of FormData elements"
|
||||||
|
);
|
||||||
|
t.equals(
|
||||||
|
options.requestOptions.formData.get("input"),
|
||||||
|
"value",
|
||||||
|
"FormData element value set correctly"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
attachForm.call(pjax, form);
|
||||||
|
|
||||||
|
form.action =
|
||||||
|
window.location.protocol + "//" + window.location.host + "/internal";
|
||||||
|
|
||||||
|
trigger(form, "submit");
|
||||||
|
// see loadUrl defined above
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
190
tests/lib/proto/attach-link.js
Normal file
190
tests/lib/proto/attach-link.js
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
var tape = require("tape");
|
||||||
|
|
||||||
|
var on = require("../../../lib/events/on");
|
||||||
|
var trigger = require("../../../lib/events/trigger");
|
||||||
|
var attachLink = require("../../../lib/proto/attach-link");
|
||||||
|
|
||||||
|
var attr = "data-pjax-state";
|
||||||
|
|
||||||
|
tape("test attach link prototype method", function(t) {
|
||||||
|
var a = document.createElement("a");
|
||||||
|
var loadUrlCalled = false;
|
||||||
|
|
||||||
|
attachLink.call(
|
||||||
|
{
|
||||||
|
options: {},
|
||||||
|
loadUrl: function() {
|
||||||
|
loadUrlCalled = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
a
|
||||||
|
);
|
||||||
|
|
||||||
|
var internalUri =
|
||||||
|
window.location.protocol +
|
||||||
|
"//" +
|
||||||
|
window.location.host +
|
||||||
|
window.location.pathname +
|
||||||
|
window.location.search;
|
||||||
|
|
||||||
|
a.href = internalUri;
|
||||||
|
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");
|
||||||
|
|
||||||
|
window.location.hash = "#anchor";
|
||||||
|
a.href = internalUri + "#anchor";
|
||||||
|
trigger(a, "click");
|
||||||
|
t.equal(a.getAttribute(attr), "anchor", "internal anchor stop behavior");
|
||||||
|
|
||||||
|
a.href = internalUri + "#another-anchor";
|
||||||
|
trigger(a, "click");
|
||||||
|
t.equal(a.getAttribute(attr), "anchor", "different 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 = window.location.protocol + "//" + window.location.host + "/internal";
|
||||||
|
trigger(a, "click");
|
||||||
|
t.equals(
|
||||||
|
a.getAttribute(attr),
|
||||||
|
"load",
|
||||||
|
"triggering an internal link sets the state attribute to 'load'"
|
||||||
|
);
|
||||||
|
t.equals(
|
||||||
|
loadUrlCalled,
|
||||||
|
true,
|
||||||
|
"triggering an internal link actually loads the page"
|
||||||
|
);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("test attach link preventDefaulted events", function(t) {
|
||||||
|
var loadUrlCalled = false;
|
||||||
|
var a = document.createElement("a");
|
||||||
|
|
||||||
|
// This needs to be before the call to attachLink()
|
||||||
|
on(a, "click", function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
attachLink.call(
|
||||||
|
{
|
||||||
|
options: {},
|
||||||
|
loadUrl: function() {
|
||||||
|
loadUrlCalled = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
a
|
||||||
|
);
|
||||||
|
|
||||||
|
a.href = "#";
|
||||||
|
trigger(a, "click");
|
||||||
|
t.equal(
|
||||||
|
loadUrlCalled,
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("test link triggered by keyboard", function(t) {
|
||||||
|
var a = document.createElement("a");
|
||||||
|
var pjax = {
|
||||||
|
options: {},
|
||||||
|
loadUrl: function() {
|
||||||
|
t.equal(
|
||||||
|
a.getAttribute(attr),
|
||||||
|
"load",
|
||||||
|
"triggering a internal link actually loads the page"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
t.plan(3);
|
||||||
|
|
||||||
|
attachLink.call(pjax, a);
|
||||||
|
|
||||||
|
a.href = window.location.protocol + "//" + window.location.host + "/internal";
|
||||||
|
|
||||||
|
trigger(a, "keyup", { keyCode: 14 });
|
||||||
|
t.equal(
|
||||||
|
a.getAttribute(attr),
|
||||||
|
"",
|
||||||
|
"keycode other than 13 doesn't trigger anything"
|
||||||
|
);
|
||||||
|
|
||||||
|
trigger(a, "keyup", { keyCode: 13, metaKey: true });
|
||||||
|
t.equal(a.getAttribute(attr), "modifier", "event key modifier stop behavior");
|
||||||
|
|
||||||
|
trigger(a, "keyup", { keyCode: 13 });
|
||||||
|
// see loadUrl defined above
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape(
|
||||||
|
"test link with the same URL as the current one, when currentUrlFullReload set to true",
|
||||||
|
function(t) {
|
||||||
|
var a = document.createElement("a");
|
||||||
|
var pjax = {
|
||||||
|
options: {
|
||||||
|
currentUrlFullReload: true
|
||||||
|
},
|
||||||
|
reload: function() {
|
||||||
|
t.pass("this.reload() was called correctly");
|
||||||
|
},
|
||||||
|
loadUrl: function() {
|
||||||
|
t.fail("loadUrl() was called wrongly");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
t.plan(2);
|
||||||
|
|
||||||
|
attachLink.call(pjax, a);
|
||||||
|
|
||||||
|
a.href = window.location.href;
|
||||||
|
|
||||||
|
trigger(a, "click");
|
||||||
|
t.equal(a.getAttribute(attr), "reload", "reload stop behavior");
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
}
|
||||||
|
);
|
||||||
293
tests/lib/proto/handle-response.js
Normal file
293
tests/lib/proto/handle-response.js
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
var tape = require("tape");
|
||||||
|
|
||||||
|
var handleResponse = require("../../../lib/proto/handle-response");
|
||||||
|
var noop = require("../../../lib/util/noop");
|
||||||
|
var loadContent = require("../../../index").prototype.loadContent;
|
||||||
|
|
||||||
|
var href = "https://example.org/";
|
||||||
|
|
||||||
|
var storeEventHandler;
|
||||||
|
var pjaxErrorEventTriggerTest;
|
||||||
|
|
||||||
|
tape("test events triggered when handleResponse(false) is called", function(t) {
|
||||||
|
t.plan(3);
|
||||||
|
|
||||||
|
var pjax = {
|
||||||
|
options: {
|
||||||
|
test: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var events = [];
|
||||||
|
|
||||||
|
storeEventHandler = function(e) {
|
||||||
|
events.push(e.type);
|
||||||
|
|
||||||
|
t.equal(e.test, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("pjax:complete", storeEventHandler);
|
||||||
|
document.addEventListener("pjax:error", storeEventHandler);
|
||||||
|
|
||||||
|
handleResponse.bind(pjax)(false, null);
|
||||||
|
|
||||||
|
t.same(
|
||||||
|
events,
|
||||||
|
["pjax:complete", "pjax:error"],
|
||||||
|
"calling handleResponse(false) triggers 'pjax:complete' and 'pjax:error'"
|
||||||
|
);
|
||||||
|
|
||||||
|
document.removeEventListener("pjax:complete", storeEventHandler);
|
||||||
|
document.removeEventListener("pjax:error", storeEventHandler);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("test when handleResponse() is called normally", function(t) {
|
||||||
|
var pjax = {
|
||||||
|
options: {
|
||||||
|
test: 1
|
||||||
|
},
|
||||||
|
loadContent: noop,
|
||||||
|
state: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = {
|
||||||
|
getResponseHeader: noop
|
||||||
|
};
|
||||||
|
|
||||||
|
handleResponse.bind(pjax)("", request, "href");
|
||||||
|
|
||||||
|
delete window.history.state.uid;
|
||||||
|
t.same(
|
||||||
|
window.history.state,
|
||||||
|
{
|
||||||
|
url: href,
|
||||||
|
title: "",
|
||||||
|
scrollPos: [0, 0]
|
||||||
|
},
|
||||||
|
"window.history.state is set correctly"
|
||||||
|
);
|
||||||
|
t.equals(pjax.state.href, "href", "this.state.href is set correctly");
|
||||||
|
t.equals(
|
||||||
|
Object.keys(pjax.state.options).length,
|
||||||
|
2,
|
||||||
|
"this.state.options is set correctly"
|
||||||
|
);
|
||||||
|
t.same(
|
||||||
|
pjax.state.options.request,
|
||||||
|
request,
|
||||||
|
"this.state.options is set correctly"
|
||||||
|
);
|
||||||
|
t.equals(pjax.state.options.test, 1, "this.state.options is set correctly");
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape(
|
||||||
|
"test when handleResponse() is called normally with request.responseURL",
|
||||||
|
function(t) {
|
||||||
|
var pjax = {
|
||||||
|
options: {},
|
||||||
|
loadContent: noop,
|
||||||
|
state: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = {
|
||||||
|
responseURL: href + "1",
|
||||||
|
getResponseHeader: noop
|
||||||
|
};
|
||||||
|
|
||||||
|
handleResponse.bind(pjax)("", request, "");
|
||||||
|
|
||||||
|
t.equals(
|
||||||
|
pjax.state.href,
|
||||||
|
request.responseURL,
|
||||||
|
"this.state.href is set correctly"
|
||||||
|
);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
tape("test when handleResponse() is called normally with X-PJAX-URL", function(
|
||||||
|
t
|
||||||
|
) {
|
||||||
|
var pjax = {
|
||||||
|
options: {},
|
||||||
|
loadContent: noop,
|
||||||
|
state: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = {
|
||||||
|
getResponseHeader: function(header) {
|
||||||
|
if (header === "X-PJAX-URL") {
|
||||||
|
return href + "2";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleResponse.bind(pjax)("", request, "");
|
||||||
|
|
||||||
|
t.equals(pjax.state.href, href + "2", "this.state.href is set correctly");
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape(
|
||||||
|
"test when handleResponse() is called normally with X-XHR-Redirected-To",
|
||||||
|
function(t) {
|
||||||
|
var pjax = {
|
||||||
|
options: {},
|
||||||
|
loadContent: noop,
|
||||||
|
state: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = {
|
||||||
|
getResponseHeader: function(header) {
|
||||||
|
if (header === "X-XHR-Redirected-To") {
|
||||||
|
return href + "3";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleResponse.bind(pjax)("", request, "");
|
||||||
|
|
||||||
|
t.equals(pjax.state.href, href + "3", "this.state.href is set correctly");
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
tape("test when handleResponse() is called normally with a hash", function(t) {
|
||||||
|
var pjax = {
|
||||||
|
options: {},
|
||||||
|
loadContent: noop,
|
||||||
|
state: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = {
|
||||||
|
responseURL: href + "2",
|
||||||
|
getResponseHeader: noop
|
||||||
|
};
|
||||||
|
|
||||||
|
handleResponse.bind(pjax)("", request, href + "1#test");
|
||||||
|
|
||||||
|
t.equals(
|
||||||
|
pjax.state.href,
|
||||||
|
href + "2#test",
|
||||||
|
"this.state.href is set correctly"
|
||||||
|
);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("test try...catch for loadContent() when options.debug is true", function(
|
||||||
|
t
|
||||||
|
) {
|
||||||
|
t.plan(2);
|
||||||
|
|
||||||
|
var pjax = {
|
||||||
|
options: {},
|
||||||
|
loadContent: noop,
|
||||||
|
state: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = {
|
||||||
|
getResponseHeader: noop
|
||||||
|
};
|
||||||
|
|
||||||
|
pjax.loadContent = function() {
|
||||||
|
throw new Error();
|
||||||
|
};
|
||||||
|
pjax.options.debug = true;
|
||||||
|
|
||||||
|
document.removeEventListener("pjax:error", storeEventHandler);
|
||||||
|
pjaxErrorEventTriggerTest = function() {
|
||||||
|
t.pass("pjax:error event triggered");
|
||||||
|
};
|
||||||
|
document.addEventListener("pjax:error", pjaxErrorEventTriggerTest);
|
||||||
|
|
||||||
|
t.throws(
|
||||||
|
function() {
|
||||||
|
handleResponse.bind(pjax)("", request, "");
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
"error is rethrown"
|
||||||
|
);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("test try...catch for loadContent()", function(t) {
|
||||||
|
t.plan(2);
|
||||||
|
|
||||||
|
var pjax = {
|
||||||
|
options: {},
|
||||||
|
loadContent: noop,
|
||||||
|
state: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = {
|
||||||
|
getResponseHeader: noop
|
||||||
|
};
|
||||||
|
|
||||||
|
pjax.loadContent = function() {
|
||||||
|
throw new Error();
|
||||||
|
};
|
||||||
|
pjax.latestChance = function() {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
pjax.options.debug = false;
|
||||||
|
|
||||||
|
document.removeEventListener("pjax:error", pjaxErrorEventTriggerTest);
|
||||||
|
|
||||||
|
t.doesNotThrow(
|
||||||
|
function() {
|
||||||
|
t.equals(
|
||||||
|
handleResponse.bind(pjax)("", request, ""),
|
||||||
|
true,
|
||||||
|
"this.latestChance() is called"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
"error is not thrown"
|
||||||
|
);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape(
|
||||||
|
"test events triggered when loadContent() is called with a non-string html argument",
|
||||||
|
function(t) {
|
||||||
|
t.plan(3);
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
test: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
var events = [];
|
||||||
|
|
||||||
|
storeEventHandler = function(e) {
|
||||||
|
events.push(e.type);
|
||||||
|
|
||||||
|
t.equal(e.test, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("pjax:complete", storeEventHandler);
|
||||||
|
document.addEventListener("pjax:error", storeEventHandler);
|
||||||
|
|
||||||
|
loadContent(null, options);
|
||||||
|
|
||||||
|
t.same(
|
||||||
|
events,
|
||||||
|
["pjax:complete", "pjax:error"],
|
||||||
|
"calling loadContent() with a non-string html argument triggers 'pjax:complete' and 'pjax:error'"
|
||||||
|
);
|
||||||
|
|
||||||
|
document.removeEventListener("pjax:complete", storeEventHandler);
|
||||||
|
document.removeEventListener("pjax:error", storeEventHandler);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
}
|
||||||
|
);
|
||||||
31
tests/lib/proto/parse-element.js
Normal file
31
tests/lib/proto/parse-element.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
var tape = require("tape");
|
||||||
|
|
||||||
|
var parseElement = require("../../../lib/proto/parse-element");
|
||||||
|
|
||||||
|
var pjax = {
|
||||||
|
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(pjax, a);
|
||||||
|
}, "<a> element can be parsed");
|
||||||
|
|
||||||
|
t.doesNotThrow(function() {
|
||||||
|
var form = document.createElement("form");
|
||||||
|
parseElement.call(pjax, form);
|
||||||
|
}, "<form> element can be parsed");
|
||||||
|
|
||||||
|
t.throws(function() {
|
||||||
|
var el = document.createElement("div");
|
||||||
|
parseElement.call(pjax, el);
|
||||||
|
}, "<div> element cannot be parsed");
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
165
tests/lib/send-request.js
Normal file
165
tests/lib/send-request.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
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 r = sendRequest(url, { cacheBust: true }, 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 r = sendRequest(url, {}, function() {
|
||||||
|
t.equal(r.responseURL, url, "XHR URL is left untouched");
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("request headers are sent properly", function(t) {
|
||||||
|
var url = "https://httpbin.org/headers";
|
||||||
|
var options = {
|
||||||
|
selectors: ["div.pjax", "div.container"]
|
||||||
|
};
|
||||||
|
|
||||||
|
sendRequest(url, options, function(responseText) {
|
||||||
|
var headers = JSON.parse(responseText).headers;
|
||||||
|
|
||||||
|
t.equals(
|
||||||
|
headers["X-Requested-With"],
|
||||||
|
"XMLHttpRequest",
|
||||||
|
"X-Requested-With header is set correctly"
|
||||||
|
);
|
||||||
|
// Httpbin.org changes the case to 'X-Pjax'
|
||||||
|
t.equals(headers["X-Pjax"], "true", "X-PJAX header is set correctly");
|
||||||
|
t.equals(
|
||||||
|
headers["X-Pjax-Selectors"],
|
||||||
|
'["div.pjax","div.container"]',
|
||||||
|
"X-PJAX-Selectors header is set correctly"
|
||||||
|
);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("HTTP status codes other than 200 are handled properly", function(t) {
|
||||||
|
var url = "https://httpbin.org/status/400";
|
||||||
|
|
||||||
|
sendRequest(url, {}, function(responseText, request) {
|
||||||
|
t.equals(responseText, null, "responseText is null");
|
||||||
|
t.equals(request.status, 400, "HTTP status code is correct");
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tape.skip("XHR error is handled properly", function(t) {
|
||||||
|
var url = "https://encrypted.google.com/foobar";
|
||||||
|
|
||||||
|
sendRequest(url, {}, function(responseText) {
|
||||||
|
t.equals(responseText, null, "responseText is null");
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("POST body data is sent properly", function(t) {
|
||||||
|
var url = "https://httpbin.org/post";
|
||||||
|
var params = [
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
value: "1"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
var options = {
|
||||||
|
requestOptions: {
|
||||||
|
requestMethod: "POST",
|
||||||
|
requestParams: params
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sendRequest(url, options, function(responseText) {
|
||||||
|
var response = JSON.parse(responseText);
|
||||||
|
|
||||||
|
t.same(
|
||||||
|
response.form[params[0].name],
|
||||||
|
params[0].value,
|
||||||
|
"requestParams were sent properly"
|
||||||
|
);
|
||||||
|
t.equals(
|
||||||
|
response.headers["Content-Type"],
|
||||||
|
"application/x-www-form-urlencoded",
|
||||||
|
"Content-Type header was set properly"
|
||||||
|
);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("GET query data is sent properly", function(t) {
|
||||||
|
var url = "https://httpbin.org/get";
|
||||||
|
var params = [
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
value: "1"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
var options = {
|
||||||
|
requestOptions: {
|
||||||
|
requestParams: params
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sendRequest(url, options, function(responseText) {
|
||||||
|
var response = JSON.parse(responseText);
|
||||||
|
|
||||||
|
t.same(
|
||||||
|
response.args[params[0].name],
|
||||||
|
params[0].value,
|
||||||
|
"requestParams were sent properly"
|
||||||
|
);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("XHR timeout is handled properly", function(t) {
|
||||||
|
var url = "https://httpbin.org/delay/5";
|
||||||
|
var options = {
|
||||||
|
timeout: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
sendRequest(url, options, function(responseText) {
|
||||||
|
t.equals(responseText, null, "responseText is null");
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
84
tests/lib/switch-selectors.js
Normal file
84
tests/lib/switch-selectors.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
var tape = require("tape");
|
||||||
|
|
||||||
|
var switchesSelectors = require("../../lib/switches-selectors.js");
|
||||||
|
var noop = require("../../lib/util/noop");
|
||||||
|
|
||||||
|
var pjax = {
|
||||||
|
onSwitch: function() {
|
||||||
|
console.log("Switched");
|
||||||
|
},
|
||||||
|
state: {},
|
||||||
|
log: noop
|
||||||
|
};
|
||||||
|
|
||||||
|
// @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 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("test switchesSelectors when number of elements don't match", function(t) {
|
||||||
|
var newTempDoc = document.implementation.createHTMLDocument();
|
||||||
|
var originalTempDoc = 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 = originalTempDoc.createElement("div");
|
||||||
|
container.innerHTML = "<p>Original text</p><span>No change</span>";
|
||||||
|
originalTempDoc.body.appendChild(container);
|
||||||
|
|
||||||
|
var container2 = newTempDoc.createElement("div");
|
||||||
|
container2.innerHTML =
|
||||||
|
"<p>New text</p><p>More new text</p><span>New span</span>";
|
||||||
|
newTempDoc.body.appendChild(container2);
|
||||||
|
|
||||||
|
var switchSelectorsFn = switchesSelectors.bind(
|
||||||
|
pjax,
|
||||||
|
{}, // switches
|
||||||
|
{}, // switchesOptions
|
||||||
|
["p"], // selectors,
|
||||||
|
newTempDoc, // fromEl
|
||||||
|
originalTempDoc, // toEl,
|
||||||
|
{} // options
|
||||||
|
);
|
||||||
|
|
||||||
|
t.throws(
|
||||||
|
switchSelectorsFn,
|
||||||
|
null,
|
||||||
|
"error was thrown properly since number of elements don't match"
|
||||||
|
);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
94
tests/lib/switches.js
Normal file
94
tests/lib/switches.js
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
var tape = require("tape");
|
||||||
|
var switches = require("../../lib/switches");
|
||||||
|
var noop = require("../../lib/util/noop");
|
||||||
|
|
||||||
|
tape("test outerHTML switch", function(t) {
|
||||||
|
var outerHTML = switches.outerHTML;
|
||||||
|
|
||||||
|
var doc = document.implementation.createHTMLDocument();
|
||||||
|
|
||||||
|
var container = doc.createElement("div");
|
||||||
|
container.innerHTML = "<p id='p'>Original Text</p>";
|
||||||
|
doc.body.appendChild(container);
|
||||||
|
|
||||||
|
var p = doc.createElement("p");
|
||||||
|
p.innerHTML = "New Text";
|
||||||
|
|
||||||
|
outerHTML.bind({
|
||||||
|
onSwitch: noop
|
||||||
|
})(doc.querySelector("p"), p);
|
||||||
|
|
||||||
|
t.equals(
|
||||||
|
doc.querySelector("p").innerHTML,
|
||||||
|
"New Text",
|
||||||
|
"Elements correctly switched"
|
||||||
|
);
|
||||||
|
t.notEquals(
|
||||||
|
doc.querySelector("p").id,
|
||||||
|
"p",
|
||||||
|
"other attributes overwritten correctly"
|
||||||
|
);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("test innerHTML switch", function(t) {
|
||||||
|
var innerHTML = switches.innerHTML;
|
||||||
|
|
||||||
|
var doc = document.implementation.createHTMLDocument();
|
||||||
|
|
||||||
|
var container = doc.createElement("div");
|
||||||
|
container.innerHTML = "<p id='p'>Original Text</p>";
|
||||||
|
doc.body.appendChild(container);
|
||||||
|
|
||||||
|
var p = doc.createElement("p");
|
||||||
|
p.innerHTML = "New Text";
|
||||||
|
p.className = "p";
|
||||||
|
|
||||||
|
innerHTML.bind({
|
||||||
|
onSwitch: noop
|
||||||
|
})(doc.querySelector("p"), p);
|
||||||
|
|
||||||
|
t.equals(
|
||||||
|
doc.querySelector("p").innerHTML,
|
||||||
|
"New Text",
|
||||||
|
"Elements correctly switched"
|
||||||
|
);
|
||||||
|
t.equals(doc.querySelector("p").className, "p", "classname set correctly");
|
||||||
|
t.equals(doc.querySelector("p").id, "p", "other attributes set correctly");
|
||||||
|
|
||||||
|
p.removeAttribute("class");
|
||||||
|
|
||||||
|
innerHTML.bind({
|
||||||
|
onSwitch: noop
|
||||||
|
})(doc.querySelector("p"), p);
|
||||||
|
|
||||||
|
t.equals(doc.querySelector("p").className, "", "classname set correctly");
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
tape("test replaceNode switch", function(t) {
|
||||||
|
var replaceNode = switches.replaceNode;
|
||||||
|
|
||||||
|
var doc = document.implementation.createHTMLDocument();
|
||||||
|
|
||||||
|
var container = doc.createElement("div");
|
||||||
|
container.innerHTML = "<p>Original Text</p>";
|
||||||
|
doc.body.appendChild(container);
|
||||||
|
|
||||||
|
var p = doc.createElement("p");
|
||||||
|
p.innerHTML = "New Text";
|
||||||
|
|
||||||
|
replaceNode.bind({
|
||||||
|
onSwitch: noop
|
||||||
|
})(doc.querySelector("p"), p);
|
||||||
|
|
||||||
|
t.equals(
|
||||||
|
doc.querySelector("div").innerHTML,
|
||||||
|
"<p>New Text</p>",
|
||||||
|
"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();
|
||||||
|
});
|
||||||
21
tests/lib/util/clone.js
Normal file
21
tests/lib/util/clone.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
var tape = require("tape");
|
||||||
|
|
||||||
|
var clone = require("../../../lib/util/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 original object");
|
||||||
|
|
||||||
|
t.same(obj, cloned, "cloned object has the same values as original object");
|
||||||
|
|
||||||
|
cloned.three = 3;
|
||||||
|
t.notSame(
|
||||||
|
obj,
|
||||||
|
cloned,
|
||||||
|
"modified cloned object doesn't have the same values as original object"
|
||||||
|
);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
25
tests/lib/util/contains.js
Normal file
25
tests/lib/util/contains.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
25
tests/lib/util/extend.js
Normal file
25
tests/lib/util/extend.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
var tape = require("tape");
|
||||||
|
|
||||||
|
var extend = require("../../../lib/util/extend");
|
||||||
|
|
||||||
|
tape("test extend method", function(t) {
|
||||||
|
var obj = { one: 1, two: 2 };
|
||||||
|
|
||||||
|
var extended = extend({}, obj, { two: "two", three: 3 });
|
||||||
|
t.notEqual(obj, extended, "extended object isn't the original object");
|
||||||
|
t.notSame(
|
||||||
|
obj,
|
||||||
|
extended,
|
||||||
|
"extended object doesn't have the same values as original object"
|
||||||
|
);
|
||||||
|
t.notSame(
|
||||||
|
obj.two,
|
||||||
|
extended.two,
|
||||||
|
"extended object value overwrites value from original object"
|
||||||
|
);
|
||||||
|
|
||||||
|
extended = extend(null);
|
||||||
|
t.equals(extended, null, "passing null returns null");
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
9
tests/lib/util/noop.js
Normal file
9
tests/lib/util/noop.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
var tape = require("tape");
|
||||||
|
|
||||||
|
var noop = require("../../../lib/util/noop");
|
||||||
|
|
||||||
|
tape("test noop function", function(t) {
|
||||||
|
t.equal(typeof noop, "function", "noop is a function");
|
||||||
|
t.equal(noop(), undefined, "noop() returns nothing");
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
33
tests/lib/util/update-query-string.js
Normal file
33
tests/lib/util/update-query-string.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
var tape = require("tape");
|
||||||
|
|
||||||
|
var updateQueryString = require("../../../lib/util/update-query-string");
|
||||||
|
|
||||||
|
tape("test update query string method", function(t) {
|
||||||
|
var url = "http://example.com";
|
||||||
|
var updatedUrl = updateQueryString(url, "foo", "bar");
|
||||||
|
|
||||||
|
t.notEqual(url, updatedUrl, "update query string modifies URL");
|
||||||
|
t.equal(
|
||||||
|
updatedUrl,
|
||||||
|
url + "?foo=bar",
|
||||||
|
"update query string creates new query string when no query string params are set"
|
||||||
|
);
|
||||||
|
|
||||||
|
updatedUrl = updateQueryString(updatedUrl, "foo", "baz");
|
||||||
|
|
||||||
|
t.equal(
|
||||||
|
updatedUrl,
|
||||||
|
url + "?foo=baz",
|
||||||
|
"update query string updates existing query string param"
|
||||||
|
);
|
||||||
|
|
||||||
|
updatedUrl = updateQueryString(updatedUrl, "bar", "");
|
||||||
|
|
||||||
|
t.equal(
|
||||||
|
updatedUrl,
|
||||||
|
url + "?foo=baz&bar=",
|
||||||
|
"update query string appends to existing query string"
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
||||||
41
tests/test.ts
Normal file
41
tests/test.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import Pjax = require("../index");
|
||||||
|
|
||||||
|
let options: Pjax.IOptions = {
|
||||||
|
elements: "a.pjax, form.pjax",
|
||||||
|
selectors: ["div.pjax"],
|
||||||
|
switches: {
|
||||||
|
"a.pjax": (oldEl, newEl) => {
|
||||||
|
oldEl.parentNode.replaceChild(newEl, oldEl);
|
||||||
|
this.onSwitch();
|
||||||
|
},
|
||||||
|
"form.pjax": Pjax.switches.innerHTML
|
||||||
|
},
|
||||||
|
switchesOptions: {},
|
||||||
|
history: true,
|
||||||
|
analytics: false,
|
||||||
|
scrollTo: 1,
|
||||||
|
scrollRestoration: false,
|
||||||
|
cacheBust: false,
|
||||||
|
debug: true,
|
||||||
|
timeout: 60000,
|
||||||
|
currentUrlFullReload: true
|
||||||
|
};
|
||||||
|
|
||||||
|
options.analytics = () => {};
|
||||||
|
options.scrollTo = [1, 1];
|
||||||
|
options.scrollTo = false;
|
||||||
|
|
||||||
|
if (Pjax.isSupported()) {
|
||||||
|
delete options.switchesOptions;
|
||||||
|
const pjax = new Pjax(options);
|
||||||
|
|
||||||
|
pjax.reload();
|
||||||
|
pjax.loadUrl("https://example.org", options);
|
||||||
|
|
||||||
|
pjax._handleResponse = pjax.handleResponse;
|
||||||
|
pjax.handleResponse = (requestText: string, request: XMLHttpRequest, href: string) => {
|
||||||
|
pjax.abortRequest(request);
|
||||||
|
|
||||||
|
return pjax._handleResponse(requestText, request, href);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user