Project

General

Profile

Feature #12909 » drag_drop_ordering_resource_trunk_r15195.patch

Takenori TAKAKI, 2016-03-07 07:16

View differences:

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
(1-1/2)