Feature #27672 » 0001-Show-selected-columns-on-gantt.patch
| app/views/gantts/show.html.erb | ||
|---|---|---|
| 28 | 28 |
<legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend> |
| 29 | 29 |
<div style="display: none;"> |
| 30 | 30 |
<table> |
| 31 |
<tr> |
|
| 32 |
<td> |
|
| 33 |
<fieldset> |
|
| 34 |
<legend> |
|
| 35 |
<%= l(:field_column_names) %> |
|
| 36 |
</legend> |
|
| 37 |
<%= render_query_columns_selection(@query) %> |
|
| 38 |
</fieldset> |
|
| 39 |
</td> |
|
| 40 |
</tr> |
|
| 31 | 41 |
<tr> |
| 32 | 42 |
<td> |
| 33 | 43 |
<fieldset> |
| ... | ... | |
| 92 | 102 | |
| 93 | 103 |
subject_width = 330 |
| 94 | 104 |
header_height = 18 |
| 105 |
selected_column_width = 50 |
|
| 95 | 106 | |
| 96 | 107 |
headers_height = header_height |
| 97 | 108 |
show_weeks = false |
| ... | ... | |
| 157 | 168 |
<% end %> |
| 158 | 169 |
<% end %> |
| 159 | 170 |
</td> |
| 160 | ||
| 161 |
<td> |
|
| 171 |
<% @query.columns.each do |column| %> |
|
| 172 |
<% next if Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.include?(column.name) %> |
|
| 173 |
<td style="width:<%= selected_column_width %>px; padding:0px;" class="gantt_<%= column.name %>_column gantt_selected_column" id="<%= column.name %>"> |
|
| 174 |
<% |
|
| 175 |
style = "position:relative;" |
|
| 176 |
style += "height: #{t_height + 24}px;"
|
|
| 177 |
style += "width: #{selected_column_width + 1}px;"
|
|
| 178 |
%> |
|
| 179 |
<%= content_tag(:div, :style => style, :class => "gantt_#{column.name}_container") do %>
|
|
| 180 |
<% |
|
| 181 |
style = "width: #{selected_column_width}px;"
|
|
| 182 |
style += "height: #{t_height}px;"
|
|
| 183 |
style += 'border-left: 0px;' |
|
| 184 |
style += 'overflow: hidden;' |
|
| 185 |
%> |
|
| 186 |
<%= content_tag(:div, '', :style => style, :class => "gantt_hdr") %> |
|
| 187 |
<% |
|
| 188 |
style = "width: #{selected_column_width}px;"
|
|
| 189 |
style += "height: #{headers_height}px;"
|
|
| 190 |
style += 'background: #eee;' |
|
| 191 |
style += 'border-left: 0px !important;' |
|
| 192 |
%> |
|
| 193 |
<%= content_tag(:div, content_tag(:p, column.caption, :class => 'gantt_hdr_selected_column_name'), :style => style, :class => "gantt_hdr") %> |
|
| 194 |
<%= content_tag(:div, :class => "gantt_#{column.name} gantt_selected_column_content") do %>
|
|
| 195 |
<%= @gantt.selected_column_content({:column => column, :top => headers_height + 8, :zoom => zoom, :g_width => g_width}).html_safe %>
|
|
| 196 |
<% end %> |
|
| 197 |
<% end %> |
|
| 198 |
</td> |
|
| 199 |
<% end %> |
|
| 200 |
<td style='padding: 0px;'> |
|
| 162 | 201 |
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt_area"> |
| 163 | 202 |
<% |
| 164 | 203 |
style = "" |
| ... | ... | |
| 371 | 410 |
<%= javascript_tag do %> |
| 372 | 411 |
var issue_relation_type = <%= raw Redmine::Helpers::Gantt::DRAW_TYPES.to_json %>; |
| 373 | 412 |
$(function() {
|
| 413 |
disable_unavailable_columns('<%= Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.map{|column| column.to_s }.join(',') %>'.split(','));
|
|
| 374 | 414 |
drawGanttHandler(); |
| 375 | 415 |
resizableSubjectColumn(); |
| 376 | 416 |
$("#draw_relations").change(drawGanttHandler);
|
| lib/redmine/helpers/gantt.rb | ||
|---|---|---|
| 32 | 32 |
IssueRelation::TYPE_PRECEDES => { :landscape_margin => 20, :color => '#628FEA' }
|
| 33 | 33 |
}.freeze |
| 34 | 34 | |
| 35 |
UNAVAILABLE_COLUMNS = [:id, :subject] |
|
| 36 | ||
| 35 | 37 |
# :nodoc: |
| 36 | 38 |
# Some utility methods for the PDF export |
| 37 | 39 |
class PDF |
| ... | ... | |
| 76 | 78 |
@date_to = (@date_from >> @months) - 1 |
| 77 | 79 |
@subjects = '' |
| 78 | 80 |
@lines = '' |
| 81 |
@columns = {}
|
|
| 79 | 82 |
@number_of_rows = nil |
| 80 | 83 |
@truncated = false |
| 81 | 84 |
if options.has_key?(:max_rows) |
| ... | ... | |
| 135 | 138 |
@lines |
| 136 | 139 |
end |
| 137 | 140 | |
| 141 |
# Renders the selected column of the Gantt chart, the right side of subjects. |
|
| 142 |
def selected_column_content(options={})
|
|
| 143 |
render(options.merge(:only => :selected_columns)) unless @columns.has_key?(options[:column].name) |
|
| 144 |
@columns[options[:column].name] |
|
| 145 |
end |
|
| 146 | ||
| 138 | 147 |
# Returns issues that will be rendered |
| 139 | 148 |
def issues |
| 140 | 149 |
@issues ||= @query.issues( |
| ... | ... | |
| 196 | 205 |
:indent_increment => 20, :render => :subject, |
| 197 | 206 |
:format => :html}.merge(options) |
| 198 | 207 |
indent = options[:indent] || 4 |
| 199 |
@subjects = '' unless options[:only] == :lines |
|
| 200 |
@lines = '' unless options[:only] == :subjects |
|
| 208 |
@subjects = '' unless options[:only] == :lines || options[:only] == :selected_columns |
|
| 209 |
@lines = '' unless options[:only] == :subjects || options[:only] == :selected_columns |
|
| 210 |
@columns[options[:column].name] = '' if options[:only] == :selected_columns && @columns.has_key?(options[:column]) == false |
|
| 201 | 211 |
@number_of_rows = 0 |
| 202 | 212 |
begin |
| 203 | 213 |
Project.project_tree(projects) do |project, level| |
| ... | ... | |
| 207 | 217 |
rescue MaxLinesLimitReached |
| 208 | 218 |
@truncated = true |
| 209 | 219 |
end |
| 210 |
@subjects_rendered = true unless options[:only] == :lines |
|
| 211 |
@lines_rendered = true unless options[:only] == :subjects |
|
| 220 |
@subjects_rendered = true unless options[:only] == :lines || options[:only] == :selected_columns
|
|
| 221 |
@lines_rendered = true unless options[:only] == :subjects || options[:only] == :selected_columns
|
|
| 212 | 222 |
render_end(options) |
| 213 | 223 |
end |
| 214 | 224 | |
| ... | ... | |
| 254 | 264 | |
| 255 | 265 |
def render_object_row(object, options) |
| 256 | 266 |
class_name = object.class.name.downcase |
| 257 |
send("subject_for_#{class_name}", object, options) unless options[:only] == :lines
|
|
| 258 |
send("line_for_#{class_name}", object, options) unless options[:only] == :subjects
|
|
| 267 |
send("subject_for_#{class_name}", object, options) unless options[:only] == :lines || options[:only] == :selected_columns
|
|
| 268 |
send("line_for_#{class_name}", object, options) unless options[:only] == :subjects || options[:only] == :selected_columns
|
|
| 269 |
column_content_for_issue(object, options) if options[:only] == :selected_columns && options[:column].present? && object.is_a?(Issue) |
|
| 259 | 270 |
options[:top] += options[:top_increment] |
| 260 | 271 |
@number_of_rows += 1 |
| 261 | 272 |
if @max_rows && @number_of_rows >= @max_rows |
| ... | ... | |
| 323 | 334 |
end |
| 324 | 335 |
end |
| 325 | 336 | |
| 337 |
def column_content_for_issue(issue, options) |
|
| 338 |
if options[:format] == :html |
|
| 339 |
style = "position: absolute;top: #{options[:top]}px; font-size: 0.8em;"
|
|
| 340 |
content = view.content_tag(:div, view.column_content(options[:column], issue), :style => style, :class => "issue_#{options[:column].name}", :id => "#{options[:column].name}_issue_#{issue.id}")
|
|
| 341 |
@columns[options[:column].name] << content if @columns.has_key?(options[:column].name) |
|
| 342 |
content |
|
| 343 |
end |
|
| 344 |
end |
|
| 345 | ||
| 326 | 346 |
def subject(label, options, object=nil) |
| 327 | 347 |
send "#{options[:format]}_subject", options, label, object
|
| 328 | 348 |
end |
| public/javascripts/gantt.js | ||
|---|---|---|
| 161 | 161 |
} |
| 162 | 162 |
} |
| 163 | 163 | |
| 164 |
function drawSelectedColumns(){
|
|
| 165 |
if(isMobile()) {
|
|
| 166 |
$('td.gantt_selected_column').each(function(i) {
|
|
| 167 |
$(this).hide(); |
|
| 168 |
}); |
|
| 169 |
}else{
|
|
| 170 |
$('#content').addClass("gantt_content");
|
|
| 171 |
$('td.gantt_selected_column').each(function() {
|
|
| 172 |
$(this).show(); |
|
| 173 |
var column_name = $(this).attr('id');
|
|
| 174 |
$(this).resizable({
|
|
| 175 |
alsoResize: `.gantt_${column_name}_container, .gantt_${column_name}_container > .gantt_hdr`,
|
|
| 176 |
minWidth: 20, |
|
| 177 |
handles: "e", |
|
| 178 |
create: function( event, ui ) {
|
|
| 179 |
$(".ui-resizable-e").css("cursor","ew-resize");
|
|
| 180 |
} |
|
| 181 |
}).on('resize', function (e) {
|
|
| 182 |
e.stopPropagation(); |
|
| 183 |
}); |
|
| 184 |
}); |
|
| 185 |
} |
|
| 186 |
} |
|
| 187 | ||
| 164 | 188 |
function drawGanttHandler() {
|
| 165 | 189 |
var folder = document.getElementById('gantt_draw_area');
|
| 166 | 190 |
if(draw_gantt != null) |
| ... | ... | |
| 168 | 192 |
else |
| 169 | 193 |
draw_gantt = Raphael(folder); |
| 170 | 194 |
setDrawArea(); |
| 195 |
drawSelectedColumns(); |
|
| 171 | 196 |
if ($("#draw_progress_line").prop('checked'))
|
| 172 | 197 |
drawGanttProgressLines(); |
| 173 | 198 |
if ($("#draw_relations").prop('checked'))
|
| ... | ... | |
| 195 | 220 |
$('td.gantt_subjects_column').resizable('enable');
|
| 196 | 221 |
}; |
| 197 | 222 |
} |
| 223 | ||
| 224 |
function disable_unavailable_columns(unavailable_columns){
|
|
| 225 |
$.each(unavailable_columns, function(index, value) {
|
|
| 226 |
$('#available_c, #selected_c').children("[value='" + value + "']").prop('disabled', true);
|
|
| 227 |
}); |
|
| 228 |
} |
|
| public/stylesheets/application.css | ||
|---|---|---|
| 1169 | 1169 | |
| 1170 | 1170 |
.gantt_hdr.nwday {background-color:#f1f1f1; color:#999;}
|
| 1171 | 1171 | |
| 1172 |
.gantt_subjects { font-size: 0.8em; }
|
|
| 1173 |
.gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
|
|
| 1172 |
.gantt_subjects, .gantt_selected_column_content.gantt_hdr { font-size: 0.8em; }
|
|
| 1173 |
.gantt_subjects div, .gantt_selected_column_content div {
|
|
| 1174 |
line-height: 16px; |
|
| 1175 |
height: 16px; |
|
| 1176 |
white-space: nowrap; |
|
| 1177 |
text-overflow: ellipsis; |
|
| 1178 |
overflow: hidden !important; |
|
| 1179 |
width: 100%; |
|
| 1180 |
} |
|
| 1174 | 1181 |
.gantt_subjects div.issue-subject:hover { background-color:#ffffdd; }
|
| 1182 |
.gantt_selected_column_content { padding-left: 3px; padding-right: 3px;}
|
|
| 1175 | 1183 | |
| 1176 | 1184 |
.gantt_subjects .issue-subject img.icon-gravatar {
|
| 1177 | 1185 |
margin: 2px 5px 0px 2px; |
| 1178 | 1186 |
} |
| 1187 |
.gantt_hdr_selected_column_name {
|
|
| 1188 |
padding-top: 15px; |
|
| 1189 |
font-size: 0.8em; |
|
| 1190 |
white-space: nowrap; |
|
| 1191 |
text-overflow: ellipsis; |
|
| 1192 |
overflow: hidden; |
|
| 1193 |
} |
|
| 1194 |
.gantt_content {
|
|
| 1195 |
overflow: scroll; |
|
| 1196 |
} |
|
| 1179 | 1197 | |
| 1180 | 1198 |
.task {
|
| 1181 | 1199 |
position: absolute; |
| test/unit/lib/redmine/helpers/gantt_test.rb | ||
|---|---|---|
| 23 | 23 | |
| 24 | 24 |
include ProjectsHelper |
| 25 | 25 |
include IssuesHelper |
| 26 |
include QueriesHelper |
|
| 26 | 27 |
include ERB::Util |
| 27 | 28 |
include Rails.application.routes.url_helpers |
| 28 | 29 | |
| ... | ... | |
| 237 | 238 |
assert_select "div.tooltip", /#{@issue.subject}/
|
| 238 | 239 |
end |
| 239 | 240 | |
| 241 |
test "#selected_column_content" do |
|
| 242 |
create_gantt |
|
| 243 |
issue = Issue.generate! |
|
| 244 |
@gantt.query.column_names = [:assigned_to] |
|
| 245 |
issue.update(:assigned_to_id => issue.assignable_users.first.id) |
|
| 246 |
@project.issues << issue |
|
| 247 |
# :column => assigned_to |
|
| 248 |
@output_buffer = @gantt.selected_column_content({ :column => @gantt.query.columns.last })
|
|
| 249 |
assert_select "div.issue_assigned_to#assigned_to_issue_#{issue.id}"
|
|
| 250 |
end |
|
| 251 | ||
| 240 | 252 |
test "#subject_for_project" do |
| 241 | 253 |
create_gantt |
| 242 | 254 |
@output_buffer = @gantt.subject_for_project(@project, :format => :html) |
| ... | ... | |
| 418 | 430 |
assert_select "div.label", :text => 'line' |
| 419 | 431 |
end |
| 420 | 432 | |
| 433 |
test "#column_content_for_issue" do |
|
| 434 |
create_gantt |
|
| 435 |
@gantt.query.column_names = [:assigned_to] |
|
| 436 |
issue = Issue.generate! |
|
| 437 |
issue.update(:assigned_to_id => issue.assignable_users.first.id) |
|
| 438 |
@project.issues << issue |
|
| 439 |
# :column => assigned_to |
|
| 440 |
options = { :column => @gantt.query.columns.last, :top => 64, :format => :html }
|
|
| 441 |
@output_buffer = @gantt.column_content_for_issue(issue, options) |
|
| 442 | ||
| 443 |
assert_select "div.issue_assigned_to#assigned_to_issue_#{issue.id}"
|
|
| 444 |
assert_includes @output_buffer, column_content(options[:column], issue) |
|
| 445 |
end |
|
| 446 | ||
| 421 | 447 |
def test_sort_issues_no_date |
| 422 | 448 |
project = Project.generate! |
| 423 | 449 |
issue1 = Issue.generate!(:subject => "test", :project => project) |