diff --git app/controllers/custom_fields_controller.rb app/controllers/custom_fields_controller.rb
index aa9c6af..5482ff9 100644
--- app/controllers/custom_fields_controller.rb
+++ app/controllers/custom_fields_controller.rb
@@ -17,6 +17,7 @@
class CustomFieldsController < ApplicationController
layout 'admin'
+ include Redmine::DragDropOrdering::Controller
before_filter :require_admin
before_filter :build_new_custom_field, :only => [:new, :create]
diff --git app/controllers/enumerations_controller.rb app/controllers/enumerations_controller.rb
index 5eba1fa..cd6cabe 100644
--- app/controllers/enumerations_controller.rb
+++ app/controllers/enumerations_controller.rb
@@ -17,6 +17,7 @@
class EnumerationsController < ApplicationController
layout 'admin'
+ include Redmine::DragDropOrdering::Controller
before_filter :require_admin, :except => :index
before_filter :require_admin_or_api_request, :only => :index
diff --git app/controllers/issue_statuses_controller.rb app/controllers/issue_statuses_controller.rb
index 7b8103e..c320a19 100644
--- app/controllers/issue_statuses_controller.rb
+++ app/controllers/issue_statuses_controller.rb
@@ -17,6 +17,7 @@
class IssueStatusesController < ApplicationController
layout 'admin'
+ include Redmine::DragDropOrdering::Controller
before_filter :require_admin, :except => :index
before_filter :require_admin_or_api_request, :only => :index
diff --git app/controllers/roles_controller.rb app/controllers/roles_controller.rb
index 33229cb..b5a33cb 100644
--- app/controllers/roles_controller.rb
+++ app/controllers/roles_controller.rb
@@ -17,6 +17,7 @@
class RolesController < ApplicationController
layout 'admin'
+ include Redmine::DragDropOrdering::Controller
before_filter :require_admin, :except => [:index, :show]
before_filter :require_admin_or_api_request, :only => [:index, :show]
diff --git app/controllers/trackers_controller.rb app/controllers/trackers_controller.rb
index 4a291b5..28d0b1e 100644
--- app/controllers/trackers_controller.rb
+++ app/controllers/trackers_controller.rb
@@ -17,6 +17,7 @@
class TrackersController < ApplicationController
layout 'admin'
+ include Redmine::DragDropOrdering::Controller
before_filter :require_admin, :except => :index
before_filter :require_admin_or_api_request, :only => :index
diff --git app/helpers/application_helper.rb app/helpers/application_helper.rb
index ff20064..09ec8b7 100644
--- app/helpers/application_helper.rb
+++ app/helpers/application_helper.rb
@@ -28,6 +28,7 @@ module ApplicationHelper
include Redmine::SudoMode::Helper
include Redmine::Themes::Helper
include Redmine::Hook::Helper
+ include Redmine::DragDropOrdering::Helper
extend Forwardable
def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
diff --git app/models/custom_field.rb app/models/custom_field.rb
index 8da7503..a3ae279 100644
--- app/models/custom_field.rb
+++ app/models/custom_field.rb
@@ -17,6 +17,7 @@
class CustomField < ActiveRecord::Base
include Redmine::SubclassFactory
+ include Redmine::DragDropOrdering::Model
has_many :enumerations,
lambda { order(:position) },
diff --git app/models/enumeration.rb app/models/enumeration.rb
index b2337fa..13211a4 100644
--- app/models/enumeration.rb
+++ app/models/enumeration.rb
@@ -17,6 +17,7 @@
class Enumeration < ActiveRecord::Base
include Redmine::SubclassFactory
+ include Redmine::DragDropOrdering::Model
default_scope lambda {order(:position)}
diff --git app/models/issue_status.rb app/models/issue_status.rb
index e39d69a..ab3cdda 100644
--- app/models/issue_status.rb
+++ app/models/issue_status.rb
@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class IssueStatus < ActiveRecord::Base
+ include Redmine::DragDropOrdering::Model
+
before_destroy :check_integrity
has_many :workflows, :class_name => 'WorkflowTransition', :foreign_key => "old_status_id"
has_many :workflow_transitions_as_new_status, :class_name => 'WorkflowTransition', :foreign_key => "new_status_id"
diff --git app/models/role.rb app/models/role.rb
index 42dddaf..949bb98 100644
--- app/models/role.rb
+++ app/models/role.rb
@@ -16,6 +16,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Role < ActiveRecord::Base
+ include Redmine::DragDropOrdering::Model
+
# Custom coder for the permissions attribute that should be an
# array of symbols. Rails 3 uses Psych which can be *unbelievably*
# slow on some platforms (eg. mingw32).
diff --git app/models/tracker.rb app/models/tracker.rb
index cbf6bc0..5976fc7 100644
--- app/models/tracker.rb
+++ app/models/tracker.rb
@@ -16,6 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Tracker < ActiveRecord::Base
+ include Redmine::DragDropOrdering::Model
CORE_FIELDS_UNDISABLABLE = %w(project_id tracker_id subject description priority_id is_private).freeze
# Fields that can be disabled
diff --git app/views/custom_fields/_index.html.erb app/views/custom_fields/_index.html.erb
index b0d3c27..6a586d8 100644
--- app/views/custom_fields/_index.html.erb
+++ app/views/custom_fields/_index.html.erb
@@ -1,4 +1,5 @@
-
+<%= form_tag reorder_all_custom_fields_path, :method => 'post', :id => 'sortable_form' do %>
+
<%=l(:field_name)%> |
<%=l(:field_field_format)%> |
@@ -14,7 +15,11 @@
<% (@custom_fields_by_type[tab[:name]] || []).sort.each do |custom_field| -%>
<% back_url = custom_fields_path(:tab => tab[:name]) %>
">
- <%= link_to custom_field.name, edit_custom_field_path(custom_field) %> |
+
+
+ <%= hidden_field_tag "id_and_positions[#{custom_field.id}]", custom_field.position, :class => 'position' %>
+ <%= link_to custom_field.name, edit_custom_field_path(custom_field) %>
+ |
<%= l(custom_field.format.label) %> |
<%= checked_image custom_field.is_required? %> |
<% if tab[:name] == 'IssueCustomField' %>
@@ -29,3 +34,6 @@
<% end; reset_cycle %>
+<% end %>
+
+<%= sortable_js('sortable_form', 'sortable_table') %>
diff --git app/views/enumerations/index.html.erb app/views/enumerations/index.html.erb
index d1fb919..6029880 100644
--- app/views/enumerations/index.html.erb
+++ app/views/enumerations/index.html.erb
@@ -5,7 +5,8 @@
<% enumerations = klass.shared %>
<% if enumerations.any? %>
-
+<%= form_tag reorder_all_enumerations_path, :method => 'post', :id => "#{klass.name.underscore}_form" do %>
+">
<%= l(:field_name) %> |
<%= l(:field_is_default) %> |
@@ -15,7 +16,11 @@
<% enumerations.each do |enumeration| %>
- <%= link_to enumeration, edit_enumeration_path(enumeration) %> |
+
+
+ <%= hidden_field_tag "id_and_positions[#{enumeration.id}]", enumeration.position, :class => 'position' %>
+ <%= link_to enumeration, edit_enumeration_path(enumeration) %>
+ |
<%= checked_image enumeration.is_default? %> |
<%= checked_image enumeration.active? %> |
<%= reorder_links('enumeration', {:action => 'update', :id => enumeration}, :put) %> |
@@ -23,6 +28,8 @@
<% end %>
+<% end %>
+<%= sortable_js("#{klass.name.underscore}_form", "#{klass.name.underscore}_table") %>
<% reset_cycle %>
<% end %>
diff --git app/views/issue_statuses/index.html.erb app/views/issue_statuses/index.html.erb
index 40a5c92..220a978 100644
--- app/views/issue_statuses/index.html.erb
+++ app/views/issue_statuses/index.html.erb
@@ -5,7 +5,8 @@
<%=l(:label_issue_status_plural)%>
-
+<%= form_tag reorder_all_issue_statuses_path, :method => 'post', :id => 'sortable_form' do %>
+
<%=l(:field_status)%> |
<% if Issue.use_status_for_done_ratio? %>
@@ -18,7 +19,11 @@
<% for status in @issue_statuses %>
">
- <%= link_to status.name, edit_issue_status_path(status) %> |
+
+
+ <%= hidden_field_tag "id_and_positions[#{status.id}]", status.position, :class => 'position' %>
+ <%= link_to status.name, edit_issue_status_path(status) %>
+ |
<% if Issue.use_status_for_done_ratio? %>
<%= status.default_done_ratio %> |
<% end %>
@@ -31,7 +36,9 @@
<% end %>
+<% end %>
<% html_title(l(:label_issue_status_plural)) -%>
+<%= sortable_js('sortable_form', 'sortable_table') %>
diff --git app/views/roles/index.html.erb app/views/roles/index.html.erb
index 6561d11..ff35351 100644
--- app/views/roles/index.html.erb
+++ app/views/roles/index.html.erb
@@ -5,7 +5,8 @@
<%=l(:label_role_plural)%>
-
+<%= form_tag reorder_all_roles_path, :method => 'post', :id => 'sortable_form' do %>
+
<%=l(:label_role)%> |
<%=l(:button_sort)%> |
@@ -14,7 +15,13 @@
<% for role in @roles %>
">
- <%= content_tag(role.builtin? ? 'em' : 'span', link_to(role.name, edit_role_path(role))) %> |
+
+ <% unless role.builtin? %>
+
+ <%= hidden_field_tag "id_and_positions[#{role.id}]", role.position, :class => 'position' %>
+ <% end %>
+ <%= content_tag(role.builtin? ? 'em' : 'span', link_to(role.name, edit_role_path(role))) %>
+ |
<% unless role.builtin? %>
<%= reorder_links('role', {:action => 'update', :id => role, :page => params[:page]}, :put) %>
@@ -28,7 +35,9 @@
<% end %>
|
+<% end %>
<% html_title(l(:label_role_plural)) -%>
+<%= sortable_js('sortable_form', 'sortable_table') %>
diff --git app/views/trackers/index.html.erb app/views/trackers/index.html.erb
index 8ba7b7c..e1ce311 100644
--- app/views/trackers/index.html.erb
+++ app/views/trackers/index.html.erb
@@ -5,7 +5,8 @@
<%=l(:label_tracker_plural)%>
-
+<%= form_tag reorder_all_trackers_path, :method => 'post', :id => 'sortable_form' do %>
+
<%=l(:label_tracker)%> |
|
@@ -15,7 +16,11 @@
<% for tracker in @trackers %>
">
- <%= link_to tracker.name, edit_tracker_path(tracker) %> |
+
+
+ <%= hidden_field_tag "id_and_positions[#{tracker.id}]", tracker.position, :class => 'position' %>
+ <%= link_to tracker.name, edit_tracker_path(tracker) %>
+ |
<% unless tracker.workflow_rules.count > 0 %>
@@ -33,7 +38,9 @@
<% end %>
|
+<% end %>
<% html_title(l(:label_tracker_plural)) -%>
+<%= sortable_js('sortable_form', 'sortable_table') %>
diff --git config/routes.rb config/routes.rb
index 3b469d7..fa057aa 100644
--- config/routes.rb
+++ config/routes.rb
@@ -376,4 +376,5 @@ Rails.application.routes.draw do
end
end
end
+ extend Redmine::DragDropOrdering::Routes
end
diff --git lib/redmine.rb lib/redmine.rb
index d797a01..e685274 100644
--- lib/redmine.rb
+++ lib/redmine.rb
@@ -63,6 +63,7 @@ require 'redmine/hook'
require 'redmine/hook/listener'
require 'redmine/hook/view_listener'
require 'redmine/plugin'
+require 'redmine/drag_drop_ordering'
Redmine::Scm::Base.add "Subversion"
Redmine::Scm::Base.add "Darcs"
diff --git lib/redmine/drag_drop_ordering.rb lib/redmine/drag_drop_ordering.rb
new file mode 100644
index 0000000..395a83c
--- /dev/null
+++ lib/redmine/drag_drop_ordering.rb
@@ -0,0 +1,65 @@
+require 'active_support/concern'
+
+module Redmine
+ module DragDropOrdering
+ module Model
+ extend ActiveSupport::Concern
+ module ClassMethods
+ def reorder_all(id_and_positions)
+ return unless id_and_positions.is_a?(Hash)
+ transaction do
+ id_and_positions.each do |obj_id, position|
+ obj = self.find(obj_id)
+ obj.position = position.to_i
+ unless obj.save
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+ end
+ end
+ end
+
+ module Controller
+ def reorder_all
+ model_klass = self.controller_name.classify.constantize
+ model_klass.reorder_all(params[:id_and_positions])
+ redirect_to :action => :index
+ end
+ end
+
+ module Routes
+ def self.extended(routes)
+ routes.instance_exec do
+ concern :sortable do
+ post :reorder_all, :on => :collection
+ end
+ resources :issue_statuses, :concerns => :sortable
+ resources :custom_fields, :concerns => :sortable
+ resources :enumerations, :concerns => :sortable
+ resources :trackers, :concerns => :sortable
+ resources :roles, :concerns => :sortable
+ end
+ end
+ end
+
+ module Helper
+ def sortable_js(form_id, table_id)
+ js = <<-"EOS"
+ $(function() {
+ $('##{table_id} tbody').sortable({
+ handle: '.sort-handle',
+ update: function(event, ui) {
+ $('##{table_id} tr').each(function(){
+ $(this).find('input.position').val($(this).index()+1);
+ });
+ $('form##{form_id}').submit();
+ }
+ });
+ });
+ EOS
+ javascript_tag(js.html_safe)
+ end
+ end
+ end
+end
diff --git test/integration/lib/redmine/drag_drop_ordering_test.rb test/integration/lib/redmine/drag_drop_ordering_test.rb
new file mode 100644
index 0000000..cc47a2a
--- /dev/null
+++ test/integration/lib/redmine/drag_drop_ordering_test.rb
@@ -0,0 +1,51 @@
+# Redmine - project management software
+# Copyright (C) 2006-2015 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require File.expand_path('../../../../test_helper', __FILE__)
+
+class DragDropOrderingTest < Redmine::IntegrationTest
+
+ fixtures :users, :custom_fields, :enumerations, :issue_statuses, :roles, :trackers
+
+ def setup
+ log_user("admin", "admin")
+ end
+
+ def test_reorder_all_uri
+ resource_names = ['custom_fields', 'enumerations', 'issue_statuses', 'roles', 'trackers']
+ resource_names.each do |resource_name|
+ # custom_fields --> CustomField
+ model_klass = resource_name.classify.constantize
+ desc_resource_ids = model_klass.all.order('position DESC').pluck(:id)
+ new_positions = (1..desc_resource_ids.count).to_a
+ # ex) {"3"=>"1", "2"=>"2", "1"=>"3"}
+ id_and_positions = Hash[*desc_resource_ids.zip(new_positions).flatten]
+
+ # ex) post "/custom_fields/reorder_all", {'id_and_positions' => {"3"=>"1","2"=>"2","3"=>"1"}}
+ post "/#{resource_name}/reorder_all", {'id_and_positions' => id_and_positions}
+
+ assert_redirected_to "/#{resource_name}"
+
+ id_and_positions.each do |id, position|
+ assert_equal position, model_klass.find(id).position
+ end
+
+ asc_resource_ids = model_klass.all.order('position ASC').pluck(:id)
+ assert_equal asc_resource_ids, desc_resource_ids
+ end
+ end
+end
diff --git test/integration/routing/reorder_all_test.rb test/integration/routing/reorder_all_test.rb
new file mode 100644
index 0000000..aa8be5c
--- /dev/null
+++ test/integration/routing/reorder_all_test.rb
@@ -0,0 +1,28 @@
+# Redmine - project management software
+# Copyright (C) 2006-2015 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require File.expand_path('../../../test_helper', __FILE__)
+
+class ReorderAllTest < Redmine::RoutingTest
+
+ def test_reorder_all
+ resource_names = ['custom_fields', 'enumerations', 'issue_statuses', 'roles', 'trackers']
+ resource_names.each do |resource_name|
+ should_route "POST /#{resource_name}/reorder_all" => "#{resource_name}#reorder_all"
+ end
+ end
+end