# Project DoList: Sortable Tasks and Projects

In this article we will add the ability to sort Tasks and Projects. We will need to do the following:

* Install a frontend sorting library
    
* Install a backend sorting library
    
* Save the new record order on sort
    

## Install Frontend Sorting

For the frontend draggable sorting we will use an oldie but goodie: jQueryUI Sortable ([https://jqueryui.com/sortable/](https://jqueryui.com/sortable/)). To install it add the following script tags to the `head` of `app/views/layouts/users.html.erb`:

```erb
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/ui/1.13.3/jquery-ui.min.js" integrity="sha256-sw0iNNXmOJbQhYFuC9OF2kOlD5KQKe1y5lfBn4C9Sjg=" crossorigin="anonymous"></script>
```

Now let's initialize sorting for tasks. In the bottom of the file `app/views/users/projects/_tasks.html.erb` add the following code:

```xml
<script>
  $('[data-sortable-tasks]').sortable({
    handle: '[data-handle]',
    placeholder: 'sortable-placeholder',

    update(event, ui) {
      const index = ui.item.index();
      const taskId = ui.item.data('task-id');

      fetch(`/app/projects/<%= @project.id %>/tasks/${taskId}/update_weight`, {
        method: 'POST',
        body: JSON.stringify({ weight: index + 1 }),
        headers: {
          "Content-Type": "application/json",
        }
      });
    }
  })
</script>
```

This does some things:

* We call `.sortable` on the container of the items that we'd like to sort. In this case we'll be adding `data-sortable-tasks` to the parent of the task items.
    
* We use the `handle: '[data-handle]'` option to sort items only when the "move" icon is clicked and dragged (not the entire row).
    
* We use `placeholder: 'sortable-placeholder'` to give us a specific classname that we can target so we can add custom styling to the empty space left when dragging an element.
    
* We use the `update(event, ui)` callback to save the new order by making an AJAX request to the backend with the task ID and position.
    

Next we need to add the new attributes and elements to the task list partial:

```erb
<div class="mb-3" data-sortable-tasks>
  <% tasks.each do |task| %>
    <%= turbo_frame_tag "#{dom_id(task)}_edit", class: 'group block bg-zinc-900 border-b border-b-zinc-700 relative flex justify-between items-center py-2', data: { task_id: task.id } do %>
      <div class="opacity-0 group-hover:opacity-100 absolute -left-[25px] pr-[5px]">
        <%= heroicon 'bars-3', options: { class: 'h-5 w-5 cursor-move text-zinc-300', data_handle: true } %>
      </div>

      ...
    <% end %>
  <% end %>
</div>
```

Finally let's add the styles to make everything look like nice. In the file `app/assets/stylesheets/application.tailwind.css` add the following code:

```css
.sortable-placeholder {
  @apply h-[40px] bg-zinc-800 block border-t-2 border-red-600;
}

.ui-sortable-helper {
  @apply px-4;
}

.ui-sortable-helper [data-handle] {
  @apply opacity-0 cursor-grabbing;
}
```

## Install Backend Sorting

To enable easy sorting of records on the backend we can use the Positioning gem ([https://github.com/brendon/positioning](https://github.com/brendon/positioning)). After installing the gem we setup the `Task` model to use positioning:

```ruby
class Task < ApplicationRecord
  belongs_to :project
  positioned on: :project, column: :weight
end
```

The line `positioned on: :project, column: :weight` does the following:

* It scopes our sorting of tasks to a project. This will ensure that tasks get proper positioning *per project.*
    
* It defines which column to store the position data (`weight`).
    

Next lets add the route that will be used to save our sort data. In the file `config/routes.rb` inside the tasks member block add the following route: `post :update_weight, action: :update_weight`. Next in our tasks controller add the following action:

```ruby
def update_weight
  @project = current_user.projects.find(params[:project_id])
  @task = current_user.tasks.find(params[:id])
  @task.update!(weight: params[:weight])
end
```

Because we are using the Positioning gem, we only have to specify one task's new position and all the other task positions in the same project will be adjusted accordingly.

Our task sorting should now look like this:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1722088644245/dea84af3-3ee6-4350-98c0-998ee3b5e97b.gif align="center")

## Apply Sorting to Projects

Let's use the same method to apply sorting to projects. At the bottom of the file `app/views/layouts/users.html.erb` add the following code:

```xml
<script>
  $('[data-sortable-projects]').sortable({
    placeholder: 'sortable-placeholder',

    update(event, ui) {
      const index = ui.item.index();
      const projectId = ui.item.data('project-id');

      fetch(`/app/projects/${projectId}/update_weight`, {
        method: 'POST',
        body: JSON.stringify({ weight: index + 1 }),
        headers: {
          "Content-Type": "application/json",
        }
      });
    }
  })
</script>
```

Next update the project list markup to include `data-sortable-projects` and `data-project-id` like so:

```erb
<div data-sortable-projects>
  <% @projects.each do |project| %>
    <div class="mb-1" data-project-id="<%= project.id %>">
      <%= active_link_to project.name,
          users_project_path(project),
          class: 'block p-2 rounded-lg',
          class_active: 'bg-[#472525] text-red-300',
          class_inactive: 'hover:bg-zinc-700'
      %>
    </div>
  <% end %>
</div>
```

Next setup positioning on the `Project` model:

```ruby
class Project < ApplicationRecord
  ...

  belongs_to :user
  positioned on: :user, column: :weight

  ...
end
```

Note that this will scope project sorting *per user*.

Finally add the new route and controller actions to handle saving the new project positions. In the routes file add a new `member` block under `projects`:

```ruby
member do
  post :update_weight, action: :update_weight
end
```

In the projects controller add the following action:

```ruby
def update_weight
  @project = current_user.projects.find(params[:id])
  @project.update!(weight: params[:weight])
end
```

Project sorting should now look like this:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1722089141485/701b550f-a8a4-43d7-9d14-b83683f07ad0.gif align="center")

## Summary

In this article we did the following:

* Installed and setup jQueryUI Sortable to enable frontend draggable sorting.
    
* Installed and setup the Positioning gem to enable backend record sorting with proper scoping.
    
* Connected the frontend to the backend using simple AJAX requests.
    
* Enabled sorting for both Tasks and Projects.
