sorting_table_for 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.mdown +4 -0
- data/init.rb +0 -4
- data/lib/sorting_table_for.rb +3 -0
- data/lib/sorting_table_for/format_cell.rb +189 -0
- data/lib/sorting_table_for/format_line.rb +110 -0
- data/lib/sorting_table_for/i18n.rb +16 -9
- data/lib/sorting_table_for/model_scope.rb +76 -0
- data/lib/sorting_table_for/table_builder.rb +15 -306
- data/spec/db/schema.rb +1 -1
- data/spec/fixtures/{user.rb → sorting_table_for_user.rb} +2 -2
- data/spec/helpers/builder_spec.rb +8 -5
- data/spec/helpers/caption_spec.rb +7 -4
- data/spec/helpers/cell_value_spec.rb +7 -4
- data/spec/helpers/column_spec.rb +14 -11
- data/spec/helpers/footer_spec.rb +7 -4
- data/spec/helpers/header_spec.rb +33 -8
- data/spec/helpers/i18n_spec.rb +12 -7
- data/spec/locales/test.yml +11 -2
- data/spec/locales/test_rails3.yml +6 -4
- data/spec/model/sorting_table_model_scope_spec.rb +24 -18
- data/spec/spec_helper.rb +18 -0
- metadata +7 -15
- data/lib/model/sorting_table_model_scope.rb +0 -72
data/CHANGELOG.mdown
CHANGED
data/init.rb
CHANGED
data/lib/sorting_table_for.rb
CHANGED
@@ -0,0 +1,189 @@
|
|
1
|
+
module SortingTableFor
|
2
|
+
class FormatCell < FormatLine
|
3
|
+
|
4
|
+
def initialize(object, args, type = nil, block = nil)
|
5
|
+
@object, @type, @block = object, type, block
|
6
|
+
if args.is_a? Array
|
7
|
+
@options, @html_options = get_cell_and_html_options( args.extract_options! )
|
8
|
+
@ask = args.first
|
9
|
+
if @ask.nil? and @options.has_key?(:action)
|
10
|
+
@type = :action
|
11
|
+
@ask = @options[:action]
|
12
|
+
end
|
13
|
+
else
|
14
|
+
@ask = args
|
15
|
+
end
|
16
|
+
set_default_options
|
17
|
+
can_sort_column?
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return a td with the formated value or action for columns
|
21
|
+
def render_cell_tbody
|
22
|
+
if @type == :action
|
23
|
+
cell_value = action_link_to(@ask)
|
24
|
+
elsif @ask
|
25
|
+
cell_value = (@ask.is_a?(Symbol)) ? format_cell_value(@object[@ask], @ask) : format_cell_value(@ask)
|
26
|
+
else
|
27
|
+
cell_value = @block
|
28
|
+
end
|
29
|
+
cell_value = action_link_to(@options[:action], cell_value) if @type != :action and @options.has_key?(:action)
|
30
|
+
content_tag(:td, cell_value, @html_options)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return a td with the formated value or action for headers
|
34
|
+
def render_cell_thead
|
35
|
+
if @ask
|
36
|
+
cell_value = (@ask.is_a?(Symbol)) ? I18n.t(@ask, {}, :header) : @ask
|
37
|
+
else
|
38
|
+
cell_value = @block
|
39
|
+
end
|
40
|
+
if @can_sort
|
41
|
+
sort_on = @options[:sort_as] || @ask
|
42
|
+
@html_options.merge!(:class => "#{@html_options[:class]} #{sorting_html_class(sort_on)}".strip)
|
43
|
+
content_tag(:th, sort_link_to(cell_value, sort_on), @html_options)
|
44
|
+
else
|
45
|
+
content_tag(:th, cell_value, @html_options)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def render_cell_tfoot
|
50
|
+
if @ask
|
51
|
+
cell_value = (@ask.is_a?(Symbol)) ? I18n.t(@ask, {}, :footer) : @ask
|
52
|
+
else
|
53
|
+
cell_value = @block
|
54
|
+
end
|
55
|
+
cell_value = action_link_to(@options[:action], cell_value) if @type != :action and @options.has_key?(:action)
|
56
|
+
content_tag(:td, cell_value, @html_options)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Return options and html options for a cell
|
62
|
+
def get_cell_and_html_options(options)
|
63
|
+
return options, options.delete(:html) || {}
|
64
|
+
end
|
65
|
+
|
66
|
+
# set to true if column is sortable
|
67
|
+
def can_sort_column?
|
68
|
+
@can_sort = true if @options[:sort] and model_have_column?(@options[:sort_as] || @ask)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Set default options for cell
|
72
|
+
# Set an empty hash if no html options
|
73
|
+
# Set an empty hash if no options
|
74
|
+
# Set sort to true if no options sort
|
75
|
+
def set_default_options
|
76
|
+
@html_options = {} unless defined? @html_options
|
77
|
+
@options = {} unless defined? @options
|
78
|
+
@html_options = format_options_to_cell(@html_options, @options)
|
79
|
+
@options[:sort] = @@options[:sort] if !@options.has_key? :sort
|
80
|
+
end
|
81
|
+
|
82
|
+
# Create the link for actions
|
83
|
+
# Set the I18n translation or the given block for the link's name
|
84
|
+
def action_link_to(action, block = nil)
|
85
|
+
object_or_array = @@object_or_array.clone
|
86
|
+
object_or_array.push @object
|
87
|
+
return case action.to_sym
|
88
|
+
when :delete
|
89
|
+
create_link_to(block || I18n.t(:delete), object_or_array, @@options[:link_remote], :delete, I18n.t(:confirm_delete))
|
90
|
+
when :show
|
91
|
+
create_link_to(block || I18n.t(:show), object_or_array, @@options[:link_remote])
|
92
|
+
else
|
93
|
+
object_or_array.insert(0, action)
|
94
|
+
create_link_to(block || I18n.t(@ask), object_or_array, @@options[:link_remote])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Create sorting link
|
99
|
+
def sort_link_to(name, sort_on)
|
100
|
+
create_link_to(name, sort_url(sort_on), @@options[:sort_remote])
|
101
|
+
end
|
102
|
+
|
103
|
+
# Create the link based on object
|
104
|
+
# Set an ajax link if option link_remote is set to true
|
105
|
+
# Compatible with rails 2 and 3.
|
106
|
+
def create_link_to(block, url, remote, method = nil, confirm = nil)
|
107
|
+
if remote and Tools::rails3?
|
108
|
+
return link_to(block, url, :method => method, :confirm => confirm, :remote => true)
|
109
|
+
elsif remote
|
110
|
+
method = :get if method.nil?
|
111
|
+
return link_to_remote(block, { :url => url, :method => method, :confirm => confirm })
|
112
|
+
end
|
113
|
+
link_to(block, url, :method => method, :confirm => confirm)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Return a string with html class of sorting for headers
|
117
|
+
# The html class is based on option: SortingTableFor::TableBuilder.html_sorting_class
|
118
|
+
def sorting_html_class(sort_on)
|
119
|
+
return TableBuilder.html_sorting_class.first if current_sorting(sort_on).nil?
|
120
|
+
return TableBuilder.html_sorting_class.second if current_sorting(sort_on) == :asc
|
121
|
+
TableBuilder.html_sorting_class.third
|
122
|
+
end
|
123
|
+
|
124
|
+
# Return an url for sorting
|
125
|
+
# Add the param sorting_table[name]=direction to the url
|
126
|
+
# Add the default direction: :asc
|
127
|
+
def sort_url(sort_on)
|
128
|
+
url_params = @@params.clone
|
129
|
+
if url_params.has_key? TableBuilder.params_sort_table
|
130
|
+
if url_params[TableBuilder.params_sort_table].has_key? sort_on
|
131
|
+
url_params[TableBuilder.params_sort_table][sort_on] = inverse_sorting(sort_on)
|
132
|
+
return url_for(url_params)
|
133
|
+
end
|
134
|
+
url_params[TableBuilder.params_sort_table].delete sort_on
|
135
|
+
end
|
136
|
+
url_params[TableBuilder.params_sort_table] = { sort_on => :asc }
|
137
|
+
url_for(url_params)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Return a symbol of the current sorting (:asc, :desc, nil)
|
141
|
+
def current_sorting(sort_on)
|
142
|
+
if @@params.has_key? TableBuilder.params_sort_table and @@params[TableBuilder.params_sort_table].has_key? sort_on
|
143
|
+
return @@params[TableBuilder.params_sort_table][sort_on].to_sym
|
144
|
+
end
|
145
|
+
nil
|
146
|
+
end
|
147
|
+
|
148
|
+
# Return a symbol, the inverse of the current sorting
|
149
|
+
def inverse_sorting(sort_on)
|
150
|
+
return :asc if current_sorting(sort_on).nil?
|
151
|
+
return :desc if current_sorting(sort_on) == :asc
|
152
|
+
:asc
|
153
|
+
end
|
154
|
+
|
155
|
+
# Return the formated cell's value
|
156
|
+
def format_cell_value(value, attribute = nil)
|
157
|
+
unless (ret_value = format_cell_value_as_ask(value)).nil?
|
158
|
+
return ret_value
|
159
|
+
end
|
160
|
+
format_cell_value_as_type(value, attribute)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Format the value if option :as is set
|
164
|
+
def format_cell_value_as_ask(value)
|
165
|
+
return nil if !@options or @options.empty? or !@options.has_key?(:as)
|
166
|
+
return case @options[:as]
|
167
|
+
when :date then ::I18n.l(value.to_date, :format => @options[:format] || TableBuilder.i18n_default_format_date)
|
168
|
+
when :time then ::I18n.l(value.to_datetime, :format => @options[:format] || TableBuilder.i18n_default_format_date)
|
169
|
+
when :currency then number_to_currency(value)
|
170
|
+
else nil
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Format the value based on value's type
|
175
|
+
def format_cell_value_as_type(value, attribute)
|
176
|
+
if value.is_a?(Time) or value.is_a?(Date)
|
177
|
+
return ::I18n.l(value, :format => @options[:format] || TableBuilder.i18n_default_format_date)
|
178
|
+
elsif TableBuilder.currency_columns.include?(attribute)
|
179
|
+
return number_to_currency(value)
|
180
|
+
elsif value.is_a?(TrueClass)
|
181
|
+
return TableBuilder.default_boolean.first
|
182
|
+
elsif value.is_a?(FalseClass)
|
183
|
+
return TableBuilder.default_boolean.second
|
184
|
+
end
|
185
|
+
value
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module SortingTableFor
|
2
|
+
class FormatLine < TableBuilder
|
3
|
+
|
4
|
+
def initialize(args, column_options = {}, html_options = {}, object = nil, type = nil)
|
5
|
+
@args, @column_options, @html_options, @object, @type = args, column_options, html_options, object, type
|
6
|
+
@cells = []
|
7
|
+
if object
|
8
|
+
@attributes = (args.empty?) ? (get_columns - TableBuilder.reserved_columns) : @args
|
9
|
+
create_cells
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create a new cell with the class FormatCell
|
14
|
+
# Add the object in @cells
|
15
|
+
def add_cell(object, args, type = nil, block = nil)
|
16
|
+
@cells << FormatCell.new(object, args, type, block)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return a tr line based on the type (:thead, :tbody or :tfoot)
|
20
|
+
def render_line
|
21
|
+
if @type == :thead
|
22
|
+
return content_tag(:tr, Tools::html_safe(@cells.collect { |cell| cell.render_cell_thead }.join), @html_options)
|
23
|
+
elsif @type == :tfoot
|
24
|
+
return content_tag(:tr, Tools::html_safe(@cells.collect { |cell| cell.render_cell_tfoot }.join), @html_options)
|
25
|
+
else
|
26
|
+
content_tag(:tr, Tools::html_safe(@cells.collect { |cell| cell.render_cell_tbody }.join), @html_options.merge(:class => "#{@html_options[:class]} #{@@template.cycle(:odd, :even)}".strip))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return the number of cells in line
|
31
|
+
def total_cells
|
32
|
+
@cells.size
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
# Return each column in the model's database table
|
38
|
+
def content_columns
|
39
|
+
model_name(@object).constantize.content_columns.collect { |c| c.name.to_sym }.compact rescue []
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return true if the column is in the model's database table
|
43
|
+
def model_have_column?(column)
|
44
|
+
model_name(@object).constantize.content_columns.each do |model_column|
|
45
|
+
return true if model_column.name == column.to_s
|
46
|
+
end
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
# Options only for cells
|
51
|
+
def only_cell_option?(key)
|
52
|
+
[:colspan].include? key
|
53
|
+
end
|
54
|
+
|
55
|
+
# Format ask to send options to cell
|
56
|
+
def format_options_to_cell(ask, options = @column_options)
|
57
|
+
options.each do |key, value|
|
58
|
+
if only_cell_option?(key)
|
59
|
+
if ask.is_a? Hash
|
60
|
+
ask.merge!(key => value)
|
61
|
+
else
|
62
|
+
ask = [ask] unless ask.is_a? Array
|
63
|
+
(ask.last.is_a? Hash and ask.last.has_key? :html) ? ask.last[:html].merge!(key => value) : ask << { :html => { key => value }}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
ask
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Call after headers or columns with no attributes (table.headers)
|
73
|
+
# Create all the cells based on each column in the model's database table
|
74
|
+
# Create cell's actions based on option default_actions or on actions given (:actions => [:edit])
|
75
|
+
def create_cells
|
76
|
+
@attributes.each { |ask| add_cell(@object, format_options_to_cell(ask)) }
|
77
|
+
if @args.empty?
|
78
|
+
TableBuilder.default_actions.each { |action| add_cell(@object, action, :action) }
|
79
|
+
else
|
80
|
+
get_column_actions.each { |action| add_cell(@object, action, :action) }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Return an Array of all actions given to headers or columns (:actions => [:edit, :delete])
|
85
|
+
def get_column_actions
|
86
|
+
if @column_options.has_key? :actions
|
87
|
+
if @column_options[:actions].is_a?(Array)
|
88
|
+
return @column_options[:actions]
|
89
|
+
else
|
90
|
+
return [ @column_options[:actions] ]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
[]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Return an Array of the columns based on options :only or :except
|
97
|
+
def get_columns
|
98
|
+
if @column_options.has_key? :only
|
99
|
+
return @column_options[:only] if @column_options[:only].is_a?(Array)
|
100
|
+
[ @column_options[:only] ]
|
101
|
+
elsif @column_options.has_key? :except
|
102
|
+
return content_columns - @column_options[:except] if @column_options[:except].is_a?(Array)
|
103
|
+
content_columns - [ @column_options[:except] ]
|
104
|
+
else
|
105
|
+
content_columns
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
@@ -5,21 +5,28 @@ module SortingTableFor
|
|
5
5
|
class << self
|
6
6
|
|
7
7
|
# Set options to create a default scope
|
8
|
-
def set_options(params, model_name,
|
9
|
-
@
|
8
|
+
def set_options(params, model_name, i18n_active = true)
|
9
|
+
@model_name, @i18n_active = model_name, i18n_active
|
10
|
+
@action = (params.has_key? :action) ? params[:action].downcase : ''
|
11
|
+
@controller = (params.has_key? :controller) ? params[:controller].downcase : ''
|
12
|
+
if @controller.include? '/'
|
13
|
+
@namespace = @controller.split '/'
|
14
|
+
@controller = @namespace.pop
|
15
|
+
end
|
10
16
|
end
|
11
17
|
|
12
|
-
# Translate
|
13
18
|
# Add a default scope if option scope isn't defined
|
14
19
|
def translate(attribute, options = {}, type = nil)
|
15
|
-
|
20
|
+
unless @i18n_active
|
16
21
|
return options[:value] if options.has_key? :value
|
17
22
|
return attribute
|
18
23
|
end
|
19
|
-
|
20
|
-
options[:scope] = create_scope
|
24
|
+
unless options.has_key? :scope
|
25
|
+
options[:scope] = create_scope
|
21
26
|
options[:scope] << TableBuilder.i18n_add_header_action_scope if type and type == :header
|
22
27
|
options[:scope] << TableBuilder.i18n_add_footer_action_scope if type and type == :footer
|
28
|
+
options[:scope] << options.delete(:add_scope)
|
29
|
+
options[:scope].flatten!
|
23
30
|
end
|
24
31
|
::I18n.t(attribute, options)
|
25
32
|
end
|
@@ -31,10 +38,10 @@ module SortingTableFor
|
|
31
38
|
def create_scope
|
32
39
|
return TableBuilder.i18n_default_scope.collect do |scope_value|
|
33
40
|
case scope_value.to_sym
|
34
|
-
when :controller then @
|
35
|
-
when :action then @
|
41
|
+
when :controller then @controller
|
42
|
+
when :action then @action
|
36
43
|
when :model then @model_name ? @model_name.downcase.to_s : ''
|
37
|
-
when :namespace then @namespace ? @namespace
|
44
|
+
when :namespace then @namespace ? @namespace : ''
|
38
45
|
else scope_value.to_s
|
39
46
|
end
|
40
47
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module SortingTableFor
|
4
|
+
module ModelScope
|
5
|
+
|
6
|
+
# Include the methods in models
|
7
|
+
def self.included(base)
|
8
|
+
base.class_eval do
|
9
|
+
extend SingletonMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module SingletonMethods
|
14
|
+
|
15
|
+
# Return a scope of the object with an order
|
16
|
+
#
|
17
|
+
# === Usage
|
18
|
+
#
|
19
|
+
# sorting_table(the params) - Sorting by the given parameters
|
20
|
+
# sorting_table(the params, column name) - Sort by the column name with direction ascending, if no parameters
|
21
|
+
# sorting_table(the params, column name, direction) - Sort by the column name with the given direction, if no parameters
|
22
|
+
#
|
23
|
+
# === Exemples
|
24
|
+
#
|
25
|
+
# User.sorting_table(params)
|
26
|
+
# User.sorting_table(params, :username)
|
27
|
+
# User.sorting_table(params, :username, :desc)
|
28
|
+
#
|
29
|
+
def sorting_table(*args)
|
30
|
+
raise ArgumentError, 'sorting_table: Too many arguments (max : 3)' if args.size > 3
|
31
|
+
sort_table_param = get_sorting_table_params(args)
|
32
|
+
return scoped({}) if !sort_table_param and args.size == 1
|
33
|
+
sort, direction = get_sort_and_direction(sort_table_param, args)
|
34
|
+
return scoped({}) if !sort or !valid_column?(sort) or !valid_direction?(direction)
|
35
|
+
scoped({ :order => "#{sort} #{direction}" })
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Return the params for sorting table
|
41
|
+
def get_sorting_table_params(args)
|
42
|
+
return nil unless args.first.is_a? Hash
|
43
|
+
return nil unless args.first.has_key? SortingTableFor::TableBuilder.params_sort_table.to_s
|
44
|
+
args.first[SortingTableFor::TableBuilder.params_sort_table.to_s]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Parse the params and return the column name and the direction
|
48
|
+
def get_sort_and_direction(sort_table_param, args)
|
49
|
+
if sort_table_param
|
50
|
+
key = sort_table_param.keys.first rescue nil
|
51
|
+
value = sort_table_param.values.first rescue nil
|
52
|
+
return nil if !key.is_a?(String) or !value.is_a?(String)
|
53
|
+
return key, value
|
54
|
+
end
|
55
|
+
return nil if args.size < 2
|
56
|
+
return args[1], 'asc' if args.size == 2
|
57
|
+
return args[1], args[2]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Return true if the column name exist
|
61
|
+
def valid_column?(column)
|
62
|
+
column_names.include? column.to_s.downcase
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return true if the direction exist
|
66
|
+
def valid_direction?(direction)
|
67
|
+
%[asc desc].include? direction.to_s.downcase
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
if defined? ActiveRecord
|
75
|
+
ActiveRecord::Base.send :include, SortingTableFor::ModelScope
|
76
|
+
end
|