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.
- 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
|