fancygrid 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.bundle/config +2 -0
- data/.rspec +1 -0
- data/CHANGELOG +34 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +125 -0
- data/LICENSE +20 -0
- data/README.rdoc +299 -0
- data/ROADMAP +1 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/app/views/fancygrid/_cells.html.haml +13 -0
- data/app/views/fancygrid/base/controls.html.haml +40 -0
- data/app/views/fancygrid/base/list_frame.html.haml +37 -0
- data/app/views/fancygrid/base/search.html.haml +33 -0
- data/app/views/fancygrid/base/sort.html.haml +20 -0
- data/app/views/fancygrid/base/table_frame.html.haml +45 -0
- data/config/initializers/fancygrid.rb +67 -0
- data/config/locales/fancygrid.de.yml +41 -0
- data/config/locales/fancygrid.en.yml +42 -0
- data/fancygrid.gemspec +162 -0
- data/init.rb +1 -0
- data/lib/fancygrid.rb +73 -0
- data/lib/fancygrid/grid.rb +387 -0
- data/lib/fancygrid/helper.rb +129 -0
- data/lib/fancygrid/node.rb +533 -0
- data/lib/fancygrid/query_generator.rb +338 -0
- data/lib/fancygrid/view.rb +148 -0
- data/lib/generators/install_generator.rb +61 -0
- data/lib/generators/views_generator.rb +25 -0
- data/lib/version.rb +0 -0
- data/public/images/fancygrid/add.png +0 -0
- data/public/images/fancygrid/clear.png +0 -0
- data/public/images/fancygrid/ddn.png +0 -0
- data/public/images/fancygrid/dn.png +0 -0
- data/public/images/fancygrid/dots.png +0 -0
- data/public/images/fancygrid/loading.gif +0 -0
- data/public/images/fancygrid/magnifier.png +0 -0
- data/public/images/fancygrid/next.png +0 -0
- data/public/images/fancygrid/order.png +0 -0
- data/public/images/fancygrid/prev.png +0 -0
- data/public/images/fancygrid/reload.png +0 -0
- data/public/images/fancygrid/remove.png +0 -0
- data/public/images/fancygrid/spacer.gif +0 -0
- data/public/images/fancygrid/submit.png +0 -0
- data/public/images/fancygrid/th_bg.png +0 -0
- data/public/images/fancygrid/up.png +0 -0
- data/public/images/fancygrid/uup.png +0 -0
- data/public/javascripts/fancygrid.js +477 -0
- data/public/javascripts/fancygrid.min.js +17 -0
- data/public/stylesheets/fancygrid.css +289 -0
- data/public/stylesheets/fancygrid.scss +302 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/project.rb +3 -0
- data/spec/dummy/app/models/ticket.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +45 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +26 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +35 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/db/migrate/20110112183948_create_projects.rb +11 -0
- data/spec/dummy/db/migrate/20110112183956_create_tickets.rb +14 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +0 -0
- data/spec/dummy/log/production.log +0 -0
- data/spec/dummy/log/server.log +0 -0
- data/spec/dummy/log/test.log +1026 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/javascripts/application.js +2 -0
- data/spec/dummy/public/javascripts/controls.js +965 -0
- data/spec/dummy/public/javascripts/dragdrop.js +974 -0
- data/spec/dummy/public/javascripts/effects.js +1123 -0
- data/spec/dummy/public/javascripts/prototype.js +6001 -0
- data/spec/dummy/public/javascripts/rails.js +175 -0
- data/spec/dummy/public/stylesheets/.gitkeep +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/grid_spec.rb +15 -0
- data/spec/integration/navigation_spec.rb +9 -0
- data/spec/node_spec.rb +326 -0
- data/spec/query_generator_spec.rb +358 -0
- data/spec/spec_helper.rb +53 -0
- 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
|