{"id":13507896,"url":"https://github.com/dwyl/phoenix-todo-list-tutorial","last_synced_at":"2025-03-30T09:33:10.313Z","repository":{"id":33576876,"uuid":"37223259","full_name":"dwyl/phoenix-todo-list-tutorial","owner":"dwyl","description":"✅ Complete beginners tutorial building a todo list from scratch in Phoenix 1.7 (latest)","archived":false,"fork":false,"pushed_at":"2025-03-06T00:14:59.000Z","size":12830,"stargazers_count":203,"open_issues_count":1,"forks_count":14,"subscribers_count":13,"default_branch":"main","last_synced_at":"2025-03-23T01:36:58.849Z","etag":null,"topics":["beginner","beginner-friendly","elixir","learn","learn-to-code","phoenix","phoenix-framework","todo","todomvc","tutorial"],"latest_commit_sha":null,"homepage":"https://phxtodo.fly.dev/","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dwyl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-06-10T21:20:36.000Z","updated_at":"2025-03-20T11:09:38.000Z","dependencies_parsed_at":"2023-11-20T23:30:01.178Z","dependency_job_id":"9a1a1491-4d37-4bfb-968b-150ba39f87d5","html_url":"https://github.com/dwyl/phoenix-todo-list-tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fphoenix-todo-list-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fphoenix-todo-list-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fphoenix-todo-list-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fphoenix-todo-list-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dwyl","download_url":"https://codeload.github.com/dwyl/phoenix-todo-list-tutorial/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246301963,"owners_count":20755512,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["beginner","beginner-friendly","elixir","learn","learn-to-code","phoenix","phoenix-framework","todo","todomvc","tutorial"],"created_at":"2024-08-01T02:00:42.288Z","updated_at":"2025-03-30T09:33:09.978Z","avatar_url":"https://github.com/dwyl.png","language":"Elixir","funding_links":[],"categories":["Examples and funny stuff"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# Phoenix Todo List Tutorial\n\nA complete beginners step-by-step tutorial\nfor building a Todo List in Phoenix.\u003cbr/\u003e\n100% functional. 0% JavaScript. \nJust `HTML`, `CSS` and `Elixir`.\nFast and maintainable.\n\n![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/dwyl/phoenix-todo-list-tutorial/ci.yml?label=build\u0026style=flat-square\u0026branch=main)\n[![codecov.io](https://img.shields.io/codecov/c/github/dwyl/phoenix-todo-list-tutorial/master.svg?style=flat-square)](http://codecov.io/github/dwyl/phoenix-todo-list-tutorial?branch=master)\n[![HitCount](http://hits.dwyl.com/dwyl/phoenix-todo-list-tutorial.svg)](http://hits.dwyl.com/dwyl/phoenix-todo-list-tutorial)\n[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat-square)](https://github.com/dwyl/phoenix-todo-list-tutorial/issues)\n\n\u003c/div\u003e\n\u003cbr /\u003e\n\n\n## Why? 🤷‍\n\nTodo lists are familiar to most people;\nwe make lists all the time.\n_Building_ a Todo list from scratch\nis a great way to learn `Elixir`/`Phoenix`\nbecause the **UI/UX** is **simple**,\nso we can focus on implementation.\n\nFor the team\n[**`@dwyl`**](https://github.com/dwyl)\nthis app/tutorial\nis a showcase of how server side rendering\n(_with client side progressive enhancement_)\ncan provide a excellent balance between\ndeveloper effectiveness (_shipping features fast_),\nUX and _accessibility_.\nThe server rendered pages take less than 5ms to respond\nso the UX is _fast_.\nOn Fly.io: \n[phxtodo.fly.dev](https://phxtodo.fly.dev/)\nround-trip response times are sub 200ms for all interactions,\nso it _feels_ like a client-side rendered App.\n\n\n\u003cbr /\u003e\n\n## What? 💭\n\nA Todo list tutorial\nthat shows a complete beginner\nhow to build an app in Elixir/Phoenix\nfrom scratch.\n\n\n### Try it on Fly.io: [phxtodo.fly.dev](https://phxtodo.fly.dev)\n\n\nTry the Fly.io version.\nAdd a few items to the list and test the functionality.\n\n![phx-todo-list-example](https://user-images.githubusercontent.com/194400/208828566-c6986ac4-17b7-4d8d-9ff0-71b6136b8ebc.gif)\n\nEven with a full HTTP round-trip for each interaction,\nthe response time is _fast_.\nPay attention to how Chrome|Firefox|Safari\nwaits for the response from the server before re-rendering the page.\nThe old full page refresh of yesteryear is _gone_.\nModern browsers intelligently render just the changes!\nSo the UX approximates \"native\"!\nSeriously, try the Fly.io app on your Phone and see!\n\n\n### TodoMVC\n\nIn this tutorial\nwe are using the\n[TodoMVC](https://github.com/dwyl/javascript-todo-list-tutorial#todomvc)\nCSS to simplify our UI.\nThis has several advantages\nthe biggest being _minimizing_ how much CSS we have to write!\nIt also means we have a guide to which _features_\nneed to be implemented to achieve full functionality.\n\n\u003e **Note**: we _love_ `CSS` for its incredible power/flexibility,\nbut we know that not everyone like it.\nsee: [learn-tachyons#why](https://github.com/dwyl/learn-tachyons#why)\nThe _last_ thing we want is to waste tons of time\nwith `CSS` in a `Phoenix` tutorial!\n\n\n\u003cbr /\u003e\n\n## Who? 👤\n\nThis tutorial is for\nanyone who is learning to Elixir/Phoenix.\nNo prior experience with Phoenix is assumed/expected.\nWe have included _all_ the steps required to build the app.\n\n\u003e If you get stuck on any step,\nplease open an\n[issue](https://github.com/dwyl/phoenix-todo-list-tutorial/issues)\non GitHub where we are happy to help  you get unstuck!\nIf you feel that any line of code can use a bit more explanation/clarity,\nplease don't hesitate to _inform_ us!\nWe _know_ what it's like to be a beginner,\nit can be _frustrating_ when something does not make sense!\nAsking questions on GitHub\nhelps _everyone_ to learn!\n\nPlease give us feedback! 🙏\nStar the repo if you found it helpful. ⭐\n\n\u003cbr /\u003e\n\n## _How_? 👩‍💻\n\n\n### Before You Start! 💡\n\n_Before_ you attempt to _build_ the Todo List,\nmake sure you have everything you need installed on you computer.\nSee:\n[prerequisites](https://github.com/dwyl/phoenix-chat-example#0-pre-requisites-before-you-start)\n\nOnce you have confirmed that you have Phoenix \u0026 PostgreSQL installed,\ntry running the _finished_ App.\n\n\n### 0. Run The _Finished_ App on Your `localhost` 💻\n\n_Before_ you start building your own version of the Todo List App,\nrun the _finished_ version on your `localhost`\nto confirm that it works.\n\nClone the project from GitHub:\n\n```sh\ngit clone git@github.com:dwyl/phoenix-todo-list-tutorial.git \u0026\u0026 cd phoenix-todo-list-tutorial\n```\n\nInstall dependencies and setup the database:\n\n```sh\nmix setup\n```\n\nStart the Phoenix server:\n\n```sh\nmix phx.server\n```\n\nVisit\n[`localhost:4000`](http://localhost:4000)\nin your web browser.\n\n\nYou should see:\n\n![phoenix-todo-list-on-localhost](https://user-images.githubusercontent.com/194400/83285190-bed5a580-a1d5-11ea-9154-80684cf9cc0e.gif)\n\nNow that you have the _finished_ example app\nrunning on your `localhost`, \u003cbr /\u003e\nlet's build it from scratch\nand understand all the steps.\n\n#### Auth [Optional]\n\nWhen running the _finished_ example app on `localhost`,\nif you want try the **`login` button**, \nyou will need to get an `AUTH_API_KEY`. [1 minute]\nSee: \n[Get your `AUTH_API_KEY`](https://github.com/dwyl/auth_plug#2-get-your-auth_api_key-)\n\n### _Build_ it!\n\nIf you ran the finished app on your `localhost`\n(_and you really should!_), \u003cbr /\u003e\nyou will need to change up a directory before starting the tutorial:\n\n```\ncd ..\n```\n\nNow you are ready to _build_!\n\n\u003cbr /\u003e\n\n### 1. Create a New Phoenix Project 🆕\n\nIn your terminal,\ncreate a new Phoenix app\nusing the following\n[`mix`](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html)\ncommand:\n\n```sh\nmix phx.new app --no-dashboard --no-gettext --no-mailer \n```\n\nWhen prompted to install dependencies,\ntype \u003ckbd\u003eY\u003c/kbd\u003e followed by \u003ckbd\u003eEnter\u003c/kbd\u003e.\n\n\u003e **Note**: those **flags** after the `app` name\n\u003e are just to avoid creating files we don't _need_ \n\u003e for this simple example. \n\u003e See: \n\u003e [hexdocs.pm/phoenix/Mix.Tasks.Phx.New](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.New.html)\n\nChange into the newly created `app` directory (`cd app`)\nand ensure you have everything you need:\n\n```sh\nmix setup\n```\n\nStart the Phoenix server:\n\n```sh\nmix phx.server\n```\n\nNow you can visit\n[`localhost:4000`](http://localhost:4000)\nin your web browser.\nYou should see something similar to:\n\n![welcome-to-phoenix](https://user-images.githubusercontent.com/17494745/206515843-2b4da196-f039-4caf-a4d2-fc316eabb2c5.png)\n\n\nShut down the Phoenix server \u003ckbd\u003ectrl\u003c/kbd\u003e+\u003ckbd\u003eC\u003c/kbd\u003e.\n\nRun the tests to ensure everything works as expected:\n\n```sh\nmix test\n```\n\nYou should see:\n\n```sh\nCompiling 16 files (.ex)\nGenerated app app\n\n17:49:40.111 [info]  Already up\n...\n\nFinished in 0.04 seconds\n3 tests, 0 failures\n```\n\nHaving established that the Phoenix App works as expected,\nlet's move on to creating some files!\n\n\u003cbr /\u003e\n\n### 2. Create `items` Schema\n\nIn creating a basic Todo List we only need one schema: `items`.\nLater we can add separate lists and tags to organise/categorise\nour `items` but for now this is all we need.\n\nRun the following [generator](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Html.html) \ncommand to create the items table:\n\n```sh\nmix phx.gen.html Todo Item items text:string person_id:integer status:integer\n```\n\nStrictly speaking we only _need_ the `text` and `status` fields,\nbut since we know we want to associate items with people\n(_later in the tutorial),\nwe are adding the field _now_.\n\n\nYou will see the following output:\n\n```\n* creating lib/app_web/controllers/item_controller.ex\n* creating lib/app_web/controllers/item_html/edit.html.heex\n* creating lib/app_web/controllers/item_html/index.html.heex\n* creating lib/app_web/controllers/item_html/new.html.heex\n* creating lib/app_web/controllers/item_html/show.html.heex\n* creating lib/app_web/controllers/item_html.ex\n* creating test/app_web/controllers/item_controller_test.exs\n* creating lib/app/todo/item.ex\n* creating priv/repo/migrations/20221205102303_create_items.exs\n* creating lib/app/todo.ex\n* injecting lib/app/todo.ex\n* creating test/app/todo_test.exs\n* injecting test/app/todo_test.exs\n* creating test/support/fixtures/todo_fixtures.ex\n* injecting test/support/fixtures/todo_fixtures.ex\n\nAdd the resource to your browser scope in lib/app_web/router.ex:\n\n    resources \"/items\", ItemController\n\n\nRemember to update your repository by running migrations:\n\n    $ mix ecto.migrate\n```\n\nThat created a _bunch_ of files!\nSome of which we don't strictly _need_. \u003cbr /\u003e\nWe could _manually_ create _only_ the files we _need_,\nbut this is the \"official\" way of creating a CRUD App in Phoenix,\nso we are using it for speed.\n\n\n\u003e **Note**: Phoenix\n[Contexts](https://hexdocs.pm/phoenix/contexts.html)\ndenoted in this example as `Todo`,\nare \"_dedicated modules that expose and group related functionality_.\"\nWe feel they _unnecessarily complicate_ basic Phoenix Apps\nwith layers of \"interface\" and we _really_ wish we could\n[avoid](https://github.com/phoenixframework/phoenix/issues/3832) them.\nBut given that they are baked into the generators,\nand the _creator_ of the framework\n[_likes_](https://youtu.be/tMO28ar0lW8?t=376) them,\nwe have a choice: either get on board with Contexts\nor _manually_ create all the files in our Phoenix projects.\nGenerators are a _much_ faster way to build!\n_Embrace_ them,\neven if you end up having to `delete` a few\nunused files along the way!\n\nWe are _not_ going to explain each of these files\nat this stage in the tutorial because\nit's _easier_ to understand the files\nas you are _building_ the App!\nThe purpose of each file will become clear\nas you progress through editing them.\n\n\n\n\u003cbr /\u003e\n\n### 2.1 Add the `/items` Resources to `router.ex`\n\nFollow the instructions noted by the generator to\nadd the `resources \"/items\", ItemController` to the `router.ex`.\n\nOpen the `lib/app_web/router.ex` file\nand locate the line: `scope \"/\", AppWeb do`.\nAdd the line to the end of the block.\ne.g:\n\n```elixir\nscope \"/\", AppWeb do\n  pipe_through :browser\n\n  get \"/\", PageController, :index\n  resources \"/items\", ItemController # this is the new line\nend\n```\n\nYour `router.ex` file should look like this:\n[`router.ex#L20`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/b158abd7f0c3fbc462a4230f07b5e5e79723a258/app/lib/app_web/router.ex#L17-L22)\n\nNow, as the terminal suggested,\nrun `mix ecto.migrate`.\nThis will finish setting up the\ndatabase tables and run the \nnecessary migrations so \neverything works properly!\n\n### 2.2 _Run_ The App!\n\nAt this point we _already_ have a functional Todo List\n(_if we were willing to use the default Phoenix UI_). \u003cbr /\u003e\nTry running the app on your `localhost`:\nRun the generated migrations with `mix ecto.migrate` then the server with:\n```\nmix phx.server\n```\n\nVisit: http://localhost:4000/items/new\nand input some data.\n\n![todo-list-phoenix-default-ui](https://user-images.githubusercontent.com/17494745/205615474-1eef8b42-86aa-4a0e-90c3-376221570255.png)\n\nClick the \"Save Item\" button\nand you will be redirected to the \"show\" page:\nhttp://localhost:4000/items/1\n\n![todo-list-phoenix-default-ui-show-item](https://user-images.githubusercontent.com/17494745/205615709-922db20e-245d-4af0-a5e3-2bbaa29771b4.png)\n\nThis is not an attractive User Experience (UX),\nbut it _works_!\nHere is a _list_ of items - a \"Todo List\".\nYou can visit this by clicking\nthe `Back to items` button or by \naccessing the following URL\nhttp://localhost:4000/items.\n\n![todo-list-phoenix-default-ui-show-items-list](https://user-images.githubusercontent.com/17494745/205616098-a514d2bb-af28-477a-b80a-6641c5b391a9.png)\n\n\nLet's improve the UX by using the TodoMVC `HTML` and `CSS`!\n\n### 3. Create the TodoMVC UI/UX\n\nTo recreate the TodoMVC UI/UX,\nlet's borrow the `HTML` code directly from the example.\n\nVisit: http://todomvc.com/examples/vanillajs\nadd a couple of items to the list.\nThen, inspect the source\nusing your browser's\n[Dev Tools](https://developers.google.com/web/tools/chrome-devtools/open).\ne.g:\n\n![todomvc-view-source](https://user-images.githubusercontent.com/194400/82698634-0b176780-9c63-11ea-9e1d-7aa6e2328766.png)\n\nRight-click on the source you want\n(e.g: `\u003csection class=\"todoapp\"\u003e`)\n and select \"Edit as HTML\":\n\n![edit-as-html](https://user-images.githubusercontent.com/194400/82721624-b8679b00-9cb6-11ea-8d3f-2405f2bd301f.png)\n\n\nOnce the `HTML` for the `\u003csection\u003e` is editable,\nselect it and copy it.\n\n![todomvc-html-editable-copy](https://user-images.githubusercontent.com/194400/82721711-b05c2b00-9cb7-11ea-85c2-083d2981440d.png)\n\n\nThe `HTML` code is:\n\n\n```html\n\u003csection class=\"todoapp\"\u003e\n  \u003cheader class=\"header\"\u003e\n    \u003ch1\u003etodos\u003c/h1\u003e\n    \u003cinput class=\"new-todo\" placeholder=\"What needs to be done?\" autofocus=\"\" /\u003e\n  \u003c/header\u003e\n  \u003csection class=\"main\" style=\"display: block;\"\u003e\n    \u003cinput id=\"toggle-all\" class=\"toggle-all\" type=\"checkbox\" /\u003e\n    \u003clabel for=\"toggle-all\"\u003eMark all as complete\u003c/label\u003e\n    \u003cul class=\"todo-list\"\u003e\n      \u003cli data-id=\"1590167947253\" class=\"\"\u003e\n        \u003cdiv class=\"view\"\u003e\n          \u003cinput class=\"toggle\" type=\"checkbox\" /\u003e\n          \u003clabel\u003eLearn how to build a Todo list in Phoenix\u003c/label\u003e\n          \u003cbutton class=\"destroy\"\u003e\u003c/button\u003e\n        \u003c/div\u003e\n      \u003c/li\u003e\n      \u003cli data-id=\"1590167956628\" class=\"completed\"\u003e\n        \u003cdiv class=\"view\"\u003e\n          \u003cinput class=\"toggle\" type=\"checkbox\" /\u003e\n          \u003clabel\u003eCompleted item\u003c/label\u003e\n          \u003cbutton class=\"destroy\"\u003e\u003c/button\u003e\n        \u003c/div\u003e\n      \u003c/li\u003e\n    \u003c/ul\u003e\n  \u003c/section\u003e\n  \u003cfooter class=\"footer\" style=\"display: block;\"\u003e\n    \u003cspan class=\"todo-count\"\u003e\u003cstrong\u003e1\u003c/strong\u003e item left\u003c/span\u003e\n    \u003cul class=\"filters\"\u003e\n      \u003cli\u003e\n        \u003ca href=\"#/\" class=\"selected\"\u003eAll\u003c/a\u003e\n      \u003c/li\u003e\n      \u003cli\u003e\n        \u003ca href=\"#/active\"\u003eActive\u003c/a\u003e\n      \u003c/li\u003e\n      \u003cli\u003e\n        \u003ca href=\"#/completed\"\u003eCompleted\u003c/a\u003e\n      \u003c/li\u003e\n    \u003c/ul\u003e\n    \u003cbutton class=\"clear-completed\" style=\"display: block;\"\u003e\n      Clear completed\n    \u003c/button\u003e\n  \u003c/footer\u003e\n\u003c/section\u003e\n```\n\nLet's convert this `HTML` to an Embedded Elixir\n([`EEx`](https://hexdocs.pm/eex/EEx.html)) template.\n\n\n\u003e **Note**: the _reason_ that we are copying this `HTML`\nfrom the browser's Elements inspector\ninstead of _directly_ from the source\non GitHub:\n[`examples/vanillajs/index.html`](https://github.com/tastejs/todomvc/blob/c50cc922495fd76cb44844e3b1cd77e35a5d6be1/examples/vanillajs/index.html#L18)\nis that this is a \"single page app\",\nso the `\u003cul class=\"todo-list\"\u003e\u003c/ul\u003e`\nonly gets populated in the browser.\nCopying it from the browser Dev Tools\nis the easiest way to get the _complete_ `HTML`.\n\n\u003cbr /\u003e\n\n### 3.1 Paste the HTML into `index.html.eex`\n\nOpen the `lib/app_web/controllers/item_html/index.html.eex` file\nand scroll to the bottom.\n\nThen (_without removing the code that is already there_)\npaste the `HTML` code we sourced from TodoMVC.\n\n\u003e e.g:\n[`/lib/app_web/controllers/item_html/index.html.eex#L27-L73`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/8bcc3239d975f5b706514d1a88ea47ca57e5239a/lib/app_web/controllers/item_html/index.html.heex#L27-L73)\n\nIf you attempt to run the app now\nand visit\n[http://localhost:4000/items/](http://localhost:4000/items/) \u003cbr /\u003e\nYou will see this (_without the TodoMVC `CSS`_):\n\n![before-adding-css](https://user-images.githubusercontent.com/17494745/205656501-b3170bc4-c8a7-403f-9db9-2823c839f61e.png)\n\nThat's obviously not what we want,\nso let's get the TodoMVC `CSS`\nand save it in our project!\n\n\u003cbr /\u003e\n\n### 3.2 Save the TodoMVC CSS to `/assets/css`\n\nVisit\nhttp://todomvc.com/examples/vanillajs/node_modules/todomvc-app-css/index.css \u003cbr /\u003e\nand save the file to `/assets/css/todomvc-app.css`.\n\ne.g:\n[`/assets/css/todomvc-app.css`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/assets/css/todomvc-app.css)\n\n\u003cbr /\u003e\n\n### 3.3 Import the `todomvc-app.css` in `app.scss`\n\nOpen the `assets/css/app.scss` file and replace it with the following:\n\n```css\n/* This file is for your main application css. */\n/* @import \"./phoenix.css\"; */\n@import \"./todomvc-app.css\";\n```\n\ne.g:\n[`/assets/css/app.scss#L4`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/8bcc3239d975f5b706514d1a88ea47ca57e5239a/assets/css/app.css#L4)\n\n\u003cbr /\u003e\n\n### 3.4 _Simplify_ The Layout Template\n\nOpen your `lib/app_web/components/layouts/app.html.heex` file\nand replace the contents with the following code:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"utf-8\"/\u003e\n    \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"/\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"/\u003e\n    \u003ctitle\u003ePhoenix Todo List\u003c/title\u003e\n    \u003clink rel=\"stylesheet\" href={~p\"/assets/app.css\"}/\u003e\n    \u003cscript defer type=\"text/javascript\" src={~p\"/assets/app.js\"}\u003e\u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cmain role=\"main\" class=\"container\"\u003e\n      \u003c%= @inner_content %\u003e\n    \u003c/main\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n\n\u003e Before:\n[`lib/app_web/components/layouts/app.html.eex`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/bddacda93ecd892fe0907210bab335e6b6e5e489/lib/app_web/templates/layout/app.html.eex) \u003cbr /\u003e\n\u003e After:\n[`lib/app_web/components/layouts/app.html.heex`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/8bcc3239d975f5b706514d1a88ea47ca57e5239a/lib/app_web/components/layouts/app.html.heex)\n\n`\u003c%= @inner_content %\u003e` is where the Todo App will be rendered.\n\n\u003e **Note**: the `\u003cscript\u003e` tag is included out of convention.\nHowever, we won't be writing _any_ `JavaScript` in this tutorial.\nWe will achieve 100% feature parity with TodoMVC,\nwithout writing a line of `JS`.\nWe don't \"hate\" `JS`,\nin fact we have a \"sister\" tutorial\nthat builds the _same_ App in `JS`:\n[dwyl/javascript-todo-list-tutorial](https://github.com/dwyl/javascript-todo-list-tutorial)\nWe just want to _remind_ you\nthat you don't _need_ any `JS`\nto build a fully functional web application\nwith great UX!\n\n\nWith the layout template saved,\nthe TodoMVC CSS file saved to `/assets/css/todomvc-app.css`\nand the `todomvc-app.css` imported in `app.scss`,\nyour `/items` page should now look like this:\n\n![items-with-todomvc-css](https://user-images.githubusercontent.com/17494745/205660411-7b199ec5-289e-4863-8a67-9c22e9dc78dd.png)\n\n\nSo our Todo List is starting to look like TodoMVC,\nbut it's still just a dummy list.\n\n\n### 4. Render _Real_ Data in the TodoMVC Layout\n\nIn order to render out `item` data\nin the TodoMVC template, \nwe are going to need to add\na few functions. \nWhen we created the project\nand generated the `item` model,\na controller was created \n(located in `lib/app_web/controllers/item_controller.ex`)\nand a component/view as well\n(located in `lib/app_web/controllers/item_html.ex`).\nThis [**Component/View**](https://hexdocs.pm/phoenix/1.7.0-rc.0/components.html)\nis what effectively \ncontrols the rendering of the \ncontents inside the\n`lib/app_web/controllers/item_html`\ndirectory that we tinkered with prior.\n\nWe know that we need make changes to the UI,\nso we are going to add a few functions in this component\n(which is akin to the *View* part of the MVC paradigm).\n\nThis is our first chance to do a bit of Test Driven Development (TDD). \u003cbr /\u003e\nCreate a new file with the path `test/app_web/controllers/item_html_test.exs`.\n\nType the following code into the file:\n\n```elixir\ndefmodule AppWeb.ItemHTMLTest do\n  use AppWeb.ConnCase, async: true\n  alias AppWeb.ItemHTML\n\n  test \"complete/1 returns completed if item.status == 1\" do\n    assert ItemHTML.complete(%{status: 1}) == \"completed\"\n  end\n\n  test \"complete/1 returns empty string if item.status == 0\" do\n    assert ItemHTML.complete(%{status: 0}) == \"\"\n  end\nend\n```\n\ne.g:\n[`/test/app_web/controllers/item_html_test.exs`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/8bcc3239d975f5b706514d1a88ea47ca57e5239a/test/app_web/controllers/item_html_test.exs)\n\n\nIf you attempt to run this test file:\n\n```sh\nmix test test/app_web/controllers/item_html_test.exs\n```\n\nYou will see the following error (because the function does not yet exist!):\n\n```\n** (UndefinedFunctionError) function AppWeb.ItemHTML.checked/1 is undefined or private\n```\n\nOpen the\n`lib/app_web/controllers/item_html.ex` file\nand write the functions to make the tests _pass_.\n\n\u003cbr /\u003e\n\nThis is how we implemented the functions.\nYour `item_html.ex` file \nnow should look like the following.\n\n```elixir\ndefmodule AppWeb.ItemHTML do\n  use AppWeb, :html\n  \n  embed_templates \"item_html/*\"\n\n  # add class \"completed\" to a list item if item.status=1\n  def complete(item) do\n    case item.status do\n      1 -\u003e \"completed\"\n      _ -\u003e \"\" # empty string means empty class so no style applied\n    end\n  end\nend\n```\n\nRe-run the tests and they should now pass:\n\n```sh\nmix test test/app_web/controllers/item_html_test.exs\n```\n\nYou should see:\n\n```sh\n....\n\nFinished in 0.1 seconds\n4 tests, 0 failures\n```\n\n\nNow that we have created these two view functions,\nand our tests are passing,\nlet's _use_ them in our template!\n\nOpen the `lib/app_web/controllers/item_html/index.html.eex` file\nand locate the line:\n\n```html\n\u003cul class=\"todo-list\"\u003e\n```\n\nReplace the _contents_ of the `\u003cul\u003e` with the following:\n\n```html\n\u003c%= for item \u003c- @items do %\u003e\n  \u003cli data-id={item.id} class={complete(item)}\u003e\n    \u003cdiv class=\"view\"\u003e\n      \u003c%= if item.status == 1 do %\u003e\n        \u003cinput class=\"toggle\" type=\"checkbox\" checked/\u003e\n      \u003c% else %\u003e\n        \u003cinput class=\"toggle\" type=\"checkbox\"/\u003e\n      \u003c% end %\u003e\n      \u003clabel\u003e\u003c%= item.text %\u003e\u003c/label\u003e\n      \u003c.link\n        class=\"destroy\"\n        href={~p\"/items/#{item}\"}\n        method=\"delete\"\n      \u003e\n      \u003c/.link\u003e\n    \u003c/div\u003e\n  \u003c/li\u003e\n\u003c% end %\u003e\n```\n\ne.g:\n[`lib/app_web/controllers/item_html/index.html.heex#L43-L53`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/3c04ee39df621cac200b4d3b45ad4045e67e388b/lib/app_web/controllers/item_html/index.html.heex#L36-L56)\n\n\nWith those two files saved,\nif you run the app now: `mix phx.server`\nand visit http://localhost:4000/items. \u003cbr /\u003e\nYou will see the _real_ `items` you created in step 2.2 above:\n\n![todo-list-real-items](https://user-images.githubusercontent.com/17494745/205710983-e079f021-c91d-4be8-9a5e-0a6b0bc85fb8.png)\n\n\nNow that we have our items rendering in the TodoMVC layout,\nlet's work on creating new items in the \"single page app\" style.\n\n\n### 5. In-line the New Item Creation Form\n\nAt present our \"New Item\" form is available at:\nhttp://localhost:4000/items/new\n(_as noted in step 2 above_)\n\nWe want the person to be able to create a new item\nwithout having to navigate to a different page.\nIn order to achieve that goal,\nwe will include the \n`lib/app_web/controllers/item_html/new.html.heex`\ntemplate (_partial_)\ninside the \n`lib/app_web/controllers/item_html/index.html.heex`\ntemplate. e.g:\n\nBefore we can do that, we need to tidy up the `new.html.heex`\ntemplate to remove the fields we don't _need_.\n\nLet's open `lib/app_web/controllers/item_html/new.html.heex`\nand simplify it to just the essential field `:text`:\n\n```elixir\n\u003c.simple_form :let={f} for={@changeset} action={~p\"/items\"}\u003e\n  \u003c.input\n    field={{f, :text}}\n    type=\"text\"\n    placeholder=\"what needs to be done?\"\n  /\u003e\n  \u003c:actions\u003e\n    \u003c.button style=\"display:none\"\u003eSave Item\u003c/.button\u003e\n  \u003c/:actions\u003e\n\u003c/.simple_form\u003e\n```\n\n\u003e Before:\n[`/lib/app_web/controllers/item_html/new.html.heex`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/b158abd7f0c3fbc462a4230f07b5e5e79723a258/app/lib/app_web/controllers/item_html/new.html.heex) \u003cbr /\u003e\n\u003e After:\n[`/lib/app_web/controllers/item_html/new.html.heex`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/8bcc3239d975f5b706514d1a88ea47ca57e5239a/lib/app_web/controllers/item_html/new.html.heex)\n\nWe need to additionally\nchange the style of the `\u003c.input\u003e` tag.\nWith Phoenix, inside the\n`lib/app_web/components/core_components.ex` file,\nthe styles are defined for pre-built components\n(which is the case with `\u003c.input\u003e`).\n\nTo change this so it uses the same style as TodoMVC,\nlocate the following line.\n\n```elixir\ndef input(assigns) do\n```\n\nChange the class attribute\nwith the `new-todo` class.\nThis function should look like the following.\n\n```elixir\n  def input(assigns) do\n    ~H\"\"\"\n    \u003cdiv phx-feedback-for={@name}\u003e\n      \u003c.label for={@id}\u003e\u003c%= @label %\u003e\u003c/.label\u003e\n      \u003cinput\n        type={@type}\n        name={@name}\n        id={@id || @name}\n        value={@value}\n        class={[\n          input_border(@errors),\n          \"new-todo\"\n        ]}\n        {@rest}\n      /\u003e\n      \u003c.error :for={msg \u003c- @errors}\u003e\u003c%= msg %\u003e\u003c/.error\u003e\n    \u003c/div\u003e\n    \"\"\"\n  end\n```\n\nWe also need to change the `actions` styles\ninside the `simple_form`.\nIn the same file, search for `def simple_form(assigns) do`\nand change it so it looks like so:\n\n```elixir\n  def simple_form(assigns) do\n    ~H\"\"\"\n    \u003c.form :let={f} for={@for} as={@as} {@rest}\u003e\n      \u003cdiv\u003e\n        \u003c%= render_slot(@inner_block, f) %\u003e\n        \u003cdiv :for={action \u003c- @actions}\u003e\n          \u003c%= render_slot(action, f) %\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/.form\u003e\n    \"\"\"\n  end\n```\n\n\nIf you run the Phoenix App now and visit\n[http://localhost:4000/items/new](http://localhost:4000/items/new)\nyou will see the _single_ `:text` input field and no \"Save\" button:\n\n![new-item-single-text-field-no-save-button](https://user-images.githubusercontent.com/17494745/205731473-4a9ce3b2-c902-4b66-9bdc-e64165f22841.png)\n\nDon't worry, you can still submit the form with \u003ckbd\u003eEnter\u003c/kbd\u003e (Return) key.\nHowever if you attempt to submit the form now,\nit won't work because we removed two of the fields required by the `changeset`!\nLet's fix that.\n\n\n### 5.1 Update the `items` Schema to Set `default` Values\n\nGiven that we have removed two of the fields (`:person_id` and `:status`)\nfrom the `new.html.eex`,\nwe need to ensure there are default values for these in\nthe schema.\nOpen the `lib/app/todo/item.ex` file\nand replace the contents with the following:\n\n```elixir\ndefmodule App.Todo.Item do\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  schema \"items\" do\n    field :person_id, :integer, default: 0\n    field :status, :integer, default: 0\n    field :text, :string\n\n    timestamps()\n  end\n\n  @doc false\n  def changeset(item, attrs) do\n    item\n    |\u003e cast(attrs, [:text, :person_id, :status])\n    |\u003e validate_required([:text])\n  end\nend\n```\n\nHere we are updating the \"items\" `schema`\nto set a default value of `0`\nfor both `person_id` and `status`.\nAnd in the `changeset/2` we are removing the _requirement_\nfor `person_id` and `status`.\nThat way our new `item` form\ncan be submitted with _just_ the `text` field.\n\n\ne.g:\n[`/lib/app/todo/item.ex#L6-L7`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/cc287975eef5ca8b738f49723fe8a03d9da52a19/lib/app/todo/item.ex#L6-L7)\n\n\nNow that we have `default` values for `person_id` and `status`\nif you submit the `/items/new` form,\nit will succeed.\n\n\n\n### 5.2 Update `index/2` in `ItemController`\n\nIn order to in-line the new item form (`new.html.eex`)\nin the `index.html.eex` template,\nwe need to update the `AppWeb.ItemController.index/2`\nto include a Changeset.\n\nOpen the `lib/app_web/controllers/item_controller.ex` file\nand update the `index/2` function to the following:\n\n```elixir\ndef index(conn, _params) do\n  items = Todo.list_items()\n  changeset = Todo.change_item(%Item{})\n  render(conn, \"index.html\", items: items, changeset: changeset)\nend\n```\n\nBefore:\n[`/lib/app_web/controllers/item_controller.ex`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/031df4076fc4ff84fd719a3a66c6dd2495268a50/lib/app_web/controllers/item_controller.ex) \u003cbr /\u003e\nAfter:\n[`/lib/app_web/controllers/item_controller.ex#L9-L10`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/aed0b2c037ea0cdf5461b2287fc4b63d61cd7b14/lib/app_web/controllers/item_controller.ex#L9-L10)\n\n\nYou will not _see_ any change in the UI or tests after this step.\nJust move on to 5.3 where the \"aha\" moment happens.\n\n\n### 5.3 Render The `new.html.eex` inside `index.html.eex`\n\nNow that we have done all the preparation work,\nthe next step is to render the `new.html.eex` (_partial_)\ninside `index.html.eex` template.\n\nOpen the `lib/app_web/controllers/item_html/index.html.heex`\nfile and locate the line:\n\n```html\n\u003cinput class=\"new-todo\" placeholder=\"What needs to be done?\" autofocus=\"\"\u003e\n```\n\nReplace it with this:\n\n```elixir\n\u003c%= new(Map.put(assigns, :action, ~p\"/items/new\")) %\u003e\n```\n\nLet's break down what we just did.\nWe are **embedding** the `new.html.heex` partial\ninside the `index.html.heex` file.\nWe are doing this by calling the\n`new/2` function inside `item_controller.ex`.\nThis function *pertains* to the page in the URL `items/new`\nand renders the `new.html.heex` file.\nHence why we call this function to successfully embed :smile:.\n\nBefore:\n[`/lib/app_web/controllers/item_html/index.html.heex#L36`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/031df4076fc4ff84fd719a3a66c6dd2495268a50/lib/app_web/templates/item/index.html.eex#L36) \u003cbr /\u003e\nAfter:\n[`/lib/app_web/controllers/item_html/index.html.heex#L36`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/3c04ee39df621cac200b4d3b45ad4045e67e388b/lib/app_web/controllers/item_html/index.html.heex#L30)\n\n\n\nIf you run the app now and visit:\n[http://localhost:4000/items](http://localhost:4000/items) \u003cbr /\u003e\nYou can create an item by typing your text\nand submit it with the \u003ckbd\u003eEnter\u003c/kbd\u003e (Return) key.\n\n\u003cdiv align=\"center\"\u003e\n\n![todo-list-tutorial-step-5](https://user-images.githubusercontent.com/17494745/205904251-8c369d94-f3f9-43e9-b276-4b377e38cdc4.gif)\n\n\u003c/div\u003e\n\nRedirecting to the \"show\" template\nis \"OK\", but we can do better UX by\nredirecting to back to the `index.html` template.\nThankfully this is as easy as updating a single line in the code.\n\n### 5.4 Update the `redirect` in `create/2`\n\nOpen the `lib/app_web/controllers/item_controller.ex` file\nand locate the `create` function.\n_Specifically_ the line:\n\n```elixir\n|\u003e redirect(to: ~p\"/items/#{item}\")\n```\n\nUpdate the line to:\n\n```elixir\n|\u003e redirect(to: ~p\"/items/\")\n```\n\nBefore:\n[`/lib/app_web/controllers/item_controller.ex#L22`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/8bcc3239d975f5b706514d1a88ea47ca57e5239a/lib/app_web/controllers/item_controller.ex#L23) \u003cbr /\u003e\nAfter:\n[`/lib/app_web/controllers/item_controller.ex#L23`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/3c04ee39df621cac200b4d3b45ad4045e67e388b/lib/app_web/controllers/item_controller.ex#L23)\n\nNow when we create a new `item`\nwe are redirected to the `index.html` template:\n\n\u003cdiv align=\"center\"\u003e\n\n![todo-list-tutorial-redirect-to-index](https://user-images.githubusercontent.com/17494745/205917351-5ccdeeed-0015-4bc5-9e67-9bd27f92f14e.gif)\n\n\u003c/div\u003e\n\n### 5.5 Update `item_controller_test.exs` to redirect to `index`\n\nThe changes we've made to the `new.html.heex` files \nand the steps above have broken some of our automated tests.\nWe ought to fix that.\n\nRun the tests:\n\n```sh\nmix test\n```\n\nYou will see the following output:\n\n```\nFinished in 0.08 seconds (0.03s async, 0.05s sync)\n23 tests, 3 failures\n```\n\nOpen the `test/app_web/controllers/item_controller_test.exs` file\nand locate `describe \"new item\"` \nand `describe \"create item\"`.\nChange these two to the following.\n\nReplace the test:\n```elixir\ndescribe \"new item\" do\n  test \"renders form\", %{conn: conn} do\n    conn = get(conn, ~p\"/items/new\")\n    assert html_response(conn, 200) =~ \"what needs to be done?\"\n  end\nend\n\ndescribe \"create item\" do\n  test \"redirects to show when data is valid\", %{conn: conn} do\n    conn = post(conn, ~p\"/items\", item: @create_attrs)\n\n    assert %{} = redirected_params(conn)\n    assert redirected_to(conn) == ~p\"/items/\"\n  end\n\n  test \"errors when invalid attributes are passed\", %{conn: conn} do\n    conn = post(conn, ~p\"/items\", item: @invalid_attrs)\n    assert html_response(conn, 200) =~ \"can\u0026#39;t be blank\"\n  end\nend\n```\n\n\u003e Updated code:\n[`/test/app_web/controllers/item_controller_test.exs#L34-L55`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/0c12a6bec7aeed5562a239d0dc8eea4952250cdd/test/app_web/controllers/item_controller_test.exs#L34-L55)\n\nIf you re-run the tests `mix test` the will now all pass again.\n\n```sh\n......................\nFinished in 0.2 seconds (0.09s async, 0.1s sync)\n22 tests, 0 failures\n```\n\n\u003cbr /\u003e\n\n### 6. Display Count of Items in UI\n\nSo far the main functionality of the TodoMVC UI is working,\nwe can create new items and they appear in our list.\nIn this step we are going to enhance the UI to include\nthe count of remaining items in the bottom left corner.\n\nOpen the `test/app_web/controllers/item_html_test.exs` file\nand create the following two tests:\n\n```elixir\ntest \"remaining_items/1 returns count of items where item.status==0\" do\n  items = [\n    %{text: \"one\", status: 0},\n    %{text: \"two\", status: 0},\n    %{text: \"done\", status: 1}\n  ]\n  assert ItemHTML.remaining_items(items) == 2\nend\n\ntest \"remaining_items/1 returns 0 (zero) when no items are status==0\" do\n  items = []\n  assert ItemHTML.remaining_items(items) == 0\nend\n```\n\ne.g:\n[`test/app_web/controllers/item_html_test.exs#L14-L26`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/32acb54881b8296fcd9cf39666f35b4761c54cb0/test/app_web/controllers/item_html_test.exs#L14-L26)\n\nThese tests will fail because the `ItemHTML.remaining_items/1`\nfunction does not exist.\n\nMake the tests _pass_ by adding the following code to\nthe `lib/app_web/controllers/item_html.ex` file:\n\n```elixir\n# returns integer value of items where item.status == 0 (not \"done\")\ndef remaining_items(items) do\n  Enum.filter(items, fn i -\u003e i.status == 0 end) |\u003e Enum.count\nend\n```\n\ne.g:\n[`/lib/app_web/controllers/item_html#L15-L17`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/lib/app_web/controllers/item_html.ex#L15-L17)\n\nNow that the tests are passing,\n_use_ the `remaining_items/1` in the `index.html` template.\nOpen the `lib/app_web/controllers/item_html/index.html.eex` file\nand locate the line of code:\n\n```html\n\u003cspan class=\"todo-count\"\u003e\u003cstrong\u003e1\u003c/strong\u003e item left\u003c/span\u003e\n```\n\nReplace it with this line:\n```html\n\u003cspan class=\"todo-count\"\u003e\u003c%= remaining_items(@items) %\u003e items left\u003c/span\u003e\n```\n\nThis just invokes the `ItemHTML.remaining_items/1` function\nwith the List of `@items` which will return the integer count\nof remaining items that have not yet been \"done\".\n\nE.g:\n[`/lib/app_web/controllers/item_html/index.html.eex#L60`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/32acb54881b8296fcd9cf39666f35b4761c54cb0/lib/app_web/controllers/item_html/index.html.heex#L60)\n\n\nAt this point the (remaining) items counter\nin the bottom left of the TodoMVC UI is _working_! \u003cbr /\u003e\nAdd a `new` item to your list and watch the count increase:\n\n![item-count-increases-to-2](https://user-images.githubusercontent.com/17494745/205924061-36d9bd5a-7884-4ca7-8237-669a97a73e75.gif)\n\nThat was easy enough let's try something a bit more advanced! \u003cbr /\u003e\nTake a break and grab yourself a fresh glass of water,\nthe next section is going be _intense_!\n\n\u003cbr /\u003e\n\n### 7. Toggle a Todo Item's `status` to `1`\n\nOne of the core functions of a Todo List is\ntoggling the `status` of an `item` from `0` to `1` (\"complete\"). \u003cbr /\u003e\nIn our schema a completed `item`\nhas the `status` of `1`.\n\n\n### 7.1 Create the Controller Tests\n\nWe are going to need two functions in our controller:\n1. `toggle_status/1` toggles the status of an item\ne.g: 0 to 1 and 1 to 0.\n2. `toggle/2` the handler function for HTTP requests\nto toggle the status of an item.\n\nOpen the `test/app_web/controllers/item_controller_test.exs` file.\nWe are going to make some changes here\nso we can add tests to the functions we\nmentioned prior.\nWe are going to import `App.Todo` \ninside `item_controller_test.exs` \nand fix create and attribute constants \nto create mock items.\nMake sure the beginning of the \nfile looks like so.\n\n```elixir\ndefmodule AppWeb.ItemControllerTest do\n  use AppWeb.ConnCase\n  alias App.Todo\n\n  import App.TodoFixtures\n\n  @create_attrs %{person_id: 42, status: 0, text: \"some text\"}\n  @public_create_attrs %{person_id: 0, status: 0, text: \"some public text\"}\n  @completed_attrs %{person_id: 42, status: 1, text: \"some text completed\"}\n  @public_completed_attrs %{person_id: 0, status: 1, text: \"some public text completed\"}\n  @update_attrs %{person_id: 43, status: 1, text: \"some updated text\"}\n  @invalid_attrs %{person_id: nil, status: nil, text: nil}\n```\n\nWe are adding fixed `Item` attributes\nto later be used in tests.\nWe are specifying `public` `Item`s \nbecause we will later add\n*authentication* to this app.\n\nAfter this, locate `defp create_item()/1`\nfunction inside the same file. \nChange it so it looks like so.\n\n```elixir\n  defp create_item(_) do\n    item = item_fixture(@create_attrs)\n    %{item: item}\n  end\n```\n\nWe are going to be using this function\nto create `Item` objects \nto use in the tests we are going to add.\nSpeaking of which, let's do that!\nAdd the following snippet to the file.\n\n```elixir\ndescribe \"toggle updates the status of an item 0 \u003e 1 | 1 \u003e 0\" do\n  setup [:create_item]\n\n  test \"toggle_status/1 item.status 1 \u003e 0\", %{item: item} do\n    assert item.status == 0\n    # first toggle\n    toggled_item = %{item | status: AppWeb.ItemController.toggle_status(item)}\n    assert toggled_item.status == 1\n    # second toggle sets status back to 0\n    assert AppWeb.ItemController.toggle_status(toggled_item) == 0\n  end\n\n  test \"toggle/2 updates an item.status 0 \u003e 1\", %{conn: conn, item: item} do\n    assert item.status == 0\n    get(conn, ~p'/items/toggle/#{item.id}')\n    toggled_item = Todo.get_item!(item.id)\n    assert toggled_item.status == 1\n  end\nend\n```\n\ne.g:\n[`/test/app_web/controllers/item_controller_test.exs#L64-L82`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/test/app_web/controllers/item_controller_test.exs#L64-L82)\n\n\u003cbr /\u003e\n\n### 7.2 Create the Functions to Make Tests Pass\n\nOpen the\n`lib/app_web/controllers/item_controller.ex`\nfile and add the following functions to it:\n\n```elixir\n  def toggle_status(item) do\n    case item.status do\n      1 -\u003e 0\n      0 -\u003e 1\n    end\n  end\n\n  def toggle(conn, %{\"id\" =\u003e id}) do\n    item = Todo.get_item!(id)\n    Todo.update_item(item, %{status: toggle_status(item)})\n    conn\n    |\u003e redirect(to: ~p\"/items\")\n  end\n```\n\ne.g:\n[`/lib/app_web/controllers/item_controller.ex#L64-L76`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/bbaa6cad585bff76602e7f3cea6a43b8a1f08cbb/lib/app_web/controllers/item_controller.ex#L64-L76)\n\nThe tests will still _fail_ at this point because\nthe route we are invoking in our test does not yet exist.\nLet's fix that!\n\n\u003cbr /\u003e\n\n### 7.3 Create `get /items/toggle/:id` Route that Invokes `toggle/2`\n\nOpen the `lib/app_web/router.ex`\nand locate the line `resources \"/items\", ItemController`.\nAdd a new line:\n\n```elixir\nget \"/items/toggle/:id\", ItemController, :toggle\n```\n\ne.g:\n[`/lib/app_web/router.ex#L21`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/bbaa6cad585bff76602e7f3cea6a43b8a1f08cbb/lib/app_web/router.ex#L21)\n\nNow our tests will _finally_ pass:\n\n```sh\nmix test\n```\n\nYou should see:\n```sh\n22:39:42.231 [info]  Already up\n...........................\n\nFinished in 0.5 seconds\n27 tests, 0 failures\n```\n\n\n\u003cbr /\u003e\n\n### 7.4 Invoke the `toggle/2` When a Checkbox is clicked in `index.html`\n\nNow that our tests are passing,\nit's time actually _use_ all this functionality we have been building\nin the UI.\nOpen the `/lib/app_web/controllers/item_html/index.html.heex` file\nand locate the line:\n\n```html\n\u003c%= if item.status == 1 do %\u003e\n...\n\u003c% else %\u003e\n...\n\u003c% end %\u003e\n```\n\nReplace it with the following:\n\n```html\n  \u003c%= if item.status == 1 do %\u003e\n    \u003c.link href={~p\"/items/toggle/#{item.id}\"}\n        class=\"toggle checked\"\u003e\n        type=\"checkbox\"\n    \u003c/.link\u003e\n  \u003c% else %\u003e\n    \u003c.link href={~p\"/items/toggle/#{item.id}\"}\n        type=\"checkbox\"\n        class=\"toggle\"\u003e\n    \u003c/.link\u003e\n  \u003c% end %\u003e\n```\n\nWhen this link is clicked\nthe `get /items/toggle/:id` endpoint is invoked, \u003cbr /\u003e\nthat in turn triggers the `toggle/2` handler\nwe defined above.\n\n\n\u003e Before:\n[`/lib/app_web/controllers/item_html/index.html.heex#L40`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/32acb54881b8296fcd9cf39666f35b4761c54cb0/lib/app_web/controllers/item_html/index.html.heex#L40) \u003cbr /\u003e\n\u003e After:\n[`/lib/app_web/controllers/item_html/index.html.heex#L47-L57`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/3329d8f2272d01641ed74c6b10adc957821bc81f/lib/app_web/controllers/item_html/index.html.heex#L47-L57)\n\n\n\n### 7.5 Add a `.checked` CSS to `app.scss`\n\n\nUnfortunately, `\u003ca\u003e` tags (that are generated with `\u003c.link\u003e`)\ncannot have a `:checked` pseudo selector,\nso the default TodoMVC styles that worked on the `\u003cinput\u003e` tag\nwill not work for the link.\nSo we need to add a couple of lines of CSS to our `app.scss`.\n\nOpen the `assets/css/app.scss` file and add the following lines to it:\n\n```css\n.todo-list li .checked + label {\n\tbackground-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');\n\tbackground-repeat: no-repeat;\n}\n```\n\nAfter saving the file you should have:\n[`/assets/css/app.scss#L8`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/assets/css/app.css#L8)\n\nAnd when you view the app,\nthe Toggle functionality is working as expected:\n\n![todo-app-toggle](https://user-images.githubusercontent.com/17494745/205961019-141d4488-3856-4c1e-b846-6ef52252c7d1.gif)\n\n\n**Implementation Note**: we are very deliberately\n_not_ using an `JavaScript` in this tutorial\nbecause we are demonstrating how to do a 100% server-side rendered App.\nThis _always_ works even when `JS` is disabled in the browser\nor the device is super old and does not have a modern web browser.\nWe could easily have added an `onclick` attribute to the `\u003cinput\u003e` tag,\ne.g:\n\n```html\n\u003cinput \u003c%= checked(item) %\u003e type=\"checkbox\" class=\"toggle\"\nonclick=\"location.href='\n  \u003c%= Routes.item_path(@conn, :toggle, item.id) %\u003e';\"\u003e\n```\n\nBut `onclick` is `JavaScript` and we don't _need_ to resort to `JS`. \u003cbr /\u003e\nThe `\u003ca\u003e` (link) is a perfectly semantic non-js approach to toggling\n`item.status`.\n\n### 7.6 Maintaining correct order of `todo` items\n\nIf you \"complete\" or revert the operation,\nthe order of the todos might differ between\nthese operations.\nTo keep this consistent, \nlet's fetch all the `todo` items in the same order.\n\nInside `lib/app/todo.ex`, \nchange `list_items/0` to the following.\n\n```elixir\n  def list_items do\n    query =\n      from(\n        i in Item,\n        select: i,\n        order_by: [asc: i.id]\n      )\n\n    Repo.all(query)\n  end\n```\n\nBy fetching the `todo` items and ordering them,\nwe guarantee the UX stays consistent! \n\n\n\u003cbr /\u003e\n\n### 8. _Edit_ an Item!\n\nThe final piece of _functionality_\nwe need to add to our UI\nis the ability to _edit_ an item's text.\n\nAt the end of this step you will have in-line editing working:\n\n![phoenix-todo-item-inline-editing](https://user-images.githubusercontent.com/194400/83204049-bf712c00-a142-11ea-8560-d68cf79a4fea.gif)\n\n\n### 8.1 Double-Click Item Text to Edit\n\nThe _reason_ for requiring two clicks to edit an item,\nis so that people don't _accidentally_ edit an item while scrolling.\nSo they have to deliberately click/tap _twice_ in order to edit.\n\nIn the TodoMVC spec this is achieved\nby creating an event listener for the double-click event\nand replacing the `\u003clabel\u003e` element with an `\u003cinput\u003e`.\nWe are trying to _avoid_ using `JavaScript`\nin our server-side rendered Phoenix App (_for now_),\nso we want to use an alternative approach.\nThankfully we can simulate the double-click event\nusing just `HTML` and `CSS`.\nsee: https://css-tricks.com/double-click-in-css\n(_we recommend reading that post and the Demo\n  to fully understand how this CSS works_!)\n\n\u003e **Note**: the CSS implementation is not a _true_ double-click,\na more accurate description would be \"two click\"\nbecause the two clicks can occur with an arbitrary delay.\ni.e. first click followed by 10sec wait and second click\nwill have the same effect as two clicks in quick succession.\nIf you want to implement true double-click,\nsee:\n[github.com/dwyl/javascript-todo-list-tutorial#52-double-click](https://github.com/dwyl/javascript-todo-list-tutorial/tree/e6736add9df1f46035f8a9d1dbdc14c71a7cdb41#52-double-click-item-label-to-edit)\n\nLet's get on with it!\nOpen the\n`lib/app_web/controllers/item_html/index.html.heex`\nfile and locate the line:\n\n```elixir\n\u003c%= new(Map.put(assigns, :action, ~p\"/items/new\")) %\u003e\n```\n\nReplace it with:\n\n```elixir\n\u003c%= if @editing.id do %\u003e\n  \u003c.link href={~p\"/items\"}\n      method=\"get\"\n      class=\"new-todo\"\u003e\n      Click here to create a new item!\n  \u003c/.link\u003e\n\u003c% else %\u003e\n  \u003c%= new(Map.put(assigns, :action, ~p\"/items/new\")) %\u003e\n\u003c% end %\u003e\n```\n\nIn here, we are checking if we are editing an item,\nand rendering a link instead of the form.\nWe do this to avoid having multiple forms on the page.\nIf we are _not_ editing an item,\nrender the `new.html.heex` as before.\nWith this, if the user is editing an item,\nhe is able to \"get out of editing mode\"\nby clicking on the link that is rendered.\n\ne.g:\n[`lib/app_web/controllers/item_html/index.html.heex#L30-L38`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/3329d8f2272d01641ed74c6b10adc957821bc81f/lib/app_web/controllers/item_html/index.html.heex#L30-L38)\n\nNext, still in the `index.html.eex` file,\nlocate the line:\n\n```html\n\u003c%= for item \u003c- @items do %\u003e\n```\n\nReplace the entire `\u003cli\u003e` tag\nwith the following code.\n\n```elixir\n\u003cli data-id={item.id} class={complete(item)}\u003e\n    \u003c%= if item.status == 1 do %\u003e\n      \u003c.link href={~p\"/items/toggle/#{item.id}\"}\n          class=\"toggle checked\"\u003e\n          type=\"checkbox\"\n      \u003c/.link\u003e\n    \u003c% else %\u003e\n      \u003c.link href={~p\"/items/toggle/#{item.id}\"}\n          type=\"checkbox\"\n          class=\"toggle\"\u003e\n      \u003c/.link\u003e\n    \u003c% end %\u003e\n\n  \u003cdiv class=\"view\"\u003e\n    \u003c%= if item.id == @editing.id do %\u003e\n      \u003c%= edit(\n        Map.put(assigns, :action, ~p\"/items/#{item.id}/edit\")\n        |\u003e Map.put(:item, item)\n      ) %\u003e\n    \u003c% else %\u003e\n      \u003c.link href={~p\"/items/#{item}/edit\"} class=\"dblclick\"\u003e\n        \u003clabel\u003e\u003c%= item.text %\u003e\u003c/label\u003e\n      \u003c/.link\u003e\n      \u003cspan\u003e\u003c/span\u003e \u003c!-- used for CSS Double Click --\u003e\n    \u003c% end %\u003e\n\n    \u003c.link\n      class=\"destroy\"\n      href={~p\"/items/#{item}\"}\n      method=\"delete\"\n    \u003e\n    \u003c/.link\u003e\n  \u003c/div\u003e\n\u003c/li\u003e\n```\n\ne.g:\n[`lib/app_web/controllers/item_html/index.html.heex#L46-L79`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/3329d8f2272d01641ed74c6b10adc957821bc81f/lib/app_web/controllers/item_html/index.html.heex#L46-L79)\n\nWe have done a few things here.\nWe changed the toggle button outside the \n`\u003cdiv class=\"view\u003e` tag.\nAdditionally, we have changed the text\nwith a `if else` block statements.\n\nIf the user is not editing,\na link (`\u003ca\u003e`) is rendered which, \nwhen clicked, allows the user to enter \"edit\" mode.\nOn the other hand, if the user *is editing*,\nit renders the `edit.html.heex` file.\n\nSpeaking of which, let's edit `edit.html.heex`\nso it renders what we want:\na text field that, once `Enter` is pressed,\nedits the referring todo item.\n\n```html\n\u003c.simple_form :let={f} for={@changeset} method=\"put\" action={~p\"/items/#{@item}\"}\u003e\n  \u003c.input\n    field={{f, :text}}\n    type=\"text\"\n    placeholder=\"what needs to be done?\"\n    class=\"new-todo\"\n  /\u003e\n  \u003c:actions\u003e\n    \u003c.button\n    style=\"display: none;\"\n    type=\"submit\"\u003e\n      Save\n    \u003c/.button\u003e\n  \u003c/:actions\u003e\n  \u003c!-- submit the form using the Return/Enter key --\u003e\n\u003c/.simple_form\u003e\n```\n\n\u003cbr /\u003e\n\n### 8.2 Update `CSS` For Editing\n\nTo enable the CSS double-click effect\nto enter `edit` mode,\nwe need to add the following CSS\nto our `assets/css/app.scss` file:\n\n```css\n.dblclick {\n  position: relative; /* So z-index works later, but no surprises now */\n}\n\n.dblclick + span {\n  position: absolute;\n  top: -1px; /* these negative numbers are to ensure */\n  left: -1px; /* that the \u003cspan\u003e covers the \u003ca\u003e */\n  width: 103%; /* Gotta do this instead of right: 0; */\n  bottom: -1px;\n  z-index: 1;\n}\n\n.dblclick + span:active {\n  left: -9999px;\n}\n\n.dblclick:hover {\n  z-index: 2;\n}\n```\n\ne.g:\n[`assets/css/app.css#L13-L32`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/assets/css/app.css#L13-L32)\n\n\nAdditionally, since our markup is _slightly_ different\nto the TodoMVC markup, we need to add a bit more CSS\nto keep the UI consistent:\n\n```css\n.todo-list li .toggle + div \u003e a \u003e label {\n  background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');\n  background-repeat: no-repeat;\n  background-position: center left;\n}\n\n.todo-list li .checked + div \u003e a \u003e label\n{\n  background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');\n  background-repeat: no-repeat;\n  background-position: center left;\n}\n\n.toggle {\n  width: 10%;\n  z-index: 3; /* keep the toggle checkmark above the rest */\n}\n\na.new-todo {\n  display: block;\n  text-decoration: none;\n}\n\n.todo-list .new-todo {\n  border: 1px #1abc9c solid;\n}\n\n.view a, .view a:visited {\n  display: block;\n  text-decoration: none;\n  color: #2b2d2f;\n}\n\n.todo-list li .destroy {\n  text-decoration: none;\n  text-align: center;\n  z-index: 3; /* keep the delete link above the text */\n}\n```\n\nThis is what your `app.scss` file should look like\nat the end of this step:\n[`assets/css/app.css#L34-L71`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/assets/css/app.css#L34-L71)\n\n\u003cbr /\u003e\n\n### 8.3 Update the `ItemController.edit/2` Function\n\nIn order to enable in-line editing,\nwe need to modify the `edit/2` function.\nOpen the `lib/app_web/controllers/item_controller.ex` file\nand replace the `edit/2` function with the following:\n\n```elixir\ndef edit(conn, params) do\n  index(conn, params)\nend\n```\n\nAdditionally, given that we are asking our `index/2` function\nto handle editing, we need to update `index/2`:\n\n```elixir\ndef index(conn, params) do\n  item = if not is_nil(params) and Map.has_key?(params, \"id\") do\n    Todo.get_item!(params[\"id\"])\n  else\n    %Item{}\n  end\n  items = Todo.list_items()\n  changeset = Todo.change_item(item)\n  render(conn, \"index.html\", items: items, changeset: changeset, editing: item)\nend\n```\n\nFinally, we need to handle the form submission\nto update an item (that is rendered in `edit.html.heex`).\nWhen we press `Enter`, the `update/2` handler is called\ninside `lib/app_web/controllers/item_controller.ex`.\nWe want to stay on the same page after updating the item.\n\nSo,change it so it looks like this.\n\n```elixir\ndef update(conn, %{\"id\" =\u003e id, \"item\" =\u003e item_params}) do\n  item = Todo.get_item!(id)\n\n  case Todo.update_item(item, item_params) do\n    {:ok, _item} -\u003e\n      conn\n      |\u003e redirect(to: ~p\"/items/\")\n\n    {:error, %Ecto.Changeset{} = changeset} -\u003e\n      render(conn, :edit, item: item, changeset: changeset)\n  end\nend\n```\n\nYour `item_controller.ex` file should now look like this:\n[`lib/app_web/controllers/item_controller.ex`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/3329d8f2272d01641ed74c6b10adc957821bc81f/lib/app_web/controllers/item_controller.ex)\n\n\u003cbr /\u003e\n\n### 8.4 Update the Tests in `ItemControllerTest`\n\nIn our quest to build a _Single_ Page App,\nwe broke a few tests! That's OK. \nThey're easy to fix.\n\nOpen the `test/app_web/controllers/item_controller_test.exs`\nfile and locate the test with the following text.\n\n`test \"renders form for editing chosen item\"`\n\nand change it so it looks like the following.\n\n```elixir\n  test \"renders form for editing chosen item\", %{conn: conn, item: item} do\n    conn = get(conn, ~p\"/items/#{item}/edit\")\n    assert html_response(conn, 200) =~ \"Click here to create a new item\"\n  end\n```\n\nWhen we enter the \"edit timer mode\",\nwe create `\u003ca\u003e` a link to return to `/items`,\nas we have previously implemented.\nThis tag has the \"Click here to create a new item\" text,\nwhich is what we are asserting.\n\ne.g:\n[`test/app_web/controllers/item_controller_test.exs#L37-L39`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/3d5d839d6053c3f6ac5140459a4c3c010d45b195/test/app_web/controllers/item_controller_test.exs#L37-L39)\n\n\nNext, locate the test with the following description:\n\n```elixir\ndescribe \"update item\"\n```\n\nUpdate the block to the following\npiece of code.\n\n```elixir\ndescribe \"update item\" do\n  setup [:create_item]\n\n  test \"redirects when data is valid\", %{conn: conn, item: item} do\n    conn = put(conn, ~p\"/items/#{item}\", item: @update_attrs)\n    assert redirected_to(conn) == ~p\"/items/\"\n\n    conn = get(conn, ~p\"/items/\")\n    assert html_response(conn, 200) =~ \"some updated text\"\n  end\n\n  test \"errors when invalid attributes are passed\", %{conn: conn, item: item} do\n    conn = put(conn, ~p\"/items/#{item}\", item: @invalid_attrs)\n    assert html_response(conn, 200) =~ \"can\u0026#39;t be blank\"\n  end\nend\n```\n\ne.g:\n[`test/app_web/controllers/item_controller_test.exs#L67-L80`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/0c12a6bec7aeed5562a239d0dc8eea4952250cdd/test/app_web/controllers/item_controller_test.exs#L67-L80)\n\nWe've updated the paths the application redirects to\nafter updating an item. \nSince we are building a single-page application,\nthat path pertains to the `/items/` URL path.\n\nIf you run the tests now, they should pass again:\n\n```\nmix test\n\n23:08:01.785 [info]  Already up\n...........................\n\nFinished in 0.5 seconds\n27 tests, 0 failures\n\nRandomized with seed 956565\n```\n\n\u003cbr /\u003e\n\n### 8.5 Remove Old Template from `index.html`\n\nNow that we have the `toggle` and `edit` features working,\nwe can finally remove the default Phoenix (table) layout\nfrom the `lib/app_web/controllers/item_html/index.html.heex` template.\n\n\u003cimg width=\"872\" alt=\"phoenix-todo-list-table-layout\" src=\"https://user-images.githubusercontent.com/194400/83200932-54245b80-a13c-11ea-92a3-6b55fc2b2652.png\"\u003e\n\nOpen the `lib/app_web/controllers/item_html/index.html.eex` file\nand remove all code before the line:\n```html\n\u003csection class=\"todoapp\"\u003e\n```\n\ne.g:\n[`lib/app_web/controllers/item_html/index.html.heex`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/3d5d839d6053c3f6ac5140459a4c3c010d45b195/lib/app_web/controllers/item_html/index.html.heex)\n\nYour app should now look like this:\n![phoenix-todo-app-without-default-table-layout](https://user-images.githubusercontent.com/194400/83201568-afa31900-a13d-11ea-9511-aadb5988cc23.png)\n\nUnfortunately, by removing the default layout,\nwe have \"broken\" the tests.\n\nOpen the\n`test/app_web/controllers/item_controller_test.exs`\nfile and locate the test\nthat has the following description:\n\n```elixir\ntest \"lists all items\"\n```\n\nUpdate the assertion from:\n\n```elixir\nassert html_response(conn, 200) =~ \"Listing Items\"\n```\n\nTo:\n\n```elixir\nassert html_response(conn, 200) =~ \"todos\"\n```\n\ne.g:\n[`test/app_web/controllers/item_controller_test.exs#L14`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/3d5d839d6053c3f6ac5140459a4c3c010d45b195/test/app_web/controllers/item_controller_test.exs#L14)\n\n\u003cbr /\u003e\n\n### 9. Footer Navigation\n\nNow that the core (create, edit/update, delete) functionality is working,\nwe can add the final UI enhancements.\nIn this step we are going to add the footer navigation/filtering.\n\n\u003cimg width=\"581\" alt=\"phoenix-todo-footer-navigation\" src=\"https://user-images.githubusercontent.com/194400/83204791-96ea3180-a144-11ea-954b-499a4348ef32.png\"\u003e\n\nThe \"All\" view is the default.\nThe \"Active\" is all the items with `status==0`.\n\"Completed\" is all items with `status==1`.\n\n\u003cbr /\u003e\n\n### 9.1 Create `/:filter` Route\n\nBefore starting, \nlet's add a unit test.\nWe want to show filtered items\naccording to the filter chosen.\n\nOpen `test/app_web/controllers/item_controller_test.exs`\nand locate `describe \"index\" do`.\nIn this block, add the following test.\nIt checks if the item is properly being shown\nwhen the filter is changed.\n\n```elixir\n  test \"lists items in filter\", %{conn: conn} do\n    conn = post(conn, ~p\"/items\", item: @public_create_attrs)\n\n    # After creating item, navigate to 'active' filter page\n    conn = get(conn, ~p\"/items/filter/active\")\n    assert html_response(conn, 200) =~ @public_create_attrs.text\n\n    # Navigate to 'completed page'\n    conn = get(conn, ~p\"/items/filter/completed\")\n    assert !(html_response(conn, 200) =~ @public_create_attrs.text)\n  end\n```\n\ne.g:\n[`test/app_web/controllers/item_controller_test.exs#L21-L32`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/0c12a6bec7aeed5562a239d0dc8eea4952250cdd/test/app_web/controllers/item_controller_test.exs#L21-L32)\n\nOpen the `lib/app_web/router.ex` and\nadd the following route:\n\n```elixir\nget \"/items/filter/:filter\", ItemController, :index\n```\n\ne.g:\n[`/lib/app_web/router.ex#L23`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/lib/app_web/router.ex#L23)\n\n\u003cbr /\u003e\n\n### 9.2 Update the Controller `index/2` to send `filter` to View/Template\n\nOpen the `lib/app_web/controllers/item_controller.ex` file\nand locate the `index/2` function.\nReplace the invocation of `render/3` at the end of `index/2`\nwith the following:\n\n```elixir\nrender(conn, \"index.html\",\n  items: items,\n  changeset: changeset,\n  editing: item,\n  filter: Map.get(params, \"filter\", \"all\")\n)\n```\n\ne.g:\n[`lib/app_web/controllers/item_controller.ex#L17-L22`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/lib/app_web/controllers/item_controller.ex#L17-L22)\n\n`Map.get(params, \"filter\", \"all\")`\nsets the default value of our `filter` to \"all\"\nso when `index.html` is rendered, show \"all\" items.\n\n\u003cbr /\u003e\n\n### 9.3 Create `filter/2` View Function\n\nIn order to filter the items by their status,\nwe need to create a new function. \u003cbr /\u003e\nOpen the `lib/app_web/controllers/item_html.ex` file\nand create the `filter/2` function as follows:\n\n```elixir\ndef filter(items, str) do\n  case str do\n    \"items\" -\u003e items\n    \"active\" -\u003e Enum.filter(items, fn i -\u003e i.status == 0 end)\n    \"completed\" -\u003e Enum.filter(items, fn i -\u003e i.status == 1 end)\n    _ -\u003e items\n  end\nend\n```\n\ne.g:\n[`lib/app_web/controllers/item_html.ex#L19-L26`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/50cce48a72d27b52cfeae158e3191e3cd1a8fe87/lib/app_web/controllers/item_html.ex#L19-L26)\n\nThis will allow us to filter the items in the next step.\n\n\u003cbr /\u003e\n\n### 9.4 Update the Footer in the `index.html` Template\n\nUse the `filter/2` function to filter the items that are displayed.\nOpen the `lib/app_web/controllers/item_html/index.html.heex` file\nand locate the `for` loop line:\n\n```elixir\n\u003c%= for item \u003c- @items do %\u003e\n```\n\nReplace it with:\n\n```elixir\n\u003c%= for item \u003c- filter(@items, @filter) do %\u003e\n```\ne.g:\n[`lib/app_web/controllers/item_html/index.html.heex#L18`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/50cce48a72d27b52cfeae158e3191e3cd1a8fe87/lib/app_web/controllers/item_html/index.html.heex#L18)\n\nThis invokes the `filter/2` function we defined in the previous step\npassing in the list of `@items` and the selected `@filter`.\n\n\nNext, locate the the `\u003cfooter\u003e`\nand replace the _contents_\nof the\n`\u003cul class=\"filters\"\u003e`\nwith the following code:\n\n```elixir\n  \u003cli\u003e\n    \u003c%= if @filter == \"items\" do %\u003e\n      \u003ca href=\"/items/filter/items\" class=\"selected\"\u003e\n        All\n      \u003c/a\u003e\n    \u003c% else %\u003e\n      \u003ca href=\"/items/filter/items\"\u003e\n        All\n      \u003c/a\u003e\n    \u003c% end %\u003e\n  \u003c/li\u003e\n  \u003cli\u003e\n    \u003c%= if @filter == \"active\" do %\u003e\n      \u003ca href=\"/items/filter/active\" class='selected'\u003e\n        Active\n        [\u003c%= Enum.count(filter(@items, \"active\")) %\u003e]\n      \u003c/a\u003e\n    \u003c% else %\u003e\n      \u003ca href=\"/items/filter/active\"\u003e\n        Active\n        [\u003c%= Enum.count(filter(@items, \"active\")) %\u003e]\n      \u003c/a\u003e\n    \u003c% end %\u003e\n  \u003c/li\u003e\n  \u003cli\u003e\n    \u003c%= if @filter == \"completed\" do %\u003e\n      \u003ca href=\"/items/filter/completed\" class='selected'\u003e\n        Completed\n        [\u003c%= Enum.count(filter(@items, \"completed\")) %\u003e]\n      \u003c/a\u003e\n    \u003c% else %\u003e\n      \u003ca href=\"/items/filter/completed\"\u003e\n        Completed\n        [\u003c%= Enum.count(filter(@items, \"completed\")) %\u003e]\n      \u003c/a\u003e\n    \u003c% end %\u003e\n  \u003c/li\u003e\n```\n\nWe are conditionally adding the `selected` class\naccording to the `@filter` assign value. \n\ne.g:\n[`/lib/app_web/controllers/item_html/index.html.heex#L62-L98`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/50cce48a72d27b52cfeae158e3191e3cd1a8fe87/lib/app_web/controllers/item_html/index.html.heex#L62-L98)\n\nAt the end of this step you will have a\nfully functioning footer filter:\n\n![phoenix-todo-footer-nav](https://user-images.githubusercontent.com/194400/83212591-ea19af80-a157-11ea-9e0b-502af5cb61b2.gif)\n\nWe can quickly cover this function we added\nwith a small unit test.\nOpen `test/app_web/controllers/item_html_test.exs`\nand add the following.\n\n```elixir\n  test \"test filter function\" do\n    items = [\n      %{text: \"one\", status: 0},\n      %{text: \"two\", status: 0},\n      %{text: \"three\", status: 1},\n      %{text: \"four\", status: 2},\n      %{text: \"five\", status: 2},\n      %{text: \"six\", status: 1},\n    ]\n\n    assert length(ItemHTML.filter(items, \"items\")) == 4\n    assert length(ItemHTML.filter(items, \"active\")) == 2\n    assert length(ItemHTML.filter(items, \"completed\")) == 2\n    assert length(ItemHTML.filter(items, \"any\")) == 4\n  end\n```\n\nAnd you should be done with this feature 😀.\nAwesome job!\n\n\u003cbr /\u003e\n\n### 10. Clear Completed\n\nWe are _almost_ done with our Phoenix implementation of TodoMVC.\nThe last thing to implement is \"clear completed\".\n\nOpen your `lib/app_web/router.ex` file\nand add the following route:\n\n```elixir\nget \"/items/clear\", ItemController, :clear_completed\n```\n\nYour `scope \"/\"` should now look like the following:\n\n```elixir\n  scope \"/\", AppWeb do\n    pipe_through :browser\n\n    get \"/\", PageController, :home\n    get \"/items/toggle/:id\", ItemController, :toggle\n    get \"/items/clear\", ItemController, :clear_completed\n    get \"/items/filter/:filter\", ItemController, :index\n    resources \"/items\", ItemController\n  end\n```\n\nIn the `lib/app_web/controllers/item_controller.ex`\nfile add the following code:\n\n```elixir\nimport Ecto.Query\nalias App.Repo\n\ndef clear_completed(conn, _param) do\n  person_id = 0\n  query = from(i in Item, where: i.person_id == ^person_id, where: i.status == 1)\n  Repo.update_all(query, set: [status: 2])\n  # render the main template:\n  index(conn, %{filter: \"all\"})\nend\n```\n\ne.g:\n[`lib/app_web/controllers/item_controller.ex#L87-L93`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/lib/app_web/controllers/item_controller.ex#L87-L93)\n\nThis uses the handy\n[`update_all/3`](https://hexdocs.pm/ecto/2.0.0-rc.5/Ecto.Repo.html#c:update_all/3)\nfunction to update all items that match the `query`.\nIn our case we searching for all `items`\nthat belong to `person_id==0`\nand have `status==1`.\n\nWe are not _deleting_ the items,\nrather we are updating their status to `2`\nwhich for the purposes of our example means they are \"archived\".\n\n\u003e **Note**: This is a useful guide to `update_all`:\nhttps://adamdelong.com/bulk-update-ecto\n\n\nFinally, in the `lib/app_web/controllers/item_html/index.html.eex`\nscroll to the bottom of the file and replace the line:\n\n```elixir\n\u003cbutton class=\"clear-completed\" style=\"display: block;\"\u003e\n  Clear completed\n\u003c/button\u003e\n```\n\nWith:\n\n```elixir\n\u003ca class=\"clear-completed\" href=\"/items/clear\"\u003e\n  Clear completed\n  [\u003c%= Enum.count(filter(@items, \"completed\")) %\u003e]\n\u003c/a\u003e\n```\n\ne.g:\n[`lib/app_web/controllers/item_html/index.html.heex#L104-L107`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/lib/app_web/controllers/item_html/index.html.heex#L104-L107)\n\nThe last thing we need to do is to \nupdate the `filter/2` function \ninside `lib/app_web/controllers/item_html.ex`.\nSince `status = 2` now pertains to an **archived** state,\nwe want to return anything that is not archived.\n\nChange the `filter/2` function so it looks like so.\n\n```elixir\ndef filter(items, str) do\n  case str do\n    \"items\" -\u003e Enum.filter(items, fn i -\u003e i.status !== 2 end)\n    \"active\" -\u003e Enum.filter(items, fn i -\u003e i.status == 0 end)\n    \"completed\" -\u003e Enum.filter(items, fn i -\u003e i.status == 1 end)\n    _ -\u003e Enum.filter(items, fn i -\u003e i.status !== 2 end)\n  end\nend\n```\n\nAt the end of this section your Todo List\nshould have the \"Clear completed\" function working:\n\n![phoenix-todo-clear-completed](https://user-images.githubusercontent.com/194400/83244744-a3e44080-a197-11ea-90b3-5420f98bbd55.gif)\n\nIt's useful to have tests cover this feature.\nOpen `test/app_web/controllers/item_controller_test.exs`.\nAlongside the constants, on top of the file,\nadd the following line.\n\n`@completed_attrs %{person_id: 42, status: 1, text: \"some text completed\"}`\n\nWe will use this to create\nan item that is already completed, \nso we can test the \"clear completed\"\nfunctionality.\n\nAdd the next lines to test the\n`clear_completed/2` function.\n\n```elixir\n  describe \"clear completed\" do\n    setup [:create_item]\n\n    test \"clears the completed items\", %{conn: conn, item: item} do\n\n      # Creating completed item\n      conn = post(conn, ~p\"/items\", item: @public_completed_attrs)\n      # Clearing completed items\n      conn = get(conn, ~p\"/items/clear\")\n\n      items = conn.assigns.items\n      [completed_item | _tail] = conn.assigns.items\n\n      assert conn.assigns.filter == \"all\"\n      assert completed_item.status == 2\n    end\n\n    test \"clears the completed items in public (person_id=0)\", %{conn: conn, item: item} do\n\n      # Creating completed item\n      conn = post(conn, ~p\"/items\", item: @public_completed_attrs)\n      # Clearing completed items\n      conn = get(conn, ~p\"/items/clear\")\n\n      items = conn.assigns.items\n      [completed_item | _tail] = conn.assigns.items\n\n      assert conn.assigns.filter == \"all\"\n      assert completed_item.status == 2\n    end\n  end\n```\n\n\n\n\u003cbr /\u003e\n\n### 11. Tidy Up! (_Optional?_)\n\nAt this point we already have a fully functioning Phoenix Todo List.\nThere are a few things we can tidy up to make the App _even_ better!\n\n\n### 11.1 _Pluralise_ Items Left\n\nIf you are the type of person to notice the _tiny_ details,\nyou would have been\n[_itching_](https://en.wikipedia.org/wiki/Obsessive%E2%80%93compulsive_disorder)\neach time you saw\nthe \"***1 items left***\" in the bottom left corner:\n\n![phoenix-todo-pluralisation-BEFORE](https://user-images.githubusercontent.com/194400/83249677-dc3b4d00-a19e-11ea-8176-2f38725c3b50.png)\n\nOpen your `test/app_web/controllers/item_html_test.exs` file\nand add the following test:\n\n```elixir\ntest \"pluralise/1 returns item for 1 item and items for \u003c 1 \u003c\" do\n  assert ItemHTML.pluralise([%{text: \"one\", status: 0}]) == \"item\"\n  assert ItemHTML.pluralise([\n    %{text: \"one\", status: 0},\n    %{text: \"two\", status: 0}\n  ]) == \"items\"\n  assert ItemHTML.pluralise([%{text: \"one\", status: 1}]) == \"items\"\nend\n```\n\ne.g:\n[`test/app_web/controllers/item_html_test.exs#L28-L35`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/test/app_web/controllers/item_html_test.exs#L28-L35)\n\n\nThis test will obviously fail because the\n`AppWeb.ItemHTML.pluralise/1` is undefined.\nLet's make it pass!\n\nOpen your `lib/app_web/controllers/item_html.ex` file\nand add the following function definition for `pluralise/1`:\n\n```elixir\n# pluralise the word item when the number of items is greater/less than 1\ndef pluralise(items) do\n  # items where status \u003c 1 is equal to Zero or Greater than One:\n  case remaining_items(items) == 0 || remaining_items(items) \u003e 1 do\n    true -\u003e \"items\"\n    false -\u003e \"item\"\n  end\nend\n```\n\ne.g:\n[`lib/app_web/controllers/item_html.ex#L28-L35`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/lib/app_web/controllers/item_html.ex#L28-L35)\n\n\u003e **Note**: we are only pluralising one word in our basic Todo App\nso we are only handling this one case in our `pluralise/1` function.\nIn a more advanced app we would use a translation tool\nto do this kind of pluralising.\nSee: https://hexdocs.pm/gettext/Gettext.Plural.html\n\nFinally, _use_ the `pluralise/1` in our template.\nOpen `lib/app_web/controllers/item_html/index.html.heex`\n\nLocate the line:\n\n```elixir\n\u003cspan class=\"todo-count\"\u003e\u003c%= remaining_items(@items) %\u003e items left\u003c/span\u003e\n```\n\nAnd replace it with the following code:\n\n```elixir\n\u003cspan class=\"todo-count\"\u003e\n  \u003c%= remaining_items(@items) %\u003e \u003c%= pluralise(@items) %\u003e left\n\u003c/span\u003e\n```\n\ne.g:\n[`lib/app_web/controllers/item_html/index.html.heex#L61`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/6649a67639ccf7ad1b3189aefe678e3621a08341/lib/app_web/controllers/item_html/index.html.heex#L61)\n\nAt the end of this step\nyou will have a working pluralisation\nfor the word item/items in the bottom left of the UI:\n\n![phx-todo-pluralise-demo](https://user-images.githubusercontent.com/194400/83257010-6ab5cb80-a1ab-11ea-93e7-e95a9fb256c1.gif)\n\n\n### 11.2 Hide Footer When There Are _Zero_ Items\n\nIf you visit one of the TodoMVC examples, you will see that no `\u003cfooter\u003e` is displayed when there are no items in the list: http://todomvc.com/examples/vanillajs\n\n![todo-mvc-vanilla-](https://user-images.githubusercontent.com/194400/83250460-0fcaa700-a1a0-11ea-8f05-f399233fad4d.png)\n\nAt present our App _shows_ the `\u003cfooter\u003e`\neven if their are Zero items: 🤦\n\n\u003cimg width=\"849\" alt=\"phoenix-todo-zero-items\" src=\"https://user-images.githubusercontent.com/194400/83250895-b3b45280-a1a0-11ea-8f13-d54470cf278a.png\"\u003e\n\nThis is a visual distraction/clutter\nthat creates\n[unnecessary questions](https://en.wikipedia.org/wiki/Don%27t_Make_Me_Think)\nin the user's mind.\nLet's fix it!\n\nOpen your `lib/app_web/controllers/item_html.ex` file\nand add the following function definition `unarchived_items/1`:\n\n```elixir\ndef got_items?(items) do\n  Enum.filter(items, fn i -\u003e i.status \u003c 2 end) |\u003e Enum.count \u003e 0\nend\n```\n\ne.g:\n[`lib/app_web/controllers/item_html.ex#L37-L39`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/lib/app_web/controllers/item_html.ex#L37-L39)\n\n\nNow _use_ `got_items?/1` in the template.\n\nWrap the `\u003cfooter\u003e` element in the following `if` statement:\n\n```elixir\n\u003c%= if got_items?(@items) do %\u003e\n\n\u003c% end %\u003e\n```\n\ne.g:\n[`lib/app_web/controllers/item_html/index.html.heex#L58`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/lib/app_web/controllers/item_html/index.html.heex#L58)\n\n\nThe convention in Phoenix/Elixir (_which came from Ruby/Rails_)\nis to have a `?` (question mark) in the name of functions\nthat return a Boolean (`true/false`) result.\n\nAt the end of this step our `\u003cfooter\u003e` element\nis hidden when there are no items:\n\n![phx-todo-footer-hidden](https://user-images.githubusercontent.com/194400/83268893-2bdd4100-a1be-11ea-88ac-f99f7e6efeda.gif)\n\n\u003cbr /\u003e\n\n### 11.3 Route `/` to `ItemController.index/2`\n\nThe final piece of tidying up we can do is\nto change the Controller that gets invoked for the \"homepage\" (`/`)\nof our app.\nCurrently when the person viewing the Todo App  \nvisits [`http://localhost:4000/`](http://localhost:4000)\nthey see the `lib/app_web/controllers/page_html/home.html.eex` template:\n\n![page_template](https://user-images.githubusercontent.com/17494745/206484573-3596814d-92ce-42a0-9817-30fc16ce74cc.png)\n\nThis is the default Phoenix home page\n(_minus the CSS Styles and images that we removed in step 3.4 above_).\nIt does not tell us anything about the actual app we have built,\nit doesn't even have a _link_ to the Todo App!\nLet's fix it!\n\nOpen the `lib/app_web/router.ex` file and locate the line:\n```elixir\nget \"/\", PageController, :index\n```\n\nUpdate the controller to `ItemController`.\n\n```elixir\nget \"/\", ItemController, :index\n```\n\ne.g:\n[`lib/app_web/router.ex#L20`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/a341c2cbb5f1ad91897293f058a5d7bee6c1e1cc/lib/app_web/router.ex#L20)\n\nNow when you run your App you will see the todo list on the home page:\n\n![todo-app-on-homepage](https://user-images.githubusercontent.com/194400/83270006-cbe79a00-a1bf-11ea-8972-91097fdabdc1.png)\n\n\nUnfortunately,\nthis update will \"break\" the page test.\nRun the tests and see:\n\n```sh\n1) test GET / (AppWeb.PageControllerTest)\n     test/app_web/controllers/page_controller_test.exs:4\n     Assertion with =~ failed\n     code:  assert html_response(conn, 200) =~ \"Welcome to Phoenix!\"\n     left:  \"\u003c!DOCTYPE html\u003e\\n\u003chtml lang=\\\"en\\\"\u003e\\n  \u003chead\u003e\\n ...\"\n```\n\nGiven that we are no longer _using_ the `Page`\nController, View, Template or Tests,\nwe might as well **`delete`** them from our project!\n\n```sh\ngit rm lib/app_web/controllers/page_controller.ex\ngit rm lib/app_web/controllers/page_html.ex\ngit rm lib/app_web/page_html/home.html.heex\ngit rm test/app_web/controllers/page_controller_test.exs\n```\n\nDeleting files is good hygiene in any software project.\nDon't be _afraid_ to do it, you can always recover files\nthat are in your `git` history.\n\nRe-run the tests:\n\n```\nmix test\n```\n\nYou should see them pass now:\n\n```\n...........................\n\nFinished in 0.5 seconds\n27 tests, 0 failures\n```\n\n\u003cbr /\u003e\n\n### 11.4 Add Turbolinks to Eliminate Page Refresh\n\nGiven that our Phoenix Todo List App is 100% server rendered,\nolder browsers will perform a full page refresh\nwhen an action (create/edit/toggle/delete) is performed.\nThis will feel like a \"blink\" in the page\nand on **_really_ slow connections**\nit will result in a temporary **_blank_ page**!\nObviously, that's _horrible_ UX and is a big part of why\nSingle Page Apps (SPAs) became popular;\nto avoid page refresh, use\n**[Turbo](https://github.com/hotwired/turbo)**!\n\nGet the performance benefits of an SPA\nwithout the added complexity\nof a client-side JavaScript framework.\nWhen a link is clicked/tapped,\nTurbolinks automatically fetches the page,\nswaps in its `\u003cbody\u003e`, and merges its `\u003chead\u003e`,\nall without incurring the cost of a full page load.\n\n\nLuckily, adding `Turbo` will require just a simple\ncopy and paste!\nCheck the [`unpkg files`](https://unpkg.com/browse/@hotwired/turbo@7.2.4/dist/) \nto fetch the latest CDN package.\n\nWe now need to add the following line\nto `lib/app_web/components/layouts/app.html.heex`\nand `lib/app_web/components/layouts/root.html.heex`.\n\n```html\n  \u003cscript src=\"https://unpkg.com/browse/@hotwired/turbo@7.2.4/dist/turbo.es2017-esm.js\"\u003e\u003c/script\u003e\n```\n\nThis will install the UMD builds from Turbo\nwithout us needing to install a package using `npm`.\nNeat, huh?\n\nAnd that's it!\nNow when you deploy your server rendered Phoenix App,\nit will _feel_ like an SPA!\nTry the Fly.io demo again:\n[phxtodo.fly.dev](https://phxtodo.fly.dev/)\nFeel that buttery-smooth page transition.\n\n### 11.5 Remove unused /items/:id route\nCurrently, our application occurs in the same page.\nHowever, there is a route that we don't use \nand is also aesthetically incompatible with the rest\nof our app.\n\n\u003cimg width=\"930\" alt=\"show_route\" src=\"https://user-images.githubusercontent.com/17494745/207366063-d0da0dec-9cbe-4ea1-863d-db18aac7e23d.png\"\u003e\n\nIf we check `lib/app_web/controllers/item_controller.ex`,\nyou might notice the following function.\n\n```elixir\n  def show(conn, %{\"id\" =\u003e id}) do\n    item = Todo.get_item!(id)\n    render(conn, :show, item: item)\n  end\n```\n\nThis serves the `GET /items/:id` route.\nWe could do the same as we did with `edit`\nand render `index`.\nHowever, let's do something different \nso we learn a bit more about routes.\n\nIf we head on to `router.ex`,\nand locate the line:\n\n```elixir\nresources \"/items\", ItemController\n```\n\nWe can change it to this.\n\n```elixir\nresources \"/items\", ItemController, except: [:show]\n```\n\nWe are saying that we want to keep\n*all* the routes in ItemController\n**except** the one related to the `show` action.\n\nWe can now safely delete it\nfrom `item_controller.ex`,\nas we don't need it any more.\n\nYour files should look like the following.\n\ne.g:\n[`/lib/router.ex#L19-L29`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/0c12a6bec7aeed5562a239d0dc8eea4952250cdd/lib/app_web/router.ex#L19-L29)\n[`lib/app_web/controllers/item_controller.ex`](https://github.com/dwyl/phoenix-todo-list-tutorial/blob/auth/lib/app_web/controllers/item_controller.ex)\n\n\n\n\n### 12. Authentication (Optional)\n\nCurrently, the application allows *anyone* \nto access it and manage todo `items`.\nWouldn't it be great if \nwe added *authentication* so each `person`\ncould check their own list?\n\nWe created a dedicated authentication guide: \n[`/auth.md`](./auth.md)\nto help you set this up.\nYou will soon find out this is extremely easy 😀.\n\n\n### Deploy!\n\nDeployment to Fly.io takes a few minutes,\nbut has a few \"steps\",\nwe suggest you follow the speed run guide:\nhttps://fly.io/docs/elixir/getting-started/\n\nOnce you have _deployed_ you will will be able\nto view/use your app in any Web/Mobile Browser.\n\ne.g: https://phxtodo.fly.dev \u003cbr /\u003exs\n\n![phxtodo-fly-io](https://user-images.githubusercontent.com/194400/206772309-77056109-8e60-40ad-8e16-4e0f3140c0eb.png)\n\n\n\u003cbr /\u003e\n\n\n\n### 13. REST API (Optional)\n\nOur `Phoenix` server currently\nonly returns **`HTML` pages** \nthat are **_server-side_ rendered**.\nThis is already *awesome* \nbut we can make use of `Phoenix`\nto extend its capabilities.\n\nWhat if our server also responded\nwith `JSON`?\nYou're in luck!\nWe've created small guide\nfor creating a `REST API`:\n[**`api.md`**](./api.md)\n\n\u003cbr /\u003e\n\n### Done!\n\n\u003cbr /\u003e\n\n## What _Next_?\n\nIf you found this example useful,\nplease ⭐️ the GitHub repository\nso we (_and others_) know you liked it!\n\nIf you want to learn more Phoenix\nand the magic of **`LiveView`**,\nconsider reading our beginner's tutorial:\n[github.com/dwyl/**phoenix-liveview-counter-tutorial**](https://github.com/dwyl/phoenix-liveview-counter-tutorial)\n\nThank you for learning with us! ☀️\n\n\n\u003cbr /\u003e\n\n\u003c!--\n### Part 2: Authentication!\n--\u003e\n\n\n## Learning\n\n+ Learn Elixir: https://github.com/dwyl/learn-elixir\n+ Learn Phoenix https://github.com/dwyl/learn-phoenix-framework\n  + Phoenix Chat Tutorial:\n  https://github.com/dwyl/phoenix-chat-example\n  + Phoenix LiveView Counter Tutorial:\n  https://github.com/dwyl/phoenix-liveview-counter-tutorial\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdwyl%2Fphoenix-todo-list-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdwyl%2Fphoenix-todo-list-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdwyl%2Fphoenix-todo-list-tutorial/lists"}