Basic TODO application using LiveView that allows for concurrent usage.
After installing Phoenix simply ran mix phx.new todo --no-gettext
- Startup the docker container with
docker compose up -d
mix setup
to get your environment readymix phx.server
to run your server on port 4000
You will find a Todo list where if you type and leave the typing area it will add automatically a new entry to your todo list. You can also do this in a concurrent manner where you can have multiple people (tabs) add, removing, toggling entries and those changes will be reflected in every users screen.
This is achieved thanks to multiple elements from the Phoenix Framework:
- LiveView to render newly changed information in a reactive way
- Phoenix.PubSub which is a way to distribute events across subscribers
- Ecto for database connection
Ecto connects to your DB and manages that connection while also offering connection guarantees (e.g. for fun try to kill your docker container while running the server and then bring it back up)
To actually have an element that represents our todo entry you will need
- A migration
- Created with
mix ecto.gen.migration create_entries
- In
priv/repo/migrations/20231110000732_create_entries.exs
create the table using theEcto.Migration
functions
- Created with
- Create a schema
- Created a file
lib/todo/entries/entry.ex
- Use
Ecto.Schema
to define what are the fields from this new entity - Define a function called
changeset
that will tell what is required to add / update a new entity using the functions fromEcto.Changeset
. This changeset is responsible for the validation of the changes we want to implement on a entry entity.
- Created a file
- Call the database
- Created
lib/todo/entries.ex
Repo.all
runs a query to fetch sorted data using theEcto.Query
DSLRepo.update
andRepo.insert
use a changeset so they can check if it's valid before going to the databaseRepo.delete
deletes based on a given entry
- Created
Defined at lib/todo_web/live/page_live.ex
The mount
function will take care of that and here we define a couple of important things:
First we subscrine to important events:
TodoWeb.Endpoint.subscribe("todo:entries")
Here we will see that we're using a stream and we're loading our entries into it:
stream(socket, :entries, Entries.list_entries())
This means that the server won't store this information on his side, making it possible to insert as many entries we want withouth impacting the server, only the client.
render
will define the HTML to be used by your view.
Two things are important to notice:
Elements have phx-*
attributes that are actually special to Phoenix and they will trigger / handle things differently. Check more about them in Bindings
The other thing is that we have a for cycle creating multiple elements based on what the stream contains
<div :for={{id, entry} <- @streams.entries} id={id} class="flex border-2 border-slate-200 rounded-xl p-2 justify-center items-center gap-2" >
<input type="checkbox" phx-click="done" phx-value-id={entry.id} checked={entry.done} />
<div class={"grow #{if entry.done, do: "line-through", else: ""}"}>
<%= entry.body %>
</div>
<button phx-click="delete" phx-value-id={entry.id} phx-throttle="2000">Delete</button>
</div>
There's a section where we have all event handlers by pattern matching against received events. Check all the handle_event
functions.
In each of this functions we are handling the action of a given event, we emit an event to all connected clients and then telling how the stream should be updated.
The same way we have a way to handle local change we have a way to handle external change based on the PubSub events. Those are handled by handle_info
and there's one extra function to avoid acting on our own events.
In theory here we could actually update our own view at this point but wanted to keep those separate to make more sense to new users.
Done at lib/todo_web/router.ex
where we tell it that we will have a live
view:
scope "/", TodoWeb do
pipe_through :browser
get "/", PageController, :home
live "/live", PageLive # This one
end
LiveView includes a great toolsuite of testing so in test/todo_web/live/page_live_test.exs
you will find all the code used for testing. They will be closer to a e2e test situation that keeps updating our view with every action we take.
To run them you just need to do mix test
Have fun with this project, try to extend it by:
- Adding a live cursor which would require LiveView hooks and JS interop
- Adding more rich editting with delta-elixir
- Change your persistency layer to ETS using etso so it's all in memory only
- Adding Machine Learning with Bumblebee to categorize each issue