Feature #6417 » 6417-collapse-expand-gantt-v3.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 | ||
---|---|---|
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, ' '.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, ' '.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, ' '.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, ' '.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, ' '.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 |
- « Previous
- 1
- …
- 4
- 5
- 6
- Next »