Project DoList: Drop-Down Actions

Photo by Bundo Kim on Unsplash

Project DoList: Drop-Down Actions

Add Drop-Down Actions for Tasks and Projects

In this article we will add the drop-down menus for tasks and projects that contain the "Edit" and "Delete" controls. We'll be utilizing AlpineJS to handle the visibility of each menu.

Task Drop-Down Menu

Let's start by changing the markup for each task item. In the file app/views/users/projects/_tasks.html.erb replace the "Edit / Delete" code with the following:

<div x-data="{ open: false }" class="h-5 flex items-center relative hover:bg-zinc-700 rounded">
  <%= heroicon 'ellipsis-horizontal',
      options: {
        class: 'h-8 w-8 text-slate-100 cursor-pointer',
        '@click' => 'open = true',
        '@click.outside' => 'open = false'
      }
  %>

  <div x-show="open" x-cloak class="absolute top-6 right-0 min-w-[200px] z-10 rounded-xl border border-zinc-600 bg-zinc-800">
    <div class="divide-y divide-zinc-700">
      <div class="px-1.5 py-1.5">
        <%= link_to edit_users_project_task_path(@project, task), class: 'flex gap-2 items-center text-[13px] py-2 px-3 hover:bg-zinc-700 rounded' do %>
          <%= heroicon 'pencil-square', options: { class: 'h-5 w-5 text-slate-100' } %>
          <span>Edit</span>
        <% end %>
      </div>

      <div class="px-1.5 py-1.5">
        <%= link_to users_project_task_path(@project, task), class: 'flex gap-2 items-center text-[13px] py-2 px-3 hover:bg-zinc-700 rounded', data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } do %>
          <%= heroicon 'trash', options: { class: 'h-5 w-5 text-slate-100' } %>
          <span>Delete</span>
        <% end %>
      </div>
    </div>
  </div>
</div>

This bit of code relies on AlpineJS attributes to handle the showing and hiding of the action menu. Some notes:

  • The x-data="{ open: false }" attribute sets up localized data scoped to the element and its children. This data can be read and changed by the children for different purposes.

  • The '@click' => 'open = true' attribute assigns a click handler to the element that will set the data variable open to true when clicked.

  • The '@click.outside' => 'open = false' attribute assigns a click handler to any element that is outside of the element (not itself or its children) that will set the data variable open to false when clicked.

  • The x-show="open" attribute will only show the element when the data variable open is true.

  • The x-cloak attribute initially hides the element using CSS to prevent the brief flicker of visibility that will occur before the JS is fully loaded on the page.

To make the x-cloak attribute work correctly we must add code to the file app/assets/stylesheets/application.css:

[x-cloak] { display: none !important; }

The task drop-down menu should now look like this:

Project Drop-Down Menu

Next let's add the same drop-down functionality to the project list. Let's start by moving the project list from the layout template into its own partial so that we stay organized. Create the file app/views/layouts/users/_projects.html.erb and add the following code:

<% active_list = is_active_link?(users_projects_path, :exact) %>
<% active_list_classes = class_names(
  'bg-[#472525]' => active_list
) %>
<div class="flex justify-between items-center mb-1 rounded-lg <%= active_list_classes %>">
  <%= link_to 'My Projects', users_projects_path, class: 'grow p-2' %>

  <div class="mr-2">
    <%= link_to new_users_project_path, data: { turbo_frame: 'modal' } do %>
      <%= heroicon 'plus', options: { class: 'text-white w-4 h-4' } %>
    <% end %>
  </div>
</div>

<div data-sortable-projects>
  <% projects.each do |project| %>
    <% active_project = is_active_link?(users_project_path(project)) %>
    <% classes = class_names(
        'bg-[#472525] text-red-300' => active_project,
        'hover:bg-zinc-700/25' => !active_project
       )
    %>

    <div x-data="{ open: false }" class="group mb-1 relative flex justify-between items-center rounded-lg <%= classes %>" data-project-id="<%= project.id %>">
      <%= link_to project.name,
          users_project_path(project),
          class: 'block grow p-2'
      %>

      <div class="group-hover:hidden text-[12px] text-zinc-500 mr-3.5">
        <% tasks_count = project.active_tasks.count %>
        <%= tasks_count if tasks_count > 0 %>
      </div>

      <div class="group-hover:flex hidden h-5 items-center relative hover:bg-zinc-700 rounded mr-2 text-white">
        <%= heroicon 'ellipsis-horizontal',
            options: {
              class: 'h-6 w-6 text-zinc-100 cursor-pointer',
              '@click' => 'open = !open; $event.preventDefault();',
              '@click.outside' => 'open = false'
            }
        %>
      </div>

      <div x-show="open" x-cloak class="absolute top-10 right-0 min-w-[200px] z-10 rounded-xl border border-zinc-600 bg-zinc-800 text-zinc-300">
        <div class="divide-y divide-zinc-700">
          <div class="px-1.5 py-1.5">
            <%= link_to edit_users_project_path(project), class: 'flex gap-2 items-center text-[13px] py-2 px-3 hover:bg-zinc-700 rounded', data: { turbo_frame: 'modal' } do %>
              <%= heroicon 'pencil-square', options: { class: 'h-5 w-5 text-slate-100' } %>
              <span>Edit</span>
            <% end %>
          </div>

          <div class="px-1.5 py-1.5">
            <%= link_to users_project_path(project), class: 'flex gap-2 items-center text-[13px] py-2 px-3 hover:bg-zinc-700 rounded', data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } do %>
              <%= heroicon 'trash', options: { class: 'h-5 w-5 text-slate-100' } %>
              <span>Delete</span>
            <% end %>
          </div>
        </div>
      </div>
    </div>
  <% end %>
</div>

This code contains the same functionality as the task drop-down menus: it uses AlpineJS attributes (x-data, x-show, @click) to toggle the visibility of the drop-down menu. This code also contains new styles to correctly match the project list that is found in the Todoist app.

Be sure to render the new partial in the app/views/layouts/users.html.erb file like so:

...
<div class="bg-zinc-800 text-white text-sm p-5 min-w-[300px]">
  <div class="mb-10">
    <div><%= current_user.email %></div>
    <div><%= link_to 'Logout', destroy_user_session_path, data: { turbo_prefetch: false } %></div>
  </div>

  <%= render 'layouts/users/projects', projects: @projects %>
</div>
...

After adding the above code the projects list should now look like so:

Summary

In this article we did the following:

  • Moved "Edit" / "Delete" task actions to a drop-down menu

  • Moved "Edit" / "Delete" project actions to a drop-down menu

  • Added styles to the project list to match the look and feel of Todoist