##// END OF EJS Templates
Set default project version after selecting a different project on the new issue form (#1828)....
Jean-Philippe Lang -
r14406:fb8e348254a2
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,517 +1,522
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 menu_item :new_issue, :only => [:new, :create]
19 menu_item :new_issue, :only => [:new, :create]
20 default_search_scope :issues
20 default_search_scope :issues
21
21
22 before_filter :find_issue, :only => [:show, :edit, :update]
22 before_filter :find_issue, :only => [:show, :edit, :update]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
24 before_filter :authorize, :except => [:index, :new, :create]
24 before_filter :authorize, :except => [:index, :new, :create]
25 before_filter :find_optional_project, :only => [:index, :new, :create]
25 before_filter :find_optional_project, :only => [:index, :new, :create]
26 before_filter :build_new_issue_from_params, :only => [:new, :create]
26 before_filter :build_new_issue_from_params, :only => [:new, :create]
27 accept_rss_auth :index, :show
27 accept_rss_auth :index, :show
28 accept_api_auth :index, :show, :create, :update, :destroy
28 accept_api_auth :index, :show, :create, :update, :destroy
29
29
30 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
30 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
31
31
32 helper :journals
32 helper :journals
33 helper :projects
33 helper :projects
34 helper :custom_fields
34 helper :custom_fields
35 helper :issue_relations
35 helper :issue_relations
36 helper :watchers
36 helper :watchers
37 helper :attachments
37 helper :attachments
38 helper :queries
38 helper :queries
39 include QueriesHelper
39 include QueriesHelper
40 helper :repositories
40 helper :repositories
41 helper :sort
41 helper :sort
42 include SortHelper
42 include SortHelper
43 helper :timelog
43 helper :timelog
44
44
45 def index
45 def index
46 retrieve_query
46 retrieve_query
47 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
47 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
48 sort_update(@query.sortable_columns)
48 sort_update(@query.sortable_columns)
49 @query.sort_criteria = sort_criteria.to_a
49 @query.sort_criteria = sort_criteria.to_a
50
50
51 if @query.valid?
51 if @query.valid?
52 case params[:format]
52 case params[:format]
53 when 'csv', 'pdf'
53 when 'csv', 'pdf'
54 @limit = Setting.issues_export_limit.to_i
54 @limit = Setting.issues_export_limit.to_i
55 if params[:columns] == 'all'
55 if params[:columns] == 'all'
56 @query.column_names = @query.available_inline_columns.map(&:name)
56 @query.column_names = @query.available_inline_columns.map(&:name)
57 end
57 end
58 when 'atom'
58 when 'atom'
59 @limit = Setting.feeds_limit.to_i
59 @limit = Setting.feeds_limit.to_i
60 when 'xml', 'json'
60 when 'xml', 'json'
61 @offset, @limit = api_offset_and_limit
61 @offset, @limit = api_offset_and_limit
62 @query.column_names = %w(author)
62 @query.column_names = %w(author)
63 else
63 else
64 @limit = per_page_option
64 @limit = per_page_option
65 end
65 end
66
66
67 @issue_count = @query.issue_count
67 @issue_count = @query.issue_count
68 @issue_pages = Paginator.new @issue_count, @limit, params['page']
68 @issue_pages = Paginator.new @issue_count, @limit, params['page']
69 @offset ||= @issue_pages.offset
69 @offset ||= @issue_pages.offset
70 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
70 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
71 :order => sort_clause,
71 :order => sort_clause,
72 :offset => @offset,
72 :offset => @offset,
73 :limit => @limit)
73 :limit => @limit)
74 @issue_count_by_group = @query.issue_count_by_group
74 @issue_count_by_group = @query.issue_count_by_group
75
75
76 respond_to do |format|
76 respond_to do |format|
77 format.html { render :template => 'issues/index', :layout => !request.xhr? }
77 format.html { render :template => 'issues/index', :layout => !request.xhr? }
78 format.api {
78 format.api {
79 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
79 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
80 }
80 }
81 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
81 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
82 format.csv { send_data(query_to_csv(@issues, @query, params[:csv]), :type => 'text/csv; header=present', :filename => 'issues.csv') }
82 format.csv { send_data(query_to_csv(@issues, @query, params[:csv]), :type => 'text/csv; header=present', :filename => 'issues.csv') }
83 format.pdf { send_file_headers! :type => 'application/pdf', :filename => 'issues.pdf' }
83 format.pdf { send_file_headers! :type => 'application/pdf', :filename => 'issues.pdf' }
84 end
84 end
85 else
85 else
86 respond_to do |format|
86 respond_to do |format|
87 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
87 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
88 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
88 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
89 format.api { render_validation_errors(@query) }
89 format.api { render_validation_errors(@query) }
90 end
90 end
91 end
91 end
92 rescue ActiveRecord::RecordNotFound
92 rescue ActiveRecord::RecordNotFound
93 render_404
93 render_404
94 end
94 end
95
95
96 def show
96 def show
97 @journals = @issue.journals.includes(:user, :details).
97 @journals = @issue.journals.includes(:user, :details).
98 references(:user, :details).
98 references(:user, :details).
99 reorder(:created_on, :id).to_a
99 reorder(:created_on, :id).to_a
100 @journals.each_with_index {|j,i| j.indice = i+1}
100 @journals.each_with_index {|j,i| j.indice = i+1}
101 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
101 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
102 Journal.preload_journals_details_custom_fields(@journals)
102 Journal.preload_journals_details_custom_fields(@journals)
103 @journals.select! {|journal| journal.notes? || journal.visible_details.any?}
103 @journals.select! {|journal| journal.notes? || journal.visible_details.any?}
104 @journals.reverse! if User.current.wants_comments_in_reverse_order?
104 @journals.reverse! if User.current.wants_comments_in_reverse_order?
105
105
106 @changesets = @issue.changesets.visible.preload(:repository, :user).to_a
106 @changesets = @issue.changesets.visible.preload(:repository, :user).to_a
107 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
107 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
108
108
109 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
109 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
110 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
110 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
111 @priorities = IssuePriority.active
111 @priorities = IssuePriority.active
112 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
112 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
113 @relation = IssueRelation.new
113 @relation = IssueRelation.new
114
114
115 respond_to do |format|
115 respond_to do |format|
116 format.html {
116 format.html {
117 retrieve_previous_and_next_issue_ids
117 retrieve_previous_and_next_issue_ids
118 render :template => 'issues/show'
118 render :template => 'issues/show'
119 }
119 }
120 format.api
120 format.api
121 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
121 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
122 format.pdf {
122 format.pdf {
123 send_file_headers! :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf"
123 send_file_headers! :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf"
124 }
124 }
125 end
125 end
126 end
126 end
127
127
128 def new
128 def new
129 respond_to do |format|
129 respond_to do |format|
130 format.html { render :action => 'new', :layout => !request.xhr? }
130 format.html { render :action => 'new', :layout => !request.xhr? }
131 format.js
131 format.js
132 end
132 end
133 end
133 end
134
134
135 def create
135 def create
136 unless User.current.allowed_to?(:add_issues, @issue.project, :global => true)
136 unless User.current.allowed_to?(:add_issues, @issue.project, :global => true)
137 raise ::Unauthorized
137 raise ::Unauthorized
138 end
138 end
139 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
139 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
140 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
140 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
141 if @issue.save
141 if @issue.save
142 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
142 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
143 respond_to do |format|
143 respond_to do |format|
144 format.html {
144 format.html {
145 render_attachment_warning_if_needed(@issue)
145 render_attachment_warning_if_needed(@issue)
146 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
146 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
147 redirect_after_create
147 redirect_after_create
148 }
148 }
149 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
149 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
150 end
150 end
151 return
151 return
152 else
152 else
153 respond_to do |format|
153 respond_to do |format|
154 format.html {
154 format.html {
155 if @issue.project.nil?
155 if @issue.project.nil?
156 render_error :status => 422
156 render_error :status => 422
157 else
157 else
158 render :action => 'new'
158 render :action => 'new'
159 end
159 end
160 }
160 }
161 format.api { render_validation_errors(@issue) }
161 format.api { render_validation_errors(@issue) }
162 end
162 end
163 end
163 end
164 end
164 end
165
165
166 def edit
166 def edit
167 return unless update_issue_from_params
167 return unless update_issue_from_params
168
168
169 respond_to do |format|
169 respond_to do |format|
170 format.html { }
170 format.html { }
171 format.js
171 format.js
172 end
172 end
173 end
173 end
174
174
175 def update
175 def update
176 return unless update_issue_from_params
176 return unless update_issue_from_params
177 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
177 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
178 saved = false
178 saved = false
179 begin
179 begin
180 saved = save_issue_with_child_records
180 saved = save_issue_with_child_records
181 rescue ActiveRecord::StaleObjectError
181 rescue ActiveRecord::StaleObjectError
182 @conflict = true
182 @conflict = true
183 if params[:last_journal_id]
183 if params[:last_journal_id]
184 @conflict_journals = @issue.journals_after(params[:last_journal_id]).to_a
184 @conflict_journals = @issue.journals_after(params[:last_journal_id]).to_a
185 @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
185 @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
186 end
186 end
187 end
187 end
188
188
189 if saved
189 if saved
190 render_attachment_warning_if_needed(@issue)
190 render_attachment_warning_if_needed(@issue)
191 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
191 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
192
192
193 respond_to do |format|
193 respond_to do |format|
194 format.html { redirect_back_or_default issue_path(@issue) }
194 format.html { redirect_back_or_default issue_path(@issue) }
195 format.api { render_api_ok }
195 format.api { render_api_ok }
196 end
196 end
197 else
197 else
198 respond_to do |format|
198 respond_to do |format|
199 format.html { render :action => 'edit' }
199 format.html { render :action => 'edit' }
200 format.api { render_validation_errors(@issue) }
200 format.api { render_validation_errors(@issue) }
201 end
201 end
202 end
202 end
203 end
203 end
204
204
205 # Bulk edit/copy a set of issues
205 # Bulk edit/copy a set of issues
206 def bulk_edit
206 def bulk_edit
207 @issues.sort!
207 @issues.sort!
208 @copy = params[:copy].present?
208 @copy = params[:copy].present?
209 @notes = params[:notes]
209 @notes = params[:notes]
210
210
211 if @copy
211 if @copy
212 unless User.current.allowed_to?(:copy_issues, @projects)
212 unless User.current.allowed_to?(:copy_issues, @projects)
213 raise ::Unauthorized
213 raise ::Unauthorized
214 end
214 end
215 end
215 end
216
216
217 @allowed_projects = Issue.allowed_target_projects
217 @allowed_projects = Issue.allowed_target_projects
218 if params[:issue]
218 if params[:issue]
219 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
219 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
220 if @target_project
220 if @target_project
221 target_projects = [@target_project]
221 target_projects = [@target_project]
222 end
222 end
223 end
223 end
224 target_projects ||= @projects
224 target_projects ||= @projects
225
225
226 if @copy
226 if @copy
227 # Copied issues will get their default statuses
227 # Copied issues will get their default statuses
228 @available_statuses = []
228 @available_statuses = []
229 else
229 else
230 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
230 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
231 end
231 end
232 @custom_fields = @issues.map{|i|i.editable_custom_fields}.reduce(:&)
232 @custom_fields = @issues.map{|i|i.editable_custom_fields}.reduce(:&)
233 @assignables = target_projects.map(&:assignable_users).reduce(:&)
233 @assignables = target_projects.map(&:assignable_users).reduce(:&)
234 @trackers = target_projects.map(&:trackers).reduce(:&)
234 @trackers = target_projects.map(&:trackers).reduce(:&)
235 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
235 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
236 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
236 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
237 if @copy
237 if @copy
238 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
238 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
239 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
239 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
240 end
240 end
241
241
242 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
242 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
243
243
244 @issue_params = params[:issue] || {}
244 @issue_params = params[:issue] || {}
245 @issue_params[:custom_field_values] ||= {}
245 @issue_params[:custom_field_values] ||= {}
246 end
246 end
247
247
248 def bulk_update
248 def bulk_update
249 @issues.sort!
249 @issues.sort!
250 @copy = params[:copy].present?
250 @copy = params[:copy].present?
251
251
252 attributes = parse_params_for_bulk_issue_attributes(params)
252 attributes = parse_params_for_bulk_issue_attributes(params)
253 copy_subtasks = (params[:copy_subtasks] == '1')
253 copy_subtasks = (params[:copy_subtasks] == '1')
254 copy_attachments = (params[:copy_attachments] == '1')
254 copy_attachments = (params[:copy_attachments] == '1')
255
255
256 if @copy
256 if @copy
257 unless User.current.allowed_to?(:copy_issues, @projects)
257 unless User.current.allowed_to?(:copy_issues, @projects)
258 raise ::Unauthorized
258 raise ::Unauthorized
259 end
259 end
260 target_projects = @projects
260 target_projects = @projects
261 if attributes['project_id'].present?
261 if attributes['project_id'].present?
262 target_projects = Project.where(:id => attributes['project_id']).to_a
262 target_projects = Project.where(:id => attributes['project_id']).to_a
263 end
263 end
264 unless User.current.allowed_to?(:add_issues, target_projects)
264 unless User.current.allowed_to?(:add_issues, target_projects)
265 raise ::Unauthorized
265 raise ::Unauthorized
266 end
266 end
267 end
267 end
268
268
269 unsaved_issues = []
269 unsaved_issues = []
270 saved_issues = []
270 saved_issues = []
271
271
272 if @copy && copy_subtasks
272 if @copy && copy_subtasks
273 # Descendant issues will be copied with the parent task
273 # Descendant issues will be copied with the parent task
274 # Don't copy them twice
274 # Don't copy them twice
275 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
275 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
276 end
276 end
277
277
278 @issues.each do |orig_issue|
278 @issues.each do |orig_issue|
279 orig_issue.reload
279 orig_issue.reload
280 if @copy
280 if @copy
281 issue = orig_issue.copy({},
281 issue = orig_issue.copy({},
282 :attachments => copy_attachments,
282 :attachments => copy_attachments,
283 :subtasks => copy_subtasks,
283 :subtasks => copy_subtasks,
284 :link => link_copy?(params[:link_copy])
284 :link => link_copy?(params[:link_copy])
285 )
285 )
286 else
286 else
287 issue = orig_issue
287 issue = orig_issue
288 end
288 end
289 journal = issue.init_journal(User.current, params[:notes])
289 journal = issue.init_journal(User.current, params[:notes])
290 issue.safe_attributes = attributes
290 issue.safe_attributes = attributes
291 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
291 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
292 if issue.save
292 if issue.save
293 saved_issues << issue
293 saved_issues << issue
294 else
294 else
295 unsaved_issues << orig_issue
295 unsaved_issues << orig_issue
296 end
296 end
297 end
297 end
298
298
299 if unsaved_issues.empty?
299 if unsaved_issues.empty?
300 flash[:notice] = l(:notice_successful_update) unless saved_issues.empty?
300 flash[:notice] = l(:notice_successful_update) unless saved_issues.empty?
301 if params[:follow]
301 if params[:follow]
302 if @issues.size == 1 && saved_issues.size == 1
302 if @issues.size == 1 && saved_issues.size == 1
303 redirect_to issue_path(saved_issues.first)
303 redirect_to issue_path(saved_issues.first)
304 elsif saved_issues.map(&:project).uniq.size == 1
304 elsif saved_issues.map(&:project).uniq.size == 1
305 redirect_to project_issues_path(saved_issues.map(&:project).first)
305 redirect_to project_issues_path(saved_issues.map(&:project).first)
306 end
306 end
307 else
307 else
308 redirect_back_or_default _project_issues_path(@project)
308 redirect_back_or_default _project_issues_path(@project)
309 end
309 end
310 else
310 else
311 @saved_issues = @issues
311 @saved_issues = @issues
312 @unsaved_issues = unsaved_issues
312 @unsaved_issues = unsaved_issues
313 @issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).to_a
313 @issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).to_a
314 bulk_edit
314 bulk_edit
315 render :action => 'bulk_edit'
315 render :action => 'bulk_edit'
316 end
316 end
317 end
317 end
318
318
319 def destroy
319 def destroy
320 @hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f
320 @hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f
321 if @hours > 0
321 if @hours > 0
322 case params[:todo]
322 case params[:todo]
323 when 'destroy'
323 when 'destroy'
324 # nothing to do
324 # nothing to do
325 when 'nullify'
325 when 'nullify'
326 TimeEntry.where(['issue_id IN (?)', @issues]).update_all('issue_id = NULL')
326 TimeEntry.where(['issue_id IN (?)', @issues]).update_all('issue_id = NULL')
327 when 'reassign'
327 when 'reassign'
328 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
328 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
329 if reassign_to.nil?
329 if reassign_to.nil?
330 flash.now[:error] = l(:error_issue_not_found_in_project)
330 flash.now[:error] = l(:error_issue_not_found_in_project)
331 return
331 return
332 else
332 else
333 TimeEntry.where(['issue_id IN (?)', @issues]).
333 TimeEntry.where(['issue_id IN (?)', @issues]).
334 update_all("issue_id = #{reassign_to.id}")
334 update_all("issue_id = #{reassign_to.id}")
335 end
335 end
336 else
336 else
337 # display the destroy form if it's a user request
337 # display the destroy form if it's a user request
338 return unless api_request?
338 return unless api_request?
339 end
339 end
340 end
340 end
341 @issues.each do |issue|
341 @issues.each do |issue|
342 begin
342 begin
343 issue.reload.destroy
343 issue.reload.destroy
344 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
344 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
345 # nothing to do, issue was already deleted (eg. by a parent)
345 # nothing to do, issue was already deleted (eg. by a parent)
346 end
346 end
347 end
347 end
348 respond_to do |format|
348 respond_to do |format|
349 format.html { redirect_back_or_default _project_issues_path(@project) }
349 format.html { redirect_back_or_default _project_issues_path(@project) }
350 format.api { render_api_ok }
350 format.api { render_api_ok }
351 end
351 end
352 end
352 end
353
353
354 private
354 private
355
355
356 def retrieve_previous_and_next_issue_ids
356 def retrieve_previous_and_next_issue_ids
357 retrieve_query_from_session
357 retrieve_query_from_session
358 if @query
358 if @query
359 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
359 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
360 sort_update(@query.sortable_columns, 'issues_index_sort')
360 sort_update(@query.sortable_columns, 'issues_index_sort')
361 limit = 500
361 limit = 500
362 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
362 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
363 if (idx = issue_ids.index(@issue.id)) && idx < limit
363 if (idx = issue_ids.index(@issue.id)) && idx < limit
364 if issue_ids.size < 500
364 if issue_ids.size < 500
365 @issue_position = idx + 1
365 @issue_position = idx + 1
366 @issue_count = issue_ids.size
366 @issue_count = issue_ids.size
367 end
367 end
368 @prev_issue_id = issue_ids[idx - 1] if idx > 0
368 @prev_issue_id = issue_ids[idx - 1] if idx > 0
369 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
369 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
370 end
370 end
371 end
371 end
372 end
372 end
373
373
374 # Used by #edit and #update to set some common instance variables
374 # Used by #edit and #update to set some common instance variables
375 # from the params
375 # from the params
376 def update_issue_from_params
376 def update_issue_from_params
377 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
377 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
378 if params[:time_entry]
378 if params[:time_entry]
379 @time_entry.safe_attributes = params[:time_entry]
379 @time_entry.safe_attributes = params[:time_entry]
380 end
380 end
381
381
382 @issue.init_journal(User.current)
382 @issue.init_journal(User.current)
383
383
384 issue_attributes = params[:issue]
384 issue_attributes = params[:issue]
385 if issue_attributes && params[:conflict_resolution]
385 if issue_attributes && params[:conflict_resolution]
386 case params[:conflict_resolution]
386 case params[:conflict_resolution]
387 when 'overwrite'
387 when 'overwrite'
388 issue_attributes = issue_attributes.dup
388 issue_attributes = issue_attributes.dup
389 issue_attributes.delete(:lock_version)
389 issue_attributes.delete(:lock_version)
390 when 'add_notes'
390 when 'add_notes'
391 issue_attributes = issue_attributes.slice(:notes)
391 issue_attributes = issue_attributes.slice(:notes)
392 when 'cancel'
392 when 'cancel'
393 redirect_to issue_path(@issue)
393 redirect_to issue_path(@issue)
394 return false
394 return false
395 end
395 end
396 end
396 end
397 @issue.safe_attributes = issue_attributes
397 @issue.safe_attributes = issue_attributes
398 @priorities = IssuePriority.active
398 @priorities = IssuePriority.active
399 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
399 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
400 true
400 true
401 end
401 end
402
402
403 # Used by #new and #create to build a new issue from the params
403 # Used by #new and #create to build a new issue from the params
404 # The new issue will be copied from an existing one if copy_from parameter is given
404 # The new issue will be copied from an existing one if copy_from parameter is given
405 def build_new_issue_from_params
405 def build_new_issue_from_params
406 @issue = Issue.new
406 @issue = Issue.new
407 if params[:copy_from]
407 if params[:copy_from]
408 begin
408 begin
409 @issue.init_journal(User.current)
409 @issue.init_journal(User.current)
410 @copy_from = Issue.visible.find(params[:copy_from])
410 @copy_from = Issue.visible.find(params[:copy_from])
411 unless User.current.allowed_to?(:copy_issues, @copy_from.project)
411 unless User.current.allowed_to?(:copy_issues, @copy_from.project)
412 raise ::Unauthorized
412 raise ::Unauthorized
413 end
413 end
414 @link_copy = link_copy?(params[:link_copy]) || request.get?
414 @link_copy = link_copy?(params[:link_copy]) || request.get?
415 @copy_attachments = params[:copy_attachments].present? || request.get?
415 @copy_attachments = params[:copy_attachments].present? || request.get?
416 @copy_subtasks = params[:copy_subtasks].present? || request.get?
416 @copy_subtasks = params[:copy_subtasks].present? || request.get?
417 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks, :link => @link_copy)
417 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks, :link => @link_copy)
418 rescue ActiveRecord::RecordNotFound
418 rescue ActiveRecord::RecordNotFound
419 render_404
419 render_404
420 return
420 return
421 end
421 end
422 end
422 end
423 @issue.project = @project
423 @issue.project = @project
424 if request.get?
424 if request.get?
425 @issue.project ||= @issue.allowed_target_projects.first
425 @issue.project ||= @issue.allowed_target_projects.first
426 end
426 end
427 @issue.author ||= User.current
427 @issue.author ||= User.current
428 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
428 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
429
429
430 attrs = (params[:issue] || {}).deep_dup
430 attrs = (params[:issue] || {}).deep_dup
431 if action_name == 'new' && params[:was_default_status] == attrs[:status_id]
431 if action_name == 'new' && params[:was_default_status] == attrs[:status_id]
432 attrs.delete(:status_id)
432 attrs.delete(:status_id)
433 end
433 end
434 if action_name == 'new' && params[:form_update_triggered_by] == 'issue_project_id'
435 # Discard submitted version when changing the project on the issue form
436 # so we can use the default version for the new project
437 attrs.delete(:fixed_version_id)
438 end
434 @issue.safe_attributes = attrs
439 @issue.safe_attributes = attrs
435
440
436 if @issue.project
441 if @issue.project
437 @issue.tracker ||= @issue.project.trackers.first
442 @issue.tracker ||= @issue.project.trackers.first
438 if @issue.tracker.nil?
443 if @issue.tracker.nil?
439 render_error l(:error_no_tracker_in_project)
444 render_error l(:error_no_tracker_in_project)
440 return false
445 return false
441 end
446 end
442 if @issue.status.nil?
447 if @issue.status.nil?
443 render_error l(:error_no_default_issue_status)
448 render_error l(:error_no_default_issue_status)
444 return false
449 return false
445 end
450 end
446 end
451 end
447
452
448 @priorities = IssuePriority.active
453 @priorities = IssuePriority.active
449 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
454 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
450 end
455 end
451
456
452 def parse_params_for_bulk_issue_attributes(params)
457 def parse_params_for_bulk_issue_attributes(params)
453 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
458 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
454 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
459 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
455 if custom = attributes[:custom_field_values]
460 if custom = attributes[:custom_field_values]
456 custom.reject! {|k,v| v.blank?}
461 custom.reject! {|k,v| v.blank?}
457 custom.keys.each do |k|
462 custom.keys.each do |k|
458 if custom[k].is_a?(Array)
463 if custom[k].is_a?(Array)
459 custom[k] << '' if custom[k].delete('__none__')
464 custom[k] << '' if custom[k].delete('__none__')
460 else
465 else
461 custom[k] = '' if custom[k] == '__none__'
466 custom[k] = '' if custom[k] == '__none__'
462 end
467 end
463 end
468 end
464 end
469 end
465 attributes
470 attributes
466 end
471 end
467
472
468 # Saves @issue and a time_entry from the parameters
473 # Saves @issue and a time_entry from the parameters
469 def save_issue_with_child_records
474 def save_issue_with_child_records
470 Issue.transaction do
475 Issue.transaction do
471 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project)
476 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project)
472 time_entry = @time_entry || TimeEntry.new
477 time_entry = @time_entry || TimeEntry.new
473 time_entry.project = @issue.project
478 time_entry.project = @issue.project
474 time_entry.issue = @issue
479 time_entry.issue = @issue
475 time_entry.user = User.current
480 time_entry.user = User.current
476 time_entry.spent_on = User.current.today
481 time_entry.spent_on = User.current.today
477 time_entry.attributes = params[:time_entry]
482 time_entry.attributes = params[:time_entry]
478 @issue.time_entries << time_entry
483 @issue.time_entries << time_entry
479 end
484 end
480
485
481 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
486 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
482 if @issue.save
487 if @issue.save
483 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
488 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
484 else
489 else
485 raise ActiveRecord::Rollback
490 raise ActiveRecord::Rollback
486 end
491 end
487 end
492 end
488 end
493 end
489
494
490 # Returns true if the issue copy should be linked
495 # Returns true if the issue copy should be linked
491 # to the original issue
496 # to the original issue
492 def link_copy?(param)
497 def link_copy?(param)
493 case Setting.link_copied_issue
498 case Setting.link_copied_issue
494 when 'yes'
499 when 'yes'
495 true
500 true
496 when 'no'
501 when 'no'
497 false
502 false
498 when 'ask'
503 when 'ask'
499 param == '1'
504 param == '1'
500 end
505 end
501 end
506 end
502
507
503 # Redirects user after a successful issue creation
508 # Redirects user after a successful issue creation
504 def redirect_after_create
509 def redirect_after_create
505 if params[:continue]
510 if params[:continue]
506 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
511 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
507 if params[:project_id]
512 if params[:project_id]
508 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
513 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
509 else
514 else
510 attrs.merge! :project_id => @issue.project_id
515 attrs.merge! :project_id => @issue.project_id
511 redirect_to new_issue_path(:issue => attrs)
516 redirect_to new_issue_path(:issue => attrs)
512 end
517 end
513 else
518 else
514 redirect_to issue_path(@issue)
519 redirect_to issue_path(@issue)
515 end
520 end
516 end
521 end
517 end
522 end
@@ -1,79 +1,79
1 <%= labelled_fields_for :issue, @issue do |f| %>
1 <%= labelled_fields_for :issue, @issue do |f| %>
2
2
3 <div class="splitcontent">
3 <div class="splitcontent">
4 <div class="splitcontentleft">
4 <div class="splitcontentleft">
5 <% if @issue.safe_attribute?('status_id') && @allowed_statuses.present? %>
5 <% if @issue.safe_attribute?('status_id') && @allowed_statuses.present? %>
6 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), {:required => true},
6 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), {:required => true},
7 :onchange => "updateIssueFrom('#{escape_javascript update_issue_form_path(@project, @issue)}')" %></p>
7 :onchange => "updateIssueFrom('#{escape_javascript update_issue_form_path(@project, @issue)}', this)" %></p>
8 <%= hidden_field_tag 'was_default_status', @issue.status_id, :id => nil if @issue.status == @issue.default_status %>
8 <%= hidden_field_tag 'was_default_status', @issue.status_id, :id => nil if @issue.status == @issue.default_status %>
9 <% else %>
9 <% else %>
10 <p><label><%= l(:field_status) %></label> <%= @issue.status %></p>
10 <p><label><%= l(:field_status) %></label> <%= @issue.status %></p>
11 <% end %>
11 <% end %>
12
12
13 <% if @issue.safe_attribute? 'priority_id' %>
13 <% if @issue.safe_attribute? 'priority_id' %>
14 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true} %></p>
14 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true} %></p>
15 <% end %>
15 <% end %>
16
16
17 <% if @issue.safe_attribute? 'assigned_to_id' %>
17 <% if @issue.safe_attribute? 'assigned_to_id' %>
18 <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true, :required => @issue.required_attribute?('assigned_to_id') %></p>
18 <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true, :required => @issue.required_attribute?('assigned_to_id') %></p>
19 <% end %>
19 <% end %>
20
20
21 <% if @issue.safe_attribute?('category_id') && @issue.project.issue_categories.any? %>
21 <% if @issue.safe_attribute?('category_id') && @issue.project.issue_categories.any? %>
22 <p><%= f.select :category_id, (@issue.project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true, :required => @issue.required_attribute?('category_id') %>
22 <p><%= f.select :category_id, (@issue.project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true, :required => @issue.required_attribute?('category_id') %>
23 <%= link_to(image_tag('add.png', :style => 'vertical-align: middle;'),
23 <%= link_to(image_tag('add.png', :style => 'vertical-align: middle;'),
24 new_project_issue_category_path(@issue.project),
24 new_project_issue_category_path(@issue.project),
25 :remote => true,
25 :remote => true,
26 :method => 'get',
26 :method => 'get',
27 :title => l(:label_issue_category_new),
27 :title => l(:label_issue_category_new),
28 :tabindex => 200) if User.current.allowed_to?(:manage_categories, @issue.project) %></p>
28 :tabindex => 200) if User.current.allowed_to?(:manage_categories, @issue.project) %></p>
29 <% end %>
29 <% end %>
30
30
31 <% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %>
31 <% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %>
32 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true, :required => @issue.required_attribute?('fixed_version_id') %>
32 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true, :required => @issue.required_attribute?('fixed_version_id') %>
33 <%= link_to(image_tag('add.png', :style => 'vertical-align: middle;'),
33 <%= link_to(image_tag('add.png', :style => 'vertical-align: middle;'),
34 new_project_version_path(@issue.project),
34 new_project_version_path(@issue.project),
35 :remote => true,
35 :remote => true,
36 :method => 'get',
36 :method => 'get',
37 :title => l(:label_version_new),
37 :title => l(:label_version_new),
38 :tabindex => 200) if User.current.allowed_to?(:manage_versions, @issue.project) %>
38 :tabindex => 200) if User.current.allowed_to?(:manage_versions, @issue.project) %>
39 </p>
39 </p>
40 <% end %>
40 <% end %>
41 </div>
41 </div>
42
42
43 <div class="splitcontentright">
43 <div class="splitcontentright">
44 <% if @issue.safe_attribute? 'parent_issue_id' %>
44 <% if @issue.safe_attribute? 'parent_issue_id' %>
45 <p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10, :required => @issue.required_attribute?('parent_issue_id') %></p>
45 <p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10, :required => @issue.required_attribute?('parent_issue_id') %></p>
46 <%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path(:project_id => @issue.project, :scope => Setting.cross_project_subtasks)}')" %>
46 <%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path(:project_id => @issue.project, :scope => Setting.cross_project_subtasks)}')" %>
47 <% end %>
47 <% end %>
48
48
49 <% if @issue.safe_attribute? 'start_date' %>
49 <% if @issue.safe_attribute? 'start_date' %>
50 <p id="start_date_area">
50 <p id="start_date_area">
51 <%= f.text_field(:start_date, :size => 10, :required => @issue.required_attribute?('start_date')) %>
51 <%= f.text_field(:start_date, :size => 10, :required => @issue.required_attribute?('start_date')) %>
52 <%= calendar_for('issue_start_date') if @issue.leaf? %>
52 <%= calendar_for('issue_start_date') if @issue.leaf? %>
53 </p>
53 </p>
54 <% end %>
54 <% end %>
55
55
56 <% if @issue.safe_attribute? 'due_date' %>
56 <% if @issue.safe_attribute? 'due_date' %>
57 <p id="due_date_area">
57 <p id="due_date_area">
58 <%= f.text_field(:due_date, :size => 10, :required => @issue.required_attribute?('due_date')) %>
58 <%= f.text_field(:due_date, :size => 10, :required => @issue.required_attribute?('due_date')) %>
59 <%= calendar_for('issue_due_date') if @issue.leaf? %>
59 <%= calendar_for('issue_due_date') if @issue.leaf? %>
60 </p>
60 </p>
61 <% end %>
61 <% end %>
62
62
63 <% if @issue.safe_attribute? 'estimated_hours' %>
63 <% if @issue.safe_attribute? 'estimated_hours' %>
64 <p><%= f.text_field :estimated_hours, :size => 3, :required => @issue.required_attribute?('estimated_hours') %> <%= l(:field_hours) %></p>
64 <p><%= f.text_field :estimated_hours, :size => 3, :required => @issue.required_attribute?('estimated_hours') %> <%= l(:field_hours) %></p>
65 <% end %>
65 <% end %>
66
66
67 <% if @issue.safe_attribute?('done_ratio') && Issue.use_field_for_done_ratio? %>
67 <% if @issue.safe_attribute?('done_ratio') && Issue.use_field_for_done_ratio? %>
68 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %></p>
68 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :required => @issue.required_attribute?('done_ratio') %></p>
69 <% end %>
69 <% end %>
70 </div>
70 </div>
71 </div>
71 </div>
72
72
73 <% if @issue.safe_attribute? 'custom_field_values' %>
73 <% if @issue.safe_attribute? 'custom_field_values' %>
74 <%= render :partial => 'issues/form_custom_fields' %>
74 <%= render :partial => 'issues/form_custom_fields' %>
75 <% end %>
75 <% end %>
76
76
77 <% end %>
77 <% end %>
78
78
79 <% include_calendar_headers_tags %>
79 <% include_calendar_headers_tags %>
@@ -1,55 +1,56
1 <%= labelled_fields_for :issue, @issue do |f| %>
1 <%= labelled_fields_for :issue, @issue do |f| %>
2 <%= call_hook(:view_issues_form_details_top, { :issue => @issue, :form => f }) %>
2 <%= call_hook(:view_issues_form_details_top, { :issue => @issue, :form => f }) %>
3 <%= hidden_field_tag 'form_update_triggered_by', '' %>
3
4
4 <% if @issue.safe_attribute? 'is_private' %>
5 <% if @issue.safe_attribute? 'is_private' %>
5 <p id="issue_is_private_wrap">
6 <p id="issue_is_private_wrap">
6 <%= f.check_box :is_private, :no_label => true %><label class="inline" for="issue_is_private" id="issue_is_private_label"><%= l(:field_is_private) %></label>
7 <%= f.check_box :is_private, :no_label => true %><label class="inline" for="issue_is_private" id="issue_is_private_label"><%= l(:field_is_private) %></label>
7 </p>
8 </p>
8 <% end %>
9 <% end %>
9
10
10 <% if @issue.safe_attribute?('project_id') && (!@issue.new_record? || @project.nil? || @issue.copy?) %>
11 <% if @issue.safe_attribute?('project_id') && (!@issue.new_record? || @project.nil? || @issue.copy?) %>
11 <p><%= f.select :project_id, project_tree_options_for_select(@issue.allowed_target_projects, :selected => @issue.project), {:required => true},
12 <p><%= f.select :project_id, project_tree_options_for_select(@issue.allowed_target_projects, :selected => @issue.project), {:required => true},
12 :onchange => "updateIssueFrom('#{escape_javascript update_issue_form_path(@project, @issue)}')" %></p>
13 :onchange => "updateIssueFrom('#{escape_javascript update_issue_form_path(@project, @issue)}', this)" %></p>
13 <% end %>
14 <% end %>
14
15
15 <% if @issue.safe_attribute? 'tracker_id' %>
16 <% if @issue.safe_attribute? 'tracker_id' %>
16 <p><%= f.select :tracker_id, @issue.project.trackers.collect {|t| [t.name, t.id]}, {:required => true},
17 <p><%= f.select :tracker_id, @issue.project.trackers.collect {|t| [t.name, t.id]}, {:required => true},
17 :onchange => "updateIssueFrom('#{escape_javascript update_issue_form_path(@project, @issue)}')" %></p>
18 :onchange => "updateIssueFrom('#{escape_javascript update_issue_form_path(@project, @issue)}', this)" %></p>
18 <% end %>
19 <% end %>
19
20
20 <% if @issue.safe_attribute? 'subject' %>
21 <% if @issue.safe_attribute? 'subject' %>
21 <p><%= f.text_field :subject, :size => 80, :maxlength => 255, :required => true %></p>
22 <p><%= f.text_field :subject, :size => 80, :maxlength => 255, :required => true %></p>
22 <% end %>
23 <% end %>
23
24
24 <% if @issue.safe_attribute? 'description' %>
25 <% if @issue.safe_attribute? 'description' %>
25 <p>
26 <p>
26 <%= f.label_for_field :description, :required => @issue.required_attribute?('description') %>
27 <%= f.label_for_field :description, :required => @issue.required_attribute?('description') %>
27 <%= link_to_function content_tag(:span, l(:button_edit), :class => 'icon icon-edit'), '$(this).hide(); $("#issue_description_and_toolbar").show()' unless @issue.new_record? %>
28 <%= link_to_function content_tag(:span, l(:button_edit), :class => 'icon icon-edit'), '$(this).hide(); $("#issue_description_and_toolbar").show()' unless @issue.new_record? %>
28 <%= content_tag 'span', :id => "issue_description_and_toolbar", :style => (@issue.new_record? ? nil : 'display:none') do %>
29 <%= content_tag 'span', :id => "issue_description_and_toolbar", :style => (@issue.new_record? ? nil : 'display:none') do %>
29 <%= f.text_area :description,
30 <%= f.text_area :description,
30 :cols => 60,
31 :cols => 60,
31 :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min),
32 :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min),
32 :accesskey => accesskey(:edit),
33 :accesskey => accesskey(:edit),
33 :class => 'wiki-edit',
34 :class => 'wiki-edit',
34 :no_label => true %>
35 :no_label => true %>
35 <% end %>
36 <% end %>
36 </p>
37 </p>
37 <%= wikitoolbar_for 'issue_description' %>
38 <%= wikitoolbar_for 'issue_description' %>
38 <% end %>
39 <% end %>
39
40
40 <div id="attributes" class="attributes">
41 <div id="attributes" class="attributes">
41 <%= render :partial => 'issues/attributes' %>
42 <%= render :partial => 'issues/attributes' %>
42 </div>
43 </div>
43
44
44 <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %>
45 <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %>
45 <% end %>
46 <% end %>
46
47
47 <% heads_for_wiki_formatter %>
48 <% heads_for_wiki_formatter %>
48
49
49 <%= javascript_tag do %>
50 <%= javascript_tag do %>
50 $(document).ready(function(){
51 $(document).ready(function(){
51 $("#issue_tracker_id, #issue_status_id").each(function(){
52 $("#issue_tracker_id, #issue_status_id").each(function(){
52 $(this).val($(this).find("option[selected=selected]").val());
53 $(this).val($(this).find("option[selected=selected]").val());
53 });
54 });
54 });
55 });
55 <% end %>
56 <% end %>
@@ -1,668 +1,671
1 /* Redmine - project management software
1 /* Redmine - project management software
2 Copyright (C) 2006-2015 Jean-Philippe Lang */
2 Copyright (C) 2006-2015 Jean-Philippe Lang */
3
3
4 function checkAll(id, checked) {
4 function checkAll(id, checked) {
5 $('#'+id).find('input[type=checkbox]:enabled').prop('checked', checked);
5 $('#'+id).find('input[type=checkbox]:enabled').prop('checked', checked);
6 }
6 }
7
7
8 function toggleCheckboxesBySelector(selector) {
8 function toggleCheckboxesBySelector(selector) {
9 var all_checked = true;
9 var all_checked = true;
10 $(selector).each(function(index) {
10 $(selector).each(function(index) {
11 if (!$(this).is(':checked')) { all_checked = false; }
11 if (!$(this).is(':checked')) { all_checked = false; }
12 });
12 });
13 $(selector).prop('checked', !all_checked);
13 $(selector).prop('checked', !all_checked);
14 }
14 }
15
15
16 function showAndScrollTo(id, focus) {
16 function showAndScrollTo(id, focus) {
17 $('#'+id).show();
17 $('#'+id).show();
18 if (focus !== null) {
18 if (focus !== null) {
19 $('#'+focus).focus();
19 $('#'+focus).focus();
20 }
20 }
21 $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100);
21 $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100);
22 }
22 }
23
23
24 function toggleRowGroup(el) {
24 function toggleRowGroup(el) {
25 var tr = $(el).parents('tr').first();
25 var tr = $(el).parents('tr').first();
26 var n = tr.next();
26 var n = tr.next();
27 tr.toggleClass('open');
27 tr.toggleClass('open');
28 while (n.length && !n.hasClass('group')) {
28 while (n.length && !n.hasClass('group')) {
29 n.toggle();
29 n.toggle();
30 n = n.next('tr');
30 n = n.next('tr');
31 }
31 }
32 }
32 }
33
33
34 function collapseAllRowGroups(el) {
34 function collapseAllRowGroups(el) {
35 var tbody = $(el).parents('tbody').first();
35 var tbody = $(el).parents('tbody').first();
36 tbody.children('tr').each(function(index) {
36 tbody.children('tr').each(function(index) {
37 if ($(this).hasClass('group')) {
37 if ($(this).hasClass('group')) {
38 $(this).removeClass('open');
38 $(this).removeClass('open');
39 } else {
39 } else {
40 $(this).hide();
40 $(this).hide();
41 }
41 }
42 });
42 });
43 }
43 }
44
44
45 function expandAllRowGroups(el) {
45 function expandAllRowGroups(el) {
46 var tbody = $(el).parents('tbody').first();
46 var tbody = $(el).parents('tbody').first();
47 tbody.children('tr').each(function(index) {
47 tbody.children('tr').each(function(index) {
48 if ($(this).hasClass('group')) {
48 if ($(this).hasClass('group')) {
49 $(this).addClass('open');
49 $(this).addClass('open');
50 } else {
50 } else {
51 $(this).show();
51 $(this).show();
52 }
52 }
53 });
53 });
54 }
54 }
55
55
56 function toggleAllRowGroups(el) {
56 function toggleAllRowGroups(el) {
57 var tr = $(el).parents('tr').first();
57 var tr = $(el).parents('tr').first();
58 if (tr.hasClass('open')) {
58 if (tr.hasClass('open')) {
59 collapseAllRowGroups(el);
59 collapseAllRowGroups(el);
60 } else {
60 } else {
61 expandAllRowGroups(el);
61 expandAllRowGroups(el);
62 }
62 }
63 }
63 }
64
64
65 function toggleFieldset(el) {
65 function toggleFieldset(el) {
66 var fieldset = $(el).parents('fieldset').first();
66 var fieldset = $(el).parents('fieldset').first();
67 fieldset.toggleClass('collapsed');
67 fieldset.toggleClass('collapsed');
68 fieldset.children('div').toggle();
68 fieldset.children('div').toggle();
69 }
69 }
70
70
71 function hideFieldset(el) {
71 function hideFieldset(el) {
72 var fieldset = $(el).parents('fieldset').first();
72 var fieldset = $(el).parents('fieldset').first();
73 fieldset.toggleClass('collapsed');
73 fieldset.toggleClass('collapsed');
74 fieldset.children('div').hide();
74 fieldset.children('div').hide();
75 }
75 }
76
76
77 // columns selection
77 // columns selection
78 function moveOptions(theSelFrom, theSelTo) {
78 function moveOptions(theSelFrom, theSelTo) {
79 $(theSelFrom).find('option:selected').detach().prop("selected", false).appendTo($(theSelTo));
79 $(theSelFrom).find('option:selected').detach().prop("selected", false).appendTo($(theSelTo));
80 }
80 }
81
81
82 function moveOptionUp(theSel) {
82 function moveOptionUp(theSel) {
83 $(theSel).find('option:selected').each(function(){
83 $(theSel).find('option:selected').each(function(){
84 $(this).prev(':not(:selected)').detach().insertAfter($(this));
84 $(this).prev(':not(:selected)').detach().insertAfter($(this));
85 });
85 });
86 }
86 }
87
87
88 function moveOptionTop(theSel) {
88 function moveOptionTop(theSel) {
89 $(theSel).find('option:selected').detach().prependTo($(theSel));
89 $(theSel).find('option:selected').detach().prependTo($(theSel));
90 }
90 }
91
91
92 function moveOptionDown(theSel) {
92 function moveOptionDown(theSel) {
93 $($(theSel).find('option:selected').get().reverse()).each(function(){
93 $($(theSel).find('option:selected').get().reverse()).each(function(){
94 $(this).next(':not(:selected)').detach().insertBefore($(this));
94 $(this).next(':not(:selected)').detach().insertBefore($(this));
95 });
95 });
96 }
96 }
97
97
98 function moveOptionBottom(theSel) {
98 function moveOptionBottom(theSel) {
99 $(theSel).find('option:selected').detach().appendTo($(theSel));
99 $(theSel).find('option:selected').detach().appendTo($(theSel));
100 }
100 }
101
101
102 function initFilters() {
102 function initFilters() {
103 $('#add_filter_select').change(function() {
103 $('#add_filter_select').change(function() {
104 addFilter($(this).val(), '', []);
104 addFilter($(this).val(), '', []);
105 });
105 });
106 $('#filters-table td.field input[type=checkbox]').each(function() {
106 $('#filters-table td.field input[type=checkbox]').each(function() {
107 toggleFilter($(this).val());
107 toggleFilter($(this).val());
108 });
108 });
109 $('#filters-table').on('click', 'td.field input[type=checkbox]', function() {
109 $('#filters-table').on('click', 'td.field input[type=checkbox]', function() {
110 toggleFilter($(this).val());
110 toggleFilter($(this).val());
111 });
111 });
112 $('#filters-table').on('click', '.toggle-multiselect', function() {
112 $('#filters-table').on('click', '.toggle-multiselect', function() {
113 toggleMultiSelect($(this).siblings('select'));
113 toggleMultiSelect($(this).siblings('select'));
114 });
114 });
115 $('#filters-table').on('keypress', 'input[type=text]', function(e) {
115 $('#filters-table').on('keypress', 'input[type=text]', function(e) {
116 if (e.keyCode == 13) $(this).closest('form').submit();
116 if (e.keyCode == 13) $(this).closest('form').submit();
117 });
117 });
118 }
118 }
119
119
120 function addFilter(field, operator, values) {
120 function addFilter(field, operator, values) {
121 var fieldId = field.replace('.', '_');
121 var fieldId = field.replace('.', '_');
122 var tr = $('#tr_'+fieldId);
122 var tr = $('#tr_'+fieldId);
123 if (tr.length > 0) {
123 if (tr.length > 0) {
124 tr.show();
124 tr.show();
125 } else {
125 } else {
126 buildFilterRow(field, operator, values);
126 buildFilterRow(field, operator, values);
127 }
127 }
128 $('#cb_'+fieldId).prop('checked', true);
128 $('#cb_'+fieldId).prop('checked', true);
129 toggleFilter(field);
129 toggleFilter(field);
130 $('#add_filter_select').val('').find('option').each(function() {
130 $('#add_filter_select').val('').find('option').each(function() {
131 if ($(this).attr('value') == field) {
131 if ($(this).attr('value') == field) {
132 $(this).attr('disabled', true);
132 $(this).attr('disabled', true);
133 }
133 }
134 });
134 });
135 }
135 }
136
136
137 function buildFilterRow(field, operator, values) {
137 function buildFilterRow(field, operator, values) {
138 var fieldId = field.replace('.', '_');
138 var fieldId = field.replace('.', '_');
139 var filterTable = $("#filters-table");
139 var filterTable = $("#filters-table");
140 var filterOptions = availableFilters[field];
140 var filterOptions = availableFilters[field];
141 if (!filterOptions) return;
141 if (!filterOptions) return;
142 var operators = operatorByType[filterOptions['type']];
142 var operators = operatorByType[filterOptions['type']];
143 var filterValues = filterOptions['values'];
143 var filterValues = filterOptions['values'];
144 var i, select;
144 var i, select;
145
145
146 var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html(
146 var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html(
147 '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' +
147 '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' +
148 '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' +
148 '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' +
149 '<td class="values"></td>'
149 '<td class="values"></td>'
150 );
150 );
151 filterTable.append(tr);
151 filterTable.append(tr);
152
152
153 select = tr.find('td.operator select');
153 select = tr.find('td.operator select');
154 for (i = 0; i < operators.length; i++) {
154 for (i = 0; i < operators.length; i++) {
155 var option = $('<option>').val(operators[i]).text(operatorLabels[operators[i]]);
155 var option = $('<option>').val(operators[i]).text(operatorLabels[operators[i]]);
156 if (operators[i] == operator) { option.attr('selected', true); }
156 if (operators[i] == operator) { option.attr('selected', true); }
157 select.append(option);
157 select.append(option);
158 }
158 }
159 select.change(function(){ toggleOperator(field); });
159 select.change(function(){ toggleOperator(field); });
160
160
161 switch (filterOptions['type']) {
161 switch (filterOptions['type']) {
162 case "list":
162 case "list":
163 case "list_optional":
163 case "list_optional":
164 case "list_status":
164 case "list_status":
165 case "list_subprojects":
165 case "list_subprojects":
166 tr.find('td.values').append(
166 tr.find('td.values').append(
167 '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' +
167 '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' +
168 ' <span class="toggle-multiselect">&nbsp;</span></span>'
168 ' <span class="toggle-multiselect">&nbsp;</span></span>'
169 );
169 );
170 select = tr.find('td.values select');
170 select = tr.find('td.values select');
171 if (values.length > 1) { select.attr('multiple', true); }
171 if (values.length > 1) { select.attr('multiple', true); }
172 for (i = 0; i < filterValues.length; i++) {
172 for (i = 0; i < filterValues.length; i++) {
173 var filterValue = filterValues[i];
173 var filterValue = filterValues[i];
174 var option = $('<option>');
174 var option = $('<option>');
175 if ($.isArray(filterValue)) {
175 if ($.isArray(filterValue)) {
176 option.val(filterValue[1]).text(filterValue[0]);
176 option.val(filterValue[1]).text(filterValue[0]);
177 if ($.inArray(filterValue[1], values) > -1) {option.attr('selected', true);}
177 if ($.inArray(filterValue[1], values) > -1) {option.attr('selected', true);}
178 } else {
178 } else {
179 option.val(filterValue).text(filterValue);
179 option.val(filterValue).text(filterValue);
180 if ($.inArray(filterValue, values) > -1) {option.attr('selected', true);}
180 if ($.inArray(filterValue, values) > -1) {option.attr('selected', true);}
181 }
181 }
182 select.append(option);
182 select.append(option);
183 }
183 }
184 break;
184 break;
185 case "date":
185 case "date":
186 case "date_past":
186 case "date_past":
187 tr.find('td.values').append(
187 tr.find('td.values').append(
188 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" /></span>' +
188 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" /></span>' +
189 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" /></span>' +
189 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" /></span>' +
190 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" /> '+labelDayPlural+'</span>'
190 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" /> '+labelDayPlural+'</span>'
191 );
191 );
192 $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions);
192 $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions);
193 $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions);
193 $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions);
194 $('#values_'+fieldId).val(values[0]);
194 $('#values_'+fieldId).val(values[0]);
195 break;
195 break;
196 case "string":
196 case "string":
197 case "text":
197 case "text":
198 tr.find('td.values').append(
198 tr.find('td.values').append(
199 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" /></span>'
199 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" /></span>'
200 );
200 );
201 $('#values_'+fieldId).val(values[0]);
201 $('#values_'+fieldId).val(values[0]);
202 break;
202 break;
203 case "relation":
203 case "relation":
204 tr.find('td.values').append(
204 tr.find('td.values').append(
205 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="6" class="value" /></span>' +
205 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="6" class="value" /></span>' +
206 '<span style="display:none;"><select class="value" name="v['+field+'][]" id="values_'+fieldId+'_1"></select></span>'
206 '<span style="display:none;"><select class="value" name="v['+field+'][]" id="values_'+fieldId+'_1"></select></span>'
207 );
207 );
208 $('#values_'+fieldId).val(values[0]);
208 $('#values_'+fieldId).val(values[0]);
209 select = tr.find('td.values select');
209 select = tr.find('td.values select');
210 for (i = 0; i < allProjects.length; i++) {
210 for (i = 0; i < allProjects.length; i++) {
211 var filterValue = allProjects[i];
211 var filterValue = allProjects[i];
212 var option = $('<option>');
212 var option = $('<option>');
213 option.val(filterValue[1]).text(filterValue[0]);
213 option.val(filterValue[1]).text(filterValue[0]);
214 if (values[0] == filterValue[1]) { option.attr('selected', true); }
214 if (values[0] == filterValue[1]) { option.attr('selected', true); }
215 select.append(option);
215 select.append(option);
216 }
216 }
217 break;
217 break;
218 case "integer":
218 case "integer":
219 case "float":
219 case "float":
220 case "tree":
220 case "tree":
221 tr.find('td.values').append(
221 tr.find('td.values').append(
222 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" /></span>' +
222 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" /></span>' +
223 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" /></span>'
223 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" /></span>'
224 );
224 );
225 $('#values_'+fieldId+'_1').val(values[0]);
225 $('#values_'+fieldId+'_1').val(values[0]);
226 $('#values_'+fieldId+'_2').val(values[1]);
226 $('#values_'+fieldId+'_2').val(values[1]);
227 break;
227 break;
228 }
228 }
229 }
229 }
230
230
231 function toggleFilter(field) {
231 function toggleFilter(field) {
232 var fieldId = field.replace('.', '_');
232 var fieldId = field.replace('.', '_');
233 if ($('#cb_' + fieldId).is(':checked')) {
233 if ($('#cb_' + fieldId).is(':checked')) {
234 $("#operators_" + fieldId).show().removeAttr('disabled');
234 $("#operators_" + fieldId).show().removeAttr('disabled');
235 toggleOperator(field);
235 toggleOperator(field);
236 } else {
236 } else {
237 $("#operators_" + fieldId).hide().attr('disabled', true);
237 $("#operators_" + fieldId).hide().attr('disabled', true);
238 enableValues(field, []);
238 enableValues(field, []);
239 }
239 }
240 }
240 }
241
241
242 function enableValues(field, indexes) {
242 function enableValues(field, indexes) {
243 var fieldId = field.replace('.', '_');
243 var fieldId = field.replace('.', '_');
244 $('#tr_'+fieldId+' td.values .value').each(function(index) {
244 $('#tr_'+fieldId+' td.values .value').each(function(index) {
245 if ($.inArray(index, indexes) >= 0) {
245 if ($.inArray(index, indexes) >= 0) {
246 $(this).removeAttr('disabled');
246 $(this).removeAttr('disabled');
247 $(this).parents('span').first().show();
247 $(this).parents('span').first().show();
248 } else {
248 } else {
249 $(this).val('');
249 $(this).val('');
250 $(this).attr('disabled', true);
250 $(this).attr('disabled', true);
251 $(this).parents('span').first().hide();
251 $(this).parents('span').first().hide();
252 }
252 }
253
253
254 if ($(this).hasClass('group')) {
254 if ($(this).hasClass('group')) {
255 $(this).addClass('open');
255 $(this).addClass('open');
256 } else {
256 } else {
257 $(this).show();
257 $(this).show();
258 }
258 }
259 });
259 });
260 }
260 }
261
261
262 function toggleOperator(field) {
262 function toggleOperator(field) {
263 var fieldId = field.replace('.', '_');
263 var fieldId = field.replace('.', '_');
264 var operator = $("#operators_" + fieldId);
264 var operator = $("#operators_" + fieldId);
265 switch (operator.val()) {
265 switch (operator.val()) {
266 case "!*":
266 case "!*":
267 case "*":
267 case "*":
268 case "t":
268 case "t":
269 case "ld":
269 case "ld":
270 case "w":
270 case "w":
271 case "lw":
271 case "lw":
272 case "l2w":
272 case "l2w":
273 case "m":
273 case "m":
274 case "lm":
274 case "lm":
275 case "y":
275 case "y":
276 case "o":
276 case "o":
277 case "c":
277 case "c":
278 enableValues(field, []);
278 enableValues(field, []);
279 break;
279 break;
280 case "><":
280 case "><":
281 enableValues(field, [0,1]);
281 enableValues(field, [0,1]);
282 break;
282 break;
283 case "<t+":
283 case "<t+":
284 case ">t+":
284 case ">t+":
285 case "><t+":
285 case "><t+":
286 case "t+":
286 case "t+":
287 case ">t-":
287 case ">t-":
288 case "<t-":
288 case "<t-":
289 case "><t-":
289 case "><t-":
290 case "t-":
290 case "t-":
291 enableValues(field, [2]);
291 enableValues(field, [2]);
292 break;
292 break;
293 case "=p":
293 case "=p":
294 case "=!p":
294 case "=!p":
295 case "!p":
295 case "!p":
296 enableValues(field, [1]);
296 enableValues(field, [1]);
297 break;
297 break;
298 default:
298 default:
299 enableValues(field, [0]);
299 enableValues(field, [0]);
300 break;
300 break;
301 }
301 }
302 }
302 }
303
303
304 function toggleMultiSelect(el) {
304 function toggleMultiSelect(el) {
305 if (el.attr('multiple')) {
305 if (el.attr('multiple')) {
306 el.removeAttr('multiple');
306 el.removeAttr('multiple');
307 el.attr('size', 1);
307 el.attr('size', 1);
308 } else {
308 } else {
309 el.attr('multiple', true);
309 el.attr('multiple', true);
310 if (el.children().length > 10)
310 if (el.children().length > 10)
311 el.attr('size', 10);
311 el.attr('size', 10);
312 else
312 else
313 el.attr('size', 4);
313 el.attr('size', 4);
314 }
314 }
315 }
315 }
316
316
317 function showTab(name, url) {
317 function showTab(name, url) {
318 $('#tab-content-' + name).parent().find('.tab-content').hide();
318 $('#tab-content-' + name).parent().find('.tab-content').hide();
319 $('#tab-content-' + name).parent().find('div.tabs a').removeClass('selected');
319 $('#tab-content-' + name).parent().find('div.tabs a').removeClass('selected');
320 $('#tab-content-' + name).show();
320 $('#tab-content-' + name).show();
321 $('#tab-' + name).addClass('selected');
321 $('#tab-' + name).addClass('selected');
322 //replaces current URL with the "href" attribute of the current link
322 //replaces current URL with the "href" attribute of the current link
323 //(only triggered if supported by browser)
323 //(only triggered if supported by browser)
324 if ("replaceState" in window.history) {
324 if ("replaceState" in window.history) {
325 window.history.replaceState(null, document.title, url);
325 window.history.replaceState(null, document.title, url);
326 }
326 }
327 return false;
327 return false;
328 }
328 }
329
329
330 function moveTabRight(el) {
330 function moveTabRight(el) {
331 var lis = $(el).parents('div.tabs').first().find('ul').children();
331 var lis = $(el).parents('div.tabs').first().find('ul').children();
332 var tabsWidth = 0;
332 var tabsWidth = 0;
333 var i = 0;
333 var i = 0;
334 lis.each(function() {
334 lis.each(function() {
335 if ($(this).is(':visible')) {
335 if ($(this).is(':visible')) {
336 tabsWidth += $(this).width() + 6;
336 tabsWidth += $(this).width() + 6;
337 }
337 }
338 });
338 });
339 if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; }
339 if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; }
340 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
340 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
341 lis.eq(i).hide();
341 lis.eq(i).hide();
342 }
342 }
343
343
344 function moveTabLeft(el) {
344 function moveTabLeft(el) {
345 var lis = $(el).parents('div.tabs').first().find('ul').children();
345 var lis = $(el).parents('div.tabs').first().find('ul').children();
346 var i = 0;
346 var i = 0;
347 while (i < lis.length && !lis.eq(i).is(':visible')) { i++; }
347 while (i < lis.length && !lis.eq(i).is(':visible')) { i++; }
348 if (i > 0) {
348 if (i > 0) {
349 lis.eq(i-1).show();
349 lis.eq(i-1).show();
350 }
350 }
351 }
351 }
352
352
353 function displayTabsButtons() {
353 function displayTabsButtons() {
354 var lis;
354 var lis;
355 var tabsWidth;
355 var tabsWidth;
356 var el;
356 var el;
357 $('div.tabs').each(function() {
357 $('div.tabs').each(function() {
358 el = $(this);
358 el = $(this);
359 lis = el.find('ul').children();
359 lis = el.find('ul').children();
360 tabsWidth = 0;
360 tabsWidth = 0;
361 lis.each(function(){
361 lis.each(function(){
362 if ($(this).is(':visible')) {
362 if ($(this).is(':visible')) {
363 tabsWidth += $(this).width() + 6;
363 tabsWidth += $(this).width() + 6;
364 }
364 }
365 });
365 });
366 if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) {
366 if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) {
367 el.find('div.tabs-buttons').hide();
367 el.find('div.tabs-buttons').hide();
368 } else {
368 } else {
369 el.find('div.tabs-buttons').show();
369 el.find('div.tabs-buttons').show();
370 }
370 }
371 });
371 });
372 }
372 }
373
373
374 function setPredecessorFieldsVisibility() {
374 function setPredecessorFieldsVisibility() {
375 var relationType = $('#relation_relation_type');
375 var relationType = $('#relation_relation_type');
376 if (relationType.val() == "precedes" || relationType.val() == "follows") {
376 if (relationType.val() == "precedes" || relationType.val() == "follows") {
377 $('#predecessor_fields').show();
377 $('#predecessor_fields').show();
378 } else {
378 } else {
379 $('#predecessor_fields').hide();
379 $('#predecessor_fields').hide();
380 }
380 }
381 }
381 }
382
382
383 function showModal(id, width, title) {
383 function showModal(id, width, title) {
384 var el = $('#'+id).first();
384 var el = $('#'+id).first();
385 if (el.length === 0 || el.is(':visible')) {return;}
385 if (el.length === 0 || el.is(':visible')) {return;}
386 if (!title) title = el.find('h3.title').text();
386 if (!title) title = el.find('h3.title').text();
387 // moves existing modals behind the transparent background
387 // moves existing modals behind the transparent background
388 $(".modal").zIndex(99);
388 $(".modal").zIndex(99);
389 el.dialog({
389 el.dialog({
390 width: width,
390 width: width,
391 modal: true,
391 modal: true,
392 resizable: false,
392 resizable: false,
393 dialogClass: 'modal',
393 dialogClass: 'modal',
394 title: title
394 title: title
395 }).on('dialogclose', function(){
395 }).on('dialogclose', function(){
396 $(".modal").zIndex(101);
396 $(".modal").zIndex(101);
397 });
397 });
398 el.find("input[type=text], input[type=submit]").first().focus();
398 el.find("input[type=text], input[type=submit]").first().focus();
399 }
399 }
400
400
401 function hideModal(el) {
401 function hideModal(el) {
402 var modal;
402 var modal;
403 if (el) {
403 if (el) {
404 modal = $(el).parents('.ui-dialog-content');
404 modal = $(el).parents('.ui-dialog-content');
405 } else {
405 } else {
406 modal = $('#ajax-modal');
406 modal = $('#ajax-modal');
407 }
407 }
408 modal.dialog("close");
408 modal.dialog("close");
409 }
409 }
410
410
411 function submitPreview(url, form, target) {
411 function submitPreview(url, form, target) {
412 $.ajax({
412 $.ajax({
413 url: url,
413 url: url,
414 type: 'post',
414 type: 'post',
415 data: $('#'+form).serialize(),
415 data: $('#'+form).serialize(),
416 success: function(data){
416 success: function(data){
417 $('#'+target).html(data);
417 $('#'+target).html(data);
418 }
418 }
419 });
419 });
420 }
420 }
421
421
422 function collapseScmEntry(id) {
422 function collapseScmEntry(id) {
423 $('.'+id).each(function() {
423 $('.'+id).each(function() {
424 if ($(this).hasClass('open')) {
424 if ($(this).hasClass('open')) {
425 collapseScmEntry($(this).attr('id'));
425 collapseScmEntry($(this).attr('id'));
426 }
426 }
427 $(this).hide();
427 $(this).hide();
428 });
428 });
429 $('#'+id).removeClass('open');
429 $('#'+id).removeClass('open');
430 }
430 }
431
431
432 function expandScmEntry(id) {
432 function expandScmEntry(id) {
433 $('.'+id).each(function() {
433 $('.'+id).each(function() {
434 $(this).show();
434 $(this).show();
435 if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) {
435 if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) {
436 expandScmEntry($(this).attr('id'));
436 expandScmEntry($(this).attr('id'));
437 }
437 }
438 });
438 });
439 $('#'+id).addClass('open');
439 $('#'+id).addClass('open');
440 }
440 }
441
441
442 function scmEntryClick(id, url) {
442 function scmEntryClick(id, url) {
443 var el = $('#'+id);
443 var el = $('#'+id);
444 if (el.hasClass('open')) {
444 if (el.hasClass('open')) {
445 collapseScmEntry(id);
445 collapseScmEntry(id);
446 el.addClass('collapsed');
446 el.addClass('collapsed');
447 return false;
447 return false;
448 } else if (el.hasClass('loaded')) {
448 } else if (el.hasClass('loaded')) {
449 expandScmEntry(id);
449 expandScmEntry(id);
450 el.removeClass('collapsed');
450 el.removeClass('collapsed');
451 return false;
451 return false;
452 }
452 }
453 if (el.hasClass('loading')) {
453 if (el.hasClass('loading')) {
454 return false;
454 return false;
455 }
455 }
456 el.addClass('loading');
456 el.addClass('loading');
457 $.ajax({
457 $.ajax({
458 url: url,
458 url: url,
459 success: function(data) {
459 success: function(data) {
460 el.after(data);
460 el.after(data);
461 el.addClass('open').addClass('loaded').removeClass('loading');
461 el.addClass('open').addClass('loaded').removeClass('loading');
462 }
462 }
463 });
463 });
464 return true;
464 return true;
465 }
465 }
466
466
467 function randomKey(size) {
467 function randomKey(size) {
468 var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
468 var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
469 var key = '';
469 var key = '';
470 for (var i = 0; i < size; i++) {
470 for (var i = 0; i < size; i++) {
471 key += chars.charAt(Math.floor(Math.random() * chars.length));
471 key += chars.charAt(Math.floor(Math.random() * chars.length));
472 }
472 }
473 return key;
473 return key;
474 }
474 }
475
475
476 function updateIssueFrom(url) {
476 function updateIssueFrom(url, el) {
477 $('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){
477 $('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){
478 $(this).data('valuebeforeupdate', $(this).val());
478 $(this).data('valuebeforeupdate', $(this).val());
479 });
479 });
480 if (el) {
481 $("#form_update_triggered_by").val($(el).attr('id'));
482 }
480 return $.ajax({
483 return $.ajax({
481 url: url,
484 url: url,
482 type: 'post',
485 type: 'post',
483 data: $('#issue-form').serialize()
486 data: $('#issue-form').serialize()
484 });
487 });
485 }
488 }
486
489
487 function replaceIssueFormWith(html){
490 function replaceIssueFormWith(html){
488 var replacement = $(html);
491 var replacement = $(html);
489 $('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){
492 $('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){
490 var object_id = $(this).attr('id');
493 var object_id = $(this).attr('id');
491 if (object_id && $(this).data('valuebeforeupdate')!=$(this).val()) {
494 if (object_id && $(this).data('valuebeforeupdate')!=$(this).val()) {
492 replacement.find('#'+object_id).val($(this).val());
495 replacement.find('#'+object_id).val($(this).val());
493 }
496 }
494 });
497 });
495 $('#all_attributes').empty();
498 $('#all_attributes').empty();
496 $('#all_attributes').prepend(replacement);
499 $('#all_attributes').prepend(replacement);
497 }
500 }
498
501
499 function updateBulkEditFrom(url) {
502 function updateBulkEditFrom(url) {
500 $.ajax({
503 $.ajax({
501 url: url,
504 url: url,
502 type: 'post',
505 type: 'post',
503 data: $('#bulk_edit_form').serialize()
506 data: $('#bulk_edit_form').serialize()
504 });
507 });
505 }
508 }
506
509
507 function observeAutocompleteField(fieldId, url, options) {
510 function observeAutocompleteField(fieldId, url, options) {
508 $(document).ready(function() {
511 $(document).ready(function() {
509 $('#'+fieldId).autocomplete($.extend({
512 $('#'+fieldId).autocomplete($.extend({
510 source: url,
513 source: url,
511 minLength: 2,
514 minLength: 2,
512 search: function(){$('#'+fieldId).addClass('ajax-loading');},
515 search: function(){$('#'+fieldId).addClass('ajax-loading');},
513 response: function(){$('#'+fieldId).removeClass('ajax-loading');}
516 response: function(){$('#'+fieldId).removeClass('ajax-loading');}
514 }, options));
517 }, options));
515 $('#'+fieldId).addClass('autocomplete');
518 $('#'+fieldId).addClass('autocomplete');
516 });
519 });
517 }
520 }
518
521
519 function observeSearchfield(fieldId, targetId, url) {
522 function observeSearchfield(fieldId, targetId, url) {
520 $('#'+fieldId).each(function() {
523 $('#'+fieldId).each(function() {
521 var $this = $(this);
524 var $this = $(this);
522 $this.addClass('autocomplete');
525 $this.addClass('autocomplete');
523 $this.attr('data-value-was', $this.val());
526 $this.attr('data-value-was', $this.val());
524 var check = function() {
527 var check = function() {
525 var val = $this.val();
528 var val = $this.val();
526 if ($this.attr('data-value-was') != val){
529 if ($this.attr('data-value-was') != val){
527 $this.attr('data-value-was', val);
530 $this.attr('data-value-was', val);
528 $.ajax({
531 $.ajax({
529 url: url,
532 url: url,
530 type: 'get',
533 type: 'get',
531 data: {q: $this.val()},
534 data: {q: $this.val()},
532 success: function(data){ if(targetId) $('#'+targetId).html(data); },
535 success: function(data){ if(targetId) $('#'+targetId).html(data); },
533 beforeSend: function(){ $this.addClass('ajax-loading'); },
536 beforeSend: function(){ $this.addClass('ajax-loading'); },
534 complete: function(){ $this.removeClass('ajax-loading'); }
537 complete: function(){ $this.removeClass('ajax-loading'); }
535 });
538 });
536 }
539 }
537 };
540 };
538 var reset = function() {
541 var reset = function() {
539 if (timer) {
542 if (timer) {
540 clearInterval(timer);
543 clearInterval(timer);
541 timer = setInterval(check, 300);
544 timer = setInterval(check, 300);
542 }
545 }
543 };
546 };
544 var timer = setInterval(check, 300);
547 var timer = setInterval(check, 300);
545 $this.bind('keyup click mousemove', reset);
548 $this.bind('keyup click mousemove', reset);
546 });
549 });
547 }
550 }
548
551
549 function beforeShowDatePicker(input, inst) {
552 function beforeShowDatePicker(input, inst) {
550 var default_date = null;
553 var default_date = null;
551 switch ($(input).attr("id")) {
554 switch ($(input).attr("id")) {
552 case "issue_start_date" :
555 case "issue_start_date" :
553 if ($("#issue_due_date").size() > 0) {
556 if ($("#issue_due_date").size() > 0) {
554 default_date = $("#issue_due_date").val();
557 default_date = $("#issue_due_date").val();
555 }
558 }
556 break;
559 break;
557 case "issue_due_date" :
560 case "issue_due_date" :
558 if ($("#issue_start_date").size() > 0) {
561 if ($("#issue_start_date").size() > 0) {
559 default_date = $("#issue_start_date").val();
562 default_date = $("#issue_start_date").val();
560 }
563 }
561 break;
564 break;
562 }
565 }
563 $(input).datepicker("option", "defaultDate", default_date);
566 $(input).datepicker("option", "defaultDate", default_date);
564 }
567 }
565
568
566 function initMyPageSortable(list, url) {
569 function initMyPageSortable(list, url) {
567 $('#list-'+list).sortable({
570 $('#list-'+list).sortable({
568 connectWith: '.block-receiver',
571 connectWith: '.block-receiver',
569 tolerance: 'pointer',
572 tolerance: 'pointer',
570 update: function(){
573 update: function(){
571 $.ajax({
574 $.ajax({
572 url: url,
575 url: url,
573 type: 'post',
576 type: 'post',
574 data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})}
577 data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})}
575 });
578 });
576 }
579 }
577 });
580 });
578 $("#list-top, #list-left, #list-right").disableSelection();
581 $("#list-top, #list-left, #list-right").disableSelection();
579 }
582 }
580
583
581 var warnLeavingUnsavedMessage;
584 var warnLeavingUnsavedMessage;
582 function warnLeavingUnsaved(message) {
585 function warnLeavingUnsaved(message) {
583 warnLeavingUnsavedMessage = message;
586 warnLeavingUnsavedMessage = message;
584 $(document).on('submit', 'form', function(){
587 $(document).on('submit', 'form', function(){
585 $('textarea').removeData('changed');
588 $('textarea').removeData('changed');
586 });
589 });
587 $(document).on('change', 'textarea', function(){
590 $(document).on('change', 'textarea', function(){
588 $(this).data('changed', 'changed');
591 $(this).data('changed', 'changed');
589 });
592 });
590 window.onbeforeunload = function(){
593 window.onbeforeunload = function(){
591 var warn = false;
594 var warn = false;
592 $('textarea').blur().each(function(){
595 $('textarea').blur().each(function(){
593 if ($(this).data('changed')) {
596 if ($(this).data('changed')) {
594 warn = true;
597 warn = true;
595 }
598 }
596 });
599 });
597 if (warn) {return warnLeavingUnsavedMessage;}
600 if (warn) {return warnLeavingUnsavedMessage;}
598 };
601 };
599 }
602 }
600
603
601 function setupAjaxIndicator() {
604 function setupAjaxIndicator() {
602 $(document).bind('ajaxSend', function(event, xhr, settings) {
605 $(document).bind('ajaxSend', function(event, xhr, settings) {
603 if ($('.ajax-loading').length === 0 && settings.contentType != 'application/octet-stream') {
606 if ($('.ajax-loading').length === 0 && settings.contentType != 'application/octet-stream') {
604 $('#ajax-indicator').show();
607 $('#ajax-indicator').show();
605 }
608 }
606 });
609 });
607 $(document).bind('ajaxStop', function() {
610 $(document).bind('ajaxStop', function() {
608 $('#ajax-indicator').hide();
611 $('#ajax-indicator').hide();
609 });
612 });
610 }
613 }
611
614
612 function hideOnLoad() {
615 function hideOnLoad() {
613 $('.hol').hide();
616 $('.hol').hide();
614 }
617 }
615
618
616 function addFormObserversForDoubleSubmit() {
619 function addFormObserversForDoubleSubmit() {
617 $('form[method=post]').each(function() {
620 $('form[method=post]').each(function() {
618 if (!$(this).hasClass('multiple-submit')) {
621 if (!$(this).hasClass('multiple-submit')) {
619 $(this).submit(function(form_submission) {
622 $(this).submit(function(form_submission) {
620 if ($(form_submission.target).attr('data-submitted')) {
623 if ($(form_submission.target).attr('data-submitted')) {
621 form_submission.preventDefault();
624 form_submission.preventDefault();
622 } else {
625 } else {
623 $(form_submission.target).attr('data-submitted', true);
626 $(form_submission.target).attr('data-submitted', true);
624 }
627 }
625 });
628 });
626 }
629 }
627 });
630 });
628 }
631 }
629
632
630 function defaultFocus(){
633 function defaultFocus(){
631 if (($('#content :focus').length == 0) && (window.location.hash == '')) {
634 if (($('#content :focus').length == 0) && (window.location.hash == '')) {
632 $('#content input[type=text], #content textarea').first().focus();
635 $('#content input[type=text], #content textarea').first().focus();
633 }
636 }
634 }
637 }
635
638
636 function blockEventPropagation(event) {
639 function blockEventPropagation(event) {
637 event.stopPropagation();
640 event.stopPropagation();
638 event.preventDefault();
641 event.preventDefault();
639 }
642 }
640
643
641 function toggleDisabledOnChange() {
644 function toggleDisabledOnChange() {
642 var checked = $(this).is(':checked');
645 var checked = $(this).is(':checked');
643 $($(this).data('disables')).attr('disabled', checked);
646 $($(this).data('disables')).attr('disabled', checked);
644 $($(this).data('enables')).attr('disabled', !checked);
647 $($(this).data('enables')).attr('disabled', !checked);
645 }
648 }
646 function toggleDisabledInit() {
649 function toggleDisabledInit() {
647 $('input[data-disables], input[data-enables]').each(toggleDisabledOnChange);
650 $('input[data-disables], input[data-enables]').each(toggleDisabledOnChange);
648 }
651 }
649 $(document).ready(function(){
652 $(document).ready(function(){
650 $('#content').on('change', 'input[data-disables], input[data-enables]', toggleDisabledOnChange);
653 $('#content').on('change', 'input[data-disables], input[data-enables]', toggleDisabledOnChange);
651 toggleDisabledInit();
654 toggleDisabledInit();
652 });
655 });
653
656
654 function keepAnchorOnSignIn(form){
657 function keepAnchorOnSignIn(form){
655 var hash = decodeURIComponent(self.document.location.hash);
658 var hash = decodeURIComponent(self.document.location.hash);
656 if (hash) {
659 if (hash) {
657 if (hash.indexOf("#") === -1) {
660 if (hash.indexOf("#") === -1) {
658 hash = "#" + hash;
661 hash = "#" + hash;
659 }
662 }
660 form.action = form.action + hash;
663 form.action = form.action + hash;
661 }
664 }
662 return true;
665 return true;
663 }
666 }
664
667
665 $(document).ready(setupAjaxIndicator);
668 $(document).ready(setupAjaxIndicator);
666 $(document).ready(hideOnLoad);
669 $(document).ready(hideOnLoad);
667 $(document).ready(addFormObserversForDoubleSubmit);
670 $(document).ready(addFormObserversForDoubleSubmit);
668 $(document).ready(defaultFocus);
671 $(document).ready(defaultFocus);
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now