wice_grid_ms 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.inch.yml +3 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +181 -0
  6. data/.travis.yml +22 -0
  7. data/CHANGELOG.md +714 -0
  8. data/Gemfile +4 -0
  9. data/MIT-LICENSE +20 -0
  10. data/README.md +1518 -0
  11. data/Rakefile +59 -0
  12. data/SAVED_QUERIES_HOWTO.md +113 -0
  13. data/TODO.md +16 -0
  14. data/app/views/kaminari/wice_grid/_gap.html.erb +1 -0
  15. data/app/views/kaminari/wice_grid/_next_page.html.erb +1 -0
  16. data/app/views/kaminari/wice_grid/_page.html.erb +1 -0
  17. data/app/views/kaminari/wice_grid/_paginator.html.erb +19 -0
  18. data/app/views/kaminari/wice_grid/_prev_page.html.erb +1 -0
  19. data/config/locales/cz.yml +45 -0
  20. data/config/locales/de.yml +47 -0
  21. data/config/locales/en.yml +47 -0
  22. data/config/locales/es.yml +47 -0
  23. data/config/locales/fr.yml +45 -0
  24. data/config/locales/is.yml +46 -0
  25. data/config/locales/it.yml +38 -0
  26. data/config/locales/ja.yml +47 -0
  27. data/config/locales/nl.yml +45 -0
  28. data/config/locales/pt-BR.yml +36 -0
  29. data/config/locales/pt.yml +45 -0
  30. data/config/locales/ru.yml +45 -0
  31. data/config/locales/sk.yml +45 -0
  32. data/config/locales/uk.yml +45 -0
  33. data/config/locales/zh.yml +45 -0
  34. data/lib/generators/wice_grid/add_migration_for_serialized_queries_generator.rb +20 -0
  35. data/lib/generators/wice_grid/install_generator.rb +14 -0
  36. data/lib/generators/wice_grid/templates/create_wice_grid_serialized_queries.rb +14 -0
  37. data/lib/generators/wice_grid/templates/wice_grid_config.rb +192 -0
  38. data/lib/wice/active_record_column_wrapper.rb +123 -0
  39. data/lib/wice/columns.rb +276 -0
  40. data/lib/wice/columns/column_action.rb +52 -0
  41. data/lib/wice/columns/column_boolean.rb +40 -0
  42. data/lib/wice/columns/column_bootstrap_datepicker.rb +48 -0
  43. data/lib/wice/columns/column_custom_dropdown.rb +115 -0
  44. data/lib/wice/columns/column_float.rb +9 -0
  45. data/lib/wice/columns/column_html5_datepicker.rb +31 -0
  46. data/lib/wice/columns/column_integer.rb +78 -0
  47. data/lib/wice/columns/column_jquery_datepicker.rb +49 -0
  48. data/lib/wice/columns/column_processor_index.rb +23 -0
  49. data/lib/wice/columns/column_rails_date_helper.rb +41 -0
  50. data/lib/wice/columns/column_rails_datetime_helper.rb +40 -0
  51. data/lib/wice/columns/column_range.rb +72 -0
  52. data/lib/wice/columns/column_string.rb +92 -0
  53. data/lib/wice/columns/common_date_datetime_mixin.rb +20 -0
  54. data/lib/wice/columns/common_js_date_datetime_conditions_generator_mixin.rb +42 -0
  55. data/lib/wice/columns/common_js_date_datetime_mixin.rb +15 -0
  56. data/lib/wice/columns/common_rails_date_datetime_conditions_generator_mixin.rb +26 -0
  57. data/lib/wice/columns/common_standard_helper_date_datetime_mixin.rb +22 -0
  58. data/lib/wice/grid_output_buffer.rb +49 -0
  59. data/lib/wice/grid_renderer.rb +609 -0
  60. data/lib/wice/helpers/bs_calendar_helpers.rb +66 -0
  61. data/lib/wice/helpers/js_calendar_helpers.rb +83 -0
  62. data/lib/wice/helpers/wice_grid_misc_view_helpers.rb +75 -0
  63. data/lib/wice/helpers/wice_grid_serialized_queries_view_helpers.rb +95 -0
  64. data/lib/wice/helpers/wice_grid_view_helpers.rb +718 -0
  65. data/lib/wice/kaminari_monkey_patching.rb +14 -0
  66. data/lib/wice/table_column_matrix.rb +65 -0
  67. data/lib/wice/wice_grid_controller.rb +223 -0
  68. data/lib/wice/wice_grid_core_ext.rb +142 -0
  69. data/lib/wice/wice_grid_misc.rb +209 -0
  70. data/lib/wice/wice_grid_serialized_queries_controller.rb +87 -0
  71. data/lib/wice/wice_grid_serialized_query.rb +14 -0
  72. data/lib/wice/wice_grid_spreadsheet.rb +20 -0
  73. data/lib/wice_grid.rb +676 -0
  74. data/release_notes/RELEASE_NOTES_3.2.pre1.rdoc +81 -0
  75. data/release_notes/RELEASE_NOTES_3.2.pre2.rdoc +6 -0
  76. data/release_notes/RELEASE_NOTES_3.3.0.rdoc +21 -0
  77. data/spec/schema.rb +9 -0
  78. data/spec/spec_helper.rb +75 -0
  79. data/spec/support/active_record.rb +11 -0
  80. data/spec/support/wice_grid_test_config.rb +175 -0
  81. data/spec/wice/grid_output_buffer_spec.rb +41 -0
  82. data/spec/wice/table_column_matrix_spec.rb +38 -0
  83. data/spec/wice/wice_grid_misc_spec.rb +159 -0
  84. data/spec/wice/wice_grid_spreadsheet_spec.rb +14 -0
  85. data/test/readme.txt +1 -0
  86. data/vendor/assets/javascripts/wice_grid.js +3 -0
  87. data/vendor/assets/javascripts/wice_grid_init.js.coffee +339 -0
  88. data/vendor/assets/javascripts/wice_grid_processor.js.coffee +133 -0
  89. data/vendor/assets/javascripts/wice_grid_saved_queries_init.js.coffee +103 -0
  90. data/vendor/assets/stylesheets/wice_grid.scss +81 -0
  91. data/wice_grid.gemspec +36 -0
  92. metadata +335 -0
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ module Wice
3
+ module Columns #:nodoc:
4
+ class ViewColumnRailsDateHelper < ViewColumn #:nodoc:
5
+
6
+ include ActionView::Helpers::DateHelper
7
+ include Wice::Columns::CommonDateDatetimeMixin
8
+ include Wice::Columns::CommonStandardDateDatetimeMixin
9
+
10
+ def chunk_names #:nodoc:
11
+ %w(year month day)
12
+ end
13
+
14
+ def do_render(params) #:nodoc:
15
+ '<div class="date-filter">' +
16
+ select_date(params[:fr], include_blank: true, prefix: @name1, id: @dom_id) + '<br/>' +
17
+ select_date(params[:to], include_blank: true, prefix: @name2, id: @dom_id2) +
18
+ '</div>'
19
+ end
20
+
21
+ # name_and_id_from_options in Rails Date helper does not substitute '.' with '_'
22
+ # like all other simpler form helpers do. Thus, overriding it here.
23
+ def name_and_id_from_options(options, type) #:nodoc:
24
+ options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]")
25
+ options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '').gsub(/\./, '_').gsub(/_+/, '_')
26
+ end
27
+
28
+
29
+ def has_auto_reloading_calendar? #:nodoc:
30
+ false
31
+ end
32
+
33
+ end
34
+
35
+ class ConditionsGeneratorColumnRailsDateHelper < ConditionsGeneratorColumn #:nodoc:
36
+
37
+ include Wice::Columns::CommonRailsDateDatetimeConditionsGeneratorMixin
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+ module Wice
3
+ module Columns #:nodoc:
4
+ class ViewColumnRailsDatetimeHelper < ViewColumn #:nodoc:
5
+
6
+ include ActionView::Helpers::DateHelper
7
+ include Wice::Columns::CommonDateDatetimeMixin
8
+ include Wice::Columns::CommonStandardDateDatetimeMixin
9
+
10
+ def chunk_names #:nodoc:
11
+ %w(year month day hour minute)
12
+ end
13
+
14
+ def do_render(params) #:nodoc:
15
+ '<div class="date-filter">' +
16
+ select_datetime(params[:fr], include_blank: true, prefix: @name1) + '<br/>' +
17
+ select_datetime(params[:to], include_blank: true, prefix: @name2) +
18
+ '</div>'
19
+ end
20
+
21
+ # name_and_id_from_options in Rails Date helper does not substitute '.' with '_'
22
+ # like all other simpler form helpers do. Thus, overriding it here.
23
+ def name_and_id_from_options(options, type) #:nodoc:
24
+ options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]")
25
+ options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '').gsub(/\./, '_').gsub(/_+/, '_')
26
+ end
27
+
28
+ def has_auto_reloading_calendar? #:nodoc:
29
+ false
30
+ end
31
+
32
+ end
33
+
34
+ class ConditionsGeneratorColumnRailsDatetimeHelper < ConditionsGeneratorColumn #:nodoc:
35
+
36
+ include Wice::Columns::CommonRailsDateDatetimeConditionsGeneratorMixin
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,72 @@
1
+ # encoding: utf-8
2
+ module Wice
3
+ module Columns #:nodoc:
4
+ class ViewColumnRange < ViewColumn #:nodoc:
5
+ def render_filter_internal(params) #:nodoc:
6
+ @contains_a_text_input = true
7
+
8
+ @query, _, parameter_name, @dom_id = form_parameter_name_id_and_query(fr: '')
9
+ @query2, _, parameter_name2, @dom_id2 = form_parameter_name_id_and_query(to: '')
10
+
11
+ opts1 = { size: 2, id: @dom_id, class: 'form-control input-sm range-start' }
12
+ opts2 = { size: 2, id: @dom_id2, class: 'form-control input-sm range-end' }
13
+
14
+ if auto_reload
15
+ opts1[:class] += ' auto-reload'
16
+ opts2[:class] += ' auto-reload'
17
+ end
18
+
19
+ content_tag(
20
+ :div,
21
+ text_field_tag(parameter_name, params[:fr], opts1) + text_field_tag(parameter_name2, params[:to], opts2),
22
+ class: 'form-inline')
23
+ end
24
+
25
+ def yield_declaration_of_column_filter #:nodoc:
26
+ {
27
+ templates: [@query, @query2],
28
+ ids: [@dom_id, @dom_id2]
29
+ }
30
+ end
31
+
32
+ def has_auto_reloading_input? #:nodoc:
33
+ auto_reload
34
+ end
35
+ end
36
+
37
+ class ConditionsGeneratorColumnRange < ConditionsGeneratorColumn #:nodoc:
38
+ def generate_conditions(table_alias, opts) #:nodoc:
39
+ unless opts.is_a? Hash
40
+ Wice.log 'invalid parameters for the grid integer filter - must be a hash'
41
+ return false
42
+ end
43
+ conditions = [[]]
44
+ if opts[:fr]
45
+ if opts[:fr] =~ /\d/
46
+ conditions[0] << " #{@column_wrapper.alias_or_table_name(table_alias)}.#{@column_wrapper.name} >= ? "
47
+ conditions << opts[:fr]
48
+ else
49
+ opts.delete(:fr)
50
+ end
51
+ end
52
+
53
+ if opts[:to]
54
+ if opts[:to] =~ /\d/
55
+ conditions[0] << " #{@column_wrapper.alias_or_table_name(table_alias)}.#{@column_wrapper.name} <= ? "
56
+ conditions << opts[:to]
57
+ else
58
+ opts.delete(:to)
59
+ end
60
+ end
61
+
62
+ if conditions.size == 1
63
+ return false
64
+ end
65
+
66
+ conditions[0] = conditions[0].join(' and ')
67
+
68
+ conditions
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,92 @@
1
+ # encoding: utf-8
2
+ module Wice
3
+ module Columns #:nodoc:
4
+ class ViewColumnString < ViewColumn #:nodoc:
5
+
6
+ # whether the filter contains a negation checkbox
7
+ attr_accessor :negation
8
+
9
+ # whether the filter contains a negation checkbox and autoreloading is necessary
10
+ attr_accessor :auto_reloading_input_with_negation_checkbox
11
+
12
+ def render_filter_internal(params) #:nodoc:
13
+ @contains_a_text_input = true
14
+ css_class = 'form-control input-sm ' + (auto_reload ? 'auto-reload' : '')
15
+
16
+ if negation
17
+ self.auto_reloading_input_with_negation_checkbox = true if auto_reload
18
+
19
+ @query, _, parameter_name, @dom_id = form_parameter_name_id_and_query(v: '')
20
+ @query2, _, parameter_name2, @dom_id2 = form_parameter_name_id_and_query(n: '')
21
+
22
+ '<div class="text-filter-container">' +
23
+ text_field_tag(parameter_name, params[:v], size: 8, id: @dom_id, class: css_class) +
24
+ if defined?(Wice::Defaults::NEGATION_CHECKBOX_LABEL) && !Wice::ConfigurationProvider.value_for(:NEGATION_CHECKBOX_LABEL).blank?
25
+ Wice::ConfigurationProvider.value_for(:NEGATION_CHECKBOX_LABEL)
26
+ else
27
+ ''
28
+ end +
29
+ check_box_tag(parameter_name2, '1', (params[:n] == '1'),
30
+ id: @dom_id2,
31
+ title: NlMessage['negation_checkbox_title'],
32
+ class: "negation-checkbox #{css_class}") +
33
+ '</div>'
34
+ else
35
+ @query, _, parameter_name, @dom_id = form_parameter_name_id_and_query('')
36
+ text_field_tag(parameter_name, (params.blank? ? '' : params), size: 8, id: @dom_id, class: css_class)
37
+ end
38
+ end
39
+
40
+ def yield_declaration_of_column_filter #:nodoc:
41
+ if negation
42
+ {
43
+ templates: [@query, @query2],
44
+ ids: [@dom_id, @dom_id2]
45
+ }
46
+ else
47
+ {
48
+ templates: [@query],
49
+ ids: [@dom_id]
50
+ }
51
+ end
52
+ end
53
+
54
+ def has_auto_reloading_input? #:nodoc:
55
+ auto_reload
56
+ end
57
+
58
+ def auto_reloading_input_with_negation_checkbox? #:nodoc:
59
+ self.auto_reloading_input_with_negation_checkbox
60
+ end
61
+ end
62
+
63
+ class ConditionsGeneratorColumnString < ConditionsGeneratorColumn #:nodoc:
64
+ def generate_conditions(table_alias, opts) #:nodoc:
65
+ if opts.is_a? String
66
+ string_fragment = opts
67
+ negation = ''
68
+ elsif (opts.is_a? Hash) && opts.key?(:v)
69
+ string_fragment = opts[:v]
70
+ negation = opts[:n] == '1' ? 'NOT' : ''
71
+ else
72
+ Wice.log "invalid parameters for the grid string filter - must be a string: #{opts.inspect} or a Hash with keys :v and :n"
73
+ return false
74
+ end
75
+ if string_fragment.empty?
76
+ return false
77
+ end
78
+
79
+ string_matching_operator = ::Wice.get_string_matching_operators(@column_wrapper.model)
80
+
81
+ comparator = if string_matching_operator == 'CI_LIKE'
82
+ " #{negation} UPPER(#{@column_wrapper.alias_or_table_name(table_alias)}.#{@column_wrapper.name}) LIKE UPPER(?)"
83
+ else
84
+ " #{negation} #{@column_wrapper.alias_or_table_name(table_alias)}.#{@column_wrapper.name} #{string_matching_operator} ?"
85
+ end
86
+
87
+ [ comparator, '%' + string_fragment + '%' ]
88
+
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ module Wice
3
+ module Columns #:nodoc:
4
+ module CommonDateDatetimeMixin #:nodoc:
5
+
6
+ def render_filter_internal(params) #:nodoc:
7
+ prepare
8
+ do_render(params)
9
+ end
10
+
11
+ def yield_declaration_of_column_filter #:nodoc:
12
+ {
13
+ templates: @queris_ids.collect { |tuple| tuple[0] },
14
+ ids: @queris_ids.collect { |tuple| tuple[1] }
15
+ }
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+ module Wice
3
+ module Columns #:nodoc:
4
+ module CommonJsDateDatetimeConditionsGeneratorMixin #:nodoc:
5
+
6
+ def generate_conditions(table_alias, opts) #:nodoc:
7
+
8
+ datetime = @column_type == :datetime || @column_type == :timestamp
9
+
10
+ conditions = [[]]
11
+ if opts[:fr]
12
+ conditions[0] << " #{@column_wrapper.alias_or_table_name(table_alias)}.#{@column_wrapper.name} >= ? "
13
+ date = opts[:fr].to_date
14
+ if datetime
15
+ date = date.to_datetime
16
+ end
17
+ conditions << date
18
+ end
19
+
20
+ if opts[:to]
21
+ op = '<='
22
+ date = opts[:to].to_date
23
+ if @column_type == :bootstrap_datepicker
24
+ date = opts[:to].to_date + 1.day
25
+ op = '<'
26
+ elsif datetime
27
+ date = (date + 1).to_datetime
28
+ op = '<'
29
+ end
30
+ conditions[0] << " #{@column_wrapper.alias_or_table_name(table_alias)}.#{@column_wrapper.name} #{op} ? "
31
+ conditions << date
32
+ end
33
+
34
+ return false if conditions.size == 1
35
+
36
+ conditions[0] = conditions[0].join(' and ')
37
+ conditions
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+ module Wice
3
+ module Columns #:nodoc:
4
+ module CommonJsDateDatetimeMixin #:nodoc:
5
+
6
+ def prepare #:nodoc:
7
+ query, _, @name1, @dom_id = form_parameter_name_id_and_query(fr: '')
8
+ query2, _, @name2, @dom_id2 = form_parameter_name_id_and_query(to: '')
9
+
10
+ @queris_ids = [[query, @dom_id], [query2, @dom_id2]]
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ module Wice
3
+ module Columns #:nodoc:
4
+ module CommonRailsDateDatetimeConditionsGeneratorMixin #:nodoc:
5
+
6
+ def generate_conditions(table_alias, opts) #:nodoc:
7
+ conditions = [[]]
8
+ if opts[:fr]
9
+ conditions[0] << " #{@column_wrapper.alias_or_table_name(table_alias)}.#{@column_wrapper.name} >= ? "
10
+ conditions << opts[:fr].to_date
11
+ end
12
+
13
+ if opts[:to]
14
+ conditions[0] << " #{@column_wrapper.alias_or_table_name(table_alias)}.#{@column_wrapper.name} <= ? "
15
+ conditions << (opts[:to].to_date + 1)
16
+ end
17
+
18
+ return false if conditions.size == 1
19
+
20
+ conditions[0] = conditions[0].join(' and ')
21
+ conditions
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ module Wice
3
+ module Columns #:nodoc:
4
+ module CommonStandardDateDatetimeMixin #:nodoc:
5
+
6
+ def prepare #:nodoc:
7
+ x = lambda do|sym|
8
+ chunk_names.map do|datetime_chunk_name|
9
+ triple = form_parameter_name_id_and_query(sym => { datetime_chunk_name => '' })
10
+ [triple[0], triple[3]]
11
+ end
12
+ end
13
+
14
+ @queris_ids = x.call(:fr) + x.call(:to)
15
+
16
+ _, _, @name1, _ = form_parameter_name_id_and_query(fr: '')
17
+ _, _, @name2, _ = form_parameter_name_id_and_query(to: '')
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+ module Wice
3
+ class GridOutputBuffer < String #:nodoc:
4
+
5
+ # defines behavior for rendering nonexistent filters.
6
+ # If return_empty_strings_for_nonexistent_filters is true, a call to render a non existent filter will raise an exception
7
+ # If return_empty_strings_for_nonexistent_filters is false (CSV mode), no exception will be raised.
8
+ attr_accessor :return_empty_strings_for_nonexistent_filters
9
+
10
+ # initializes a grid output buffer
11
+ def initialize(*attrs)
12
+ super(*attrs)
13
+ @filters = HashWithIndifferentAccess.new
14
+ end
15
+
16
+ # returns HTML code the grid
17
+ def to_s
18
+ super.html_safe
19
+ end
20
+
21
+ # stores HTML code for a detached filter
22
+ def add_filter(detach_with_id, filter_code)
23
+ fail WiceGridException.new("Detached ID #{detach_with_id} is already used!") if @filters.key? detach_with_id
24
+ @filters[detach_with_id] = filter_code
25
+ end
26
+
27
+ # returns HTML code for a detached filter
28
+ def filter_for(detach_with_id)
29
+ unless @filters.key? detach_with_id
30
+ if @return_empty_strings_for_nonexistent_filters
31
+ return ''
32
+ else
33
+ fail WiceGridException.new("No filter with Detached ID '#{detach_with_id}'!")
34
+ end
35
+ end
36
+
37
+ unless @filters[detach_with_id]
38
+ fail WiceGridException.new("Filter with Detached ID '#{detach_with_id}' has already been requested once! There cannot be two instances of the same filter on one page")
39
+ end
40
+
41
+ res = @filters[detach_with_id]
42
+ @filters[detach_with_id] = false
43
+ res
44
+ end
45
+
46
+ # returns HTML code for a detached filter
47
+ alias_method :[], :filter_for
48
+ end
49
+ end
@@ -0,0 +1,609 @@
1
+ # encoding: utf-8
2
+ module Wice
3
+
4
+ # Instance of `GridRenderer` is injected into the top level block of the `grid` helper.
5
+ # `g.column`, `g.action_column` are all examples of methods of `GridRenderer`
6
+ class GridRenderer
7
+ include ActionView::Helpers::TagHelper
8
+ include ActionView::Helpers::CaptureHelper
9
+ include ActionView::Helpers::TextHelper
10
+ include ActionView::Helpers::AssetTagHelper
11
+ include ActionView::Helpers::JavaScriptHelper
12
+
13
+ # a Proc object for the after_row block
14
+ attr_reader :after_row_handler
15
+
16
+ # a Proc object for the before_row block
17
+ attr_reader :before_row_handler
18
+
19
+ # a Proc object for the replace_row block
20
+ attr_reader :replace_row_handler
21
+
22
+ # Configuration or a Proc object for the blank_slate block
23
+ attr_reader :blank_slate_handler
24
+
25
+ # a Proc object which returns contents of the last row
26
+ attr_reader :last_row_handler
27
+
28
+ # reference to the WiceGrid instance
29
+ attr_reader :grid
30
+
31
+ # Contents of <caption></caption>
32
+ attr_reader :kaption
33
+
34
+ # HTTP parameter for the order field
35
+ ORDER_PARAMETER_NAME = 'order'
36
+
37
+ # HTTP parameter for the order direction (asc/desc)
38
+ ORDER_DIRECTION_PARAMETER_NAME = 'order_direction'
39
+
40
+ def initialize(grid, view) #:nodoc:
41
+ @grid = grid
42
+ @grid.renderer = self
43
+ @columns = []
44
+ @columns_table = {}
45
+ @action_column_present = false
46
+ @view = view
47
+ end
48
+
49
+ def config #:nodoc:
50
+ @view.config
51
+ end
52
+
53
+ def controller #:nodoc:
54
+ @view.controller
55
+ end
56
+
57
+ def add_column(vc) #:nodoc:
58
+ @columns_table[vc.fully_qualified_attribute_name] = vc if vc.attribute
59
+ @columns << vc
60
+ end
61
+
62
+ def [](k) #:nodoc:
63
+ @columns_table[k]
64
+ end
65
+
66
+ def number_of_columns(filter = nil) #:nodoc:
67
+ filter_columns(filter).size
68
+ end
69
+
70
+ def each_column_label(filter = nil) #:nodoc:
71
+ filter_columns(filter).each { |col| yield col.name }
72
+ end
73
+
74
+ def column_labels(filter = nil) #:nodoc:
75
+ filter_columns(filter).collect(&:name)
76
+ end
77
+
78
+ def each_column(filter = nil) #:nodoc:
79
+ filter_columns(filter).each { |col| yield col }
80
+ end
81
+
82
+ def each_column_aware_of_one_last_one(filter = nil) #:nodoc:
83
+ cols = filter_columns(filter)
84
+ cols[0..-2].each { |col| yield col, false }
85
+ yield cols.last, true
86
+ end
87
+
88
+ def last_column_for_html #:nodoc:
89
+ filter_columns(:in_html).last
90
+ end
91
+
92
+ def select_for(filter) #:nodoc:
93
+ filter_columns(filter).select { |col| yield col }
94
+ end
95
+
96
+ def find_one_for(filter) #:nodoc:
97
+ filter_columns(filter).find { |col| yield col }
98
+ end
99
+
100
+ def each_column_with_attribute #:nodoc:
101
+ @columns.select(&:attribute).each { |col| yield col }
102
+ end
103
+
104
+ alias_method :each, :each_column
105
+ include Enumerable
106
+
107
+ def csv_export_icon #:nodoc:
108
+ content_tag(
109
+ :div,
110
+ content_tag(:i, '', class: 'fa fa-file-excel-o'),
111
+ title: NlMessage['csv_export_tooltip'],
112
+ class: 'clickable export-to-csv-button'
113
+ )
114
+ end
115
+
116
+ def pagination_panel(number_of_columns, hide_csv_button) #:nodoc:
117
+ panel = yield
118
+
119
+ render_csv_button = @grid.export_to_csv_enabled && !hide_csv_button
120
+
121
+ if panel.nil?
122
+ if render_csv_button
123
+ "<tr><td colspan=\"#{number_of_columns}\"></td><td>#{csv_export_icon}</td></tr>"
124
+ else
125
+ ''
126
+ end
127
+ else
128
+ if render_csv_button
129
+ "<tr><td colspan=\"#{number_of_columns}\">#{panel}</td><td>#{csv_export_icon}</td></tr>"
130
+ else
131
+ "<tr><td colspan=\"#{number_of_columns + 1}\">#{panel}</td></tr>"
132
+ end
133
+ end
134
+ end
135
+
136
+ # Takes one argument and adds the <caption></caption> tag to the table with the
137
+ # argument value as the contents of <caption>.
138
+ def caption(kaption)
139
+ @kaption = kaption
140
+ end
141
+
142
+ # Adds a column with checkboxes for each record. Useful for actions with multiple records, for example, deleting
143
+ # selected records. Please note that +action_column+ only creates the checkboxes and the 'Select All' and
144
+ # 'Deselect All' buttons, and the form itelf as well as processing the parameters should be taken care of
145
+ # by the application code.
146
+ #
147
+ # * <tt>:param_name</tt> - The name of the HTTP parameter.
148
+ # The complete HTTP parameter is <tt>"#{grid_name}[#{param_name}][]"</tt>.
149
+ # The default param_name is 'selected'.
150
+ # * <tt>:html</tt> - a hash of HTML attributes to be included into the <tt>td</tt> tag.
151
+ # * <tt>:select_all_buttons</tt> - show/hide buttons 'Select All' and 'Deselect All' in the column header.
152
+ # The default is +true+.
153
+ # * <tt>:object_property</tt> - a method used to obtain the value for the HTTP parameter. The default is +id+.
154
+ # * <tt>:html_check_box</tt> - can be used to switch from a real check box to two images. The default is +true+.
155
+ #
156
+ # You can hide a certain action checkbox if you add the usual block to +g.action_column+, just like with the
157
+ # +g.column+ definition. If the block returns +nil+ or +false+ no checkbox will be rendered.
158
+
159
+ def action_column(opts = {}, &block)
160
+ if @action_column_present
161
+ fail Wice::WiceGridException.new('There can be only one action column in a WiceGrid')
162
+ end
163
+
164
+ options = {
165
+ param_name: :selected,
166
+ html: {},
167
+ select_all_buttons: true,
168
+ object_property: :id,
169
+ html_check_box: true
170
+ }
171
+
172
+ opts.assert_valid_keys(options.keys)
173
+ options.merge!(opts)
174
+ @action_column_present = true
175
+ column_processor_klass = Columns.get_view_column_processor(:action)
176
+
177
+ @columns << column_processor_klass.new(
178
+ @grid,
179
+ options[:html],
180
+ options[:param_name],
181
+ options[:select_all_buttons],
182
+ options[:object_property],
183
+ options[:html_check_box],
184
+ @view,
185
+ block
186
+ )
187
+ end
188
+
189
+ # Defines everything related to a column in a grid - column name, filtering, rendering cells, etc.
190
+ #
191
+ # +column+ is only used inside the block of the +grid+ method. See documentation for the +grid+ method for more details.
192
+ #
193
+ # The only parameter is a hash of options. None of them is optional. If no options are supplied, the result will be a
194
+ # column with no name, no filtering and no sorting.
195
+ #
196
+ # * <tt>:name</tt> - Name of the column.
197
+ # * <tt>:html</tt> - a hash of HTML attributes to be included into the <tt>td</tt> tag.
198
+ # * <tt>:class</tt> - a shortcut for <tt>html: {class: 'css_class'}</tt>
199
+ # * <tt>:attribute</tt> - name of a database column (which normally correspond to a model attribute with the
200
+ # same name). By default the field is assumed to belong to the default table (see documentation for the
201
+ # +initialize_grid+ method). Parameter <tt>:assoc</tt> (association) allows to specify another joined table. Presence of
202
+ # this parameter
203
+ # * adds sorting capabilities by this field
204
+ # * automatically creates a filter based on the type of the field unless parameter <tt>:filter</tt> is set to false.
205
+ # The following filters exist for the following types:
206
+ # * <tt>string</tt> - a text field
207
+ # * <tt>integer</tt> and <tt>float</tt> - two text fields to specify the range. Both limits or only one
208
+ # can be specified
209
+ # * <tt>boolean</tt> - a dropdown list with 'yes', 'no', or '-'. These labels can be changed in
210
+ # <tt>lib/wice_grid_config.rb</tt>.
211
+ # * <tt>date</tt> - two sets of standard date dropdown lists so specify the time range.
212
+ # * <tt>datetime</tt> - two sets of standard datetime dropdown lists so specify the time range. This filter
213
+ # is far from being user-friendly due to the number of dropdown lists.
214
+ # * <tt>:filter</tt> - Disables filters when set to false.
215
+ # This is needed if sorting is required while filters are not.
216
+ # * <tt>:filter_type</tt> - Using a column filter different from the default filter chosen automatically based on the
217
+ # data type or the <tt>:custom_filter</tt> argument. See <tt>lib/columns/column_processor_index.rb</tt> for the
218
+ # list of available filters.
219
+ # * <tt>:ordering</tt> - Enable/disable ordering links in the column titles. The default is +true+
220
+ # (i.e. if <tt>:attribute</tt> is defined, ordering is enabled)
221
+ # * <tt>:assoc</tt> - Name of the model association. <tt>:attribute</tt> belongs to the table joined via this association.
222
+ # * <tt>:table_alias</tt> - In case there are two joined assocations both referring to the same table, ActiveRecord
223
+ # constructs a query where the second join provides an alias for the joined table. Setting <tt>:table_alias</tt>
224
+ # to this alias will enable WiceGrid to order and filter by columns belonging to different associatiations but
225
+ # originating from the same table without confusion. See README for an example.
226
+ # * <tt>:custom_filter</tt> - Allows to construct a custom dropdown filter. Depending on the value of
227
+ # <tt>:custom_filter</tt> different modes are available:
228
+ # * array of strings and/or numbers - this is a direct definition of possible values of the dropdown.
229
+ # Every item will be used both as the value of the select option and as its label.
230
+ # * Array of two-element arrays - Every first item of the two-element array is used for the label of the select option
231
+ # while the second element is the value of the select option
232
+ # * Hash - The keys of the hash become the labels of the generated dropdown list,
233
+ # while the values will be values of options of the dropdown list:
234
+ # * <tt>:auto</tt> - a powerful option which populates the dropdown list with all unique values of the field specified by
235
+ # <tt>:attribute</tt> and <tt>:model</tt>.
236
+ # <tt>:attribute</tt> throughout all pages. In other words, this runs an SQL query without +offset+ and +limit+
237
+ # clauses and with <tt>distinct(table.field)</tt> instead of <tt>distinct(*)</tt>
238
+ # * any other symbol name (method name) - The dropdown list is populated by all unique value returned by the
239
+ # method with this name sent to <em>all</em> ActiveRecord objects throughout all pages. The main difference
240
+ # from <tt>:auto</tt> is that this method does not have to be a field in the result set, it is just some
241
+ # value computed in the method after the database call and ActiveRecord instantiation.
242
+ #
243
+ # But here lies the major drawback - this mode requires additional query without +offset+ and +limit+
244
+ # clauses to instantiate _all_ ActiveRecord objects, and performance-wise it brings all the advantages
245
+ # of pagination to nothing. Thus, memory- and performance-wise this can be really bad for some queries
246
+ # and tables and should be used with care.
247
+ #
248
+ # If the method returns a atomic value like a string or an integer, it is used for both the value and the
249
+ # label of the select option element. However, if the retuned value is a two element array, the first element
250
+ # is used for the option label and the second - for the value.
251
+ # Read more in README, section 'Custom dropdown filters'
252
+ # * An array of symbols (method names) - similar to the mode with a single symbol name. The first method name
253
+ # is sent to the ActiveRecord object if it responds to this method, the second method name is sent to the
254
+ # returned value unless it is +nil+, and so on. In other words, a single symbol mode is a
255
+ # case of an array of symbols where the array contains just one element. Thus the warning about the single method name
256
+ # mode applies here as well.
257
+ #
258
+ # If the last method returns a atomic value like a string or an integer, it is used for both the value and the
259
+ # label of the select option element.
260
+ # However, if the retuned value is a two element array, the first element is used for the option label and the
261
+ # second - for the value.
262
+ # Read more in README, section 'Custom dropdown filters'
263
+ # * <tt>:boolean_filter_true_label</tt> - overrides the label for <tt>true</tt> in the boolean filter (<tt>wice_grid.boolean_filter_true_label</tt> in <tt>wice_grid.yml</tt>).
264
+ # * <tt>:boolean_filter_false_label</tt> - overrides the label for <tt>false</tt> in the boolean filter (<tt>wice_grid.boolean_filter_false_label</tt> in <tt>wice_grid.yml</tt>).
265
+ # * <tt>:allow_multiple_selection</tt> - enables or disables switching between single and multiple selection modes for
266
+ # custom dropdown boxes. +true+ by default (see +ALLOW_MULTIPLE_SELECTION+ in the configuration file).
267
+ # * <tt>:filter_all_label</tt> - overrides the default value for <tt>BOOLEAN_FILTER_FALSE_LABEL</tt> ('<tt>--</tt>')
268
+ # in the config. Has effect in a column with a boolean filter _or_ a custom filter.
269
+ # * <tt>:detach_with_id</tt> - allows to detach the filter and render it after or before the grid with the
270
+ # +grid_filter+ helper. The value is an arbitrary unique identifier
271
+ # of the filter. Read section 'Detached Filters' in README for details.
272
+ # Has effect in a column with a boolean filter _or_ a custom filter.
273
+ # * <tt>:in_csv</tt> - When CSV export is enabled, all columns are included into the export. Setting <tt>:in_csv</tt>
274
+ # to false will prohibit the column from inclusion into the export.
275
+ # * <tt>:in_html</tt> - When CSV export is enabled and it is needed to use a column for CSV export only and ignore it
276
+ # in HTML, set this parameter to false.
277
+ # * <tt>:negation</tt> - turn on/off the negation checkbox in string filters
278
+ # * <tt>:auto_reload</tt> - a boolean value specifying if a change in a filter triggers reloading of the grid. Works with all
279
+ # filter types including the JS calendar, the only exception is the standard Rails date/datetime filters.
280
+ # The default is false. (see +AUTO_RELOAD+ in the configuration file).
281
+ #
282
+ # The block parameter is an ActiveRecord instance. This block is called for every ActiveRecord shown, and the return
283
+ # value of the block is a string which becomes the contents of one table cell, or an array of two elements where
284
+ # the first element is the cell contents and the second is a hash of HTML attributes to be added for the <tt><td></tt>
285
+ # tag of the current cell.
286
+ #
287
+ # In case of an array output, please note that if you need to define HTML attributes for all <tt><td></tt>'s in a
288
+ # column, use +html+. Also note that if the method returns a hash with a <tt>:class</tt> or <tt>'class'</tt>
289
+ # element, it will not overwrite the class defined in +html+, or classes added by the grid itself
290
+ # (+active-filter+ and +sorted+), instead they will be all concatenated:
291
+ # <tt><td class="sorted user_class_for_columns user_class_for_this_specific_cell"></tt>
292
+ #
293
+ # It is up to the developer to make sure that what in rendered in column cells
294
+ # corresponds to sorting and filtering specified by parameters <tt>:attribute</tt> and <tt>:model</tt>.
295
+
296
+ def column(opts = {}, &block)
297
+ options = {
298
+ allow_multiple_selection: ConfigurationProvider.value_for(:ALLOW_MULTIPLE_SELECTION),
299
+ assoc: nil,
300
+ attribute: nil,
301
+ auto_reload: ConfigurationProvider.value_for(:AUTO_RELOAD),
302
+ boolean_filter_false_label: NlMessage['boolean_filter_false_label'],
303
+ boolean_filter_true_label: NlMessage['boolean_filter_true_label'],
304
+ class: nil,
305
+ custom_filter: nil,
306
+ detach_with_id: nil,
307
+ filter: true,
308
+ filter_all_label: ConfigurationProvider.value_for(:CUSTOM_FILTER_ALL_LABEL),
309
+ filter_type: nil,
310
+ html: {},
311
+ in_csv: true,
312
+ in_html: true,
313
+ model: nil, # will throw an exception with instructions
314
+ name: '',
315
+ negation: ConfigurationProvider.value_for(:NEGATION_IN_STRING_FILTERS),
316
+ ordering: true,
317
+ table_alias: nil
318
+ }
319
+
320
+ opts.assert_valid_keys(options.keys)
321
+ options.merge!(opts)
322
+
323
+ assocs = nil
324
+
325
+ if options[:model]
326
+ fail WiceGridArgumentError.new('Instead of specifying a model of a joined table please use assoc: :name_of_association')
327
+ end
328
+
329
+ unless options[:assoc].nil?
330
+
331
+ unless options[:assoc].is_a?(Symbol) ||
332
+ (options[:assoc].is_a?(Array) && ! options[:assoc].empty? && options[:assoc].all?{ |assoc| assoc.is_a?(Symbol)})
333
+
334
+ fail WiceGridArgumentError.new('Option :assoc can only be a symbol or an array of symbols')
335
+ end
336
+
337
+ assocs = options[:assoc].is_a?(Symbol) ? [options[:assoc]] : options[:assoc]
338
+
339
+ options[:model] = get_model_from_associations(@grid.klass, assocs)
340
+ end
341
+
342
+ if options[:attribute].nil? && options[:model]
343
+ fail WiceGridArgumentError.new('Option :assoc is only used together with :attribute')
344
+ end
345
+
346
+ if options[:attribute] && options[:attribute].index('.')
347
+ fail WiceGridArgumentError.new("Invalid attribute name #{options[:attribute]}. An attribute name must not contain a table name!")
348
+ end
349
+
350
+
351
+ if options[:class]
352
+ options[:html] ||= {}
353
+ Wice::WgHash.add_or_append_class_value!(options[:html], options[:class])
354
+ options.delete(:class)
355
+ end
356
+
357
+ if block.nil?
358
+ if !options[:attribute].blank?
359
+ if assocs.nil?
360
+ block = ->(obj) { obj.send(options[:attribute]) }
361
+ else
362
+ messages = assocs + [ options[:attribute] ]
363
+ block = ->(obj) { obj.deep_send(*messages) }
364
+ end
365
+ else
366
+ fail WiceGridArgumentError.new(
367
+ 'Missing column block without attribute defined. You can only omit the block if attribute is present.')
368
+ end
369
+ end
370
+
371
+ klass = Columns::ViewColumn
372
+ if options[:attribute] &&
373
+ col_type_and_table_name = @grid.declare_column(
374
+ column_name: options[:attribute],
375
+ model: options[:model],
376
+ custom_filter_active: options[:custom_filter],
377
+ table_alias: options[:table_alias],
378
+ filter_type: options[:filter_type],
379
+ assocs: assocs
380
+ )
381
+
382
+ # [ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::Column, String, Boolean]
383
+ db_column, table_name, main_table = col_type_and_table_name
384
+ col_type = db_column.type
385
+
386
+ if options[:custom_filter]
387
+
388
+ custom_filter = if options[:custom_filter] == :auto
389
+ -> { @grid.distinct_values_for_column(db_column) } # Thank God Ruby has higher order functions!!!
390
+
391
+ elsif options[:custom_filter].class == Symbol
392
+ -> { @grid.distinct_values_for_column_in_resultset([options[:custom_filter]]) }
393
+
394
+ elsif options[:custom_filter].class == Hash
395
+ options[:custom_filter].keys
396
+
397
+ options[:custom_filter].to_a
398
+
399
+ elsif options[:custom_filter].class == Array
400
+ if options[:custom_filter].empty?
401
+ []
402
+ elsif Wice::WgEnumerable.all_items_are_of_class(options[:custom_filter], Symbol)
403
+ -> { @grid.distinct_values_for_column_in_resultset(options[:custom_filter]) }
404
+
405
+ elsif Wice::WgEnumerable.all_items_are_of_class(options[:custom_filter], String) || WgEnumerable.all_items_are_of_class(options[:custom_filter], Numeric)
406
+ options[:custom_filter].map { |i| [i, i] }
407
+
408
+ elsif Wice::WgEnumerable.all_items_are_of_class(options[:custom_filter], Array)
409
+ options[:custom_filter]
410
+ else
411
+ fail WiceGridArgumentError.new(
412
+ ':custom_filter can equal :auto, an array of string and/or numbers (direct values for the dropdown), ' \
413
+ 'a homogeneous array of symbols (a sequence of methods to send to AR objects in the result set to ' \
414
+ 'retrieve unique values for the dropdown), a Symbol (a shortcut for a one member array of symbols), ' \
415
+ 'a hash where keys are labels and values are values for the dropdown option, or an array of two-item arrays, ' \
416
+ 'each of which contains the label (first element) and the value (second element) for a dropdown option'
417
+ )
418
+ end
419
+ end
420
+
421
+ klass = Columns.get_view_column_processor(:custom)
422
+ elsif options[:filter_type]
423
+ klass = Columns.get_view_column_processor(options[:filter_type])
424
+ else
425
+
426
+ col_type = case col_type
427
+ when :date
428
+ Wice::Defaults::DEFAULT_FILTER_FOR_DATE
429
+ when :datetime
430
+ Wice::Defaults::DEFAULT_FILTER_FOR_DATETIME
431
+ when :timestamp
432
+ Wice::Defaults::DEFAULT_FILTER_FOR_DATETIME
433
+ else
434
+ col_type
435
+ end
436
+
437
+ klass = Columns.get_view_column_processor(col_type)
438
+ end # custom_filter
439
+
440
+ end # attribute
441
+
442
+ vc = klass.new(block, options, @grid, table_name, main_table, custom_filter, @view)
443
+
444
+ vc.negation = options[:negation] if vc.respond_to? :negation=
445
+
446
+ vc.filter_all_label = options[:filter_all_label] if vc.is_a?(Columns.get_view_column_processor(:custom))
447
+ if vc.is_a?(Columns.get_view_column_processor(:boolean))
448
+ vc.boolean_filter_true_label = options[:boolean_filter_true_label]
449
+ vc.boolean_filter_false_label = options[:boolean_filter_false_label]
450
+ end
451
+ add_column(vc)
452
+ end
453
+
454
+ def get_model_from_associations(model, assocs) # :nodoc:
455
+ if assocs.empty?
456
+ model
457
+ else
458
+ head = assocs[0]
459
+ tail = assocs[1..-1]
460
+
461
+ if reflection = model.reflect_on_association(head)
462
+ next_model = reflection.klass
463
+ get_model_from_associations(next_model, tail)
464
+ else
465
+ fail WiceGridArgumentError.new("Association #{head} not found in #{model}")
466
+ end
467
+ end
468
+ end
469
+
470
+ # Optional method inside the +grid+ block, to which every ActiveRecord instance is injected, just like +column+.
471
+ # Unlike +column+, it returns a hash which will be used as HTML attributes for the row with the given ActiveRecord instance.
472
+ #
473
+ # Note that if the method returns a hash with a <tt>:class</tt> or <tt>'class'</tt> element, it will not overwrite
474
+ # classes +even+ and +odd+, instead they will be concatenated: <tt><tr class="even highlighted_row_class_name"></tt>
475
+ def row_attributes(&block)
476
+ @row_attributes_handler = block
477
+ end
478
+
479
+ # Can be used to add HTML code (another row, for example) right after each grid row.
480
+ # Nothing is added if the block return +false+ or +nil+.
481
+ def after_row(&block)
482
+ @after_row_handler = block
483
+ end
484
+
485
+ # Can be used to add HTML code (another row, for example) right before each grid row.
486
+ # Nothing is added if the block return +false+ or +nil+.
487
+ def before_row(&block)
488
+ @before_row_handler = block
489
+ end
490
+
491
+ # Can be used to replace the HTML code (for example to make a multi-column spanning row) of a row.
492
+ # Nothing is replaced if the block return +false+ or +nil+.
493
+ def replace_row(&block)
494
+ @replace_row_handler = block
495
+ end
496
+
497
+ # Can be used to add HTML code (calculation results, for example) after all rows.
498
+ # Nothing is added if the block return +false+ or +nil+.
499
+ def last_row(&block)
500
+ @last_row_handler = block
501
+ end
502
+
503
+ # The output of the block submitted to +blank_slate+ is rendered instead of the whole grid if no filters are active
504
+ # and there are no records to render.
505
+ # In addition to the block style two other variants are accepted:
506
+ # * <tt>g.blank_slate "some text to be rendered"</tt>
507
+ # * <tt>g.blank_slate partial: "partial_name"</tt>
508
+ def blank_slate(opts = nil, &block)
509
+ if (opts.is_a?(Hash) && opts.key?(:partial) && block.nil?) || (opts.is_a?(String) && block.nil?)
510
+ @blank_slate_handler = opts
511
+ elsif opts.nil? && block
512
+ @blank_slate_handler = block
513
+ else
514
+ fail WiceGridArgumentError.new("blank_slate accepts only a string, a block, or template: 'path_to_template' ")
515
+ end
516
+ end
517
+
518
+ def get_row_attributes(ar_object) #:nodoc:
519
+ if @row_attributes_handler
520
+ row_attributes = @row_attributes_handler.call(ar_object)
521
+ row_attributes = {} if row_attributes.blank?
522
+ unless row_attributes.is_a?(Hash)
523
+ fail WiceGridArgumentError.new("row_attributes block must return a hash containing HTML attributes. The returned value is #{row_attributes.inspect}")
524
+ end
525
+ row_attributes
526
+ else
527
+ {}
528
+ end
529
+ end
530
+
531
+ def no_filter_needed? #:nodoc:
532
+ !@columns.inject(false) { |a, b| a || b.filter_shown? }
533
+ end
534
+
535
+ def no_filter_needed_in_main_table? #:nodoc:
536
+ !@columns.inject(false) { |a, b| a || b.filter_shown_in_main_table? }
537
+ end
538
+
539
+ def base_link_for_filter(controller, extra_parameters = {}) #:nodoc:
540
+ new_params = Wice::WgHash.deep_clone controller.params
541
+ new_params.merge!(extra_parameters)
542
+
543
+ if new_params[@grid.name]
544
+ new_params[@grid.name].delete(:page) # we reset paging here
545
+ new_params[@grid.name].delete(:f) # no filter for the base url
546
+ new_params[@grid.name].delete(:foc) # nullify the focus
547
+ new_params[@grid.name].delete(:q) # and no request for the saved query
548
+ end
549
+
550
+ new_params[:only_path] = false
551
+ base_link_with_pp_info = omit_empty_query controller.url_for(new_params)
552
+
553
+ if new_params[@grid.name]
554
+ new_params[@grid.name].delete(:pp) # and reset back to pagination if show all mode is on
555
+ end
556
+ [base_link_with_pp_info, omit_empty_query(controller.url_for(new_params))]
557
+ end
558
+
559
+ def link_for_export(controller, format, extra_parameters = {}) #:nodoc:
560
+ new_params = Wice::WgHash.deep_clone controller.params
561
+ new_params.merge!(extra_parameters)
562
+
563
+ new_params[@grid.name] = {} unless new_params[@grid.name]
564
+ new_params[@grid.name][:export] = format
565
+
566
+ new_params[:only_path] = false
567
+ controller.url_for(new_params)
568
+ end
569
+
570
+ def column_link(column, direction, params, extra_parameters = {}) #:nodoc:
571
+ column_attribute_name = if column.attribute.index('.') || column.main_table
572
+ column.attribute
573
+ else
574
+ column.table_alias_or_table_name + '.' + column.attribute
575
+ end
576
+
577
+ query_params = { @grid.name => {
578
+ ORDER_PARAMETER_NAME => column_attribute_name,
579
+ ORDER_DIRECTION_PARAMETER_NAME => direction
580
+ } }
581
+
582
+ cleaned_params = Wice::WgHash.deep_clone params
583
+ cleaned_params.merge!(extra_parameters)
584
+
585
+ cleaned_params.delete(:controller)
586
+ cleaned_params.delete(:action)
587
+
588
+ query_params = Wice::WgHash.rec_merge(cleaned_params, query_params)
589
+
590
+ '?' + query_params.to_query
591
+ end
592
+
593
+ protected
594
+
595
+ def filter_columns(method_name = nil) #:nodoc:
596
+ method_name ? @columns.select(&method_name) : @columns
597
+ end
598
+
599
+ def omit_empty_query(url) #:nodoc:
600
+ # /foo? --> /foo
601
+ # /foo?grid= --> /foo
602
+ # /foo?grid=&page=1 --> /foo?page=1
603
+ # /foo?grid=some_value --> /foo?grid=some_value
604
+
605
+ empty_hash_rx = Regexp.new "([&?])#{Regexp.escape @grid.name}=([&?]|$)" # c.f., issue #140
606
+ url.gsub(empty_hash_rx, '\1').gsub(/\?+$/, '')
607
+ end
608
+ end
609
+ end