wice_grid_mongoid 0.5.6

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 (67) hide show
  1. data/.gitignore +8 -0
  2. data/CHANGELOG +409 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +1172 -0
  5. data/Rakefile +42 -0
  6. data/SAVED_QUERIES_HOWTO.rdoc +123 -0
  7. data/VERSION +1 -0
  8. data/generators/common_templates/icons/arrow_down.gif +0 -0
  9. data/generators/common_templates/icons/arrow_up.gif +0 -0
  10. data/generators/common_templates/icons/calendar_view_month.png +0 -0
  11. data/generators/common_templates/icons/delete.png +0 -0
  12. data/generators/common_templates/icons/expand.png +0 -0
  13. data/generators/common_templates/icons/page_white_excel.png +0 -0
  14. data/generators/common_templates/icons/page_white_find.png +0 -0
  15. data/generators/common_templates/icons/table.png +0 -0
  16. data/generators/common_templates/icons/table_refresh.png +0 -0
  17. data/generators/common_templates/icons/tick_all.png +0 -0
  18. data/generators/common_templates/icons/untick_all.png +0 -0
  19. data/generators/common_templates/initializers/wice_grid_config.rb +215 -0
  20. data/generators/common_templates/locales/wice_grid.yml +269 -0
  21. data/generators/common_templates/stylesheets/wice_grid.css +173 -0
  22. data/generators/wice_grid_assets_jquery/templates/USAGE +6 -0
  23. data/generators/wice_grid_assets_jquery/templates/javascripts/wice_grid_jquery.js +161 -0
  24. data/generators/wice_grid_assets_jquery/wice_grid_assets_jquery_generator.rb +35 -0
  25. data/generators/wice_grid_assets_prototype/USAGE +8 -0
  26. data/generators/wice_grid_assets_prototype/templates/javascripts/calendarview.js +1168 -0
  27. data/generators/wice_grid_assets_prototype/templates/javascripts/wice_grid_prototype.js +153 -0
  28. data/generators/wice_grid_assets_prototype/templates/stylesheets/calendarview.css +107 -0
  29. data/generators/wice_grid_assets_prototype/wice_grid_assets_prototype_generator.rb +37 -0
  30. data/init.rb +1 -0
  31. data/install.rb +1 -0
  32. data/lib/grid_output_buffer.rb +52 -0
  33. data/lib/grid_renderer.rb +531 -0
  34. data/lib/helpers/js_calendar_helpers.rb +188 -0
  35. data/lib/helpers/wice_grid_misc_view_helpers.rb +113 -0
  36. data/lib/helpers/wice_grid_serialized_queries_view_helpers.rb +82 -0
  37. data/lib/helpers/wice_grid_view_helpers.rb +781 -0
  38. data/lib/js_adaptors/jquery_adaptor.rb +145 -0
  39. data/lib/js_adaptors/js_adaptor.rb +12 -0
  40. data/lib/js_adaptors/prototype_adaptor.rb +168 -0
  41. data/lib/table_column_matrix.rb +51 -0
  42. data/lib/view_columns.rb +469 -0
  43. data/lib/views/create.rjs +13 -0
  44. data/lib/views/delete.rjs +12 -0
  45. data/lib/wice_grid.rb +809 -0
  46. data/lib/wice_grid_controller.rb +165 -0
  47. data/lib/wice_grid_core_ext.rb +179 -0
  48. data/lib/wice_grid_misc.rb +99 -0
  49. data/lib/wice_grid_serialized_queries_controller.rb +77 -0
  50. data/lib/wice_grid_serialized_query.rb +14 -0
  51. data/lib/wice_grid_spreadsheet.rb +33 -0
  52. data/tasks/wice_grid_tasks.rake +28 -0
  53. data/test/.gitignore +2 -0
  54. data/test/database.yml +21 -0
  55. data/test/schema.rb +33 -0
  56. data/test/test_helper.rb +89 -0
  57. data/test/views/projects_and_people_grid.html.erb +12 -0
  58. data/test/views/projects_and_people_grid_invalid.html.erb +12 -0
  59. data/test/views/simple_projects_grid.html.erb +9 -0
  60. data/test/wice_grid_core_ext_test.rb +183 -0
  61. data/test/wice_grid_functional_test.rb +68 -0
  62. data/test/wice_grid_misc_test.rb +41 -0
  63. data/test/wice_grid_test.rb +42 -0
  64. data/test/wice_grid_view_helper_test.rb +12 -0
  65. data/uninstall.rb +1 -0
  66. data/wice_grid_mongoid.gemspec +111 -0
  67. metadata +141 -0
@@ -0,0 +1,153 @@
1
+ function WiceGridProcessor(name, base_request_for_filter, base_link_for_show_all_records,
2
+ link_for_export, parameter_name_for_query_loading, parameter_name_for_focus, environment){
3
+
4
+ this.checkIfJsFrameworkIsLoaded = function(){
5
+ if (typeof(Prototype) == "undefined"){
6
+ alert("Prototype javascript library not loaded, WiceGrid cannot proceed!")
7
+ }
8
+ }
9
+
10
+
11
+ this.checkIfJsFrameworkIsLoaded();
12
+ this.name = name;
13
+ this.parameter_name_for_query_loading = parameter_name_for_query_loading;
14
+ this.parameter_name_for_focus = parameter_name_for_focus;
15
+ this.base_request_for_filter = base_request_for_filter;
16
+ this.base_link_for_show_all_records = base_link_for_show_all_records;
17
+ this.link_for_export = link_for_export;
18
+ this.filter_declarations = new Array();
19
+ this.environment = environment;
20
+
21
+ this.toString = function(){
22
+ return "<WiceGridProcessor instance for grid '" + this.name + "'>";
23
+ }
24
+
25
+
26
+ this.process = function(dom_id_to_focus){
27
+ window.location = this.build_url_with_params(dom_id_to_focus);
28
+ }
29
+
30
+ this.reload_page_for_given_grid_state = function(grid_state){
31
+ var request_path = this.grid_state_to_request(grid_state);
32
+ window.location = this.append_to_url(this.base_link_for_show_all_records, request_path);
33
+ }
34
+
35
+ this.load_query = function(query_id){
36
+ var request = this.append_to_url(this.build_url_with_params(),
37
+ (this.parameter_name_for_query_loading + encodeURIComponent(query_id)));
38
+ window.location = request;
39
+ }
40
+
41
+ this.save_query = function(query_name, base_path_to_query_controller, grid_state, input_ids){
42
+ if (input_ids instanceof Array) {
43
+ input_ids.each(function(dom_id){
44
+ grid_state.push(['extra[' + dom_id + ']', $F(dom_id)])
45
+ });
46
+ }
47
+
48
+ var request_path = this.grid_state_to_request(grid_state);
49
+
50
+ new Ajax.Request(base_path_to_query_controller, {
51
+ asynchronous:true,
52
+ evalScripts:true,
53
+ parameters: request_path + '&query_name=' + encodeURIComponent(query_name)
54
+ })
55
+ }
56
+
57
+ this.grid_state_to_request = function(grid_state){
58
+ return res = grid_state.collect(function(pair){
59
+ return encodeURIComponent(pair[0]) + '=' + encodeURIComponent(pair[1]);
60
+ }).join('&');
61
+ }
62
+
63
+
64
+ this.append_to_url = function(url, str){
65
+ var sep;
66
+ if (url.include('?')){
67
+ if (/[&\?]$/.exec(url)){
68
+ sep = '';
69
+ }else{
70
+ sep = '&';
71
+ }
72
+ }else{
73
+ sep = '?';
74
+ }
75
+ return url + sep + str;
76
+ }
77
+
78
+
79
+ this.build_url_with_params = function(dom_id_to_focus){
80
+ var results = new Array();
81
+ this.filter_declarations.each(function(filter_declaration){
82
+ param = this.read_values_and_form_query_string(
83
+ filter_declaration.filter_name, filter_declaration.detached,
84
+ filter_declaration.templates, filter_declaration.ids);
85
+ if (param && param != ''){
86
+ results.push(param);
87
+ }
88
+ }.bind(this));
89
+ var res = this.base_request_for_filter;
90
+ if ( results.length != 0){
91
+ all_filter_params = results.join('&');
92
+ res = this.append_to_url(res, all_filter_params);
93
+ }
94
+ if (dom_id_to_focus){
95
+ res = this.append_to_url(res, this.parameter_name_for_focus + dom_id_to_focus);
96
+ }
97
+ return res;
98
+ }
99
+
100
+ this.reset = function(){
101
+ window.location = this.base_request_for_filter;
102
+ }
103
+
104
+ this.export_to_csv = function(){
105
+ window.location = this.link_for_export;
106
+ }
107
+
108
+
109
+ this.register = function(func){
110
+ this.filter_declarations.push(func);
111
+ }
112
+
113
+ this.read_values_and_form_query_string = function(filter_name, detached, templates, ids){
114
+ var res = new Array();
115
+ for(i = 0; i < templates.length; i++){
116
+ if($(ids[i]) == null){
117
+ if (this.environment == "development"){
118
+ message = 'WiceGrid: Error reading state of filter "' + filter_name + '". No DOM element with id "' + ids[i] + '" found.'
119
+ if (detached){
120
+ message += 'You have declared "' + filter_name +
121
+ '" as a detached filter but have not output it anywhere in the template. Read documentation about detached filters.'
122
+ }
123
+ alert(message);
124
+ }
125
+ return '';
126
+ }
127
+ var val = $F(ids[i]);
128
+ if (val instanceof Array) {
129
+ for(j = 0; j < val.length; j++){
130
+ if (val[j] && val[j] != "")
131
+ res.push(templates[i] + encodeURIComponent(val[j]));
132
+ }
133
+ } else if (val && ! val.empty()){
134
+ res.push(templates[i] + encodeURIComponent(val));
135
+ }
136
+ }
137
+ return res.join('&');
138
+ }
139
+
140
+ };
141
+
142
+ function toggle_multi_select(select_id, link_obj, expand_label, collapse_label) {
143
+ var select = $(select_id);
144
+ if (select.multiple == true) {
145
+ select.multiple = false;
146
+ link_obj.title = expand_label;
147
+ } else {
148
+ select.multiple = true;
149
+ link_obj.title = collapse_label;
150
+ }
151
+ }
152
+
153
+ WiceGridProcessor._version = '0.4.3';
@@ -0,0 +1,107 @@
1
+
2
+ div.calendar
3
+ {
4
+ font-size: smaller;
5
+ color: #000;
6
+ z-index: 5;
7
+ }
8
+
9
+ div.calendar.popup
10
+ {
11
+ margin-left: -40px;
12
+ margin-top: -100px;
13
+ }
14
+
15
+ div.calendar table
16
+ {
17
+ background-color: #eee;
18
+ border: 1px solid #aaa;
19
+ border-collapse: collapse;
20
+ }
21
+
22
+ div.calendar thead {
23
+ background-color: white;
24
+ }
25
+
26
+ div.calendar td,
27
+ div.calendar th
28
+ {
29
+ padding: 3px;
30
+ text-align: center;
31
+ }
32
+
33
+ div.calendar td.title
34
+ {
35
+ font-weight: bold;
36
+ }
37
+
38
+ div.calendar th
39
+ {
40
+ background: #ddd;
41
+ border-bottom: 1px solid #ccc;
42
+ border-top: 1px solid #ccc;
43
+ font-weight: bold;
44
+ color: #555;
45
+ }
46
+
47
+ div.calendar tr.days td {
48
+ width: 2em;
49
+ color: #555;
50
+ text-align: center;
51
+ cursor: pointer;
52
+ }
53
+
54
+ div.calendar tr.days td:hover,
55
+ div.calendar td.cvbutton:hover
56
+ {
57
+ background-color: #34ABFA;
58
+ cursor: pointer;
59
+ }
60
+
61
+
62
+ div.calendar tr td.closeButton:hover
63
+ {
64
+ background-color: #34ABFA;
65
+ cursor: pointer;
66
+ }
67
+
68
+
69
+ div.calendar tr.days td:active
70
+ div.calendar td.cvbutton:active
71
+ {
72
+ background-color: #cde;
73
+ }
74
+
75
+ div.calendar tr.days td.selected
76
+ {
77
+ font-weight: bold;
78
+ background-color: #fff;
79
+ color: #000;
80
+ }
81
+
82
+ div.calendar tr.days td.today
83
+ {
84
+ font-weight: bold;
85
+ color: #D50000;
86
+ }
87
+
88
+ div.calendar tr.days td.otherDay
89
+ {
90
+ color: #bbb;
91
+ }
92
+
93
+ div.calendar .draggableHandler{
94
+ cursor: move;
95
+ }
96
+
97
+ /* styles for the date_picker Rails plugin */
98
+ span.date_picker a.date_label{
99
+ margin-left: 5px;
100
+ margin-right: 5px;
101
+ text-decoration: none;
102
+ }
103
+ span.date_picker a.date_label:hover{text-decoration: line-through;}
104
+
105
+ span.date_picker span.trigger:hover{
106
+ cursor: pointer;
107
+ }
@@ -0,0 +1,37 @@
1
+ class WiceGridAssetsPrototypeGenerator < Rails::Generator::Base
2
+ def active_js_framework
3
+ 'prototype'
4
+ end
5
+ def inactive_js_framework
6
+ 'jquery'
7
+ end
8
+
9
+ def manifest
10
+ record do |m|
11
+ # wice_grid config
12
+ m.directory "config/initializers"
13
+ m.template "../../common_templates/initializers/wice_grid_config.rb", "config/initializers/wice_grid_config.rb"
14
+
15
+ # wice_grid locales
16
+ m.directory "config/locales"
17
+ m.file "../../common_templates/locales/wice_grid.yml", "config/locales/wice_grid.yml"
18
+
19
+ # wice_grid js & css
20
+ m.file "javascripts/wice_grid_prototype.js", "public/javascripts/wice_grid.js"
21
+ m.file "../../common_templates/stylesheets/wice_grid.css", "public/stylesheets/wice_grid.css"
22
+
23
+ # calendarview js & css
24
+ m.file "javascripts/calendarview.js", "public/javascripts/calendarview.js"
25
+ m.file "stylesheets/calendarview.css", "public/stylesheets/calendarview.css"
26
+
27
+ # images
28
+ m.directory "public/images/icons/grid"
29
+
30
+ %w(arrow_down.gif calendar_view_month.png expand.png page_white_find.png table_refresh.png
31
+ arrow_up.gif delete.png page_white_excel.png table.png tick_all.png untick_all.png ).each do |f|
32
+ m.file "../../common_templates/icons/#{f}", "public/images/icons/grid/#{f}"
33
+ end
34
+
35
+ end
36
+ end
37
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'wice_grid.rb'
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,52 @@
1
+ # encoding: UTF-8
2
+ module Wice
3
+
4
+ class GridOutputBuffer < String #:nodoc:
5
+
6
+ attr_reader :stubborn_output_mode
7
+ attr_accessor :return_empty_strings_for_nonexistent_filters
8
+
9
+ def stubborn_output_mode=(m)
10
+ RAILS_DEFAULT_LOGGER.debug("=== WiceGrid: detached filters are requested, postponing output till the second call of the view helper") if m
11
+ @stubborn_output_mode = m
12
+ end
13
+
14
+ def initialize(*attrs)
15
+ super(*attrs)
16
+ @filters = HashWithIndifferentAccess.new
17
+ @first_output = false
18
+ @stubborn_output_mode = false
19
+ end
20
+
21
+ def to_s
22
+ if @first_output || ! @stubborn_output_mode
23
+ super.html_safe_if_necessary
24
+ else
25
+ @first_output = true
26
+ ''
27
+ end
28
+ end
29
+
30
+ def add_filter(detach_with_id, filter_code)
31
+ raise WiceGridException.new("Detached ID #{detach_with_id} is already used!") if @filters.has_key? detach_with_id
32
+ @filters[detach_with_id] = filter_code
33
+ end
34
+
35
+ def filter_for detach_with_id
36
+ unless @filters.has_key? detach_with_id
37
+ if @return_empty_strings_for_nonexistent_filters
38
+ return ''
39
+ else
40
+ raise WiceGridException.new("No filter with Detached ID '#{detach_with_id}'!")
41
+ end
42
+ end
43
+ raise 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") if @filters[detach_with_id] == false
44
+ res = @filters[detach_with_id]
45
+ @filters[detach_with_id] = false
46
+ return res
47
+ end
48
+
49
+ alias_method :[], :filter_for
50
+
51
+ end
52
+ end
@@ -0,0 +1,531 @@
1
+ # encoding: UTF-8
2
+ module Wice
3
+ class GridRenderer
4
+
5
+ include ActionView::Helpers::TagHelper
6
+ include ActionView::Helpers::CaptureHelper
7
+ include ActionView::Helpers::TextHelper
8
+ include ActionView::Helpers::AssetTagHelper
9
+ include ActionView::Helpers::JavaScriptHelper
10
+ include ::WillPaginate::ViewHelpers
11
+
12
+ attr_reader :page_parameter_name
13
+ attr_reader :after_row_handler
14
+ attr_reader :before_row_handler
15
+ attr_reader :blank_slate_handler
16
+ attr_reader :grid
17
+ attr_accessor :erb_mode
18
+
19
+ attr_accessor :reset_button_present, :submit_button_present, :show_hide_button_present, :csv_export_icon_present
20
+
21
+ @@order_parameter_name = "order"
22
+ @@order_direction_parameter_name = "order_direction"
23
+ @@page_parameter_name = "page"
24
+
25
+ def initialize(grid, view) #:nodoc:
26
+ @grid = grid
27
+ @grid.renderer = self
28
+ @columns = []
29
+ @columns_table = {}
30
+ @action_column_present = false
31
+ @view = view
32
+ end
33
+
34
+ def add_column(vc) #:nodoc:
35
+ @columns_table[vc.fully_qualified_attribute_name] = vc if vc.attribute_name
36
+ @columns << vc
37
+ end
38
+
39
+ def [](k) #:nodoc:
40
+ @columns_table[k]
41
+ end
42
+
43
+ def number_of_columns(filter = nil) #:nodoc:
44
+ filter_columns(filter).size
45
+ end
46
+
47
+ def each_column_label(filter = nil) #:nodoc:
48
+ filter_columns(filter).each{|col| yield col.column_name}
49
+ end
50
+
51
+ def column_labels(filter = nil) #:nodoc:
52
+ filter_columns(filter).collect(&:column_name)
53
+ end
54
+
55
+ def each_column(filter = nil) #:nodoc:
56
+ filter_columns(filter).each{|col| yield col}
57
+ end
58
+
59
+ def contains_a_text_input? #:nodoc:
60
+ filter_columns(:in_html).detect(&:contains_a_text_input)
61
+ end
62
+
63
+ def each_column_aware_of_one_last_one(filter = nil) #:nodoc:
64
+ cols = filter_columns(filter)
65
+ cols[0..-2].each{|col| yield col, false}
66
+ yield cols.last, true
67
+ end
68
+
69
+ def last_column_for_html #:nodoc:
70
+ filter_columns(:in_html).last
71
+ end
72
+
73
+ def select_for(filter) #:nodoc:
74
+ filter_columns(filter).select{|col| yield col}
75
+ end
76
+
77
+ def find_one_for(filter) #:nodoc:
78
+ filter_columns(filter).find{|col| yield col}
79
+ end
80
+
81
+
82
+ def each_column_with_attribute #:nodoc:
83
+ @columns.select(&:attribute_name).each{|col| yield col}
84
+ end
85
+
86
+ alias_method :each, :each_column
87
+ include Enumerable
88
+
89
+ def csv_export_icon #:nodoc:
90
+ tooltip = WiceGridNlMessageProvider.get_message(:CSV_EXPORT_TOOLTIP)
91
+ @csv_export_icon_present = true
92
+ image_tag(Defaults::CSV_EXPORT_ICON,
93
+ :title => tooltip,
94
+ :class => 'clickable export_to_csv_button',
95
+ :alt => tooltip
96
+ )
97
+ end
98
+
99
+ def pagination_panel(no_rightmost_column, hide_csv_button) #:nodoc:
100
+ panel = yield
101
+
102
+ render_csv_button = @grid.export_to_csv_enabled && ! hide_csv_button
103
+
104
+ number_of_columns = self.number_of_columns(:in_html)
105
+ number_of_columns -= 1 if no_rightmost_column
106
+
107
+ if panel.nil?
108
+ if render_csv_button
109
+ "<tr><td colspan=\"#{number_of_columns}\"></td><td>#{csv_export_icon}</td></tr>"
110
+ else
111
+ ''
112
+ end
113
+ else
114
+ if render_csv_button
115
+ "<tr><td colspan=\"#{number_of_columns}\">#{panel}</td><td>#{csv_export_icon}</td></tr>"
116
+ else
117
+ "<tr><td colspan=\"#{number_of_columns + 1}\">#{panel}</td></tr>"
118
+ end
119
+ end
120
+ end
121
+
122
+ def element_to_focus #:nodoc:
123
+ grid.status['foc']
124
+ end
125
+
126
+
127
+ # Adds a column with checkboxes for each record. Useful for actions with multiple records, for example, deleting
128
+ # selected records. Please note that +action_column+ only creates the checkboxes and the 'Select All' and
129
+ # 'Deselect All' buttons, and the form itelf as well as processing the parameters should be taken care of
130
+ # by the application code.
131
+ #
132
+ # * <tt>:param_name</tt> - The name of the HTTP parameter.
133
+ # The complete HTTP parameter is <tt>"#{grid_name}[#{param_name}][]"</tt>.
134
+ # The default param_name is 'selected'.
135
+ # * <tt>:td_html_attrs</tt> - a hash of HTML attributes to be included into the <tt>td</tt> tag.
136
+ # * <tt>:select_all_buttons</tt> - show/hide buttons 'Select All' and 'Deselect All' in the column header.
137
+ # The default is +true+.
138
+ # * <tt>:object_property</tt> - a method used to obtain the value for the HTTP parameter. The default is +id+.
139
+ def action_column(opts = {})
140
+
141
+ if @action_column_present
142
+ raise Wice::WiceGridException.new('There can be only one action column in a WiceGrid')
143
+ end
144
+
145
+ options = {
146
+ :param_name => :selected,
147
+ :td_html_attrs => {},
148
+ :select_all_buttons => true,
149
+ :object_property => :id
150
+ }
151
+
152
+ opts.assert_valid_keys(options.keys)
153
+ options.merge!(opts)
154
+ @action_column_present = true
155
+ @columns << ActionViewColumn.new(@grid, options[:td_html_attrs], options[:param_name],
156
+ options[:select_all_buttons], options[:object_property], @view)
157
+ end
158
+
159
+ # Defines everything related to a column in a grid - column name, filtering, rendering cells, etc.
160
+ #
161
+ # +column+ is only used inside the block of the +grid+ method. See documentation for the +grid+ method for more details.
162
+ #
163
+ # The only parameter is a hash of options. None of them is optional. If no options are supplied, the result will be a
164
+ # column with no name, no filtering and no sorting.
165
+ #
166
+ # * <tt>:column_name</tt> - Name of the column.
167
+ # * <tt>:td_html_attrs</tt> - a hash of HTML attributes to be included into the <tt>td</tt> tag.
168
+ # * <tt>:class</tt> - a shortcut for <tt>:td_html_attrs => {:class => 'css_class'}</tt>
169
+ # * <tt>:attribute_name</tt> - name of a database column (which normally correspond to a model attribute with the
170
+ # same name). By default the field is assumed to belong to the default table (see documentation for the
171
+ # +initialize_grid+ method). Parameter <tt>:model_class</tt> allows to specify another table. Presence of
172
+ # this parameter
173
+ # * adds sorting capabilities by this field
174
+ # * automatically creates a filter based on the type of the field unless parameter <tt>:no_filter</tt> is set to true.
175
+ # The following filters exist for the following types:
176
+ # * <tt>string</tt> - a text field
177
+ # * <tt>integer</tt> and <tt>float</tt> - two text fields to specify the range. Both limits or only one
178
+ # can be specified
179
+ # * <tt>boolean</tt> - a dropdown list with 'yes', 'no', or '-'. These labels can be changed in
180
+ # <tt>lib/wice_grid_config.rb</tt>.
181
+ # * <tt>date</tt> - two sets of standard date dropdown lists so specify the time range.
182
+ # * <tt>datetime</tt> - two sets of standard datetime dropdown lists so specify the time range. This filter
183
+ # is far from being user-friendly due to the number of dropdown lists.
184
+ # * <tt>:no_filter</tt> - Turns off filters even if <tt>:attribute_name</tt> is specified.
185
+ # This is needed if sorting is required while filters are not.
186
+ # * <tt>:allow_ordering</tt> - Enable/disable ordering links in the column titles. The default is +true+
187
+ # (i.e. if <tt>:attribute_name</tt> is defined, ordering is enabled)
188
+ # * <tt>:model_class</tt> - Name of the model class to which <tt>:attribute_name</tt> belongs to if this is not the main table.
189
+ # * <tt>:table_alias</tt> - In case there are two joined assocations both referring to the same table, ActiveRecord
190
+ # constructs a query where the second join provides an alias for the joined table. Setting <tt>:table_alias</tt>
191
+ # to this alias will enable WiceGrid to order and filter by columns belonging to different associatiations but
192
+ # originating from the same table without confusion. See README for an example.
193
+ # * <tt>:custom_filter</tt> - Allows to construct a custom dropdown filter. Depending on the value of
194
+ # <tt>:custom_filter</tt> different modes are available:
195
+ # * array of strings and/or numbers - this is a direct definition of possible values of the dropdown.
196
+ # Every item will be used both as the value of the select option and as its label.
197
+ # * Array of two-element arrays - Every first item of the two-element array is used for the label of the select option
198
+ # while the second element is the value of the select option
199
+ # * Hash - The keys of the hash become the labels of the generated dropdown list,
200
+ # while the values will be values of options of the dropdown list:
201
+ # * <tt>:auto</tt> - a powerful option which populates the dropdown list with all unique values of the field specified by
202
+ # <tt>:attribute_name</tt> and <tt>:model_class</tt>.
203
+ # <tt>:attribute_name</tt> throughout all pages. In other words, this runs an SQL query without +offset+ and +limit+
204
+ # clauses and with <tt>distinct(table.field)</tt> instead of <tt>distinct(*)</tt>
205
+ # * any other symbol name (method name) - The dropdown list is populated by all unique value returned by the
206
+ # method with this name sent to <em>all</em> ActiveRecord objects throughout all pages. The main difference
207
+ # from <tt>:auto</tt> is that this method does not have to be a field in the result set, it is just some
208
+ # value computed in the method after the database call and ActiveRecord instantiation.
209
+ #
210
+ # But here lies the major drawback - this mode requires additional query without +offset+ and +limit+
211
+ # clauses to instantiate _all_ ActiveRecord objects, and performance-wise it brings all the advantages
212
+ # of pagination to nothing. Thus, memory- and performance-wise this can be really bad for some queries
213
+ # and tables and should be used with care.
214
+ #
215
+ # If the method returns a atomic value like a string or an integer, it is used for both the value and the
216
+ # label of the select option element. However, if the retuned value is a two element array, the first element
217
+ # is used for the option label and the second - for the value.
218
+ # Read more in README, section 'Custom dropdown filters'
219
+ # * An array of symbols (method names) - similar to the mode with a single symbol name. The first method name
220
+ # is sent to the ActiveRecord object if it responds to this method, the second method name is sent to the
221
+ # returned value unless it is +nil+, and so on. In other words, a single symbol mode is a
222
+ # case of an array of symbols where the array contains just one element. Thus the warning about the single method name
223
+ # mode applies here as well.
224
+ #
225
+ # If the last method returns a atomic value like a string or an integer, it is used for both the value and the
226
+ # label of the select option element.
227
+ # However, if the retuned value is a two element array, the first element is used for the option label and the
228
+ # second - for the value.
229
+ # Read more in README, section 'Custom dropdown filters'
230
+ # * <tt>:boolean_filter_true_label</tt> - overrides the default value for <tt>BOOLEAN_FILTER_TRUE_LABEL</tt>
231
+ # ('+yes+') in the config.
232
+ # Only has effect in a column with a boolean filter.
233
+ # * <tt>:boolean_filter_false_label</tt> - overrides the default value for <tt>BOOLEAN_FILTER_FALSE_LABEL</tt>
234
+ # ('+no+') in the config.
235
+ # Only has effect in a column with a boolean filter.
236
+ # * <tt>:allow_multiple_selection</tt> - enables or disables switching between single and multiple selection modes for
237
+ # custom dropdown boxes. +true+ by default (see +ALLOW_MULTIPLE_SELECTION+ in the configuration file).
238
+ # * <tt>:filter_all_label</tt> - overrides the default value for <tt>BOOLEAN_FILTER_FALSE_LABEL</tt> ('<tt>--</tt>')
239
+ # in the config. Has effect in a column with a boolean filter _or_ a custom filter.
240
+ # * <tt>:detach_with_id</tt> - allows to detach the filter and render it after or before the grid with the
241
+ # +grid_filter+ helper. The value is an arbitrary unique identifier
242
+ # of the filter. Read section 'Detached Filters' in README for details.
243
+ # Has effect in a column with a boolean filter _or_ a custom filter.
244
+ # * <tt>:in_csv</tt> - When CSV export is enabled, all columns are included into the export. Setting <tt>:in_csv</tt>
245
+ # to false will prohibit the column from inclusion into the export.
246
+ # * <tt>:in_html</tt> - When CSV export is enabled and it is needed to use a column for CSV export only and ignore it
247
+ # in HTML, set this parameter to false.
248
+ # * <tt>:helper_style</tt> - Changes the flavor of Date and DateTime filters. The values are:
249
+ # * <tt>:standard</tt> - the default Rails Date/DateTime helper
250
+ # * <tt>:calendar</tt> - a Javascript popup calendar control
251
+ # * <tt>:negation_in_filter</tt> - turn on/off the negation checkbox in string filters
252
+ # * <tt>:auto_reload</tt> - a boolean value specifying if a change in a filter triggers reloading of the grid. Works with all
253
+ # filter types including the JS calendar, the only exception is the standard Rails date/datetime filters.
254
+ # The default is false. (see +AUTO_RELOAD+ in the configuration file).
255
+ #
256
+ # The block parameter is an ActiveRecord instance. This block is called for every ActiveRecord shown, and the return
257
+ # value of the block is a string which becomes the contents of one table cell, or an array of two elements where
258
+ # the first element is the cell contents and the second is a hash of HTML attributes to be added for the <tt><td></tt>
259
+ # tag of the current cell.
260
+ #
261
+ # In case of an array output, please note that if you need to define HTML attributes for all <tt><td></tt>'s in a
262
+ # column, use +td_html_attrs+. Also note that if the method returns a hash with a <tt>:class</tt> or <tt>'class'</tt>
263
+ # element, it will not overwrite the class defined in +td_html_attrs+, or classes added by the grid itself
264
+ # (+active_filter+ and +sorted+), instead they will be all concatenated:
265
+ # <tt><td class="sorted user_class_for_columns user_class_for_this_specific_cell"></tt>
266
+ #
267
+ # It is up to the developer to make sure that what in rendered in column cells
268
+ # corresponds to sorting and filtering specified by parameters <tt>:attribute_name</tt> and <tt>:model_class</tt>.
269
+
270
+ def column(opts = {}, &block)
271
+ options = {
272
+ :allow_multiple_selection => Defaults::ALLOW_MULTIPLE_SELECTION,
273
+ :allow_ordering => true,
274
+ :attribute_name => nil,
275
+ :auto_reload => Defaults::AUTO_RELOAD,
276
+ :boolean_filter_false_label => WiceGridNlMessageProvider.get_message(:BOOLEAN_FILTER_FALSE_LABEL),
277
+ :boolean_filter_true_label => WiceGridNlMessageProvider.get_message(:BOOLEAN_FILTER_TRUE_LABEL),
278
+ :class => nil,
279
+ :column_name => '',
280
+ :custom_filter => nil,
281
+ :custom_order => nil,
282
+ :detach_with_id => nil,
283
+ :filter_all_label => Defaults::CUSTOM_FILTER_ALL_LABEL,
284
+ :helper_style => Defaults::HELPER_STYLE,
285
+ :in_csv => true,
286
+ :in_html => true,
287
+ :model_class => nil,
288
+ :negation_in_filter => Defaults::NEGATION_IN_STRING_FILTERS,
289
+ :no_filter => false,
290
+ :table_alias => nil,
291
+ :td_html_attrs => {}
292
+ }
293
+
294
+ opts.assert_valid_keys(options.keys)
295
+ options.merge!(opts)
296
+
297
+ unless options[:model_class].nil?
298
+ options[:model_class] = options[:model_class].constantize if options[:model_class].is_a? String
299
+ raise WiceGridArgumentError.new("Option :model_class can be either a class or a string instance") unless options[:model_class].is_a? Class
300
+ end
301
+
302
+ if options[:attribute_name].nil? && options[:model_class]
303
+ raise WiceGridArgumentError.new("Option :model_class is only used together with :attribute_name")
304
+ end
305
+
306
+ if options[:attribute_name] && options[:attribute_name].index('.')
307
+ raise WiceGridArgumentError.new("Invalid attribute name #{options[:attribute_name]}. An attribute name must not contain a table name!")
308
+ end
309
+
310
+ if options[:class]
311
+ options[:td_html_attrs].add_or_append_class_value!(options[:class])
312
+ options.delete(:class)
313
+ end
314
+
315
+ if block.nil?
316
+ if ! options[:attribute_name].blank?
317
+ block = lambda{|obj| obj.send(options[:attribute_name])}
318
+ else
319
+ raise WiceGridArgumentError.new(
320
+ "Missing column block without attribute_name defined. You can only omit the block if attribute_name is present.")
321
+ end
322
+ end
323
+
324
+ klass = ViewColumn
325
+ if options[:attribute_name] &&
326
+ col_type_and_table_name = @grid.declare_column(options[:attribute_name], options[:model_class],
327
+ options[:custom_filter], options[:table_alias])
328
+
329
+ db_column, table_name, main_table = col_type_and_table_name
330
+ col_type = db_column.type
331
+
332
+ if options[:custom_filter]
333
+
334
+ custom_filter = if options[:custom_filter] == :auto
335
+ lambda{ @grid.distinct_values_for_column(db_column) } # Thank God Ruby has higher order functions!!!
336
+
337
+ elsif options[:custom_filter].class == Symbol
338
+ lambda{ @grid.distinct_values_for_column_in_resultset([options[:custom_filter]])}
339
+
340
+ elsif options[:custom_filter].class == Hash
341
+ options[:custom_filter].keys
342
+
343
+ options[:custom_filter].to_a
344
+
345
+ elsif options[:custom_filter].class == Array
346
+ if options[:custom_filter].empty?
347
+ []
348
+ elsif options[:custom_filter].all_items_are_of_class(Symbol)
349
+ lambda{ @grid.distinct_values_for_column_in_resultset(options[:custom_filter]) }
350
+
351
+ elsif options[:custom_filter].all_items_are_of_class(String) || options[:custom_filter].all_items_are_of_class(Numeric)
352
+ options[:custom_filter].map{|i| [i,i]}
353
+
354
+ elsif options[:custom_filter].all_items_are_of_class(Array)
355
+ options[:custom_filter]
356
+ else
357
+ raise WiceGridArgumentError.new(
358
+ ':custom_filter can equal :auto, an array of string and/or numbers (direct values for the dropdown), ' +
359
+ 'a homogeneous array of symbols (a sequence of methods to send to AR objects in the result set to ' +
360
+ 'retrieve unique values for the dropdown), a Symbol (a shortcut for a one member array of symbols), ' +
361
+ 'a hash where keys are labels and values are values for the dropdown option, or an array of two-item arrays, ' +
362
+ 'each of which contains the label (first element) and the value (second element) for a dropdown option'
363
+ )
364
+ end
365
+ end
366
+
367
+ klass = ViewColumnCustomDropdown
368
+ else
369
+ klass = ViewColumn.handled_type[col_type] || ViewColumn
370
+ end # custom_filter
371
+ end # attribute_name
372
+
373
+ vc = klass.new(block, options, @grid, table_name, main_table, custom_filter, @view)
374
+
375
+ vc.negation = options[:negation_in_filter] if vc.respond_to? :negation=
376
+
377
+ vc.filter_all_label = options[:filter_all_label] if vc.kind_of?(ViewColumnCustomDropdown)
378
+ if vc.kind_of?(ViewColumnBoolean)
379
+ vc.boolean_filter_true_label = options[:boolean_filter_true_label]
380
+ vc.boolean_filter_false_label = options[:boolean_filter_false_label]
381
+ end
382
+ add_column(vc)
383
+ end
384
+
385
+ # Optional method inside the +grid+ block, to which every ActiveRecord instance is injected, just like +column+.
386
+ # Unlike +column+, it returns a hash which will be used as HTML attributes for the row with the given ActiveRecord instance.
387
+ #
388
+ # Note that if the method returns a hash with a <tt>:class</tt> or <tt>'class'</tt> element, it will not overwrite
389
+ # classes +even+ and +odd+, instead they will be concatenated: <tt><tr class="even highlighted_row_class_name"></tt>
390
+ def row_attributes(&block)
391
+ @row_attributes_handler = block
392
+ end
393
+
394
+ # Can be used to add HTML code (another row, for example) right after each grid row.
395
+ # Nothing is added if the block return +false+ or +nil+.
396
+ def after_row(&block)
397
+ @after_row_handler = block
398
+ end
399
+
400
+ # Can be used to add HTML code (another row, for example) right before each grid row.
401
+ # Nothing is added if the block return +false+ or +nil+.
402
+ def before_row(&block)
403
+ @before_row_handler = block
404
+ end
405
+
406
+ # The output of the block submitted to +blank_slate+ is rendered instead of the whole grid if no filters are active
407
+ # and there are no records to render.
408
+ # In addition to the block style two other variants are accepted:
409
+ # * <tt>g.blank_slate "some text to be rendered"</tt>
410
+ # * <tt>g.blank_slate :partial => "partial_name"</tt>
411
+ def blank_slate(opts = nil, &block)
412
+ if (opts.is_a?(Hash) && opts.has_key?(:partial) && block.nil?) || (opts.is_a?(String) && block.nil?)
413
+ @blank_slate_handler = opts
414
+ elsif opts.nil? && block
415
+ @blank_slate_handler = block
416
+ else
417
+ raise WiceGridArgumentError.new("blank_slate accepts only a string, a block, or :template => 'path_to_template' ")
418
+ end
419
+ end
420
+
421
+
422
+ def get_row_attributes(ar_object) #:nodoc:
423
+ if @row_attributes_handler
424
+ row_attributes = @row_attributes_handler.call(ar_object)
425
+ row_attributes = {} if row_attributes.blank?
426
+ unless row_attributes.is_a?(Hash)
427
+ raise WiceGridArgumentError.new("row_attributes block must return a hash containing HTML attributes. The returned value is #{row_attributes.inspect}")
428
+ end
429
+ row_attributes
430
+ else
431
+ {}
432
+ end
433
+ end
434
+
435
+
436
+ def no_filter_needed? #:nodoc:
437
+ not @columns.inject(false){|a,b| a || b.filter_shown? }
438
+ end
439
+
440
+ def no_filter_needed_in_main_table? #:nodoc:
441
+ not @columns.inject(false){|a,b| a || b.filter_shown_in_main_table? }
442
+ end
443
+
444
+ def base_link_for_filter(controller, extra_parameters = {}) #:nodoc:
445
+ new_params = controller.params.deep_clone_yl
446
+ new_params.merge!(extra_parameters)
447
+
448
+ if new_params[@grid.name]
449
+ new_params[@grid.name].delete(:page) # we reset paging here
450
+ new_params[@grid.name].delete(:f) # no filter for the base url
451
+ new_params[@grid.name].delete(:foc) # nullify the focus
452
+ new_params[@grid.name].delete(:q) # and no request for the saved query
453
+ end
454
+
455
+ new_params[:only_path] = false
456
+ base_link_with_pp_info = controller.url_for(new_params).gsub(/\?+$/,'')
457
+
458
+ if new_params[@grid.name]
459
+ new_params[@grid.name].delete(:pp) # and reset back to pagination if show all mode is on
460
+ end
461
+ [base_link_with_pp_info, controller.url_for(new_params).gsub(/\?+$/,'')]
462
+ end
463
+
464
+
465
+
466
+ def link_for_export(controller, format, extra_parameters = {}) #:nodoc:
467
+ new_params = controller.params.deep_clone_yl
468
+ new_params.merge!(extra_parameters)
469
+
470
+ new_params[@grid.name] = {} unless new_params[@grid.name]
471
+ new_params[@grid.name][:export] = format
472
+
473
+ new_params[:only_path] = false
474
+ controller.url_for(new_params)
475
+ end
476
+
477
+
478
+ def column_link(column, direction, params, extra_parameters = {}) #:nodoc:
479
+
480
+ column_attribute_name = if column.attribute_name.index('.') or column.main_table
481
+ column.attribute_name
482
+ else
483
+ column.table_alias_or_table_name + '.' + column.attribute_name
484
+ end
485
+
486
+ query_params = {@grid.name => {
487
+ @@order_parameter_name => column_attribute_name,
488
+ @@order_direction_parameter_name => direction
489
+ }}
490
+
491
+ cleaned_params = params.deep_clone_yl
492
+ cleaned_params.merge!(extra_parameters)
493
+
494
+ cleaned_params.delete(:controller)
495
+ cleaned_params.delete(:action)
496
+
497
+
498
+ query_params = cleaned_params.rec_merge(query_params)
499
+
500
+ '?' + query_params.to_query
501
+ end
502
+
503
+ def contains_auto_reloading_inputs #:nodoc:
504
+ contains_auto_reloading_elements(:has_auto_reloading_input?)
505
+ end
506
+
507
+ def contains_auto_reloading_inputs_with_negation_checkboxes #:nodoc:
508
+ contains_auto_reloading_elements(:auto_reloading_input_with_negation_checkbox?)
509
+ end
510
+
511
+ def contains_auto_reloading_selects #:nodoc:
512
+ contains_auto_reloading_elements(:has_auto_reloading_select?)
513
+ end
514
+
515
+ def contains_auto_reloading_calendars #:nodoc:
516
+ contains_auto_reloading_elements(:has_auto_reloading_calendar?)
517
+ end
518
+
519
+ protected
520
+
521
+ def contains_auto_reloading_elements(what) #:nodoc:
522
+ filter_columns(:in_html).detect{|column| column.filter_shown? && column.send(what)}
523
+ end
524
+
525
+
526
+ def filter_columns(method_name = nil) #:nodoc:
527
+ method_name ? @columns.select(&method_name) : @columns
528
+ end
529
+
530
+ end
531
+ end