fancygrid 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/.bundle/config +2 -0
  2. data/.rspec +1 -0
  3. data/CHANGELOG +34 -0
  4. data/Gemfile +15 -0
  5. data/Gemfile.lock +125 -0
  6. data/LICENSE +20 -0
  7. data/README.rdoc +299 -0
  8. data/ROADMAP +1 -0
  9. data/Rakefile +45 -0
  10. data/VERSION +1 -0
  11. data/app/views/fancygrid/_cells.html.haml +13 -0
  12. data/app/views/fancygrid/base/controls.html.haml +40 -0
  13. data/app/views/fancygrid/base/list_frame.html.haml +37 -0
  14. data/app/views/fancygrid/base/search.html.haml +33 -0
  15. data/app/views/fancygrid/base/sort.html.haml +20 -0
  16. data/app/views/fancygrid/base/table_frame.html.haml +45 -0
  17. data/config/initializers/fancygrid.rb +67 -0
  18. data/config/locales/fancygrid.de.yml +41 -0
  19. data/config/locales/fancygrid.en.yml +42 -0
  20. data/fancygrid.gemspec +162 -0
  21. data/init.rb +1 -0
  22. data/lib/fancygrid.rb +73 -0
  23. data/lib/fancygrid/grid.rb +387 -0
  24. data/lib/fancygrid/helper.rb +129 -0
  25. data/lib/fancygrid/node.rb +533 -0
  26. data/lib/fancygrid/query_generator.rb +338 -0
  27. data/lib/fancygrid/view.rb +148 -0
  28. data/lib/generators/install_generator.rb +61 -0
  29. data/lib/generators/views_generator.rb +25 -0
  30. data/lib/version.rb +0 -0
  31. data/public/images/fancygrid/add.png +0 -0
  32. data/public/images/fancygrid/clear.png +0 -0
  33. data/public/images/fancygrid/ddn.png +0 -0
  34. data/public/images/fancygrid/dn.png +0 -0
  35. data/public/images/fancygrid/dots.png +0 -0
  36. data/public/images/fancygrid/loading.gif +0 -0
  37. data/public/images/fancygrid/magnifier.png +0 -0
  38. data/public/images/fancygrid/next.png +0 -0
  39. data/public/images/fancygrid/order.png +0 -0
  40. data/public/images/fancygrid/prev.png +0 -0
  41. data/public/images/fancygrid/reload.png +0 -0
  42. data/public/images/fancygrid/remove.png +0 -0
  43. data/public/images/fancygrid/spacer.gif +0 -0
  44. data/public/images/fancygrid/submit.png +0 -0
  45. data/public/images/fancygrid/th_bg.png +0 -0
  46. data/public/images/fancygrid/up.png +0 -0
  47. data/public/images/fancygrid/uup.png +0 -0
  48. data/public/javascripts/fancygrid.js +477 -0
  49. data/public/javascripts/fancygrid.min.js +17 -0
  50. data/public/stylesheets/fancygrid.css +289 -0
  51. data/public/stylesheets/fancygrid.scss +302 -0
  52. data/spec/dummy/Rakefile +7 -0
  53. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  54. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  55. data/spec/dummy/app/models/project.rb +3 -0
  56. data/spec/dummy/app/models/ticket.rb +3 -0
  57. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  58. data/spec/dummy/config.ru +4 -0
  59. data/spec/dummy/config/application.rb +45 -0
  60. data/spec/dummy/config/boot.rb +10 -0
  61. data/spec/dummy/config/database.yml +22 -0
  62. data/spec/dummy/config/environment.rb +5 -0
  63. data/spec/dummy/config/environments/development.rb +26 -0
  64. data/spec/dummy/config/environments/production.rb +49 -0
  65. data/spec/dummy/config/environments/test.rb +35 -0
  66. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  67. data/spec/dummy/config/initializers/inflections.rb +10 -0
  68. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  69. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  70. data/spec/dummy/config/initializers/session_store.rb +8 -0
  71. data/spec/dummy/config/locales/en.yml +5 -0
  72. data/spec/dummy/config/routes.rb +58 -0
  73. data/spec/dummy/db/migrate/20110112183948_create_projects.rb +11 -0
  74. data/spec/dummy/db/migrate/20110112183956_create_tickets.rb +14 -0
  75. data/spec/dummy/db/test.sqlite3 +0 -0
  76. data/spec/dummy/log/development.log +0 -0
  77. data/spec/dummy/log/production.log +0 -0
  78. data/spec/dummy/log/server.log +0 -0
  79. data/spec/dummy/log/test.log +1026 -0
  80. data/spec/dummy/public/404.html +26 -0
  81. data/spec/dummy/public/422.html +26 -0
  82. data/spec/dummy/public/500.html +26 -0
  83. data/spec/dummy/public/favicon.ico +0 -0
  84. data/spec/dummy/public/javascripts/application.js +2 -0
  85. data/spec/dummy/public/javascripts/controls.js +965 -0
  86. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  87. data/spec/dummy/public/javascripts/effects.js +1123 -0
  88. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  89. data/spec/dummy/public/javascripts/rails.js +175 -0
  90. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  91. data/spec/dummy/script/rails +6 -0
  92. data/spec/grid_spec.rb +15 -0
  93. data/spec/integration/navigation_spec.rb +9 -0
  94. data/spec/node_spec.rb +326 -0
  95. data/spec/query_generator_spec.rb +358 -0
  96. data/spec/spec_helper.rb +53 -0
  97. metadata +214 -0
@@ -0,0 +1,129 @@
1
+ require "active_support/hash_with_indifferent_access"
2
+
3
+ module Fancygrid
4
+
5
+ module Helper
6
+
7
+ def fancygrid_params(name)
8
+ opts = params[:fancygrid] || HashWithIndifferentAccess.new({})
9
+ opts[name]
10
+ end
11
+
12
+ def fancygrid_remote_call?(name)
13
+ !fancygrid_params(name).nil?
14
+ end
15
+
16
+ # Creates a fancygrid instance for the given model name, its class and
17
+ # its table name.
18
+ # === Example
19
+ # fancygrid_for :users do |grid|
20
+ #
21
+ # # specify attributes to display
22
+ # grid.attributes( :id, :username, :email )
23
+ #
24
+ # # specify the callback url for ajax loading
25
+ # grid.url = users_path
26
+ #
27
+ # # finally call find with some customized find options
28
+ # grid.find( :order => "users.created_at DESC" )
29
+ #
30
+ # end
31
+ def fancygrid_for(name, klass = nil, table_name = nil)#:yields: grid
32
+ raise "block missing" unless block_given?
33
+
34
+ @fancygrid ||= HashWithIndifferentAccess.new({})
35
+ @fancygrid[name] ||= Grid.new(name, klass, table_name, params)
36
+
37
+ fancygrid_instance = @fancygrid[name]
38
+
39
+ yield fancygrid_instance
40
+
41
+ view_opts = fancygrid_params(name)
42
+ view_opts ||= fancygrid_instance.load_view_proc_evaluate
43
+
44
+ # load the fancygrid view
45
+ fancygrid_instance.load_view(view_opts || {})
46
+
47
+ # store the view right back
48
+ fancygrid_instance.store_view_proc_evaluate
49
+
50
+ # now the fancygrid setup is complete and the view is loaded
51
+ # run the database query when we are in the remote state
52
+ if !fancygrid_instance.is_static? && fancygrid_remote_call?(name)
53
+ fancygrid_instance.query_for_data
54
+ end
55
+
56
+ fancygrid_instance.sort_leafs!
57
+ end
58
+
59
+ # Renders an existing fancygrid for the given name. You can append a rendering block
60
+ # or pass a template name as an option for custom rendering.
61
+ # === Options
62
+ # * <tt>data</tt> - The data to render
63
+ # * <tt>template</tt> - The template to use for custom rendering columns
64
+ # * <tt>url</tt> - The callback url for ajax
65
+ # * <tt>search_visible</tt> - If true, the search will be visible
66
+ # * <tt>hide_top_control</tt> - If true, the top control bar will be hidden
67
+ # * <tt>hide_bottom_control</tt> - If true, the bottom control bar will be hidden
68
+ # * <tt>grid_type</tt> - may be one of <tt>:list</tt> table <tt>:table</tt> to render a list or a table
69
+ def fancygrid(name, options=nil, &block)#:yields: column, record, value
70
+ store_name = name.to_s
71
+ raise "Missing fancygrid for name '#{store_name}'" if(@fancygrid.nil? || @fancygrid[store_name].nil?)
72
+ fancygrid_instance = @fancygrid[store_name]
73
+
74
+ options ||= {}
75
+ [:data, :template, :url, :search_visible, :hide_top_control,
76
+ :hide_bottom_control, :grid_type, :search_formats
77
+ ].each do |option|
78
+ fancygrid_instance.send(option.to_s + "=", options[option]) if options[option]
79
+ end
80
+
81
+ format_block = block_given? ? block : nil
82
+ template = Fancygrid.table_template
83
+ template = Fancygrid.list_template if(fancygrid_instance.grid_type.to_s == "list")
84
+
85
+ render(:template => template, :locals => {
86
+ :fancygrid => fancygrid_instance,
87
+ :cells_block => format_block, :format_block => format_block
88
+ })
89
+ end
90
+
91
+ # Renders the given <tt>record</tt>, <tt>leaf</tt> and <tt>value</tt> with the
92
+ # leafs template or the passed rendering block. The result is a column cell content.
93
+ def format_fancygrid_value(record, leaf, value=nil, &format_block)
94
+ if block_given?
95
+ if defined?(Haml::Helpers) && is_haml?
96
+ capture_haml(leaf, record, value, &format_block)
97
+ else
98
+ capture(leaf, record, value, &format_block)
99
+ end
100
+ else
101
+ render( :template => leaf.root.template, :locals => {
102
+ :grid => leaf.root, :table => leaf.root,
103
+ :record => record,
104
+ :cell => leaf, :column => leaf,
105
+ :value => value
106
+ })
107
+ end
108
+ end
109
+ alias :fancy_rendering_for :format_fancygrid_value # backward compatibility
110
+
111
+ # Returns the <tt>value</tt> of the given <tt>leaf</tt> if it is not <tt>:formatable</tt>.
112
+ # Otherwie the <tt>leaf</tt> ist <tt>value</tt> and the <tt>record</tt> will
113
+ # be passed to the <tt>format_fancygrid_value</tt> method to render and format
114
+ # the value. The result is a column cell content.
115
+ def render_fancygrid_leaf(record, leaf, &format_block)
116
+ value = leaf.value_from(record)
117
+ return value if(!leaf.formatable && leaf.root.grid_type == :table)
118
+ format_fancygrid_value(record, leaf, value, &format_block)
119
+ end
120
+ alias :fancyvalue_for :render_fancygrid_leaf # backward compatibility
121
+
122
+ def fancygrid_button name, translate_scope, default, alt=nil
123
+ title = I18n.t(translate_scope, :default => default, :scope => Fancygrid.i18n_scope)
124
+ alt ||= title
125
+ image_tag('/images/fancygrid/spacer.gif', :size => '16x16', :class => "#{name} fg-button", :title => title, :alt => title ).html_safe
126
+ end
127
+ end
128
+
129
+ end
@@ -0,0 +1,533 @@
1
+
2
+ module Fancygrid#:nodoc:
3
+
4
+ class Node
5
+
6
+ # Top level node. Must be a Fancygrid::Grid instance
7
+ attr_accessor :root
8
+
9
+ # Parent node. Can be a Fancygrid::Grid or Fancygrid::Node instance or <tt>nil</tt>
10
+ attr_accessor :parent
11
+
12
+ # Collection of Fancygrid::Node's that define fields or nested resources of
13
+ # this node
14
+ attr_accessor :children
15
+
16
+
17
+ # User defined name of this field or table
18
+ attr_accessor :name
19
+
20
+ # Class constant of the model that is represented by this node
21
+ attr_accessor :record_klass
22
+
23
+ # Table name of the model that is represented by this node
24
+ attr_accessor :record_table_name
25
+
26
+
27
+ # Specifies the column position from left to right in the final table
28
+ attr_accessor :position
29
+
30
+ # Specifies the search value of this column
31
+ attr_accessor :search_value
32
+
33
+ # Specifies whether this column is searchable or not
34
+ attr_accessor :searchable
35
+
36
+ # Specifies whether this column is formatted with a custom rendering code
37
+ attr_accessor :formatable
38
+
39
+ # Specifies whether this column is rendered or not
40
+ attr_accessor :visible
41
+
42
+ # Specifies whether this column refers to a database field and has a selector
43
+ attr_accessor :selectable
44
+
45
+ # Specifies the custom block to use on a record to resolve a value for rendering
46
+ attr_accessor :proc_block
47
+
48
+
49
+ def initialize(root, parent, name)
50
+ raise "root element must be an instance of Fancygrid::Grid" unless root.is_a?(Fancygrid::Grid)
51
+ self.root = root
52
+ self.parent = parent
53
+ self.name = name
54
+ self.children = nil
55
+ end
56
+
57
+
58
+ # Creates a child node with given <tt>name</tt>, <tt>klass</tt> and <tt>table_name</tt>.
59
+ # See +initialize_node+ method for more information
60
+ def columns_for(name, klass = nil, table_name = nil, options = nil, &block)
61
+ raise ArgumentError, "Missing block" unless block_given?
62
+ node = Fancygrid::Node.new(self.root, self, name)
63
+ node.initialize_node(name, klass, table_name, options)
64
+
65
+ self.children ||= []
66
+ self.children << node
67
+
68
+ yield node
69
+ end
70
+
71
+ # Creates a child leaf for this node. A leaf represents a column in the final table.
72
+ # === options
73
+ # * <tt>:visible</tt> _TrueClass_ value specifying whether the column is rendered to the final table or not
74
+ # * <tt>:searchable</tt> _TrueClass_ value specifying whether the column has a search field in the final table or not
75
+ # * <tt>:formatable</tt> _TrueClass_ value specifying whether the column is formatted with custom rendering code or not
76
+ # * <tt>:selectable</tt> _TrueClass_ value specifying whether the column is a database field and has a selector
77
+ # * <tt>:proc</tt> _Proc_ block that should be run to retrieve a value from a record
78
+ def column(name, options = nil)
79
+ node = Fancygrid::Node.new(self.root, self, name)
80
+ node.initialize_node(name, self.record_klass, self.record_table_name)
81
+ node.initialize_leaf(options)
82
+
83
+ self.children ||= []
84
+ self.children << node
85
+ root.insert_node(node)
86
+ #root.leafs << node
87
+ end
88
+
89
+ # Creates a <tt>column</tt> for each value in the <tt>names</tt> array with the passed <tt>options</tt>.
90
+ def columns(names, options)
91
+ names.flatten.each do |name|
92
+ column(name, options)
93
+ end
94
+ end
95
+
96
+ # Creates a <tt>column</tt> for each value in the <tt>names</tt> argument
97
+ # Sets the following <tt>options</tt> if not already set in the <tt>options</tt> argument
98
+ # * <tt>:searchable => true</tt>
99
+ # * <tt>:formatable => false</tt>
100
+ # * <tt>:visible => true</tt>
101
+ # * <tt>:selectable => true</tt>
102
+ # === Example
103
+ #
104
+ # node.attributes(:status)
105
+ #
106
+ # # is a shortcut for
107
+ #
108
+ # node.column(:status, {
109
+ # :searchable => true,
110
+ # :formatable => false,
111
+ # :visible => true,
112
+ # :selectable => true,
113
+ # })
114
+ def attributes(*names)
115
+ options = names.extract_options!
116
+ options[:searchable] = true if options[:searchable].nil?
117
+ options[:formatable] = false if options[:formatable].nil?
118
+ options[:visible] = true if options[:visible].nil?
119
+ options[:selectable] = true if options[:selectable].nil?
120
+ columns(names, options)
121
+ end
122
+
123
+ # Creates a <tt>column</tt> for each value in the <tt>names</tt> argument.
124
+ # Sets the following <tt>options</tt> if not already set in the <tt>options</tt> argument
125
+ # * <tt>:searchable => false</tt>
126
+ # * <tt>:formatable => false</tt>
127
+ # * <tt>:visible => true</tt>
128
+ # * <tt>:selectable => false</tt>
129
+ # === Example
130
+ #
131
+ # node.methods(:status)
132
+ #
133
+ # # is a shortcut for
134
+ #
135
+ # node.column(:status, {
136
+ # :searchable => false,
137
+ # :formatable => false,
138
+ # :visible => true,
139
+ # :selectable => false,
140
+ # })
141
+ def methods(*names)
142
+ options = names.extract_options!
143
+ options[:searchable] = false if options[:searchable].nil?
144
+ options[:formatable] = false if options[:formatable].nil?
145
+ options[:visible] = true if options[:visible].nil?
146
+ options[:selectable] = options[:selectable].nil? ? options[:searchable] : options[:selectable]
147
+ columns(names, options)
148
+ end
149
+
150
+ # Creates a <tt>column</tt> for each value in the <tt>names</tt> argument.
151
+ # Sets the following <tt>options</tt> if not already set in the <tt>options</tt> argument
152
+ # * <tt>:searchable => false</tt>
153
+ # * <tt>:formatable => true</tt>
154
+ # * <tt>:visible => true</tt>
155
+ # * <tt>:selectable => false</tt>
156
+ # === Example
157
+ #
158
+ # node.rendered(:status)
159
+ #
160
+ # # is a shortcut for
161
+ #
162
+ # node.column(:status, {
163
+ # :searchable => false,
164
+ # :formatable => true,
165
+ # :visible => true,
166
+ # :selectable => false,
167
+ # })
168
+ def rendered(*names)
169
+ options = names.extract_options!
170
+ options[:searchable] = false if options[:searchable].nil?
171
+ options[:formatable] = true if options[:formatable].nil?
172
+ options[:visible] = true if options[:visible].nil?
173
+ options[:selectable] = options[:selectable].nil? ? options[:searchable] : options[:selectable]
174
+ columns(names, options)
175
+ end
176
+
177
+ # Creates a <tt>column</tt> for each value in the <tt>names</tt> argument.
178
+ # Sets the following <tt>options</tt> if not already set in the <tt>options</tt> argument
179
+ # * <tt>:searchable => true</tt>
180
+ # * <tt>:formatable => false</tt>
181
+ # * <tt>:visible => false</tt>
182
+ # * <tt>:selectable => true</tt>
183
+ # === Example
184
+ #
185
+ # node.hidden(:status)
186
+ #
187
+ # # is a shortcut for
188
+ #
189
+ # node.column(:status, {
190
+ # :searchable => true,
191
+ # :formatable => false,
192
+ # :visible => false,
193
+ # :selectable => true,
194
+ # })
195
+ def hidden(*names)
196
+ options = names.extract_options!
197
+ options[:searchable] = true if options[:searchable].nil?
198
+ options[:formatable] = false if options[:formatable].nil?
199
+ options[:visible] = false if options[:visible].nil?
200
+ options[:selectable] = true if options[:selectable].nil?
201
+ columns(names, options)
202
+ end
203
+
204
+ # Creates a <tt>column</tt> for each value in the <tt>names</tt> argument.
205
+ # Sets the following <tt>options</tt> if not already set in the <tt>options</tt> argument
206
+ # * <tt>:searchable => false</tt>
207
+ # * <tt>:formatable => false</tt>
208
+ # * <tt>:visible => true</tt>
209
+ # * <tt>:selectable => false</tt>
210
+ # * <tt>:proc => proc</tt>
211
+ # === Example
212
+ #
213
+ # node.proc(:status) do |record|
214
+ # record.status
215
+ # end
216
+ #
217
+ # # is a shortcut for
218
+ #
219
+ # node.column(:status, {
220
+ # :searchable => false,
221
+ # :formatable => false,
222
+ # :visible => true,
223
+ # :selectable => false,
224
+ # :proc => Proc.new { |record| record.status }
225
+ # })
226
+ def proc(name, options=nil)
227
+ raise "Missing block" unless block_given?
228
+ options ||= {}
229
+ options[:searchable] = false if options[:searchable].nil?
230
+ options[:formatable] = false if options[:formatable].nil?
231
+ options[:visible] = true if options[:visible].nil?
232
+ options[:selectable] = options[:selectable].nil? ? options[:searchable] : options[:selectable]
233
+ options[:proc] = Proc.new
234
+ column(name, options)
235
+ end
236
+
237
+ # Gets a value indicating whether this node is a leaf or not.
238
+ # === Example
239
+ #
240
+ # grid = Fancygrid::Grid.new(:ticket) # is_leaf? => false
241
+ # grid.column(:column) # is_leaf? => true
242
+ # grid.attributes(:title) # is_leaf? => true
243
+ # grid.methods(:status) # is_leaf? => true
244
+ # grid.rendered(:foo) # is_leaf? => true
245
+ # grid.hidden(:bar) # is_leaf? => true
246
+ #
247
+ # grid.columns_for(:project) do |p| # is_leaf? => false
248
+ # p.attributes(:description) # is_leaf? => true
249
+ # end
250
+ def is_leaf?
251
+ self.children.nil?
252
+ end
253
+
254
+ # Returns the <tt>tag_name</tt> of this node if it is a leaf. Otherwise returns <tt>nil</tt>.
255
+ # === Example
256
+ #
257
+ # grid = Fancygrid::Grid.new(:ticket) # tag_name => nil
258
+ # grid.attributes(:title) # tag_name => "tickets[title]"
259
+ # grid.attributes(:status) # tag_name => "tickets[status]"
260
+ #
261
+ # grid.columns_for(:project) do |p| # tag_name => nil
262
+ # p.attributes(:description) # tag_name => "projects[description]"
263
+ # end
264
+ def tag_name
265
+ if is_leaf? && @tag_name.nil?
266
+ @tag_name = "#{self.record_table_name}[#{self.name}]"
267
+ end
268
+ @tag_name
269
+ end
270
+
271
+ # Returns the database select name of this node if it is a leaf and is selectable.
272
+ # Otherwise returns <tt>nil</tt>. The <tt>select_name</tt> is used in the
273
+ # finder <tt>:select</tt> option to select fields from database.
274
+ # === Example
275
+ #
276
+ # grid = Fancygrid::Grid.new(:ticket) # select_name => nil
277
+ # grid.attributes(:title) # select_name => "tickets.title"
278
+ # grid.attributes(:status) # select_name => "tickets.status"
279
+ # grid.methods(:foo) # select_name => nil
280
+ #
281
+ # grid.columns_for(:project) do |p| # select_name => nil
282
+ # p.attributes(:description) # select_name => "projects.description"
283
+ # end
284
+ def select_name
285
+ if is_leaf? && selectable && @select_name.nil?
286
+ @select_name = "#{self.record_table_name}.#{self.name}"
287
+ end
288
+ @select_name
289
+ end
290
+
291
+ # Returns the css selector of this node if it is a leaf. Otherwise returns
292
+ # <tt>nil</tt>. The <tt>css_class</tt> is there to identify a column in the rendered output
293
+ # and consist of the <tt>record_table_name</tt> and the leafs +name+
294
+ # === Example
295
+ #
296
+ # grid = Fancygrid::Grid.new(:ticket) # css_class => nil
297
+ # grid.attributes(:title) # css_class => "tickets title"
298
+ # grid.attributes(:status) # css_class => "tickets status"
299
+ #
300
+ # grid.columns_for(:project) do |p| # css_class => nil
301
+ # p.attributes(:description) # css_class => "projects description"
302
+ # end
303
+ def css_class
304
+ if is_leaf? && @css_class.nil?
305
+ @css_class = []
306
+ @css_class << self.record_table_name
307
+ @css_class << self.name
308
+ @css_class << "fg-orderable" if self.searchable
309
+ @css_class = @css_class.join(" ")
310
+ end
311
+ @css_class
312
+ end
313
+
314
+ def applied_sort_order
315
+ return "" unless root.view
316
+
317
+ # get the sort order from the view. it may look like this: "table.column ASC"
318
+ sort_order = root.view.get_sort_order.to_s
319
+ sort_order = sort_order.gsub(self.select_name.to_s, "").gsub(" ", "")
320
+ if %w(ASC DESC).include?(sort_order)
321
+ sort_order
322
+ else
323
+ ""
324
+ end
325
+ end
326
+
327
+ def search_input_kind
328
+ return @search_input_kind if @search_input_kind
329
+
330
+ @search_input_kind = :none
331
+ root.search_formats.each do |key, values|
332
+ next unless values.is_a? Hash
333
+ @search_input_kind = key if values.keys.include?(self.select_name)
334
+ end
335
+
336
+ @search_input_kind
337
+ end
338
+
339
+ def search_input_options
340
+ return @search_input_options if @search_input_options
341
+
342
+ @search_input_options = {}
343
+
344
+ root.search_formats.each do |key, values|
345
+ next unless values.is_a? Hash
346
+ opts = values[self.select_name.to_s]
347
+ @search_input_options = opts if opts.is_a?(Hash)
348
+ end
349
+
350
+ @search_input_options
351
+ end
352
+
353
+ def search_select_collection
354
+ return @search_select_collection if @search_select_collection
355
+
356
+ opts = search_input_options
357
+
358
+ collection = opts[:collection] || []
359
+ text_method = opts[:text_method] || "id"
360
+ value_method = opts[:value_method] || "to_s"
361
+
362
+ collection = collection.map do |item|
363
+ [item.send(text_method), item.send(value_method)]
364
+ end
365
+
366
+ if opts[:prompt]
367
+ collection.insert(0, [opts[:prompt], ""])
368
+ else
369
+ collection.insert(0, ["", ""])
370
+ end
371
+
372
+
373
+ @search_select_collection = collection
374
+ end
375
+
376
+ # Returns the internationalization path for this node if it is a leaf.
377
+ # Otherwise returns nil. The <tt>i18n_path</tt> is used to lookup the <tt>human_name</tt>
378
+ # of this node and is the <tt>trace_path</tt> preceded with the value from
379
+ # <tt>Fancygrid.i18n_scope</tt>
380
+ def i18n_path
381
+ if is_leaf? && @i18n_path.nil?
382
+ @i18n_path = "#{Fancygrid.i18n_scope}.tables.#{self.trace_path}"
383
+ end
384
+ @i18n_path
385
+ end
386
+
387
+ # Returns the trace path of this node. A trace path is the path from the
388
+ # root node to this node including all names joined with a dot <tt>.</tt>
389
+ # === Example
390
+ #
391
+ # grid = Fancygrid::Grid.new(:ticket)
392
+ # grid.columns_for(:project) do |p|
393
+ # p.trace_path # => "ticket.project"
394
+ # end
395
+ # grid.trace_path # => "ticket"
396
+
397
+ def trace_path
398
+ unless @trace_path
399
+ prefix = (parent and parent.trace_path)
400
+ @trace_path = [prefix, self.name].compact.join(".")
401
+ end
402
+ @trace_path
403
+ end
404
+
405
+ # Sets the human name on this node
406
+ def human_name= value
407
+ @human_name = value
408
+ end
409
+
410
+ # Returns a human name of this node if it is a leaf. Otherwie returns <tt>nil</tt>.
411
+ def human_name
412
+ if is_leaf? && @human_name.nil?
413
+ default = self.name.to_s.humanize
414
+ if self.record_klass.respond_to?(:human_attribute_name)
415
+ default = self.record_klass.human_attribute_name(self.name, :default => default)
416
+ end
417
+ @human_name = I18n.t(self.i18n_path, :default => default)
418
+ end
419
+ @human_name
420
+ end
421
+
422
+ # Gets a value from given <tt>record</tt> using the nodes <tt>trace_path</tt>.
423
+ # === Example
424
+ #
425
+ # # having a node with the following trace path
426
+ # node # trace_path => "ticket.project.description"
427
+ #
428
+ # # and a ticket model with an assigned project
429
+ # ticket = new Ticket(project)
430
+ #
431
+ # # then the following are the same
432
+ # node.value_from(ticket)
433
+ # ticket.project.description
434
+ #
435
+ # ---
436
+ # If the node has a proc, then the trace path is ignored
437
+ # === Example
438
+ #
439
+ # # having a node with the following trace path
440
+ # node # trace_path => "ticket.project.description"
441
+ # # and we assign a proc to the node
442
+ # node.proc_block = Proc.new { |record| record.project.description }
443
+ #
444
+ # # and a ticket model with an assigned project
445
+ # ticket = new Ticket(project)
446
+ #
447
+ # # then the following are the same
448
+ # node.value_from(ticket)
449
+ # ticket.proc_block.call(ticket)
450
+ # ticket.project.description
451
+ def value_from record
452
+ root.log("Resolve '#{self.trace_path}' from '#{record}' (#{record.to_param})")
453
+
454
+ if self.proc_block
455
+ return proc_block.call(record)
456
+ end
457
+
458
+ # default result value is an empty string
459
+ value = ""
460
+
461
+ # create an array from the nodes trace path and shift the first name away
462
+ # since the first name should reference the passed record
463
+ reflection_path = self.trace_path.split(/\./)
464
+ reflection_path.shift
465
+
466
+ # set the current evaluated object and iterate over all reflection path tokens
467
+ evaluated = record
468
+ while reflection_path.length > 0
469
+ token = reflection_path.shift
470
+ if(token.blank? || evaluated.nil? || !evaluated.respond_to?(token))
471
+ root.log("Step >> '#{evaluated.to_s}'.'#{token}' cant be resolved")
472
+ break
473
+ end
474
+
475
+ value = evaluated.send(token)
476
+ root.log("Step >> '#{evaluated.to_s}'.'#{token}' is '#{value.to_s}'")
477
+
478
+ evaluated = value
479
+ end
480
+
481
+ return value
482
+ end
483
+
484
+ # Searches for a node in the sub tree with given trace <tt>path</tt>
485
+ def find_by_path path
486
+ path = path.split(".") unless path.is_a?(Array)
487
+
488
+ if (path.first == self.name.to_s)
489
+ path.shift
490
+ return self if (path.empty?)
491
+
492
+
493
+ children.each do |node|
494
+ result = node.find_by_path(path)
495
+ return result if result
496
+ end
497
+ end
498
+
499
+ return nil
500
+ end
501
+
502
+ protected
503
+ def initialize_node(name, klass = nil, table_name = nil, options = nil)
504
+ self.name = name
505
+ if klass.is_a? Class
506
+ self.record_klass = klass
507
+ else
508
+ self.record_klass = self.name.to_s.classify.constantize
509
+ end
510
+ if table_name
511
+ self.record_table_name = table_name
512
+ elsif self.record_klass.respond_to?(:table_name)
513
+ self.record_table_name = self.record_klass.table_name
514
+ else
515
+ self.record_table_name = self.record_klass.name.tableize
516
+ end
517
+ end
518
+
519
+ # Reads the given options and applies to the nodes attributes
520
+ def initialize_leaf options = nil
521
+ options ||= {}
522
+
523
+ self.searchable = options[:searchable]
524
+ self.formatable = options[:formatable]
525
+ self.visible = options[:visible]
526
+ self.search_value = options[:search_value]
527
+ self.human_name = options[:human_name]
528
+ self.selectable = options[:selectable]
529
+ self.proc_block = options[:proc] if options[:proc].is_a? Proc
530
+ end
531
+
532
+ end
533
+ end