fancygrid 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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