-
Notifications
You must be signed in to change notification settings - Fork 178
Has Many Relationship Tab (with reordering)
This guide will allow you to drag & drop to reorder your model's has_many associations.
We will be using jQuery Sortable for frontend and nested attributes to handle the backend.
Let's get started!
Download the plugin here: https://github.com/johnny/jquery-sortable. This guide has been tested with v0.9.13.
Once downloaded, place the jquery-sortable.js
file in app/assets/javascripts/
.
Add the following to your custom.js.erb
file:
//= require "jquery-sortable"
Trestle.init(function() {
// Sortable table
$(".sortable-table").sortable({
containerSelector: 'table',
itemPath: '> tbody',
itemSelector: 'tr',
handle: 'i.fa.fa-bars',
placeholder: '<tr class="placeholder"><td colspan=99><em>Place item here...</em></td></tr>',
onDrop: function ($item, container, _super) {
$(container.el).find(".sortable-table-position-input").each(function(index, input) {
$(input).val(index);
})
_super($item, container);
}
});
});
The code does 2 things here:
- It makes sure jQuery Sortable works properly with tables.
- It calculates the position of each row and saves it in a hidden field. We will use that field later to update the row's position.
We will not be using the the jQuery Sortable styling because our tables are already styled and we want to make the integration as seamless as possible.
However, we do need a few things for the drag and drop animation to look good. Place the following in your _custom.scss
file:
body.dragging, body.dragging * {
cursor: move !important;
}
// Trestle applies a background when your curser hovers table rows. We need to remove that, otherwise the drag and drop animation is jittery.
body.dragging table.sortable-table tr {
background-color: #FFFFFF !important;
}
.dragged {
position: absolute;
top: 0;
opacity: .5;
z-index: 2000;
}
tr.placeholder td {
height: 24px;
}
The frontend is now ready.
Let's say we have a model called Project
that has many Tasks
. For everything to work correctly, we'll have to do a few things:
- Create an
integer
column calledposition
inTasks
- Add a scope called
ordered
that will orderTasks
byposition
. Something like this:scope :ordered, -> { order(position: :asc) }
- Add
accepts_nested_attributes_for :tasks
. This will make sure that we can update theTask
positions when updating theProject
. Don't worry if this is confusing, it will make sense below.
We'll need to authorise passing of our nested attributes. Open the projects_admin.rb
and permit tasks_attributes
params. Something like this:
params do |params|
params.require(:project).permit(
:name,
tasks_attributes: [
:id,
:description,
:position
]
)
end
We need to add an additional column to our table. This column will do 2 things:
- Show a drag-and-drop icon
- Store the current row's position
To do this, create the file app/columns/sortable_column.rb
and paste the following code:
class SortableColumn < Trestle::Table
attr_reader :field, :options
def initialize(field, options = {})
@field, @options = field, options
end
def renderer(table, template)
Renderer.new(self, table: table, template: template)
end
class Renderer < Trestle::Table::Column::Renderer
include ActionView::Helpers::TagHelper
include ActionView::Helpers::FormTagHelper
include ActionView::Context
def header
""
end
def content(instance)
model = options[:collection][0]
collection = options[:collection][1]
safe_join [
content_tag(:i, "", class: "fa fa-bars cursor-move"),
hidden_field_tag("#{model}[#{collection}_attributes][#{instance.id}][id]", instance.id),
hidden_field_tag("#{model}[#{collection}_attributes][#{instance.id}][#{@column.field.to_s}]", instance.send(@column.field), class: "sortable-table-position-input")
]
end
end
end
Now we need to register this class so that we can use it in our Trestle tables. Paste the following at the end of your trestle.rb
file:
class Trestle::Table::Builder
def sortable_column(field, options = {})
table.columns << SortableColumn.new(field, options)
end
end
Now we can add the sortable_column
to our tables. Like so:
table collection: project.tasks.ordered, admin: :tasks, class: "sortable-table", autolink: false do
sortable_column :position, collection: [:project, :tasks]
column :description, link: true
actions
end
A few things are happening here:
- We are using
project.tasks.ordered
as a collection. This is to ensure that our tasks are always ordered by theposition
field. - We add the
sortable-table
CSS class to add drag and drop capabilities to our table. - We disable autolink in the table to prevent misclicks from going to the task show page.
- We are using the
sortable_column
that we just created.- The first parameter is the name of the field that will hold the actual position.
- The second parameter is used to define the relationship between
Project
andTask
. The first part has to be singular and the second part has to be plural. Otherwise, it will not work properly.