This project is the work-along code for the Code & Coffee 2018 Conference in Vancouver. It's a guided introduction to Elm by building a magic-eight ball. You can follow along using Ellie, and in-browser Elm editor, or you can download this repo and run it locally.
The lesson is broken into several smaller chapters. Each chapter starts with the answer to the previous chapter so you can compare or pick up if you get stuck. Each chapter is complete once you've checked the steps and your program compiles.
Walk-through the Elm Architecture and Syntax Ellie Link
- Walk-through Ellie, Compiling, and Formatting.
- Walk-through the Elm Architecture.
- Change the view from a
div
containing a text node to adiv
containing abutton
with the phrase "Ask the Magic Eight".
Change from beginnerProgram
to Program
Ellie Link
Elm's beginnerProgram
is a great way to learn the Elm Architecture because of its stripped-down features, but it can't handle side-effects or interact with the outside world. When we need to do more we use program
. Since generating random numbers is considered a side-effect in functional programming we need to use program. We'll rely on the Elm compiler to guide us through refactoring our code.
- Change
Html.beginnerProgram
toHtml.program
and fix the compiler errors. - Update the
Main
type annotation and ensure the arguments match. Hint: we'll ignore all subscriptions in this project by passing inalways Sub.none
which means subscriptions will always be nothing. - Update the
init
type annotation to a Tuple returning the init model and the init command. Hint: we don't want to pass any commands on project initialization so we can ignore it withCmd.none
. - Update the
update
return value to match theinit
return value.
Get a Random Int and Render it Ellie Link
Our first look into the Elm Architecture process. We're going to update a value on our model when a button is clicked. In addition, we're going to issue a command to update the model with a random number after it's been generated.
- Add
randNum : Int
to the model and render it in the view function. Hint: in Elm we must explicitly convert types when we need them to be something else.text
nodes only accept strings so we usetoString
. - Add an
onClick
event to the button, passing it a new message namedGetNumber
. - Use the Random library to generate a random number as a command in the
GetNumber
branch. Hint:Random.generate
takes aGenerator
value and returns aMsg
. Random values are considered side-effects because they are changing the program in an unpredictable way. - Create another message type to receive the result of the randomly generated number as an argument, and update
randNum
in the update branch. Hint: Elm is immutable so updating records isn't possible. Instead we create a new record with a new value on the field like{ record | field = newValue }
.
Create an Array of Responses Ellie Link
It's time to prepare some responses for our magic eight ball. We'll create a list of responses in an array so that we have an automatic index. We'll also create some type aliases so our code is easier to understand.
- Import
Array
into the project. - Create two new fields,
currentResponse
andallResponses
, on theModel
.currentResponse
will hold the string value of the magic eight ball's current response, which it will have plucked from the array ofallResponses
. - Add type aliases for
currentResponse
andallResponses
so we can make better type annotations. - Create the initial values of all our responses. Hint: To get indexes on our List of responses we need to convert it to an array with
Array.fromList
.
Add the Magic Eight Ball Markup Ellie Link
- So far we only have a button with no way to display responses. Convert the following HTML markup into Elm so we can see the current response.
<div class="eight-ball">
<div class="window">
<div class="triangle">
<div class="triangle__message">{{ show the current response from the model }}</div>
</div>
</div>
</div>
Get Random Int in answer List Ellie Link
Using what we've learned, we're going to request a response when we click the button, which will generate a random number that we'll use to pluck a response from the array, and return it to the model. This chapter puts it all together!
- Using
GetNumber
as an example, create a message that requests a response. -
RequestResponse
should issue a command to generate a random number that returns the response as a side-effect. -
ReturnResponse
should get a response from all the responses then updatecurrentResponse
with the new response. Hint: It's possible to get an index that's not available. In Elm we need to cover all cases soArray.get
returns a maybe. We can use Maybe.withDefault to provide a default value if an index can't be found likeMaybe.withDefault ""
. - Since we're generating random integers but using them in array indexes, we should create a type alias for
ResponseIndex
.
Remove Dead Code with the Compiler Ellie Link
At this point our application works! There's a bit of cleanup remaining and we can improve the UX a touch. Let's lean on the compiler to clean up our code.
- Delete
RandNum
from the model and follow the compiler errors. - Delete
text
from the model and follow the compiler errors. - Delete
BasicUpdate
from the messages and follow the compiler errors. - Delete
GetNumber
from the messages and follow the compiler errors. - Delete
NewNumber
from the messages and follow the compiler errors. - Add additional responses so the magic-eight has more to choose from.
Render New Responses with Better Animation Ellie Link
Our magic-eight ball always shows the triangle, but real magic-eight balls refresh their view. Elm's virtual-DOM diffing automatically optimizes rendering which means only our text changes when we update the response. Ideally, we'd like to force the triangle to fade-in on every response. We can do that by using Keyed Nodes.
- We need to keep track of an ID when using keyed, so add a
responseIndex
to the model. - Create a function to return the triangle HTML when there is a response and nothing when there is no response. Hint: All return types must be the same so in order to return nothing in HTML we can use
text ""
for an empty text node. - Import
Keyed
for use in the view and convert thediv
with thewindow
class into a keyed node. Hint: A keyed node takes a tuple, a string identifier and a regular HTML list. We want to use theresponseIndex
as the identifier. - Assign the
responseIndex
to the random integer returned in theReturnResponse
update branch.
Leave Your Fate to the Magic-Eight Ball Completed Ellie Link
You've completed the lesson!
This project uses a Node environment using Yarn. The Elm installation itself can be found on the Elm installation page. Once the dependencies are installed, navigate to the project repo and run
yarn
Some packages may need to be install globally if they're not running correctly:
- Elm:
npm install -g elm
- Postcss-cli
npm i -g postcss-cli
To start the Elm development server (Elm Reactor) and the Postcss-cli run
yarn start
Visiting http://localhost:3000/
and navigating to your Elm files utilizes the standard Elm Reactor, which doesn't contain CSS styles.
It's recommended to install Prettier and Elm Language Support for your IDE. These tools will auto-format Elm, CSS, and JS code.
To build/update the dist folder (which will update the Github Page repo), run
yarn build
The build process should be run prior to every commit.
CSS is run through PostCSS. Available CSS variables are in ./postcss.config.js
.
When adding a new CSS file, you may need to restart the development server.