Skip to content

Commit b867817

Browse files
authored
Merge pull request #50 from natefaubion/remove-ffi
API Updates (breaking)
2 parents 7b0773e + 0bf0186 commit b867817

File tree

10 files changed

+227
-115
lines changed

10 files changed

+227
-115
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
/node_modules/
66
/output/
77
/public/test.js
8+
/test/dist

bower.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
"purescript-aff": "^4.0.0",
3939
"purescript-control": "^3.0.0",
4040
"purescript-console": "^3.0.0",
41-
"purescript-integers": "^3.0.0"
41+
"purescript-integers": "^3.0.0",
42+
"purescript-foldable-traversable": "^3.7.1"
4243
},
4344
"devDependencies": {
4445
"purescript-console": "^3.0.0",

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"scripts": {
44
"clean": "rimraf output && rimraf .pulp-cache",
55
"build": "pulp build -- --censor-lib --strict",
6-
"test": "pulp test"
6+
"test": "pulp test",
7+
"test:browser": "pulp build -I test -m Test.Browser --to test/dist/bundle.js"
78
},
89
"devDependencies": {
910
"pulp": "^11.0.0",

src/Routing.js

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/Routing.purs

Lines changed: 18 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,28 @@
1-
module Routing (
2-
hashChanged,
3-
hashes,
4-
matches,
5-
matches',
6-
match,
7-
matchWith,
8-
matchesAff,
9-
matchesAff'
1+
module Routing
2+
( RoutingEffects
3+
, match
4+
, matchWith
105
) where
116

12-
import Control.Monad.Aff (Aff, makeAff, nonCanceler)
13-
import Control.Monad.Eff (Eff)
14-
import Data.Either (Either(..), either)
15-
import Data.Maybe (Maybe(..))
16-
import Data.String.Regex as R
17-
import Data.String.Regex.Flags as RF
18-
import Data.Tuple (Tuple(..))
19-
import Prelude (Unit, const, pure, unit, ($), (<$))
7+
import Prelude
8+
9+
import Control.Monad.Eff.Ref (REF)
10+
import Data.Either (Either)
11+
import DOM (DOM)
12+
import Global (decodeURIComponent)
2013
import Routing.Match (Match, runMatch)
2114
import Routing.Parser (parse)
2215

16+
type RoutingEffects eff =
17+
( dom :: DOM
18+
, ref :: REF
19+
| eff
20+
)
2321

24-
foreign import decodeURIComponent :: String -> String
25-
26-
foreign import hashChanged :: forall e. (String -> String -> Eff e Unit) -> Eff e Unit
27-
28-
29-
hashes :: forall e. (String -> String -> Eff e Unit) -> Eff e Unit
30-
hashes cb =
31-
hashChanged $ \old new -> do
32-
cb (dropHash old) (dropHash new)
33-
where dropHash h =
34-
case R.regex "^[^#]*#" RF.noFlags of
35-
Right regX -> R.replace regX "" h
36-
Left _ -> h
37-
38-
39-
-- | Stream of hash changed, callback called when new hash can be matched
40-
-- | First argument of callback is `Just a` when old hash can be matched
41-
-- | and `Nothing` when it can't.
42-
matches :: forall e a. Match a -> (Maybe a -> a -> Eff e Unit) -> Eff e Unit
43-
matches = matches' decodeURIComponent
44-
45-
matches' :: forall e a. (String -> String) ->
46-
Match a -> (Maybe a -> a -> Eff e Unit) -> Eff e Unit
47-
matches' decoder routing cb = hashes $ \old new ->
48-
let mr = matchWith decoder routing
49-
fst = either (const Nothing) Just $ mr old
50-
in either (const $ pure unit) (cb fst) $ mr new
51-
52-
matchesAff' :: forall e a. (String -> String) ->
53-
Match a -> Aff e (Tuple (Maybe a) a)
54-
matchesAff' decoder routing =
55-
makeAff \k -> nonCanceler <$
56-
matches' decoder routing \old new ->
57-
k $ Right $ Tuple old new
58-
59-
matchesAff :: forall e a. Match a -> Aff e (Tuple (Maybe a) a)
60-
matchesAff = matchesAff' decodeURIComponent
61-
62-
22+
-- | Runs a `Match` parser.
6323
match :: forall a. Match a -> String -> Either String a
6424
match = matchWith decodeURIComponent
6525

26+
-- | Runs a `Match` parser given a custom String decoder.
6627
matchWith :: forall a. (String -> String) -> Match a -> String -> Either String a
67-
matchWith decoder matcher hash = runMatch matcher $ parse decoder hash
28+
matchWith decoder matcher = runMatch matcher <<< parse decoder

src/Routing/Hash.js

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/Routing/Hash.purs

Lines changed: 86 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,91 @@
1-
module Routing.Hash where
1+
module Routing.Hash
2+
( getHash
3+
, setHash
4+
, modifyHash
5+
, foldHashes
6+
, hashes
7+
, matches
8+
, matchesWith
9+
, module Routing
10+
) where
211

3-
import Prelude (Unit, (>>=), (<$>))
4-
import Control.Monad.Eff (Eff())
5-
import DOM (DOM())
12+
import Prelude
613

7-
foreign import setHash :: forall e. String -> Eff (dom :: DOM |e) Unit
14+
import Control.Monad.Eff (Eff)
15+
import Control.Monad.Eff.Ref (newRef, readRef, writeRef)
16+
import DOM (DOM)
17+
import DOM.Event.EventTarget (addEventListener, eventListener, removeEventListener)
18+
import DOM.HTML (window)
19+
import DOM.HTML.Event.EventTypes (hashchange)
20+
import DOM.HTML.Location as L
21+
import DOM.HTML.Types (windowToEventTarget)
22+
import DOM.HTML.Window (location)
23+
import Data.Foldable (class Foldable, indexl)
24+
import Data.Maybe (Maybe(..), fromMaybe, maybe)
25+
import Data.String (Pattern(..), stripPrefix)
26+
import Routing (RoutingEffects, match, matchWith)
27+
import Routing.Match (Match)
828

9-
foreign import getHash :: forall e. Eff (dom :: DOM |e) String
29+
-- | Gets the global location hash.
30+
getHash :: forall eff. Eff (dom :: DOM | eff) String
31+
getHash = window >>= location >>= L.hash >>> map (stripPrefix (Pattern "#") >>> fromMaybe "")
1032

11-
modifyHash :: forall e. (String -> String) -> Eff (dom :: DOM|e) Unit
33+
-- | Sets the global location hash.
34+
setHash :: forall eff. String -> Eff (dom :: DOM | eff) Unit
35+
setHash h = window >>= location >>= L.setHash h
36+
37+
-- | Modifies the global location hash.
38+
modifyHash :: forall eff. (String -> String) -> Eff (dom :: DOM | eff) Unit
1239
modifyHash fn = (fn <$> getHash) >>= setHash
40+
41+
-- | Folds effectfully over hash changes given a callback and an initial hash.
42+
-- | The provided String is the hash portion of the `Location` with the '#'
43+
-- | prefix stripped. Returns an effect which will remove the listener.
44+
foldHashes
45+
:: forall eff a
46+
. (a -> String -> Eff (RoutingEffects eff) a)
47+
-> (String -> Eff (RoutingEffects eff) a)
48+
-> Eff (RoutingEffects eff) (Eff (RoutingEffects eff) Unit)
49+
foldHashes cb init = do
50+
ref <- newRef =<< init =<< getHash
51+
win <- windowToEventTarget <$> window
52+
let listener = eventListener \_ -> writeRef ref =<< join (cb <$> readRef ref <*> getHash)
53+
addEventListener hashchange listener false win
54+
pure $ removeEventListener hashchange listener false win
55+
56+
-- | Runs the callback on every hash change providing the previous hash and the
57+
-- | latest hash. The provided String is the hash portion of the `Location` with
58+
-- | the '#' prefix stripped. Returns an effect which will remove the listener.
59+
hashes
60+
:: forall eff
61+
. (Maybe String -> String -> Eff (RoutingEffects eff) Unit)
62+
-> Eff (RoutingEffects eff) (Eff (RoutingEffects eff) Unit)
63+
hashes = matchesWith Just
64+
65+
-- | Runs the callback on every hash change using a given `Match` parser to
66+
-- | extract a route from the hash. If a hash fails to parse, it is ignored.
67+
-- | To avoid dropping hashes, provide a fallback alternative in your parser.
68+
-- | Returns an effect which will remove the listener.
69+
matches
70+
:: forall eff a
71+
. Match a
72+
-> (Maybe a -> a -> Eff (RoutingEffects eff) Unit)
73+
-> Eff (RoutingEffects eff) (Eff (RoutingEffects eff) Unit)
74+
matches = matchesWith <<< match
75+
76+
-- | Runs the callback on every hash change using a given custom parser to
77+
-- | extract a route from the hash. If a hash fails to parse, it is ignored.
78+
-- | To avoid dropping hashes, provide a fallback alternative in your parser.
79+
-- | Returns an effect which will remove the listener.
80+
matchesWith
81+
:: forall eff f a
82+
. Foldable f
83+
=> (String -> f a)
84+
-> (Maybe a -> a -> Eff (RoutingEffects eff) Unit)
85+
-> Eff (RoutingEffects eff) (Eff (RoutingEffects eff) Unit)
86+
matchesWith parser cb = foldHashes go (go Nothing)
87+
where
88+
go a =
89+
maybe (pure a) (\b -> Just b <$ cb a b)
90+
<<< indexl 0
91+
<<< parser

src/Routing/Hash/Aff.purs

Lines changed: 0 additions & 13 deletions
This file was deleted.

test/Test/Browser.purs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
module Test.Browser where
2+
3+
import Prelude
4+
5+
import Control.Monad.Eff (Eff)
6+
import Control.Monad.Eff.Exception (EXCEPTION, error, throwException)
7+
import DOM.Event.EventTarget (addEventListener, eventListener)
8+
import DOM.HTML (window)
9+
import DOM.HTML.Document (body)
10+
import DOM.HTML.Event.EventTypes (load)
11+
import DOM.HTML.Types (htmlDocumentToDocument, htmlElementToNode, windowToEventTarget)
12+
import DOM.HTML.Window (document)
13+
import DOM.Node.Document (createElement, createTextNode)
14+
import DOM.Node.Element (setAttribute)
15+
import DOM.Node.Node (appendChild)
16+
import DOM.Node.Types (Document, Node, elementToNode, textToNode)
17+
import Data.Foldable (oneOf)
18+
import Data.Maybe (Maybe(..), maybe)
19+
import Routing.Hash (RoutingEffects, hashes, setHash)
20+
import Routing.Match (Match)
21+
import Routing.Match.Class (lit)
22+
23+
type Effects = RoutingEffects (exception :: EXCEPTION)
24+
25+
data Route = A | B | U
26+
27+
route :: Match Route
28+
route = oneOf
29+
[ lit "a" $> A
30+
, lit "b" $> B
31+
, pure U
32+
]
33+
34+
runTests :: Eff Effects Unit
35+
runTests = do
36+
doc <- window >>= document
37+
body <- body doc >>= maybe (throwException (error "Body not found")) pure
38+
39+
let
40+
doc' :: Document
41+
doc' = htmlDocumentToDocument doc
42+
43+
renderSuccess :: String -> Eff Effects Node
44+
renderSuccess testName = do
45+
row <- createElement "div" doc'
46+
setAttribute "class" "success" row
47+
tag <- createElement "b" doc'
48+
ok <- createTextNode "[OK]" doc'
49+
name <- createTextNode testName doc'
50+
_ <- appendChild (elementToNode tag) (elementToNode row)
51+
_ <- appendChild (textToNode name) (elementToNode row)
52+
_ <- appendChild (textToNode ok) (elementToNode tag)
53+
pure (elementToNode row)
54+
55+
renderError :: String -> String -> Eff Effects Node
56+
renderError testName err = do
57+
row <- createElement "div" doc'
58+
setAttribute "class" "error" row
59+
tag <- createElement "b" doc'
60+
ok <- createTextNode "[FAIL]" doc'
61+
name <- createTextNode testName doc'
62+
error <- createElement "div" doc'
63+
setAttribute "class" "error-text" error
64+
errText <- createTextNode err doc'
65+
_ <- appendChild (textToNode ok) (elementToNode tag)
66+
_ <- appendChild (elementToNode tag) (elementToNode row)
67+
_ <- appendChild (textToNode name) (elementToNode row)
68+
_ <- appendChild (textToNode errText) (elementToNode error)
69+
_ <- appendChild (elementToNode error) (elementToNode row)
70+
pure (elementToNode row)
71+
72+
assertEq :: forall a. Show a => Eq a => String -> a -> a -> Eff Effects Unit
73+
assertEq testName a b = do
74+
if a == b
75+
then do
76+
void $ flip appendChild (htmlElementToNode body) =<< renderSuccess testName
77+
else do
78+
let err = show a <> " /= " <> show b
79+
_ <- flip appendChild (htmlElementToNode body) =<< renderError testName err
80+
throwException (error $ testName <> ": " <> err)
81+
82+
assert :: String -> Boolean -> Eff Effects Unit
83+
assert testName = assertEq testName true
84+
85+
_ <- hashes case _, _ of
86+
Nothing, "" -> assert "Hashes: Initial value" true
87+
Just "", "a" -> assert "Hashes: ? -> a" true *> setHash "b"
88+
Just "a", "b" -> assert "Hashes: a -> b" true *> setHash ""
89+
Just "b", "" -> assert "Hashes: b -> ?" true
90+
_, _ -> assert "Hashes: fail" false
91+
92+
setHash "a"
93+
94+
main :: Eff Effects Unit
95+
main =
96+
window
97+
>>= windowToEventTarget
98+
>>> addEventListener load (eventListener (const runTests)) false

test/index.html

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!doctype html>
2+
<title>Routing tests</title>
3+
<style>
4+
body {
5+
font-family: sans-serif;
6+
}
7+
body > div {
8+
margin-bottom: 10px;
9+
}
10+
b {
11+
margin-right: 10px;
12+
}
13+
.success b {
14+
color: green;
15+
}
16+
.error b {
17+
color: red;
18+
}
19+
</style>
20+
<script src="dist/bundle.js"></script>

0 commit comments

Comments
 (0)