@@ -18,9 +18,12 | |||||
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
19 |
|
19 | |||
20 | module QueriesHelper |
|
20 | module QueriesHelper | |
21 |
|
21 | def filters_options_for_select(query) | ||
22 | def operators_for_select(filter_type) |
|
22 | options = [[]] | |
23 | Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]} |
|
23 | options += query.available_filters.sort {|a,b| a[1][:order] <=> b[1][:order]}.map do |field, field_options| | |
|
24 | [field_options[:name], field] | |||
|
25 | end | |||
|
26 | options_for_select(options) | |||
24 | end |
|
27 | end | |
25 |
|
28 | |||
26 | def column_header(column) |
|
29 | def column_header(column) |
@@ -214,6 +214,11 class Query < ActiveRecord::Base | |||||
214 | @trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers |
|
214 | @trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers | |
215 | end |
|
215 | end | |
216 |
|
216 | |||
|
217 | # Returns a hash of localized labels for all filter operators | |||
|
218 | def self.operators_labels | |||
|
219 | operators.inject({}) {|h, operator| h[operator.first] = l(operator.last); h} | |||
|
220 | end | |||
|
221 | ||||
217 | def available_filters |
|
222 | def available_filters | |
218 | return @available_filters if @available_filters |
|
223 | return @available_filters if @available_filters | |
219 |
|
224 | |||
@@ -309,9 +314,22 class Query < ActiveRecord::Base | |||||
309 | @available_filters.delete field |
|
314 | @available_filters.delete field | |
310 | } |
|
315 | } | |
311 |
|
316 | |||
|
317 | @available_filters.each do |field, options| | |||
|
318 | options[:name] ||= l("field_#{field}".gsub(/_id$/, '')) | |||
|
319 | end | |||
|
320 | ||||
312 | @available_filters |
|
321 | @available_filters | |
313 | end |
|
322 | end | |
314 |
|
323 | |||
|
324 | # Returns a representation of the available filters for JSON serialization | |||
|
325 | def available_filters_as_json | |||
|
326 | json = {} | |||
|
327 | available_filters.each do |field, options| | |||
|
328 | json[field] = options.slice(:type, :name, :values).stringify_keys | |||
|
329 | end | |||
|
330 | json | |||
|
331 | end | |||
|
332 | ||||
315 | def add_filter(field, operator, values) |
|
333 | def add_filter(field, operator, values) | |
316 | # values must be an array |
|
334 | # values must be an array | |
317 | return unless values.nil? || values.is_a?(Array) |
|
335 | return unless values.nil? || values.is_a?(Array) |
@@ -1,53 +1,27 | |||||
|
1 | <%= javascript_tag do %> | |||
|
2 | var operatorLabels = <%= raw Query.operators_labels.to_json %>; | |||
|
3 | var operatorByType = <%= raw Query.operators_by_filter_type.to_json %>; | |||
|
4 | var availableFilters = <%= raw query.available_filters_as_json.to_json %>; | |||
|
5 | var labelDayPlural = "<%= raw escape_javascript(l(:label_day_plural)) %>"; | |||
|
6 | $(document).ready(function(){ | |||
|
7 | initFilters(); | |||
|
8 | <% query.filters.each do |field, options| %> | |||
|
9 | addFilter("<%= field %>", <%= raw query.operator_for(field).to_json %>, <%= raw query.values_for(field).to_json %>); | |||
|
10 | <% end %> | |||
|
11 | }); | |||
|
12 | <% end %> | |||
|
13 | ||||
1 | <table style="width:100%"> |
|
14 | <table style="width:100%"> | |
2 | <tr> |
|
15 | <tr> | |
3 | <td> |
|
16 | <td> | |
4 | <table> |
|
17 | <table id="filters-table"> | |
5 | <% query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.each do |filter| %> |
|
|||
6 | <% field = filter[0] |
|
|||
7 | options = filter[1] %> |
|
|||
8 | <tr <%= 'style="display:none;"'.html_safe unless query.has_filter?(field) %> id="tr_<%= field %>" class="filter"> |
|
|||
9 | <td class="field"> |
|
|||
10 | <%= check_box_tag 'f[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %> |
|
|||
11 | <label for="cb_<%= field %>"><%= filter[1][:name] || l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) %></label> |
|
|||
12 | </td> |
|
|||
13 | <td class="operator"> |
|
|||
14 | <%= label_tag "operators_#{field}", l(:description_filter), :class => "hidden-for-sighted" %> |
|
|||
15 | <%= select_tag "op[#{field}]", options_for_select(operators_for_select(options[:type]), |
|
|||
16 | query.operator_for(field)), :id => "operators_#{field}", |
|
|||
17 | :onchange => "toggle_operator('#{field}');" %> |
|
|||
18 | </td> |
|
|||
19 | <td class="values"> |
|
|||
20 | <div id="div_values_<%= field %>" style="display:none;"> |
|
|||
21 | <% case options[:type] |
|
|||
22 | when :list, :list_optional, :list_status, :list_subprojects %> |
|
|||
23 | <span class="span_values_<%= field %>"> |
|
|||
24 | <%= select_tag "v[#{field}][]", options_for_select(options[:values], query.values_for(field)), :class => "values_#{field}", :id => "values_#{field}_1", :multiple => (query.values_for(field) && query.values_for(field).length > 1) %> |
|
|||
25 | <%= link_to_function image_tag('bullet_toggle_plus.png'), "toggle_multi_select('values_#{field}_1');" %> |
|
|||
26 | </span> |
|
|||
27 | <% when :date, :date_past %> |
|
|||
28 | <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :size => 10, :class => "values_#{field}", :id => "values_#{field}_1" %> <%= calendar_for "values_#{field}_1" %></span> |
|
|||
29 | <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field, 1), :size => 10, :class => "values_#{field}", :id => "values_#{field}_2" %> <%= calendar_for "values_#{field}_2" %></span> |
|
|||
30 | <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :size => 3, :class => "values_#{field}" %> <%= l(:label_day_plural) %></span> |
|
|||
31 | <% when :string, :text %> |
|
|||
32 | <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :class => "values_#{field}", :id => "values_#{field}", :size => 30 %></span> |
|
|||
33 | <% when :integer, :float %> |
|
|||
34 | <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :class => "values_#{field}", :id => "values_#{field}_1", :size => 6 %></span> |
|
|||
35 | <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field, 1), :class => "values_#{field}", :id => "values_#{field}_2", :size => 6 %></span> |
|
|||
36 | <% end %> |
|
|||
37 | </div> |
|
|||
38 | <script type="text/javascript">toggle_filter('<%= field %>');</script> |
|
|||
39 | </td> |
|
|||
40 | </tr> |
|
|||
41 | <% end %> |
|
|||
42 | </table> |
|
18 | </table> | |
43 | </td> |
|
19 | </td> | |
44 | <td class="add-filter"> |
|
20 | <td class="add-filter"> | |
45 | <%= label_tag('add_filter_select', l(:label_filter_add)) %> |
|
21 | <%= label_tag('add_filter_select', l(:label_filter_add)) %> | |
46 | <%= select_tag 'add_filter_select', options_for_select([["",""]] + query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.collect{|field| [ field[1][:name] || l(("field_"+field[0].to_s.gsub(/_id$/, "")).to_sym), field[0]] unless query.has_filter?(field[0])}.compact), |
|
22 | <%= select_tag 'add_filter_select', filters_options_for_select(query), :name => nil %> | |
47 | :onchange => "add_filter();", |
|
|||
48 | :name => nil %> |
|
|||
49 | </td> |
|
23 | </td> | |
50 | </tr> |
|
24 | </tr> | |
51 | </table> |
|
25 | </table> | |
52 | <%= hidden_field_tag 'f[]', '' %> |
|
26 | <%= hidden_field_tag 'f[]', '' %> | |
53 | <%= javascript_tag '$(document).ready(function(){observeIssueFilters();});' %> |
|
27 | <% include_calendar_headers_tags %> |
@@ -23,7 +23,7 | |||||
23 | <%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p> |
|
23 | <%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p> | |
24 | </div> |
|
24 | </div> | |
25 |
|
25 | |||
26 | <fieldset><legend><%= l(:label_filter_plural) %></legend> |
|
26 | <fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend> | |
27 | <%= render :partial => 'queries/filters', :locals => {:query => query}%> |
|
27 | <%= render :partial => 'queries/filters', :locals => {:query => query}%> | |
28 | </fieldset> |
|
28 | </fieldset> | |
29 |
|
29 |
@@ -78,35 +78,131 function hideFieldset(el) { | |||||
78 | fieldset.children('div').hide(); |
|
78 | fieldset.children('div').hide(); | |
79 | } |
|
79 | } | |
80 |
|
80 | |||
81 |
function |
|
81 | function initFilters(){ | |
82 |
|
|
82 | $('#add_filter_select').change(function(){ | |
83 | var field = select.val(); |
|
83 | addFilter($(this).val(), '', []); | |
84 | $('#tr_'+field).show(); |
|
84 | }); | |
85 | var check_box = $('#cb_' + field); |
|
85 | $('#filters-table td.field input[type=checkbox]').each(function(){ | |
86 | check_box.attr('checked', true); |
|
86 | toggleFilter($(this).val()); | |
87 | toggle_filter(field); |
|
87 | }); | |
88 | select.val(''); |
|
88 | $('#filters-table td.field input[type=checkbox]').live('click',function(){ | |
89 |
|
89 | toggleFilter($(this).val()); | ||
90 | select.children('option').each(function(index) { |
|
90 | }); | |
|
91 | $('#filters-table .toggle-multiselect').live('click',function(){ | |||
|
92 | toggleMultiSelect($(this).siblings('select')); | |||
|
93 | }); | |||
|
94 | $('#filters-table input[type=text]').live('keypress', function(e){ | |||
|
95 | if (e.keyCode == 13) submit_query_form("query_form"); | |||
|
96 | }); | |||
|
97 | } | |||
|
98 | ||||
|
99 | function addFilter(field, operator, values) { | |||
|
100 | var fieldId = field.replace('.', '_'); | |||
|
101 | var tr = $('#tr_'+fieldId); | |||
|
102 | if (tr.length > 0) { | |||
|
103 | tr.show(); | |||
|
104 | } else { | |||
|
105 | buildFilterRow(field, operator, values); | |||
|
106 | } | |||
|
107 | $('#cb_'+fieldId).attr('checked', true); | |||
|
108 | toggleFilter(field); | |||
|
109 | $('#add_filter_select').val('').children('option').each(function(){ | |||
91 | if ($(this).attr('value') == field) { |
|
110 | if ($(this).attr('value') == field) { | |
92 | $(this).attr('disabled', true); |
|
111 | $(this).attr('disabled', true); | |
93 | } |
|
112 | } | |
94 | }); |
|
113 | }); | |
95 | } |
|
114 | } | |
96 |
|
115 | |||
97 | function toggle_filter(field) { |
|
116 | function buildFilterRow(field, operator, values) { | |
98 | check_box = $('#cb_' + field); |
|
117 | var fieldId = field.replace('.', '_'); | |
99 | if (check_box.is(':checked')) { |
|
118 | var filterTable = $("#filters-table"); | |
100 | $("#operators_" + field).show().removeAttr('disabled'); |
|
119 | var filterOptions = availableFilters[field]; | |
101 | toggle_operator(field); |
|
120 | var operators = operatorByType[filterOptions['type']]; | |
|
121 | var filterValues = filterOptions['values']; | |||
|
122 | var i, select; | |||
|
123 | ||||
|
124 | var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html( | |||
|
125 | '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' + | |||
|
126 | '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' + | |||
|
127 | '<td class="values"></td>' | |||
|
128 | ); | |||
|
129 | filterTable.append(tr); | |||
|
130 | ||||
|
131 | select = tr.find('td.operator select'); | |||
|
132 | for (i=0;i<operators.length;i++){ | |||
|
133 | var option = $('<option>').val(operators[i]).html(operatorLabels[operators[i]]); | |||
|
134 | if (operators[i] == operator) {option.attr('selected', true)}; | |||
|
135 | select.append(option); | |||
|
136 | } | |||
|
137 | select.change(function(){toggleOperator(field)}); | |||
|
138 | ||||
|
139 | switch (filterOptions['type']){ | |||
|
140 | case "list": | |||
|
141 | case "list_optional": | |||
|
142 | case "list_status": | |||
|
143 | case "list_subprojects": | |||
|
144 | tr.find('td.values').append( | |||
|
145 | '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' + | |||
|
146 | ' <span class="toggle-multiselect"> </span></span>' | |||
|
147 | ); | |||
|
148 | select = tr.find('td.values select'); | |||
|
149 | if (values.length > 1) {select.attr('multiple', true)}; | |||
|
150 | for (i=0;i<filterValues.length;i++){ | |||
|
151 | var filterValue = filterValues[i]; | |||
|
152 | var option = $('<option>'); | |||
|
153 | if ($.isArray(filterValue)) { | |||
|
154 | option.val(filterValue[1]).html(filterValue[0]); | |||
|
155 | } else { | |||
|
156 | option.val(filterValue).html(filterValue); | |||
|
157 | } | |||
|
158 | if (values.indexOf(filterValues[i][1]) > -1) {option.attr('selected', true)}; | |||
|
159 | select.append(option); | |||
|
160 | } | |||
|
161 | break; | |||
|
162 | case "date": | |||
|
163 | case "date_past": | |||
|
164 | tr.find('td.values').append( | |||
|
165 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" value="'+values[0]+'" /></span>' + | |||
|
166 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" value="'+values[1]+'" /></span>' + | |||
|
167 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" value="'+values[0]+'" /> '+labelDayPlural+'</span>' | |||
|
168 | ); | |||
|
169 | $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions); | |||
|
170 | $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions); | |||
|
171 | $('#values_'+fieldId).val(values[0]); | |||
|
172 | break; | |||
|
173 | case "string": | |||
|
174 | case "text": | |||
|
175 | tr.find('td.values').append( | |||
|
176 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" value="'+values[0]+'" /></span>' | |||
|
177 | ); | |||
|
178 | $('#values_'+fieldId).val(values[0]); | |||
|
179 | break; | |||
|
180 | case "integer": | |||
|
181 | case "float": | |||
|
182 | tr.find('td.values').append( | |||
|
183 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" value="'+values[0]+'" /></span>' + | |||
|
184 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" value="'+values[1]+'" /></span>' | |||
|
185 | ); | |||
|
186 | $('#values_'+fieldId+'_1').val(values[0]); | |||
|
187 | $('#values_'+fieldId+'_2').val(values[1]); | |||
|
188 | break; | |||
|
189 | } | |||
|
190 | } | |||
|
191 | ||||
|
192 | function toggleFilter(field) { | |||
|
193 | var fieldId = field.replace('.', '_'); | |||
|
194 | if ($('#cb_' + fieldId).is(':checked')) { | |||
|
195 | $("#operators_" + fieldId).show().removeAttr('disabled'); | |||
|
196 | toggleOperator(field); | |||
102 | } else { |
|
197 | } else { | |
103 | $("#operators_" + field).hide().attr('disabled', true); |
|
198 | $("#operators_" + fieldId).hide().attr('disabled', true); | |
104 | enableValues(field, []); |
|
199 | enableValues(field, []); | |
105 | } |
|
200 | } | |
106 | } |
|
201 | } | |
107 |
|
202 | |||
108 | function enableValues(field, indexes) { |
|
203 | function enableValues(field, indexes) { | |
109 | $(".values_" + field).each(function(index) { |
|
204 | var fieldId = field.replace('.', '_'); | |
|
205 | $('#tr_'+fieldId+' td.values .value').each(function(index) { | |||
110 | if (indexes.indexOf(index) >= 0) { |
|
206 | if (indexes.indexOf(index) >= 0) { | |
111 | $(this).removeAttr('disabled'); |
|
207 | $(this).removeAttr('disabled'); | |
112 | $(this).parents('span').first().show(); |
|
208 | $(this).parents('span').first().show(); | |
@@ -122,16 +218,11 function enableValues(field, indexes) { | |||||
122 | $(this).show(); |
|
218 | $(this).show(); | |
123 | } |
|
219 | } | |
124 | }); |
|
220 | }); | |
125 |
|
||||
126 | if (indexes.length > 0) { |
|
|||
127 | $("#div_values_" + field).show(); |
|
|||
128 | } else { |
|
|||
129 | $("#div_values_" + field).hide(); |
|
|||
130 | } |
|
|||
131 | } |
|
221 | } | |
132 |
|
222 | |||
133 |
function toggle |
|
223 | function toggleOperator(field) { | |
134 | operator = $("#operators_" + field); |
|
224 | var fieldId = field.replace('.', '_'); | |
|
225 | var operator = $("#operators_" + fieldId); | |||
135 | switch (operator.val()) { |
|
226 | switch (operator.val()) { | |
136 | case "!*": |
|
227 | case "!*": | |
137 | case "*": |
|
228 | case "*": | |
@@ -158,12 +249,11 function toggle_operator(field) { | |||||
158 | } |
|
249 | } | |
159 | } |
|
250 | } | |
160 |
|
251 | |||
161 |
function toggle |
|
252 | function toggleMultiSelect(el) { | |
162 | var select = $('#'+id); |
|
253 | if (el.attr('multiple')) { | |
163 |
|
|
254 | el.removeAttr('multiple'); | |
164 | select.removeAttr('multiple'); |
|
|||
165 | } else { |
|
255 | } else { | |
166 |
|
|
256 | el.attr('multiple', true); | |
167 | } |
|
257 | } | |
168 | } |
|
258 | } | |
169 |
|
259 | |||
@@ -172,12 +262,6 function submit_query_form(id) { | |||||
172 | $('#'+id).submit(); |
|
262 | $('#'+id).submit(); | |
173 | } |
|
263 | } | |
174 |
|
264 | |||
175 | function observeIssueFilters() { |
|
|||
176 | $('#query_form input[type=text]').keypress(function(e){ |
|
|||
177 | if (e.keyCode == 13) submit_query_form("query_form"); |
|
|||
178 | }); |
|
|||
179 | } |
|
|||
180 |
|
||||
181 | var fileFieldCount = 1; |
|
265 | var fileFieldCount = 1; | |
182 | function addFileField() { |
|
266 | function addFileField() { | |
183 | var fields = $('#attachments_fields'); |
|
267 | var fields = $('#attachments_fields'); |
@@ -338,13 +338,14 fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_co | |||||
338 | fieldset#date-range p { margin: 2px 0 2px 0; } |
|
338 | fieldset#date-range p { margin: 2px 0 2px 0; } | |
339 | fieldset#filters table { border-collapse: collapse; } |
|
339 | fieldset#filters table { border-collapse: collapse; } | |
340 | fieldset#filters table td { padding: 0; vertical-align: middle; } |
|
340 | fieldset#filters table td { padding: 0; vertical-align: middle; } | |
341 | fieldset#filters tr.filter { height: 2em; } |
|
341 | fieldset#filters tr.filter { height: 2.1em; } | |
342 | fieldset#filters td.field { width:200px; } |
|
342 | fieldset#filters td.field { width:200px; } | |
343 | fieldset#filters td.operator { width:170px; } |
|
343 | fieldset#filters td.operator { width:170px; } | |
344 | fieldset#filters td.values { white-space:nowrap; } |
|
344 | fieldset#filters td.values { white-space:nowrap; } | |
345 | fieldset#filters td.values select {min-width:130px;} |
|
345 | fieldset#filters td.values select {min-width:130px;} | |
346 |
fieldset#filters td.values i |
|
346 | fieldset#filters td.values input {height:1em;} | |
347 | fieldset#filters td.add-filter { text-align: right; vertical-align: top; } |
|
347 | fieldset#filters td.add-filter { text-align: right; vertical-align: top; } | |
|
348 | .toggle-multiselect {background: url(../images/bullet_toggle_plus.png) no-repeat 0% 40%; padding-left:8px; margin-left:0; cursor:pointer;} | |||
348 | .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; } |
|
349 | .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; } | |
349 |
|
350 | |||
350 | div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;} |
|
351 | div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;} |
General Comments 0
You need to be logged in to leave comments.
Login now