diff --git a/app/views/gantts/show.html.erb b/app/views/gantts/show.html.erb
index 90f6a85fa..e076bce4b 100644
--- a/app/views/gantts/show.html.erb
+++ b/app/views/gantts/show.html.erb
@@ -375,6 +375,7 @@
resizableSubjectColumn();
$("#draw_relations").change(drawGanttHandler);
$("#draw_progress_line").change(drawGanttHandler);
+ $('div.gantt_subjects .expander').on('click', collapseOrExpandGantt);
});
$(window).resize(function() {
drawGanttHandler();
diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb
index 312a3133a..3a243abfa 100644
--- a/lib/redmine/helpers/gantt.rb
+++ b/lib/redmine/helpers/gantt.rb
@@ -195,6 +195,7 @@ module Redmine
options = {:top => 0, :top_increment => 20,
:indent_increment => 20, :render => :subject,
:format => :html}.merge(options)
+ options[:indent_increment ] += 12 if options[:format] == :html
indent = options[:indent] || 4
@subjects = '' unless options[:only] == :lines
@lines = '' unless options[:only] == :subjects
@@ -221,7 +222,9 @@ module Redmine
# then render project versions and their issues
versions = project_versions(project)
self.class.sort_versions!(versions)
+ indent = options[:indent]
versions.each do |version|
+ options[:indent] = indent
render_version(project, version, options)
end
end
@@ -698,21 +701,36 @@ module Redmine
end
def html_subject(params, subject, object)
- style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
- style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
content = html_subject_content(object) || subject
- tag_options = {:style => style}
+ tag_options = {}
case object
when Issue
tag_options[:id] = "issue-#{object.id}"
tag_options[:class] = "issue-subject hascontextmenu"
tag_options[:title] = object.subject
+ has_children = object.children?
when Version
tag_options[:id] = "version-#{object.id}"
tag_options[:class] = "version-name"
+ has_children = object.fixed_issues.exists?
when Project
tag_options[:class] = "project-name"
+ has_children = object.issues.exists? || object.versions.exists?
+ end
+ tag_options[:data] = {
+ :collapse_expand => {
+ :top_increment => params[:top_increment],
+ :obj_id => "#{object.class}-#{object.id}".downcase,
+ },
+ }
+ if has_children
+ content = view.content_tag(:span, nil, :class => :expander) + content
+ params[:indent] -= 12 if params[:indent] >= 12
+ tag_options[:class] << ' open'
end
+ style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
+ style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
+ tag_options[:style] = style
output = view.content_tag(:div, content, tag_options)
@subjects << output
output
@@ -751,6 +769,9 @@ module Redmine
def html_task(params, coords, markers, label, object)
output = ''
+ data_options = {
+ :collapse_expand => "#{object.class}-#{object.id}".downcase,
+ }
css = "task " + case object
when Project
@@ -774,13 +795,15 @@ module Redmine
html_id = "task-todo-version-#{object.id}" if object.is_a?(Version)
content_opt = {:style => style,
:class => "#{css} task_todo",
- :id => html_id}
+ :id => html_id,
+ :data => {}}
if object.is_a?(Issue)
rels = issue_relations(object)
if rels.present?
content_opt[:data] = {"rels" => rels.to_json}
end
end
+ content_opt[:data].merge!(data_options)
output << view.content_tag(:div, ' '.html_safe, content_opt)
if coords[:bar_late_end]
width = coords[:bar_late_end] - coords[:bar_start] - 2
@@ -790,7 +813,8 @@ module Redmine
style << "width:#{width}px;"
output << view.content_tag(:div, ' '.html_safe,
:style => style,
- :class => "#{css} task_late")
+ :class => "#{css} task_late",
+ :data => data_options)
end
if coords[:bar_progress_end]
width = coords[:bar_progress_end] - coords[:bar_start] - 2
@@ -803,7 +827,8 @@ module Redmine
output << view.content_tag(:div, ' '.html_safe,
:style => style,
:class => "#{css} task_done",
- :id => html_id)
+ :id => html_id,
+ :data => data_options)
end
end
# Renders the markers
@@ -815,7 +840,8 @@ module Redmine
style << "width:15px;"
output << view.content_tag(:div, ' '.html_safe,
:style => style,
- :class => "#{css} marker starting")
+ :class => "#{css} marker starting",
+ :data => data_options)
end
if coords[:end]
style = ""
@@ -824,7 +850,8 @@ module Redmine
style << "width:15px;"
output << view.content_tag(:div, ' '.html_safe,
:style => style,
- :class => "#{css} marker ending")
+ :class => "#{css} marker ending",
+ :data => data_options)
end
end
# Renders the label on the right
@@ -835,7 +862,8 @@ module Redmine
style << "width:15px;"
output << view.content_tag(:div, label,
:style => style,
- :class => "#{css} label")
+ :class => "#{css} label",
+ :data => data_options)
end
# Renders the tooltip
if object.is_a?(Issue) && coords[:bar_start] && coords[:bar_end]
@@ -851,7 +879,8 @@ module Redmine
style << "height:12px;"
output << view.content_tag(:div, s.html_safe,
:style => style,
- :class => "tooltip hascontextmenu")
+ :class => "tooltip hascontextmenu",
+ :data => data_options)
end
@lines << output
output
diff --git a/public/javascripts/gantt.js b/public/javascripts/gantt.js
index 2e71178b0..436fdebe7 100644
--- a/public/javascripts/gantt.js
+++ b/public/javascripts/gantt.js
@@ -17,6 +17,7 @@ function setDrawArea() {
function getRelationsArray() {
var arr = new Array();
$.each($('div.task_todo[data-rels]'), function(index_div, element) {
+ if(!$(element).is(':visible')) return true;
var element_id = $(element).attr("id");
if (element_id != null) {
var issue_id = element_id.replace("task-todo-issue-", "");
@@ -106,6 +107,7 @@ function getProgressLinesArray() {
var today_left = $('#today_line').position().left;
arr.push({left: today_left, top: 0});
$.each($('div.issue-subject, div.version-name'), function(index, element) {
+ if(!$(element).is(':visible')) return true;
var t = $(element).position().top - draw_top ;
var h = ($(element).height() / 9);
var element_top_upper = t - h;
@@ -169,7 +171,7 @@ function drawGanttHandler() {
draw_gantt = Raphael(folder);
setDrawArea();
if ($("#draw_progress_line").prop('checked'))
- drawGanttProgressLines();
+ try{drawGanttProgressLines();}catch(e){}
if ($("#draw_relations").prop('checked'))
drawRelations();
}
@@ -195,3 +197,59 @@ function resizableSubjectColumn(){
$('td.gantt_subjects_column').resizable('enable');
};
}
+
+collapseOrExpandGantt = function(e){
+ var subject = $(e.target.parentElement);
+ var subject_left = parseInt(subject.css('left'));
+ var target_shown = null;
+ var target_top = 0;
+ var total_height = 0;
+ var out_of_hierarchy = false;
+ var iconChange = null;
+ if(subject.hasClass('open'))
+ iconChange = function(element){
+ $(element).removeClass('open');
+ };
+ else
+ iconChange = function(element){
+ $(element).addClass('open');
+ };
+ iconChange(subject);
+ subject.nextAll('div').each(function(_, element){
+ var el = $(element);
+ var json = el.data('collapse-expand');
+ if(out_of_hierarchy || parseInt(el.css('left')) <= subject_left){
+ out_of_hierarchy = true;
+ if(target_shown == null) return false;
+
+ var new_top_val = parseInt(el.css('top')) + total_height * (target_shown ? -1 : 1);
+ el.css('top', new_top_val);
+ $('#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"]').each(function(_, task){
+ $(task).css('top', new_top_val);
+ });
+ return true;
+ }
+
+ var is_shown = el.is(':visible');
+ if(target_shown == null){
+ target_shown = is_shown;
+ target_top = parseInt(el.css('top'));
+ total_height = 0;
+ }
+ if(is_shown == target_shown){
+ $('#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"]').each(function(_, task){
+ var el_task = $(task);
+ if(!is_shown)
+ el_task.css('top', target_top + total_height);
+ if(!el_task.hasClass('tooltip'))
+ el_task.toggle(!is_shown);
+ });
+ if(!is_shown)
+ el.css('top', target_top + total_height);
+ iconChange(el);
+ el.toggle(!is_shown);
+ total_height += parseInt(json.top_increment);
+ }
+ });
+ drawGanttHandler();
+};
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index 08ad6aa45..ac001e470 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -290,8 +290,9 @@ tr.entry td.age { text-align: right; }
tr.entry.file td.filename a { margin-left: 16px; }
tr.entry.file td.filename_no_report a { margin-left: 16px; }
-tr span.expander {background: url(../images/arrow_right.png) no-repeat 2px 50%; padding-left: 8px; margin-left: 0; cursor: pointer;}
-tr.open span.expander {background-image: url(../images/arrow_down.png);}
+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;}
+tr.open span.expander, .gantt_subjects div.open > span.expander {background-image: url(../images/arrow_down.png);}
+.gantt_subjects div > span.expander {padding-left: 12px;}
tr.changeset { height: 20px }
tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }