Class: DataTable

Inherits:
Object
  • Object
show all
Defined in:
lib/data-table/data_table.rb,
lib/data-table/version.rb

Overview

Config Options

id: the html id title: the title of the data table subtitle: the subtitle of the data table css_class: an extra css class to get applied to the table empty_text: the text to display of the collection is empty display_header => false: hide the column headers for the data table alternate_rows => false: turn off alternating of row css classes alternate_cols => true: turn on alternating of column classes, defaults to false

columns: an array of hashes of the column specs for this table

group_by: an array of columns to group on pivot_on: an array of columns to pivot on

subtotals: an array of hashes that contain the subtotal information for each column that should be subtotaled totals: an array of hashes that contain the total information for each column that should be totaled

Constant Summary collapse

VERSION =
"1.0.1"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(collection) ⇒ DataTable

Returns a new instance of DataTable.



31
32
33
34
35
36
37
38
39
40
# File 'lib/data-table/data_table.rb', line 31

def initialize(collection)
  @collection = collection
  default_options!
  
  @columns = []
  
  @groupings, @pivot_columns = [], []
  @pivoted_data, @grouped_data = false, false
  @subtotals, @totals = {}, {}
end

Instance Attribute Details

#alternate_colsObject

Returns the value of attribute alternate_cols.



29
30
31
# File 'lib/data-table/data_table.rb', line 29

def alternate_cols
  @alternate_cols
end

#alternate_rowsObject

Returns the value of attribute alternate_rows.



29
30
31
# File 'lib/data-table/data_table.rb', line 29

def alternate_rows
  @alternate_rows
end

#css_classObject

Returns the value of attribute css_class.



29
30
31
# File 'lib/data-table/data_table.rb', line 29

def css_class
  @css_class
end

#custom_headersObject

Returns the value of attribute custom_headers.



29
30
31
# File 'lib/data-table/data_table.rb', line 29

def custom_headers
  @custom_headers
end

#display_headerObject

Returns the value of attribute display_header.



29
30
31
# File 'lib/data-table/data_table.rb', line 29

def display_header
  @display_header
end

#empty_textObject

Returns the value of attribute empty_text.



29
30
31
# File 'lib/data-table/data_table.rb', line 29

def empty_text
  @empty_text
end

#grouped_dataObject (readonly)

CONFIG



28
29
30
# File 'lib/data-table/data_table.rb', line 28

def grouped_data
  @grouped_data
end

#hide_if_emptyObject

Returns the value of attribute hide_if_empty.



29
30
31
# File 'lib/data-table/data_table.rb', line 29

def hide_if_empty
  @hide_if_empty
end

#idObject

Returns the value of attribute id.



29
30
31
# File 'lib/data-table/data_table.rb', line 29

def id
  @id
end

#pivoted_dataObject (readonly)

CONFIG



28
29
30
# File 'lib/data-table/data_table.rb', line 28

def pivoted_data
  @pivoted_data
end

#repeat_headers_for_groupsObject

Returns the value of attribute repeat_headers_for_groups.



29
30
31
# File 'lib/data-table/data_table.rb', line 29

def repeat_headers_for_groups
  @repeat_headers_for_groups
end

#subtotal_calculationsObject (readonly)

CONFIG



28
29
30
# File 'lib/data-table/data_table.rb', line 28

def subtotal_calculations
  @subtotal_calculations
end

#subtotalsObject (readonly)

CONFIG



28
29
30
# File 'lib/data-table/data_table.rb', line 28

def subtotals
  @subtotals
end

#titleObject

Returns the value of attribute title.



29
30
31
# File 'lib/data-table/data_table.rb', line 29

def title
  @title
end

#total_calculationsObject (readonly)

CONFIG



28
29
30
# File 'lib/data-table/data_table.rb', line 28

def total_calculations
  @total_calculations
end

#totalsObject (readonly)

CONFIG



28
29
30
# File 'lib/data-table/data_table.rb', line 28

def totals
  @totals
end

Class Method Details

.default_css_stylesObject



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/data-table/data_table.rb', line 59

def self.default_css_styles
  <<-CSS_STYLE
    .data_table {width: 100%; empty-cells: show}
    .data_table td, .data_table th {padding: 3px}
    
    .data_table caption {font-size: 2em; font-weight: bold}
    
    .data_table thead {}
    .data_table thead th {background-color: #ddd; border-bottom: 1px solid #bbb;}
    
    .data_table tbody {}
    .data_table tbody tr.alt {background-color: #eee;}
    
    .data_table .group_header th {text-align: left;}
    
    .data_table .subtotal {}
    .data_table .subtotal td {border-top: 1px solid #000;}
    
    .data_table tfoot {}
    .data_table tfoot td {border-top: 1px solid #000;}
    
    .empty_data_table {text-align: center; background-color: #ffc;}
    
    /* Data Types */
    .data_table .number, .data_table .money {text-align: right}
    .data_table .text {text-align: left}
  CSS_STYLE
end

.render(collection) {|t| ... } ⇒ Object

GENERAL RENDERING

Yields:

  • (t)


105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/data-table/data_table.rb', line 105

def self.render(collection, &blk)
  # make a new table
  t = self.new(collection)
  
  # yield it to the block for configuration
  yield t
  
  # modify the data structure if necessary and do calculations
  t.prepare_data
  
  # render the table
  t.render.html_safe
end

Instance Method Details

#calculate(data, column_name, function) ⇒ Object



348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/data-table/data_table.rb', line 348

def calculate(data, column_name, function)
  
  col = @columns.select { |column| column.name == column_name }
  
  if function.is_a?(Proc)
    case function.arity
      when 1; function.call(data)
      when 2; function.call(data, col.first)
    end
  elsif function.is_a?(Array)
    result = self.send("calculate_#{function[0].to_s}", data, column_name)
    case function[1].arity
      when 1; function[1].call(result)
      when 2; function[1].call(result, col.first)
    end
  else
    self.send("calculate_#{function.to_s}", data, column_name)
  end
end

#calculate_avg(collection, column_name) ⇒ Object



372
373
374
375
# File 'lib/data-table/data_table.rb', line 372

def calculate_avg(collection, column_name)
  sum = calculate_sum(collection, column_name)
  sum / collection.size
end

#calculate_max(collection, column_name) ⇒ Object



377
378
379
# File 'lib/data-table/data_table.rb', line 377

def calculate_max(collection, column_name)
  collection.collect{|r| r[column_name].to_f }.max
end

#calculate_min(collection, column_name) ⇒ Object



381
382
383
# File 'lib/data-table/data_table.rb', line 381

def calculate_min(collection, column_name)
  collection.collect{|r| r[column_name].to_f }.min
end

#calculate_subtotals!Object



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/data-table/data_table.rb', line 331

def calculate_subtotals!
  @subtotal_calculations = Hash.new { |h,k| h[k] = {} }
  
  #ensure that we are dealing with a grouped results set.
  unless @grouped_data
    raise 'Subtotals only work with grouped results sets'
  end
  
  @collection.each do |group_name, group_data|
    @subtotals.each do |column_name, function|
      result = calculate(group_data, column_name, function)
      @subtotal_calculations[group_name][column_name] = result
    end
  end
  
end

#calculate_sum(collection, column_name) ⇒ Object



368
369
370
# File 'lib/data-table/data_table.rb', line 368

def calculate_sum(collection, column_name)
  collection.inject(0) {|sum, row| sum += row[column_name].to_f }
end

#calculate_totals!Object



321
322
323
324
325
326
327
328
329
# File 'lib/data-table/data_table.rb', line 321

def calculate_totals!
  @total_calculations = {}
  
  @totals.each do |column_name, function|
    collection = @collection.is_a?(Hash) ? @collection.values.flatten : @collection
    result = calculate(collection, column_name, function)
    @total_calculations[column_name] = result
  end
end

#column(id, title = "", opts = {}, &b) ⇒ Object

Define a new column for the table



89
90
91
# File 'lib/data-table/data_table.rb', line 89

def column(id, title="", opts={}, &b)
  @columns << DataTableColumn.new(id, title, opts, &b)
end

#custom_header(&blk) ⇒ Object



198
199
200
# File 'lib/data-table/data_table.rb', line 198

def custom_header(&blk)
  instance_eval(&blk)
end

#default_options!Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/data-table/data_table.rb', line 42

def default_options!
  @id = ''
  @title = ''
  @subtitle = ''
  @css_class = ''
  @empty_text = 'No records found'
  @hide_if_empty = false
  @display_header = true
  @alternate_rows = true
  @alternate_cols = false
  @subtotal_title = "Subtotal:"
  @total_title = "Total:"
  @repeat_headers_for_groups = false
  @custom_headers = []
  @row_attributes = nil
end

#group_by(group_column, &blk) ⇒ Object

TODO: allow for group column only, block only and group column and block



215
216
217
218
219
# File 'lib/data-table/data_table.rb', line 215

def group_by(group_column, &blk)
  @grouped_data = true
  @groupings = group_column
  @columns.reject!{|c| c.name == group_column}
end

#group_data!Object



221
222
223
# File 'lib/data-table/data_table.rb', line 221

def group_data!
  @collection = @collection.group_by {|row| row[@groupings] }
end

#has_subtotals?Boolean

Returns:

  • (Boolean)


300
301
302
# File 'lib/data-table/data_table.rb', line 300

def has_subtotals?
  !@subtotals.empty?
end

#has_totals?Boolean

Returns:

  • (Boolean)


317
318
319
# File 'lib/data-table/data_table.rb', line 317

def has_totals?
  !@totals.empty?
end

#pivot_data!Object



264
265
266
# File 'lib/data-table/data_table.rb', line 264

def pivot_data!
  @collection.pivot_on
end

#pivot_on(pivot_column) ⇒ Object

PIVOTING



259
260
261
262
# File 'lib/data-table/data_table.rb', line 259

def pivot_on(pivot_column)
  @pivoted_data = true
  @pivot_column = pivot_column
end

#prepare_dataObject



93
94
95
96
97
98
99
# File 'lib/data-table/data_table.rb', line 93

def prepare_data
  self.pivot_data! if @pivoted_data
  self.group_data! if @grouped_data
  
  self.calculate_subtotals! if has_subtotals?
  self.calculate_totals! if has_totals?
end

#renderObject



119
120
121
# File 'lib/data-table/data_table.rb', line 119

def render
  render_data_table
end

#render_custom_table_headerObject



148
149
150
151
152
153
154
# File 'lib/data-table/data_table.rb', line 148

def render_custom_table_header
  html = "<tr>"
    @custom_headers.each do |h|
      html << "<th class=\"#{h[:css]}\" colspan=\"#{h[:colspan]}\">#{h[:text]}</th>"
    end
  html << "</tr>"
end

#render_data_tableObject



123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/data-table/data_table.rb', line 123

def render_data_table
  html = "<table id='#{@id}' class='data_table #{@css_class}' cellspacing='0' cellpadding='0'>"
    html << "<caption>#{@title}</caption>" if @title
    html << render_data_table_header if @display_header
    if @collection.any?
      html << render_data_table_body(@collection)
      html << render_totals if has_totals?
    else
      html << "<tr><td class='empty_data_table' colspan='#{@columns.size}'>#{@empty_text}</td></tr>"
    end
  html << "</table>"
end

#render_data_table_body(collection) ⇒ Object



156
157
158
159
160
161
162
# File 'lib/data-table/data_table.rb', line 156

def render_data_table_body(collection)
  if @grouped_data
    render_grouped_data_table_body(collection)
  else
    "<tbody>#{render_rows(collection)}</tbody>"
  end
end

#render_data_table_headerObject



136
137
138
139
140
141
142
143
144
145
146
# File 'lib/data-table/data_table.rb', line 136

def render_data_table_header
  html = "<thead>"
  
  html << render_custom_table_header unless @custom_headers.empty?
  
  html << "<tr>"
    @columns.each do |col|
      html << col.render_column_header
    end
  html << "</tr></thead>"
end

#render_group(group_header, group_data) ⇒ Object



246
247
248
249
250
251
252
# File 'lib/data-table/data_table.rb', line 246

def render_group(group_header, group_data)
  html = "<tbody class='#{group_header.to_s.downcase.gsub(/[^A-Za-z0-9]+/, '_')}'>" #replace non-letters and numbers with '_'
    html << render_group_header(group_header)
    html << render_rows(group_data)
    html << render_subtotals(group_header, group_data) if has_subtotals?
  html << "</tbody>"
end

#render_group_header(group_header) ⇒ Object



233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/data-table/data_table.rb', line 233

def render_group_header(group_header)
  html = "<tr class='group_header'>"
  if @repeat_headers_for_groups 
    @columns.each_with_index do |col, i|
      html << (i == 0 ? "<th>#{group_header}</th>" : col.render_column_header)
    end
  else
    html << "<th colspan='#{@columns.size}'>#{group_header}</th>"
  end
  html << "</tr>"
  html
end

#render_grouped_data_table_body(collection) ⇒ Object



225
226
227
228
229
230
231
# File 'lib/data-table/data_table.rb', line 225

def render_grouped_data_table_body(collection)
  html = ""
  collection.keys.each do |group_name|
    html << render_group(group_name, collection[group_name])
  end
  html
end

#render_row(row, row_index, css_class = '', row_attributes = {}) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/data-table/data_table.rb', line 178

def render_row(row, row_index, css_class='', row_attributes={})
  if row_attributes.nil?
    attributes = ''
  else
    attributes = row_attributes.map {|attr, val| "#{attr}='#{val}'"}.join " "
  end
  
  html = "<tr class='row_#{row_index} #{css_class}' #{attributes}>"
    @columns.each_with_index do |col, col_index|
      cell = row[col.name] rescue nil
      html << col.render_cell(cell, row, row_index, col_index)
    end
  html << "</tr>"
end

#render_rows(collection) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/data-table/data_table.rb', line 164

def render_rows(collection)
  html = ""
  collection.each_with_index do |row, row_index|
    css_class = @alternate_rows && row_index % 2 == 1 ? 'alt ' : ''
    if @row_style && style = @row_style.call(row, row_index)
      css_class << style
    end

    attributes = @row_attributes.nil? ? {} : @row_attributes.call(row)
    html << render_row(row, row_index, css_class, attributes)
  end
  html
end

#render_subtotals(group_header, group_data) ⇒ Object



279
280
281
282
283
284
285
# File 'lib/data-table/data_table.rb', line 279

def render_subtotals(group_header, group_data)
  html = "<tr class='subtotal'>"
    @columns.each do |col|
      html << col.render_cell(@subtotal_calculations[group_header][col.name])
    end
  html << "</tr>"
end

#render_totalsObject

TOTALS AND SUBTOTALS



271
272
273
274
275
276
277
# File 'lib/data-table/data_table.rb', line 271

def render_totals
  html = "<tfoot><tr>"
    @columns.each do |col|
      html << col.render_cell(@total_calculations[col.name])
    end
  html << "</tr></tfoot>"
end

#row_attributes(&b) ⇒ Object



206
207
208
# File 'lib/data-table/data_table.rb', line 206

def row_attributes(&b)
  @row_attributes = b
end

#row_style(&b) ⇒ Object

define a custom block to be used to determine the css class for a row.



194
195
196
# File 'lib/data-table/data_table.rb', line 194

def row_style(&b)
  @row_style = b
end

#subtotal(column_name, function = nil, &b) ⇒ Object

define a new total column definition. total columns take the name of the column that should be totaled they also take a default aggregate function name and/or a block if only a default function is given, then it is used to calculate the total if only a block is given then only it is used to calculated the total if both a block and a function are given then the default aggregate function is called first then its result is passed into the block for further processing.



294
295
296
297
298
# File 'lib/data-table/data_table.rb', line 294

def subtotal(column_name, function=nil, &b)
  function_or_block = function || b
  f = function && block_given? ? [function, b] : function_or_block
  @subtotals.merge!({column_name => f})
end

#th(header_text, options) ⇒ Object



202
203
204
# File 'lib/data-table/data_table.rb', line 202

def th(header_text, options)
  @custom_headers << options.merge(:text => header_text)
end

#total(column_name, function = nil, &b) ⇒ Object

define a new total column definition. total columns take the name of the column that should be totaled they also take a default aggregate function name and/or a block if only a default function is given, then it is used to calculate the total if only a block is given then only it is used to calculated the total if both a block and a function are given then the default aggregate function is called first then its result is passed into the block for further processing.



311
312
313
314
315
# File 'lib/data-table/data_table.rb', line 311

def total(column_name, function=nil, &b)
  function_or_block = function || b
  f = function && block_given? ? [function, b] : function_or_block
  @totals.merge!({column_name => f})
end