Project

General

Profile

Feature #13839 » custom_field_layout_editing.diff

Alisa M, 2017-02-13 21:41

View differences:

app/helpers/projects_helper.rb
22 22
    tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
23 23
            {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
24 24
            {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
25
            {:name => 'custom_fields_layouts', :action => :manage_custom_fields_layout, :partial => 'projects/settings/custom_fields_layout', :label => :label_custom_field_plural},
25 26
            {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
26 27
            {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
27 28
            {:name => 'resolutions', :action => :manage_resolutions, :partial => 'projects/settings/issue_resolutions', :label => :label_issue_resolution_plural},
app/helpers/custom_fields_helper.rb
85 85

  
86 86
    content_tag "label", content +
87 87
      (required ? " <span class=\"required\">*</span>".html_safe : ""),
88
      :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
88
      :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}", :class => "custom_field_label"
89 89
  end
90 90

  
91 91
  # Return custom field tag with its label tag
app/models/custom_field_layout.rb
1
class CustomFieldLayout < ActiveRecord::Base
2
  
3
end
app/controllers/custom_fields_layouts_controller.rb
1
class CustomFieldsLayoutsController < ApplicationController
2
  helper :all
3
  before_filter :find_custom_field_values
4

  
5
  def new
6
    @layout = CustomFieldLayout.new(project_id: @project.id, tracker_id: @tracker.id)
7
  end
8
 
9
  def apply
10
    if @cf_layout.nil? 
11
      @layout = CustomFieldLayout.new(project_id: @project.id, tracker_id: @tracker.id)
12
      @layout.location =  JSON.parse(params[:ids])
13
      @layout.project_id = @project.id
14
      @layout.tracker_id = @tracker.id
15
        if @layout.save
16
          redirect_to settings_project_path(@project)
17
        end
18
    else
19
      @cf_layout.location =  JSON.parse(params[:ids])
20
        if @cf_layout.save
21
          redirect_to settings_project_path(@project)
22
        end
23
    end 
24
  end
25

  
26
  def show
27
    respond_to do |format|
28
      format.js
29
    end
30
  end
31
  
32
  private
33
    def find_custom_field_values
34
      @project = Project.find(params[:project_id])
35
      @tracker = Tracker.find(params[:layout][:tracker_id]) 
36
      
37
      #get last edited custom field location 
38
      @cf_layout = CustomFieldLayout.find_by({:tracker_id => @tracker.id, :project_id => @project.id})
39
      cf_ids = @cf_layout.nil? ? [[],[]] : JSON.parse(@cf_layout.location.gsub("nil","null"))
40
      
41
      #get ids of nil elements
42
      ids_of_nil = [[],[]]
43
      cf_ids.each_index{ |i| cf_ids[i].each_index.select{|k| ids_of_nil[i] << k if cf_ids[i][k].nil?}}.map!{|i|  i -= [nil]}
44
      
45
      #get default custom field location
46
      default_values = (@project.issues.new(:tracker_id => @tracker.id, 
47
                                       :subject => " ", :author_id => @current_user).editable_custom_field_values)     
48

  
49
      #if not set yet then default
50
      if !cf_ids.all?{|array| array.empty?}
51
        ordered_values = default_values.group_by(&:custom_field_id).values_at(*cf_ids.flatten).flatten(1)
52
        added =  default_values - ordered_values
53
        @custom_fields_values = [ordered_values[0 ... cf_ids.first.count],
54
                                 ordered_values[cf_ids.first.count .. -1] + added]
55
        ids_of_nil.each_index{|i| ids_of_nil[i].each{|v| @custom_fields_values[i].insert(v,nil)}} 
56
      else
57
        @custom_fields_values = [default_values[0 ... default_values.count/2], default_values[default_values.count/2 .. -1]]
58
      end
59
    end
60
end
app/controllers/projects_controller.rb
161 161
    @member ||= @project.members.new
162 162
    @trackers = Tracker.sorted.to_a
163 163
    @wiki ||= @project.wiki || Wiki.new(:project => @project)
164
    @layout =  CustomFieldLayout.new()
165
   
164 166
  end
165 167

  
166 168
  def edit
app/views/custom_fields_layouts/show.js.erb
1
$("#cfv").html('<%= escape_javascript(render(partial: "show"))%>');
2

  
app/views/custom_fields_layouts/_custom_fields_layout.html.erb
1
<div class="splitcontent">
2
<div class="splitcontentleft">
3
  <div class="sortable">
4
    <% i = 0 %>
5
    <% custom_fields_values.each do |side|%>
6
      <% side.each do |value|%>
7
        <% if !value.nil? %>
8
          <p class="custom_field_p" id=<%=value.custom_field_id%>>
9
            <%= custom_field_tag_with_label :issue, value, :required => issue.required_attribute?(value.custom_field_id) %>
10
          </p>
11
        <%else %>
12
          <p class="custom_field_p clean"%></p>
13
        <% end %>
14
      <% end -%>
15
      <% i = i + 1 %>  
16
      <% if (i == 1) %>
17
       </div></div> <div class="splitcontentright"> <div class="sortable">   
18
      <% end -%>
19
    <% end -%>
20
  </div>
21
<p> </p>
22
</div>
23
</div>
24

  
app/views/custom_fields_layouts/_show.html.erb
1
<% issue = @project.issues.new(tracker_id: @tracker.id, subject: " ", author_id: @current_user) %>
2

  
3
<%= render partial: "custom_fields_layout" , locals: {issue: issue, custom_fields_values:  @custom_fields_values} %>
4

  
5

  
app/views/projects/settings/_custom_fields_layout.html.erb
1
<h2><%= l(:label_custom_field_layout) %></h2>
2
<script>
3

  
4
function addBlank(){
5
  $(".splitcontent").children().find(".sortable").each(
6
    function(){
7
      var cf_count =  $(this).children().length;
8
      if (cf_count == 0){ $(this).append("<p class='custom_field_p clean'> </p>") };
9
    });
10

  
11
}
12

  
13
/*add sortable ui to added elements*/
14
function loadCf(){
15
  if($('.custom_field_label').length != 0) {
16
   
17
    /*add fields for removing/adding if there is smth to add/remove */ 
18
    $(".add_remove").append(" <p class='custom_field_remove' > <%= l(:label_remove_field) %> </p>");
19
    $(".copy").append("<p class='custom_field_p clean'> <%= l(:label_move_field) %> </p> ");
20

  
21
    /*add blank div by default if any splitcontent is fully empty 
22
      to show where we can move element*/ 
23
    addBlank();
24
     
25
    /*make borders of divs visible*/
26
    $(".custom_field_p, .custom_field_remove").css({"border" : "thin solid",
27
                                                    "border-color" : "#ddd"});
28
    $(".custom_field_remove").css({"border-color" : "#B00"});
29
    $(".custom_field_p.clean").css({"height" : "2.3em"});
30
    $(".sortable").css({"min-height": "4.6em", 
31
                        "border-style" : "ridge", 
32
                        "padding" : "4px",
33
                        "border-width": "1px", 
34
                        "border-color" : "#dddFFF"});
35

  
36

  
37
    /*make elements sortable (allow reordering)*/
38
    /*not allow moving last elements in the splitcontent(left|right) */
39
    var is_alone = 1;
40
    $(".sortable").sortable({connectWith: ".sortable",
41
                             dropOnEmpty: true,
42
                             receive: function(e,ui) {
43
                                         copyHelper= null;}
44
                            });
45

  
46
    /*allow to copy blank div and make it sortable*/
47
    $( ".copy" ).sortable({
48
      connectWith: ".sortable",
49
      forcePlaceholderSize: false,
50
      helper: function(e,li) {
51
        copyHelper= li.clone().insertAfter(li.text(""));
52
        return li.clone();
53
      },
54
      stop: function() {
55
        copyHelper && copyHelper.remove();
56
      }
57
    });
58

  
59
    /* make field droppable (moving div inside droppable one removes it)*/
60
    $('.custom_field_remove').droppable({ 
61
      accept: ".clean",
62
      drop: function(event, ui) { ui.draggable.remove(); } 
63
   });
64
 
65
}};
66

  
67
/*get custom fields for selected tracker*/
68
function getCustomFields() {
69
  $(".clean").remove();
70
  $(".custom_field_remove").remove();
71
  var tracker = $("#layout_tracker_id").find(":selected").val(); 
72
  $.ajax({
73
    method: "get",
74
    url: "<%= url_for :controller=>'custom_fields_layouts', :action => 'show', :project_id => @project %>",
75
    contentType: 'application/json',
76
    data: {layout: { tracker_id:  tracker}},
77
    cache: false,
78
    error: function(jqXHR, textStatus, errorThrown){
79
      alert('Exception' + errorThrown);
80
     },
81
   success: function(){ loadCf(); }
82
  })
83
};
84

  
85
/*get custom fields after loading*/
86
$(document).ready(function(){
87
  getCustomFields();
88
});
89

  
90

  
91

  
92
/*set custom field ids on hidden field */
93
function setCfIds(){
94
  var array = [];
95
  var left = [];
96
  var right = []; 
97
  $("#cfv").find(".splitcontentleft").find(".custom_field_p").each(function() {
98
   left.push(parseInt($(this).attr("id")));
99
   });
100
  $("#cfv").find(".splitcontentright").find(".custom_field_p").each(function() {
101
    right.push(parseInt($(this).attr("id")));
102
   });
103
  array.push(left,right);
104
  $("#custom_fields_locations").val(JSON.stringify(array)); 
105
} ;
106

  
107
/* don't need datepicker functionality for editing locations of custom fields*/
108

  
109

  
110

  
111
</script>
112

  
113
<div class="box tabular">
114

  
115
<%= labelled_form_for @layout,:as => 'layout', url: {controller: "custom_fields_layouts", 
116
                                                     action: "apply", :project_id => @project} do |f| %>
117

  
118
  <% include_calendar_headers_tags %>
119
  <% if @project.issues.new.safe_attribute? 'tracker_id' %>
120
    <p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, {:required => true},
121
                                                                                  :onchange => "getCustomFields();" %></p>
122
  <%end%>
123
  
124
  <div class= "add_remove"> </div><p > </p> 
125
 
126
  <div id="cfv">  </div> 
127

  
128
  <%= hidden_field_tag  :ids, "", :id => "custom_fields_locations" %>
129
  <%= submit_tag l(:button_update), :onclick => "setCfIds();"  %>
130
  
131
  <div class ="splitcontentright copy">  </div> 
132

  
133
<%end %>
134
</div>
app/views/issues/show.html.erb
72 72
    rows.right l(:label_spent_time), (@issue.total_spent_hours > 0 ? link_to(l_hours(@issue.total_spent_hours), issue_time_entries_path(@issue)) : "-"), :class => 'spent-time'
73 73
  end
74 74
end %>
75

  
76

  
75 77
<%= render_custom_fields_rows(@issue) %>
76 78
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
77 79
</table>
80

  
81

  
lib/redmine.rb
86 86
  map.permission :manage_members, {:projects => :settings, :members => [:index, :show, :new, :create, :update, :destroy, :autocomplete]}, :require => :member
87 87
  map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
88 88
  map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
89

  
89
  map.permission :manage_custom_fields_layout, {:projects => :settings, :custom_fields_editing => [:apply]}, :require => :member
90
  
90 91
  map.project_module :issue_tracking do |map|
91 92
    # Issues
92 93
    map.permission :view_issues, {:issues => [:index, :show],
app/views/issues/_form_custom_fields.html.erb
1
<% custom_field_values = @issue.editable_custom_field_values %>
2
<% if custom_field_values.present? %>
3
<div class="splitcontent">
4
<div class="splitcontentleft">
5
<% i = 0 %>
6
<% split_on = (custom_field_values.size / 2.0).ceil - 1 %>
7
<% custom_field_values.each do |value| %>
8
  <p><%= custom_field_tag_with_label :issue, value, :required => @issue.required_attribute?(value.custom_field_id) %></p>
9
<% if i == split_on -%>
10
</div><div class="splitcontentright">
11
<% end -%>
12
<% i += 1 -%>
13
<% end -%>
14
</div>
15
</div>
16
<% end %>
1
<%  cf_layout  = CustomFieldLayout.all.where(:tracker_id => @issue.tracker_id, :project_id => @project.id).last %>
2
<%  default_values = @issue.editable_custom_field_values %>
3
<%  cf_ids = cf_layout.nil? ? [[],[]] : JSON.parse(cf_layout.location.gsub("nil","null")) %>
4
      
5
<%  ids_of_nil = [[],[]] %>
6
<%  cf_ids.each_index{ |i| cf_ids[i].each_index.select{|k| ids_of_nil[i] << k if cf_ids[i][k].nil?}}.map!{|i|  i -= [nil]} %>
7

  
8
<%  if !cf_ids.all?{|array| array.empty?} %>
9
<%    ordered_values = default_values.group_by(&:custom_field_id).values_at(*cf_ids.flatten).flatten(1) %>
10
<%    added = default_values - ordered_values %>
11
<%    custom_fields_values = [ordered_values[0 ... cf_ids.first.count],
12
                              ordered_values[cf_ids.first.count .. -1] + added] %>
13
<%    ids_of_nil.each_index{|i| ids_of_nil[i].each{|v| custom_fields_values[i].insert(v,nil)}} %>
14
<%  else %>
15
<%    custom_fields_values = [default_values[0 ... default_values.count/2], default_values[default_values.count/2 .. -1]] %>
16
<%  end %>
17

  
18
<%= render partial: "custom_fields_layouts/custom_fields_layout" , locals: {issue: @issue, custom_fields_values: custom_fields_values} %>
19

  
20

  
config/routes.rb
136 136
    get 'versions.:format', :to => 'versions#index'
137 137
    get 'roadmap', :to => 'versions#index', :format => false
138 138
    get 'versions', :to => 'versions#index'
139
 
140
    resources :custom_fields_layouts, :only => [:show]
141
    match 'settings/custom_fields_layouts', :to => 'custom_fields_layouts#apply', :via => [:post]
139 142

  
140 143
    resources :news, :except => [:show, :edit, :update, :destroy]
141 144
    resources :time_entries, :controller => 'timelog', :except => [:show, :edit, :update, :destroy] do
app/helpers/issues_helper.rb
220 220
    r.to_html
221 221
  end
222 222

  
223
  #add empty elements for proper cfs showing
224
  def add_nills(cfs)
225
    diff  = cfs[0].count - cfs[1].count
226
    if (diff > 0)
227
      cfs[1] += [nil] * diff
228
    elsif (diff < 0)
229
      cfs[0] += [nil] * (-diff)
230
    end
231
    cfs
232
  end
233

  
223 234
  def render_custom_fields_rows(issue)
224
    values = issue.visible_custom_field_values
225
    return if values.empty?
226
    half = (values.size / 2.0).ceil
227
    issue_fields_rows do |rows|
228
      values.each_with_index do |value, i|
229
        css = "cf_#{value.custom_field.id}"
230
        m = (i < half ? :left : :right)
231
        rows.send m, custom_field_name_tag(value.custom_field), show_value(value), :class => css
235
      default_values = issue.visible_custom_field_values
236
    return if default_values.empty?
237

  
238
    #get last edited custom field location 
239
    cf_layout = CustomFieldLayout.find_by(:tracker_id => issue.tracker_id, :project_id => issue.project_id)
240
    cf_ids = cf_layout.nil? ? [[],[]] : JSON.parse(cf_layout.location.gsub("nil","null"))
241

  
242
    #get ids of nil elements
243
    ids_of_nil = [[],[]]
244
    cf_ids.each_index{ |i| cf_ids[i].each_index.select{|k| ids_of_nil[i] << k if cf_ids[i][k].nil?}}.map!{|i|  i -= [nil]}
245

  
246
    #if not set yet then default
247
    if !cf_ids.all?{|array| array.empty?}
248
      ordered_values = default_values.group_by(&:custom_field_id).values_at(*cf_ids.flatten).flatten(1)
249
      added = default_values - ordered_values
250
      custom_fields_values = [ordered_values[0 ... cf_ids.first.count],
251
                              ordered_values[cf_ids.first.count .. -1] + added]
252
      ids_of_nil.each_index{|i| ids_of_nil[i].each{|v| custom_fields_values[i].insert(v,nil)}}
253
    else
254
      custom_fields_values = [default_values[0 ... default_values.count/2], default_values[default_values.count/2 .. -1]]
255
    end
256
    custom_fields_values = add_nills(custom_fields_values)
257
    cfs = custom_fields_values[0].zip(custom_fields_values[1]).flatten
258

  
259
    s = "<tr>\n"
260
    n = 0
261
    cfs.each do |value|
262
      s << "</tr>\n<tr>\n" if n > 0 && (n % 2) == 0
263
      if value.nil?
264
        css = "custom_field_p clean"
265
        s << "\t<th class=\"#{css}\"> &nbsp; </th><td class=\"#{css}\"> &nbsp; </td>\n"
266
      else
267
        css = "cf_#{value.custom_field_id}"
268
        s << "\t<th class=\"#{css}\">#{ h(value.custom_field.name) }:</th><td class=\"#{css}\">#{ h(show_value(value)) }</td>\n"
232 269
      end
270
      n +=1
233 271
    end
272
    s << "</tr>\n"
273
    s.html_safe
234 274
  end
235 275
  # Returns the path for updating the issue form
config/locales/ru.yml
469 469
  label_current_version: Текущая версия
470 470
  label_custom_field: Настраиваемое поле
471 471
  label_custom_field_new: Новое настраиваемое поле
472
  label_custom_field_layout: Редактирование расположения кастомных полей
472 473
  label_custom_field_plural: Настраиваемые поля
473 474
  label_date_from: С
474 475
  label_date_from_to: С %{start} по %{end}
......
577 578
  label_month: Месяц
578 579
  label_more_than_ago: более, чем дней(я) назад
579 580
  label_more: Больше
581
  label_move_field: Переместите пустое поле
580 582
  label_my_account: Моя учётная запись
581 583
  label_my_page: Моя страница
582 584
  label_my_page_block: Блок моей страницы
......
625 627
  label_project_plural2: проекта
626 628
  label_project_plural5: проектов
627 629
  label_public_projects: Общие проекты
630
  label_remove_field: Удалите пустое поле, перреместив его сюда 
628 631
  label_query: Сохранённый запрос
629 632
  label_query_new: Новый запрос
630 633
  label_query_plural: Сохранённые запросы
config/locales/en.yml (revision 1956)
551 551
  label_custom_field: Custom field
552 552
  label_custom_field_plural: Custom fields
553 553
  label_custom_field_new: New custom field
554
  label_custom_field_layout: Edit custom fields layout
554 555
  label_enumerations: Enumerations
555 556
  label_enumeration_new: New value
556 557
  label_information: Information
......
560 561
  label_login_with_open_id_option: or login with OpenID
561 562
  label_password_lost: Lost password
562 563
  label_home: Home
564
  label_move_field: Move blank field 
563 565
  label_my_page: My page
564 566
  label_my_account: My account
565 567
  label_my_projects: My projects
......
572 574
  label_assigned_to_me_issues: Issues assigned to me
573 575
  label_last_login: Last connection
574 576
  label_registered_on: Registered on
577
  label_remove_field: Remove blank fields moving them here
575 578
  label_activity: Activity
576 579
  label_overall_activity: Overall activity
577 580
  label_user_activity: "%{value}'s activity"
(4-4/4)