##// END OF EJS Templates
Displays the full form when creating a version from the issue form so that required custom fields can be filled (#7398)....
Jean-Philippe Lang -
r8725:60b9e59d1580
parent child
Show More
@@ -0,0 +1,9
1 <h3 class="title"><%=l(:label_version_new)%></h3>
2
3 <% labelled_remote_form_for @version, :url => project_versions_path(@project) do |f| %>
4 <%= render :partial => 'versions/form', :locals => { :f => f } %>
5 <p class="buttons">
6 <%= submit_tag l(:button_create), :name => nil %>
7 <%= submit_tag l(:button_cancel), :name => nil, :onclick => "hideModal(this);", :type => 'button' %>
8 </p>
9 <% end %>
@@ -1,189 +1,205
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 VersionsController < ApplicationController
18 class VersionsController < ApplicationController
19 menu_item :roadmap
19 menu_item :roadmap
20 model_object Version
20 model_object Version
21 before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
21 before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
22 before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
22 before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
23 before_filter :find_project, :only => [:index, :new, :create, :close_completed]
23 before_filter :find_project, :only => [:index, :new, :create, :close_completed]
24 before_filter :authorize
24 before_filter :authorize
25
25
26 accept_api_auth :index, :create, :update, :destroy
26 accept_api_auth :index, :create, :update, :destroy
27
27
28 helper :custom_fields
28 helper :custom_fields
29 helper :projects
29 helper :projects
30
30
31 def index
31 def index
32 respond_to do |format|
32 respond_to do |format|
33 format.html {
33 format.html {
34 @trackers = @project.trackers.find(:all, :order => 'position')
34 @trackers = @project.trackers.find(:all, :order => 'position')
35 retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
35 retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
36 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
36 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
37 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
37 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
38
38
39 @versions = @project.shared_versions || []
39 @versions = @project.shared_versions || []
40 @versions += @project.rolled_up_versions.visible if @with_subprojects
40 @versions += @project.rolled_up_versions.visible if @with_subprojects
41 @versions = @versions.uniq.sort
41 @versions = @versions.uniq.sort
42 unless params[:completed]
42 unless params[:completed]
43 @completed_versions = @versions.select {|version| version.closed? || version.completed? }
43 @completed_versions = @versions.select {|version| version.closed? || version.completed? }
44 @versions -= @completed_versions
44 @versions -= @completed_versions
45 end
45 end
46
46
47 @issues_by_version = {}
47 @issues_by_version = {}
48 unless @selected_tracker_ids.empty?
48 unless @selected_tracker_ids.empty?
49 @versions.each do |version|
49 @versions.each do |version|
50 issues = version.fixed_issues.visible.find(:all,
50 issues = version.fixed_issues.visible.find(:all,
51 :include => [:project, :status, :tracker, :priority],
51 :include => [:project, :status, :tracker, :priority],
52 :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids},
52 :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids},
53 :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
53 :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
54 @issues_by_version[version] = issues
54 @issues_by_version[version] = issues
55 end
55 end
56 end
56 end
57 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
57 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?}
58 }
58 }
59 format.api {
59 format.api {
60 @versions = @project.shared_versions.all
60 @versions = @project.shared_versions.all
61 }
61 }
62 end
62 end
63 end
63 end
64
64
65 def show
65 def show
66 respond_to do |format|
66 respond_to do |format|
67 format.html {
67 format.html {
68 @issues = @version.fixed_issues.visible.find(:all,
68 @issues = @version.fixed_issues.visible.find(:all,
69 :include => [:status, :tracker, :priority],
69 :include => [:status, :tracker, :priority],
70 :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
70 :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
71 }
71 }
72 format.api
72 format.api
73 end
73 end
74 end
74 end
75
75
76 def new
76 def new
77 @version = @project.versions.build(params[:version])
77 @version = @project.versions.build(params[:version])
78
79 respond_to do |format|
80 format.html
81 format.js do
82 render :update do |page|
83 page.replace_html 'ajax-modal', :partial => 'versions/new_modal'
84 page << "showModal('ajax-modal', '600px');"
85 page << "Form.Element.focus('version_name');"
86 end
87 end
88 end
78 end
89 end
79
90
80 def create
91 def create
81 @version = @project.versions.build
92 @version = @project.versions.build
82 if params[:version]
93 if params[:version]
83 attributes = params[:version].dup
94 attributes = params[:version].dup
84 attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
95 attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
85 @version.attributes = attributes
96 @version.attributes = attributes
86 end
97 end
87
98
88 if request.post?
99 if request.post?
89 if @version.save
100 if @version.save
90 respond_to do |format|
101 respond_to do |format|
91 format.html do
102 format.html do
92 flash[:notice] = l(:notice_successful_create)
103 flash[:notice] = l(:notice_successful_create)
93 redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
104 redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
94 end
105 end
95 format.js do
106 format.js do
96 # IE doesn't support the replace_html rjs method for select box options
107 render(:update) {|page|
97 render(:update) {|page| page.replace "issue_fixed_version_id",
108 page << 'hideModal();'
98 content_tag('select', '<option></option>' + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
109 # IE doesn't support the replace_html rjs method for select box options
110 page.replace "issue_fixed_version_id",
111 content_tag('select', '<option></option>' + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
99 }
112 }
100 end
113 end
101 format.api do
114 format.api do
102 render :action => 'show', :status => :created, :location => version_url(@version)
115 render :action => 'show', :status => :created, :location => version_url(@version)
103 end
116 end
104 end
117 end
105 else
118 else
106 respond_to do |format|
119 respond_to do |format|
107 format.html { render :action => 'new' }
120 format.html { render :action => 'new' }
108 format.js do
121 format.js do
109 render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
122 render :update do |page|
123 page.replace_html 'ajax-modal', :partial => 'versions/new_modal'
124 page << "Form.Element.focus('version_name');"
125 end
110 end
126 end
111 format.api { render_validation_errors(@version) }
127 format.api { render_validation_errors(@version) }
112 end
128 end
113 end
129 end
114 end
130 end
115 end
131 end
116
132
117 def edit
133 def edit
118 end
134 end
119
135
120 def update
136 def update
121 if request.put? && params[:version]
137 if request.put? && params[:version]
122 attributes = params[:version].dup
138 attributes = params[:version].dup
123 attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
139 attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
124 if @version.update_attributes(attributes)
140 if @version.update_attributes(attributes)
125 respond_to do |format|
141 respond_to do |format|
126 format.html {
142 format.html {
127 flash[:notice] = l(:notice_successful_update)
143 flash[:notice] = l(:notice_successful_update)
128 redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
144 redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
129 }
145 }
130 format.api { head :ok }
146 format.api { head :ok }
131 end
147 end
132 else
148 else
133 respond_to do |format|
149 respond_to do |format|
134 format.html { render :action => 'edit' }
150 format.html { render :action => 'edit' }
135 format.api { render_validation_errors(@version) }
151 format.api { render_validation_errors(@version) }
136 end
152 end
137 end
153 end
138 end
154 end
139 end
155 end
140
156
141 def close_completed
157 def close_completed
142 if request.put?
158 if request.put?
143 @project.close_completed_versions
159 @project.close_completed_versions
144 end
160 end
145 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
161 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
146 end
162 end
147
163
148 verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
164 verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
149 def destroy
165 def destroy
150 if @version.fixed_issues.empty?
166 if @version.fixed_issues.empty?
151 @version.destroy
167 @version.destroy
152 respond_to do |format|
168 respond_to do |format|
153 format.html { redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project }
169 format.html { redirect_back_or_default :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project }
154 format.api { head :ok }
170 format.api { head :ok }
155 end
171 end
156 else
172 else
157 respond_to do |format|
173 respond_to do |format|
158 format.html {
174 format.html {
159 flash[:error] = l(:notice_unable_delete_version)
175 flash[:error] = l(:notice_unable_delete_version)
160 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
176 redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
161 }
177 }
162 format.api { head :unprocessable_entity }
178 format.api { head :unprocessable_entity }
163 end
179 end
164 end
180 end
165 end
181 end
166
182
167 def status_by
183 def status_by
168 respond_to do |format|
184 respond_to do |format|
169 format.html { render :action => 'show' }
185 format.html { render :action => 'show' }
170 format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
186 format.js { render(:update) {|page| page.replace_html 'status_by', render_issue_status_by(@version, params[:status_by])} }
171 end
187 end
172 end
188 end
173
189
174 private
190 private
175 def find_project
191 def find_project
176 @project = Project.find(params[:project_id])
192 @project = Project.find(params[:project_id])
177 rescue ActiveRecord::RecordNotFound
193 rescue ActiveRecord::RecordNotFound
178 render_404
194 render_404
179 end
195 end
180
196
181 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
197 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
182 if ids = params[:tracker_ids]
198 if ids = params[:tracker_ids]
183 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
199 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
184 else
200 else
185 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
201 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
186 end
202 end
187 end
203 end
188
204
189 end
205 end
@@ -1,69 +1,67
1 <% labelled_fields_for :issue, @issue do |f| %>
1 <% labelled_fields_for :issue, @issue do |f| %>
2
2
3 <div class="splitcontentleft">
3 <div class="splitcontentleft">
4 <% if @issue.safe_attribute? 'status_id' %>
4 <% if @issue.safe_attribute? 'status_id' %>
5 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
5 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
6 <% else %>
6 <% else %>
7 <p><label><%= l(:field_status) %></label> <%= h(@issue.status.name) %></p>
7 <p><label><%= l(:field_status) %></label> <%= h(@issue.status.name) %></p>
8 <% end %>
8 <% end %>
9
9
10 <% if @issue.safe_attribute? 'priority_id' %>
10 <% if @issue.safe_attribute? 'priority_id' %>
11 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %></p>
11 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %></p>
12 <% end %>
12 <% end %>
13
13
14 <% if @issue.safe_attribute? 'assigned_to_id' %>
14 <% if @issue.safe_attribute? 'assigned_to_id' %>
15 <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>
15 <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>
16 <% end %>
16 <% end %>
17
17
18 <% if @issue.safe_attribute?('category_id') && @issue.project.issue_categories.any? %>
18 <% if @issue.safe_attribute?('category_id') && @issue.project.issue_categories.any? %>
19 <p><%= f.select :category_id, (@issue.project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
19 <p><%= f.select :category_id, (@issue.project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
20 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
20 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
21 l(:label_issue_category_new),
21 l(:label_issue_category_new),
22 'issue_category[name]',
22 'issue_category[name]',
23 {:controller => 'issue_categories', :action => 'create', :project_id => @issue.project},
23 {:controller => 'issue_categories', :action => 'create', :project_id => @issue.project},
24 :title => l(:label_issue_category_new),
24 :title => l(:label_issue_category_new),
25 :tabindex => 199) if User.current.allowed_to?(:manage_categories, @issue.project) %></p>
25 :tabindex => 199) if User.current.allowed_to?(:manage_categories, @issue.project) %></p>
26 <% end %>
26 <% end %>
27
27
28 <% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %>
28 <% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %>
29 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %>
29 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %>
30 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
30 <%= link_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
31 l(:label_version_new),
31 {:url => new_project_version_path(@issue.project), :method => 'get'},
32 'version[name]',
32 :title => l(:label_version_new),
33 {:controller => 'versions', :action => 'create', :project_id => @issue.project},
33 :tabindex => 200) if User.current.allowed_to?(:manage_versions, @issue.project) %>
34 :title => l(:label_version_new),
35 :tabindex => 200) if User.current.allowed_to?(:manage_versions, @issue.project) %>
36 </p>
34 </p>
37 <% end %>
35 <% end %>
38 </div>
36 </div>
39
37
40 <div class="splitcontentright">
38 <div class="splitcontentright">
41 <% if @issue.safe_attribute? 'parent_issue_id' %>
39 <% if @issue.safe_attribute? 'parent_issue_id' %>
42 <p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10 %></p>
40 <p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10 %></p>
43 <div id="parent_issue_candidates" class="autocomplete"></div>
41 <div id="parent_issue_candidates" class="autocomplete"></div>
44 <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @issue.project) }')" %>
42 <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @issue.project) }')" %>
45 <% end %>
43 <% end %>
46
44
47 <% if @issue.safe_attribute? 'start_date' %>
45 <% if @issue.safe_attribute? 'start_date' %>
48 <p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
46 <p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
49 <% end %>
47 <% end %>
50
48
51 <% if @issue.safe_attribute? 'due_date' %>
49 <% if @issue.safe_attribute? 'due_date' %>
52 <p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
50 <p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
53 <% end %>
51 <% end %>
54
52
55 <% if @issue.safe_attribute? 'estimated_hours' %>
53 <% if @issue.safe_attribute? 'estimated_hours' %>
56 <p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p>
54 <p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p>
57 <% end %>
55 <% end %>
58
56
59 <% if @issue.safe_attribute?('done_ratio') && @issue.leaf? && Issue.use_field_for_done_ratio? %>
57 <% if @issue.safe_attribute?('done_ratio') && @issue.leaf? && Issue.use_field_for_done_ratio? %>
60 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
58 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
61 <% end %>
59 <% end %>
62 </div>
60 </div>
63
61
64 <div style="clear:both;"> </div>
62 <div style="clear:both;"> </div>
65 <% if @issue.safe_attribute? 'custom_field_values' %>
63 <% if @issue.safe_attribute? 'custom_field_values' %>
66 <%= render :partial => 'issues/form_custom_fields' %>
64 <%= render :partial => 'issues/form_custom_fields' %>
67 <% end %>
65 <% end %>
68
66
69 <% end %>
67 <% end %>
@@ -1,517 +1,522
1 /* Redmine - project management software
1 /* Redmine - project management software
2 Copyright (C) 2006-2012 Jean-Philippe Lang */
2 Copyright (C) 2006-2012 Jean-Philippe Lang */
3
3
4 function checkAll (id, checked) {
4 function checkAll (id, checked) {
5 var els = Element.descendants(id);
5 var els = Element.descendants(id);
6 for (var i = 0; i < els.length; i++) {
6 for (var i = 0; i < els.length; i++) {
7 if (els[i].disabled==false) {
7 if (els[i].disabled==false) {
8 els[i].checked = checked;
8 els[i].checked = checked;
9 }
9 }
10 }
10 }
11 }
11 }
12
12
13 function toggleCheckboxesBySelector(selector) {
13 function toggleCheckboxesBySelector(selector) {
14 boxes = $$(selector);
14 boxes = $$(selector);
15 var all_checked = true;
15 var all_checked = true;
16 for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } }
16 for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } }
17 for (i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; }
17 for (i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; }
18 }
18 }
19
19
20 function setCheckboxesBySelector(checked, selector) {
20 function setCheckboxesBySelector(checked, selector) {
21 var boxes = $$(selector);
21 var boxes = $$(selector);
22 boxes.each(function(ele) {
22 boxes.each(function(ele) {
23 ele.checked = checked;
23 ele.checked = checked;
24 });
24 });
25 }
25 }
26
26
27 function showAndScrollTo(id, focus) {
27 function showAndScrollTo(id, focus) {
28 Element.show(id);
28 Element.show(id);
29 if (focus!=null) { Form.Element.focus(focus); }
29 if (focus!=null) { Form.Element.focus(focus); }
30 Element.scrollTo(id);
30 Element.scrollTo(id);
31 }
31 }
32
32
33 function toggleRowGroup(el) {
33 function toggleRowGroup(el) {
34 var tr = Element.up(el, 'tr');
34 var tr = Element.up(el, 'tr');
35 var n = Element.next(tr);
35 var n = Element.next(tr);
36 tr.toggleClassName('open');
36 tr.toggleClassName('open');
37 while (n != undefined && !n.hasClassName('group')) {
37 while (n != undefined && !n.hasClassName('group')) {
38 Element.toggle(n);
38 Element.toggle(n);
39 n = Element.next(n);
39 n = Element.next(n);
40 }
40 }
41 }
41 }
42
42
43 function collapseAllRowGroups(el) {
43 function collapseAllRowGroups(el) {
44 var tbody = Element.up(el, 'tbody');
44 var tbody = Element.up(el, 'tbody');
45 tbody.childElements('tr').each(function(tr) {
45 tbody.childElements('tr').each(function(tr) {
46 if (tr.hasClassName('group')) {
46 if (tr.hasClassName('group')) {
47 tr.removeClassName('open');
47 tr.removeClassName('open');
48 } else {
48 } else {
49 tr.hide();
49 tr.hide();
50 }
50 }
51 })
51 })
52 }
52 }
53
53
54 function expandAllRowGroups(el) {
54 function expandAllRowGroups(el) {
55 var tbody = Element.up(el, 'tbody');
55 var tbody = Element.up(el, 'tbody');
56 tbody.childElements('tr').each(function(tr) {
56 tbody.childElements('tr').each(function(tr) {
57 if (tr.hasClassName('group')) {
57 if (tr.hasClassName('group')) {
58 tr.addClassName('open');
58 tr.addClassName('open');
59 } else {
59 } else {
60 tr.show();
60 tr.show();
61 }
61 }
62 })
62 })
63 }
63 }
64
64
65 function toggleAllRowGroups(el) {
65 function toggleAllRowGroups(el) {
66 var tr = Element.up(el, 'tr');
66 var tr = Element.up(el, 'tr');
67 if (tr.hasClassName('open')) {
67 if (tr.hasClassName('open')) {
68 collapseAllRowGroups(el);
68 collapseAllRowGroups(el);
69 } else {
69 } else {
70 expandAllRowGroups(el);
70 expandAllRowGroups(el);
71 }
71 }
72 }
72 }
73
73
74 function toggleFieldset(el) {
74 function toggleFieldset(el) {
75 var fieldset = Element.up(el, 'fieldset');
75 var fieldset = Element.up(el, 'fieldset');
76 fieldset.toggleClassName('collapsed');
76 fieldset.toggleClassName('collapsed');
77 Effect.toggle(fieldset.down('div'), 'slide', {duration:0.2});
77 Effect.toggle(fieldset.down('div'), 'slide', {duration:0.2});
78 }
78 }
79
79
80 function hideFieldset(el) {
80 function hideFieldset(el) {
81 var fieldset = Element.up(el, 'fieldset');
81 var fieldset = Element.up(el, 'fieldset');
82 fieldset.toggleClassName('collapsed');
82 fieldset.toggleClassName('collapsed');
83 fieldset.down('div').hide();
83 fieldset.down('div').hide();
84 }
84 }
85
85
86 function add_filter() {
86 function add_filter() {
87 select = $('add_filter_select');
87 select = $('add_filter_select');
88 field = select.value
88 field = select.value
89 Element.show('tr_' + field);
89 Element.show('tr_' + field);
90 check_box = $('cb_' + field);
90 check_box = $('cb_' + field);
91 check_box.checked = true;
91 check_box.checked = true;
92 toggle_filter(field);
92 toggle_filter(field);
93 select.selectedIndex = 0;
93 select.selectedIndex = 0;
94
94
95 for (i=0; i<select.options.length; i++) {
95 for (i=0; i<select.options.length; i++) {
96 if (select.options[i].value == field) {
96 if (select.options[i].value == field) {
97 select.options[i].disabled = true;
97 select.options[i].disabled = true;
98 }
98 }
99 }
99 }
100 }
100 }
101
101
102 function toggle_filter(field) {
102 function toggle_filter(field) {
103 check_box = $('cb_' + field);
103 check_box = $('cb_' + field);
104 if (check_box.checked) {
104 if (check_box.checked) {
105 Element.show("operators_" + field);
105 Element.show("operators_" + field);
106 Form.Element.enable("operators_" + field);
106 Form.Element.enable("operators_" + field);
107 toggle_operator(field);
107 toggle_operator(field);
108 } else {
108 } else {
109 Element.hide("operators_" + field);
109 Element.hide("operators_" + field);
110 Form.Element.disable("operators_" + field);
110 Form.Element.disable("operators_" + field);
111 enableValues(field, []);
111 enableValues(field, []);
112 }
112 }
113 }
113 }
114
114
115 function enableValues(field, indexes) {
115 function enableValues(field, indexes) {
116 var f = $$(".values_" + field);
116 var f = $$(".values_" + field);
117 for(var i=0;i<f.length;i++) {
117 for(var i=0;i<f.length;i++) {
118 if (indexes.include(i)) {
118 if (indexes.include(i)) {
119 Form.Element.enable(f[i]);
119 Form.Element.enable(f[i]);
120 f[i].up('span').show();
120 f[i].up('span').show();
121 } else {
121 } else {
122 f[i].value = '';
122 f[i].value = '';
123 Form.Element.disable(f[i]);
123 Form.Element.disable(f[i]);
124 f[i].up('span').hide();
124 f[i].up('span').hide();
125 }
125 }
126 }
126 }
127 if (indexes.length > 0) {
127 if (indexes.length > 0) {
128 Element.show("div_values_" + field);
128 Element.show("div_values_" + field);
129 } else {
129 } else {
130 Element.hide("div_values_" + field);
130 Element.hide("div_values_" + field);
131 }
131 }
132 }
132 }
133
133
134 function toggle_operator(field) {
134 function toggle_operator(field) {
135 operator = $("operators_" + field);
135 operator = $("operators_" + field);
136 switch (operator.value) {
136 switch (operator.value) {
137 case "!*":
137 case "!*":
138 case "*":
138 case "*":
139 case "t":
139 case "t":
140 case "w":
140 case "w":
141 case "o":
141 case "o":
142 case "c":
142 case "c":
143 enableValues(field, []);
143 enableValues(field, []);
144 break;
144 break;
145 case "><":
145 case "><":
146 enableValues(field, [0,1]);
146 enableValues(field, [0,1]);
147 break;
147 break;
148 case "<t+":
148 case "<t+":
149 case ">t+":
149 case ">t+":
150 case "t+":
150 case "t+":
151 case ">t-":
151 case ">t-":
152 case "<t-":
152 case "<t-":
153 case "t-":
153 case "t-":
154 enableValues(field, [2]);
154 enableValues(field, [2]);
155 break;
155 break;
156 default:
156 default:
157 enableValues(field, [0]);
157 enableValues(field, [0]);
158 break;
158 break;
159 }
159 }
160 }
160 }
161
161
162 function toggle_multi_select(el) {
162 function toggle_multi_select(el) {
163 var select = $(el);
163 var select = $(el);
164 if (select.multiple == true) {
164 if (select.multiple == true) {
165 select.multiple = false;
165 select.multiple = false;
166 } else {
166 } else {
167 select.multiple = true;
167 select.multiple = true;
168 }
168 }
169 }
169 }
170
170
171 function submit_query_form(id) {
171 function submit_query_form(id) {
172 selectAllOptions("selected_columns");
172 selectAllOptions("selected_columns");
173 $(id).submit();
173 $(id).submit();
174 }
174 }
175
175
176 function apply_filters_observer() {
176 function apply_filters_observer() {
177 $$("#query_form input[type=text]").invoke("observe", "keypress", function(e){
177 $$("#query_form input[type=text]").invoke("observe", "keypress", function(e){
178 if(e.keyCode == Event.KEY_RETURN) {
178 if(e.keyCode == Event.KEY_RETURN) {
179 submit_query_form("query_form");
179 submit_query_form("query_form");
180 }
180 }
181 });
181 });
182 }
182 }
183
183
184 var fileFieldCount = 1;
184 var fileFieldCount = 1;
185
185
186 function addFileField() {
186 function addFileField() {
187 var fields = $('attachments_fields');
187 var fields = $('attachments_fields');
188 if (fields.childElements().length >= 10) return false;
188 if (fields.childElements().length >= 10) return false;
189 fileFieldCount++;
189 fileFieldCount++;
190 var s = new Element('span');
190 var s = new Element('span');
191 s.update(fields.down('span').innerHTML);
191 s.update(fields.down('span').innerHTML);
192 s.down('input.file').name = "attachments[" + fileFieldCount + "][file]";
192 s.down('input.file').name = "attachments[" + fileFieldCount + "][file]";
193 s.down('input.description').name = "attachments[" + fileFieldCount + "][description]";
193 s.down('input.description').name = "attachments[" + fileFieldCount + "][description]";
194 fields.appendChild(s);
194 fields.appendChild(s);
195 }
195 }
196
196
197 function removeFileField(el) {
197 function removeFileField(el) {
198 var fields = $('attachments_fields');
198 var fields = $('attachments_fields');
199 var s = Element.up(el, 'span');
199 var s = Element.up(el, 'span');
200 if (fields.childElements().length > 1) {
200 if (fields.childElements().length > 1) {
201 s.remove();
201 s.remove();
202 } else {
202 } else {
203 s.update(s.innerHTML);
203 s.update(s.innerHTML);
204 }
204 }
205 }
205 }
206
206
207 function checkFileSize(el, maxSize, message) {
207 function checkFileSize(el, maxSize, message) {
208 var files = el.files;
208 var files = el.files;
209 if (files) {
209 if (files) {
210 for (var i=0; i<files.length; i++) {
210 for (var i=0; i<files.length; i++) {
211 if (files[i].size > maxSize) {
211 if (files[i].size > maxSize) {
212 alert(message);
212 alert(message);
213 el.value = "";
213 el.value = "";
214 }
214 }
215 }
215 }
216 }
216 }
217 }
217 }
218
218
219 function showTab(name) {
219 function showTab(name) {
220 var f = $$('div#content .tab-content');
220 var f = $$('div#content .tab-content');
221 for(var i=0; i<f.length; i++){
221 for(var i=0; i<f.length; i++){
222 Element.hide(f[i]);
222 Element.hide(f[i]);
223 }
223 }
224 var f = $$('div.tabs a');
224 var f = $$('div.tabs a');
225 for(var i=0; i<f.length; i++){
225 for(var i=0; i<f.length; i++){
226 Element.removeClassName(f[i], "selected");
226 Element.removeClassName(f[i], "selected");
227 }
227 }
228 Element.show('tab-content-' + name);
228 Element.show('tab-content-' + name);
229 Element.addClassName('tab-' + name, "selected");
229 Element.addClassName('tab-' + name, "selected");
230 return false;
230 return false;
231 }
231 }
232
232
233 function moveTabRight(el) {
233 function moveTabRight(el) {
234 var lis = Element.up(el, 'div.tabs').down('ul').childElements();
234 var lis = Element.up(el, 'div.tabs').down('ul').childElements();
235 var tabsWidth = 0;
235 var tabsWidth = 0;
236 var i;
236 var i;
237 for (i=0; i<lis.length; i++) {
237 for (i=0; i<lis.length; i++) {
238 if (lis[i].visible()) {
238 if (lis[i].visible()) {
239 tabsWidth += lis[i].getWidth() + 6;
239 tabsWidth += lis[i].getWidth() + 6;
240 }
240 }
241 }
241 }
242 if (tabsWidth < Element.up(el, 'div.tabs').getWidth() - 60) {
242 if (tabsWidth < Element.up(el, 'div.tabs').getWidth() - 60) {
243 return;
243 return;
244 }
244 }
245 i=0;
245 i=0;
246 while (i<lis.length && !lis[i].visible()) {
246 while (i<lis.length && !lis[i].visible()) {
247 i++;
247 i++;
248 }
248 }
249 lis[i].hide();
249 lis[i].hide();
250 }
250 }
251
251
252 function moveTabLeft(el) {
252 function moveTabLeft(el) {
253 var lis = Element.up(el, 'div.tabs').down('ul').childElements();
253 var lis = Element.up(el, 'div.tabs').down('ul').childElements();
254 var i = 0;
254 var i = 0;
255 while (i<lis.length && !lis[i].visible()) {
255 while (i<lis.length && !lis[i].visible()) {
256 i++;
256 i++;
257 }
257 }
258 if (i>0) {
258 if (i>0) {
259 lis[i-1].show();
259 lis[i-1].show();
260 }
260 }
261 }
261 }
262
262
263 function displayTabsButtons() {
263 function displayTabsButtons() {
264 var lis;
264 var lis;
265 var tabsWidth = 0;
265 var tabsWidth = 0;
266 var i;
266 var i;
267 $$('div.tabs').each(function(el) {
267 $$('div.tabs').each(function(el) {
268 lis = el.down('ul').childElements();
268 lis = el.down('ul').childElements();
269 for (i=0; i<lis.length; i++) {
269 for (i=0; i<lis.length; i++) {
270 if (lis[i].visible()) {
270 if (lis[i].visible()) {
271 tabsWidth += lis[i].getWidth() + 6;
271 tabsWidth += lis[i].getWidth() + 6;
272 }
272 }
273 }
273 }
274 if ((tabsWidth < el.getWidth() - 60) && (lis[0].visible())) {
274 if ((tabsWidth < el.getWidth() - 60) && (lis[0].visible())) {
275 el.down('div.tabs-buttons').hide();
275 el.down('div.tabs-buttons').hide();
276 } else {
276 } else {
277 el.down('div.tabs-buttons').show();
277 el.down('div.tabs-buttons').show();
278 }
278 }
279 });
279 });
280 }
280 }
281
281
282 function setPredecessorFieldsVisibility() {
282 function setPredecessorFieldsVisibility() {
283 relationType = $('relation_relation_type');
283 relationType = $('relation_relation_type');
284 if (relationType && (relationType.value == "precedes" || relationType.value == "follows")) {
284 if (relationType && (relationType.value == "precedes" || relationType.value == "follows")) {
285 Element.show('predecessor_fields');
285 Element.show('predecessor_fields');
286 } else {
286 } else {
287 Element.hide('predecessor_fields');
287 Element.hide('predecessor_fields');
288 }
288 }
289 }
289 }
290
290
291 function promptToRemote(text, param, url) {
291 function promptToRemote(text, param, url) {
292 value = prompt(text + ':');
292 value = prompt(text + ':');
293 if (value) {
293 if (value) {
294 new Ajax.Request(url + '?' + param + '=' + encodeURIComponent(value), {asynchronous:true, evalScripts:true});
294 new Ajax.Request(url + '?' + param + '=' + encodeURIComponent(value), {asynchronous:true, evalScripts:true});
295 return false;
295 return false;
296 }
296 }
297 }
297 }
298
298
299 function showModal(id, width) {
299 function showModal(id, width) {
300 el = $(id);
300 el = $(id);
301 if (el == undefined || el.visible()) {return;}
301 if (el == undefined || el.visible()) {return;}
302 var h = $$('body')[0].getHeight();
302 var h = $$('body')[0].getHeight();
303 var d = document.createElement("div");
303 var d = document.createElement("div");
304 d.id = 'modalbg';
304 d.id = 'modalbg';
305 $('main').appendChild(d);
305 $('main').appendChild(d);
306 $('modalbg').setStyle({ width: '100%', height: h + 'px' });
306 $('modalbg').setStyle({ width: '100%', height: h + 'px' });
307 $('modalbg').show();
307 $('modalbg').show();
308
308
309 var pageWidth = document.viewport.getWidth();
309 var pageWidth = document.viewport.getWidth();
310 if (width) {
310 if (width) {
311 el.setStyle({'width': width});
311 el.setStyle({'width': width});
312 }
312 }
313 el.setStyle({'left': (((pageWidth - el.getWidth())/2 *100) / pageWidth) + '%'});
313 el.setStyle({'left': (((pageWidth - el.getWidth())/2 *100) / pageWidth) + '%'});
314 el.addClassName('modal');
314 el.addClassName('modal');
315 el.show();
315 el.show();
316
316
317 var submit = el.down("input[type=submit]");
317 var submit = el.down("input[type=submit]");
318 if (submit) {
318 if (submit) {
319 submit.focus();
319 submit.focus();
320 }
320 }
321 }
321 }
322
322
323 function hideModal(el) {
323 function hideModal(el) {
324 var modal = Element.up(el, 'div.modal');
324 var modal;
325 if (el) {
326 modal = Element.up(el, 'div.modal');
327 } else {
328 modal = $('ajax-modal');
329 }
325 if (modal) {
330 if (modal) {
326 modal.hide();
331 modal.hide();
327 }
332 }
328 var bg = $('modalbg');
333 var bg = $('modalbg');
329 if (bg) {
334 if (bg) {
330 bg.remove();
335 bg.remove();
331 }
336 }
332 }
337 }
333
338
334 function collapseScmEntry(id) {
339 function collapseScmEntry(id) {
335 var els = document.getElementsByClassName(id, 'browser');
340 var els = document.getElementsByClassName(id, 'browser');
336 for (var i = 0; i < els.length; i++) {
341 for (var i = 0; i < els.length; i++) {
337 if (els[i].hasClassName('open')) {
342 if (els[i].hasClassName('open')) {
338 collapseScmEntry(els[i].id);
343 collapseScmEntry(els[i].id);
339 }
344 }
340 Element.hide(els[i]);
345 Element.hide(els[i]);
341 }
346 }
342 $(id).removeClassName('open');
347 $(id).removeClassName('open');
343 }
348 }
344
349
345 function expandScmEntry(id) {
350 function expandScmEntry(id) {
346 var els = document.getElementsByClassName(id, 'browser');
351 var els = document.getElementsByClassName(id, 'browser');
347 for (var i = 0; i < els.length; i++) {
352 for (var i = 0; i < els.length; i++) {
348 Element.show(els[i]);
353 Element.show(els[i]);
349 if (els[i].hasClassName('loaded') && !els[i].hasClassName('collapsed')) {
354 if (els[i].hasClassName('loaded') && !els[i].hasClassName('collapsed')) {
350 expandScmEntry(els[i].id);
355 expandScmEntry(els[i].id);
351 }
356 }
352 }
357 }
353 $(id).addClassName('open');
358 $(id).addClassName('open');
354 }
359 }
355
360
356 function scmEntryClick(id) {
361 function scmEntryClick(id) {
357 el = $(id);
362 el = $(id);
358 if (el.hasClassName('open')) {
363 if (el.hasClassName('open')) {
359 collapseScmEntry(id);
364 collapseScmEntry(id);
360 el.addClassName('collapsed');
365 el.addClassName('collapsed');
361 return false;
366 return false;
362 } else if (el.hasClassName('loaded')) {
367 } else if (el.hasClassName('loaded')) {
363 expandScmEntry(id);
368 expandScmEntry(id);
364 el.removeClassName('collapsed');
369 el.removeClassName('collapsed');
365 return false;
370 return false;
366 }
371 }
367 if (el.hasClassName('loading')) {
372 if (el.hasClassName('loading')) {
368 return false;
373 return false;
369 }
374 }
370 el.addClassName('loading');
375 el.addClassName('loading');
371 return true;
376 return true;
372 }
377 }
373
378
374 function scmEntryLoaded(id) {
379 function scmEntryLoaded(id) {
375 Element.addClassName(id, 'open');
380 Element.addClassName(id, 'open');
376 Element.addClassName(id, 'loaded');
381 Element.addClassName(id, 'loaded');
377 Element.removeClassName(id, 'loading');
382 Element.removeClassName(id, 'loading');
378 }
383 }
379
384
380 function randomKey(size) {
385 function randomKey(size) {
381 var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
386 var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
382 var key = '';
387 var key = '';
383 for (i = 0; i < size; i++) {
388 for (i = 0; i < size; i++) {
384 key += chars[Math.floor(Math.random() * chars.length)];
389 key += chars[Math.floor(Math.random() * chars.length)];
385 }
390 }
386 return key;
391 return key;
387 }
392 }
388
393
389 function observeParentIssueField(url) {
394 function observeParentIssueField(url) {
390 new Ajax.Autocompleter('issue_parent_issue_id',
395 new Ajax.Autocompleter('issue_parent_issue_id',
391 'parent_issue_candidates',
396 'parent_issue_candidates',
392 url,
397 url,
393 { minChars: 3,
398 { minChars: 3,
394 frequency: 0.5,
399 frequency: 0.5,
395 paramName: 'q',
400 paramName: 'q',
396 method: 'get',
401 method: 'get',
397 updateElement: function(value) {
402 updateElement: function(value) {
398 document.getElementById('issue_parent_issue_id').value = value.id;
403 document.getElementById('issue_parent_issue_id').value = value.id;
399 }});
404 }});
400 }
405 }
401
406
402 function observeRelatedIssueField(url) {
407 function observeRelatedIssueField(url) {
403 new Ajax.Autocompleter('relation_issue_to_id',
408 new Ajax.Autocompleter('relation_issue_to_id',
404 'related_issue_candidates',
409 'related_issue_candidates',
405 url,
410 url,
406 { minChars: 3,
411 { minChars: 3,
407 frequency: 0.5,
412 frequency: 0.5,
408 paramName: 'q',
413 paramName: 'q',
409 method: 'get',
414 method: 'get',
410 updateElement: function(value) {
415 updateElement: function(value) {
411 document.getElementById('relation_issue_to_id').value = value.id;
416 document.getElementById('relation_issue_to_id').value = value.id;
412 },
417 },
413 parameters: 'scope=all'
418 parameters: 'scope=all'
414 });
419 });
415 }
420 }
416
421
417 function setVisible(id, visible) {
422 function setVisible(id, visible) {
418 var el = $(id);
423 var el = $(id);
419 if (el) {if (visible) {el.show();} else {el.hide();}}
424 if (el) {if (visible) {el.show();} else {el.hide();}}
420 }
425 }
421
426
422 function observeProjectModules() {
427 function observeProjectModules() {
423 var f = function() {
428 var f = function() {
424 /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */
429 /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */
425 var c = ($('project_enabled_module_names_issue_tracking').checked == true);
430 var c = ($('project_enabled_module_names_issue_tracking').checked == true);
426 setVisible('project_trackers', c);
431 setVisible('project_trackers', c);
427 setVisible('project_issue_custom_fields', c);
432 setVisible('project_issue_custom_fields', c);
428 };
433 };
429
434
430 Event.observe(window, 'load', f);
435 Event.observe(window, 'load', f);
431 Event.observe('project_enabled_module_names_issue_tracking', 'change', f);
436 Event.observe('project_enabled_module_names_issue_tracking', 'change', f);
432 }
437 }
433
438
434 /*
439 /*
435 * Class used to warn user when leaving a page with unsaved textarea
440 * Class used to warn user when leaving a page with unsaved textarea
436 * Author: mathias.fischer@berlinonline.de
441 * Author: mathias.fischer@berlinonline.de
437 */
442 */
438
443
439 var WarnLeavingUnsaved = Class.create({
444 var WarnLeavingUnsaved = Class.create({
440 observedForms: false,
445 observedForms: false,
441 observedElements: false,
446 observedElements: false,
442 changedForms: false,
447 changedForms: false,
443 message: null,
448 message: null,
444
449
445 initialize: function(message){
450 initialize: function(message){
446 this.observedForms = $$('form');
451 this.observedForms = $$('form');
447 this.observedElements = $$('textarea');
452 this.observedElements = $$('textarea');
448 this.message = message;
453 this.message = message;
449
454
450 this.observedElements.each(this.observeChange.bind(this));
455 this.observedElements.each(this.observeChange.bind(this));
451 this.observedForms.each(this.submitAction.bind(this));
456 this.observedForms.each(this.submitAction.bind(this));
452
457
453 window.onbeforeunload = this.unload.bind(this);
458 window.onbeforeunload = this.unload.bind(this);
454 },
459 },
455
460
456 unload: function(){
461 unload: function(){
457 this.observedElements.each(function(el) {el.blur();})
462 this.observedElements.each(function(el) {el.blur();})
458 if(this.changedForms)
463 if(this.changedForms)
459 return this.message;
464 return this.message;
460 },
465 },
461
466
462 setChanged: function(){
467 setChanged: function(){
463 this.changedForms = true;
468 this.changedForms = true;
464 },
469 },
465
470
466 setUnchanged: function(){
471 setUnchanged: function(){
467 this.changedForms = false;
472 this.changedForms = false;
468 },
473 },
469
474
470 observeChange: function(element){
475 observeChange: function(element){
471 element.observe('change',this.setChanged.bindAsEventListener(this));
476 element.observe('change',this.setChanged.bindAsEventListener(this));
472 },
477 },
473
478
474 submitAction: function(element){
479 submitAction: function(element){
475 element.observe('submit',this.setUnchanged.bindAsEventListener(this));
480 element.observe('submit',this.setUnchanged.bindAsEventListener(this));
476 }
481 }
477 });
482 });
478
483
479 /*
484 /*
480 * 1 - registers a callback which copies the csrf token into the
485 * 1 - registers a callback which copies the csrf token into the
481 * X-CSRF-Token header with each ajax request. Necessary to
486 * X-CSRF-Token header with each ajax request. Necessary to
482 * work with rails applications which have fixed
487 * work with rails applications which have fixed
483 * CVE-2011-0447
488 * CVE-2011-0447
484 * 2 - shows and hides ajax indicator
489 * 2 - shows and hides ajax indicator
485 */
490 */
486 Ajax.Responders.register({
491 Ajax.Responders.register({
487 onCreate: function(request){
492 onCreate: function(request){
488 var csrf_meta_tag = $$('meta[name=csrf-token]')[0];
493 var csrf_meta_tag = $$('meta[name=csrf-token]')[0];
489
494
490 if (csrf_meta_tag) {
495 if (csrf_meta_tag) {
491 var header = 'X-CSRF-Token',
496 var header = 'X-CSRF-Token',
492 token = csrf_meta_tag.readAttribute('content');
497 token = csrf_meta_tag.readAttribute('content');
493
498
494 if (!request.options.requestHeaders) {
499 if (!request.options.requestHeaders) {
495 request.options.requestHeaders = {};
500 request.options.requestHeaders = {};
496 }
501 }
497 request.options.requestHeaders[header] = token;
502 request.options.requestHeaders[header] = token;
498 }
503 }
499
504
500 if ($('ajax-indicator') && Ajax.activeRequestCount > 0) {
505 if ($('ajax-indicator') && Ajax.activeRequestCount > 0) {
501 Element.show('ajax-indicator');
506 Element.show('ajax-indicator');
502 }
507 }
503 },
508 },
504 onComplete: function(){
509 onComplete: function(){
505 if ($('ajax-indicator') && Ajax.activeRequestCount == 0) {
510 if ($('ajax-indicator') && Ajax.activeRequestCount == 0) {
506 Element.hide('ajax-indicator');
511 Element.hide('ajax-indicator');
507 }
512 }
508 }
513 }
509 });
514 });
510
515
511 function hideOnLoad() {
516 function hideOnLoad() {
512 $$('.hol').each(function(el) {
517 $$('.hol').each(function(el) {
513 el.hide();
518 el.hide();
514 });
519 });
515 }
520 }
516
521
517 Event.observe(window, 'load', hideOnLoad);
522 Event.observe(window, 'load', hideOnLoad);
@@ -1,187 +1,211
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'versions_controller'
19 require 'versions_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class VersionsController; def rescue_action(e) raise e end; end
22 class VersionsController; def rescue_action(e) raise e end; end
23
23
24 class VersionsControllerTest < ActionController::TestCase
24 class VersionsControllerTest < ActionController::TestCase
25 fixtures :projects, :versions, :issues, :users, :roles, :members, :member_roles, :enabled_modules, :issue_statuses
25 fixtures :projects, :versions, :issues, :users, :roles, :members, :member_roles, :enabled_modules, :issue_statuses
26
26
27 def setup
27 def setup
28 @controller = VersionsController.new
28 @controller = VersionsController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 User.current = nil
31 User.current = nil
32 end
32 end
33
33
34 def test_index
34 def test_index
35 get :index, :project_id => 1
35 get :index, :project_id => 1
36 assert_response :success
36 assert_response :success
37 assert_template 'index'
37 assert_template 'index'
38 assert_not_nil assigns(:versions)
38 assert_not_nil assigns(:versions)
39 # Version with no date set appears
39 # Version with no date set appears
40 assert assigns(:versions).include?(Version.find(3))
40 assert assigns(:versions).include?(Version.find(3))
41 # Completed version doesn't appear
41 # Completed version doesn't appear
42 assert !assigns(:versions).include?(Version.find(1))
42 assert !assigns(:versions).include?(Version.find(1))
43 # Context menu on issues
43 # Context menu on issues
44 assert_select "script", :text => Regexp.new(Regexp.escape("new ContextMenu('/issues/context_menu')"))
44 assert_select "script", :text => Regexp.new(Regexp.escape("new ContextMenu('/issues/context_menu')"))
45 # Links to versions anchors
45 # Links to versions anchors
46 assert_tag 'a', :attributes => {:href => '#2.0'},
46 assert_tag 'a', :attributes => {:href => '#2.0'},
47 :ancestor => {:tag => 'div', :attributes => {:id => 'sidebar'}}
47 :ancestor => {:tag => 'div', :attributes => {:id => 'sidebar'}}
48 # Links to completed versions in the sidebar
48 # Links to completed versions in the sidebar
49 assert_tag 'a', :attributes => {:href => '/versions/1'},
49 assert_tag 'a', :attributes => {:href => '/versions/1'},
50 :ancestor => {:tag => 'div', :attributes => {:id => 'sidebar'}}
50 :ancestor => {:tag => 'div', :attributes => {:id => 'sidebar'}}
51 end
51 end
52
52
53 def test_index_with_completed_versions
53 def test_index_with_completed_versions
54 get :index, :project_id => 1, :completed => 1
54 get :index, :project_id => 1, :completed => 1
55 assert_response :success
55 assert_response :success
56 assert_template 'index'
56 assert_template 'index'
57 assert_not_nil assigns(:versions)
57 assert_not_nil assigns(:versions)
58 # Version with no date set appears
58 # Version with no date set appears
59 assert assigns(:versions).include?(Version.find(3))
59 assert assigns(:versions).include?(Version.find(3))
60 # Completed version appears
60 # Completed version appears
61 assert assigns(:versions).include?(Version.find(1))
61 assert assigns(:versions).include?(Version.find(1))
62 end
62 end
63
63
64 def test_index_with_tracker_ids
64 def test_index_with_tracker_ids
65 get :index, :project_id => 1, :tracker_ids => [1, 3]
65 get :index, :project_id => 1, :tracker_ids => [1, 3]
66 assert_response :success
66 assert_response :success
67 assert_template 'index'
67 assert_template 'index'
68 assert_not_nil assigns(:issues_by_version)
68 assert_not_nil assigns(:issues_by_version)
69 assert_nil assigns(:issues_by_version).values.flatten.detect {|issue| issue.tracker_id == 2}
69 assert_nil assigns(:issues_by_version).values.flatten.detect {|issue| issue.tracker_id == 2}
70 end
70 end
71
71
72 def test_index_showing_subprojects_versions
72 def test_index_showing_subprojects_versions
73 @subproject_version = Version.generate!(:project => Project.find(3))
73 @subproject_version = Version.generate!(:project => Project.find(3))
74 get :index, :project_id => 1, :with_subprojects => 1
74 get :index, :project_id => 1, :with_subprojects => 1
75 assert_response :success
75 assert_response :success
76 assert_template 'index'
76 assert_template 'index'
77 assert_not_nil assigns(:versions)
77 assert_not_nil assigns(:versions)
78
78
79 assert assigns(:versions).include?(Version.find(4)), "Shared version not found"
79 assert assigns(:versions).include?(Version.find(4)), "Shared version not found"
80 assert assigns(:versions).include?(@subproject_version), "Subproject version not found"
80 assert assigns(:versions).include?(@subproject_version), "Subproject version not found"
81 end
81 end
82
82
83 def test_show
83 def test_show
84 get :show, :id => 2
84 get :show, :id => 2
85 assert_response :success
85 assert_response :success
86 assert_template 'show'
86 assert_template 'show'
87 assert_not_nil assigns(:version)
87 assert_not_nil assigns(:version)
88
88
89 assert_tag :tag => 'h2', :content => /1.0/
89 assert_tag :tag => 'h2', :content => /1.0/
90 end
90 end
91
91
92 def test_new
92 def test_new
93 @request.session[:user_id] = 2
93 @request.session[:user_id] = 2
94 get :new, :project_id => '1'
94 get :new, :project_id => '1'
95 assert_response :success
95 assert_response :success
96 assert_template 'new'
96 assert_template 'new'
97 end
97 end
98
98
99 def test_new_from_issue_form
100 @request.session[:user_id] = 2
101 xhr :get, :new, :project_id => '1'
102 assert_response :success
103 assert_select_rjs :replace_html, "ajax-modal" do
104 assert_select "form[action=/projects/ecookbook/versions]"
105 assert_select "input#version_name"
106 end
107 end
108
99 def test_create
109 def test_create
100 @request.session[:user_id] = 2 # manager
110 @request.session[:user_id] = 2 # manager
101 assert_difference 'Version.count' do
111 assert_difference 'Version.count' do
102 post :create, :project_id => '1', :version => {:name => 'test_add_version'}
112 post :create, :project_id => '1', :version => {:name => 'test_add_version'}
103 end
113 end
104 assert_redirected_to '/projects/ecookbook/settings/versions'
114 assert_redirected_to '/projects/ecookbook/settings/versions'
105 version = Version.find_by_name('test_add_version')
115 version = Version.find_by_name('test_add_version')
106 assert_not_nil version
116 assert_not_nil version
107 assert_equal 1, version.project_id
117 assert_equal 1, version.project_id
108 end
118 end
109
119
110 def test_create_from_issue_form
120 def test_create_from_issue_form
111 @request.session[:user_id] = 2 # manager
121 @request.session[:user_id] = 2
112 assert_difference 'Version.count' do
122 assert_difference 'Version.count' do
113 xhr :post, :create, :project_id => '1', :version => {:name => 'test_add_version_from_issue_form'}
123 xhr :post, :create, :project_id => '1', :version => {:name => 'test_add_version_from_issue_form'}
114 end
124 end
115 assert_response :success
116 assert_select_rjs :replace, 'issue_fixed_version_id'
117 version = Version.find_by_name('test_add_version_from_issue_form')
125 version = Version.find_by_name('test_add_version_from_issue_form')
118 assert_not_nil version
126 assert_not_nil version
119 assert_equal 1, version.project_id
127 assert_equal 1, version.project_id
128
129 assert_response :success
130 assert_select_rjs :replace, 'issue_fixed_version_id' do
131 assert_select "option[value=#{version.id}][selected=selected]"
132 end
133 end
134
135 def test_create_from_issue_form_with_failure
136 @request.session[:user_id] = 2
137 assert_no_difference 'Version.count' do
138 xhr :post, :create, :project_id => '1', :version => {:name => ''}
139 end
140 assert_response :success
141 assert_select_rjs :replace_html, "ajax-modal" do
142 assert_select "div#errorExplanation"
143 end
120 end
144 end
121
145
122 def test_get_edit
146 def test_get_edit
123 @request.session[:user_id] = 2
147 @request.session[:user_id] = 2
124 get :edit, :id => 2
148 get :edit, :id => 2
125 assert_response :success
149 assert_response :success
126 assert_template 'edit'
150 assert_template 'edit'
127 end
151 end
128
152
129 def test_close_completed
153 def test_close_completed
130 Version.update_all("status = 'open'")
154 Version.update_all("status = 'open'")
131 @request.session[:user_id] = 2
155 @request.session[:user_id] = 2
132 put :close_completed, :project_id => 'ecookbook'
156 put :close_completed, :project_id => 'ecookbook'
133 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
157 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
134 assert_not_nil Version.find_by_status('closed')
158 assert_not_nil Version.find_by_status('closed')
135 end
159 end
136
160
137 def test_post_update
161 def test_post_update
138 @request.session[:user_id] = 2
162 @request.session[:user_id] = 2
139 put :update, :id => 2,
163 put :update, :id => 2,
140 :version => { :name => 'New version name',
164 :version => { :name => 'New version name',
141 :effective_date => Date.today.strftime("%Y-%m-%d")}
165 :effective_date => Date.today.strftime("%Y-%m-%d")}
142 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
166 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
143 version = Version.find(2)
167 version = Version.find(2)
144 assert_equal 'New version name', version.name
168 assert_equal 'New version name', version.name
145 assert_equal Date.today, version.effective_date
169 assert_equal Date.today, version.effective_date
146 end
170 end
147
171
148 def test_post_update_with_validation_failure
172 def test_post_update_with_validation_failure
149 @request.session[:user_id] = 2
173 @request.session[:user_id] = 2
150 put :update, :id => 2,
174 put :update, :id => 2,
151 :version => { :name => '',
175 :version => { :name => '',
152 :effective_date => Date.today.strftime("%Y-%m-%d")}
176 :effective_date => Date.today.strftime("%Y-%m-%d")}
153 assert_response :success
177 assert_response :success
154 assert_template 'edit'
178 assert_template 'edit'
155 end
179 end
156
180
157 def test_destroy
181 def test_destroy
158 @request.session[:user_id] = 2
182 @request.session[:user_id] = 2
159 assert_difference 'Version.count', -1 do
183 assert_difference 'Version.count', -1 do
160 delete :destroy, :id => 3
184 delete :destroy, :id => 3
161 end
185 end
162 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
186 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
163 assert_nil Version.find_by_id(3)
187 assert_nil Version.find_by_id(3)
164 end
188 end
165
189
166 def test_destroy_version_in_use_should_fail
190 def test_destroy_version_in_use_should_fail
167 @request.session[:user_id] = 2
191 @request.session[:user_id] = 2
168 assert_no_difference 'Version.count' do
192 assert_no_difference 'Version.count' do
169 delete :destroy, :id => 2
193 delete :destroy, :id => 2
170 end
194 end
171 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
195 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook'
172 assert flash[:error].match(/Unable to delete version/)
196 assert flash[:error].match(/Unable to delete version/)
173 assert Version.find_by_id(2)
197 assert Version.find_by_id(2)
174 end
198 end
175
199
176 def test_issue_status_by
200 def test_issue_status_by
177 xhr :get, :status_by, :id => 2
201 xhr :get, :status_by, :id => 2
178 assert_response :success
202 assert_response :success
179 assert_template '_issue_counts'
203 assert_template '_issue_counts'
180 end
204 end
181
205
182 def test_issue_status_by_status
206 def test_issue_status_by_status
183 xhr :get, :status_by, :id => 2, :status_by => 'status'
207 xhr :get, :status_by, :id => 2, :status_by => 'status'
184 assert_response :success
208 assert_response :success
185 assert_template '_issue_counts'
209 assert_template '_issue_counts'
186 end
210 end
187 end
211 end
General Comments 0
You need to be logged in to leave comments. Login now