sorting_table_for 0.2.0 → 0.2.1
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/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
|