Project

General

Profile

Feature #6417 » 6417-collapse-expand-gantt-v3.patch

Yuichi HARADA, 2019-03-05 03:42

View differences:

app/views/gantts/show.html.erb
375 375
    resizableSubjectColumn();
376 376
    $("#draw_relations").change(drawGanttHandler);
377 377
    $("#draw_progress_line").change(drawGanttHandler);
378
    $('div.gantt_subjects .expander').on('click', ganttEntryClick);
378 379
  });
379 380
  $(window).resize(function() {
380 381
    drawGanttHandler();
lib/redmine/helpers/gantt.rb
698 698
      end
699 699

  
700 700
      def html_subject(params, subject, object)
701
        style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
702
        style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
703 701
        content = html_subject_content(object) || subject
704
        tag_options = {:style => style}
702
        tag_options = {}
705 703
        case object
706 704
        when Issue
707 705
          tag_options[:id] = "issue-#{object.id}"
708 706
          tag_options[:class] = "issue-subject hascontextmenu"
709 707
          tag_options[:title] = object.subject
708
          children = object.children & project_issues(object.project)
709
          has_children = children.present? && (children.collect(&:fixed_version).uniq & [object.fixed_version]).present?
710 710
        when Version
711 711
          tag_options[:id] = "version-#{object.id}"
712 712
          tag_options[:class] = "version-name"
713
          has_children = object.fixed_issues.exists?
713 714
        when Project
714 715
          tag_options[:class] = "project-name"
716
          has_children = object.issues.exists? || object.versions.exists?
717
        end
718
        if object
719
          tag_options[:data] = {
720
            :collapse_expand => {
721
              :top_increment => params[:top_increment],
722
              :obj_id => "#{object.class}-#{object.id}".downcase,
723
            },
724
          }
725
        end
726
        if has_children
727
          content = view.content_tag(:span, nil, :class => :expander) + content
728
          tag_options[:class] << ' open'
729
        else
730
          if params[:indent]
731
            params = params.dup
732
            params[:indent] += 12
733
          end
715 734
        end
735
        style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
736
        style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
737
        tag_options[:style] = style
716 738
        output = view.content_tag(:div, content, tag_options)
717 739
        @subjects << output
718 740
        output
......
751 773

  
752 774
      def html_task(params, coords, markers, label, object)
753 775
        output = ''
776
        data_options = {}
777
        data_options[:collapse_expand] = "#{object.class}-#{object.id}".downcase if object
754 778

  
755 779
        css = "task " + case object
756 780
          when Project
......
774 798
          html_id = "task-todo-version-#{object.id}" if object.is_a?(Version)
775 799
          content_opt = {:style => style,
776 800
                         :class => "#{css} task_todo",
777
                         :id => html_id}
801
                         :id => html_id,
802
                         :data => {}}
778 803
          if object.is_a?(Issue)
779 804
            rels = issue_relations(object)
780 805
            if rels.present?
781 806
              content_opt[:data] = {"rels" => rels.to_json}
782 807
            end
783 808
          end
809
          content_opt[:data].merge!(data_options)
784 810
          output << view.content_tag(:div, '&nbsp;'.html_safe, content_opt)
785 811
          if coords[:bar_late_end]
786 812
            width = coords[:bar_late_end] - coords[:bar_start] - 2
......
790 816
            style << "width:#{width}px;"
791 817
            output << view.content_tag(:div, '&nbsp;'.html_safe,
792 818
                                       :style => style,
793
                                       :class => "#{css} task_late")
819
                                       :class => "#{css} task_late",
820
                                       :data => data_options)
794 821
          end
795 822
          if coords[:bar_progress_end]
796 823
            width = coords[:bar_progress_end] - coords[:bar_start] - 2
......
803 830
            output << view.content_tag(:div, '&nbsp;'.html_safe,
804 831
                                       :style => style,
805 832
                                       :class => "#{css} task_done",
806
                                       :id => html_id)
833
                                       :id => html_id,
834
                                       :data => data_options)
807 835
          end
808 836
        end
809 837
        # Renders the markers
......
815 843
            style << "width:15px;"
816 844
            output << view.content_tag(:div, '&nbsp;'.html_safe,
817 845
                                       :style => style,
818
                                       :class => "#{css} marker starting")
846
                                       :class => "#{css} marker starting",
847
                                       :data => data_options)
819 848
          end
820 849
          if coords[:end]
821 850
            style = ""
......
824 853
            style << "width:15px;"
825 854
            output << view.content_tag(:div, '&nbsp;'.html_safe,
826 855
                                       :style => style,
827
                                       :class => "#{css} marker ending")
856
                                       :class => "#{css} marker ending",
857
                                       :data => data_options)
828 858
          end
829 859
        end
830 860
        # Renders the label on the right
......
835 865
          style << "width:15px;"
836 866
          output << view.content_tag(:div, label,
837 867
                                     :style => style,
838
                                     :class => "#{css} label")
868
                                     :class => "#{css} label",
869
                                     :data => data_options)
839 870
        end
840 871
        # Renders the tooltip
841 872
        if object.is_a?(Issue) && coords[:bar_start] && coords[:bar_end]
......
851 882
          style << "height:12px;"
852 883
          output << view.content_tag(:div, s.html_safe,
853 884
                                     :style => style,
854
                                     :class => "tooltip hascontextmenu")
885
                                     :class => "tooltip hascontextmenu",
886
                                     :data => data_options)
855 887
        end
856 888
        @lines << output
857 889
        output
public/javascripts/gantt.js
17 17
function getRelationsArray() {
18 18
  var arr = new Array();
19 19
  $.each($('div.task_todo[data-rels]'), function(index_div, element) {
20
    if(!$(element).is(':visible')) return true;
20 21
    var element_id = $(element).attr("id");
21 22
    if (element_id != null) {
22 23
      var issue_id = element_id.replace("task-todo-issue-", "");
......
106 107
  var today_left = $('#today_line').position().left;
107 108
  arr.push({left: today_left, top: 0});
108 109
  $.each($('div.issue-subject, div.version-name'), function(index, element) {
110
    if(!$(element).is(':visible')) return true;
109 111
    var t = $(element).position().top - draw_top ;
110 112
    var h = ($(element).height() / 9);
111 113
    var element_top_upper  = t - h;
......
169 171
    draw_gantt = Raphael(folder);
170 172
  setDrawArea();
171 173
  if ($("#draw_progress_line").prop('checked'))
172
    drawGanttProgressLines();
174
    try{drawGanttProgressLines();}catch(e){}
173 175
  if ($("#draw_relations").prop('checked'))
174 176
    drawRelations();
175 177
}
......
195 197
    $('td.gantt_subjects_column').resizable('enable');
196 198
  };
197 199
}
200

  
201
ganttEntryClick = function(e){
202
  var subject = $(e.target.parentElement);
203
  var subject_left = parseInt(subject.css('left'));
204
  var target_shown = null;
205
  var target_top = 0;
206
  var total_height = 0;
207
  var out_of_hierarchy = false;
208
  var iconChange = null;
209
  if(subject.hasClass('open'))
210
    iconChange = function(element){
211
      $(element).removeClass('open');
212
    };
213
  else
214
    iconChange = function(element){
215
      $(element).addClass('open');
216
    };
217
  iconChange(subject);
218
  subject.nextAll('div').each(function(_, element){
219
    var el = $(element);
220
    var json = el.data('collapse-expand');
221
    if(out_of_hierarchy || parseInt(el.css('left')) <= subject_left){
222
      out_of_hierarchy = true;
223
      if(target_shown == null) return false;
224

  
225
      var new_top_val = parseInt(el.css('top')) + total_height * (target_shown ? -1 : 1);
226
      el.css('top', new_top_val);
227
      $('#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"]').each(function(_, task){
228
        $(task).css('top', new_top_val);
229
      });
230
      return true;
231
    }
232

  
233
    var is_shown = el.is(':visible');
234
    if(target_shown == null){
235
      target_shown = is_shown;
236
      target_top = parseInt(el.css('top'));
237
      total_height = 0;
238
    }
239
    if(is_shown == target_shown){
240
      $('#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"]').each(function(_, task){
241
        var el_task = $(task);
242
        if(!is_shown)
243
          el_task.css('top', target_top + total_height);
244
        if(!el_task.hasClass('tooltip'))
245
          el_task.toggle(!is_shown);
246
      });
247
      if(!is_shown)
248
        el.css('top', target_top + total_height);
249
      iconChange(el);
250
      el.toggle(!is_shown);
251
      total_height += parseInt(json.top_increment);
252
    }
253
  });
254
  drawGanttHandler();
255
};
public/stylesheets/application.css
291 291
tr.entry.file td.filename a { margin-left: 16px; }
292 292
tr.entry.file td.filename_no_report a { margin-left: 16px; }
293 293

  
294
tr span.expander {background: url(../images/arrow_right.png) no-repeat 2px 50%; padding-left: 8px; margin-left: 0; cursor: pointer;}
295
tr.open span.expander {background-image: url(../images/arrow_down.png);}
294
tr span.expander, .gantt_subjects div > span.expander {background: url(../images/arrow_right.png) no-repeat 2px 50%; padding-left: 8px; margin-left: 0; cursor: pointer;}
295
tr.open span.expander, .gantt_subjects div.open > span.expander {background-image: url(../images/arrow_down.png);}
296
.gantt_subjects div > span.expander {padding-left: 12px;}
297
.gantt_subjects div > span .icon-gravatar {float: none;}
296 298

  
297 299
tr.changeset { height: 20px }
298 300
tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }
test/unit/lib/redmine/helpers/gantt_test.rb
152 152
    setup_subjects
153 153
    @output_buffer = @gantt.subjects
154 154
    assert_select "div.issue-subject", /#{@issue.subject}/
155
    assert_select 'div.issue-subject[style*="left:44px"]'
155
    # subject 56px: 44px + 12px(collapse/expand icon's width)
156
    assert_select 'div.issue-subject[style*="left:56px"]'
156 157
  end
157 158

  
158 159
  test "#subjects issue assigned to a shared version of another project should be rendered" do
......
200 201
    assert_select 'div.issue-subject[style*="left:44px"]', /#{@issue.subject}/
201 202
    # children 64px
202 203
    assert_select 'div.issue-subject[style*="left:64px"]', /child1/
203
    assert_select 'div.issue-subject[style*="left:64px"]', /child2/
204
    # grandchild 84px
205
    assert_select 'div.issue-subject[style*="left:84px"]', /grandchild/, @output_buffer
204
    # children 76px: 64px + 12px(collapse/expand icon's width)
205
    assert_select 'div.issue-subject[style*="left:76px"]', /child2/
206
    # grandchild 96px: 84px + 12px(collapse/expand icon's width)
207
    assert_select 'div.issue-subject[style*="left:96px"]', /grandchild/, @output_buffer
206 208
  end
207 209

  
208 210
  test "#lines" do
......
298 300
  test "#subject should use the indent option to move the div to the right" do
299 301
    create_gantt
300 302
    @output_buffer = @gantt.subject('subject', :format => :html, :indent => 40)
301
    assert_select 'div[style*="left:40"]'
303
    # subject 52px: 40px(indent) + 12px(collapse/expand icon's width)
304
    assert_select 'div[style*="left:52px"]'
302 305
  end
303 306

  
304 307
  test "#line_for_project" do
(6-6/6)