wice_grid_mongoid 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
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