active_scaffold_sortable adds AJAX drag-and-drop ordering to Active Scaffold
lists and association subforms.
It supports:
- ordinary ordered lists, including automatic setup for
acts_as_listmodels; - sortable
has_manyassociation subforms; - sibling ordering in nested-set trees using the
awesome_nested_setAPI; - sibling ordering in Ancestry trees;
- a dedicated drag-handle column, optional list refreshes, and custom DOM selectors for non-standard list markup.
- Ruby 2.0 or newer
- Active Scaffold 4.4.0 or newer
- a Rails version supported by the selected Active Scaffold version
- jQuery UI Sortable on pages that use this plugin
For Rails 3 applications, use an active_scaffold_sortable 3.2.x release.
Add the gem to the application's Gemfile:
gem "active_scaffold_sortable"Then run:
bundle installThe engine registers the Active Scaffold action, route, JavaScript, stylesheet, and view overrides automatically.
Add a position column and configure the model with
acts_as_list:
# db/migrate/..._add_position_to_entries.rb
add_column :entries, :position, :integer
add_index :entries, :positionclass Entry < ApplicationRecord
acts_as_list
endThe plugin detects acts_as_list, enables the :sortable action, and uses
the model's configured position column. No Active Scaffold controller
configuration is needed.
Scopes configured in acts_as_list still define the logical lists. A rendered
Active Scaffold list should contain records from only one scope so that a drag
does not mix independent lists.
Sorting can use any persisted column that accepts one-based integer positions. Enable the action and select the column explicitly:
class EntriesController < ApplicationController
active_scaffold :entry do |config|
config.actions << :sortable
config.sortable.column = :position
end
endOn every drop, the visible records receive consecutive values beginning at
1. Updates are made directly with update_all, so validations and model
callbacks do not run. If the table has an updated_at column, it is updated.
The reorder action uses Active Scaffold's update authorization check.
A collection association is draggable in a create or update form when the
associated model's Active Scaffold configuration includes :sortable. This
works whether sorting was enabled automatically by acts_as_list or manually:
class TasksController < ApplicationController
active_scaffold :task do |config|
config.actions << :sortable
config.sortable.column = :position
end
endFor example, a Project form that renders a has_many :tasks subform will
allow its task rows to be reordered. Dragging updates the hidden position
fields in the form; the order is persisted when the parent form is submitted,
not through a separate reorder request. New associated records without a
position are placed after the highest existing position.
The association must be a collection. Singular associations are not sortable.
The associated scaffold may exclude the :list action when sorting is needed
only in a subform.
Tree support reorders nodes only among their current siblings. It does not move a node to a different parent.
Models using
awesome_nested_set
are detected automatically:
class Category < ApplicationRecord
acts_as_nested_set
endThe plugin enables :sortable, uses the model's configured left column, and
calls the nested-set movement methods to preserve valid left and right bounds.
Root nodes can be reordered as siblings as well.
Detection is API-based rather than gem-name-based. A different nested-set
implementation may work only if it provides the same API used here:
nested_set_scope, left_column_name, self_and_siblings, move_left, and
move_right. awesome_nested_set is the intended integration; no other
nested-set gem is explicitly supported by this project.
Ancestry models are recognized during reordering, but they are not enabled automatically. Add a separate position column and configure sorting manually:
class Category < ApplicationRecord
has_ancestry
endclass CategoriesController < ApplicationController
active_scaffold :category do |config|
config.actions << :sortable
config.sortable.column = :position
end
endThe submitted records are reordered within the first record's sibling set.
Both root nodes and children are supported. The ancestry column itself is a
path, not a sibling position, and should not be configured as the sortable
column.
By default, the whole row is the drag handle. Add a dedicated handle column at the beginning or end of the list with:
config.sortable.add_handle_column = :first
# or
config.sortable.add_handle_column = :lastOnly :first, :last, and false/nil are accepted.
The default response only reapplies alternating row styles. Refresh the entire Active Scaffold list after each reorder when other displayed values depend on the order:
config.sortable.refresh_list = trueconfig.sortable.options is merged into the sortable container's data-*
attributes. Supported overrides include:
config.sortable.options = {
tag: "> tr", # selector for draggable items
handle: ".my-drag-handle", # selector that starts dragging
content_selector: ".my-records", # sortable content within the container
key: "as_entries-tbody", # serialized request parameter base
format: "^[^_-].*-(.*)-row$", # extracts the record ID from a row ID
with: "scope_id=42" # extra reorder request parameters
}These are integration hooks for custom Active Scaffold markup. They are not a
general pass-through for every jQuery UI Sortable option. When overriding
key, keep it aligned with the parameter name expected by the reorder action.
The same settings can be used as defaults for every sortable scaffold. Set them in an initializer before Active Scaffold configurations are built:
ActiveScaffold::Config::Sortable.add_handle_column = :first
ActiveScaffold::Config::Sortable.refresh_list = true
ActiveScaffold::Config::Sortable.options = { handle: ".my-drag-handle" }Sortable list and association-subform wrappers expose the
sortable_container UI element. UI framework adapters can add or replace its
HTML attributes without overriding either partial:
ActiveScaffold.add_element_attributes :sortable_container,
class: "overflow-auto"The plugin keeps the sortable-container class and data-* attributes used
by its JavaScript and merges them with attributes supplied by the adapter.
Enabling :sortable also:
- disables pagination so all draggable records are present in one list;
- orders the list ascending by the sortable column;
- disables sorting by other columns;
- hides the sortable column from normal action columns and configures its form UI as hidden so subforms can carry positions without displaying the column.
Because the browser submits only rendered rows, filters and nested views update only the matching subset. Non-rendered records retain their stored positions, which can produce duplicate positions in an ordinary list. Keep each sortable view within one logical list or sibling set, and use filtering with care.
For help, use the Active Scaffold discussion group or open an issue.
Fork the repository, make the change with tests, and open a pull request.
Released under the MIT License.
- Tim Harper
- Sergio Cambra
- Volker Hochstein