├── .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 |
--------------------------------------------------------------------------------