diff options
-rw-r--r-- | app/helpers/queries_helper.rb | 9 | ||||
-rw-r--r-- | app/models/query.rb | 18 | ||||
-rw-r--r-- | app/views/queries/_filters.html.erb | 58 | ||||
-rw-r--r-- | app/views/queries/_form.html.erb | 2 | ||||
-rw-r--r-- | public/javascripts/application.js | 156 | ||||
-rw-r--r-- | public/stylesheets/application.css | 5 |
6 files changed, 164 insertions, 84 deletions
diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb index c35bfc01e..076a8959e 100644 --- a/app/helpers/queries_helper.rb +++ b/app/helpers/queries_helper.rb @@ -18,9 +18,12 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module QueriesHelper - - def operators_for_select(filter_type) - Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]} + def filters_options_for_select(query) + options = [[]] + options += query.available_filters.sort {|a,b| a[1][:order] <=> b[1][:order]}.map do |field, field_options| + [field_options[:name], field] + end + options_for_select(options) end def column_header(column) diff --git a/app/models/query.rb b/app/models/query.rb index 8c5f0b090..628a1dfeb 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -214,6 +214,11 @@ class Query < ActiveRecord::Base @trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers end + # Returns a hash of localized labels for all filter operators + def self.operators_labels + operators.inject({}) {|h, operator| h[operator.first] = l(operator.last); h} + end + def available_filters return @available_filters if @available_filters @@ -309,9 +314,22 @@ class Query < ActiveRecord::Base @available_filters.delete field } + @available_filters.each do |field, options| + options[:name] ||= l("field_#{field}".gsub(/_id$/, '')) + end + @available_filters end + # Returns a representation of the available filters for JSON serialization + def available_filters_as_json + json = {} + available_filters.each do |field, options| + json[field] = options.slice(:type, :name, :values).stringify_keys + end + json + end + def add_filter(field, operator, values) # values must be an array return unless values.nil? || values.is_a?(Array) diff --git a/app/views/queries/_filters.html.erb b/app/views/queries/_filters.html.erb index d9f91ce2e..80b06c10a 100644 --- a/app/views/queries/_filters.html.erb +++ b/app/views/queries/_filters.html.erb @@ -1,53 +1,27 @@ +<%= javascript_tag do %> +var operatorLabels = <%= raw Query.operators_labels.to_json %>; +var operatorByType = <%= raw Query.operators_by_filter_type.to_json %>; +var availableFilters = <%= raw query.available_filters_as_json.to_json %>; +var labelDayPlural = "<%= raw escape_javascript(l(:label_day_plural)) %>"; +$(document).ready(function(){ + initFilters(); + <% query.filters.each do |field, options| %> + addFilter("<%= field %>", <%= raw query.operator_for(field).to_json %>, <%= raw query.values_for(field).to_json %>); + <% end %> +}); +<% end %> + <table style="width:100%"> <tr> <td> -<table> -<% query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.each do |filter| %> - <% field = filter[0] - options = filter[1] %> - <tr <%= 'style="display:none;"'.html_safe unless query.has_filter?(field) %> id="tr_<%= field %>" class="filter"> - <td class="field"> - <%= check_box_tag 'f[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %> - <label for="cb_<%= field %>"><%= filter[1][:name] || l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) %></label> - </td> - <td class="operator"> - <%= label_tag "operators_#{field}", l(:description_filter), :class => "hidden-for-sighted" %> - <%= select_tag "op[#{field}]", options_for_select(operators_for_select(options[:type]), - query.operator_for(field)), :id => "operators_#{field}", - :onchange => "toggle_operator('#{field}');" %> - </td> - <td class="values"> - <div id="div_values_<%= field %>" style="display:none;"> - <% case options[:type] - when :list, :list_optional, :list_status, :list_subprojects %> - <span class="span_values_<%= field %>"> - <%= 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) %> - <%= link_to_function image_tag('bullet_toggle_plus.png'), "toggle_multi_select('values_#{field}_1');" %> - </span> - <% when :date, :date_past %> - <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> - <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> - <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :size => 3, :class => "values_#{field}" %> <%= l(:label_day_plural) %></span> - <% when :string, :text %> - <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :class => "values_#{field}", :id => "values_#{field}", :size => 30 %></span> - <% when :integer, :float %> - <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :class => "values_#{field}", :id => "values_#{field}_1", :size => 6 %></span> - <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> - <% end %> - </div> - <script type="text/javascript">toggle_filter('<%= field %>');</script> - </td> - </tr> -<% end %> +<table id="filters-table"> </table> </td> <td class="add-filter"> <%= label_tag('add_filter_select', l(:label_filter_add)) %> -<%= 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), - :onchange => "add_filter();", - :name => nil %> +<%= select_tag 'add_filter_select', filters_options_for_select(query), :name => nil %> </td> </tr> </table> <%= hidden_field_tag 'f[]', '' %> -<%= javascript_tag '$(document).ready(function(){observeIssueFilters();});' %> +<% include_calendar_headers_tags %> diff --git a/app/views/queries/_form.html.erb b/app/views/queries/_form.html.erb index 76ce35ebe..0e75d9418 100644 --- a/app/views/queries/_form.html.erb +++ b/app/views/queries/_form.html.erb @@ -23,7 +23,7 @@ <%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p> </div> -<fieldset><legend><%= l(:label_filter_plural) %></legend> +<fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend> <%= render :partial => 'queries/filters', :locals => {:query => query}%> </fieldset> diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 42fb945ad..20bfb0895 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -78,35 +78,131 @@ function hideFieldset(el) { fieldset.children('div').hide(); } -function add_filter() { - var select = $('#add_filter_select'); - var field = select.val(); - $('#tr_'+field).show(); - var check_box = $('#cb_' + field); - check_box.attr('checked', true); - toggle_filter(field); - select.val(''); - - select.children('option').each(function(index) { +function initFilters(){ + $('#add_filter_select').change(function(){ + addFilter($(this).val(), '', []); + }); + $('#filters-table td.field input[type=checkbox]').each(function(){ + toggleFilter($(this).val()); + }); + $('#filters-table td.field input[type=checkbox]').live('click',function(){ + toggleFilter($(this).val()); + }); + $('#filters-table .toggle-multiselect').live('click',function(){ + toggleMultiSelect($(this).siblings('select')); + }); + $('#filters-table input[type=text]').live('keypress', function(e){ + if (e.keyCode == 13) submit_query_form("query_form"); + }); +} + +function addFilter(field, operator, values) { + var fieldId = field.replace('.', '_'); + var tr = $('#tr_'+fieldId); + if (tr.length > 0) { + tr.show(); + } else { + buildFilterRow(field, operator, values); + } + $('#cb_'+fieldId).attr('checked', true); + toggleFilter(field); + $('#add_filter_select').val('').children('option').each(function(){ if ($(this).attr('value') == field) { $(this).attr('disabled', true); } }); } -function toggle_filter(field) { - check_box = $('#cb_' + field); - if (check_box.is(':checked')) { - $("#operators_" + field).show().removeAttr('disabled'); - toggle_operator(field); +function buildFilterRow(field, operator, values) { + var fieldId = field.replace('.', '_'); + var filterTable = $("#filters-table"); + var filterOptions = availableFilters[field]; + var operators = operatorByType[filterOptions['type']]; + var filterValues = filterOptions['values']; + var i, select; + + var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html( + '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' + + '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' + + '<td class="values"></td>' + ); + filterTable.append(tr); + + select = tr.find('td.operator select'); + for (i=0;i<operators.length;i++){ + var option = $('<option>').val(operators[i]).html(operatorLabels[operators[i]]); + if (operators[i] == operator) {option.attr('selected', true)}; + select.append(option); + } + select.change(function(){toggleOperator(field)}); + + switch (filterOptions['type']){ + case "list": + case "list_optional": + case "list_status": + case "list_subprojects": + tr.find('td.values').append( + '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' + + ' <span class="toggle-multiselect"> </span></span>' + ); + select = tr.find('td.values select'); + if (values.length > 1) {select.attr('multiple', true)}; + for (i=0;i<filterValues.length;i++){ + var filterValue = filterValues[i]; + var option = $('<option>'); + if ($.isArray(filterValue)) { + option.val(filterValue[1]).html(filterValue[0]); + } else { + option.val(filterValue).html(filterValue); + } + if (values.indexOf(filterValues[i][1]) > -1) {option.attr('selected', true)}; + select.append(option); + } + break; + case "date": + case "date_past": + tr.find('td.values').append( + '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" value="'+values[0]+'" /></span>' + + ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" value="'+values[1]+'" /></span>' + + ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" value="'+values[0]+'" /> '+labelDayPlural+'</span>' + ); + $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions); + $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions); + $('#values_'+fieldId).val(values[0]); + break; + case "string": + case "text": + tr.find('td.values').append( + '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" value="'+values[0]+'" /></span>' + ); + $('#values_'+fieldId).val(values[0]); + break; + case "integer": + case "float": + tr.find('td.values').append( + '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" value="'+values[0]+'" /></span>' + + ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" value="'+values[1]+'" /></span>' + ); + $('#values_'+fieldId+'_1').val(values[0]); + $('#values_'+fieldId+'_2').val(values[1]); + break; + } +} + +function toggleFilter(field) { + var fieldId = field.replace('.', '_'); + if ($('#cb_' + fieldId).is(':checked')) { + $("#operators_" + fieldId).show().removeAttr('disabled'); + toggleOperator(field); } else { - $("#operators_" + field).hide().attr('disabled', true); + $("#operators_" + fieldId).hide().attr('disabled', true); enableValues(field, []); } } function enableValues(field, indexes) { - $(".values_" + field).each(function(index) { + var fieldId = field.replace('.', '_'); + $('#tr_'+fieldId+' td.values .value').each(function(index) { if (indexes.indexOf(index) >= 0) { $(this).removeAttr('disabled'); $(this).parents('span').first().show(); @@ -122,16 +218,11 @@ function enableValues(field, indexes) { $(this).show(); } }); - - if (indexes.length > 0) { - $("#div_values_" + field).show(); - } else { - $("#div_values_" + field).hide(); - } } -function toggle_operator(field) { - operator = $("#operators_" + field); +function toggleOperator(field) { + var fieldId = field.replace('.', '_'); + var operator = $("#operators_" + fieldId); switch (operator.val()) { case "!*": case "*": @@ -158,12 +249,11 @@ function toggle_operator(field) { } } -function toggle_multi_select(id) { - var select = $('#'+id); - if (select.attr('multiple')) { - select.removeAttr('multiple'); +function toggleMultiSelect(el) { + if (el.attr('multiple')) { + el.removeAttr('multiple'); } else { - select.attr('multiple', true); + el.attr('multiple', true); } } @@ -172,12 +262,6 @@ function submit_query_form(id) { $('#'+id).submit(); } -function observeIssueFilters() { - $('#query_form input[type=text]').keypress(function(e){ - if (e.keyCode == 13) submit_query_form("query_form"); - }); -} - var fileFieldCount = 1; function addFileField() { var fields = $('#attachments_fields'); diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 3b4831d47..3400bd4c0 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -338,13 +338,14 @@ fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_co fieldset#date-range p { margin: 2px 0 2px 0; } fieldset#filters table { border-collapse: collapse; } fieldset#filters table td { padding: 0; vertical-align: middle; } -fieldset#filters tr.filter { height: 2em; } +fieldset#filters tr.filter { height: 2.1em; } fieldset#filters td.field { width:200px; } fieldset#filters td.operator { width:170px; } fieldset#filters td.values { white-space:nowrap; } fieldset#filters td.values select {min-width:130px;} -fieldset#filters td.values img { vertical-align: middle; margin-left:1px; } +fieldset#filters td.values input {height:1em;} fieldset#filters td.add-filter { text-align: right; vertical-align: top; } +.toggle-multiselect {background: url(../images/bullet_toggle_plus.png) no-repeat 0% 40%; padding-left:8px; margin-left:0; cursor:pointer;} .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; } div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;} |