Forem

Cover image for How To Build a [Counter with Elixir, Phoenix, LiveView and Tailwind CSS] | No Javascript
Anthony Gonzalez
Anthony Gonzalez

Posted on

5 2

How To Build a [Counter with Elixir, Phoenix, LiveView and Tailwind CSS] | No Javascript

 
 

 
 

Learn how to use Elixir/Phoenix, to create a counter without javascript, only using Elixir with the help of Phoenix LiveView, and Tailwind CSS for the user interface.

Step 1:

Let's create a fresh elixir/phoenix new project by going to the command line and typing the following in your desired directory:

$ mix phx.new counter --no-ecto --no-gettext --no-mailer --no-dashboard && cd counter

Step 2:

Let's add and configure Tailwind CSS, go to the website https://hex.pm and search for the tailwind library to grab the latest version, so you can copy and that to your mix config file, at the time of the making of this tutorial the latest version is 0.1.5, add the following to your project dependencies inside your mix.exs file:

{:tailwind, "~> 0.1.5"}

Then in the command line type in $ mix deps.get && mix tailwind.install to fetch your new dependency and install tailwind. Back in your mix.exs file, under aliases, add the following:

"assets.deploy": ["tailwind default --minify", ..., "phx.digest"]

Go to your config/config/exs file and add the following configuration:

config :tailwind,
  version: "3.0.10",
  default: [
    args: ~w(
      --config=tailwind.config.js
      --input=css/app.css
      --output=../priv/static/assets/app.css
    ),
    cd: Path.expand("../assets", __DIR__)
  ]
Enter fullscreen mode Exit fullscreen mode

Go to your config/dev.exs file and add the following to your watchers:

tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}
Enter fullscreen mode Exit fullscreen mode

Step 3:

Add the following folder under test/counter_web:

live

Add the following file to that newly created folder:

page_live_test.exs

To that newly created file test/counter_web/live/page_live_test.exs add the following tests:

defmodule CounterWeb.PageLiveTest do
  use CounterWeb.ConnCase

  import Phoenix.LiveViewTest

  test "disconnected and connected render", %{conn: conn} do
    {:ok, page_live, disconnected_html} = live(conn, "/")
    assert disconnected_html =~ "0"
    assert render(page_live) =~ "0"
  end

  test "increment event and decrement", %{conn: conn} do
    {:ok, page_live, _html} = live(conn, "/")
    assert render_click(page_live, :inc, %{}) =~ "1"
    assert render_click(page_live, :inc, %{}) =~ "2"
    assert render_click(page_live, :inc, %{}) =~ "3"
    assert render_click(page_live, :dec, %{}) =~ "2"
    assert render_click(page_live, :dec, %{}) =~ "1"
    assert render_click(page_live, :dec, %{}) =~ "0"
  end

  test "clear event", %{conn: conn} do
    {:ok, page_live, _html} = live(conn, "/")
    assert render_click(page_live, :inc, %{}) =~ "1"
    assert render_click(page_live, :clear, %{}) =~ "0"
  end
end
Enter fullscreen mode Exit fullscreen mode

If you run those tests by going to the command line and typing mix test test/counter_web/live they should fail of course.

Step 4:

Open your lib/counter_web/router.ex file and remove the root path and change it to the following:

  scope "/", CounterWeb do
    pipe_through :browser

    live "/", PageLive
  end
Enter fullscreen mode Exit fullscreen mode

Run the server with the following command in your terminal:

$ iex -S mix phx.server

Open your lib/counter_web/templates/layout/root.html.heex and remove the header nav, your html body tag should look like the following:

  <body>
    <%= @inner_content %>
  </body>
Enter fullscreen mode Exit fullscreen mode

To your lib/counter_web/templates/layout/live.html.heex file, add the following css class to your main tag:

<main class="container mx-auto max-w-full">
Enter fullscreen mode Exit fullscreen mode

Add the following folder to lib/counter_web:

live

Add the following file to that newly created live folder:

page_live.ex

Add the following to that newly created file lib/counter_web/live/page_live.ex:

defmodule CounterWeb.PageLive do
  use CounterWeb, :live_view
end
Enter fullscreen mode Exit fullscreen mode

Under lib/counter_web/live add the following file:

page_live.html.heex

Add the following to that newly created lib/counter_web/live/page_live_html.heex file:

<div class="flex flex-col items-center justify-center h-screen bg-gray-200">
  <h1 class="text-5xl font-medium text-gray-700">Counter</h1>

  <span class="m-5 text-9xl">
    0
  </span>

  <section> 
    <button class="text-white text-4xl bg-indigo-600 px-6 py-4 rounded hover:bg-indigo-900">-</button>

    <button class="m-10 text-white text-4xl bg-indigo-900 px-6 py-4 rounded hover:bg-indigo-500">clear</button>

    <button class="text-white text-4xl bg-indigo-600 px-6 py-4 rounded hover:bg-indigo-900">+</button>
  </section>
</div>
Enter fullscreen mode Exit fullscreen mode

Step 5:

Let's add our liveview mount function and assign the default number.

To lib/counter_web/live/page_live.ex file add the following function:

  def mount(_params, _session, socket) do
    {:ok,
     socket
     |> assign(number: 0)}
  end
Enter fullscreen mode Exit fullscreen mode

Then change the number 0 to our new assign variable in our lib/counter_web/live/page_lve.html.heex file, inside our span tag:

  <span class="m-5 text-9xl">
    <%= @number %>
  </span>
Enter fullscreen mode Exit fullscreen mode

Step 6:

Inside our lib/counter_web/live/page_lve.html.heex file, add the following click event binding to our increase button:

<button phx-click="inc" class="...">+</button>
Enter fullscreen mode Exit fullscreen mode

Then to our lib/counter_web/live/page_live.ex file let's add the following function to handle that event:

  def handle_event("inc", _params, socket) do
    {:noreply,
     socket
     |> update(:number, &(&1 + 1))}
  end
Enter fullscreen mode Exit fullscreen mode

Step 7:

Inside our lib/counter_web/live/page_lve.html.heex file, add the following click event binding to our decrease button:

<button phx-click="dec" class="...">-</button>
Enter fullscreen mode Exit fullscreen mode

To our lib/counter_web/live/page_live.ex file let's add the following function to handle the decrease event:

  def handle_event("dec", _params, socket) do
    {:noreply,
     socket
     |> update(:number, &max(0, &1 - 1))}
  end
Enter fullscreen mode Exit fullscreen mode

Step 8:

Let's add the following click event binding to our clear button inside our lib/counter_web/live/page_lve.html.heex file:

<button phx-click="clear" class="...">clear</button>
Enter fullscreen mode Exit fullscreen mode

To our lib/counter_web/live/page_live.ex file let's add the following function to handle the clear event:

  def handle_event("clear", _params, socket) do
    {:noreply, socket |> assign(number: 0)}
  end
Enter fullscreen mode Exit fullscreen mode

Go to http://localhost:4000 to test your application manually as a user, and if you go to the command line and run the tests with mix test test/counter_web/live they should pass with no failure.

Conclusion

That's all folks, we went through the simple steps to set up Tailwind CSS with your phoenix applications, and how to handle click events with Phoenix LiveView over sockets without the need of using Javascript code, the Javascript is handled nicely in the background for us by the LiveView library. Thank you so much for your time, I really appreciate it.

 
 

Join The Elixir Army

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more →

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

đź‘‹ Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay