From be8acd569badd9bc7117cdf610b356393ffb24f9 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Thu, 9 Feb 2023 13:27:26 -0700 Subject: [PATCH 001/137] Version 4.15.0 --- lib/effective_datatables/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/effective_datatables/version.rb b/lib/effective_datatables/version.rb index a3553a5d..417053e8 100644 --- a/lib/effective_datatables/version.rb +++ b/lib/effective_datatables/version.rb @@ -1,3 +1,3 @@ module EffectiveDatatables - VERSION = '4.14.2'.freeze + VERSION = '4.15.0'.freeze end From 3a8c94b12ffe2ff4c98f23eed315795395b74ba2 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Wed, 15 Feb 2023 12:32:05 -0700 Subject: [PATCH 002/137] tweak the value tool search column --- app/models/effective/datatable_value_tool.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/effective/datatable_value_tool.rb b/app/models/effective/datatable_value_tool.rb index e11a7791..cc8c85de 100644 --- a/app/models/effective/datatable_value_tool.rb +++ b/app/models/effective/datatable_value_tool.rb @@ -95,7 +95,9 @@ def search_column(collection, original, column, index) value = obj_to_value(row[index], column, row) next if value.nil? || value == '' - case column[:as] + obj_equals = obj.respond_to?(column[:name]) && obj.send(column[:name]).to_s.downcase == term_downcased + + obj_equals || case column[:as] when :boolean if fuzzy term ? (obj == true) : (obj != true) From fb481614883d77e8ee2b517963a58765c041d7f4 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Wed, 15 Feb 2023 12:32:26 -0700 Subject: [PATCH 003/137] Version 4.15.1 --- lib/effective_datatables/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/effective_datatables/version.rb b/lib/effective_datatables/version.rb index 417053e8..fc583300 100644 --- a/lib/effective_datatables/version.rb +++ b/lib/effective_datatables/version.rb @@ -1,3 +1,3 @@ module EffectiveDatatables - VERSION = '4.15.0'.freeze + VERSION = '4.15.1'.freeze end From 10df42552ddcea9e84086a8848a615bfc2f416bf Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Thu, 2 Mar 2023 14:45:36 -0700 Subject: [PATCH 004/137] Adjust text-align styles --- .../effective_datatables/_overrides.bootstrap4.scss | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/effective_datatables/_overrides.bootstrap4.scss b/app/assets/stylesheets/effective_datatables/_overrides.bootstrap4.scss index 4be2ed01..07392167 100644 --- a/app/assets/stylesheets/effective_datatables/_overrides.bootstrap4.scss +++ b/app/assets/stylesheets/effective_datatables/_overrides.bootstrap4.scss @@ -204,9 +204,9 @@ table.dataTable.hide-buttons { // Column specific adjustments table.dataTable { - td.col-price { text-align: right; } - td.col-decimal { text-align: right; } - td.col-right { text-align: right; } + th.col-price, td.col-price { text-align: right; } + th.col-decimal, td.col-decimal { text-align: right; } + th.col-right, td.col-right { text-align: right; } td.col-bulk_actions { label { @@ -232,5 +232,3 @@ table.dataTable { td.col-resource_item { word-break: keep-all; } } - - From c74279a11ff0b26397d60a238e4317a8e2f96a58 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Thu, 2 Mar 2023 14:45:48 -0700 Subject: [PATCH 005/137] Version 4.15.2 --- lib/effective_datatables/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/effective_datatables/version.rb b/lib/effective_datatables/version.rb index fc583300..f84f27f4 100644 --- a/lib/effective_datatables/version.rb +++ b/lib/effective_datatables/version.rb @@ -1,3 +1,3 @@ module EffectiveDatatables - VERSION = '4.15.1'.freeze + VERSION = '4.15.2'.freeze end From dae38f48340449a986d2a6bfd58ce41d8163ea33 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Wed, 8 Mar 2023 12:42:24 -0700 Subject: [PATCH 006/137] add date filters --- .../effective_datatables_private_helper.rb | 6 ++-- app/models/effective/datatable.rb | 24 +++++++++++++++ .../effective/datatable_filters_form.rb | 21 ++++++++++++++ .../effective_datatable/dsl/filters.rb | 23 +++++++++++++++ .../datatables/_filter_date_range.html.haml | 29 +++++++++++++++++++ .../effective/datatables/_filters.html.haml | 8 +++-- 6 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 app/models/effective/datatable_filters_form.rb create mode 100644 app/views/effective/datatables/_filter_date_range.html.haml diff --git a/app/helpers/effective_datatables_private_helper.rb b/app/helpers/effective_datatables_private_helper.rb index 632dd146..a00b30ab 100644 --- a/app/helpers/effective_datatables_private_helper.rb +++ b/app/helpers/effective_datatables_private_helper.rb @@ -128,14 +128,16 @@ def render_datatable_filters(datatable) return unless datatable._scopes.present? || datatable._filters.present? if datatable._filters_form_required? - render partial: 'effective/datatables/filters', locals: { datatable: datatable } + render('effective/datatables/filters', datatable: datatable) else - render(partial: 'effective/datatables/filters', locals: { datatable: datatable }).gsub('', '/div>').html_safe + render('effective/datatables/filters', datatable: datatable).gsub('', '/div>').html_safe end end def datatable_filter_tag(form, datatable, name, opts) + return if opts[:visible] == false + as = opts[:as].to_s.chomp('_field').to_sym value = datatable.state[:filter][name] collection = opts[:collection] diff --git a/app/models/effective/datatable.rb b/app/models/effective/datatable.rb index e0672e2e..3d3d9722 100644 --- a/app/models/effective/datatable.rb +++ b/app/models/effective/datatable.rb @@ -199,6 +199,26 @@ def to_param "#{self.class.name.underscore.parameterize}-#{[self.class, attributes].hash.abs.to_s.last(12)}" end + def date_range(value = nil) + now = Time.zone.now + + value ||= filters[:date_range] + start_date ||= filters[:start_date] + end_date ||= filters[:end_date] + + return (nil..nil) if value.blank? + + case value.to_sym + when :current_month then (now.beginning_of_month..now.end_of_day) + when :current_year then (now.beginning_of_year..now.end_of_day) + when :last_month then (now - 1.month).all_month + when :last_year then (now - 1.year).all_year + when :custom then (start_date&.beginning_of_day..end_date&.end_of_day) + else + raise('unexpected date range value') + end + end + def columns @_columns end @@ -223,6 +243,10 @@ def default_visibility columns.values.inject({}) { |h, col| h[col[:index]] = col[:visible]; h } end + def filters_form + DatatableFiltersForm.new(datatable: self) + end + private def column_tool diff --git a/app/models/effective/datatable_filters_form.rb b/app/models/effective/datatable_filters_form.rb new file mode 100644 index 00000000..4ff93c0e --- /dev/null +++ b/app/models/effective/datatable_filters_form.rb @@ -0,0 +1,21 @@ +# Form Object for the filters form + +module Effective + class DatatableFiltersForm + include ActiveModel::Model + + attr_accessor :scope + + def initialize(datatable:) + # Assign the current value of scope + assign_attributes(scope: datatable.state[:scope]) + + # Create an attr_accesor for each filter and assign value + datatable._filters.each do |name, options| + self.class.send(:attr_accessor, name) + assign_attributes(name => datatable.state[:filter][name]) + end + end + + end +end diff --git a/app/models/effective/effective_datatable/dsl/filters.rb b/app/models/effective/effective_datatable/dsl/filters.rb index 39739ec9..fb0c8ce2 100644 --- a/app/models/effective/effective_datatable/dsl/filters.rb +++ b/app/models/effective/effective_datatable/dsl/filters.rb @@ -4,6 +4,29 @@ module Effective module EffectiveDatatable module Dsl module Filters + + DATE_RANGES = [ + ['Current month', :current_month], + ['Last month', :last_month], + ['Current year', :current_year], + ['Last year', :last_year], + ['Custom', :custom], + ] + + # This sets up the select field with start_on and end_on + def filter_date_range(default = nil) + if default.present? + valid = DATE_RANGES.map(&:last) + raise("unexpected value #{default}. Try one of #{valid.to_sentence}") unless valid.include?(default) + end + + (default_start_date, default_end_date) = datatable.date_range(default) + + filter :date_range, default, collection: DATE_RANGES, partial: 'effective/datatables/filter_date_range' + filter :start_date, default_start_date, as: :date, visible: false + filter :end_date, default_end_date, as: :date, visible: false + end + def filter(name = nil, value = :_no_value, as: nil, label: nil, parse: nil, required: false, **input_html) return datatable.filter if (name == nil && value == :_no_value) # This lets block methods call 'filter' and get the values diff --git a/app/views/effective/datatables/_filter_date_range.html.haml b/app/views/effective/datatables/_filter_date_range.html.haml new file mode 100644 index 00000000..c695269b --- /dev/null +++ b/app/views/effective/datatables/_filter_date_range.html.haml @@ -0,0 +1,29 @@ +- name = '' unless datatable._filters_form_required? +- now = Time.zone.now + += f.select :date_range, opts[:collection], autocomplete: 'off', feedback: false + +.mx-2 + += f.show_if :date_range, :current_month do + = f.static_field :dates do + #{now.beginning_of_month.strftime('%F')} to #{now.strftime('%F')} (today) + += f.show_if :date_range, :current_year do + = f.static_field :dates do + #{now.beginning_of_year.strftime('%F')} to #{now.strftime('%F')} (today) + += f.show_if :date_range, :last_month do + = f.static_field :dates do + #{(now - 1.month).beginning_of_month.strftime('%F')} to #{(now - 1.month).end_of_month.strftime('%F')} + += f.show_if :date_range, :last_year do + = f.static_field :dates do + #{(now - 1.year).beginning_of_year.strftime('%F')} to #{(now - 1.year).end_of_year.strftime('%F')} + += f.show_if :date_range, :custom do + .row + .col + = f.date_field :start_date, name: name, autocomplete: 'off', feedback: false + .col + = f.date_field :end_date, name: name, autocomplete: 'off', feedback: false diff --git a/app/views/effective/datatables/_filters.html.haml b/app/views/effective/datatables/_filters.html.haml index 0c651ffe..527db597 100644 --- a/app/views/effective/datatables/_filters.html.haml +++ b/app/views/effective/datatables/_filters.html.haml @@ -1,11 +1,15 @@ .effective-datatables-filters{'aria-controls': datatable.to_param} - = effective_form_with(scope: :filters, url: (datatable._form[:url] || '#'), method: datatable._form[:verb], id: nil) do |form| + + = effective_form_with(model: datatable.filters_form, scope: :filters, url: (datatable._form[:url] || '#'), method: datatable._form[:verb], id: nil) do |form| .form-row.align-items-center - if datatable._scopes.present? = datatable_scope_tag(form, datatable) - datatable._filters.each do |name, opts| - = datatable_filter_tag(form, datatable, name, opts) + - if opts[:partial].present? + = render(opts[:partial], form: form, f: form, datatable: datatable, name: name, opts: opts) + - else + = datatable_filter_tag(form, datatable, name, opts) .form-group.col-auto - if datatable._filters_form_required? From 5e90da251fa56507619817b44b41ce18ac4a25db Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Wed, 8 Mar 2023 13:56:42 -0700 Subject: [PATCH 007/137] yea --- app/models/effective/datatable.rb | 7 ++- .../effective_datatable/dsl/filters.rb | 10 ++-- .../datatables/_filter_date_range.html.haml | 49 ++++++++++++++++++- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/app/models/effective/datatable.rb b/app/models/effective/datatable.rb index 3d3d9722..fa40e91e 100644 --- a/app/models/effective/datatable.rb +++ b/app/models/effective/datatable.rb @@ -210,10 +210,15 @@ def date_range(value = nil) case value.to_sym when :current_month then (now.beginning_of_month..now.end_of_day) - when :current_year then (now.beginning_of_year..now.end_of_day) when :last_month then (now - 1.month).all_month + when :custom_month then (start_date || now).all_month + + when :current_year then (now.beginning_of_year..now.end_of_day) when :last_year then (now - 1.year).all_year + when :custom_year then (start_date || now).all_year + when :custom then (start_date&.beginning_of_day..end_date&.end_of_day) + else raise('unexpected date range value') end diff --git a/app/models/effective/effective_datatable/dsl/filters.rb b/app/models/effective/effective_datatable/dsl/filters.rb index fb0c8ce2..a65bce17 100644 --- a/app/models/effective/effective_datatable/dsl/filters.rb +++ b/app/models/effective/effective_datatable/dsl/filters.rb @@ -8,8 +8,12 @@ module Filters DATE_RANGES = [ ['Current month', :current_month], ['Last month', :last_month], + ['Custom month', :custom_month], + ['Current year', :current_year], ['Last year', :last_year], + ['Custom year', :custom_year], + ['Custom', :custom], ] @@ -20,11 +24,11 @@ def filter_date_range(default = nil) raise("unexpected value #{default}. Try one of #{valid.to_sentence}") unless valid.include?(default) end - (default_start_date, default_end_date) = datatable.date_range(default) + date_range = datatable.date_range(default) filter :date_range, default, collection: DATE_RANGES, partial: 'effective/datatables/filter_date_range' - filter :start_date, default_start_date, as: :date, visible: false - filter :end_date, default_end_date, as: :date, visible: false + filter :start_date, date_range&.begin, as: :date, visible: false + filter :end_date, date_range&.end, as: :date, visible: false end def filter(name = nil, value = :_no_value, as: nil, label: nil, parse: nil, required: false, **input_html) diff --git a/app/views/effective/datatables/_filter_date_range.html.haml b/app/views/effective/datatables/_filter_date_range.html.haml index c695269b..4cee4581 100644 --- a/app/views/effective/datatables/_filter_date_range.html.haml +++ b/app/views/effective/datatables/_filter_date_range.html.haml @@ -1,26 +1,73 @@ - name = '' unless datatable._filters_form_required? -- now = Time.zone.now +- now = Time.zone.now.beginning_of_day = f.select :date_range, opts[:collection], autocomplete: 'off', feedback: false .mx-2 +- date = (f.object.start_date || now) +- raise('expected a date') unless date.respond_to?(:strftime) + +- path = effective_datatables.datatable_path(datatable) + +- prev_month = (date - 1.month).beginning_of_month +- next_month = (date + 1.month).beginning_of_month +- prev_year = (date - 1.year).beginning_of_year +- next_year = (date + 1.year).beginning_of_year + +- prev_month_path = effective_datatables.datatable_path(datatable, date_range: :custom_month, start_date: prev_month.strftime('%F'), end_date: prev_month.end_of_month.strftime('%F')) +- next_month_path = effective_datatables.datatable_path(datatable, date_range: :custom_month, start_date: next_month.strftime('%F'), end_date: next_month.end_of_month.strftime('%F')) +- prev_year_path = effective_datatables.datatable_path(datatable, date_range: :custom_year, start_date: prev_year.strftime('%F'), end_date: prev_year.end_of_year.strftime('%F')) +- next_year_path = effective_datatables.datatable_path(datatable, date_range: :custom_year, start_date: next_year.strftime('%F'), end_date: next_year.end_of_month.strftime('%F')) + = f.show_if :date_range, :current_month do + = f.static_field :month do + = link_to((prev_month.strftime('%b') + icon('arrow-left-circle')).html_safe, prev_month_path.sub(path, request.path)) + = link_to((icon('arrow-right-circle') + next_month.strftime('%b')).html_safe, next_month_path.sub(path, request.path)) + = f.static_field :dates do #{now.beginning_of_month.strftime('%F')} to #{now.strftime('%F')} (today) = f.show_if :date_range, :current_year do + = f.static_field :year do + = link_to((prev_year.strftime('%Y') + icon('arrow-left-circle')).html_safe, prev_year_path.sub(path, request.path)) + = link_to((icon('arrow-right-circle') + next_year.strftime('%Y')).html_safe, next_year_path.sub(path, request.path)) + = f.static_field :dates do #{now.beginning_of_year.strftime('%F')} to #{now.strftime('%F')} (today) = f.show_if :date_range, :last_month do + = f.static_field :month do + = link_to((prev_month.strftime('%b') + icon('arrow-left-circle')).html_safe, prev_month_path.sub(path, request.path)) + = link_to((icon('arrow-right-circle') + next_month.strftime('%b')).html_safe, next_month_path.sub(path, request.path)) + = f.static_field :dates do #{(now - 1.month).beginning_of_month.strftime('%F')} to #{(now - 1.month).end_of_month.strftime('%F')} = f.show_if :date_range, :last_year do + = f.static_field :year do + = link_to((prev_year.strftime('%Y') + icon('arrow-left-circle')).html_safe, prev_year_path.sub(path, request.path)) + = link_to((icon('arrow-right-circle') + next_year.strftime('%Y')).html_safe, next_year_path.sub(path, request.path)) + = f.static_field :dates do #{(now - 1.year).beginning_of_year.strftime('%F')} to #{(now - 1.year).end_of_year.strftime('%F')} += f.show_if :date_range, :custom_year do + = f.static_field :year do + = link_to((prev_year.strftime('%Y') + icon('arrow-left-circle')).html_safe, prev_year_path.sub(path, request.path)) + = link_to((icon('arrow-right-circle') + next_year.strftime('%Y')).html_safe, next_year_path.sub(path, request.path)) + + = f.static_field :dates do + #{date.beginning_of_year.strftime('%F')} to #{date.end_of_year.strftime('%F')} + += f.show_if :date_range, :custom_month do + = f.static_field :month do + = link_to((prev_month.strftime('%Y') + icon('arrow-left-circle')).html_safe, prev_month_path.sub(path, request.path)) + = link_to((icon('arrow-right-circle') + next_month.strftime('%Y')).html_safe, next_month_path.sub(path, request.path)) + + = f.static_field :dates do + #{date.beginning_of_month.strftime('%F')} to #{date.end_of_month.strftime('%F')} + = f.show_if :date_range, :custom do .row .col From 6c24f478fe15ebd01951f893da5f51e91d26648f Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Wed, 8 Mar 2023 14:29:42 -0700 Subject: [PATCH 008/137] filters --- app/models/effective/datatable.rb | 22 +++--- .../effective_datatable/dsl/filters.rb | 13 ++-- .../datatables/_filter_date_range.html.haml | 67 ++++++------------- 3 files changed, 38 insertions(+), 64 deletions(-) diff --git a/app/models/effective/datatable.rb b/app/models/effective/datatable.rb index fa40e91e..e3709b2e 100644 --- a/app/models/effective/datatable.rb +++ b/app/models/effective/datatable.rb @@ -209,16 +209,18 @@ def date_range(value = nil) return (nil..nil) if value.blank? case value.to_sym - when :current_month then (now.beginning_of_month..now.end_of_day) - when :last_month then (now - 1.month).all_month - when :custom_month then (start_date || now).all_month - - when :current_year then (now.beginning_of_year..now.end_of_day) - when :last_year then (now - 1.year).all_year - when :custom_year then (start_date || now).all_year - - when :custom then (start_date&.beginning_of_day..end_date&.end_of_day) - + when :current_month + (now.beginning_of_month..now.end_of_day) + when :current_year + (now.beginning_of_year..now.end_of_day) + when :month + (start_date || now).all_month + when :year + (start_date || now).all_year + when :custom + (start_date&.beginning_of_day..end_date&.end_of_day) + when :all + (nil..nil) else raise('unexpected date range value') end diff --git a/app/models/effective/effective_datatable/dsl/filters.rb b/app/models/effective/effective_datatable/dsl/filters.rb index a65bce17..7e0afc7f 100644 --- a/app/models/effective/effective_datatable/dsl/filters.rb +++ b/app/models/effective/effective_datatable/dsl/filters.rb @@ -6,15 +6,12 @@ module Dsl module Filters DATE_RANGES = [ - ['Current month', :current_month], - ['Last month', :last_month], - ['Custom month', :custom_month], - - ['Current year', :current_year], - ['Last year', :last_year], - ['Custom year', :custom_year], - + ['Current Month', :current_month], + ['Current Year', :current_year], + ['Month', :month], + ['Year', :year], ['Custom', :custom], + ['All Time', :all] ] # This sets up the select field with start_on and end_on diff --git a/app/views/effective/datatables/_filter_date_range.html.haml b/app/views/effective/datatables/_filter_date_range.html.haml index 4cee4581..0d04fe0a 100644 --- a/app/views/effective/datatables/_filter_date_range.html.haml +++ b/app/views/effective/datatables/_filter_date_range.html.haml @@ -5,68 +5,43 @@ .mx-2 -- date = (f.object.start_date || now) -- raise('expected a date') unless date.respond_to?(:strftime) +- start_date = (f.object.start_date || now) +- end_date = (f.object.end_date || now) + +- raise('expected a date start_date') unless start_date.respond_to?(:strftime) +- raise('expected a date end_date') unless end_date.respond_to?(:strftime) - path = effective_datatables.datatable_path(datatable) -- prev_month = (date - 1.month).beginning_of_month -- next_month = (date + 1.month).beginning_of_month -- prev_year = (date - 1.year).beginning_of_year -- next_year = (date + 1.year).beginning_of_year +- prev_month = (start_date - 1.month).beginning_of_month +- next_month = (start_date + 1.month).beginning_of_month +- prev_year = (start_date - 1.year).beginning_of_year +- next_year = (start_date + 1.year).beginning_of_year -- prev_month_path = effective_datatables.datatable_path(datatable, date_range: :custom_month, start_date: prev_month.strftime('%F'), end_date: prev_month.end_of_month.strftime('%F')) -- next_month_path = effective_datatables.datatable_path(datatable, date_range: :custom_month, start_date: next_month.strftime('%F'), end_date: next_month.end_of_month.strftime('%F')) -- prev_year_path = effective_datatables.datatable_path(datatable, date_range: :custom_year, start_date: prev_year.strftime('%F'), end_date: prev_year.end_of_year.strftime('%F')) -- next_year_path = effective_datatables.datatable_path(datatable, date_range: :custom_year, start_date: next_year.strftime('%F'), end_date: next_year.end_of_month.strftime('%F')) +- prev_month_path = effective_datatables.datatable_path(datatable, date_range: :month, start_date: prev_month.strftime('%F'), end_date: prev_month.end_of_month.strftime('%F')) +- next_month_path = effective_datatables.datatable_path(datatable, date_range: :month, start_date: next_month.strftime('%F'), end_date: next_month.end_of_month.strftime('%F')) +- prev_year_path = effective_datatables.datatable_path(datatable, date_range: :year, start_date: prev_year.strftime('%F'), end_date: prev_year.end_of_year.strftime('%F')) +- next_year_path = effective_datatables.datatable_path(datatable, date_range: :year, start_date: next_year.strftime('%F'), end_date: next_year.end_of_month.strftime('%F')) = f.show_if :date_range, :current_month do = f.static_field :month do - = link_to((prev_month.strftime('%b') + icon('arrow-left-circle')).html_safe, prev_month_path.sub(path, request.path)) - = link_to((icon('arrow-right-circle') + next_month.strftime('%b')).html_safe, next_month_path.sub(path, request.path)) - - = f.static_field :dates do #{now.beginning_of_month.strftime('%F')} to #{now.strftime('%F')} (today) = f.show_if :date_range, :current_year do = f.static_field :year do - = link_to((prev_year.strftime('%Y') + icon('arrow-left-circle')).html_safe, prev_year_path.sub(path, request.path)) - = link_to((icon('arrow-right-circle') + next_year.strftime('%Y')).html_safe, next_year_path.sub(path, request.path)) - - = f.static_field :dates do #{now.beginning_of_year.strftime('%F')} to #{now.strftime('%F')} (today) -= f.show_if :date_range, :last_month do += f.show_if :date_range, :month do = f.static_field :month do - = link_to((prev_month.strftime('%b') + icon('arrow-left-circle')).html_safe, prev_month_path.sub(path, request.path)) - = link_to((icon('arrow-right-circle') + next_month.strftime('%b')).html_safe, next_month_path.sub(path, request.path)) + = link_to((prev_month.strftime('%b %Y') + ' ' + icon('arrow-left-circle')).html_safe, prev_month_path.sub(path, request.path)) + #{start_date.beginning_of_month.strftime('%F')} to #{start_date.end_of_month.strftime('%F')} + = link_to((icon('arrow-right-circle') + ' ' + next_month.strftime('%b %Y')).html_safe, next_month_path.sub(path, request.path)) - = f.static_field :dates do - #{(now - 1.month).beginning_of_month.strftime('%F')} to #{(now - 1.month).end_of_month.strftime('%F')} - -= f.show_if :date_range, :last_year do += f.show_if :date_range, :year do = f.static_field :year do - = link_to((prev_year.strftime('%Y') + icon('arrow-left-circle')).html_safe, prev_year_path.sub(path, request.path)) - = link_to((icon('arrow-right-circle') + next_year.strftime('%Y')).html_safe, next_year_path.sub(path, request.path)) - - = f.static_field :dates do - #{(now - 1.year).beginning_of_year.strftime('%F')} to #{(now - 1.year).end_of_year.strftime('%F')} - -= f.show_if :date_range, :custom_year do - = f.static_field :year do - = link_to((prev_year.strftime('%Y') + icon('arrow-left-circle')).html_safe, prev_year_path.sub(path, request.path)) - = link_to((icon('arrow-right-circle') + next_year.strftime('%Y')).html_safe, next_year_path.sub(path, request.path)) - - = f.static_field :dates do - #{date.beginning_of_year.strftime('%F')} to #{date.end_of_year.strftime('%F')} - -= f.show_if :date_range, :custom_month do - = f.static_field :month do - = link_to((prev_month.strftime('%Y') + icon('arrow-left-circle')).html_safe, prev_month_path.sub(path, request.path)) - = link_to((icon('arrow-right-circle') + next_month.strftime('%Y')).html_safe, next_month_path.sub(path, request.path)) - - = f.static_field :dates do - #{date.beginning_of_month.strftime('%F')} to #{date.end_of_month.strftime('%F')} + = link_to((prev_year.strftime('%Y') + ' ' + icon('arrow-left-circle')).html_safe, prev_year_path.sub(path, request.path)) + #{start_date.beginning_of_year.strftime('%F')} to #{start_date.end_of_year.strftime('%F')} + = link_to((icon('arrow-right-circle') + ' ' + next_year.strftime('%Y')).html_safe, next_year_path.sub(path, request.path)) = f.show_if :date_range, :custom do .row From 952b11ae4aad3ce43c979084b101dce491d94916 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Wed, 8 Mar 2023 15:03:57 -0700 Subject: [PATCH 009/137] filter date range --- README.md | 19 +++++++++++++++++++ .../effective_datatables/initialize.js.coffee | 2 +- .../effective_datatable/dsl/filters.rb | 3 +-- .../datatables/_filter_date_range.html.haml | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ed4da007..011b0920 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Please check out [Effective Datatables 3.x](https://github.com/code-and-effect/e * [filters](#filters) * [scope](#scope) * [filter](#filter) + * [filter_date_range](#filter_date_range) * [bulk_actions](#bulk_actions) * [bulk_action](#bulk_action) * [bulk_action](#bulk_action_divider) @@ -791,6 +792,24 @@ required: true|false # Passed to form Any other option given will be yielded to EffectiveBootstrap as options. +## filter_date_range + +There is also a special date range filter built in. To use: + +```ruby +filters do + filter_date_range +end + +collection do + Things.where(updated_at: date_range) +end +``` + +This method creates 3 filters, `filters[:date_range]`, `filters[:start_date]` and `filters[:end_date]` and presents a rough Prev/Next month and year navigation. Do not have any columns named the same as these. + +You can pass a default into `filter_date_range`, one of `:current_month`, `:current_year`, `:month`, `:year` and `:custom`. + ## bulk_actions Creates a single dropdown menu with a link to each action, download or content. diff --git a/app/assets/javascripts/effective_datatables/initialize.js.coffee b/app/assets/javascripts/effective_datatables/initialize.js.coffee index ea73030b..a3cdd58d 100644 --- a/app/assets/javascripts/effective_datatables/initialize.js.coffee +++ b/app/assets/javascripts/effective_datatables/initialize.js.coffee @@ -70,7 +70,7 @@ initializeDataTables = (target) -> params['scope'] = $form.find("input[name='filters[scope]']:checked").val() || '' params['filter'] = {} - $form.find("select,textarea,input:not([type=submit])").each -> + $form.find("select,textarea,input:enabled:not([type=submit])").each -> $input = $(this) if ['utf8', 'authenticity_token', 'filters[scope]'].includes($input.attr('name')) diff --git a/app/models/effective/effective_datatable/dsl/filters.rb b/app/models/effective/effective_datatable/dsl/filters.rb index 7e0afc7f..211699e6 100644 --- a/app/models/effective/effective_datatable/dsl/filters.rb +++ b/app/models/effective/effective_datatable/dsl/filters.rb @@ -10,8 +10,7 @@ module Filters ['Current Year', :current_year], ['Month', :month], ['Year', :year], - ['Custom', :custom], - ['All Time', :all] + ['Custom', :custom] ] # This sets up the select field with start_on and end_on diff --git a/app/views/effective/datatables/_filter_date_range.html.haml b/app/views/effective/datatables/_filter_date_range.html.haml index 0d04fe0a..b6bebed1 100644 --- a/app/views/effective/datatables/_filter_date_range.html.haml +++ b/app/views/effective/datatables/_filter_date_range.html.haml @@ -1,7 +1,7 @@ - name = '' unless datatable._filters_form_required? - now = Time.zone.now.beginning_of_day -= f.select :date_range, opts[:collection], autocomplete: 'off', feedback: false += f.select :date_range, opts[:collection], autocomplete: 'off', feedback: false, placeholder: 'All available dates' .mx-2 From dd8700e62b80492c8a2bdc42c254ae736009a31c Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Wed, 8 Mar 2023 15:07:13 -0700 Subject: [PATCH 010/137] Version 4.16.0 --- lib/effective_datatables/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/effective_datatables/version.rb b/lib/effective_datatables/version.rb index f84f27f4..d9ce4266 100644 --- a/lib/effective_datatables/version.rb +++ b/lib/effective_datatables/version.rb @@ -1,3 +1,3 @@ module EffectiveDatatables - VERSION = '4.15.2'.freeze + VERSION = '4.16.0'.freeze end From 26a55766dabf70bd46d59b0805260f3a1f8a6938 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Wed, 8 Mar 2023 15:07:57 -0700 Subject: [PATCH 011/137] filter date range --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 011b0920..141b9789 100644 --- a/README.md +++ b/README.md @@ -802,7 +802,7 @@ filters do end collection do - Things.where(updated_at: date_range) + Thing.where(updated_at: date_range) end ``` From 3424ec046ff7717c961294fa05eb706a0bdb5f67 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Fri, 10 Mar 2023 13:15:34 -0700 Subject: [PATCH 012/137] Use datatable translations where available --- app/models/effective/datatable.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/models/effective/datatable.rb b/app/models/effective/datatable.rb index e3709b2e..802fcc6a 100644 --- a/app/models/effective/datatable.rb +++ b/app/models/effective/datatable.rb @@ -59,6 +59,16 @@ def initialize(view = nil, attributes = nil) self.view = view if view end + # Checks en.datatables.admin/my_datatable + def self.datatable_name + value = I18n.t("datatables.#{name.underscore}").to_s + value.start_with?('translation missing:') ? name.titleize.split('/').last.chomp(' Datatable') : value + end + + def datatable_name + self.class.datatable_name + end + def rendered(params = {}) raise('expected a hash of params') unless params.kind_of?(Hash) From a40b2af5c81349f7112786991ff034b0767fd1c0 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Fri, 10 Mar 2023 13:15:47 -0700 Subject: [PATCH 013/137] Version 4.16.1 --- lib/effective_datatables/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/effective_datatables/version.rb b/lib/effective_datatables/version.rb index d9ce4266..1a77bf4e 100644 --- a/lib/effective_datatables/version.rb +++ b/lib/effective_datatables/version.rb @@ -1,3 +1,3 @@ module EffectiveDatatables - VERSION = '4.16.0'.freeze + VERSION = '4.16.1'.freeze end From dac31c1bfb2c4d43afef9669acf20234ee47b81e Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Thu, 16 Mar 2023 15:40:46 -0600 Subject: [PATCH 014/137] Better csv file outside of a controller --- app/models/effective/effective_datatable/csv.rb | 15 ++++++++++++--- .../effective/effective_datatable/params.rb | 6 +++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/models/effective/effective_datatable/csv.rb b/app/models/effective/effective_datatable/csv.rb index 2aa436f0..29680615 100644 --- a/app/models/effective/effective_datatable/csv.rb +++ b/app/models/effective/effective_datatable/csv.rb @@ -24,7 +24,7 @@ def csv_human_attribute_name(name) if active_record_collection? collection_class.human_attribute_name(name) else - (name.to_s.split('.').last || '') + (name.to_s.split('.').last.titleize|| '') end end @@ -32,8 +32,17 @@ def csv_file CSV.generate do |csv| csv << csv_header() - collection.find_in_batches do |resources| - resources = arrayize(resources, csv: true) + if active_record_collection? + collection.find_in_batches do |resources| + resources = arrayize(resources, csv: true) + format(resources, csv: true) + finalize(resources) + + resources.each { |resource| csv << resource } + end + else + resources = collection + format(resources, csv: true) finalize(resources) diff --git a/app/models/effective/effective_datatable/params.rb b/app/models/effective/effective_datatable/params.rb index 05c3b1fd..521335ac 100644 --- a/app/models/effective/effective_datatable/params.rb +++ b/app/models/effective/effective_datatable/params.rb @@ -10,20 +10,20 @@ def datatables_ajax_request? return @_datatables_ajax_request unless @_datatables_ajax_request.nil? return unless view.respond_to?(:params) - @_datatables_ajax_request = (view.params.key?(:draw) && view.params.key?(:columns)) + @_datatables_ajax_request = view.params.present? && (view.params.key?(:draw) && view.params.key?(:columns)) end def datatables_inline_request? return @_datatables_inline_request unless @_datatables_inline_request.nil? return unless view.respond_to?(:params) - @_datatables_inline_request = (view.params[:_datatable_id].to_s.split('-')[0...-1] == to_param.split('-')[0...-1]) + @_datatables_inline_request = view.params.present? && (view.params[:_datatable_id].to_s.split('-')[0...-1] == to_param.split('-')[0...-1]) end def params return {} unless view.present? return view.rendered_params if view.respond_to?(:rendered_params) - return {} unless view.respond_to?(:request) + return {} unless view.respond_to?(:request) && view.request.present? @params ||= {}.tap do |params| Rack::Utils.parse_query(URI(view.request.referer.presence || '/').query).each { |k, v| params[k.to_sym] = v } From e7a83e61ce5738693a0dd4df902b80c58350a6cc Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Thu, 16 Mar 2023 15:40:58 -0600 Subject: [PATCH 015/137] Version 4.16.2 --- lib/effective_datatables/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/effective_datatables/version.rb b/lib/effective_datatables/version.rb index 1a77bf4e..70ae857c 100644 --- a/lib/effective_datatables/version.rb +++ b/lib/effective_datatables/version.rb @@ -1,3 +1,3 @@ module EffectiveDatatables - VERSION = '4.16.1'.freeze + VERSION = '4.16.2'.freeze end From f6a9adb1894dddcc5673305e73b90c182ec04dc5 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Mon, 20 Mar 2023 16:44:51 -0600 Subject: [PATCH 016/137] Sort Show/hide alphabetically --- .../javascripts/dataTables/buttons/buttons.colVis.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/dataTables/buttons/buttons.colVis.js b/app/assets/javascripts/dataTables/buttons/buttons.colVis.js index d5ec8de8..1bd7b6a0 100755 --- a/app/assets/javascripts/dataTables/buttons/buttons.colVis.js +++ b/app/assets/javascripts/dataTables/buttons/buttons.colVis.js @@ -62,9 +62,15 @@ $.extend( DataTable.ext.buttons, { columns: idx, columnText: conf.columnText }; - } ).toArray(); + }) - return columns; + var sorted = columns.sort(function (a, b) { + var a = dt.settings()[0].aoColumns[a.columns].sTitle; + var b = dt.settings()[0].aoColumns[b.columns].sTitle; + return a.localeCompare(b) + }).toArray(); + + return sorted; }, // Single button to toggle column visibility From 2653766232d00c98e27423712f456f3e4ec2b903 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Mon, 20 Mar 2023 16:45:03 -0600 Subject: [PATCH 017/137] Version 4.17.0 --- lib/effective_datatables/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/effective_datatables/version.rb b/lib/effective_datatables/version.rb index 70ae857c..ce095d37 100644 --- a/lib/effective_datatables/version.rb +++ b/lib/effective_datatables/version.rb @@ -1,3 +1,3 @@ module EffectiveDatatables - VERSION = '4.16.2'.freeze + VERSION = '4.17.0'.freeze end From 980637d1d2b9f4f109dc2842935eaedd16da3f5d Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Mon, 3 Apr 2023 10:49:41 -0600 Subject: [PATCH 018/137] Add rubocop --- .gitignore | 1 + .rubocop.yml | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 .rubocop.yml diff --git a/.gitignore b/.gitignore index 9af112d6..061912ed 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Gemfile.lock log/*.log pkg/ tmp/ +.rubocop-* diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..9c876bbc --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,2 @@ +inherit_from: + - https://raw.githubusercontent.com/code-and-effect/effective_developer/master/rubocop.yml From f96fca230c57ab2ae7c6a73991e53f8b80e09f6c Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Mon, 3 Apr 2023 10:49:52 -0600 Subject: [PATCH 019/137] Tweak belongs to human name --- app/helpers/effective_datatables_private_helper.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/helpers/effective_datatables_private_helper.rb b/app/helpers/effective_datatables_private_helper.rb index a00b30ab..1d3a6172 100644 --- a/app/helpers/effective_datatables_private_helper.rb +++ b/app/helpers/effective_datatables_private_helper.rb @@ -69,13 +69,19 @@ def datatable_human_attribute_name(datatable, name, opts) case opts[:as] when :belongs_to - opts[:resource].human_name + foreign_key = opts[:resource].initialized_name.try(:foreign_key).to_s.downcase + klass_name = opts[:resource].initialized_name.try(:class_name).to_s.downcase + + if foreign_key.starts_with?(klass_name) + opts[:resource].human_name + else + datatable.collection_class.human_attribute_name(name) + end when :has_many opts[:resource].human_plural_name else datatable.collection_class.human_attribute_name(name) end - end def datatable_search_tag(datatable, name, opts) From bf6c10a51c63ffcfebd583f0bb7a7f8f415ad7e1 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Mon, 3 Apr 2023 10:50:05 -0600 Subject: [PATCH 020/137] Version 4.17.1 --- lib/effective_datatables/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/effective_datatables/version.rb b/lib/effective_datatables/version.rb index ce095d37..94ac194f 100644 --- a/lib/effective_datatables/version.rb +++ b/lib/effective_datatables/version.rb @@ -1,3 +1,3 @@ module EffectiveDatatables - VERSION = '4.17.0'.freeze + VERSION = '4.17.1'.freeze end From ee7bf8b134a0b2bd9d58b5d832952e2cf33d10ed Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Tue, 30 May 2023 13:24:05 -0600 Subject: [PATCH 021/137] array collection search fix --- app/models/effective/effective_datatable/resource.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/effective/effective_datatable/resource.rb b/app/models/effective/effective_datatable/resource.rb index 4c001fc3..9134dfa8 100644 --- a/app/models/effective/effective_datatable/resource.rb +++ b/app/models/effective/effective_datatable/resource.rb @@ -178,6 +178,8 @@ def load_resource_search! search[:collection] = search[:collection].map { |obj| [obj.to_s, obj.id] } elsif search[:collection].kind_of?(Array) && search[:collection].first.kind_of?(ActiveRecord::Base) search[:collection] = search[:collection].map { |obj| [obj.to_s, obj.id] } + elsif search[:collection].kind_of?(Array) + search[:collection] = search[:collection] end search[:as] ||= :select if search.key?(:collection) @@ -189,6 +191,8 @@ def load_resource_search! if array_collection? && opts[:resource].present? search.reverse_merge!(search_resource.search_form_field(name, collection.first[opts[:index]])) + elsif search[:as] == :select && search[:collection].kind_of?(Array) + # Nothing to do elsif search[:as] != :string search.reverse_merge!(search_resource.search_form_field(name, opts[:as])) end From b8891204a0f3e90b39300fd718aaf51539121741 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Tue, 30 May 2023 13:24:18 -0600 Subject: [PATCH 022/137] Version 4.17.2 --- lib/effective_datatables/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/effective_datatables/version.rb b/lib/effective_datatables/version.rb index 94ac194f..a368d86f 100644 --- a/lib/effective_datatables/version.rb +++ b/lib/effective_datatables/version.rb @@ -1,3 +1,3 @@ module EffectiveDatatables - VERSION = '4.17.1'.freeze + VERSION = '4.17.2'.freeze end From 6e2b3cffe9eb082128291d3191f05c504d8e665f Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Fri, 16 Jun 2023 12:12:58 -0600 Subject: [PATCH 023/137] tweak col and val tool selection when search method is provided --- app/models/effective/datatable_column_tool.rb | 2 +- app/models/effective/datatable_value_tool.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/effective/datatable_column_tool.rb b/app/models/effective/datatable_column_tool.rb index 37277336..6e833707 100644 --- a/app/models/effective/datatable_column_tool.rb +++ b/app/models/effective/datatable_column_tool.rb @@ -9,7 +9,7 @@ def initialize(datatable) @datatable = datatable if datatable.active_record_collection? - @columns = datatable.columns.select { |_, col| col[:sql_column].present? } + @columns = datatable.columns.select { |_, col| col[:sql_column].present? || (col[:search_method].present? && col[:sql_column] != false) } else @columns = {} end diff --git a/app/models/effective/datatable_value_tool.rb b/app/models/effective/datatable_value_tool.rb index cc8c85de..d7072843 100644 --- a/app/models/effective/datatable_value_tool.rb +++ b/app/models/effective/datatable_value_tool.rb @@ -12,7 +12,7 @@ def initialize(datatable) if datatable.array_collection? @columns = datatable.columns else - @columns = datatable.columns.select { |_, col| col[:sql_column].blank? } + @columns = datatable.columns.reject { |_, col| col[:sql_column].present? || (col[:search_method].present? && col[:sql_column] != false) } end end From c9fb59e00dd32f5bd4de25b2fe24ecd1998a6d52 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Fri, 16 Jun 2023 12:13:11 -0600 Subject: [PATCH 024/137] Version 4.17.3 --- lib/effective_datatables/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/effective_datatables/version.rb b/lib/effective_datatables/version.rb index a368d86f..a1e0660d 100644 --- a/lib/effective_datatables/version.rb +++ b/lib/effective_datatables/version.rb @@ -1,3 +1,3 @@ module EffectiveDatatables - VERSION = '4.17.2'.freeze + VERSION = '4.17.3'.freeze end From 4f1a3215795c7dc73f64965fbcb7b6376feac136 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Thu, 3 Aug 2023 11:06:08 -0600 Subject: [PATCH 025/137] i18n fix for datatable_name --- app/models/effective/datatable.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/models/effective/datatable.rb b/app/models/effective/datatable.rb index 802fcc6a..fb3f14df 100644 --- a/app/models/effective/datatable.rb +++ b/app/models/effective/datatable.rb @@ -61,8 +61,14 @@ def initialize(view = nil, attributes = nil) # Checks en.datatables.admin/my_datatable def self.datatable_name - value = I18n.t("datatables.#{name.underscore}").to_s - value.start_with?('translation missing:') ? name.titleize.split('/').last.chomp(' Datatable') : value + key = "datatables.#{name.underscore}" + value = ::I18n.t(key) + + if value.include?(key) # missing translation + name.titleize.split('/').last.chomp(' Datatable') + else + value + end end def datatable_name From 6f18131c1e4b3671a6db177a5667bf8780816a80 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Thu, 3 Aug 2023 11:06:22 -0600 Subject: [PATCH 026/137] Version 4.17.4 --- lib/effective_datatables/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/effective_datatables/version.rb b/lib/effective_datatables/version.rb index a1e0660d..d2a93231 100644 --- a/lib/effective_datatables/version.rb +++ b/lib/effective_datatables/version.rb @@ -1,3 +1,3 @@ module EffectiveDatatables - VERSION = '4.17.3'.freeze + VERSION = '4.17.4'.freeze end From 8dc92009ab92150a5aa55db6f372a497882f32e2 Mon Sep 17 00:00:00 2001 From: Matt Riemer Date: Tue, 8 Aug 2023 13:15:14 -0600 Subject: [PATCH 027/137] Upgrade to datatables javascript 1.13.6 --- app/assets/javascripts/dataTables/UPGRADE.md | 17 + .../dataTables/buttons/buttons.bootstrap4.js | 92 +- .../dataTables/buttons/buttons.colVis.js | 286 +- .../dataTables/buttons/buttons.html5.js | 1416 ++-- .../dataTables/buttons/buttons.print.js | 160 +- .../dataTables/buttons/dataTables.buttons.js | 2477 ++++--- .../dataTables/dataTables.bootstrap4.js | 326 +- .../dataTables/jquery.dataTables.js | 5823 +++++++++-------- .../responsive/dataTables.responsive.js | 1238 ++-- .../responsive/responsive.bootstrap4.js | 94 +- .../rowReorder/dataTables.rowReorder.js | 1701 ++--- .../rowReorder/rowReorder.bootstrap4.js | 80 +- .../buttons/buttons.bootstrap4.scss | 329 +- .../dataTables/dataTables.bootstrap4.scss | 381 +- .../responsive/responsive.bootstrap4.scss | 125 +- .../rowReorder/rowReorder.bootstrap4.scss | 27 +- .../_overrides.bootstrap4.scss | 114 +- 17 files changed, 8366 insertions(+), 6320 deletions(-) create mode 100644 app/assets/javascripts/dataTables/UPGRADE.md mode change 100755 => 100644 app/assets/javascripts/dataTables/buttons/buttons.bootstrap4.js mode change 100755 => 100644 app/assets/javascripts/dataTables/buttons/buttons.colVis.js mode change 100755 => 100644 app/assets/javascripts/dataTables/buttons/buttons.html5.js mode change 100755 => 100644 app/assets/javascripts/dataTables/buttons/buttons.print.js mode change 100755 => 100644 app/assets/javascripts/dataTables/buttons/dataTables.buttons.js mode change 100755 => 100644 app/assets/javascripts/dataTables/jquery.dataTables.js mode change 100755 => 100644 app/assets/javascripts/dataTables/responsive/dataTables.responsive.js mode change 100755 => 100644 app/assets/javascripts/dataTables/responsive/responsive.bootstrap4.js mode change 100755 => 100644 app/assets/stylesheets/dataTables/buttons/buttons.bootstrap4.scss mode change 100755 => 100644 app/assets/stylesheets/dataTables/dataTables.bootstrap4.scss mode change 100755 => 100644 app/assets/stylesheets/dataTables/responsive/responsive.bootstrap4.scss diff --git a/app/assets/javascripts/dataTables/UPGRADE.md b/app/assets/javascripts/dataTables/UPGRADE.md new file mode 100644 index 00000000..ba33d80b --- /dev/null +++ b/app/assets/javascripts/dataTables/UPGRADE.md @@ -0,0 +1,17 @@ +# Upgrade + +To upgrade the datatables.net source code: + +Visit https://datatables.net/download/ + +Step 1: Choose Bootstrap4 +Step 2: + Packages: Just DataTables + Extensions: Buttons, Column visibility, HTML5 export, Print view, Responsive, RowReorder +Step 3: Download do not minify or concatenate +Step 4: Replace existing javascript and css with downloaded + +After replacing the existing JS/CSS: + +- Consider the buttons.colVis.js and add back the custom sorted functionality +- Consider the stylesheets/effective_datatables/overrides.bootstrap4.scss diff --git a/app/assets/javascripts/dataTables/buttons/buttons.bootstrap4.js b/app/assets/javascripts/dataTables/buttons/buttons.bootstrap4.js old mode 100755 new mode 100644 index f2dedd9b..ff40aeec --- a/app/assets/javascripts/dataTables/buttons/buttons.bootstrap4.js +++ b/app/assets/javascripts/dataTables/buttons/buttons.bootstrap4.js @@ -1,5 +1,5 @@ /*! Bootstrap integration for DataTables' Buttons - * ©2016 SpryMedia Ltd - datatables.net/license + * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ @@ -11,21 +11,37 @@ } else if ( typeof exports === 'object' ) { // CommonJS - module.exports = function (root, $) { - if ( ! root ) { - root = window; - } - - if ( ! $ || ! $.fn.dataTable ) { - $ = require('datatables.net-bs4')(root, $).$; + var jq = require('jquery'); + var cjsRequires = function (root, $) { + if ( ! $.fn.dataTable ) { + require('datatables.net-bs4')(root, $); } if ( ! $.fn.dataTable.Buttons ) { require('datatables.net-buttons')(root, $); } - - return factory( $, root, root.document ); }; + + if (typeof window === 'undefined') { + module.exports = function (root, $) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = jq( root ); + } + + cjsRequires( root, $ ); + return factory( $, root, root.document ); + }; + } + else { + cjsRequires( window, jq ); + module.exports = factory( jq, window, window.document ); + } } else { // Browser @@ -35,29 +51,67 @@ 'use strict'; var DataTable = $.fn.dataTable; -$.extend( true, DataTable.Buttons.defaults, { + + +$.extend(true, DataTable.Buttons.defaults, { dom: { container: { - className: 'dt-buttons btn-group' + className: 'dt-buttons btn-group flex-wrap' }, button: { - className: 'btn btn-secondary' + className: 'btn btn-secondary', + active: 'active' }, collection: { - tag: 'div', - className: 'dt-button-collection dropdown-menu', + action: { + dropHtml: '' + }, + container: { + tag: 'div', + className: 'dropdown-menu dt-button-collection' + }, + closeButton: false, button: { tag: 'a', className: 'dt-button dropdown-item', - active: 'active', - disabled: 'disabled' + active: 'dt-button-active', + disabled: 'disabled', + spacer: { + className: 'dropdown-divider', + tag: 'hr' + } + } + }, + split: { + action: { + tag: 'a', + className: 'btn btn-secondary dt-button-split-drop-button', + closeButton: false + }, + dropdown: { + tag: 'button', + dropHtml: '', + className: + 'btn btn-secondary dt-button-split-drop dropdown-toggle dropdown-toggle-split', + closeButton: false, + align: 'split-left', + splitAlignClass: 'dt-button-split-left' + }, + wrapper: { + tag: 'div', + className: 'dt-button-split btn-group', + closeButton: false } } + }, + buttonCreated: function (config, button) { + return config.buttons ? $('
').append(button) : button; } -} ); +}); DataTable.ext.buttons.collection.className += ' dropdown-toggle'; DataTable.ext.buttons.collection.rightAlignClassName = 'dropdown-menu-right'; -return DataTable.Buttons; + +return DataTable; })); diff --git a/app/assets/javascripts/dataTables/buttons/buttons.colVis.js b/app/assets/javascripts/dataTables/buttons/buttons.colVis.js old mode 100755 new mode 100644 index 1bd7b6a0..19db9dd9 --- a/app/assets/javascripts/dataTables/buttons/buttons.colVis.js +++ b/app/assets/javascripts/dataTables/buttons/buttons.colVis.js @@ -1,6 +1,6 @@ /*! * Column visibility buttons for Buttons and DataTables. - * 2016 SpryMedia Ltd - datatables.net/license + * © SpryMedia Ltd - datatables.net/license */ (function( factory ){ @@ -12,21 +12,37 @@ } else if ( typeof exports === 'object' ) { // CommonJS - module.exports = function (root, $) { - if ( ! root ) { - root = window; - } - - if ( ! $ || ! $.fn.dataTable ) { - $ = require('datatables.net')(root, $).$; + var jq = require('jquery'); + var cjsRequires = function (root, $) { + if ( ! $.fn.dataTable ) { + require('datatables.net')(root, $); } if ( ! $.fn.dataTable.Buttons ) { require('datatables.net-buttons')(root, $); } - - return factory( $, root, root.document ); }; + + if (typeof window === 'undefined') { + module.exports = function (root, $) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = jq( root ); + } + + cjsRequires( root, $ ); + return factory( $, root, root.document ); + }; + } + else { + cjsRequires( window, jq ); + module.exports = factory( jq, window, window.document ); + } } else { // Browser @@ -37,44 +53,68 @@ var DataTable = $.fn.dataTable; -$.extend( DataTable.ext.buttons, { + +$.extend(DataTable.ext.buttons, { // A collection of column visibility buttons - colvis: function ( dt, conf ) { - return { + colvis: function (dt, conf) { + var node = null; + var buttonConf = { extend: 'collection', - text: function ( dt ) { - return dt.i18n( 'buttons.colvis', 'Column visibility' ); + init: function (dt, n) { + node = n; + }, + text: function (dt) { + return dt.i18n('buttons.colvis', 'Column visibility'); }, className: 'buttons-colvis', - buttons: [ { - extend: 'columnsToggle', - columns: conf.columns, - columnText: conf.columnText - } ] + closeButton: false, + buttons: [ + { + extend: 'columnsToggle', + columns: conf.columns, + columnText: conf.columnText + } + ] }; + + // Rebuild the collection with the new column structure if columns are reordered + dt.on('column-reorder.dt' + conf.namespace, function (e, settings, details) { + dt.button(null, dt.button(null, node).node()).collectionRebuild([ + { + extend: 'columnsToggle', + columns: conf.columns, + columnText: conf.columnText + } + ]); + }); + + return buttonConf; }, // Selected columns with individual buttons - toggle column visibility - columnsToggle: function ( dt, conf ) { - var columns = dt.columns( conf.columns ).indexes().map( function ( idx ) { - return { - extend: 'columnToggle', - columns: idx, - columnText: conf.columnText - }; - }) - - var sorted = columns.sort(function (a, b) { - var a = dt.settings()[0].aoColumns[a.columns].sTitle; - var b = dt.settings()[0].aoColumns[b.columns].sTitle; - return a.localeCompare(b) - }).toArray(); - - return sorted; + columnsToggle: function (dt, conf) { + var columns = dt + .columns(conf.columns) + .indexes() + .map(function (idx) { + return { + extend: 'columnToggle', + columns: idx, + columnText: conf.columnText + }; + }) + + var sorted = columns.sort(function (a, b) { + var a = dt.settings()[0].aoColumns[a.columns].sTitle; + var b = dt.settings()[0].aoColumns[b.columns].sTitle; + return a.localeCompare(b) + }).toArray(); + + return sorted; }, // Single button to toggle column visibility - columnToggle: function ( dt, conf ) { + columnToggle: function (dt, conf) { return { extend: 'columnVisibility', columns: conf.columns, @@ -83,15 +123,19 @@ $.extend( DataTable.ext.buttons, { }, // Selected columns with individual buttons - set column visibility - columnsVisibility: function ( dt, conf ) { - var columns = dt.columns( conf.columns ).indexes().map( function ( idx ) { - return { - extend: 'columnVisibility', - columns: idx, - visibility: conf.visibility, - columnText: conf.columnText - }; - } ).toArray(); + columnsVisibility: function (dt, conf) { + var columns = dt + .columns(conf.columns) + .indexes() + .map(function (idx) { + return { + extend: 'columnVisibility', + columns: idx, + visibility: conf.visibility, + columnText: conf.columnText + }; + }) + .toArray(); return columns; }, @@ -99,111 +143,113 @@ $.extend( DataTable.ext.buttons, { // Single button to set column visibility columnVisibility: { columns: undefined, // column selector - text: function ( dt, button, conf ) { - return conf._columnText( dt, conf ); + text: function (dt, button, conf) { + return conf._columnText(dt, conf); }, className: 'buttons-columnVisibility', - action: function ( e, dt, button, conf ) { - var col = dt.columns( conf.columns ); + action: function (e, dt, button, conf) { + var col = dt.columns(conf.columns); var curr = col.visible(); - col.visible( conf.visibility !== undefined ? - conf.visibility : - ! (curr.length ? curr[0] : false ) + col.visible( + conf.visibility !== undefined ? conf.visibility : !(curr.length ? curr[0] : false) ); }, - init: function ( dt, button, conf ) { + init: function (dt, button, conf) { var that = this; - button.attr( 'data-cv-idx', conf.columns ); - - dt - .on( 'column-visibility.dt'+conf.namespace, function (e, settings) { - if ( ! settings.bDestroying && settings.nTable == dt.settings()[0].nTable ) { - that.active( dt.column( conf.columns ).visible() ); - } - } ) - .on( 'column-reorder.dt'+conf.namespace, function (e, settings, details) { - // Don't rename buttons based on column name if the button - // controls more than one column! - if ( dt.columns( conf.columns ).count() !== 1 ) { - return; - } - - conf.columns = $.inArray( conf.columns, details.mapping ); - button.attr( 'data-cv-idx', conf.columns ); - - // Reorder buttons for new table order - button - .parent() - .children('[data-cv-idx]') - .sort( function (a, b) { - return (a.getAttribute('data-cv-idx')*1) - (b.getAttribute('data-cv-idx')*1); - } ) - .appendTo(button.parent()); - } ); - - this.active( dt.column( conf.columns ).visible() ); + button.attr('data-cv-idx', conf.columns); + + dt.on('column-visibility.dt' + conf.namespace, function (e, settings) { + if (!settings.bDestroying && settings.nTable == dt.settings()[0].nTable) { + that.active(dt.column(conf.columns).visible()); + } + }).on('column-reorder.dt' + conf.namespace, function (e, settings, details) { + // Button has been removed from the DOM + if (conf.destroying) { + return; + } + + if (dt.columns(conf.columns).count() !== 1) { + return; + } + + // This button controls the same column index but the text for the column has + // changed + that.text(conf._columnText(dt, conf)); + + // Since its a different column, we need to check its visibility + that.active(dt.column(conf.columns).visible()); + }); + + this.active(dt.column(conf.columns).visible()); }, - destroy: function ( dt, button, conf ) { - dt - .off( 'column-visibility.dt'+conf.namespace ) - .off( 'column-reorder.dt'+conf.namespace ); + destroy: function (dt, button, conf) { + dt.off('column-visibility.dt' + conf.namespace).off( + 'column-reorder.dt' + conf.namespace + ); }, - _columnText: function ( dt, conf ) { + _columnText: function (dt, conf) { // Use DataTables' internal data structure until this is presented // is a public API. The other option is to use // `$( column(col).node() ).text()` but the node might not have been // populated when Buttons is constructed. - var idx = dt.column( conf.columns ).index(); - var title = dt.settings()[0].aoColumns[ idx ].sTitle - .replace(/\n/g," ") // remove new lines - .replace(//gi, " ") // replace line breaks with spaces - .replace(//g, "") // remove select tags, including options text - .replace(//g, "") // strip HTML comments - .replace(/<.*?>/g, "") // strip HTML - .replace(/^\s+|\s+$/g,""); // trim - - return conf.columnText ? - conf.columnText( dt, idx, title ) : - title; + var idx = dt.column(conf.columns).index(); + var title = dt.settings()[0].aoColumns[idx].sTitle; + + if (!title) { + title = dt.column(idx).header().innerHTML; + } + + title = title + .replace(/\n/g, ' ') // remove new lines + .replace(//gi, ' ') // replace line breaks with spaces + .replace(//g, '') // remove select tags, including options text + .replace(//g, '') // strip HTML comments + .replace(/<.*?>/g, '') // strip HTML + .replace(/^\s+|\s+$/g, ''); // trim + + return conf.columnText ? conf.columnText(dt, idx, title) : title; } }, - colvisRestore: { className: 'buttons-colvisRestore', - text: function ( dt ) { - return dt.i18n( 'buttons.colvisRestore', 'Restore visibility' ); + text: function (dt) { + return dt.i18n('buttons.colvisRestore', 'Restore visibility'); }, - init: function ( dt, button, conf ) { - conf._visOriginal = dt.columns().indexes().map( function ( idx ) { - return dt.column( idx ).visible(); - } ).toArray(); + init: function (dt, button, conf) { + conf._visOriginal = dt + .columns() + .indexes() + .map(function (idx) { + return dt.column(idx).visible(); + }) + .toArray(); }, - action: function ( e, dt, button, conf ) { - dt.columns().every( function ( i ) { + action: function (e, dt, button, conf) { + dt.columns().every(function (i) { // Take into account that ColReorder might have disrupted our // indexes - var idx = dt.colReorder && dt.colReorder.transpose ? - dt.colReorder.transpose( i, 'toOriginal' ) : - i; + var idx = + dt.colReorder && dt.colReorder.transpose + ? dt.colReorder.transpose(i, 'toOriginal') + : i; - this.visible( conf._visOriginal[ idx ] ); - } ); + this.visible(conf._visOriginal[idx]); + }); } }, - colvisGroup: { className: 'buttons-colvisGroup', - action: function ( e, dt, button, conf ) { - dt.columns( conf.show ).visible( true, false ); - dt.columns( conf.hide ).visible( false, false ); + action: function (e, dt, button, conf) { + dt.columns(conf.show).visible(true, false); + dt.columns(conf.hide).visible(false, false); dt.columns.adjust(); }, @@ -212,8 +258,8 @@ $.extend( DataTable.ext.buttons, { hide: [] } -} ); +}); -return DataTable.Buttons; +return DataTable; })); diff --git a/app/assets/javascripts/dataTables/buttons/buttons.html5.js b/app/assets/javascripts/dataTables/buttons/buttons.html5.js old mode 100755 new mode 100644 index b7f3835e..8f2881cc --- a/app/assets/javascripts/dataTables/buttons/buttons.html5.js +++ b/app/assets/javascripts/dataTables/buttons/buttons.html5.js @@ -1,6 +1,6 @@ /*! * HTML5 export buttons for Buttons and DataTables. - * 2016 SpryMedia Ltd - datatables.net/license + * © SpryMedia Ltd - datatables.net/license * * FileSaver.js (1.3.3) - MIT license * Copyright © 2016 Eli Grey - http://eligrey.com @@ -15,21 +15,37 @@ } else if ( typeof exports === 'object' ) { // CommonJS - module.exports = function (root, $, jszip, pdfmake) { - if ( ! root ) { - root = window; - } - - if ( ! $ || ! $.fn.dataTable ) { - $ = require('datatables.net')(root, $).$; + var jq = require('jquery'); + var cjsRequires = function (root, $) { + if ( ! $.fn.dataTable ) { + require('datatables.net')(root, $); } if ( ! $.fn.dataTable.Buttons ) { require('datatables.net-buttons')(root, $); } - - return factory( $, root, root.document, jszip, pdfmake ); }; + + if (typeof window === 'undefined') { + module.exports = function (root, $, jszip, pdfmake) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = jq( root ); + } + + cjsRequires( root, $ ); + return factory( $, root, root.document, jszip, pdfmake ); + }; + } + else { + cjsRequires( window, jq ); + module.exports = factory( jq, window, window.document ); + } } else { // Browser @@ -39,29 +55,33 @@ 'use strict'; var DataTable = $.fn.dataTable; + + // Allow the constructor to pass in JSZip and PDFMake from external requires. // Otherwise, use globally defined variables, if they are available. -function _jsZip () { - return jszip || window.JSZip; +var useJszip; +var usePdfmake; + +function _jsZip() { + return useJszip || window.JSZip; } -function _pdfMake () { - return pdfmake || window.pdfMake; +function _pdfMake() { + return usePdfmake || window.pdfMake; } DataTable.Buttons.pdfMake = function (_) { - if ( ! _ ) { + if (!_) { return _pdfMake(); } - pdfmake = m_ake; -} + usePdfmake = _; +}; DataTable.Buttons.jszip = function (_) { - if ( ! _ ) { + if (!_) { return _jsZip(); } - jszip = _; -} - + useJszip = _; +}; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * FileSaver.js dependency @@ -69,50 +89,55 @@ DataTable.Buttons.jszip = function (_) { /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ -var _saveAs = (function(view) { - "use strict"; +var _saveAs = (function (view) { + 'use strict'; // IE <10 is explicitly unsupported - if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { + if ( + typeof view === 'undefined' || + (typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent)) + ) { return; } - var - doc = view.document - // only get URL when necessary in case Blob.js hasn't overridden it yet - , get_URL = function() { + var doc = view.document, + // only get URL when necessary in case Blob.js hasn't overridden it yet + get_URL = function () { return view.URL || view.webkitURL || view; - } - , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") - , can_use_save_link = "download" in save_link - , click = function(node) { - var event = new MouseEvent("click"); + }, + save_link = doc.createElementNS('http://www.w3.org/1999/xhtml', 'a'), + can_use_save_link = 'download' in save_link, + click = function (node) { + var event = new MouseEvent('click'); node.dispatchEvent(event); - } - , is_safari = /constructor/i.test(view.HTMLElement) || view.safari - , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent) - , throw_outside = function(ex) { - (view.setImmediate || view.setTimeout)(function() { + }, + is_safari = /constructor/i.test(view.HTMLElement) || view.safari, + is_chrome_ios = /CriOS\/[\d]+/.test(navigator.userAgent), + throw_outside = function (ex) { + (view.setImmediate || view.setTimeout)(function () { throw ex; }, 0); - } - , force_saveable_type = "application/octet-stream" + }, + force_saveable_type = 'application/octet-stream', // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to - , arbitrary_revoke_timeout = 1000 * 40 // in ms - , revoke = function(file) { - var revoker = function() { - if (typeof file === "string") { // file is an object URL + arbitrary_revoke_timeout = 1000 * 40, // in ms + revoke = function (file) { + var revoker = function () { + if (typeof file === 'string') { + // file is an object URL get_URL().revokeObjectURL(file); - } else { // file is a File + } + else { + // file is a File file.remove(); } }; setTimeout(revoker, arbitrary_revoke_timeout); - } - , dispatch = function(filesaver, event_types, event) { + }, + dispatch = function (filesaver, event_types, event) { event_types = [].concat(event_types); var i = event_types.length; while (i--) { - var listener = filesaver["on" + event_types[i]]; - if (typeof listener === "function") { + var listener = filesaver['on' + event_types[i]]; + if (typeof listener === 'function') { try { listener.call(filesaver, event || filesaver); } catch (ex) { @@ -120,38 +145,43 @@ var _saveAs = (function(view) { } } } - } - , auto_bom = function(blob) { + }, + auto_bom = function (blob) { // prepend BOM for UTF-8 XML and text/* types (including HTML) // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF - if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { - return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); + if ( + /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test( + blob.type + ) + ) { + return new Blob([String.fromCharCode(0xfeff), blob], { type: blob.type }); } return blob; - } - , FileSaver = function(blob, name, no_auto_bom) { + }, + FileSaver = function (blob, name, no_auto_bom) { if (!no_auto_bom) { blob = auto_bom(blob); } // First try a.download, then web filesystem, then object URLs - var - filesaver = this - , type = blob.type - , force = type === force_saveable_type - , object_url - , dispatch_all = function() { - dispatch(filesaver, "writestart progress write writeend".split(" ")); - } + var filesaver = this, + type = blob.type, + force = type === force_saveable_type, + object_url, + dispatch_all = function () { + dispatch(filesaver, 'writestart progress write writeend'.split(' ')); + }, // on any filesys errors revert to saving with object URLs - , fs_error = function() { + fs_error = function () { if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { // Safari doesn't allow downloading of blob urls var reader = new FileReader(); - reader.onloadend = function() { - var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); + reader.onloadend = function () { + var url = is_chrome_ios + ? reader.result + : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); var popup = view.open(url, '_blank'); - if(!popup) view.location.href = url; - url=undefined; // release reference before dispatching + if (!popup) view.location.href = url; + url = undefined; // release reference before dispatching filesaver.readyState = filesaver.DONE; dispatch_all(); }; @@ -165,8 +195,9 @@ var _saveAs = (function(view) { } if (force) { view.location.href = object_url; - } else { - var opened = view.open(object_url, "_blank"); + } + else { + var opened = view.open(object_url, '_blank'); if (!opened) { // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html view.location.href = object_url; @@ -175,13 +206,12 @@ var _saveAs = (function(view) { filesaver.readyState = filesaver.DONE; dispatch_all(); revoke(object_url); - } - ; + }; filesaver.readyState = filesaver.INIT; if (can_use_save_link) { object_url = get_URL().createObjectURL(blob); - setTimeout(function() { + setTimeout(function () { save_link.href = object_url; save_link.download = name; click(save_link); @@ -193,16 +223,15 @@ var _saveAs = (function(view) { } fs_error(); - } - , FS_proto = FileSaver.prototype - , saveAs = function(blob, name, no_auto_bom) { - return new FileSaver(blob, name || blob.name || "download", no_auto_bom); - } - ; + }, + FS_proto = FileSaver.prototype, + saveAs = function (blob, name, no_auto_bom) { + return new FileSaver(blob, name || blob.name || 'download', no_auto_bom); + }; // IE 10+ (native saveAs) - if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { - return function(blob, name, no_auto_bom) { - name = name || blob.name || "download"; + if (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob) { + return function (blob, name, no_auto_bom) { + name = name || blob.name || 'download'; if (!no_auto_bom) { blob = auto_bom(blob); @@ -211,33 +240,31 @@ var _saveAs = (function(view) { }; } - FS_proto.abort = function(){}; + FS_proto.abort = function () {}; FS_proto.readyState = FS_proto.INIT = 0; FS_proto.WRITING = 1; FS_proto.DONE = 2; FS_proto.error = - FS_proto.onwritestart = - FS_proto.onprogress = - FS_proto.onwrite = - FS_proto.onabort = - FS_proto.onerror = - FS_proto.onwriteend = - null; + FS_proto.onwritestart = + FS_proto.onprogress = + FS_proto.onwrite = + FS_proto.onabort = + FS_proto.onerror = + FS_proto.onwriteend = + null; return saveAs; -}( - typeof self !== "undefined" && self - || typeof window !== "undefined" && window - || this.content -)); - +})( + (typeof self !== 'undefined' && self) || + (typeof window !== 'undefined' && window) || + this.content +); // Expose file saver on the DataTables API. Can't attach to `DataTables.Buttons` // since this file can be loaded before Button's core! DataTable.fileSave = _saveAs; - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Local (private) functions */ @@ -247,11 +274,10 @@ DataTable.fileSave = _saveAs; * * @param {object} config Button configuration */ -var _sheetname = function ( config ) -{ +var _sheetname = function (config) { var sheetName = 'Sheet1'; - if ( config.sheetName ) { + if (config.sheetName) { sheetName = config.sheetName.replace(/[\[\]\*\/\\\?\:]/g, ''); } @@ -264,13 +290,8 @@ var _sheetname = function ( config ) * @param {object} config Button configuration * @return {string} Newline character */ -var _newLine = function ( config ) -{ - return config.newline ? - config.newline : - navigator.userAgent.match(/Windows/) ? - '\r\n' : - '\n'; +var _newLine = function (config) { + return config.newline ? config.newline : navigator.userAgent.match(/Windows/) ? '\r\n' : '\n'; }; /** @@ -281,44 +302,41 @@ var _newLine = function ( config ) * @param {object} config Button configuration * @return {object} The data to export */ -var _exportData = function ( dt, config ) -{ - var newLine = _newLine( config ); - var data = dt.buttons.exportData( config.exportOptions ); +var _exportData = function (dt, config) { + var newLine = _newLine(config); + var data = dt.buttons.exportData(config.exportOptions); var boundary = config.fieldBoundary; var separator = config.fieldSeparator; - var reBoundary = new RegExp( boundary, 'g' ); - var escapeChar = config.escapeChar !== undefined ? - config.escapeChar : - '\\'; - var join = function ( a ) { + var reBoundary = new RegExp(boundary, 'g'); + var escapeChar = config.escapeChar !== undefined ? config.escapeChar : '\\'; + var join = function (a) { var s = ''; // If there is a field boundary, then we might need to escape it in // the source data - for ( var i=0, ien=a.length ; i 0 ) { + for (var i = 0, ien = a.length; i < ien; i++) { + if (i > 0) { s += separator; } - s += boundary ? - boundary + ('' + a[i]).replace( reBoundary, escapeChar+boundary ) + boundary : - a[i]; + s += boundary + ? boundary + ('' + a[i]).replace(reBoundary, escapeChar + boundary) + boundary + : a[i]; } return s; }; - var header = config.header ? join( data.header )+newLine : ''; - var footer = config.footer && data.footer ? newLine+join( data.footer ) : ''; + var header = config.header ? join(data.header) + newLine : ''; + var footer = config.footer && data.footer ? newLine + join(data.footer) : ''; var body = []; - for ( var i=0, ien=data.body.length ; i 1 && version[1]*1 < 603.1 ) { + var version = navigator.userAgent.match(/AppleWebKit\/(\d+\.\d+)/); + if (version && version.length > 1 && version[1] * 1 < 603.1) { return true; } @@ -352,14 +370,14 @@ var _isDuffSafari = function () * @param {int} n Column number * @return {string} Column letter(s) name */ -function createCellPos( n ){ +function createCellPos(n) { var ordA = 'A'.charCodeAt(0); var ordZ = 'Z'.charCodeAt(0); var len = ordZ - ordA + 1; - var s = ""; + var s = ''; - while( n >= 0 ) { - s = String.fromCharCode(n % len + ordA) + s; + while (n >= 0) { + s = String.fromCharCode((n % len) + ordA) + s; n = Math.floor(n / len) - 1; } @@ -369,8 +387,7 @@ function createCellPos( n ){ try { var _serialiser = new XMLSerializer(); var _ieExcel; -} -catch (t) {} +} catch (t) {} /** * Recursively add XML files from an object's structure to a ZIP file. This @@ -380,24 +397,28 @@ catch (t) {} * @param {JSZip} zip ZIP package * @param {object} obj Object to add (recursive) */ -function _addToZip( zip, obj ) { - if ( _ieExcel === undefined ) { +function _addToZip(zip, obj) { + if (_ieExcel === undefined) { // Detect if we are dealing with IE's _awful_ serialiser by seeing if it // drop attributes - _ieExcel = _serialiser - .serializeToString( - $.parseXML( excelStrings['xl/worksheets/sheet1.xml'] ) - ) - .indexOf( 'xmlns:r' ) === -1; + _ieExcel = + _serialiser + .serializeToString( + new window.DOMParser().parseFromString( + excelStrings['xl/worksheets/sheet1.xml'], + 'text/xml' + ) + ) + .indexOf('xmlns:r') === -1; } - $.each( obj, function ( name, val ) { - if ( $.isPlainObject( val ) ) { - var newDir = zip.folder( name ); - _addToZip( newDir, val ); + $.each(obj, function (name, val) { + if ($.isPlainObject(val)) { + var newDir = zip.folder(name); + _addToZip(newDir, val); } else { - if ( _ieExcel ) { + if (_ieExcel) { // IE's XML serialiser will drop some name space attributes from // from the root node, so we need to save them. Do this by // replacing the namespace nodes with a regular attribute that @@ -407,47 +428,49 @@ function _addToZip( zip, obj ) { var i, ien; var attrs = []; - for ( i=worksheet.attributes.length-1 ; i>=0 ; i-- ) { + for (i = worksheet.attributes.length - 1; i >= 0; i--) { var attrName = worksheet.attributes[i].nodeName; var attrValue = worksheet.attributes[i].nodeValue; - if ( attrName.indexOf( ':' ) !== -1 ) { - attrs.push( { name: attrName, value: attrValue } ); + if (attrName.indexOf(':') !== -1) { + attrs.push({ name: attrName, value: attrValue }); - worksheet.removeAttribute( attrName ); + worksheet.removeAttribute(attrName); } } - for ( i=0, ien=attrs.length ; i]*?) xmlns=""([^<>]*?)>/g, '<$1 $2>' ); + str = str.replace(/<([^<>]*?) xmlns=""([^<>]*?)>/g, '<$1 $2>'); - zip.file( name, str ); + zip.file(name, str); } - } ); + }); } /** @@ -460,22 +483,22 @@ function _addToZip( zip, obj ) { * (child nodes) and `text` (text content) * @return {node} Created node */ -function _createNode( doc, nodeName, opts ) { - var tempNode = doc.createElement( nodeName ); +function _createNode(doc, nodeName, opts) { + var tempNode = doc.createElement(nodeName); - if ( opts ) { - if ( opts.attr ) { - $(tempNode).attr( opts.attr ); + if (opts) { + if (opts.attr) { + $(tempNode).attr(opts.attr); } - if ( opts.children ) { - $.each( opts.children, function ( key, value ) { - tempNode.appendChild( value ); - } ); + if (opts.children) { + $.each(opts.children, function (key, value) { + tempNode.appendChild(value); + }); } - if ( opts.text !== null && opts.text !== undefined ) { - tempNode.appendChild( doc.createTextNode( opts.text ) ); + if (opts.text !== null && opts.text !== undefined) { + tempNode.appendChild(doc.createTextNode(opts.text)); } } @@ -488,27 +511,25 @@ function _createNode( doc, nodeName, opts ) { * @param {int} col Column index * @return {int} Column width */ -function _excelColWidth( data, col ) { +function _excelColWidth(data, col) { var max = data.header[col].length; var len, lineSplit, str; - if ( data.footer && data.footer[col].length > max ) { + if (data.footer && data.footer[col].length > max) { max = data.footer[col].length; } - for ( var i=0, ien=data.body.length ; i max ) { + if (len > max) { max = len; } // Max width rather than having potentially massive column widths - if ( max > 40 ) { + if (max > 40) { return 54; // 40 * 1.35 } } @@ -534,233 +555,234 @@ function _excelColWidth( data, col ) { // Excel - Pre-defined strings to build a basic XLSX file var excelStrings = { - "_rels/.rels": - ''+ - ''+ - ''+ + '_rels/.rels': + '' + + '' + + '' + '', - "xl/_rels/workbook.xml.rels": - ''+ - ''+ - ''+ - ''+ + 'xl/_rels/workbook.xml.rels': + '' + + '' + + '' + + '' + '', - "[Content_Types].xml": - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ + '[Content_Types].xml': + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '', - "xl/workbook.xml": - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ + 'xl/workbook.xml': + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '', - "xl/worksheets/sheet1.xml": - ''+ - ''+ - ''+ - ''+ + 'xl/worksheets/sheet1.xml': + '' + + '' + + '' + + '' + '', - "xl/styles.xml": - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ // Excel appears to use this as a dotted background regardless of values but - ''+ // to be valid to the schema, use a patternFill - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ + 'xl/styles.xml': + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + // Excel appears to use this as a dotted background regardless of values but + '' + // to be valid to the schema, use a patternFill + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '' }; // Note we could use 3 `for` loops for the styles, but when gzipped there is @@ -771,21 +793,50 @@ var excelStrings = { // Ref: section 3.8.30 - built in formatters in open spreadsheet // https://www.ecma-international.org/news/TC45_current_work/Office%20Open%20XML%20Part%204%20-%20Markup%20Language%20Reference.pdf var _excelSpecials = [ - { match: /^\-?\d+\.\d%$/, style: 60, fmt: function (d) { return d/100; } }, // Precent with d.p. - { match: /^\-?\d+\.?\d*%$/, style: 56, fmt: function (d) { return d/100; } }, // Percent - { match: /^\-?\$[\d,]+.?\d*$/, style: 57 }, // Dollars - { match: /^\-?£[\d,]+.?\d*$/, style: 58 }, // Pounds - { match: /^\-?€[\d,]+.?\d*$/, style: 59 }, // Euros - { match: /^\-?\d+$/, style: 65 }, // Numbers without thousand separators - { match: /^\-?\d+\.\d{2}$/, style: 66 }, // Numbers 2 d.p. without thousands separators - { match: /^\([\d,]+\)$/, style: 61, fmt: function (d) { return -1 * d.replace(/[\(\)]/g, ''); } }, // Negative numbers indicated by brackets - { match: /^\([\d,]+\.\d{2}\)$/, style: 62, fmt: function (d) { return -1 * d.replace(/[\(\)]/g, ''); } }, // Negative numbers indicated by brackets - 2d.p. - { match: /^\-?[\d,]+$/, style: 63 }, // Numbers with thousand separators - { match: /^\-?[\d,]+\.\d{2}$/, style: 64 } // Numbers with 2 d.p. and thousands separators + { + match: /^\-?\d+\.\d%$/, + style: 60, + fmt: function (d) { + return d / 100; + } + }, // Percent with d.p. + { + match: /^\-?\d+\.?\d*%$/, + style: 56, + fmt: function (d) { + return d / 100; + } + }, // Percent + { match: /^\-?\$[\d,]+.?\d*$/, style: 57 }, // Dollars + { match: /^\-?£[\d,]+.?\d*$/, style: 58 }, // Pounds + { match: /^\-?€[\d,]+.?\d*$/, style: 59 }, // Euros + { match: /^\-?\d+$/, style: 65 }, // Numbers without thousand separators + { match: /^\-?\d+\.\d{2}$/, style: 66 }, // Numbers 2 d.p. without thousands separators + { + match: /^\([\d,]+\)$/, + style: 61, + fmt: function (d) { + return -1 * d.replace(/[\(\)]/g, ''); + } + }, // Negative numbers indicated by brackets + { + match: /^\([\d,]+\.\d{2}\)$/, + style: 62, + fmt: function (d) { + return -1 * d.replace(/[\(\)]/g, ''); + } + }, // Negative numbers indicated by brackets - 2d.p. + { match: /^\-?[\d,]+$/, style: 63 }, // Numbers with thousand separators + { match: /^\-?[\d,]+\.\d{2}$/, style: 64 }, + { + match: /^[\d]{4}\-[01][\d]\-[0123][\d]$/, + style: 67, + fmt: function (d) { + return Math.round(25569 + Date.parse(d) / (86400 * 1000)); + } + } //Date yyyy-mm-dd ]; - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Buttons */ @@ -796,83 +847,87 @@ var _excelSpecials = [ DataTable.ext.buttons.copyHtml5 = { className: 'buttons-copy buttons-html5', - text: function ( dt ) { - return dt.i18n( 'buttons.copy', 'Copy' ); + text: function (dt) { + return dt.i18n('buttons.copy', 'Copy'); }, - action: function ( e, dt, button, config ) { - this.processing( true ); + action: function (e, dt, button, config) { + this.processing(true); var that = this; - var exportData = _exportData( dt, config ); - var info = dt.buttons.exportInfo( config ); + var exportData = _exportData(dt, config); + var info = dt.buttons.exportInfo(config); var newline = _newLine(config); var output = exportData.str; - var hiddenDiv = $('
') - .css( { - height: 1, - width: 1, - overflow: 'hidden', - position: 'fixed', - top: 0, - left: 0 - } ); - - if ( info.title ) { + var hiddenDiv = $('
').css({ + height: 1, + width: 1, + overflow: 'hidden', + position: 'fixed', + top: 0, + left: 0 + }); + + if (info.title) { output = info.title + newline + newline + output; } - if ( info.messageTop ) { + if (info.messageTop) { output = info.messageTop + newline + newline + output; } - if ( info.messageBottom ) { + if (info.messageBottom) { output = output + newline + newline + info.messageBottom; } - if ( config.customize ) { - output = config.customize( output, config, dt ); + if (config.customize) { + output = config.customize(output, config, dt); } - var textarea = $('