Compare commits

...

92 Commits
0.1.4 ... 0.2.4

Author SHA1 Message Date
Maxime Thirouin
917c6f6bcb 0.2.4 2016-07-11 07:11:39 +02:00
Bart Nagel
b201b96a37 Polyfill XHR.responseURL so tests pass in old Safari (#73)
This property is only used in the tests. It doesn't exist in Safari 8
and before, so where it's not available this patch polyfills it,
allowing the test to continue.
2016-07-11 06:55:47 +02:00
Maxime Thirouin
eff7cfd452 Merge pull request #67 from compressed/refresh
Update refresh and handle attributes without values
2016-06-28 07:17:43 +02:00
Bart Nagel
7976f06043 Added: cacheBust option (#71)
* Refactor request test to allow further tests to be added

* Make cache-busting optional

Closes #70
2016-06-28 06:46:14 +02:00
Maxime Thirouin
ee530f4c0a 0.2.3 (bower) 2016-05-03 09:23:50 +02:00
Christopher Brickley
bf71894395 fix(attributes): for attributes without a value, add only the attribute
Some attributes, such as `itemscope` have no corresponding value. This
change allows them to still be set.
2016-04-19 12:15:39 -04:00
Christopher Brickley
beaa21fb3a fix(refresh): use this.parseDOM for refresh
Fix for issue mentioned in #36
2016-04-19 12:13:14 -04:00
Maxime Thirouin
b96b0f41a7 Merge pull request #66 from christophwolff/patch-1
Fix bracket in switches example
2016-04-05 17:20:49 +02:00
Christoph Wolff
6e113b2d06 Fix bracket in switches example 2016-04-05 16:40:52 +02:00
Maxime Thirouin
46912e6797 More logical npm publish (testling don't like to be spawn or something) 2016-03-24 13:34:11 +01:00
Maxime Thirouin
4877bac2ae 0.2.3 2016-03-24 12:39:07 +01:00
Maxime Thirouin
afe0ddb6b9 Fixed: `this.reload` is now a Function
Closes #65
2016-03-24 12:38:15 +01:00
Maxime Thirouin
a5d36d28f8 Update README.md 2016-03-15 11:36:53 +01:00
Maxime Thirouin
7a4056fd77 Update README.md 2016-03-15 11:35:13 +01:00
Maxime Thirouin
ba6ef126c0 Internal: add "npm run release" command
—skip-test is used because of tesling (it does not like to be spawned
or something like that)…
2016-03-12 07:52:52 +01:00
Maxime Thirouin
01536bfbf5 0.2.2
Closes #55
2016-03-12 07:42:37 +01:00
Maxime Thirouin
722ddf2a30 Internal: simple repository url 2016-03-12 07:27:47 +01:00
Maxime Thirouin
34fc00c89d Add standalone file back, no big deal
Closes #57
2016-03-12 07:27:06 +01:00
Maxime Thirouin
c02193c61b Internal: update CHANGELOG #59 2016-03-12 07:26:26 +01:00
Maxime Thirouin
824b229158 Merge pull request #59 from coolhihi/master
Fixed: error when using pjax with google analytics
2016-03-12 07:09:55 +01:00
COoL
e3d0f8cc1b fixed an error when using pjax with google analytics 2016-01-15 17:32:07 +08:00
Maxime Thirouin
8ea8ffad07 Update CHANGELOG 2016-01-05 07:42:00 +01:00
Maxime Thirouin
c12e4cdedd Merge pull request #52 from darylteo/patch-51
Patch 51
2016-01-05 07:15:11 +01:00
Maxime Thirouin
546b7e309a Merge pull request #50 from darylteo/patch-5
Patch 5
2016-01-05 07:14:29 +01:00
Maxime Thirouin
2f4bd760a5 Merge pull request #49 from darylteo/patch-39
[FIX] #39 - events on top level
2016-01-05 07:14:17 +01:00
darylteo
477d967804 Add clone and executeScripts as well 2016-01-05 15:02:39 +11:00
darylteo
e882b8639a Use required forEachElse 2016-01-05 15:02:00 +11:00
darylteo
3d25bee131 Bind required functions to pjax 2016-01-05 14:22:03 +11:00
darylteo
47059bdb04 Add test for switch selectors 2016-01-05 14:12:52 +11:00
darylteo
791400ed20 Tests for #5 2016-01-04 23:45:25 +11:00
darylteo
97c8b2d749 [NEW] #5 Ignore default prevented clicks 2016-01-04 23:19:36 +11:00
darylteo
b156a4f389 [TEST] Test for #39 2016-01-04 22:52:30 +11:00
darylteo
1d292a1a6e [FIX] #39 - events on top level
Events triggered on top level elements (such as window or document)
lead to a HierarchyRequestError due to a fix in IE browsers
where dispatchEvent does not fire if an element is not in the DOM.

The current test is simply to check for the existence of parentNode
However, this means top level elements get added to itself, causing
the error.

The proposed fix simply tests for top level elements in the test.
2016-01-04 22:30:00 +11:00
Maxime Thirouin
aaa2631eb7 Merge pull request #42 from MohamedBoualleg/patch-1
Update README.md
2015-12-18 08:25:55 +01:00
Maxime Thirouin
460dea8a9e Merge pull request #44 from rstacruz/fix/stuff
Add examples, remove failing test
2015-11-14 12:28:26 +01:00
Rico Sta. Cruz
b908621842 Fix lint errors 2015-11-13 23:59:22 +11:00
Rico Sta. Cruz
b20ee2261e Add example 2015-11-13 10:40:59 +11:00
Rico Sta. Cruz
ad6292fffb Add prepublish hook 2015-11-13 10:28:16 +11:00
Mohamed Bouallegue
4ed4577539 Update README.md 2015-10-11 04:06:04 +01:00
Maxime Thirouin
6eafcf8dc6 Add a standalone build to ensure it "should" work 2015-02-19 07:51:42 +01:00
Maxime Thirouin
b244a8cac4 Add files into npm release 2015-02-04 08:41:15 +01:00
Maxime Thirouin
a601e301cd 0.2.0 2015-02-04 08:10:13 +01:00
Maxime Thirouin
c16996d2b2 Update changelog with unreleased stuff 2015-01-29 07:33:26 +01:00
Maxime Thirouin
af8783587c refresh now accept an argument so you can refresh just a part of a page 2015-01-29 07:33:13 +01:00
Maxime Thirouin
14f997c8d1 Merge pull request #36 from pklada/refresh
Add simple refresh method and protect against links being bound more than once.
2015-01-29 07:03:16 +01:00
Peter Lada
9a86044f90 changed existing refresh method to reload. changed the data check to trigger a refresh on the element based on the already bound data attr 2015-01-28 18:27:16 -08:00
Peter Lada
e6a35f38e4 attach data to pjaxified links so they can't be pjaxified twice. add simple refresh method 2015-01-21 00:11:59 -08:00
Isaac Gierard
3d50ae9131 fixed uniqueid so that it is a function as expected by index.js and elsewhere
also added test for uniqueid. only test that two calls to uniqueid return unique enties. had to modify the output slightly and added a counter to the system otherwise two back to back calls would return the same value.
2015-01-16 16:21:26 -08:00
Isaac Gierard
949d1be1a0 fixed calling parseOptions with arrayed arguments 2015-01-16 15:46:31 -08:00
Isaac Gierard
a119033870 fixed some require paths also added isSupported include to index.js 2015-01-16 15:44:57 -08:00
Maxime Thirouin
482ba2c117 Merge pull request #33 from igierard/disableable-scrollTo
prevent scrollTo from being converted from false to 0
2015-01-16 06:56:25 +01:00
Isaac Gierard
7e81f791a9 fixing issues with parse-options and related tests 2014-11-20 16:21:19 -08:00
Isaac Gierard
d4ba34e5ed moved options parsing into separate file and created test
moved the options paring system into it's own function so that it may be testable
2014-11-20 14:39:48 -08:00
Isaac Gierard
ea7d1d2fce prevent scrollTo from being converted from false to 0
In the loadUrl function there is an explicit check for false as a means to disable the scrollTo behavior however if the scrollTo option is passed to the constructor as false the gaud statement was converting false to 0.
2014-11-18 14:45:50 -08:00
Maxime Thirouin
1d087e8b00 Add travis badge 2014-10-14 11:55:48 +02:00
Maxime Thirouin
3bc67f245c Cleanup & travis (should break) 2014-10-14 11:42:36 +02:00
Maxime Thirouin
332cd4e876 Add notes about current wip 2014-10-14 08:36:02 +02:00
Maxime Thirouin
db02366324 remove todo.md
all lines opened as issues
2014-10-14 08:31:31 +02:00
Maxime Thirouin
b5bf998fc6 Merge pull request #20 from MoOx/testling
Exploded & tested stuff
2014-10-14 08:27:04 +02:00
Maxime Thirouin
74e224c018 Merge wip 2014-10-14 08:24:50 +02:00
Maxime Thirouin
165532d43c Relocate all the things 2014-10-14 08:23:56 +02:00
Maxime Thirouin
414650113b Add missing switches methods (untested) 2014-10-14 08:19:44 +02:00
Maxime Thirouin
a6c9b57647 Reintroduce e0d33c9e18
https://github.com/MoOx/pjax/commit/e0d33c9e18ed542cd22c5cab74ae8c061982
2e22
2014-10-14 08:16:26 +02:00
Maxime Thirouin
f4e5ec254a old pjax huge file must die 2014-10-14 08:13:50 +02:00
Maxime Thirouin
f1976c3a10 Remove gulp (overkill) 2014-10-14 08:11:23 +02:00
François Vaux
799dd51597 Typo 2014-10-14 08:04:22 +02:00
Maxime Thirouin
76026cf8d9 Add forEachSelectors method 2014-10-14 08:04:22 +02:00
Maxime Thirouin
b6702a5ea0 Add attachLink method with test 2014-10-14 08:04:22 +02:00
Maxime Thirouin
673c483ccb Add event options as an option for the trigger function 2014-10-14 08:04:22 +02:00
Maxime Thirouin
d1536eb228 Add function.bind polyfill
https://github.com/ariya/phantomjs/issues/10522
2014-10-14 08:04:22 +02:00
Maxime Thirouin
681aa74e1d Add parseDOM & (new) parseElement proto methods 2014-10-14 08:04:22 +02:00
Maxime Thirouin
3f3fa761e1 Add getElements method 2014-10-14 08:04:22 +02:00
Maxime Thirouin
0d6643cf7a Add log method 2014-10-14 08:04:22 +02:00
Maxime Thirouin
4a4fb6fbc6 Add uniqueid helper 2014-10-14 08:04:22 +02:00
Maxime Thirouin
d5395c3d07 Test isSupported
kind of stupid, but I don't want to maintain a list of browser UA or
whatever.
2014-10-14 08:04:22 +02:00
Maxime Thirouin
8c05692004 Fix dispatchEvent for IE9/10/11
dispatchEvent doesn't work if element is not in the dom
This is not the silverbullet since an element can have a parent that is
not in the dom too, but to avoid dom update all the time, it should be
enough for very particular use cases (eg in my case unit test).
Maybe this should be moved into the test directly to be sure there is a
parent, we will see that later.
2014-10-14 08:04:21 +02:00
Maxime Thirouin
13b3485111 add npm run test--html for debugging 2014-10-14 08:04:21 +02:00
Maxime Thirouin
b1de220555 Drop fireEvent since we are supporting IE10 2014-10-14 08:03:43 +02:00
Maxime Thirouin
b4f7aceab6 history is not available before IE10
http://caniuse.com/history
2014-10-14 08:03:43 +02:00
Maxime Thirouin
1cc1aa9d04 bye README gif 2014-10-14 08:03:43 +02:00
Maxime Thirouin
52d7971dc5 Test xhr request 2014-10-14 08:03:43 +02:00
Maxime Thirouin
9ac709b5d8 Test forEachEls 2014-10-14 08:03:43 +02:00
Maxime Thirouin
fa27e05606 Test executeScripts 2014-10-14 08:03:43 +02:00
Maxime Thirouin
106b14b851 Tests events (on, off, trigger) 2014-10-14 08:03:42 +02:00
Maxime Thirouin
2990f93a20 Test evalScript 2014-10-14 08:03:42 +02:00
Maxime Thirouin
5644893c59 Run testling on less browsers 2014-10-14 08:03:42 +02:00
Maxime Thirouin
7631cc92c7 Test clone method 2014-10-14 08:03:42 +02:00
Maxime Thirouin
25a725c858 ignore dist/ 2014-10-14 08:03:42 +02:00
Maxime Thirouin
f93efb0c00 Add Gulp tasks 2014-10-14 08:03:42 +02:00
Maxime Thirouin
cac43b7c59 Add coverage command 2014-10-14 08:03:42 +02:00
Maxime Thirouin
dd590bae28 Setup testling 2014-10-14 08:00:10 +02:00
Maxime Thirouin
48376db0ef Update workflow/conventions/tools 2014-10-14 07:56:42 +02:00
52 changed files with 14096 additions and 827 deletions

3
.gitignore vendored
View File

@@ -1 +1,4 @@
.DS_Store
node_modules/
tests/scripts/index.html
pjax.js

View File

@@ -1,112 +0,0 @@
{
"excludeFiles": [
]
, "requireCurlyBraces": [
"if"
, "else"
, "for"
, "while"
, "do"
, "try"
, "catch"
]
, "requireSpaceAfterKeywords": [
"if"
, "else"
, "for"
, "while"
, "do"
, "switch"
, "return"
, "try"
, "catch"
]
, "requireSpacesInFunctionExpression": {
"beforeOpeningCurlyBrace": true
}
, "disallowSpacesInFunctionExpression": {
"beforeOpeningRoundBrace": true
}
, "disallowEmptyBlocks": true
, "disallowSpacesInsideObjectBrackets": true
, "disallowSpacesInsideArrayBrackets": true
, "disallowSpacesInsideParentheses": true
, "disallowSpaceAfterObjectKeys": true
, "disallowCommaBeforeLineBreak": true
, "requireOperatorBeforeLineBreak": [
"?"
, "+"
, "-"
, "/"
, "*"
, "="
, "=="
, "==="
, "!="
, "!=="
, ">"
, ">="
, "<"
, "<="
]
, "disallowSpaceAfterPrefixUnaryOperators": [
"++"
, "--"
, "+"
, "-"
, "~"
, "!"
]
, "disallowSpaceBeforePostfixUnaryOperators": [
"++"
, "--"
]
, "requireSpaceBeforeBinaryOperators": [
"+"
, "-"
, "/"
, "*"
, "="
, "=="
, "==="
, "!="
, "!=="
]
, "requireSpaceAfterBinaryOperators": [
"+"
, "-"
, "/"
, "*"
, "="
, "=="
, "==="
, "!="
, "!=="
]
, "disallowImplicitTypeConversion": [
"numeric"
, "boolean"
, "binary"
, "string"
]
, "disallowKeywords": [
"with"
]
, "disallowMultipleLineStrings": true
, "validateQuoteMarks": "\""
, "disallowMixedSpacesAndTabs": true
, "disallowTrailingWhitespace": true
, "requireKeywordsOnNewLine": [
]
, "requireLineFeedAtFileEnd": true
, "requireCapitalizedConstructors": true
, "safeContextKeyword": "that"
, "validateJSDoc": {
"checkParamNames": true
, "checkRedundantParams": true
, "requireParamTypes": true
}
}

130
.jscsrc Normal file
View File

@@ -0,0 +1,130 @@
{
"excludeFiles": [
"node_modules/**"
],
"fileExtensions": [
".js"
],
"requireCurlyBraces": [
"if",
"else",
"for",
"while",
"do",
"try",
"catch"
],
"requireSpaceAfterKeywords": [
"if",
"else",
"for",
"while",
"do",
"switch",
"return",
"try",
"catch"
],
"requireSpaceBeforeBlockStatements": true,
"requireParenthesesAroundIIFE": true,
"requireSpacesInConditionalExpression": {
"afterTest": true,
"beforeConsequent": true,
"afterConsequent": true,
"beforeAlternate": true
},
"requireSpacesInFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"disallowSpacesInFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowMultipleVarDecl": true,
"requireBlocksOnNewline": 1,
"disallowPaddingNewlinesInBlocks": true,
"disallowEmptyBlocks": true,
"disallowSpacesInsideObjectBrackets": true,
"disallowSpacesInsideArrayBrackets": true,
"disallowSpacesInsideParentheses": true,
"disallowQuotedKeysInObjects": "allButReserved",
"disallowSpaceAfterObjectKeys": true,
"requireCommaBeforeLineBreak": true,
"requireOperatorBeforeLineBreak": [
"?",
"+",
"-",
"/",
"*",
"=",
"==",
"===",
"!=",
"!==",
">",
">=",
"<",
"<="
],
"disallowSpaceAfterPrefixUnaryOperators": [
"++",
"--",
"+",
"-",
"~",
"!"
],
"disallowSpaceBeforePostfixUnaryOperators": [
"++",
"--"
],
"requireSpaceBeforeBinaryOperators": [
"+",
"-",
"/",
"*",
"=",
"==",
"===",
"!=",
"!=="
],
"requireSpaceAfterBinaryOperators": [
"+",
"-",
"/",
"*",
"=",
"==",
"===",
"!=",
"!=="
],
"disallowImplicitTypeConversion": [
"numeric",
"boolean",
"binary",
"string"
],
"requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties",
"disallowKeywords": [
"with"
],
"disallowMultipleLineStrings": true,
"validateQuoteMarks": "\"",
"validateIndentation": 2,
"disallowMixedSpacesAndTabs": true,
"disallowTrailingWhitespace": true,
"requireKeywordsOnNewLine": [
"else"
],
"requireLineFeedAtFileEnd": true,
"requireCapitalizedConstructors": true,
"safeContextKeyword": "that",
"requireDotNotation": true,
"validateJSDoc": {
"checkParamNames": true,
"checkRedundantParams": true,
"requireParamTypes": true
},
"requireSpaceAfterLineComment": true
}

View File

@@ -1,4 +1,9 @@
{
"asi": true
, "laxcomma": true
"newcap": false,
"undef": true,
"unused": true,
"asi": true,
"esnext": true,
"node": true,
"browser": true
}

7
.travis.yml Normal file
View File

@@ -0,0 +1,7 @@
language: "node_js"
before_script:
# testling use headless browser
# on travis-ci, firefox is the default one
# & it needs a display to works
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start

View File

@@ -1,22 +1,66 @@
# 0.2.4 - 2016-06-28
- Fixed: ``refresh`` should now work (use `this.parseDOM` for refresh)
([#67](https://github.com/MoOx/pjax/pull/67) - @compressed)
- Fixed: Some attributes, such as `itemscope` have no corresponding value.
This change allows them to still be set.
([#67](https://github.com/MoOx/pjax/pull/67) - @compressed)
- Added: ``cacheBust`` option
([#71](https://github.com/MoOx/pjax/pull/71) - @tremby)
# 0.2.3 - 2016-03-24
- Fixed: ``currentUrlFullReload`` option now works
- Fixed: ``this.reload`` is now a Function
([#65](https://github.com/MoOx/pjax/issues/65))
# 0.2.2 - 2016-03-12
- Fixed: added back standalone version in `./pjax.js`
([#57](https://github.com/MoOx/pjax/issues/57)
- Fixed: error when using pjax with google analytics (``options`` was undefined)
([#59](https://github.com/MoOx/pjax/pull/59))
- Fixed: HierarchyRequestError error
([#49](https://github.com/MoOx/pjax/pull/49))
- Fixed: ``TypeError: Pjax.forEachEls is not a function``
([#52](https://github.com/MoOx/pjax/pull/52))
- Fixed: ``TypeError: Pjax.executeScripts is not a function``
([#52](https://github.com/MoOx/pjax/pull/52))
- Fixed: ``TypeError: Pjax.clone is not a function``
([#52](https://github.com/MoOx/pjax/pull/52))
- Added: Ignore events with prevented defaults
([#50](https://github.com/MoOx/pjax/pull/50))
# 0.2.1 - 2015-02-04
- Fixed: it's better when a release have actual files.
# 0.2.0 - 2015-02-04
- Fixed: prevent scrollTo from being converted from false to 0 ([#33](https://github.com/MoOx/pjax/pull/33))
- Changed: code exploded in commonjs style
- Added: lots of tests
- Added: `refresh` method to force update a DOM element ([#36](https://github.com/MoOx/pjax/pull/36))
# 0.1.4 - 2014-10-14
- allow to load pages without any attributes on `<html>` element (fix [#6](https://github.com/MoOx/pjax/issues/6))
- make `Pjax.switches.sideBySide` method usable (fix [#13](https://github.com/MoOx/pjax/issues/13))
- Fixed: allow to load pages without any attributes on `<html>` element (fix [#6](https://github.com/MoOx/pjax/issues/6))
- Fixed: make `Pjax.switches.sideBySide` method usable (fix [#13](https://github.com/MoOx/pjax/issues/13))
# 0.1.3 - 2014-09-16
- clicking on the current url somewhere does not produce a full reload by default (see option `currentUrlFullReload`)
- fix `document.implementation.createHTMLDocument` error (in IE10, ref [#16](https://github.com/MoOx/pjax/pull/16))
- Fixed: clicking on the current url somewhere does not produce a full reload by default (see option `currentUrlFullReload`)
- Fixed: `document.implementation.createHTMLDocument` error (in IE10, ref [#16](https://github.com/MoOx/pjax/pull/16))
# 0.1.2 - 2014-04-03
- pjax.js relocated in `src/`
- <html> attributes of pjaxified document are now available
- Changed: `pjax.js` relocated in `src/`
- Fixed: `<html>` attributes of pjaxified document are now available
# 0.1.1 - 2014-04-02
- Safer UMD wrapper (fix concat issue)
- Fixed: safer UMD wrapper (fix concat issue)
# 0.1.0 - 2014-03-24
Initial release
Initial release

104
README.md
View File

@@ -1,6 +1,6 @@
# Pjax
<img align="right" src="https://dl.dropboxusercontent.com/u/14108185/memes/mind-blow.gif">
[![Build Status](http://img.shields.io/travis/MoOx/pjax.svg)](https://travis-ci.org/MoOx/pjax) [@todo fix CI](https://github.com/MoOx/pjax/issues/63).
> Easily enable fast Ajax navigation on any website (using pushState + xhr)
@@ -15,12 +15,10 @@ Especially for user that have low bandwidth connection._
**No more full page reload. No more lots of HTTP request.**
## No tests or Demo ?
~~There is still some work to make this repo sexy with tests & simple demo.~~
Actually there is a [WIP branch where I'm adding lot of tests](https://github.com/MoOx/pjax/tree/testling)
## Demo
For now [you can see this running on my website](http://moox.io), with sexy CSS animations when switching pages.
[You can see this running on my website](http://moox.io), with sexy CSS animations when switching pages.
## How Pjax works
@@ -42,7 +40,7 @@ want (including html metas, title, navigation state).
### Under the hood
- It listen to every clicks on links _you want_ (by default all of them),
- When a internal link hitted, Pjax grabs HTML from your server via ajax,
- When an internal link is clicked, Pjax grabs HTML from your server via ajax,
- Pjax render pages DOM tree (without loading any resources - images, css, js...)
- It check if all defined parts can be replaced:
- if page doesn't suit requirement, classic navigation used,
@@ -135,8 +133,8 @@ Let's talk more about the most basic way to get started:
```js
new Pjax({
elements: "a" // default is "a[href], form[action]"
, selectors: ["title", ".my-Header", ".my-Content", ".my-Sidebar"]
elements: "a", // default is "a[href], form[action]"
selectors: ["title", ".my-Header", ".my-Content", ".my-Sidebar"]
})
```
@@ -203,8 +201,8 @@ Examples:
```js
new Pjax({
selectors: ["title", ".Navbar", ".js-Pjax"]
, switches: {
selectors: ["title", ".Navbar", ".js-Pjax"],
switches: {
// "title": Pjax.switches.outerHTML // default behavior
".Navbar": function(oldEl, newEl, options) {
// here it's a stupid example since it's the default behavior too
@@ -249,23 +247,23 @@ with or without [WOW.js](https://github.com/matthieua/WOW).
```js
new Pjax({
selectors: ["title", ".js-Pjax"]
, switches: {
selectors: ["title", ".js-Pjax"],
switches: {
".js-Pjax": Pjax.switches.sideBySide
}
, switchesOptions: {
},
switchesOptions: {
".js-Pjax": {
classNames: {
// class added on the element that will be removed
remove: "Animated Animated--reverse Animate--fast Animate--noDelay"
remove: "Animated Animated--reverse Animate--fast Animate--noDelay",
// class added on the element that will be added
, add: "Animated"
add: "Animated",
// class added on the element when it go backward
, backward: "Animate--slideInRight"
backward: "Animate--slideInRight",
// class added on the element when it go forward (used for new page too)
, forward: "Animate--slideInLeft"
}
, callbacks: {
forward: "Animate--slideInLeft"
},
callbacks: {
// to make a nice transition with 2 pages as the same time
// we are playing with absolute positioning for element we are removing
// & we need live metrics to have something great
@@ -275,6 +273,7 @@ new Pjax({
}
}
}
}
})
```
_Note that remove class include `Animated--reverse` which is a simple way to not have
@@ -383,6 +382,12 @@ It's called every time a page is switched, even for history buttons.
Value (in px) to scrollTo when a page is switched.
##### `cacheBust` (Boolean, default true)
When set to true,
append a timestamp query string segment to the requested URLs
in order to skip browser cache.
##### `debug` (Boolean, default to false)
Enable verbose mode & doesn't use fallback when there is an error.
@@ -392,33 +397,6 @@ Useful to debug page layout differences.
When set to true, clicking on a link that point the current url trigger a full page reload.
#### Extend Pjax
Pjax prototype & utilities methods can be used & changed so you can patch or hack
Pjax behavior, as you wish.
Here is a summary of functions:
- `Pjax.isSupported` (`function()`): return wheter or not the browser handle pushState correctly
- `Pjax.on` (`function(els, events, listener, useCapture)`): addEventListener, that handles NodeList & supports space separated event name
- `Pjax.off` (`function(els, events, listener, useCapture)`): removeEventListener, that handles NodeList & supports space separated event name
- `Pjax.trigger` (`function(els, events)`): fireEvent, that handles NodeList & supports space separated event name
- `Pjax.clone` (`function(obj)`): clone object
- `Pjax.executeScripts` (`function(el)`): execute scripts that are inside an element (script src or inline scripts through `Pjax.evalScript`)
- `Pjax.evalScript` (`function(el)`): execute inline script. Don't execute a script if it contains `document.write`.
- `Pjax.prototype.log` (`function()`): console.log function that is enable/disabled by `debug` option
- `Pjax.prototype.getElements` (`function(el)`): retrieve elements to attach Pjax behavior
- `Pjax.prototype.parseDOM` (`function(el)`): parse DOM to attach behavior using `Pjax.prototype.getElements` & `Pjax.prototype.attachLink`
- `Pjax.prototype.attachLink` (`function(el)`): attach Pjax behavior to a link
- `Pjax.prototype.forEachSelectors` (`function(cb, context, DOMcontext)`): call a function for each selectors defined
- `Pjax.prototype.switchSelectors` (`function(selectors, fromEl, toEl, options)`): loop on selectors to switch elements
- `Pjax.prototype.latestChance` (`function(href)`): when everything is fucked up, it's our only hope (just call `window.location = href`)
- `Pjax.prototype.onSwitch` (`function()`): callback triggered when elements are switched, for now it's just trigger a window resize event (lots of lib are listening to this event to draw stuff)
- `Pjax.prototype.loadContent` (`function(html, options)`): switch elements for each selectors
- `Pjax.prototype.doRequest` (`function(location, callback)`): make the ajax request to grab page from the server
- `Pjax.prototype.loadUrl` (`function(href, options)`): do the ajax request, handle html results & eventually handle browser history, analytics & scroll.
### Events
Pjax fires a number of events regardless of how its invoked.
@@ -495,9 +473,9 @@ wrapper on each page (to avoid differences of DOM between pages)
```html
<script>
var disqus_shortname = 'YOURSHORTNAME'
, disqus_identifier = 'PAGEID'
, disqus_url = 'PAGEURL'
, disqus_script = 'embed.js'
var disqus_identifier = 'PAGEID'
var disqus_url = 'PAGEURL'
var disqus_script = 'embed.js'
(function(d,s) {
s = d.createElement('script');s.async=1;s.src = '//' + disqus_shortname + '.disqus.com/'+disqus_script;
(d.getElementsByTagName('head')[0]).appendChild(s);
@@ -512,9 +490,9 @@ wrapper on each page (to avoid differences of DOM between pages)
<!-- if (blah blah) { // eventual server side test to know wheter or not you include this script -->
<script>
var disqus_shortname = 'YOURSHORTNAME'
, disqus_identifier = 'PAGEID'
, disqus_url = 'PAGEURL'
, disqus_script = 'embed.js'
var disqus_identifier = 'PAGEID'
var disqus_url = 'PAGEURL'
var disqus_script = 'embed.js'
// here we will only load the disqus <script> if not already loaded
if (!window.DISQUS) {
@@ -543,15 +521,19 @@ wrapper on each page (to avoid differences of DOM between pages)
---
## Contributing
## Examples
Work on a branch, install dev-dependencies, respect coding style & run tests before submitting a bug fix or a feature.
Clone this repository and run `npm run example`, then open `http://localhost:3000/example` in your browser.
$ git clone https://github.com/MoOx/pjax.git
$ git checkout -b patch-1
$ npm install
$ npm test
---
## [Changelog](CHANGELOG.md)
## 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`).
## [CHANGELOG](CHANGELOG.md)
## [LICENSE](LICENSE)
## [License](LICENSE)

11
TODO.md
View File

@@ -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 ?

View File

@@ -1,6 +1,6 @@
{
"name": "pjax",
"version": "0.1.4",
"version": "0.2.4",
"description": "Easily enable fast Ajax navigation on any website (using pushState + xhr)",
"keywords": [
"pjax",
@@ -11,15 +11,13 @@
"transition",
"animation"
],
"main": "src/pjax.js",
"main": "pjax.js",
"homepage": "https://github.com/MoOx/pjax",
"authors": [
"Maxime Thirouin"
],
"license": "MIT",
"moduleType": [
"amd",
"globals",
"node"
],
"ignore": [

26
example/example.js Normal file
View File

@@ -0,0 +1,26 @@
/* global Pjax */
console.log("Document initialized:", window.location.href)
document.addEventListener("pjax:send", function() {
console.log("Event: pjax:send", arguments)
})
document.addEventListener("pjax:complete", function() {
console.log("Event: pjax:complete", arguments)
})
document.addEventListener("pjax:error", function() {
console.log("Event: pjax:error", arguments)
})
document.addEventListener("pjax:success", function() {
console.log("Event: pjax:success", arguments)
})
document.addEventListener("DOMContentLoaded", function() {
var pjax = new Pjax({
selectors: [".body"],
// currentUrlFullReload: true,
})
console.log("Pjax initialized.", pjax)
})

17
example/index.html Normal file
View File

@@ -0,0 +1,17 @@
<!doctype html>
<html>
<head>
<meta charset='utf-8'>
<title>Hello</title>
<script src='../pjax.js'></script>
<script src='example.js'></script>
</head>
<body>
<div class='body'>
<h1>Index</h1>
Hello.
Go to <a href='page2.html'>Page 2</a> and view your console to see Pjax events.
Clicking on <a href='index.html'>this page</a> will just reload the page entierly.
</div>
</body>
</html>

15
example/page2.html Normal file
View File

@@ -0,0 +1,15 @@
<!doctype html>
<html>
<head>
<meta charset='utf-8'>
<title>Hello</title>
<script src='../pjax.js'></script>
<script src='example.js'></script>
</head>
<body>
<div class='body'>
<h1>Page 2</h1>
Hello. Go to <a href='index.html'>Index</a>.
</div>
</body>
</html>

242
index.js Normal file
View File

@@ -0,0 +1,242 @@
var clone = require('./lib/clone.js')
var executeScripts = require('./lib/execute-scripts.js')
var forEachEls = require("./lib/foreach-els.js")
var newUid = require("./lib/uniqueid.js")
var on = require("./lib/events/on.js")
// var off = require("./lib/events/on.js")
var trigger = require("./lib/events/trigger.js")
var Pjax = function(options) {
this.firstrun = true
var parseOptions = require("./lib/proto/parse-options.js");
parseOptions.apply(this,[options])
this.log("Pjax options", this.options)
this.maxUid = this.lastUid = newUid()
this.parseDOM(document)
on(window, "popstate", function(st) {
if (st.state) {
var opt = clone(this.options)
opt.url = st.state.url
opt.title = st.state.title
opt.history = false
if (st.state.uid < this.lastUid) {
opt.backward = true
}
else {
opt.forward = true
}
this.lastUid = st.state.uid
// @todo implement history cache here, based on uid
this.loadUrl(st.state.url, opt)
}
}.bind(this))
}
Pjax.prototype = {
log: require("./lib/proto/log.js"),
getElements: require("./lib/proto/get-elements.js"),
parseDOM: require("./lib/proto/parse-dom.js"),
refresh: require("./lib/proto/refresh.js"),
reload: require("./lib/reload.js"),
attachLink: require("./lib/proto/attach-link.js"),
forEachSelectors: function(cb, context, DOMcontext) {
return require("./lib/foreach-selectors.js").bind(this)(this.options.selectors, cb, context, DOMcontext)
},
switchSelectors: function(selectors, fromEl, toEl, options) {
return require("./lib/switches-selectors.js").bind(this)(this.options.switches, this.options.switchesOptions, selectors, fromEl, toEl, options)
},
// too much problem with the code below
// + its too dangerous
// switchFallback: function(fromEl, toEl) {
// this.switchSelectors(["head", "body"], fromEl, toEl)
// // execute script when DOM is like it should be
// Pjax.executeScripts(document.querySelector("head"))
// Pjax.executeScripts(document.querySelector("body"))
// }
latestChance: function(href) {
window.location = href
},
onSwitch: function() {
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>)
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.
// we clear focus on non form elements
if (document.activeElement && !document.activeElement.value) {
try {
document.activeElement.blur()
} catch (e) { }
}
// try {
this.switchSelectors(this.options.selectors, tmpEl, document, options)
// FF bug: Wont autofocus fields that are inserted via JS.
// This behavior is incorrect. So if theres no current focus, autofocus
// the last field.
//
// http://www.w3.org/html/wg/drafts/html/master/forms.html
var autofocusEl = Array.prototype.slice.call(document.querySelectorAll("[autofocus]")).pop()
if (autofocusEl && document.activeElement !== autofocusEl) {
autofocusEl.focus();
}
// execute scripts when DOM have been completely updated
this.options.selectors.forEach(function(selector) {
forEachEls(document.querySelectorAll(selector), function(el) {
executeScripts(el)
})
})
// }
// catch(e) {
// if (this.options.debug) {
// this.log("Pjax switch fail: ", e)
// }
// this.switchFallback(tmpEl, document)
// }
},
doRequest: require("./lib/request.js"),
loadUrl: function(href, options) {
this.log("load href", href, options)
trigger(document, "pjax:send", options);
// Do the request
this.doRequest(href, function(html) {
// Fail if unable to load HTML via AJAX
if (html === false) {
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
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.isSupported = require("./lib/is-supported.js");
//arguably could do `if( require("./lib/is-supported.js")()) {` but that might be a little to simple
if (Pjax.isSupported()) {
module.exports = Pjax
}
// if there isnt required browser functions, returning stupid api
else {
var stupidPjax = function() {}
for (var key in Pjax.prototype) {
if (Pjax.prototype.hasOwnProperty(key) && typeof Pjax.prototype[key] === "function") {
stupidPjax[key] = stupidPjax
}
}
module.exports = stupidPjax
}

12
lib/clone.js Normal file
View File

@@ -0,0 +1,12 @@
module.exports = function(obj) {
if (null === obj || "object" != typeof obj) {
return obj
}
var copy = obj.constructor()
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) {
copy[attr] = obj[attr]
}
}
return copy
}

29
lib/eval-script.js Normal file
View File

@@ -0,0 +1,29 @@
module.exports = function(el) {
// console.log("going to execute script", el)
var code = (el.text || el.textContent || el.innerHTML || "")
var head = 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. Cant 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
}

11
lib/events/off.js Normal file
View 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
View 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
View 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 // = new CustomEvent(e) // doesn't everywhere yet
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) {
// THANKS YOU IE (9/10//11 concerned)
// dispatchEvent doesn't work if element is not in the dom
domFix = true
document.body.appendChild(el)
}
el.dispatchEvent(event)
if (domFix) {
el.parentNode.removeChild(el)
}
})
})
}

15
lib/execute-scripts.js Normal file
View File

@@ -0,0 +1,15 @@
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) {
// console.log("going to execute scripts for ", el)
forEachEls(el.querySelectorAll("script"), function(script) {
if (!script.type || script.type.toLowerCase() === "text/javascript") {
if (script.parentNode) {
script.parentNode.removeChild(script)
}
evalScript(script)
}
})
}

9
lib/foreach-els.js Normal file
View File

@@ -0,0 +1,9 @@
/* global HTMLCollection: true */
module.exports = function(els, fn, context) {
if (els instanceof HTMLCollection || els instanceof NodeList || els instanceof Array) {
return Array.prototype.forEach.call(els, fn, context)
}
// assume simple dom element
return fn.call(context, els)
}

8
lib/foreach-selectors.js Normal file
View File

@@ -0,0 +1,8 @@
var forEachEls = require("./foreach-els")
module.exports = function(selectors, cb, context, DOMcontext) {
DOMcontext = DOMcontext || document
selectors.forEach(function(selector) {
forEachEls(DOMcontext.querySelectorAll(selector), cb, context)
})
}

8
lib/is-supported.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = function() {
// Borrowed wholesale from https://github.com/defunkt/jquery-pjax
return window.history &&
window.history.pushState &&
window.history.replaceState &&
// pushState isnt reliable on iOS until 5.
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)
}

View File

@@ -0,0 +1,20 @@
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable")
}
var aArgs = Array.prototype.slice.call(arguments, 1)
var that = this
var Fnoop = function() {}
var fBound = function() {
return that.apply(this instanceof Fnoop && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)))
}
Fnoop.prototype = this.prototype
fBound.prototype = new Fnoop()
return fBound
}
}

89
lib/proto/attach-link.js Normal file
View File

@@ -0,0 +1,89 @@
require("../polyfills/Function.prototype.bind")
var on = require("../events/on")
var clone = require("../clone")
var attrClick = "data-pjax-click-state"
var attrKey = "data-pjax-keyup-state"
var linkAction = function(el, event) {
// Dont break browser special behavior on links (like page in new window)
if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
el.setAttribute(attrClick, "modifier")
return
}
// we do test on href now to prevent unexpected behavior if for some reason
// user have href that can be dynamically updated
// Ignore external links.
if (el.protocol !== window.location.protocol || el.host !== window.location.host) {
el.setAttribute(attrClick, "external")
return
}
// Ignore click if we are on an anchor on the same page
if (el.pathname === window.location.pathname && el.hash.length > 0) {
el.setAttribute(attrClick, "anchor-present")
return
}
// Ignore anchors on the same page (keep native behavior)
if (el.hash && el.href.replace(el.hash, "") === window.location.href.replace(location.hash, "")) {
el.setAttribute(attrClick, "anchor")
return
}
// Ignore empty anchor "foo.html#"
if (el.href === window.location.href.split("#")[0] + "#") {
el.setAttribute(attrClick, "anchor-empty")
return
}
event.preventDefault()
// dont do "nothing" if user try to reload the page by clicking the same link twice
if (
this.options.currentUrlFullReload &&
el.href === window.location.href.split("#")[0]
) {
el.setAttribute(attrClick, "reload")
this.reload()
return
}
el.setAttribute(attrClick, "load")
this.loadUrl(el.href, clone(this.options))
}
var isDefaultPrevented = function(event) {
return event.defaultPrevented || event.returnValue === false;
}
module.exports = function(el) {
var that = this
on(el, "click", function(event) {
if (isDefaultPrevented(event)) {
return
}
linkAction.call(that, el, event)
})
on(el, "keyup", function(event) {
if (isDefaultPrevented(event)) {
return
}
// Dont break browser special behavior on links (like page in new window)
if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
el.setAttribute(attrKey, "modifier")
return
}
if (event.keyCode == 13) {
linkAction.call(that, el, event)
}
}.bind(this))
}

View File

@@ -0,0 +1,3 @@
module.exports = function(el) {
return el.querySelectorAll(this.options.elements)
}

11
lib/proto/log.js Normal file
View 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);
}
}
}

7
lib/proto/parse-dom.js Normal file
View File

@@ -0,0 +1,7 @@
var forEachEls = require("../foreach-els")
var parseElement = require("./parse-element")
module.exports = function(el) {
forEachEls(this.getElements(el), parseElement, this)
}

View File

@@ -0,0 +1,17 @@
module.exports = function(el) {
switch (el.tagName.toLowerCase()) {
case "a":
// only attach link if el does not already have link attached
if (!el.hasAttribute('data-pjax-click-state')) {
this.attachLink(el)
}
break
case "form":
throw "Pjax doesnt support <form> yet."
break
default:
throw "Pjax can only be applied on <a> or <form> submit"
}
}

View File

@@ -0,0 +1,38 @@
/* global _gaq: true, ga: true */
module.exports = function(options){
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.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: location.pathname, title: document.title})
}
}
this.options.scrollTo = (typeof this.options.scrollTo === 'undefined') ? 0 : this.options.scrollTo;
this.options.cacheBust = (typeof this.options.cacheBust === 'undefined') ? true : this.options.cacheBust
this.options.debug = this.options.debug || false
// we cant replace body.outerHTML or head.outerHTML
// it create a bug where new body or new head are created in the dom
// if you set head.outerHTML, a new body tag is appended, so the dom get 2 body
// & it break the switchFallback which replace head & body
if (!this.options.switches.head) {
this.options.switches.head = this.switchElementsAlt
}
if (!this.options.switches.body) {
this.options.switches.body = this.switchElementsAlt
}
if (typeof options.analytics !== "function") {
options.analytics = function() {}
}
}

3
lib/proto/refresh.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = function(el) {
this.parseDOM(el || document)
}

3
lib/reload.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = function() {
window.location.reload()
}

24
lib/request.js Normal file
View File

@@ -0,0 +1,24 @@
module.exports = function(location, callback) {
var request = new XMLHttpRequest()
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200) {
callback(request.responseText, request)
}
else {
callback(null, request)
}
}
}
// Add a timestamp as part of the query string if cache busting is enabled
if (this.options.cacheBust) {
location += (!/[?&]/.test(location) ? "?" : "&") + new Date().getTime()
}
request.open("GET", location, true)
request.setRequestHeader("X-Requested-With", "XMLHttpRequest")
request.send(null)
return request
}

35
lib/switches-selectors.js Normal file
View File

@@ -0,0 +1,35 @@
var forEachEls = require("./foreach-els")
var defaultSwitches = require("./switches")
module.exports = function(switches, switchesOptions, selectors, fromEl, toEl, options) {
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) {
// forEachEls(newEls, function(el) {
// this.log("newEl", el, el.outerHTML)
// }, this)
// forEachEls(oldEls, function(el) {
// this.log("oldEl", el, el.outerHTML)
// }, this)
throw "DOM doesnt 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)
}
if (switches[selector]) {
switches[selector].bind(this)(oldEl, newEl, options, switchesOptions[selector])
}
else {
defaultSwitches.outerHTML.bind(this)(oldEl, newEl, options)
}
}, this)
}, this)
}

115
lib/switches.js Normal file
View File

@@ -0,0 +1,115 @@
var on = require("./events/on.js")
// var off = require("./lib/events/on.js")
// var trigger = require("./lib/events/trigger.js")
module.exports = {
outerHTML: function(oldEl, newEl) {
oldEl.outerHTML = newEl.outerHTML
this.onSwitch()
},
innerHTML: function(oldEl, newEl) {
oldEl.innerHTML = newEl.innerHTML
oldEl.className = newEl.className
this.onSwitch()
},
sideBySide: function(oldEl, newEl, options, switchOptions) {
var forEach = Array.prototype.forEach
var elsToRemove = []
var elsToAdd = []
var fragToAppend = document.createDocumentFragment()
// height transition are shitty on safari
// so commented for now (until I found something ?)
// var relevantHeight = 0
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")
// Pjax.off(el, animationEventNames, sexyAnimationEnd, true)
})
elsToAdd = null // free memory
elsToRemove = null // free memory
// assume the height is now useless (avoid bug since there is overflow hidden on the parent)
// oldEl.style.height = "auto"
// this is to trigger some repaint (example: picturefill)
this.onSwitch()
// Pjax.trigger(window, "scroll")
}
}.bind(this)
// Force height to be able to trigger css animation
// here we get the relevant height
// oldEl.parentNode.appendChild(newEl)
// relevantHeight = newEl.getBoundingClientRect().height
// oldEl.parentNode.removeChild(newEl)
// oldEl.style.height = oldEl.getBoundingClientRect().height + "px"
switchOptions = switchOptions || {}
forEach.call(oldEl.childNodes, function(el) {
elsToRemove.push(el)
if (el.classList && !el.classList.contains("js-Pjax-remove")) {
// for fast switch, clean element that just have been added, & not cleaned yet.
if (el.hasAttribute("data-pjax-classes")) {
el.className = el.className.replace(el.getAttribute("data-pjax-classes"), "")
el.removeAttribute("data-pjax-classes")
}
el.classList.add("js-Pjax-remove")
if (switchOptions.callbacks && switchOptions.callbacks.removeElement) {
switchOptions.callbacks.removeElement(el)
}
if (switchOptions.classNames) {
el.className += " " + switchOptions.classNames.remove + " " + (options.backward ? switchOptions.classNames.backward : switchOptions.classNames.forward)
}
animatedElsNumber++
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)
// oldEl.style.height = relevantHeight + "px"
}
}

8
lib/uniqueid.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = (function() {
var counter = 0
return function() {
var id = ("pjax" + (new Date().getTime())) + "_" + counter
counter++
return id
}
})()

View File

@@ -1,6 +1,6 @@
{
"name": "pjax",
"version": "0.1.4",
"version": "0.2.4",
"description": "Easily enable fast Ajax navigation on any website (using pushState + xhr)",
"keywords": [
"pjax",
@@ -11,29 +11,54 @@
"transition",
"animation"
],
"repository": {
"type": "git",
"url": "https://github.com/MoOx/pjax.git"
},
"homepage": "https://github.com/MoOx/pjax",
"bugs": {
"url": "https://github.com/MoOx/pjax/issues"
},
"repository": "https://github.com/MoOx/pjax.git",
"author": "Maxime Thirouin",
"license": "MIT",
"main": "src/pjax.js",
"files": [
"CHANGELOG.md",
"LICENSE",
"src"
"index.js",
"lib",
"pjax.js"
],
"devDependencies": {
"browserify": "^3.46.0",
"coverify": "^1.0.6",
"jscs": "^1.6.2",
"jshint": "^2.5.6",
"postcss": "^2.2.5"
"npmpub": "^3.1.0",
"opn-cli": "^3.1.0",
"serve": "1.4.0",
"tape": "^3.0.0",
"testling": "^1.6.1"
},
"scripts": {
"lint": "jscs **/*.js && jshint . --exclude-path .gitignore",
"test": "npm run lint"
"standalone": "browserify index.js --standalone Pjax > pjax.js",
"tests": "testling",
"test": "npm run lint && npm run standalone && npm run tests",
"test--html": "testling --html > tests/scripts/index.html",
"coverage": "browserify -t coverify tests/**/*.js | testling | coverify",
"example": "opn http://localhost:3000/example/; serve .",
"prepublish": "npm run standalone",
"#release": "testling does not work in a process launch by npm... :facepalm:",
"release": "echo \"npmpub --skip-test --dry && npm test && npmpub --skip-test --skip-cleanup\""
},
"testling": {
"files": "tests/**/*.js",
"browsers": [
"ie/10..latest",
"firefox/4.0",
"firefox/latest",
"firefox/nightly",
"chrome/10",
"chrome/latest",
"chrome/canary",
"opera/12..latest",
"opera/next",
"safari/5.1..latest",
"ipad/6.0..latest",
"iphone/6.0..latest",
"android-browser/4.2..latest"
]
}
}

View File

@@ -1,615 +0,0 @@
;(function(root, factory) {
if (typeof exports === "object") {
// CommonJS
module.exports = factory()
}
else if (typeof define === "function" && define.amd) {
// AMD
define([], factory)
}
else {
// Global Variables
root.Pjax = factory()
}
}(this, function() {
"use strict";
function newUid() {
return (new Date().getTime())
}
var Pjax = function(options) {
this.firstrun = true
this.options = options
this.options.elements = this.options.elements || "a[href], form[action]"
this.options.selectors = this.options.selectors || ["title", ".js-Pjax"]
this.options.switches = this.options.switches || {}
this.options.switchesOptions = this.options.switchesOptions || {}
this.options.history = this.options.history || true
this.options.currentUrlFullReload = this.options.currentUrlFullReload || false
this.options.analytics = this.options.analytics || function(options) {
// options.backward or options.foward can be true or undefined
// by default, we do track back/foward hit
// https://productforums.google.com/forum/#!topic/analytics/WVwMDjLhXYk
if (window._gaq) {
_gaq.push(["_trackPageview"])
}
if (window.ga) {
ga("send", "pageview", {"page": options.url, "title": options.title})
}
}
this.options.scrollTo = this.options.scrollTo || 0
this.options.debug = this.options.debug || false
this.maxUid = this.lastUid = newUid()
// we cant replace body.outerHTML or head.outerHTML
// it create a bug where new body or new head are created in the dom
// if you set head.outerHTML, a new body tag is appended, so the dom get 2 body
// & it break the switchFallback which replace head & body
if (!this.options.switches.head) {
this.options.switches.head = this.switchElementsAlt
}
if (!this.options.switches.body) {
this.options.switches.body = this.switchElementsAlt
}
this.log("Pjax options", this.options)
if (typeof options.analytics !== "function") {
options.analytics = function() {}
}
this.parseDOM(document)
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 isnt reliable on iOS until 5.
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)
}
Pjax.forEachEls = function(els, fn, context) {
if (els instanceof HTMLCollection || els instanceof NodeList) {
return Array.prototype.forEach.call(els, fn, context)
}
// assume simple dom element
fn.call(context, els)
}
Pjax.on = function(els, events, listener, useCapture) {
events = (typeof events === "string" ? events.split(" ") : events)
events.forEach(function(e) {
Pjax.forEachEls(els, function(el) {
el.addEventListener(e, listener, useCapture)
})
}, this)
}
Pjax.off = function(els, events, listener, useCapture) {
events = (typeof events === "string" ? events.split(" ") : events)
events.forEach(function(e) {
Pjax.forEachEls(els, function(el) {
el.removeEventListener(e, listener, useCapture)
})
}, this)
}
Pjax.trigger = function(els, events) {
events = (typeof events === "string" ? events.split(" ") : events)
events.forEach(function(e) {
var event
if (document.createEvent) {
event = document.createEvent("HTMLEvents")
event.initEvent(e, true, true)
}
else {
event = document.createEventObject()
event.eventType = e
}
event.eventName = e
if (document.createEvent) {
Pjax.forEachEls(els, function(el) {
el.dispatchEvent(event)
})
}
else {
Pjax.forEachEls(els, function(el) {
el.fireEvent("on" + event.eventType, event)
})
}
}, this)
}
Pjax.clone = function(obj) {
if (null === obj || "object" != typeof obj) {
return obj
}
var copy = obj.constructor()
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) {
copy[attr] = obj[attr]
}
}
return copy
}
// Finds and executes scripts (used for newly added elements)
// Needed since innerHTML does not run scripts
Pjax.executeScripts = function(el) {
// console.log("going to execute scripts for ", el)
Pjax.forEachEls(el.querySelectorAll("script"), function(script) {
if (!script.type || script.type.toLowerCase() === "text/javascript") {
if (script.parentNode) {
script.parentNode.removeChild(script)
}
Pjax.evalScript(script)
}
})
}
Pjax.evalScript = function(el) {
// console.log("going to execute script", el)
var code = (el.text || el.textContent || el.innerHTML || "")
, head = document.querySelector("head") || document.documentElement
, script = document.createElement("script")
if (code.match("document.write")) {
if (console && console.log) {
console.log("Script contains document.write. Cant 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
// Dont break browser special behavior on links (like page in new window)
if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
return -1
}
// Ignore external links.
if (el.protocol !== window.location.protocol || el.host !== window.location.host) {
return -2
}
// Ignore anchors on the same page
if (el.pathname === location.pathname && el.hash.length > 0) {
return -3
}
// Ignore anchors on the same page
if (el.hash && el.href.replace(el.hash, "") === location.href.replace(location.hash, "")) {
return -4
}
// Ignore empty anchor "foo.html#"
if (el.href === location.href + "#") {
return -5
}
event.preventDefault()
if (this.options.currentUrlFullReload) {
if (el.href === window.location.href) {
window.location.reload()
return -6
}
}
this.loadUrl(el.href, Pjax.clone(this.options))
}.bind(this))
Pjax.on(el, "keyup", function(event) {
this.log("pjax todo")
// todo handle a link hitted by keyboard (enter/space) when focus is on it
}.bind(this))
}
, forEachSelectors: function(cb, context, DOMcontext) {
DOMcontext = DOMcontext || document
this.options.selectors.forEach(function(selector) {
Pjax.forEachEls(DOMcontext.querySelectorAll(selector), cb, context)
})
}
, switchSelectors: function(selectors, fromEl, toEl, options) {
selectors.forEach(function(selector) {
var newEls = fromEl.querySelectorAll(selector)
var oldEls = toEl.querySelectorAll(selector)
this.log("Pjax switch", selector, newEls, oldEls)
if (newEls.length !== oldEls.length) {
// Pjax.forEachEls(newEls, function(el) {
// this.log("newEl", el, el.outerHTML)
// }, this)
// Pjax.forEachEls(oldEls, function(el) {
// this.log("oldEl", el, el.outerHTML)
// }, this)
throw "DOM doesnt 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
// + its too dangerous
// , switchFallback: function(fromEl, toEl) {
// this.switchSelectors(["head", "body"], fromEl, toEl)
// // execute script when DOM is like it should be
// Pjax.executeScripts(document.querySelector("head"))
// Pjax.executeScripts(document.querySelector("body"))
// }
, latestChance: function(href) {
window.location = href
}
, onSwitch: function() {
Pjax.trigger(window, "resize scroll")
}
, loadContent: function(html, options) {
var tmpEl = document.implementation.createHTMLDocument("")
// parse HTML attributes to copy them
// since we are forced to use documentElement.innerHTML (outerHTML can't be used for <html>)
, htmlRegex = /<html[^>]+>/gi
, htmlAttribsRegex = /\s?[a-z:]+(?:\=(?:\'|\")[^\'\">]+(?:\'|\"))*/gi
, matches = html.match(htmlRegex)
if (matches && matches.length) {
matches = matches[0].match(htmlAttribsRegex)
if (matches.length) {
matches.shift()
matches.forEach(function(htmlAttrib) {
var attr = htmlAttrib.trim().split("=")
tmpEl.documentElement.setAttribute(attr[0], attr[1].slice(1, -1))
})
}
}
tmpEl.documentElement.innerHTML = html
this.log("load content", tmpEl.documentElement.attributes, tmpEl.documentElement.innerHTML.length)
// try {
this.switchSelectors(this.options.selectors, tmpEl, document, options)
// FF bug: Wont autofocus fields that are inserted via JS.
// This behavior is incorrect. So if theres no current focus, autofocus
// the last field.
//
// http://www.w3.org/html/wg/drafts/html/master/forms.html
var autofocusEl = Array.prototype.slice.call(document.querySelectorAll("[autofocus]")).pop()
if (autofocusEl && document.activeElement !== autofocusEl) {
autofocusEl.focus();
}
// execute scripts when DOM have been completely updated
this.options.selectors.forEach(function(selector) {
Pjax.forEachEls(document.querySelectorAll(selector), function(el) {
Pjax.executeScripts(el)
})
})
// }
// catch(e) {
// if (this.options.debug) {
// this.log("Pjax switch fail: ", e)
// }
// this.switchFallback(tmpEl, document)
// }
}
, doRequest: function(location, callback) {
var request = new XMLHttpRequest()
request.onreadystatechange = function() {
if (request.readyState === 4 && request.status === 200) {
callback(request.responseText)
}
else if (request.readyState === 4 && (request.status === 404 || request.status === 500)){
callback(false)
}
}
request.open("GET", location + (!/[?&]/.test(location) ? "?" : "&") + (new Date().getTime()), true)
request.setRequestHeader("X-Requested-With", "XMLHttpRequest")
request.send(null)
}
, loadUrl: function(href, options) {
this.log("load href", href, options)
Pjax.trigger(document, "pjax:send", options);
// Do the request
this.doRequest(href, function(html) {
// Fail if unable to load HTML via AJAX
if (html === false) {
Pjax.trigger(document,"pjax:complete pjax:error", options)
return
}
// Clear out any focused controls before inserting new page contents.
document.activeElement.blur()
try {
this.loadContent(html, options)
}
catch (e) {
if (!this.options.debug) {
if (console && console.error) {
console.error("Pjax switch fail: ", e)
}
this.latestChance(href)
return
}
else {
throw e
}
}
if (options.history) {
if (this.firstrun) {
this.lastUid = this.maxUid = newUid()
this.firstrun = false
window.history.replaceState({
"url": window.location.href
, "title": document.title
, "uid": this.maxUid
}
, document.title)
}
// Update browser history
this.lastUid = this.maxUid = newUid()
window.history.pushState({
"url": href
, "title": options.title
, "uid": this.maxUid
}
, options.title
, href)
}
this.forEachSelectors(function(el) {
this.parseDOM(el)
}, this)
// Fire Events
Pjax.trigger(document,"pjax:complete pjax:success", options)
options.analytics()
// Scroll page to top on new page load
if (options.scrollTo !== false) {
if (options.scrollTo.length > 1) {
window.scrollTo(options.scrollTo[0], options.scrollTo[1])
}
else {
window.scrollTo(0, options.scrollTo)
}
}
}.bind(this))
}
}
Pjax.switches = {
outerHTML: function(oldEl, newEl, options) {
oldEl.outerHTML = newEl.outerHTML
this.onSwitch()
}
, innerHTML: function(oldEl, newEl, options) {
oldEl.innerHTML = newEl.innerHTML
oldEl.className = newEl.className
this.onSwitch()
}
, sideBySide: function(oldEl, newEl, options, switchOptions) {
var forEach = Array.prototype.forEach
, elsToRemove = []
, elsToAdd = []
, fragToAppend = document.createDocumentFragment()
// height transition are shitty on safari
// so commented for now (until I found something ?)
// , relevantHeight = 0
, animationEventNames = "animationend webkitAnimationEnd MSAnimationEnd oanimationend"
, animatedElsNumber = 0
, sexyAnimationEnd = function(e) {
if (e.target != e.currentTarget) {
// end triggered by an animation on a child
return
}
animatedElsNumber--
if (animatedElsNumber <= 0 && elsToRemove) {
elsToRemove.forEach(function(el) {
// browsing quickly can make the el
// already removed by last page update ?
if (el.parentNode) {
el.parentNode.removeChild(el)
}
})
elsToAdd.forEach(function(el) {
el.className = el.className.replace(el.getAttribute("data-pjax-classes"), "")
el.removeAttribute("data-pjax-classes")
// Pjax.off(el, animationEventNames, sexyAnimationEnd, true)
})
elsToAdd = null // free memory
elsToRemove = null // free memory
// assume the height is now useless (avoid bug since there is overflow hidden on the parent)
// oldEl.style.height = "auto"
// this is to trigger some repaint (example: picturefill)
this.onSwitch()
//Pjax.trigger(window, "scroll")
}
}.bind(this)
// Force height to be able to trigger css animation
// here we get the relevant height
// oldEl.parentNode.appendChild(newEl)
// relevantHeight = newEl.getBoundingClientRect().height
// oldEl.parentNode.removeChild(newEl)
// oldEl.style.height = oldEl.getBoundingClientRect().height + "px"
switchOptions = switchOptions || {}
forEach.call(oldEl.childNodes, function(el) {
elsToRemove.push(el)
if (el.classList && !el.classList.contains("js-Pjax-remove")) {
// for fast switch, clean element that just have been added, & not cleaned yet.
if (el.hasAttribute("data-pjax-classes")) {
el.className = el.className.replace(el.getAttribute("data-pjax-classes"), "")
el.removeAttribute("data-pjax-classes")
}
el.classList.add("js-Pjax-remove")
if (switchOptions.callbacks && switchOptions.callbacks.removeElement) {
switchOptions.callbacks.removeElement(el)
}
if (switchOptions.classNames) {
el.className += " " + switchOptions.classNames.remove + " " + (options.backward ? switchOptions.classNames.backward : switchOptions.classNames.forward)
}
animatedElsNumber++
Pjax.on(el, animationEventNames, sexyAnimationEnd, true)
}
})
forEach.call(newEl.childNodes, function(el) {
if (el.classList) {
var addClasses = ""
if (switchOptions.classNames) {
addClasses = " js-Pjax-add " + switchOptions.classNames.add + " " + (options.backward ? switchOptions.classNames.forward : switchOptions.classNames.backward)
}
if (switchOptions.callbacks && switchOptions.callbacks.addElement) {
switchOptions.callbacks.addElement(el)
}
el.className += addClasses
el.setAttribute("data-pjax-classes", addClasses)
elsToAdd.push(el)
fragToAppend.appendChild(el)
animatedElsNumber++
Pjax.on(el, animationEventNames, sexyAnimationEnd, true)
}
})
// pass all className of the parent
oldEl.className = newEl.className
oldEl.appendChild(fragToAppend)
// oldEl.style.height = relevantHeight + "px"
}
}
if (Pjax.isSupported()) {
return Pjax
}
// if there isnt required browser functions, returning stupid api
else {
var stupidPjax = function() {}
for (var key in Pjax.prototype) {
if (Pjax.prototype.hasOwnProperty(key) && typeof Pjax.prototype[key] === "function") {
stupidPjax[key] = stupidPjax
}
}
return stupidPjax
}
}));

12496
tests/index.html Normal file

File diff suppressed because one or more lines are too long

17
tests/lib/clone.js Normal file
View File

@@ -0,0 +1,17 @@
var tape = require("tape")
var clone = require("../../lib/clone")
tape("test clone method", function(t) {
var obj = {one: 1, two: 2}
var cloned = clone(obj)
t.notEqual(obj, cloned, "cloned object isn't the object")
t.same(obj, cloned, "cloned object have the same values than object")
cloned.tree = 3
t.notSame(obj, cloned, "modified cloned object haven't the same values than object")
t.end()
})

22
tests/lib/eval-scripts.js Normal file
View File

@@ -0,0 +1,22 @@
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.body.text
// evalScript(script)
// t.equal(document.body.text, bodyText, "document.write hasn't been executed")
t.end()
})

110
tests/lib/events.js Normal file
View File

@@ -0,0 +1,110 @@
var tape = require("tape")
var on = require("../../lib/events/on")
var off = require("../../lib/events/off")
var trigger = require("../../lib/events/trigger")
var el = document.createElement("div")
var el2 = document.createElement("span")
var els = [el, el2]
// var eventType2 = "resize"
// var eventsType = "click resize"
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 = ""
on(el, "click", classCb)
trigger(el, "click")
t.equal(el.className, "on", "attached callback has been fired properly on window")
t.end()
})

View File

@@ -0,0 +1,16 @@
var tape = require("tape")
var executeScripts = require("../../lib/execute-scripts")
tape("test executeScripts method", function(t) {
document.body.className = ""
var container = document.createElement("div")
container.innerHTML = "<" + "script" + ">document.body.className = 'executed';</" + "script" + "><" + "script" + ">document.body.className += ' correctly';</" + "script" + ">"
t.equal(document.body.className, "", "script hasn't been executed yet")
executeScripts(container)
t.equal(document.body.className, "executed correctly", "script has been properly executed")
t.end()
})

45
tests/lib/foreach-els.js Normal file
View File

@@ -0,0 +1,45 @@
var tape = require("tape")
var forEachEls = require("../../lib/foreach-els.js")
var div = document.createElement("div")
var span = document.createElement("span")
var cb = function(el) {
el.innerHTML = "boom"
}
tape("test forEachEls on one element", function(t) {
div.innerHTML = "div tag"
forEachEls(div, cb)
t.equal(div.innerHTML, "boom", "works correctly on one element")
t.end()
})
tape("test forEachEls on an array", function(t) {
div.innerHTML = "div tag"
span.innerHTML = "span tag"
forEachEls([div, span], cb)
t.equal(div.innerHTML, "boom", "works correctly on the first element of the array")
t.equal(span.innerHTML, "boom", "works correctly on the last element of the array")
t.end()
})
tape("test forEachEls on a NodeList", function(t) {
div.innerHTML = "div tag"
span.innerHTML = "span tag"
var frag = document.createDocumentFragment()
frag.appendChild(div)
frag.appendChild(span)
forEachEls(frag.childNodes, cb)
t.equal(div.innerHTML, "boom", "works correctly on the first element of the document fragment")
t.equal(span.innerHTML, "boom", "works correctly on the last element of the document fragment")
t.end()
})

View File

@@ -0,0 +1,24 @@
var tape = require("tape")
var forEachEls = require("../../lib/foreach-selectors.js")
var cb = function(el) {
el.className = "modified"
}
tape("test forEachSelector", function(t) {
forEachEls(["html", "body"], cb)
t.equal(document.documentElement.className, "modified", "callback has been executed on first selector")
t.equal(document.body.className, "modified", "callback has been executed on first selector")
document.documentElement.className = ""
document.body.className = ""
forEachEls(["html", "body"], cb, null, document.documentElement)
t.equal(document.documentElement.className, "", "callback has not been executed on first selector when context is used")
t.equal(document.body.className, "modified", "callback has been executed on first selector when context is used")
t.end()
})

View File

@@ -0,0 +1,8 @@
var tape = require("tape")
var isSupported = require("../../lib/is-supported.js")
tape("test isSupported method", function(t) {
t.true(isSupported(), "well, we run test on supported browser, so it should be ok here")
t.end()
})

View File

@@ -0,0 +1,77 @@
var tape = require("tape")
var on = require("../../../lib/events/on")
var trigger = require("../../../lib/events/trigger")
var attachLink = require("../../../lib/proto/attach-link")
var a = document.createElement("a")
var attr = "data-pjax-click-state"
var preventDefault = function(e) { e.preventDefault() }
tape("test attach link prototype method", function(t) {
t.plan(7)
attachLink.call({
options: {},
reload: function() {
t.equal(a.getAttribute(attr), "reload", "triggering exact same url reload the page")
},
loadUrl: function() {
t.equal(a.getAttribute(attr), "load", "triggering a internal link actually load the page")
}
}, a)
var internalUri = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search
a.href = internalUri
on(a, "click", preventDefault) // to avoid link to be open (break testing env)
trigger(a, "click", {metaKey: true})
t.equal(a.getAttribute(attr), "modifier", "event key modifier stop behavior")
a.href = "http://external.com/"
trigger(a, "click")
t.equal(a.getAttribute(attr), "external", "external url stop behavior")
a.href = internalUri + "#anchor"
trigger(a, "click")
t.equal(a.getAttribute(attr), "anchor-present", "internal anchor stop behavior")
window.location.hash = "#anchor"
a.href = internalUri + "#another-anchor"
trigger(a, "click")
t.notEqual(a.getAttribute(attr), "anchor", "differents anchors stop behavior")
window.location.hash = ""
a.href = internalUri + "#"
trigger(a, "click")
t.equal(a.getAttribute(attr), "anchor-empty", "empty anchor stop behavior")
a.href = internalUri
trigger(a, "click")
// see reload defined above
a.href = window.location.protocol + "//" + window.location.host + "/internal"
trigger(a, "click")
// see loadUrl defined above
t.end()
})
tape("test attach link preventDefaulted events", function(t) {
var callbacked = false
var a = document.createElement("a")
attachLink.call({
options: {},
loadUrl: function() {
callbacked = true
}
}, a)
a.href = "#"
on(a, "click", preventDefault)
trigger(a, "click")
t.equal(callbacked, false, "events that are preventDefaulted should not fire callback")
t.end()
})

View File

@@ -0,0 +1,17 @@
var tape = require("tape")
var parseElement = require("../../../lib/proto/parse-element")
var protoMock = {attachLink: function() { return true}}
tape("test parse element prototype method", function(t) {
t.doesNotThrow(function() {
var a = document.createElement("a")
parseElement.call(protoMock, a)
}, "<a> element can be parsed")
t.throws(function() {
var form = document.createElement("form")
parseElement.call(protoMock, form)
}, "<form> cannot be used (for now)")
t.end()
})

View File

@@ -0,0 +1,73 @@
var tape = require("tape")
var parseOptions = require("../../../lib/proto/parse-options.js")
tape("test parse initalization options function", function(t) {
// via http://stackoverflow.com/questions/1173549/how-to-determine-if-an-object-is-an-object-literal-in-javascript
function isObjLiteral(_obj) {
var _test = _obj;
return ( typeof _obj !== 'object' || _obj === null ?
false :
(
(function () {
while (!false) {
if ( Object.getPrototypeOf( _test = Object.getPrototypeOf(_test) ) === null) {
break;
}
}
return Object.getPrototypeOf(_obj) === _test;
})()
)
);
}
function enumerableKeys(_obj) {
var c = 0;
for(var n in _obj){ n = n; c++; }
return c;
}
t.test("- default options", function(t){
var body_1 = {};
var options_1 = {};
parseOptions.apply(body_1,[options_1]);
t.deepEqual(body_1.options.elements,"a[href], form[action]");
t.deepEqual(body_1.options.selectors.length,2,"selectors length");
t.deepEqual(body_1.options.selectors[0],"title");
t.deepEqual(body_1.options.selectors[1],".js-Pjax");
t.deepEqual(isObjLiteral(body_1.options.switches),true);
t.deepEqual(enumerableKeys(body_1.options.switches),2);//head and body
t.deepEqual(isObjLiteral(body_1.options.switchesOptions),true);
t.deepEqual(enumerableKeys(body_1.options.switchesOptions),0);
t.deepEqual(body_1.options.history,true);
//TODO analytics is a little weird right now
t.deepEqual(typeof body_1.options.analytics,"function");
t.deepEqual(body_1.options.scrollTo,0);
t.deepEqual(body_1.options.cacheBust,true);
t.deepEqual(body_1.options.debug,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 body_2 = {};
var options_2 = {analytics:"some string"};
parseOptions.apply(body_2,[options_2]);
t.deepEqual(typeof body_2.options.analytics,"function");
t.end();
});
//verify that the value false for scrollTo is not squashed
t.test("- scrollTo remains false", function(t){
var body_3 = {};
var options_3 = {scrollTo:false};
parseOptions.apply(body_3,[options_3]);
t.deepEqual( body_3.options.scrollTo,false);
t.end();
});
t.end()
})

48
tests/lib/request.js Normal file
View File

@@ -0,0 +1,48 @@
var tape = require("tape")
var request = require("../../lib/request.js")
// Polyfill responseURL property into XMLHttpRequest if it doesn't exist,
// just for the purposes of this test
// This polyfill is not complete; it won't show the updated location if a
// redirection occurred, but it's fine for our purposes.
if (!('responseURL' in XMLHttpRequest.prototype)) {
var nativeOpen = XMLHttpRequest.prototype.open
XMLHttpRequest.prototype.open = function (method, url) {
this.responseURL = url
return nativeOpen.apply(this, arguments)
}
}
tape("test xhr request", function(t) {
t.test("- request is made, gets a result, and is cache-busted", function(t) {
var requestCacheBust = request.bind({
options: {
cacheBust: true,
},
});
var r = requestCacheBust("https://api.github.com/", function(result) {
t.equal(r.responseURL.indexOf("?"), 23, "XHR URL is cache-busted when configured to be")
try {
result = JSON.parse(result)
}
catch (e) {
t.fail("xhr doesn't get a JSON response")
}
t.same(typeof result, "object", "xhr request get a result")
t.end()
})
})
t.test("- request is not cache-busted when configured not to be", function(t) {
var requestNoCacheBust = request.bind({
options: {
cacheBust: false,
},
});
var r = requestNoCacheBust("https://api.github.com/", function() {
t.equal(r.responseURL, "https://api.github.com/", "XHR URL is left untouched")
t.end()
})
})
t.end()
})

View File

@@ -0,0 +1,41 @@
var tape = require("tape")
var switchesSelectors = require("../../lib/switches-selectors.js")
// @author darylteo
tape("test switchesSelectors", function(t) {
// switchesSelectors relies on a higher level function callback
// should really be passed in instead so I'll leave it here as a TODO:
var pjax = {
onSwitch: function() {
console.log('Switched')
}
}
var tmpEl = document.implementation.createHTMLDocument()
// a div container is used because swapping the containers
// will generate a new element, so things get weird
// using "body" generates a lot of testling cruft that I don't
// want so let's avoid that
var container = document.createElement("div")
container.innerHTML = "<p>Original Text</p><span>No Change</span>"
document.body.appendChild(container)
var container2 = tmpEl.createElement("div")
container2.innerHTML = "<p>New Text</p><span>New Span</span>"
tmpEl.body.appendChild(container2)
switchesSelectors.bind(pjax)(
{}, // switches
{}, // switchesOptions
['p'], //selectors,
tmpEl, // fromEl
document, // toEl,
{} // options
)
t.equals(container.innerHTML, '<p>New Text</p><span>No Change</span>', 'Elements correctly switched')
t.end()
})

12
tests/lib/uniqueid.js Normal file
View 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()
})

View File