Feature #6417 » 6417-collapse-expand-gantt-v2.patch
| 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 | ||
|---|---|---|
| 195 | 195 |         options = {:top => 0, :top_increment => 20, | 
| 196 | 196 | :indent_increment => 20, :render => :subject, | 
| 197 | 197 | :format => :html}.merge(options) | 
| 198 | options[:indent_increment ] += 12 if options[:format] == :html | |
| 198 | 199 | indent = options[:indent] || 4 | 
| 199 | 200 | @subjects = '' unless options[:only] == :lines | 
| 200 | 201 | @lines = '' unless options[:only] == :subjects | 
| ... | ... | |
| 221 | 222 | # then render project versions and their issues | 
| 222 | 223 | versions = project_versions(project) | 
| 223 | 224 | self.class.sort_versions!(versions) | 
| 225 | indent = options[:indent] | |
| 224 | 226 | versions.each do |version| | 
| 227 | options[:indent] = indent | |
| 225 | 228 | render_version(project, version, options) | 
| 226 | 229 | end | 
| 227 | 230 | end | 
| ... | ... | |
| 698 | 701 | end | 
| 699 | 702 | |
| 700 | 703 | 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 | 704 | content = html_subject_content(object) || subject | 
| 704 |         tag_options = {:style => style} | |
| 705 |         tag_options = {} | |
| 705 | 706 | case object | 
| 706 | 707 | when Issue | 
| 707 | 708 |           tag_options[:id] = "issue-#{object.id}" | 
| 708 | 709 | tag_options[:class] = "issue-subject hascontextmenu" | 
| 709 | 710 | tag_options[:title] = object.subject | 
| 711 | children = object.children & project_issues(object.project) | |
| 712 | has_children = children.present? && (children.collect(&:fixed_version).uniq & [object.fixed_version]).present? | |
| 710 | 713 | when Version | 
| 711 | 714 |           tag_options[:id] = "version-#{object.id}" | 
| 712 | 715 | tag_options[:class] = "version-name" | 
| 716 | has_children = object.fixed_issues.exists? | |
| 713 | 717 | when Project | 
| 714 | 718 | tag_options[:class] = "project-name" | 
| 719 | has_children = object.issues.exists? || object.versions.exists? | |
| 720 | end | |
| 721 |         tag_options[:data] = { | |
| 722 |           :collapse_expand => { | |
| 723 | :top_increment => params[:top_increment], | |
| 724 |             :obj_id => "#{object.class}-#{object.id}".downcase, | |
| 725 | }, | |
| 726 | } | |
| 727 | if has_children | |
| 728 | content = view.content_tag(:span, nil, :class => :expander) + content | |
| 729 | params = params.dup | |
| 730 | params[:indent] -= 12 if params[:indent] >= 12 | |
| 731 | tag_options[:class] << ' open' | |
| 715 | 732 | end | 
| 733 |         style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;" | |
| 734 |         style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width] | |
| 735 | tag_options[:style] = style | |
| 716 | 736 | output = view.content_tag(:div, content, tag_options) | 
| 717 | 737 | @subjects << output | 
| 718 | 738 | output | 
| ... | ... | |
| 751 | 771 | |
| 752 | 772 | def html_task(params, coords, markers, label, object) | 
| 753 | 773 | output = '' | 
| 774 |         data_options = { | |
| 775 |           :collapse_expand => "#{object.class}-#{object.id}".downcase, | |
| 776 | } | |
| 754 | 777 | |
| 755 | 778 | css = "task " + case object | 
| 756 | 779 | when Project | 
| ... | ... | |
| 774 | 797 |           html_id = "task-todo-version-#{object.id}" if object.is_a?(Version) | 
| 775 | 798 |           content_opt = {:style => style, | 
| 776 | 799 |                          :class => "#{css} task_todo", | 
| 777 | :id => html_id} | |
| 800 | :id => html_id, | |
| 801 |                          :data => {}} | |
| 778 | 802 | if object.is_a?(Issue) | 
| 779 | 803 | rels = issue_relations(object) | 
| 780 | 804 | if rels.present? | 
| 781 | 805 |               content_opt[:data] = {"rels" => rels.to_json} | 
| 782 | 806 | end | 
| 783 | 807 | end | 
| 808 | content_opt[:data].merge!(data_options) | |
| 784 | 809 | output << view.content_tag(:div, ' '.html_safe, content_opt) | 
| 785 | 810 | if coords[:bar_late_end] | 
| 786 | 811 | width = coords[:bar_late_end] - coords[:bar_start] - 2 | 
| ... | ... | |
| 790 | 815 |             style << "width:#{width}px;" | 
| 791 | 816 | output << view.content_tag(:div, ' '.html_safe, | 
| 792 | 817 | :style => style, | 
| 793 |                                        :class => "#{css} task_late") | |
| 818 |                                        :class => "#{css} task_late", | |
| 819 | :data => data_options) | |
| 794 | 820 | end | 
| 795 | 821 | if coords[:bar_progress_end] | 
| 796 | 822 | width = coords[:bar_progress_end] - coords[:bar_start] - 2 | 
| ... | ... | |
| 803 | 829 | output << view.content_tag(:div, ' '.html_safe, | 
| 804 | 830 | :style => style, | 
| 805 | 831 |                                        :class => "#{css} task_done", | 
| 806 | :id => html_id) | |
| 832 | :id => html_id, | |
| 833 | :data => data_options) | |
| 807 | 834 | end | 
| 808 | 835 | end | 
| 809 | 836 | # Renders the markers | 
| ... | ... | |
| 815 | 842 | style << "width:15px;" | 
| 816 | 843 | output << view.content_tag(:div, ' '.html_safe, | 
| 817 | 844 | :style => style, | 
| 818 |                                        :class => "#{css} marker starting") | |
| 845 |                                        :class => "#{css} marker starting", | |
| 846 | :data => data_options) | |
| 819 | 847 | end | 
| 820 | 848 | if coords[:end] | 
| 821 | 849 | style = "" | 
| ... | ... | |
| 824 | 852 | style << "width:15px;" | 
| 825 | 853 | output << view.content_tag(:div, ' '.html_safe, | 
| 826 | 854 | :style => style, | 
| 827 |                                        :class => "#{css} marker ending") | |
| 855 |                                        :class => "#{css} marker ending", | |
| 856 | :data => data_options) | |
| 828 | 857 | end | 
| 829 | 858 | end | 
| 830 | 859 | # Renders the label on the right | 
| ... | ... | |
| 835 | 864 | style << "width:15px;" | 
| 836 | 865 | output << view.content_tag(:div, label, | 
| 837 | 866 | :style => style, | 
| 838 |                                      :class => "#{css} label") | |
| 867 |                                      :class => "#{css} label", | |
| 868 | :data => data_options) | |
| 839 | 869 | end | 
| 840 | 870 | # Renders the tooltip | 
| 841 | 871 | if object.is_a?(Issue) && coords[:bar_start] && coords[:bar_end] | 
| ... | ... | |
| 851 | 881 | style << "height:12px;" | 
| 852 | 882 | output << view.content_tag(:div, s.html_safe, | 
| 853 | 883 | :style => style, | 
| 854 | :class => "tooltip hascontextmenu") | |
| 884 | :class => "tooltip hascontextmenu", | |
| 885 | :data => data_options) | |
| 855 | 886 | end | 
| 856 | 887 | @lines << output | 
| 857 | 888 | 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; } |