├── .gitignore ├── LICENSE ├── README.md ├── part1 └── syntax-workshop │ ├── Main.elm │ ├── README.md │ ├── Solutions.elm │ ├── elm-package.json │ └── index.html ├── part2 ├── README.md ├── html-workshop │ ├── Main.elm │ ├── README.md │ └── elm-package.json ├── map-workshop │ ├── MapExercises.elm │ ├── README.md │ ├── Solutions.elm │ ├── elm-package.json │ └── tests │ │ ├── .gitignore │ │ ├── Main.elm │ │ └── elm-package.json └── mini-todos-workshop │ ├── README.md │ ├── Solution.elm │ ├── Todo.elm │ └── elm-package.json └── part3 ├── http-workshop ├── Main.elm ├── README.md ├── elm-package.json └── index.html └── json-workshop ├── DeeplyNested.elm ├── Guardian.elm ├── README.md ├── SampleJson.elm ├── Solutions ├── DeeplyNested.elm ├── Guardian.elm └── Todo.elm ├── Todo.elm └── elm-package.json /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff 2 | elm.js 3 | part3/http-workshop/Solutions.elm 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Centre for the Acceleration of Social Technology (CAST) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elm Week 2 | 3 | Elm week at Founders & Coders 4 | 5 | 6 | ## Core Resources 7 | 8 | + Elm Lang gitbook - https://guide.elm-lang.org/ 9 | + Frontend Masters videos - https://frontendmasters.com/courses/elm/ 10 | + Frontend Masters workshop - https://github.com/rtfeldman/elm-workshop 11 | + Elm Package - http://package.elm-lang.org/ 12 | + Elm Core docs - http://package.elm-lang.org/packages/elm-lang/core/latest 13 | + Html docs - http://package.elm-lang.org/packages/elm-lang/html/latest 14 | 15 | 16 | ## Tools 17 | 18 | + Online Elm Repl - http://elmrepl.cuberoot.in/ 19 | + Online Elm Program Viewer - https://ellie-app.com/new 20 | 21 | 22 | ## Useful Resources 23 | 24 | + Cheat sheet - https://www.elm-tutorial.org/en/ 25 | + Great tutorial but gets pretty advanced later on - https://www.elm-tutorial.org/en/ 26 | -------------------------------------------------------------------------------- /part1/syntax-workshop/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Html exposing (..) 4 | import Html.Attributes exposing (..) 5 | 6 | 7 | -- write a function that takes two strings and concats them together 8 | 9 | 10 | stringConcat s1 s2 = 11 | s1 ++ s2 12 | 13 | 14 | 15 | -- 16 | -- write a function that takes a string and reverses it (take a look at Elm's list module) 17 | -- 18 | -- stringReverse = ? 19 | -- 20 | -- add adds two numbers together 21 | -- 22 | -- add = ? 23 | -- 24 | -- write a function that squares the Int given to it 25 | -- 26 | -- square = ? 27 | -- 28 | -- multiply takes two numbers and multiplies them together 29 | -- 30 | -- multiply = ? 31 | -- 32 | -- write a function that adds 1 to the argument given to it, reusing the add function you wrote 33 | -- 34 | -- plusOne = ? 35 | -- 36 | -- double multiplies its argument by 2, write it using multiply from earlier 37 | -- 38 | -- 39 | -- double = ? 40 | -- 41 | -- isTeenage takes an age (a number) and returns True if it is a teenager, false if not 42 | -- 43 | -- isTeenage = ? 44 | -- 45 | -- write a function that concats two lists together 46 | -- 47 | -- listConcat = ? 48 | -- 49 | -- write a function that returns the length of a list 50 | -- 51 | -- listLength = ? 52 | -- 53 | -- write a function which takes a Int and returns a string which says whether the Int was 54 | -- 'negative', 'positive' or 'zero' 55 | -- 56 | -- negativeOrPositive = ? 57 | -- 58 | -- write a function that returns the absolute value of a Int using `if-then-else` 59 | -- 60 | -- myAbs = ? 61 | -- 62 | -- use a case statement to write a function that takes numbers 0-5 and turns them into strings. 63 | -- make sure you have a fallback case too 64 | -- 65 | -- digitToString = ? 66 | -- 67 | -- isMultipleof3 does what it says 68 | -- 69 | -- isMultipleof3 = ? 70 | -- 71 | -- write the myAbs again, this time using a case statement 72 | -- 73 | -- myOtherAbs = ? 74 | -- 75 | -- come up with a function that takes a Int and returns 'fizz' if it is a multiple of 3, 'buzz' 76 | -- if it is a multiple of 5, or 'fizzbuzz if it is a multiple of both' 77 | -- 78 | -- fizzBuzz = ? 79 | -- 80 | -- rewrite capitalizeWord using a let in expression, setting intermediate variables firstLetter and otherLetters 81 | -- 82 | 83 | 84 | capitalizeWord word = 85 | String.toUpper (String.left 1 word) ++ String.dropLeft 1 word 86 | 87 | 88 | 89 | -- myCapitalizeWord = ? 90 | -- 91 | -- fst takes a tuple and returns the first element 92 | -- 93 | -- fst = ? 94 | -- 95 | -- snd takes a tuple and return the second element 96 | -- 97 | -- snd = ? 98 | -- 99 | -- area takes a tuple with height as first element and width as second and returns the area 100 | -- 101 | -- area = ? 102 | -- 103 | -- rewrite area using `let-in` expression assigning intermediate variables to width and height 104 | -- 105 | -- area1 = ? 106 | -- 107 | -- rewrite area using pattern matching 108 | -- 109 | -- area2 = ? 110 | -- 111 | -- write a function that takes an number and a list and splits the list at the number given and 112 | -- and returns the two lists in a tuple 113 | -- 114 | -- splitList = ? 115 | -- 116 | -- use pattern matching and a case statement to write a function that takes a list and returns a 117 | -- string informing you how many elements a list has 118 | -- the return values should be "empty list", "one element", "two elements", "many elements" 119 | -- 120 | -- listDescritption = ? 121 | -- 122 | 123 | 124 | steppenwolf = 125 | { name = "Steppenwolf" 126 | , author = "Hermann Hesse" 127 | , pages = 237 128 | } 129 | 130 | 131 | siddharta = 132 | { name = "Siddharta" 133 | , author = "Herman Hesse" 134 | , pages = 150 135 | } 136 | 137 | 138 | island = 139 | { name = "Island" 140 | , author = "Aldous Huxley" 141 | , pages = 258 142 | } 143 | 144 | 145 | doorsOfPerception = 146 | { name = "Doors of Perception" 147 | , author = "Aldous Huxley" 148 | , pages = 101 149 | } 150 | 151 | 152 | aliceInWonderland = 153 | { name = "Alice in Wonderland" 154 | , author = "Lewis Carrol" 155 | , pages = 287 156 | } 157 | 158 | 159 | 160 | -- write a function that takes one of the above records and returns the name field 161 | -- name = ? 162 | -- 163 | -- write a function that takes a record and returns the number of pages (this time use pattern matching) 164 | -- 165 | -- pages = ? 166 | -- 167 | -- over150 takes a record and returns True if it has over 250 pages, false if not 168 | -- over150 = ? 169 | -- 170 | -- updatePages takes a number and a record and updates a records page field 171 | -- 172 | -- updatePages = ? 173 | -- updateNameAndAuthor takes a two strings and a record and updates the name and author fields 174 | -- 175 | -- updateNameAndAuthor = ? 176 | -- 177 | -- write a function that takes a string, reverses, uppercases and then concats "CHOCOLATE " to the 178 | -- beginning of it (use your stringReverse and stringConcat functions) 179 | -- 180 | -- pipePractice = ? 181 | -- 182 | -- write a function that takes a number adds 1 to it, doubles it, then squares it and then checks 183 | -- if it is a teenage age. Use plusOne, double, square and isTeenage 184 | -- 185 | --pipePractice1 = ? 186 | -- 187 | -- TESTS 188 | 189 | 190 | testSpec : List ( Bool, String ) 191 | testSpec = 192 | [ ( stringConcat "hello" "world" == "helloworld", "stringConcat" ) 193 | -- , ( stringReverse "stressed" == "desserts", "stringReverse" ) 194 | -- , ( add 4 5 == 9, "add" ) 195 | -- , ( square 3 == 9, "square" ) 196 | -- , ( multiply 4 5 == 20, "mulitply" ) 197 | -- , ( plusOne 4 == 5, "plusOne" ) 198 | -- , ( double 20 == 40, "double" ) 199 | -- , ( isTeenage 13 == True, "isTeenage" ) 200 | -- , ( isTeenage 20 == False, "isTeenage" ) 201 | -- , ( listConcat [ 1, 2, 3 ] [ 4, 5, 6 ] == [ 1, 2, 3, 4, 5, 6 ], "listConcat" ) 202 | -- , ( listLength [ 'a', 'b', 'c' ] == 3, "listLength" ) 203 | -- , ( negativeOrPositive -1 == "n is negative", "negativeOrPositive" ) 204 | -- , ( negativeOrPositive 1 == "n is positive", "negativeOrPositive" ) 205 | -- , ( negativeOrPositive 0 == "n is zero", "negativeOrPositive" ) 206 | -- , ( myAbs -2 == 2, "abs" ) 207 | -- , ( myAbs 2 == 2, "abs1" ) 208 | -- , ( digitToString 3 == "three", "digitToString" ) 209 | -- , ( digitToString 5 == "five", "digitToString" ) 210 | -- , ( isMultipleof3 5 == False, "isMultipleof3" ) 211 | -- , ( isMultipleof3 6 == True, "isMultipleof3" ) 212 | -- , ( myOtherAbs -2 == 2, "abs" ) 213 | -- , ( myOtherAbs 2 == 2, "abs" ) 214 | -- , ( fizzBuzz 15 == "fizzbuzz", "fizzBuzz" ) 215 | -- , ( fizzBuzz 12 == "fizz", "fizzBuzz" ) 216 | -- , ( fizzBuzz 10 == "buzz", "fizzBuzz" ) 217 | -- , ( myCapitalizeWord "elm" == "Elm", "myCapitalizeWord" ) 218 | -- , ( fst ( 4, 5 ) == 4, "fst" ) 219 | -- , ( snd ( "react", "elm" ) == "elm", "elm" ) 220 | -- , ( area ( 4, 5 ) == 20, "area" ) 221 | -- , ( area1 ( 4, 5 ) == 20, "area1" ) 222 | -- , ( area2 ( 4, 5 ) == 20, "area2" ) 223 | -- , ( splitList 2 [ 1, 2, 3, 4 ] == ( [ 1, 2 ], [ 3, 4 ] ), "splitList" ) 224 | -- , ( listDescritption [] == "empty list", "listDescritption" ) 225 | -- , ( listDescritption [ 1 ] == "one element", "listDescritption" ) 226 | -- , ( listDescritption [ 1, 2 ] == "two elements", "listDescritption" ) 227 | -- , ( listDescritption [ 1, 2, 3, 4, 5 ] == "many elements", "listDescritption" ) 228 | -- , ( name island == "Island", "name" ) 229 | -- , ( pages doorsOfPerception == 101, "pages" ) 230 | -- , ( over150 siddharta == False, "over150" ) 231 | -- , ( updatePages 151 siddharta == { siddharta | pages = 151 }, "updatePages" ) 232 | -- , ( updateNameAndAuthor "Der Steppenwolf" "Hermann Karl Hesse" steppenwolf 233 | -- == { steppenwolf 234 | -- | name = "Der Steppenwolf" 235 | -- , author = "Hermann Karl Hesse" 236 | -- } 237 | -- , "updateNameAndAuthor" 238 | -- ) 239 | -- , ( pipePractice "stressed" == "CHOCOLATE DESSERTS", "pipePractice" ) 240 | -- , ( pipePractice1 1 == True, "pipePractice1" ) 241 | -- , ( pipePractice1 2 == False, "pipePractice1" ) 242 | ] 243 | 244 | 245 | testOutputToString : a -> Html msg 246 | testOutputToString x = 247 | x 248 | |> toString 249 | |> text 250 | |> List.singleton 251 | |> li [] 252 | 253 | 254 | testsPassed : List ( Bool, String ) -> Bool 255 | testsPassed l = 256 | l 257 | |> List.map (\( b, _ ) -> b) 258 | |> List.all (\b -> b == True) 259 | 260 | 261 | testSummary : Bool -> String 262 | testSummary b = 263 | if b then 264 | "All tests passed :)" 265 | else 266 | "Uh oh, some tests failed" 267 | 268 | 269 | main : Html msg 270 | main = 271 | div [] 272 | [ div [] [ ol [] (List.map testOutputToString testSpec) ] 273 | , h2 [ class "ml2 blue" ] [ text <| testSummary <| testsPassed testSpec ] 274 | ] 275 | -------------------------------------------------------------------------------- /part1/syntax-workshop/README.md: -------------------------------------------------------------------------------- 1 | # Part 1 2 | 3 | ## Goals 4 | 5 | * Get used to Elm syntax 6 | * Introduction to new Elm datatypes 7 | * Introduction to type signatures 8 | 9 | ## Elm syntax && datatypes 10 | 11 | 1. Read through https://guide.elm-lang.org/core_language.html 12 | 13 | 2. Complete exercises in `Main.elm`, keeping this page open for reference - http://elm-lang.org/docs/syntax 14 | 15 | N.B. There are a lot of exercises and it might start to get a little tedious without any context. However these are the building blocks of the language and once you've worked through the exercises you'll have a solid foundation that will allow us to move on to the juicy bits 🍹 16 | 17 | ## Type signatures 18 | 19 | 1. Read through https://guide.elm-lang.org/types/ up to the `Type Aliases` 20 | 21 | 2. Add type signatures to your solutions in `Main.elm` 22 | 23 | ## Tips 24 | 25 | * You will probably have a hard time getting your code to compile at first. This can be frustrating, however if you keep at it the compiler will soon become your best friend 26 | 27 | * If you don't understand what a function is meant to do, have a look at the test case for it near the bottom of the file 28 | 29 | 30 | ## Installation 31 | 32 | If you haven't already 33 | 34 | ```bash 35 | npm i -g elm elm-live 36 | ``` 37 | 38 | ```bash 39 | elm-package install 40 | ``` 41 | 42 | (Answer `y` when prompted.) 43 | 44 | 45 | ## Building 46 | 47 | ```bash 48 | elm-live Main.elm --open --pushstate --output=elm.js 49 | ``` 50 | -------------------------------------------------------------------------------- /part1/syntax-workshop/Solutions.elm: -------------------------------------------------------------------------------- 1 | module Solutions exposing (..) 2 | 3 | -- STRINGS 4 | -- 5 | -- write a function that takes two strings and concats them together 6 | 7 | 8 | stringConcat : String -> String -> String 9 | stringConcat s1 s2 = 10 | s1 ++ s2 11 | 12 | 13 | 14 | -- write a function that takes a string and reverses it (a lot easier than it looks) 15 | 16 | 17 | stringReverse : String -> String 18 | stringReverse s = 19 | String.reverse s 20 | 21 | 22 | 23 | -- NUMBERS 24 | -- 25 | -- add adds two numbers together 26 | 27 | 28 | add : Int -> Int -> Int 29 | add x y = 30 | x + y 31 | 32 | 33 | 34 | -- write a function that squares the Int given to it 35 | 36 | 37 | square : Int -> Int 38 | square x = 39 | x * x 40 | 41 | 42 | 43 | -- multiply takes two numbers and multiplies them together 44 | 45 | 46 | multiply : Int -> Int -> Int 47 | multiply x y = 48 | x * y 49 | 50 | 51 | 52 | -- write a function that adds 1 to the argument given to it, reusing the add function you wrote 53 | 54 | 55 | plusOne : Int -> Int 56 | plusOne = 57 | add 1 58 | 59 | 60 | 61 | -- double multiplies its argument by 2, write it using multiply from earlier 62 | 63 | 64 | double : Int -> Int 65 | double = 66 | multiply 2 67 | 68 | 69 | 70 | -- isTeenage takes an age (Int) and returns True if it is a teenager, false if not 71 | 72 | 73 | isTeenage : Int -> Bool 74 | isTeenage age = 75 | (age > 12) && (age < 20) 76 | 77 | 78 | 79 | -- LISTS 80 | -- 81 | -- write a function that concats two lists together 82 | 83 | 84 | listConcat : List a -> List a -> List a 85 | listConcat l1 l2 = 86 | l1 ++ l2 87 | 88 | 89 | 90 | -- write a function that returns the length of a list 91 | 92 | 93 | listLength : List a -> Int 94 | listLength l = 95 | List.length l 96 | 97 | 98 | 99 | -- IF-ELSE-THEN 100 | -- 101 | -- write a function which takes a Int and returns a string which says whether the Int was 102 | -- 'negative', 'positive' or 'zero' 103 | 104 | 105 | negativeOrPositive : Int -> String 106 | negativeOrPositive n = 107 | if n < 0 then 108 | "n is negative" 109 | else if n > 0 then 110 | "n is positive" 111 | else 112 | "n is zero" 113 | 114 | 115 | 116 | -- write a function that returns the absolute value of a Int using `if-then-else` 117 | 118 | 119 | myAbs : Int -> Int 120 | myAbs n = 121 | if n < 0 then 122 | -n 123 | else 124 | n 125 | 126 | 127 | 128 | -- CASE STATEMENT 129 | -- 130 | -- use a case statement to write a function that takes numbers 0-5 and turns them into strings. 131 | -- make sure you have a fallback case too 132 | 133 | 134 | digitToString : Int -> String 135 | digitToString n = 136 | case n of 137 | 0 -> 138 | "zero" 139 | 140 | 1 -> 141 | "one" 142 | 143 | 2 -> 144 | "two" 145 | 146 | 3 -> 147 | "three" 148 | 149 | 4 -> 150 | "four" 151 | 152 | 5 -> 153 | "five" 154 | 155 | _ -> 156 | "many" 157 | 158 | 159 | 160 | -- isMultipleof3 does what it says 161 | 162 | 163 | isMultipleof3 : Int -> Bool 164 | isMultipleof3 n = 165 | case n % 3 of 166 | 0 -> 167 | True 168 | 169 | _ -> 170 | False 171 | 172 | 173 | 174 | -- write the myAbs again, this time using a case statement 175 | 176 | 177 | myOtherAbs : Int -> Int 178 | myOtherAbs n = 179 | case n < 0 of 180 | True -> 181 | -n 182 | 183 | False -> 184 | n 185 | 186 | 187 | 188 | -- come up with a function that takes a Int and returns 'fizz' if it is a multiple of 3, 'buzz' 189 | -- if it is a multiple of 5, or 'fizzbuzz if it is a multiple of both' 190 | 191 | 192 | fizzBuzz : Int -> String 193 | fizzBuzz n = 194 | case ( n % 3 == 0, n % 5 == 0 ) of 195 | ( True, False ) -> 196 | "fizz" 197 | 198 | ( False, True ) -> 199 | "buzz" 200 | 201 | ( True, True ) -> 202 | "fizzbuzz" 203 | 204 | _ -> 205 | toString n 206 | 207 | 208 | 209 | -- LET expression 210 | -- 211 | -- rewrite capitalizeWord using a let in expression, setting variables firstLetter and otherLetters 212 | -- capitalizeWord "elm" = "Elm" 213 | 214 | 215 | capitalizeWord : String -> String 216 | capitalizeWord word = 217 | String.toUpper (String.left 1 word) ++ String.dropLeft 1 word 218 | 219 | 220 | myCapitalizeWord : String -> String 221 | myCapitalizeWord word = 222 | let 223 | firstLetter = 224 | String.left 1 word 225 | 226 | otherLetters = 227 | String.dropLeft 1 word 228 | in 229 | String.toUpper firstLetter ++ otherLetters 230 | 231 | 232 | 233 | -- TUPLES -- 234 | -- 235 | -- fst takes a tuple and returns the first element 236 | 237 | 238 | fst : ( a, b ) -> a 239 | fst t = 240 | Tuple.first t 241 | 242 | 243 | 244 | -- snd takes a tuple and return the second element 245 | 246 | 247 | snd : ( a, b ) -> b 248 | snd t = 249 | Tuple.second t 250 | 251 | 252 | 253 | -- area takes a tuple with height as first element and width as second and returns the area 254 | 255 | 256 | area : ( Int, Int ) -> Int 257 | area t = 258 | Tuple.first t * Tuple.second t 259 | 260 | 261 | 262 | -- rewrite area using `let-in` expression assigning intermediate variables to width and height 263 | 264 | 265 | area1 : ( Int, Int ) -> Int 266 | area1 t = 267 | let 268 | width = 269 | Tuple.first t 270 | 271 | height = 272 | Tuple.second t 273 | in 274 | width * height 275 | 276 | 277 | 278 | -- rewrite area using pattern matching 279 | 280 | 281 | area2 : ( Int, Int ) -> Int 282 | area2 ( width, height ) = 283 | width * height 284 | 285 | 286 | 287 | -- write a function that takes an number and a list and splits the list at the number given and 288 | -- and returns the two lists in a tuple 289 | 290 | 291 | splitList : Int -> List a -> ( List a, List a ) 292 | splitList n list = 293 | ( List.take n list, List.drop n list ) 294 | 295 | 296 | 297 | -- use pattern matching and a case statement to write a function that takes a list and returns a 298 | -- string informing you how many elements a list has 299 | -- the return values should be "empty list", "one element", "two elements", "many elements" 300 | 301 | 302 | listDescritption : List a -> String 303 | listDescritption list = 304 | case list of 305 | [] -> 306 | "empty list" 307 | 308 | [ _ ] -> 309 | "one element" 310 | 311 | [ a, b ] -> 312 | "two elements" 313 | 314 | _ -> 315 | "many elements" 316 | 317 | 318 | 319 | -- RECORDS 320 | 321 | 322 | type alias Book = 323 | { name : String 324 | , author : String 325 | , pages : Int 326 | } 327 | 328 | 329 | steppenwolf : Book 330 | steppenwolf = 331 | { name = "Steppenwolf" 332 | , author = "Hermann Hesse" 333 | , pages = 237 334 | } 335 | 336 | 337 | siddharta : Book 338 | siddharta = 339 | { name = "Siddharta" 340 | , author = "Herman Hesse" 341 | , pages = 150 342 | } 343 | 344 | 345 | island : Book 346 | island = 347 | { name = "Island" 348 | , author = "Aldous Huxley" 349 | , pages = 258 350 | } 351 | 352 | 353 | doorsOfPerception : Book 354 | doorsOfPerception = 355 | { name = "Doors of Perception" 356 | , author = "Aldous Huxley" 357 | , pages = 101 358 | } 359 | 360 | 361 | 362 | -- write a function that takes one of the above records and returns the name field 363 | 364 | 365 | name : Book -> String 366 | name record = 367 | .name record 368 | 369 | 370 | 371 | -- write a function that takes a record and returns the number of pages (this time use pattern matching) 372 | 373 | 374 | pages : Book -> Int 375 | pages { pages } = 376 | pages 377 | 378 | 379 | 380 | -- over150 takes a record and returns True if it has over 250 pages, false if not 381 | 382 | 383 | over150 : Book -> Bool 384 | over150 { pages } = 385 | pages > 150 386 | 387 | 388 | 389 | -- updatePages takes a number and a record and updates a records page field 390 | 391 | 392 | updatePages : Int -> Book -> Book 393 | updatePages n rec = 394 | { rec | pages = n } 395 | 396 | 397 | 398 | -- updateNameAndAuthor takes a two strings and a record and updates the name and author fields 399 | 400 | 401 | updateNameAndAuthor : String -> String -> Book -> Book 402 | updateNameAndAuthor name author rec = 403 | { rec | name = name, author = author } 404 | 405 | 406 | 407 | -- PIPE OPERATOR 408 | -- 409 | -- write a function that takes a string, reverses, uppercases and then concats "CHOCOLATE " to the 410 | -- beginning of it (use your stringReverse and stringConcat functions) 411 | 412 | 413 | pipePractice : String -> String 414 | pipePractice string = 415 | string 416 | |> stringReverse 417 | |> String.toUpper 418 | |> stringConcat "CHOCOLATE " 419 | 420 | 421 | 422 | -- write a function that takes a number adds 1 to it, doubles it, then squares it and then checks 423 | -- if it is a teenage age. Use plusOne, double, square and isTeenage 424 | 425 | 426 | pipePractice1 : Int -> Bool 427 | pipePractice1 n = 428 | n 429 | |> plusOne 430 | |> double 431 | |> square 432 | |> isTeenage 433 | -------------------------------------------------------------------------------- /part1/syntax-workshop/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "Exercises for learning Elm syntax", 4 | "repository": "https://github.com/TechforgoodCAST/elm-week.git", 5 | "license": "BSD-3-Clause", 6 | "source-directories": [ 7 | "." 8 | ], 9 | "exposed-modules": [], 10 | "dependencies": { 11 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 12 | "elm-lang/html": "2.0.0 <= v < 3.0.0" 13 | }, 14 | "elm-version": "0.18.0 <= v < 0.19.0" 15 | } 16 | -------------------------------------------------------------------------------- /part1/syntax-workshop/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Syntax workshop 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /part2/README.md: -------------------------------------------------------------------------------- 1 | # Part 2 2 | 3 | + Html Primer 4 | + The Elm Architecture 5 | + Exploring types 6 | 7 | 8 | ## Short exercise writing Html in Elm 9 | 10 | + Work through exercise in [html workshop](https://github.com/TechforgoodCAST/elm-week/tree/master/part3/html-workshop) 11 | 12 | 13 | ## The Elm Architecture 14 | 15 | How an Elm app fits together 16 | 17 | + Frontend masters section on The Elm Architecture (TEA) - https://frontendmasters.com/courses/elm/the-elm-architecture/ 18 | + Read through the page on the elm-lang guide - https://guide.elm-lang.org/architecture/ 19 | + Work through first 3 User input exercises (up to `Form`) - https://guide.elm-lang.org/architecture/user_input/ 20 | 21 | 22 | ## Exploring Types 23 | 24 | + Read types section on elm-lang guide - https://guide.elm-lang.org/types/ 25 | + Error handling (Maybe and Result) - https://guide.elm-lang.org/error_handling/ 26 | + Video on Union Types - https://frontendmasters.com/courses/elm/union-types 27 | + [Map workshop](https://github.com/TechforgoodCAST/elm-week/tree/master/part3/map-workshop) 28 | 29 | 30 | ## A short exercise to tie it all together 31 | 32 | [mini-todos-workshop](https://github.com/TechforgoodCAST/elm-week/tree/master/part3/mini-todos-workshop) - Implement a todo app based on the first example in the union types section 33 | -------------------------------------------------------------------------------- /part2/html-workshop/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Html exposing (..) 4 | import Html.Attributes exposing (..) 5 | 6 | 7 | main : Html msg 8 | main = 9 | div [] 10 | [ stylesheetLink 11 | -- add your components here 12 | ] 13 | 14 | 15 | stylesheetLink : Html msg 16 | stylesheetLink = 17 | -- this appends the tachyons stylesheet to the DOM 18 | node "link" 19 | [ rel "stylesheet" 20 | , href "https://unpkg.com/tachyons@4.8.0/css/tachyons.min.css" 21 | ] 22 | [] 23 | -------------------------------------------------------------------------------- /part2/html-workshop/README.md: -------------------------------------------------------------------------------- 1 | # Html Exercise 2 | 3 | Try recreating some of your components from the tachyons workshop in elm 4 | 5 | https://github.com/TechforgoodCAST/prototyping-workshop/tree/master/tachyons-workshop 6 | 7 | ### Getting Started 8 | 9 | 1. cd into this directory 10 | 2. run `elm-reactor` in the terminal - this will let you preview an elm app straight away 11 | 3. visit `localhost:8000` 12 | 4. Click on `Main.elm` - this will run the program in `Main.elm` (all output comes from the `main` function in `Main.elm`) 13 | 5. add your components to the `main` function to see them on the page 14 | 15 | 16 | An example to get you started: 17 | 18 | This: 19 | ```html 20 | 21 | ``` 22 | 23 | Can be written in Elm as: 24 | ```elm 25 | myButton = 26 | button [ class "bg-blue white br2" ] [ text "Click Me" ] 27 | ``` 28 | 29 | Just remember: 30 | + `button` is a function that takes two `List`s 31 | + The first is a list of `Attribute`s (like `class` or `style`) 32 | + The second a list of child nodes (i.e. other html elements) 33 | 34 | 35 | ### Resources 36 | 37 | + Html and the Virtual DOM - https://frontendmasters.com/courses/elm/html-and-the-virtual-dom 38 | + Elm Html Docs - http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html 39 | + Html Attributes - http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Attributes 40 | 41 | 42 | ### Tachyons Stylesheet 43 | 44 | The tachyons stylesheet is included in `Main.elm`, at the moment it's not possible to include external scripts and stylsheets in `elm-reactor` so elm appends this to the DOM 45 | 46 | This: 47 | ```elm 48 | stylesheetLink : Html msg 49 | stylesheetLink = 50 | node "link" 51 | [ rel "stylesheet" 52 | , href "https://unpkg.com/tachyons@4.8.0/css/tachyons.min.css" 53 | ] 54 | [] 55 | ``` 56 | 57 | Becomes this: 58 | ```html 59 | 60 | ``` 61 | -------------------------------------------------------------------------------- /part2/html-workshop/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "helpful summary of your project, less than 80 characters", 4 | "repository": "https://github.com/user/project.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "." 8 | ], 9 | "exposed-modules": [], 10 | "dependencies": { 11 | "elm-lang/core": "5.1.1 <= v < 6.0.0", 12 | "elm-lang/html": "2.0.0 <= v < 3.0.0" 13 | }, 14 | "elm-version": "0.18.0 <= v < 0.19.0" 15 | } 16 | -------------------------------------------------------------------------------- /part2/map-workshop/MapExercises.elm: -------------------------------------------------------------------------------- 1 | module MapExercises exposing (..) 2 | 3 | -- NB: Each function has a default value so the program compiles 4 | -- Fill these in with your new implementation to make the tests pass 5 | {--LISTS --} 6 | -- double the items in the list 7 | 8 | 9 | double : List number -> List number 10 | double list = 11 | [] 12 | 13 | 14 | 15 | -- return a list with only the first elements of each tuple 16 | 17 | 18 | tuples : List ( number, String ) -> List number 19 | tuples list = 20 | [] 21 | 22 | 23 | 24 | -- return a list of booleans where each item is if the value in the list is less than 3 25 | 26 | 27 | bools : List number -> List Bool 28 | bools list = 29 | [] 30 | 31 | 32 | 33 | {--MAYBES --} 34 | -- get the head of the list (remember the list could be empty so should return nothing in this case) 35 | 36 | 37 | headOfTheList : List a -> Maybe a 38 | headOfTheList list = 39 | Nothing 40 | 41 | 42 | 43 | -- concat a string to the value inside a maybe 44 | 45 | 46 | concatMeMaybe : String -> Maybe String -> Maybe String 47 | concatMeMaybe str maybeString = 48 | Just "" 49 | 50 | 51 | 52 | -- get the number out of the maybe but if it's not there return zero instead 53 | 54 | 55 | numberOrZero : Maybe number -> number 56 | numberOrZero maybeNumber = 57 | 12 58 | 59 | 60 | 61 | -- add the values of the two maybes together but return nothing if either of them are not there (hint have a look at Maybe.map2) 62 | 63 | 64 | addMaybes : Maybe number -> Maybe number -> Maybe number 65 | addMaybes maybe1 maybe2 = 66 | Nothing 67 | 68 | 69 | 70 | {--RESULTS --} 71 | {--attempt to parse a string into an Int 72 | remember you could pass the function something like "errhmagerrd" which is not an age 73 | in that case it would return `Err "could not convert string 'errhmagerrd' to an Int"` 74 | but if you gave the function 25 it would return `Ok 25` 75 | Hint, have a look in the String module of the core elm library (it's a one liner) 76 | --} 77 | 78 | 79 | getMyAge : String -> Result String Int 80 | getMyAge ageString = 81 | Ok 1 82 | 83 | 84 | 85 | -- parse an age from a string and add 3 to it 86 | 87 | 88 | makeMe3YearsOlder : String -> Result String Int 89 | makeMe3YearsOlder ageString = 90 | Err "" 91 | 92 | 93 | 94 | -- parse two ages from strings and add the results together (hint have a look at Result.map2) 95 | 96 | 97 | combinedAge : String -> String -> Result String Int 98 | combinedAge ageString1 ageString2 = 99 | Ok 3 100 | -------------------------------------------------------------------------------- /part2/map-workshop/README.md: -------------------------------------------------------------------------------- 1 | # Map Workshop 2 | 3 | getting to grips to using the wonderful `map` function in Elm 4 | 5 | ## Why? 6 | 7 | We're used to using map in JS on arrays 8 | 9 | ```js 10 | const myArray = [ 1, 2, 3, 4, 5 ].map(x => x * 2) 11 | // myArray: [ 1, 4, 6, 8, 10 ] 12 | ``` 13 | 14 | Map is super useful! But it can do so much more in Elm!! Map is not just for lists 15 | 16 | Work through the exercises in `MapExercises.elm` to make the tests pass in `tests/Main.elm` 17 | 18 | If you get really stuck the solutions are in `Solutions.elm` but try not to look at these! 19 | 20 | ## Get up and running 21 | 22 | 1. Have `elm-test` installed globally (`npm install -g elm-test`) 23 | 2. Run `elm package install` in this directory 24 | 3. Run `elm package install` in the `tests` directory 25 | 4. To run the tests cd into this directory and run the command `elm-test` 26 | 27 | 28 | ## Resources 29 | 30 | Some handy reading :wink: :https://medium.com/@andrewMacmurray/the-meaning-of-map-in-elm-6480afc8139d 31 | -------------------------------------------------------------------------------- /part2/map-workshop/Solutions.elm: -------------------------------------------------------------------------------- 1 | module Solutions exposing (..) 2 | 3 | -- Dont look at these until you've finished or you're stuck! 4 | {--LISTS --} 5 | -- double the items in the list 6 | 7 | 8 | double : List number -> List number 9 | double list = 10 | list |> List.map (\x -> x * 2) 11 | 12 | 13 | 14 | -- return a list with only the first elements of each tuple 15 | 16 | 17 | tuples : List ( number, String ) -> List number 18 | tuples list = 19 | list |> List.map (\( a, b ) -> a) 20 | 21 | 22 | 23 | -- return a list of booleans where each item is if the value in the list is less than 3 24 | 25 | 26 | bools : List number -> List Bool 27 | bools list = 28 | list |> List.map (\x -> x < 3) 29 | 30 | 31 | 32 | {--MAYBES --} 33 | -- get the head of the list (remember the list could be empty so should return nothing in this case) 34 | 35 | 36 | headOfTheList : List a -> Maybe a 37 | headOfTheList list = 38 | list |> List.head 39 | 40 | 41 | 42 | -- concat a string to the value inside a maybe 43 | 44 | 45 | concatMeMaybe : String -> Maybe String -> Maybe String 46 | concatMeMaybe str maybeString = 47 | maybeString |> Maybe.map (\x -> x ++ str) 48 | 49 | 50 | 51 | -- get the number out of the maybe but if it's not there return zero instead 52 | 53 | 54 | numberOrZero : Maybe number -> number 55 | numberOrZero maybeNumber = 56 | maybeNumber |> Maybe.withDefault 0 57 | 58 | 59 | 60 | -- add the values of the two maybes together but return nothing if either of them are not there (hint have a look at Maybe.map2) 61 | 62 | 63 | addMaybes : Maybe number -> Maybe number -> Maybe number 64 | addMaybes maybe1 maybe2 = 65 | Maybe.map2 (\n1 n2 -> n1 + n2) maybe1 maybe2 66 | 67 | 68 | 69 | {--RESULTS --} 70 | {--attempt to parse a string into an Int 71 | remember you could pass the function something like "errhmagerrd" which is not an age 72 | in that case it would return `Err "could not convert string 'errhmagerrd' to an Int"` 73 | but if you gave the function 25 it would return `Ok 25` 74 | Hint, have a look in the String module of the core elm library (it's a one liner) 75 | --} 76 | 77 | 78 | getMyAge : String -> Result String Int 79 | getMyAge ageString = 80 | String.toInt ageString 81 | 82 | 83 | 84 | -- parse an age from a string and add 3 to it 85 | 86 | 87 | makeMe3YearsOlder : String -> Result String Int 88 | makeMe3YearsOlder ageString = 89 | ageString 90 | |> String.toInt 91 | |> Result.map (\age -> age + 3) 92 | 93 | 94 | 95 | -- parse two ages from strings and add the results together (hint have a look at Result.map2) 96 | 97 | 98 | combinedAge : String -> String -> Result String Int 99 | combinedAge ageString1 ageString2 = 100 | Result.map2 101 | (\age1 age2 -> age1 + age2) 102 | (String.toInt ageString1) 103 | (String.toInt ageString2) 104 | -------------------------------------------------------------------------------- /part2/map-workshop/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "map workshop exercises", 4 | "repository": "https://github.com/TechforgoodCAST/elm-week.git", 5 | "license": "MIT", 6 | "source-directories": [ 7 | "." 8 | ], 9 | "exposed-modules": [], 10 | "dependencies": { 11 | "elm-lang/core": "5.1.1 <= v < 6.0.0", 12 | "elm-lang/html": "2.0.0 <= v < 3.0.0" 13 | }, 14 | "elm-version": "0.18.0 <= v < 0.19.0" 15 | } 16 | -------------------------------------------------------------------------------- /part2/map-workshop/tests/.gitignore: -------------------------------------------------------------------------------- 1 | /elm-stuff/ 2 | -------------------------------------------------------------------------------- /part2/map-workshop/tests/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Expect 4 | import Fuzz exposing (..) 5 | import MapExercises exposing (..) 6 | import Test exposing (..) 7 | 8 | 9 | listTests : Test 10 | listTests = 11 | describe "list tests" 12 | [ describe "double list" 13 | [ test "should double numbers in list" <| 14 | \() -> 15 | Expect.equal 16 | (double [ 1, 2, 3, 4, 5 ]) 17 | [ 2, 4, 6, 8, 10 ] 18 | , test "can handle negatives" <| 19 | \() -> 20 | Expect.equal 21 | (double [ -1, -2, -3, -4, -5 ]) 22 | [ -2, -4, -6, -8, -10 ] 23 | ] 24 | , describe "tuples tests" 25 | [ test "extracts only the numbers" <| 26 | \() -> 27 | Expect.equal 28 | (tuples 29 | [ ( 0, "dont" ) 30 | , ( 1, "take" ) 31 | , ( 2, "these" ) 32 | , ( 3, "values" ) 33 | , ( 4, "please" ) 34 | ] 35 | ) 36 | (List.range 0 4) 37 | ] 38 | , describe "fuzz tuples" 39 | -- tupleFuzzer generates random lists of (String, Int)s 40 | [ fuzz tupleFuzzer "extracts only the numbers" <| 41 | \stringInts -> 42 | Expect.equal 43 | (tuples stringInts) 44 | (List.map Tuple.first stringInts) 45 | ] 46 | , describe "bools tests" 47 | [ test "returns list of bools where number is less than 3" <| 48 | \() -> 49 | Expect.equal 50 | (bools [ 1, 2, 3, 4, 5 ]) 51 | [ True, True, False, False, False ] 52 | ] 53 | ] 54 | 55 | 56 | maybeTests : Test 57 | maybeTests = 58 | describe "maybes tests" 59 | [ describe "headOfTheList Fuzz tests" 60 | [ fuzz (list string) "gets the head of the list" <| 61 | \xs -> 62 | Expect.equal 63 | (headOfTheList xs) 64 | (List.head xs) 65 | , fuzz (list (maybe int)) "gets the head of the list pt. II" <| 66 | \xs -> 67 | Expect.equal 68 | (headOfTheList xs) 69 | (List.head xs) 70 | ] 71 | , describe "concatMeMaybe tests" 72 | [ test "adds a string to the maybe" <| 73 | \() -> 74 | Expect.equal 75 | (concatMeMaybe ", and this is crazy" (Just "I just met you")) 76 | (Just "I just met you, and this is crazy") 77 | , test "handles nothing values" <| 78 | \() -> 79 | Expect.equal 80 | (concatMeMaybe "But here's my number" Nothing) 81 | Nothing 82 | ] 83 | , describe "numberOrZero tests" 84 | [ test "returns the number from a Just" <| 85 | \() -> 86 | Expect.equal (numberOrZero (Just 3)) 3 87 | , test "returns zero if given nothing" <| 88 | \() -> 89 | Expect.equal (numberOrZero Nothing) 0 90 | ] 91 | , describe "numberOrZero fuzz tests" 92 | [ fuzz (maybe int) "returns int correct" <| 93 | \maybeInt -> 94 | Expect.equal 95 | (numberOrZero maybeInt) 96 | (Maybe.withDefault 0 maybeInt) 97 | ] 98 | , describe "addMaybes tests" 99 | [ test "adds two numbers inside Justs together" <| 100 | \() -> 101 | Expect.equal 102 | (addMaybes (Just 3) (Just 5)) 103 | (Just 8) 104 | , test "If either is Nothing return Nothing" <| 105 | \() -> 106 | Expect.equal 107 | (addMaybes Nothing (Just 800)) 108 | Nothing 109 | , test "If either is Nothing return Nothing pt. II" <| 110 | \() -> 111 | Expect.equal 112 | (addMaybes (Just 800) Nothing) 113 | Nothing 114 | ] 115 | ] 116 | 117 | 118 | resultTests : Test 119 | resultTests = 120 | describe 121 | "result tests" 122 | [ describe "getMyAge tests" 123 | [ test "parses an age string correctly" <| 124 | \() -> 125 | Expect.equal 126 | (getMyAge "28") 127 | (Ok 28) 128 | , test "returns an Err if given and invalid age" <| 129 | \() -> 130 | Expect.equal 131 | (getMyAge "errhmagerrd") 132 | (Err "could not convert string 'errhmagerrd' to an Int") 133 | ] 134 | , describe "makeMe3YearsOlder tests" 135 | [ test "adds 3 years to an age string" <| 136 | \() -> 137 | Expect.equal 138 | (makeMe3YearsOlder "16") 139 | (Ok 19) 140 | , test "returns an Err if given an invalid age" <| 141 | \() -> 142 | Expect.equal 143 | (makeMe3YearsOlder "errhmagerrd") 144 | (Err "could not convert string 'errhmagerrd' to an Int") 145 | ] 146 | , describe "combinedAge tests" 147 | [ test "adds two valid ages together" <| 148 | \() -> 149 | Expect.equal 150 | (combinedAge "28" "25") 151 | (Ok 53) 152 | , test "returns an Err if either of them are invalid" <| 153 | \() -> 154 | Expect.equal 155 | (combinedAge "28" "blergh") 156 | (Err "could not convert string 'blergh' to an Int") 157 | , test "returns an Err if either of them are invalid pt. II" <| 158 | \() -> 159 | Expect.equal 160 | (combinedAge "blergh" "28") 161 | (Err "could not convert string 'blergh' to an Int") 162 | ] 163 | ] 164 | 165 | 166 | 167 | -- tuple fuzzer is a special function that generates random lists of (Int, String)s 168 | 169 | 170 | tupleFuzzer : Fuzzer (List ( Int, String )) 171 | tupleFuzzer = 172 | list <| tuple ( int, string ) 173 | -------------------------------------------------------------------------------- /part2/map-workshop/tests/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "Test Suites", 4 | "repository": "https://github.com/TechforgoodCAST/elm-week.git", 5 | "license": "MIT", 6 | "source-directories": [ 7 | "..", 8 | "." 9 | ], 10 | "exposed-modules": [], 11 | "dependencies": { 12 | "eeue56/elm-html-test": "5.1.1 <= v < 6.0.0", 13 | "elm-community/elm-test": "4.0.0 <= v < 5.0.0", 14 | "elm-lang/core": "5.1.1 <= v < 6.0.0", 15 | "elm-lang/html": "2.0.0 <= v < 3.0.0" 16 | }, 17 | "elm-version": "0.18.0 <= v < 0.19.0" 18 | } 19 | -------------------------------------------------------------------------------- /part2/mini-todos-workshop/README.md: -------------------------------------------------------------------------------- 1 | # Mini Todos Workshop 2 | 3 | Make your own simple todo app based on the types from the first section of https://guide.elm-lang.org/types/union_types.html 4 | 5 | 6 | ## Features 7 | 8 | + Can see a list of your todos 9 | + Can click on buttons to change which todos can be seen (`All`, `Completed` and `Active`) 10 | 11 | Remember if you get stuck with the basic scaffold of an app, look back at the user input section https://guide.elm-lang.org/architecture/user_input/ 12 | 13 | ## Stretch Goals 14 | 15 | + Add some extra visibility options (try adding something like `Urgent`) 16 | + Can you add a priority to your todos? How might you link this to the `Urgent` visibility? 17 | + Can you make each button go a different colour when it's selected? 18 | 19 | ## Getting started 20 | 21 | 1. cd into this directory 22 | 2. run `elm-reactor` 23 | 3. Add your solution to `Todo.elm` 24 | 4. Preview it at `localhost:8000/Todo.elm` 25 | 26 | Only check the `Solution.elm` if you get stuck! 27 | -------------------------------------------------------------------------------- /part2/mini-todos-workshop/Solution.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Html exposing (..) 4 | import Html.Attributes exposing (..) 5 | import Html.Events exposing (onClick) 6 | 7 | 8 | main : Program Never Model Msg 9 | main = 10 | beginnerProgram 11 | { model = model 12 | , view = view 13 | , update = update 14 | } 15 | 16 | 17 | type alias Model = 18 | { todos : List Todo 19 | , visibility : Visibility 20 | } 21 | 22 | 23 | type Visibility 24 | = All 25 | | Active 26 | | Completed 27 | | Urgent 28 | 29 | 30 | type alias Todo = 31 | { task : String 32 | , completed : Bool 33 | , urgent : Bool 34 | } 35 | 36 | 37 | visibleTodos : Visibility -> List Todo -> List Todo 38 | visibleTodos visibility todoList = 39 | case visibility of 40 | All -> 41 | todoList 42 | 43 | Active -> 44 | List.filter (not << .completed) todoList 45 | 46 | Completed -> 47 | List.filter .completed todoList 48 | 49 | Urgent -> 50 | List.filter .urgent todoList 51 | 52 | 53 | model : Model 54 | model = 55 | { todos = sampleTodos 56 | , visibility = All 57 | } 58 | 59 | 60 | sampleTodos : List Todo 61 | sampleTodos = 62 | [ { task = "Get food" 63 | , completed = False 64 | , urgent = True 65 | } 66 | , { task = "Clean room" 67 | , completed = False 68 | , urgent = False 69 | } 70 | , { task = "Finish CV" 71 | , completed = False 72 | , urgent = False 73 | } 74 | , { task = "Learn Javascript" 75 | , completed = True 76 | , urgent = False 77 | } 78 | ] 79 | 80 | 81 | type Msg 82 | = SetVisibility Visibility 83 | 84 | 85 | update : Msg -> Model -> Model 86 | update msg model = 87 | case msg of 88 | SetVisibility visibility -> 89 | { model | visibility = visibility } 90 | 91 | 92 | view : Model -> Html Msg 93 | view model = 94 | div [] 95 | [ stylesheetLink 96 | , renderApp model 97 | ] 98 | 99 | 100 | renderApp : Model -> Html Msg 101 | renderApp model = 102 | div [ class "mw6 sans-serif center" ] 103 | [ h1 [ class "tc" ] [ text "Todos" ] 104 | , div [ style [ ( "min-height", "200px" ) ] ] <| List.map renderTodo <| visibleTodos model.visibility model.todos 105 | , visibilityFilters 106 | ] 107 | 108 | 109 | visibilityFilters : Html Msg 110 | visibilityFilters = 111 | let 112 | buttonClasses = 113 | "br2 mr3 bg-blue bn white ph3 pv2 outline-0" 114 | in 115 | div [] 116 | [ button [ class buttonClasses, onClick <| SetVisibility All ] [ text "all" ] 117 | , button [ class buttonClasses, onClick <| SetVisibility Active ] [ text "active" ] 118 | , button [ class buttonClasses, onClick <| SetVisibility Completed ] [ text "completed" ] 119 | , button [ class buttonClasses, onClick <| SetVisibility Urgent ] [ text "urgent" ] 120 | ] 121 | 122 | 123 | renderTodo : Todo -> Html Msg 124 | renderTodo todo = 125 | p [] [ text todo.task ] 126 | 127 | 128 | stylesheetLink : Html msg 129 | stylesheetLink = 130 | node "link" 131 | [ rel "stylesheet" 132 | , href "https://unpkg.com/tachyons@4.8.0/css/tachyons.min.css" 133 | ] 134 | [] 135 | -------------------------------------------------------------------------------- /part2/mini-todos-workshop/Todo.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | 4 | {-- put your todo app here! 5 | remember start with your basic scaffold - model, update and view 6 | hook these up to your `main` function with Html.beginnerProgram 7 | 8 | start with the `type alias Task` and `type Visibility` types from the first section of the union types chapter 9 | https://guide.elm-lang.org/types/union_types.html 10 | 11 | Make a todo app where you can click on buttons to change the visibility of your todos 12 | --} 13 | -------------------------------------------------------------------------------- /part2/mini-todos-workshop/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "helpful summary of your project, less than 80 characters", 4 | "repository": "https://github.com/user/project.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "." 8 | ], 9 | "exposed-modules": [], 10 | "dependencies": { 11 | "elm-lang/core": "5.1.1 <= v < 6.0.0", 12 | "elm-lang/html": "2.0.0 <= v < 3.0.0" 13 | }, 14 | "elm-version": "0.18.0 <= v < 0.19.0" 15 | } 16 | -------------------------------------------------------------------------------- /part3/http-workshop/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Html exposing (..) 4 | 5 | 6 | main : Html msg 7 | main = 8 | text "hello world" 9 | -------------------------------------------------------------------------------- /part3/http-workshop/README.md: -------------------------------------------------------------------------------- 1 | # Http code-along 2 | 3 | # Objectives 4 | 5 | * Become familiar with the step by step process required to make a Http request in Elm 6 | * Understand how Elm handles failures in the outside world 7 | * Start practicing how to decode incoming Json into Elm datatypes 8 | 9 | ## Steps 10 | 11 | 1. Read through https://guide.elm-lang.org/architecture/effects/, up to the Random section. 12 | 2. Go to https://github.com/settings/tokens and generate yourself a new token. Put it in a safe place! 13 | 3. Get ready for some code-along fun! 14 | 15 | ## Tips 16 | 17 | * Be careful with Elm package versions. It is always safer to go to http://package.elm-lang.org/ and search on there rather than on google. Searching for them on google often takes you to an outdated version. 18 | 19 | ## Building 20 | 21 | ```bash 22 | elm-live Main.elm --open --pushstate --output=elm.js 23 | ``` 24 | -------------------------------------------------------------------------------- /part3/http-workshop/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "", 4 | "repository": "https://github.com/TechforgoodCAST/elm-week.git", 5 | "license": "BSD-3-Clause", 6 | "source-directories": [ 7 | "." 8 | ], 9 | "exposed-modules": [], 10 | "dependencies": { 11 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 12 | "elm-lang/html": "2.0.0 <= v < 3.0.0", 13 | "elm-lang/http": "1.0.0 <= v < 2.0.0" 14 | }, 15 | "elm-version": "0.18.0 <= v < 0.19.0" 16 | } 17 | -------------------------------------------------------------------------------- /part3/http-workshop/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Syntax workshop 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /part3/json-workshop/DeeplyNested.elm: -------------------------------------------------------------------------------- 1 | module DeeplyNested exposing (..) 2 | 3 | import Html exposing (..) 4 | import SampleJson exposing (deeplyNestedString) 5 | 6 | 7 | main : Html msg 8 | main = 9 | text "render your solution here" 10 | 11 | 12 | type alias RescuedData = 13 | { saved1 : String 14 | , saved2 : String 15 | , savedList : List Int 16 | } 17 | 18 | 19 | 20 | {-- 21 | to run your decoder call `decodeString` (from Json.Decode) with your `decoder` and a `string` of raw JSON 22 | 23 | main might look something like: 24 | 25 | main = text <| toString <| decodeString rescueDecoder todoString 26 | 27 | You don't normally need to do the decodeString step however (Http handles this bit for you) 28 | --} 29 | -------------------------------------------------------------------------------- /part3/json-workshop/Guardian.elm: -------------------------------------------------------------------------------- 1 | module Guardian exposing (..) 2 | 3 | import Html exposing (..) 4 | import SampleJson exposing (guardianApiString) 5 | 6 | 7 | type alias Article = 8 | { title : String 9 | , section : String 10 | , url : String 11 | } 12 | 13 | 14 | main : Html msg 15 | main = 16 | text "render your solution here" 17 | 18 | 19 | 20 | {-- 21 | to run your decoder call `decodeString` (from Json.Decode) with your `decoder` and a `string` of raw JSON 22 | 23 | main might look something like: 24 | 25 | main = text <| toString <| decodeString articlesDecoder todoString 26 | 27 | You don't normally need to do the decodeString step however (Http handles this bit for you) 28 | --} 29 | -------------------------------------------------------------------------------- /part3/json-workshop/README.md: -------------------------------------------------------------------------------- 1 | # JSON Workshop 2 | 3 | This workshop will help you get to grips with writing JSON decoders rather than the full HTTP request response cycle 4 | 5 | JSON in elm may seem like a weird an unnecessarily complex task at first but it can have some really positive effects on the reliability of your app! 6 | 7 | 8 | ## Decoders 9 | 10 | + A string of JSON must be decoded (turned into `Elm` values) before it can be used in your elm app 11 | + We write special functions called `Decoders` to do this 12 | + These are a bit like a template of what Elm expects the JSON structure to be like 13 | 14 | ## Why? 15 | 16 | + JSON has a habit of exploding (try calling `JSON.parse` on an invalid JSON string in JavaScript) 17 | + Having a schema for your JSON means you always get back the values you expect (so the rest of your app can use them) 18 | + Errors are gracefully handled using the `Result` type (the result of parsing a string can be either `OK YourData` or `Err reason`) 19 | 20 | 21 | ### Reference Docs and guides: 22 | 23 | Elm Lang Guide section - https://guide.elm-lang.org/interop/json.html 24 | JSON Decode docs - http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Json-Decode 25 | Elm Decode Pipeline docs - http://package.elm-lang.org/packages/NoRedInk/elm-decode-pipeline/latest 26 | 27 | ## Exercises 28 | 29 | There are 3 strings of raw JSON for you to decode into elm values (in `SampleJson.elm`), and three files set up for you (`Todo.elm`, `DeeplyNested.elm`, `Guardian.elm`) 30 | For now all you need to do is render the result of the decoder to the screen 31 | 32 | ### 1. Decode Todos JSON 33 | 34 | Turn the string of todos JSON into a list of todos `List Todo` 35 | 36 | ```json 37 | { 38 | "data": { 39 | "todos": [ 40 | { "task": "Clean room", "urgent": false, "completed": false }, 41 | { "task": "Get food", "urgent": true, "completed": false }, 42 | { "task": "Learn JavaScript", "urgent": false, "completed": true }, 43 | { "task": "Finish CV", "urgent": false, "completed": false } 44 | ] 45 | } 46 | } 47 | ``` 48 | 49 | 50 | ### 2. Decode Deeply Nested JSON 51 | 52 | This is a slightly contrived example; put each of the nested keys into the `RescuedData` record type 53 | 54 | ```json 55 | { 56 | "key1": { 57 | "key2": { 58 | "key3": { 59 | "key4": "help me I'm buried away!" 60 | }, 61 | "key5": "save me too!" 62 | } 63 | }, 64 | "random_list_of_numbers": [ 120, 23, 45, 96, 5 ] 65 | } 66 | ``` 67 | 68 | ### 3. Decode Guardian API response 69 | 70 | Extract the useful data from the sample guardian api response string into a list of articles `List Article` 71 | 72 | ```json 73 | { 74 | "response": { 75 | "status": "ok", 76 | "total": 13724, 77 | "startIndex": 1, 78 | "pageSize": 10, 79 | "currentPage": 1, 80 | "pages": 1373, 81 | "orderBy": "relevance", 82 | "results": [ 83 | { 84 | "id": "lifeandstyle/2017/aug/08/share-your-underwhelming-pictures-of-cats", 85 | "type": "article", 86 | "sectionId": "lifeandstyle", 87 | "sectionName": "Life and style", 88 | "webPublicationDate": "2017-08-08T11:09:15Z", 89 | "webTitle": "Share your underwhelming pictures of cats", 90 | "webUrl": "https://www.theguardian.com/lifeandstyle/2017/aug/08/share-your-underwhelming-pictures-of-cats", 91 | "apiUrl": "https://content.guardianapis.com/lifeandstyle/2017/aug/08/share-your-underwhelming-pictures-of-cats", 92 | "isHosted": false 93 | }, 94 | { 95 | "id": "lifeandstyle/2017/may/05/experience-my-dog-rescues-cats", 96 | "type": "article", 97 | "sectionId": "lifeandstyle", 98 | "sectionName": "Life and style", 99 | "webPublicationDate": "2017-05-05T13:00:34Z", 100 | "webTitle": "Experience: my dog rescues cats", 101 | "webUrl": "https://www.theguardian.com/lifeandstyle/2017/may/05/experience-my-dog-rescues-cats", 102 | "apiUrl": "https://content.guardianapis.com/lifeandstyle/2017/may/05/experience-my-dog-rescues-cats", 103 | "isHosted": false 104 | } 105 | ] 106 | } 107 | } 108 | ``` 109 | 110 | 111 | ## Tips 112 | 113 | + Think of your decoders as bits of lego that you can plug together (start small and build them into bigger pieces) 114 | + Always start with your core data structure (e.g. your `Article` or `Todo`) 115 | + The order of the keys that you decode is the same order that they are defined in your record type (the `Article` constructor is just a function that takes 3 strings and returns an `Article`) 116 | + Your Elm Data doesn't have to have the same nesting as the JSON! Even though the data may be deeply nested in the JSON, your decoders will most likely flatten it into a more useful structure 117 | -------------------------------------------------------------------------------- /part3/json-workshop/SampleJson.elm: -------------------------------------------------------------------------------- 1 | module SampleJson exposing (..) 2 | 3 | 4 | todosString : String 5 | todosString = 6 | """ 7 | { 8 | "data": { 9 | "todos": [ 10 | { "task": "Clean room", "urgent": false, "completed": false }, 11 | { "task": "Get food", "urgent": true, "completed": false }, 12 | { "task": "Learn JavaScript", "urgent": false, "completed": true }, 13 | { "task": "Finish CV", "urgent": false, "completed": false } 14 | ] 15 | } 16 | } 17 | """ 18 | 19 | 20 | deeplyNestedString : String 21 | deeplyNestedString = 22 | """ 23 | { 24 | "key1": { 25 | "key2": { 26 | "key3": { 27 | "key4": "help me I'm buried away!" 28 | }, 29 | "key5": "save me too!" 30 | } 31 | }, 32 | "list_of_numbers": [ 120, 23, 45, 96, 5 ] 33 | } 34 | """ 35 | 36 | 37 | guardianApiString : String 38 | guardianApiString = 39 | """ 40 | { 41 | "response": { 42 | "status": "ok", 43 | "total": 13724, 44 | "startIndex": 1, 45 | "pageSize": 10, 46 | "currentPage": 1, 47 | "pages": 1373, 48 | "orderBy": "relevance", 49 | "results": [ 50 | { 51 | "id": "lifeandstyle/2017/aug/08/share-your-underwhelming-pictures-of-cats", 52 | "type": "article", 53 | "sectionId": "lifeandstyle", 54 | "sectionName": "Life and style", 55 | "webPublicationDate": "2017-08-08T11:09:15Z", 56 | "webTitle": "Share your underwhelming pictures of cats", 57 | "webUrl": "https://www.theguardian.com/lifeandstyle/2017/aug/08/share-your-underwhelming-pictures-of-cats", 58 | "apiUrl": "https://content.guardianapis.com/lifeandstyle/2017/aug/08/share-your-underwhelming-pictures-of-cats", 59 | "isHosted": false 60 | }, 61 | { 62 | "id": "lifeandstyle/2017/may/05/experience-my-dog-rescues-cats", 63 | "type": "article", 64 | "sectionId": "lifeandstyle", 65 | "sectionName": "Life and style", 66 | "webPublicationDate": "2017-05-05T13:00:34Z", 67 | "webTitle": "Experience: my dog rescues cats", 68 | "webUrl": "https://www.theguardian.com/lifeandstyle/2017/may/05/experience-my-dog-rescues-cats", 69 | "apiUrl": "https://content.guardianapis.com/lifeandstyle/2017/may/05/experience-my-dog-rescues-cats", 70 | "isHosted": false 71 | }, 72 | { 73 | "id": "environment/commentisfree/2017/jul/11/cats-dogs-caterpillars-pets-butterflies", 74 | "type": "article", 75 | "sectionId": "environment", 76 | "sectionName": "Environment", 77 | "webPublicationDate": "2017-07-11T07:00:03Z", 78 | "webTitle": "Forget cats and dogs – caterpillars make the best pets | Patrick Barkham", 79 | "webUrl": "https://www.theguardian.com/environment/commentisfree/2017/jul/11/cats-dogs-caterpillars-pets-butterflies", 80 | "apiUrl": "https://content.guardianapis.com/environment/commentisfree/2017/jul/11/cats-dogs-caterpillars-pets-butterflies", 81 | "isHosted": false 82 | }, 83 | { 84 | "id": "film/2017/jun/29/kedi-review-street-cats-of-istanbul-documentary-ceyda-torun", 85 | "type": "article", 86 | "sectionId": "film", 87 | "sectionName": "Film", 88 | "webPublicationDate": "2017-06-29T11:00:21Z", 89 | "webTitle": "Kedi review – slinking around with the street cats of Istanbul", 90 | "webUrl": "https://www.theguardian.com/film/2017/jun/29/kedi-review-street-cats-of-istanbul-documentary-ceyda-torun", 91 | "apiUrl": "https://content.guardianapis.com/film/2017/jun/29/kedi-review-street-cats-of-istanbul-documentary-ceyda-torun", 92 | "isHosted": false 93 | }, 94 | { 95 | "id": "artanddesign/2017/sep/25/turner-prize-2017-exhibition-review-a-snake-infested-garden-and-fat-cats-on-horseback", 96 | "type": "article", 97 | "sectionId": "artanddesign", 98 | "sectionName": "Art and design", 99 | "webPublicationDate": "2017-09-25T17:22:56Z", 100 | "webTitle": "Turner prize 2017 exhibition review: a snake-infested garden and fat cats on horseback", 101 | "webUrl": "https://www.theguardian.com/artanddesign/2017/sep/25/turner-prize-2017-exhibition-review-a-snake-infested-garden-and-fat-cats-on-horseback", 102 | "apiUrl": "https://content.guardianapis.com/artanddesign/2017/sep/25/turner-prize-2017-exhibition-review-a-snake-infested-garden-and-fat-cats-on-horseback", 103 | "isHosted": false 104 | }, 105 | { 106 | "id": "music/2017/may/29/english-national-opera-the-day-after-review-jonathan-dove", 107 | "type": "article", 108 | "sectionId": "music", 109 | "sectionName": "Music", 110 | "webPublicationDate": "2017-05-29T13:09:38Z", 111 | "webTitle": "The Day After review – devastation, dead cats and godlike grandeur", 112 | "webUrl": "https://www.theguardian.com/music/2017/may/29/english-national-opera-the-day-after-review-jonathan-dove", 113 | "apiUrl": "https://content.guardianapis.com/music/2017/may/29/english-national-opera-the-day-after-review-jonathan-dove", 114 | "isHosted": false 115 | } 116 | ] 117 | } 118 | } 119 | """ 120 | -------------------------------------------------------------------------------- /part3/json-workshop/Solutions/DeeplyNested.elm: -------------------------------------------------------------------------------- 1 | module Solutions.DeeplyNested exposing (..) 2 | 3 | import Html exposing (..) 4 | import Json.Decode exposing (Decoder, string, list, int, decodeString) 5 | import Json.Decode.Pipeline exposing (decode, required, requiredAt) 6 | import SampleJson 7 | 8 | 9 | main : Html msg 10 | main = 11 | text <| toString <| decodeString rescueDecoder SampleJson.deeplyNestedString 12 | 13 | 14 | type alias RescuedData = 15 | { saved1 : String 16 | , saved2 : String 17 | , savedList : List Int 18 | } 19 | 20 | 21 | rescueDecoder : Decoder RescuedData 22 | rescueDecoder = 23 | decode RescuedData 24 | |> requiredAt [ "key1", "key2", "key3", "key4" ] string 25 | |> requiredAt [ "key1", "key2", "key5" ] string 26 | |> required "list_of_numbers" (list int) 27 | -------------------------------------------------------------------------------- /part3/json-workshop/Solutions/Guardian.elm: -------------------------------------------------------------------------------- 1 | module Solutions.Guardian exposing (..) 2 | 3 | import Html exposing (..) 4 | import Json.Decode exposing (..) 5 | import Json.Decode.Pipeline exposing (..) 6 | import SampleJson 7 | 8 | 9 | type alias Article = 10 | { title : String 11 | , section : String 12 | , url : String 13 | } 14 | 15 | 16 | main : Html msg 17 | main = 18 | text <| toString <| decodeString guardianDecoder SampleJson.guardianApiString 19 | 20 | 21 | guardianDecoder : Decoder (List Article) 22 | guardianDecoder = 23 | at [ "response", "results" ] (list articleDecoder) 24 | 25 | 26 | articleDecoder : Decoder Article 27 | articleDecoder = 28 | decode Article 29 | |> required "webTitle" string 30 | |> required "sectionName" string 31 | |> required "webUrl" string 32 | -------------------------------------------------------------------------------- /part3/json-workshop/Solutions/Todo.elm: -------------------------------------------------------------------------------- 1 | module Solutions.Todo exposing (..) 2 | 3 | import Html exposing (..) 4 | import Json.Decode as Decode exposing (Decoder, decodeString, string, bool, list, at) 5 | import Json.Decode.Pipeline exposing (decode, required) 6 | import SampleJson 7 | 8 | 9 | type alias Todo = 10 | { task : String 11 | , completed : Bool 12 | , urgent : Bool 13 | } 14 | 15 | 16 | main : Html msg 17 | main = 18 | text <| toString <| decodeString todosDecoder SampleJson.todosString 19 | 20 | 21 | todosDecoder : Decoder (List Todo) 22 | todosDecoder = 23 | at [ "data", "todos" ] (list todoDecoder) 24 | 25 | 26 | todoDecoder : Decoder Todo 27 | todoDecoder = 28 | decode Todo 29 | |> required "task" string 30 | |> required "completed" bool 31 | |> required "urgent" bool 32 | 33 | 34 | 35 | {-- 36 | the decoder below is equivalent to the one above 37 | under the hood decode pipeline uses the core functions from Json.Decode 38 | the pipeline style makes it easier to add new fields -- you have to keep changing to map3, map4 etc to add more fields 39 | --} 40 | -- todoDecoder : Decoder Todo 41 | -- todoDecoder = 42 | -- Json.Decode.map3 Todo 43 | -- (field "task" string) 44 | -- (field "completed" bool) 45 | -- (field "urgent" bool) 46 | -------------------------------------------------------------------------------- /part3/json-workshop/Todo.elm: -------------------------------------------------------------------------------- 1 | module TodoPipeline exposing (..) 2 | 3 | import Html exposing (..) 4 | import SampleJson exposing (todosString) 5 | 6 | 7 | type alias Todo = 8 | { task : String 9 | , completed : Bool 10 | , urgent : Bool 11 | } 12 | 13 | 14 | main : Html msg 15 | main = 16 | text "render your solution here" 17 | 18 | 19 | 20 | {-- 21 | to run your decoder call `decodeString` (from Json.Decode) with your `decoder` and a `string` of raw JSON 22 | 23 | main might look something like: 24 | 25 | main = text <| toString <| decodeString todosDecoder todoString 26 | 27 | You don't normally need to do the decodeString step however (Http handles this bit for you) 28 | --} 29 | -------------------------------------------------------------------------------- /part3/json-workshop/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "helpful summary of your project, less than 80 characters", 4 | "repository": "https://github.com/user/project.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "." 8 | ], 9 | "exposed-modules": [], 10 | "dependencies": { 11 | "NoRedInk/elm-decode-pipeline": "3.0.0 <= v < 4.0.0", 12 | "elm-lang/core": "5.1.1 <= v < 6.0.0", 13 | "elm-lang/html": "2.0.0 <= v < 3.0.0" 14 | }, 15 | "elm-version": "0.18.0 <= v < 0.19.0" 16 | } 17 | --------------------------------------------------------------------------------