Feature #12909 » drag_drop_ordering_resource_trunk_r15195.patch
| app/controllers/custom_fields_controller.rb | ||
|---|---|---|
| 17 | 17 | |
| 18 | 18 |
class CustomFieldsController < ApplicationController |
| 19 | 19 |
layout 'admin' |
| 20 |
include Redmine::DragDropOrdering::Controller |
|
| 20 | 21 | |
| 21 | 22 |
before_filter :require_admin |
| 22 | 23 |
before_filter :build_new_custom_field, :only => [:new, :create] |
| app/controllers/enumerations_controller.rb | ||
|---|---|---|
| 17 | 17 | |
| 18 | 18 |
class EnumerationsController < ApplicationController |
| 19 | 19 |
layout 'admin' |
| 20 |
include Redmine::DragDropOrdering::Controller |
|
| 20 | 21 | |
| 21 | 22 |
before_filter :require_admin, :except => :index |
| 22 | 23 |
before_filter :require_admin_or_api_request, :only => :index |
| app/controllers/issue_statuses_controller.rb | ||
|---|---|---|
| 17 | 17 | |
| 18 | 18 |
class IssueStatusesController < ApplicationController |
| 19 | 19 |
layout 'admin' |
| 20 |
include Redmine::DragDropOrdering::Controller |
|
| 20 | 21 | |
| 21 | 22 |
before_filter :require_admin, :except => :index |
| 22 | 23 |
before_filter :require_admin_or_api_request, :only => :index |
| app/controllers/roles_controller.rb | ||
|---|---|---|
| 17 | 17 | |
| 18 | 18 |
class RolesController < ApplicationController |
| 19 | 19 |
layout 'admin' |
| 20 |
include Redmine::DragDropOrdering::Controller |
|
| 20 | 21 | |
| 21 | 22 |
before_filter :require_admin, :except => [:index, :show] |
| 22 | 23 |
before_filter :require_admin_or_api_request, :only => [:index, :show] |
| app/controllers/trackers_controller.rb | ||
|---|---|---|
| 17 | 17 | |
| 18 | 18 |
class TrackersController < ApplicationController |
| 19 | 19 |
layout 'admin' |
| 20 |
include Redmine::DragDropOrdering::Controller |
|
| 20 | 21 | |
| 21 | 22 |
before_filter :require_admin, :except => :index |
| 22 | 23 |
before_filter :require_admin_or_api_request, :only => :index |
| app/helpers/application_helper.rb | ||
|---|---|---|
| 28 | 28 |
include Redmine::SudoMode::Helper |
| 29 | 29 |
include Redmine::Themes::Helper |
| 30 | 30 |
include Redmine::Hook::Helper |
| 31 |
include Redmine::DragDropOrdering::Helper |
|
| 31 | 32 | |
| 32 | 33 |
extend Forwardable |
| 33 | 34 |
def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter |
| app/models/custom_field.rb | ||
|---|---|---|
| 17 | 17 | |
| 18 | 18 |
class CustomField < ActiveRecord::Base |
| 19 | 19 |
include Redmine::SubclassFactory |
| 20 |
include Redmine::DragDropOrdering::Model |
|
| 20 | 21 | |
| 21 | 22 |
has_many :enumerations, |
| 22 | 23 |
lambda { order(:position) },
|
| app/models/enumeration.rb | ||
|---|---|---|
| 17 | 17 | |
| 18 | 18 |
class Enumeration < ActiveRecord::Base |
| 19 | 19 |
include Redmine::SubclassFactory |
| 20 |
include Redmine::DragDropOrdering::Model |
|
| 20 | 21 | |
| 21 | 22 |
default_scope lambda {order(:position)}
|
| 22 | 23 | |
| app/models/issue_status.rb | ||
|---|---|---|
| 16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 17 | 17 | |
| 18 | 18 |
class IssueStatus < ActiveRecord::Base |
| 19 |
include Redmine::DragDropOrdering::Model |
|
| 20 | ||
| 19 | 21 |
before_destroy :check_integrity |
| 20 | 22 |
has_many :workflows, :class_name => 'WorkflowTransition', :foreign_key => "old_status_id" |
| 21 | 23 |
has_many :workflow_transitions_as_new_status, :class_name => 'WorkflowTransition', :foreign_key => "new_status_id" |
| app/models/role.rb | ||
|---|---|---|
| 16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 17 | 17 | |
| 18 | 18 |
class Role < ActiveRecord::Base |
| 19 |
include Redmine::DragDropOrdering::Model |
|
| 20 | ||
| 19 | 21 |
# Custom coder for the permissions attribute that should be an |
| 20 | 22 |
# array of symbols. Rails 3 uses Psych which can be *unbelievably* |
| 21 | 23 |
# slow on some platforms (eg. mingw32). |
| app/models/tracker.rb | ||
|---|---|---|
| 16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 17 | 17 | |
| 18 | 18 |
class Tracker < ActiveRecord::Base |
| 19 |
include Redmine::DragDropOrdering::Model |
|
| 19 | 20 | |
| 20 | 21 |
CORE_FIELDS_UNDISABLABLE = %w(project_id tracker_id subject description priority_id is_private).freeze |
| 21 | 22 |
# Fields that can be disabled |
| app/views/custom_fields/_index.html.erb | ||
|---|---|---|
| 1 |
<table class="list"> |
|
| 1 |
<%= form_tag reorder_all_custom_fields_path, :method => 'post', :id => 'sortable_form' do %> |
|
| 2 |
<table class="list" id="sortable_table"> |
|
| 2 | 3 |
<thead><tr> |
| 3 | 4 |
<th><%=l(:field_name)%></th> |
| 4 | 5 |
<th><%=l(:field_field_format)%></th> |
| ... | ... | |
| 14 | 15 |
<% (@custom_fields_by_type[tab[:name]] || []).sort.each do |custom_field| -%> |
| 15 | 16 |
<% back_url = custom_fields_path(:tab => tab[:name]) %> |
| 16 | 17 |
<tr class="<%= cycle("odd", "even") %>">
|
| 17 |
<td class="name"><%= link_to custom_field.name, edit_custom_field_path(custom_field) %></td> |
|
| 18 |
<td class="name"> |
|
| 19 |
<span class="sort-handle ui-icon ui-icon-arrowthick-2-n-s"></span> |
|
| 20 |
<%= hidden_field_tag "id_and_positions[#{custom_field.id}]", custom_field.position, :class => 'position' %>
|
|
| 21 |
<%= link_to custom_field.name, edit_custom_field_path(custom_field) %> |
|
| 22 |
</td> |
|
| 18 | 23 |
<td><%= l(custom_field.format.label) %></td> |
| 19 | 24 |
<td><%= checked_image custom_field.is_required? %></td> |
| 20 | 25 |
<% if tab[:name] == 'IssueCustomField' %> |
| ... | ... | |
| 29 | 34 |
<% end; reset_cycle %> |
| 30 | 35 |
</tbody> |
| 31 | 36 |
</table> |
| 37 |
<% end %> |
|
| 38 | ||
| 39 |
<%= sortable_js('sortable_form', 'sortable_table') %>
|
|
| app/views/enumerations/index.html.erb | ||
|---|---|---|
| 5 | 5 | |
| 6 | 6 |
<% enumerations = klass.shared %> |
| 7 | 7 |
<% if enumerations.any? %> |
| 8 |
<table class="list"><thead> |
|
| 8 |
<%= form_tag reorder_all_enumerations_path, :method => 'post', :id => "#{klass.name.underscore}_form" do %>
|
|
| 9 |
<table class="list" id="<%= "#{klass.name.underscore}_table" %>"><thead>
|
|
| 9 | 10 |
<tr> |
| 10 | 11 |
<th><%= l(:field_name) %></th> |
| 11 | 12 |
<th><%= l(:field_is_default) %></th> |
| ... | ... | |
| 15 | 16 |
</tr></thead> |
| 16 | 17 |
<% enumerations.each do |enumeration| %> |
| 17 | 18 |
<tr class="<%= cycle('odd', 'even') %>">
|
| 18 |
<td class="name"><%= link_to enumeration, edit_enumeration_path(enumeration) %></td> |
|
| 19 |
<td class="name"> |
|
| 20 |
<span class="sort-handle ui-icon ui-icon-arrowthick-2-n-s"></span> |
|
| 21 |
<%= hidden_field_tag "id_and_positions[#{enumeration.id}]", enumeration.position, :class => 'position' %>
|
|
| 22 |
<%= link_to enumeration, edit_enumeration_path(enumeration) %> |
|
| 23 |
</td> |
|
| 19 | 24 |
<td class="tick"><%= checked_image enumeration.is_default? %></td> |
| 20 | 25 |
<td class="tick"><%= checked_image enumeration.active? %></td> |
| 21 | 26 |
<td class="reorder"><%= reorder_links('enumeration', {:action => 'update', :id => enumeration}, :put) %></td>
|
| ... | ... | |
| 23 | 28 |
</tr> |
| 24 | 29 |
<% end %> |
| 25 | 30 |
</table> |
| 31 |
<% end %> |
|
| 32 |
<%= sortable_js("#{klass.name.underscore}_form", "#{klass.name.underscore}_table") %>
|
|
| 26 | 33 |
<% reset_cycle %> |
| 27 | 34 |
<% end %> |
| 28 | 35 | |
| app/views/issue_statuses/index.html.erb | ||
|---|---|---|
| 5 | 5 | |
| 6 | 6 |
<h2><%=l(:label_issue_status_plural)%></h2> |
| 7 | 7 | |
| 8 |
<table class="list"> |
|
| 8 |
<%= form_tag reorder_all_issue_statuses_path, :method => 'post', :id => 'sortable_form' do %> |
|
| 9 |
<table class="list" id="sortable_table"> |
|
| 9 | 10 |
<thead><tr> |
| 10 | 11 |
<th><%=l(:field_status)%></th> |
| 11 | 12 |
<% if Issue.use_status_for_done_ratio? %> |
| ... | ... | |
| 18 | 19 |
<tbody> |
| 19 | 20 |
<% for status in @issue_statuses %> |
| 20 | 21 |
<tr class="<%= cycle("odd", "even") %>">
|
| 21 |
<td class="name"><%= link_to status.name, edit_issue_status_path(status) %></td> |
|
| 22 |
<td class="name"> |
|
| 23 |
<span class="sort-handle ui-icon ui-icon-arrowthick-2-n-s"></span> |
|
| 24 |
<%= hidden_field_tag "id_and_positions[#{status.id}]", status.position, :class => 'position' %>
|
|
| 25 |
<%= link_to status.name, edit_issue_status_path(status) %> |
|
| 26 |
</td> |
|
| 22 | 27 |
<% if Issue.use_status_for_done_ratio? %> |
| 23 | 28 |
<td><%= status.default_done_ratio %></td> |
| 24 | 29 |
<% end %> |
| ... | ... | |
| 31 | 36 |
<% end %> |
| 32 | 37 |
</tbody> |
| 33 | 38 |
</table> |
| 39 |
<% end %> |
|
| 34 | 40 | |
| 35 | 41 |
<span class="pagination"><%= pagination_links_full @issue_status_pages %></span> |
| 36 | 42 | |
| 37 | 43 |
<% html_title(l(:label_issue_status_plural)) -%> |
| 44 |
<%= sortable_js('sortable_form', 'sortable_table') %>
|
|
| app/views/roles/index.html.erb | ||
|---|---|---|
| 5 | 5 | |
| 6 | 6 |
<h2><%=l(:label_role_plural)%></h2> |
| 7 | 7 | |
| 8 |
<table class="list"> |
|
| 8 |
<%= form_tag reorder_all_roles_path, :method => 'post', :id => 'sortable_form' do %> |
|
| 9 |
<table class="list" id="sortable_table"> |
|
| 9 | 10 |
<thead><tr> |
| 10 | 11 |
<th><%=l(:label_role)%></th> |
| 11 | 12 |
<th><%=l(:button_sort)%></th> |
| ... | ... | |
| 14 | 15 |
<tbody> |
| 15 | 16 |
<% for role in @roles %> |
| 16 | 17 |
<tr class="<%= cycle("odd", "even") %>">
|
| 17 |
<td class="name"><%= content_tag(role.builtin? ? 'em' : 'span', link_to(role.name, edit_role_path(role))) %></td> |
|
| 18 |
<td class="name"> |
|
| 19 |
<% unless role.builtin? %> |
|
| 20 |
<span class="sort-handle ui-icon ui-icon-arrowthick-2-n-s"></span> |
|
| 21 |
<%= hidden_field_tag "id_and_positions[#{role.id}]", role.position, :class => 'position' %>
|
|
| 22 |
<% end %> |
|
| 23 |
<%= content_tag(role.builtin? ? 'em' : 'span', link_to(role.name, edit_role_path(role))) %> |
|
| 24 |
</td> |
|
| 18 | 25 |
<td class="reorder"> |
| 19 | 26 |
<% unless role.builtin? %> |
| 20 | 27 |
<%= reorder_links('role', {:action => 'update', :id => role, :page => params[:page]}, :put) %>
|
| ... | ... | |
| 28 | 35 |
<% end %> |
| 29 | 36 |
</tbody> |
| 30 | 37 |
</table> |
| 38 |
<% end %> |
|
| 31 | 39 | |
| 32 | 40 |
<span class="pagination"><%= pagination_links_full @role_pages %></span> |
| 33 | 41 | |
| 34 | 42 |
<% html_title(l(:label_role_plural)) -%> |
| 43 |
<%= sortable_js('sortable_form', 'sortable_table') %>
|
|
| app/views/trackers/index.html.erb | ||
|---|---|---|
| 5 | 5 | |
| 6 | 6 |
<h2><%=l(:label_tracker_plural)%></h2> |
| 7 | 7 | |
| 8 |
<table class="list"> |
|
| 8 |
<%= form_tag reorder_all_trackers_path, :method => 'post', :id => 'sortable_form' do %> |
|
| 9 |
<table class="list" id="sortable_table"> |
|
| 9 | 10 |
<thead><tr> |
| 10 | 11 |
<th><%=l(:label_tracker)%></th> |
| 11 | 12 |
<th></th> |
| ... | ... | |
| 15 | 16 |
<tbody> |
| 16 | 17 |
<% for tracker in @trackers %> |
| 17 | 18 |
<tr class="<%= cycle("odd", "even") %>">
|
| 18 |
<td class="name"><%= link_to tracker.name, edit_tracker_path(tracker) %></td> |
|
| 19 |
<td class="name"> |
|
| 20 |
<span class="sort-handle ui-icon ui-icon-arrowthick-2-n-s"></span> |
|
| 21 |
<%= hidden_field_tag "id_and_positions[#{tracker.id}]", tracker.position, :class => 'position' %>
|
|
| 22 |
<%= link_to tracker.name, edit_tracker_path(tracker) %> |
|
| 23 |
</td> |
|
| 19 | 24 |
<td> |
| 20 | 25 |
<% unless tracker.workflow_rules.count > 0 %> |
| 21 | 26 |
<span class="icon icon-warning"> |
| ... | ... | |
| 33 | 38 |
<% end %> |
| 34 | 39 |
</tbody> |
| 35 | 40 |
</table> |
| 41 |
<% end %> |
|
| 36 | 42 | |
| 37 | 43 |
<span class="pagination"><%= pagination_links_full @tracker_pages %></span> |
| 38 | 44 | |
| 39 | 45 |
<% html_title(l(:label_tracker_plural)) -%> |
| 46 |
<%= sortable_js('sortable_form', 'sortable_table') %>
|
|
| config/routes.rb | ||
|---|---|---|
| 376 | 376 |
end |
| 377 | 377 |
end |
| 378 | 378 |
end |
| 379 |
extend Redmine::DragDropOrdering::Routes |
|
| 379 | 380 |
end |
| lib/redmine.rb | ||
|---|---|---|
| 63 | 63 |
require 'redmine/hook/listener' |
| 64 | 64 |
require 'redmine/hook/view_listener' |
| 65 | 65 |
require 'redmine/plugin' |
| 66 |
require 'redmine/drag_drop_ordering' |
|
| 66 | 67 | |
| 67 | 68 |
Redmine::Scm::Base.add "Subversion" |
| 68 | 69 |
Redmine::Scm::Base.add "Darcs" |
| lib/redmine/drag_drop_ordering.rb | ||
|---|---|---|
| 1 |
require 'active_support/concern' |
|
| 2 | ||
| 3 |
module Redmine |
|
| 4 |
module DragDropOrdering |
|
| 5 |
module Model |
|
| 6 |
extend ActiveSupport::Concern |
|
| 7 |
module ClassMethods |
|
| 8 |
def reorder_all(id_and_positions) |
|
| 9 |
return unless id_and_positions.is_a?(Hash) |
|
| 10 |
transaction do |
|
| 11 |
id_and_positions.each do |obj_id, position| |
|
| 12 |
obj = self.find(obj_id) |
|
| 13 |
obj.position = position.to_i |
|
| 14 |
unless obj.save |
|
| 15 |
raise ActiveRecord::Rollback |
|
| 16 |
end |
|
| 17 |
end |
|
| 18 |
end |
|
| 19 |
end |
|
| 20 |
end |
|
| 21 |
end |
|
| 22 | ||
| 23 |
module Controller |
|
| 24 |
def reorder_all |
|
| 25 |
model_klass = self.controller_name.classify.constantize |
|
| 26 |
model_klass.reorder_all(params[:id_and_positions]) |
|
| 27 |
redirect_to :action => :index |
|
| 28 |
end |
|
| 29 |
end |
|
| 30 | ||
| 31 |
module Routes |
|
| 32 |
def self.extended(routes) |
|
| 33 |
routes.instance_exec do |
|
| 34 |
concern :sortable do |
|
| 35 |
post :reorder_all, :on => :collection |
|
| 36 |
end |
|
| 37 |
resources :issue_statuses, :concerns => :sortable |
|
| 38 |
resources :custom_fields, :concerns => :sortable |
|
| 39 |
resources :enumerations, :concerns => :sortable |
|
| 40 |
resources :trackers, :concerns => :sortable |
|
| 41 |
resources :roles, :concerns => :sortable |
|
| 42 |
end |
|
| 43 |
end |
|
| 44 |
end |
|
| 45 | ||
| 46 |
module Helper |
|
| 47 |
def sortable_js(form_id, table_id) |
|
| 48 |
js = <<-"EOS" |
|
| 49 |
$(function() {
|
|
| 50 |
$('##{table_id} tbody').sortable({
|
|
| 51 |
handle: '.sort-handle', |
|
| 52 |
update: function(event, ui) {
|
|
| 53 |
$('##{table_id} tr').each(function(){
|
|
| 54 |
$(this).find('input.position').val($(this).index()+1);
|
|
| 55 |
}); |
|
| 56 |
$('form##{form_id}').submit();
|
|
| 57 |
} |
|
| 58 |
}); |
|
| 59 |
}); |
|
| 60 |
EOS |
|
| 61 |
javascript_tag(js.html_safe) |
|
| 62 |
end |
|
| 63 |
end |
|
| 64 |
end |
|
| 65 |
end |
|
| test/integration/lib/redmine/drag_drop_ordering_test.rb | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2015 Jean-Philippe Lang |
|
| 3 |
# |
|
| 4 |
# This program is free software; you can redistribute it and/or |
|
| 5 |
# modify it under the terms of the GNU General Public License |
|
| 6 |
# as published by the Free Software Foundation; either version 2 |
|
| 7 |
# of the License, or (at your option) any later version. |
|
| 8 |
# |
|
| 9 |
# This program is distributed in the hope that it will be useful, |
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 12 |
# GNU General Public License for more details. |
|
| 13 |
# |
|
| 14 |
# You should have received a copy of the GNU General Public License |
|
| 15 |
# along with this program; if not, write to the Free Software |
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 17 | ||
| 18 |
require File.expand_path('../../../../test_helper', __FILE__)
|
|
| 19 | ||
| 20 |
class DragDropOrderingTest < Redmine::IntegrationTest |
|
| 21 |
|
|
| 22 |
fixtures :users, :custom_fields, :enumerations, :issue_statuses, :roles, :trackers |
|
| 23 |
|
|
| 24 |
def setup |
|
| 25 |
log_user("admin", "admin")
|
|
| 26 |
end |
|
| 27 | ||
| 28 |
def test_reorder_all_uri |
|
| 29 |
resource_names = ['custom_fields', 'enumerations', 'issue_statuses', 'roles', 'trackers'] |
|
| 30 |
resource_names.each do |resource_name| |
|
| 31 |
# custom_fields --> CustomField |
|
| 32 |
model_klass = resource_name.classify.constantize |
|
| 33 |
desc_resource_ids = model_klass.all.order('position DESC').pluck(:id)
|
|
| 34 |
new_positions = (1..desc_resource_ids.count).to_a |
|
| 35 |
# ex) {"3"=>"1", "2"=>"2", "1"=>"3"}
|
|
| 36 |
id_and_positions = Hash[*desc_resource_ids.zip(new_positions).flatten] |
|
| 37 | ||
| 38 |
# ex) post "/custom_fields/reorder_all", {'id_and_positions' => {"3"=>"1","2"=>"2","3"=>"1"}}
|
|
| 39 |
post "/#{resource_name}/reorder_all", {'id_and_positions' => id_and_positions}
|
|
| 40 | ||
| 41 |
assert_redirected_to "/#{resource_name}"
|
|
| 42 | ||
| 43 |
id_and_positions.each do |id, position| |
|
| 44 |
assert_equal position, model_klass.find(id).position |
|
| 45 |
end |
|
| 46 | ||
| 47 |
asc_resource_ids = model_klass.all.order('position ASC').pluck(:id)
|
|
| 48 |
assert_equal asc_resource_ids, desc_resource_ids |
|
| 49 |
end |
|
| 50 |
end |
|
| 51 |
end |
|
| test/integration/routing/reorder_all_test.rb | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2015 Jean-Philippe Lang |
|
| 3 |
# |
|
| 4 |
# This program is free software; you can redistribute it and/or |
|
| 5 |
# modify it under the terms of the GNU General Public License |
|
| 6 |
# as published by the Free Software Foundation; either version 2 |
|
| 7 |
# of the License, or (at your option) any later version. |
|
| 8 |
# |
|
| 9 |
# This program is distributed in the hope that it will be useful, |
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 12 |
# GNU General Public License for more details. |
|
| 13 |
# |
|
| 14 |
# You should have received a copy of the GNU General Public License |
|
| 15 |
# along with this program; if not, write to the Free Software |
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 17 | ||
| 18 |
require File.expand_path('../../../test_helper', __FILE__)
|
|
| 19 | ||
| 20 |
class ReorderAllTest < Redmine::RoutingTest |
|
| 21 | ||
| 22 |
def test_reorder_all |
|
| 23 |
resource_names = ['custom_fields', 'enumerations', 'issue_statuses', 'roles', 'trackers'] |
|
| 24 |
resource_names.each do |resource_name| |
|
| 25 |
should_route "POST /#{resource_name}/reorder_all" => "#{resource_name}#reorder_all"
|
|
| 26 |
end |
|
| 27 |
end |
|
| 28 |
end |
|