Code Monkey home page Code Monkey logo

effective_datatables's Introduction

Effective DataTables

Use a high level DSL and just one ruby file to create a Datatables jQuery table for any ActiveRecord class or Array.

Powerful server-side searching, sorting and filtering of ActiveRecord classes, with belongs_to and has_many relationships.

Does the right thing with searching sql columns as well as computed values from both ActiveRecord and Array collections.

Displays links to associated edit/show/destroy actions based on current_user authorized actions.

Other features include aggregate (total/average) footer rows, bulk actions, show/hide columns, responsive collapsing columns, google charts, and inline crud.

This gem includes the jQuery DataTables assets.

Works with postgres, mysql, sqlite3 and arrays.

Live Demo

Click here for a Live Demo.

See effective_datatables_demo for a working rails website example.

effective_datatables 4.0

This is the 4.0 series of effective_datatables.

This requires Twitter Bootstrap 4 and Rails 5.1+

Please check out Effective Datatables 3.x for more information using this gem with Bootstrap 3.

Contents

Getting Started

gem 'haml-rails'            # or try using gem 'hamlit-rails'
gem 'effective_datatables'

Run the bundle command to install it:

bundle install

Install the configuration file:

rails generate effective_datatables:install

The generator will install an initializer which describes all configuration options.

Make sure you have Twitter Bootstrap 4 installed.

Require the javascript on the asset pipeline by adding the following to your application.js:

//= require effective_datatables

Require the stylesheet on the asset pipeline by adding the following to your application.css:

*= require effective_datatables

Quick Start

All logic for the table exists in its own model file. Once that's built, we initialize in the controller, render in the view.

The Model

Start by creating a new datatable.

This model exists at /app/datatables/posts_datatable.rb:

class PostsDatatable < Effective::Datatable
  datatable do
    col :created_at
    col :title
    col :user      # Post belongs_to :user
    col :comments  # Post has_many :comments

    actions_col
  end

  collection do
    Post.all
  end
end

The Controller

We're going to display this DataTable on the posts#index action.

class PostsController < ApplicationController
  def index
    @datatable = PostsDatatable.new
  end
end

The View

Here we just render the datatable:

<h1>All Posts</h1>
<%= render_datatable(@datatable) %>

Usage

Once your controller and view are set up to render a datatable, the model is the central point to configure all behaviour.

Here is an advanced example:

The Model

This model exists at /app/datatables/posts_datatable.rb:

class PostsDatatable < Effective::Datatable

  # The collection block is the only required section in a datatable
  # It has access to the attributes and filters Hashes, representing the current state
  # It must return an ActiveRecord::Relation or an Array of Arrays
  collection do
    scope = Post.all.joins(:user).where(created_at: filters[:start_date]...filters[:end_date])
    scope = scope.where(user_id: attributes[:user_id]) if attributes[:user_id]
    scope
  end

  # Everything in the filters block ends up in a single form
  # The form is submitted by datatables javascript as an AJAX post
  filters do
    # Scopes are rendered as a single radio button form field (works well with effective_bootstrap gem)
    # The scopes only work when your collection is an ActiveRecord class, and they must exist on the model
    # The current scope is automatically applied by effective_datatables to your collection
    # You don't have to consider the current scope when writing your collection block
    scope :all, default: true
    scope :approved
    scope :draft
    scope :for_user, (attributes[:user_id] ? User.find(attributes[:user_id]) : current_user)

    # Each filter has a name and a default value and the default can be nil
    # Each filter is displayed on the front end form as a single field
    # The filters are NOT automatically applied to your collection
    # You are responsible for considering filters in your collection block
    filter :start_date, Time.zone.now-3.months, required: true
    filter :end_date, Time.zone.now.end_of_day
  end

  # These are displayed as a dropdown menu next to the datatables built-in buttons.
  bulk_actions do
    # bulk_action is just passthrough to link_to(), but the action of POST is forced
    # POSTs to the given url with params[:ids], an Array of ids for all selected rows
    # These actions are assumed to change the underlying collection
    bulk_action 'Approve all', bulk_approve_posts_path, data: { confirm: 'Approve all selected posts?' }
    # GETs to the given url. Pass the ids via cookie, encoded in url, or LocalStorage.
    # These actions are assumed to redirect the user to some other page.
    bulk_action 'Action 1 | ids encoded in params', action_1_posts_path, data: { method: :get }

    bulk_action 'Action 2 | ids stored in _ids_ field in local storage', action_2_posts_path, data: { 'payload-mode' => 'local-storage', method: :get }

    bulk_action 'Action 3 | ids stored in _ids_ field in a cookie', action_3_posts_path, data: { 'payload-mode' => 'cookie', method: :get }
    bulk_action_divider
    bulk_action 'Destroy all', bulk_destroy_posts_path, data: { confirm: 'Destroy all selected posts?' }
  end

  # Google Charts
  # https://developers.google.com/chart/interactive/docs/quick_start
  # effective_datatables does all the javascript boilerplate. Just return an Array of Arrays.
  # Charts are updated whenever the current filters and search change
  charts do
    chart :posts_per_day, 'LineChart', label: 'Posts per Day', legend: false do |collection|
      collection.group_by { |post| post.created_at.beginning_of_day }.map do |date, posts|
        [date.strftime('%F'), posts.length]
      end
    end
  end

  # Datatables
  # https://datatables.net/
  # Each column header has a form field controlled by the search: { as: :string } option
  # The user's selected filters, search, sort, length, column visibility and pagination settings are saved between visits
  # on a per-table basis and can be Reset with a button
  datatable do
    length 25  # 5, 10, 25, 50, 100, 500, :all
    order :updated_at, :desc

    # Renders a column of checkboxes to select items for any bulk_actions
    bulk_actions_col

    col :id, visible: false
    col :updated_at, visible: false

    col :created_at, label: 'Created' do |post|
      time_ago_in_words(post.created_at)
    end

    # This is a belongs_to column
    # effective_datatables will try to put in an edit or show link, depending on the current_user's authorization
    # It will also initialize the search field with PostCategory.all
    col :post_category, action: :edit

    if attributes[:user_id].nil?  # Show all users, otherwise this table is meant for one user only
      col :user, search: User.authors.all
    end

    col 'user.first_name'  # Using the joined syntax

    if can?(:index, Comment)
      col :comments
    end

    col :category, search: { collection: Post::CATEGORY } do |survey|
      Post::CATEGORY.invert[post.category]
    end

    # This is a computed method, not an attribute on the post database table.
    # The first block takes the object from the collection do ... end block and does some work on it.
    # It computes some value. A val.
    # The first block returns a Float/Integer.  All sorting/ordering is then performed on this number.
    # The second block formats the number and returns a String
    val :approval_rating do |post|
      post.approvals.sum { |a| a.rating }
    end.format do |rating|
      number_to_percentage(rating, precision: 2)
    end

    # In a col there is only one block, the format block.
    # A col takes the value as per the collection do ... end block and just formats it
    # All sorting/ordering is performed as per the original value.
    col :approved do |post|
      if post.approved?
        content_tag(:span, 'Approved', 'badge badge-approved')
      else
        content_tag(:span, 'Draft', 'badge badge-draft')
      end
    end

    # Will add a Total row to the table's tfoot
    # :average is also supported, or you can do a custom block
    aggregate :total

    # Uses effective_resources gem to discover the resource path and authorization actions
    # Puts links to show/edit/destroy actions, if authorized to those actions.
    # Use the actions_col block to add additional actions

    actions_col

    # actions_col(edit: false) do |post|
    #   dropdown_link_to('Approve', approve_post_path(post) data: { method: :post, confirm: "Approve #{post}?"})
    # end
  end

end

The Controller

Any options used to initialize a datatable become the attributes. Use these to configure datatables behavior.

In the above example, when attributes[:user_id] is present, the table displays information for just that user.

class PostsController < ApplicationController
  def index
    @datatable = PostsDatatable.new(user_id: current_user.id)
  end
end

The View

Render the datatable with its filters and charts, all together:

<h1>All Posts</h1>
<%= render_datatable(@datatable) %>

or, the datatable, filter and charts may be rendered individually:

<h1>All Posts</h1>
<p>
  <%= render_datatable_filters(@datatable) %>
</p>

<p>
  <%= render_datatable_charts(@datatable) %>
</p>

<p>
<%= render_datatable(@datatable, charts: false, filters: false) %>
</p>

or, to render a simple table, (without filters, charts, pagination, sorting, searching, export buttons, per page, or default visibility):

<%= render_datatable(@datatable, simple: true) %>

DSL

The effective_datatables DSL is made up of 5 sections: collection, datatable, filters bulk_actions, charts

As well, a datatable can be initialized with attributes.

attributes

When initialized with a Hash, that hash is available throughout the entire datatable as attributes.

You can call the attributes from within the datatable as attributes or within a partial/view as @datatable.attributes.

These attributes are serialized and stored in an encrypted data attribute. Objects won't work. Keep it simple.

Attributes cannot be changed by search, filter, or state in any way. They're guaranteed to be the same as when first initialized.

class PostsController < ApplicationController
  def index
    @datatable = PostsDatatable.new(user_id: current_user.id, admin: true)
  end
end

Use attributes to restrict the collection scope, exclude columns or otherwise tweak the table.

An example of using attributes[:user_id] to make a user specific posts table is above.

Here we do something similar with attributes[:admin]:

class PostsDatatable < Effective::Datatable
  collection do
    attributes[:admin] ? Post.all : Post.where(draft: false)
  end

  datatable do
    col :title

    if attributes[:admin]
      col :user
    end

    col :post_category
    col :comments
  end
end

collection

The collection do ... end block must return an ActiveRecord relation or an Array of Arrays.

collection do
  Post.all
end

or

collection do
  scope = Post.includes(:user).where(created_at: filters[:start_date]...filters[:end_date])
  scope = scope.where(user_id: attributes[:user_id]) if attributes[:user_id]
  scope
end

or

collection do
  [
    ['June', 'Huang', '[email protected]'],
    ['Leo', 'Stubbs', '[email protected]'],
    ['Quincy', 'Pompey', '[email protected]'],
    ['Annie', 'Wojcik', '[email protected]'],
  ]
end

or

collection do
  time_entries = TimeEntry.where(date: filter[:start_date].beginning_of_year...filter[:end_date].end_of_year)
    .group_by { |time_entry| "#{time_entry.client_id}_#{time_entry.created_at.strftime('%b').downcase}" }

  Client.all.map do |client|
    [client] + [:jan, :feb, :mar, :apr, :may, :jun, :jul, :aug, :sep, :oct, :nov, :dec].map do |month|
      entries = time_entries["#{client.id}_#{month}"] || []

      calc = TimeEntryCalculator.new(entries)

      [calc.duration, calc.bill_duration, calc.overtime, calc.revenue, calc.cost, calc.net]
    end
  end
end

The collection block is responsible for applying any attribute and filters logic.

When an ActiveRecord collection, the current_scope, will be applied automatically by effective_datatables.

All searching and ordering is also done by effective_datatables.

Your collection method should not contain a .order(), or implement search in any way.

Sometimes it's handy to call .reorder(nil) on a scope.

datatable

The datatable do ... end block configures a table of data.

Initialize the datatable in your controller or view, @datatable = PostsDatatable.new(self), and render it in your view <%= render_datatable(@datatable) %>

col

This is the main DSL method that you will interact with.

col defines a 1:1 mapping between the underlying SQL database table column or Array index to a frontend jQuery Datatables table column. It creates a column.

Each column's search and sorting is performed on its underlying value, as per the collection.

It accepts one optional block used to format the value after any search or sorting is done.

The following options are available:

action: :show|:edit|false  # Render as a link to this action. edit -> show by default
as: :string|:integer|etc   # Sets the type of column initializing defaults for search, sort and format
col_class: 'col-green'     # Sets the html class to use on this column's td and th
label: 'My label'          # The label for this column
partial: 'posts/category'  # Render this column with a partial. The local will be named resource
partial_as: 'category'     # The name of the object's local variable, otherwise resource
responsive: 10000          # Controls how columns collapse https://datatables.net/reference/option/columns.responsivePriority

# Configure the search behavior. Autodetects by default.
search: false
search: :string
search: { as: :string, fuzzy: true }
search: User.all
search: { as: :select, collection: User.all, multiple: true, include_null: 'All Users' }
search: { collection: { 'All Books' => Book.all, 'All Shirts' => Shirt.all}, polymorphic: true }

sort: true|false           # Should this column be orderable. true by default
sql_column: 'posts.rating' # The sql column to search/sort on. Only needed when doing custom selects or tricky joins.
visible: true|false        # Show/Hide this column by default

The :as setting determines a column's search, sort and format behaviour.

It is auto-detected from an ActiveRecord collection's SQL datatype, and set to :string for any Array-based collections.

Valid options for :as are as follows:

:boolean, :currency, :datetime, :date, :decimal, :duration, :email, :float, :integer, :percent, :price, :resource, :string, :text

These settings are loosely based on the regular datatypes, with some custom effective types thrown in:

  • :currency expects the underlying datatype to be a Float.
  • :duration expects the underlying datatype to be an Integer representing the number of minutes. 120 == 2 hours
  • :email expects the underlying datatype to be a String
  • :percent expects the underlying datatype to be an Integer * 1000. 50000 == 50%. 50125 == 50.125%.
  • :price expects the underlying datatype to be an Integer representing the number of cents. 5000 == $50.00
  • :resource can be used for an Array based collection which includes an ActiveRecord object

The column will be formatted as per its as: setting, unless a custom format block is present:

col :approved do |post|
  if post.approved?
    content_tag(:span, 'Approved', 'badge badge-approved')
  else
    content_tag(:span, 'Draft', 'badge badge-draft')
  end
end

You can also set custom search and sort on a per-column basis. See Advanced Search and Sort below.

If the column name matches a belongs_to, has_many or other association on your collection class, like col :user, the column will be created as a resource column.

A resource column will try to link to the show/edit/destroy actions of its objects, based on permissions and routes. You can alter this behaviour with the action: variable.

You can also use the joined syntax, col 'user.email' to create a column for just this one field.

This feature is only working with belongs_to and you need to add the .joins(:user) to the collection do ... end block yourself.

val

Shorthand for value, this command also creates a column on the datatable.

It accepts all the same options as col with the additional requirement of a "compute" block.

val :approval_rating do |post|
  post.approvals.sum { |a| a.rating }
end.format do |rating|
  number_to_percentage(rating, precision: 2)
end

So, val yields the object from the collection to the first/compute block, and stores the result.

All searching and sorting for this column will be performed on this computed value.

This is implemented as a full Array search/sort and is much slower for large datasets than a paginated SQL query

The .format do ... end block can then be used to apply custom formatting.

bulk_actions_col

Creates a column of checkboxes for use with the bulk_actions section.

Each input checkbox has a value equal to its row object.to_param and gets submitted as an Array of ids, params[:ids]

Use these checkboxes to select all / none / one or more rows for the bulk_actions do ... end section (below).

You can only have one bulk_actions_col per datatable.

actions_col

When working with an ActiveRecord based collection, this column will consider the current_user's authorization, and generate links to edit, show and destroy actions for any collection class.

The authorization method is configured via the config/initializers/effective_datatables.rb initializer file.

There are just a few options:

btn_class: 'btn-sm btn-outline-primary'
show: true|false
edit: true|false
destroy: true|false
visible: true|false
actions_partial: :dropleft
inline: true|false

Each object is checked individually for authorization.

The arguments to actions_col are passed through to the effective_resource gem's render_resource_actions.

It's all very complicated.

If you just want to override this entire column with your own actions implementation, you can pass actions_col partial: 'my_partial' and roll your own.

Otherwise, use the following block syntax to add additional actions. This helper comes from effective_bootstrap gem.

actions_col do |post|
  dropdown_link_to('Approve', approve_post_path(post) data: { method: :post, confirm: "Approve #{post}?"})
end

Any data-remote actions will be hijacked and performed as inline ajax by datatables.

If you'd like to opt-out of this behavior, use actions_col(inline: false) or add data-inline: false to your action link.

If the automatic actions_col aren't being displayed, try setting the namespace directly when calling the table

MyApp::UsersTable.new(namespace: :my_app)

length

Sets the default number of rows per page. Valid lengths are 5, 10, 25, 50, 100, 250, 500, :all

When not specified, effective_datatables uses the default as per the config/initializers/effective_datatables.rb or 25.

length 100

order

Sets the default order of table rows. The first argument is the column, the second the direction.

The column must exist as a col or val and the direction is either :asc or :desc.

When not specified, effective_datatables will sort by the first defined column.

order :created_at, :asc|:desc

reorder

Enables drag-and-drop row re-ordering.

Only works with ActiveRecord collections.

The underlying field must be an Integer, and it's assumed to be a sequential list of unique numbers.

When a drag and drop is completed, a POST request is made to the datatables#reorder action that will update the indexes.

Both zero and one based lists will work.

reorder :position

Using reorder will sort the collection by this field and disable all other column sorting.

aggregate

The aggregate command inserts a row in the table's tfoot.

The only option available is :label.

You can only have one aggregate per datatable. (Unfortunately, this is a limit of the jQuery Datatables)

There is built in support for automatic :total and :average aggregates:

aggregate :total|:average

or write your own:

aggregate :average_as_percentage do |values, column|
  if column[:name] == :first_name
    'Average'
  elsif values.present?
    average = values.map { |value| value.presence || 0 }.sum / [values.length, 1].max
    content_tag(:span, number_to_percentage(average, precision: 1))
  end
end

You can also override an individual columns aggregate calculation as follows:

col :created_at, label: 'Created' do |post|
  time_ago_in_words(post.created_at)
end.aggregate { |values, column| distance_of_time_in_words(values.min, values.max) }

In the above example, values is an Array containing all row's values for one column at a time.

download

Add a Download button which streams a CSV file containing all rows and columns in the table's collection, ignoring any search, sort or filtering.

This is an opt-in feature.

To enable, please set config.download = true in your config/initializers/effective_datatables.rb file.

Once enabled, you can disable it on an individual table by:

datatable do
  download false
end

and you can exclude individual columns from being rendered on the CSV export

col :first_name, csv: false

The column will still appear in the export, but the contents will be blank.

filters

Creates a single form with fields for each filter and a single radio input field for all scopes.

The form is submitted by an AJAX POST action, or, in some advanced circumstances (see Dynamic Columns below) as a regular POST or even GET.

Initialize the datatable in your controller or view, @datatable = PostsDatatable.new(self), and render its filters anywhere with <%= render_datatable_filters(@datatable) %>.

scope

All defined scopes are rendered as a single radio button form field. Works great with the effective_form_inputs gem.

Only supported for ActiveRecord based collections. They must exist as regular scopes on the model.

The currently selected scope will be automatically applied. You shouldn't consider it in your collection block.

filters do
  scope :approved
  scope :for_user, current_user
end

Must match the scopes in your app/models/post.rb:

class Post < ApplicationRecord | ActiveRecord::Base
  scope :approved, -> { where(draft: false) }
  scope :for_user, Proc.new { |user| where(user: user) }
end

filter

Each filter has a name and a default/fallback value. If the form is submitted blank, the default values are used.

effective_datatables looks at the default value, and tries to cast the incoming (String) value into that datatype.

This ensures that calling filters[:name] always return a value. The default can be nil.

You can override the parsing on a per-filter basis.

Unlike scopes, the filters are NOT automatically applied to your collection. You are responsible for considering filters in your collection block.

filters do
  filter :start_date, Time.zone.now-3.months, required: true
  filter :end_date, nil, parse: -> { |term| Time.zone.local(term).end_of_day }
  filter :user, current_user, as: :select, collection: User.all
  filter :year, 2018, as: :select, collection: [2018, 2017], label: false, include_blank: false
  filter :year_group, '2018', as: :select, grouped: true, collection: { 'Years' => [['2017', 2017], ['2018', 2018]], 'Months' => [['January', 1], ['February', 2]] }
end

and apply these to your collection do ... end block by calling filters[:start_date]:

collection do
  scope = Post.includes(:post_category, :user).where('created_at > ?', filters[:start_date])

  if filters[:end_date].present?
    scope = scope.where('created_at < ?', filters[:end_date])
  end

  scope
end

The filter command has the following options:

as: :select|:date|:boolean      # Passed to form
label: 'My label'               # Label for this form field
parse: -> { |term| term.to_i }  # Parse the incoming term (string) into whatever datatype
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:

filters do
  filter_date_range
end

collection do
  Thing.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.

Along with this section, you must put a bulk_actions_col somewhere in your datatable do ... end section.

bulk_action

Creates a link that becomes clickable when one or more checkbox/rows are selected as per the bulk_actions_col column.

A controller action must be created to accept a POST with an array of selected ids, params[:ids].

This is a pass-through to link_to and accepts all the same options, except that the method POST is used by default.

You can also specify data-method: :get to instead make a GET request with the selected ids and redirect the browser link a normal link.

bulk_actions do
  bulk_action 'Approve all', bulk_approve_posts_path, data: { confirm: 'Approve all selected posts?' }

  bulk_action 'Action 1 | ids encoded in params', action_1_posts_path, data: { method: :get }

  bulk_action 'Action 2 | ids stored in _ids_ field in local storage', action_2_posts_path, data: { 'payload-mode' => 'local-storage', method: :get }

  bulk_action 'Action 3 | ids stored in _ids_ field in a cookie', action_3_posts_path, data: { 'payload-mode' => 'cookie', method: :get }
end

In your routes file:

resources :posts do
  collection do
    post :bulk_approve
    get :action_1
    get :action_2
    get :action_3
  end
end

In your PostsController:

def bulk_approve
  @posts = Post.where(id: params[:ids])

  # You should probably write this inside a transaction.  This is just an example.
  begin
    @posts.each { |post| post.approve! }
    render json: { status: 200, message: "Successfully approved #{@posts.length} posts." }
  rescue => e
    render json: { status: 500, message: 'An error occured while approving a post.' }
  end
end

def action_1
  @posts = Post.where(id: params[:ids])

  render :some_partial
end

def action_2
  @posts = Post.where(id: cookies[:ids].split(','))

  render :some_partial
end

def action_3
  render :some_partial # and get ids via JS:  localStorage.getItem('ids');
end

or if using effective_resources:

include Effective::CrudController

and in your model

def approve!
  update_attributes!(status: :approved)
end

bulk_action_divider

Inserts a menu divider <li class='divider' role='separator'></li>

bulk_download

So it turns out there are some http issues with using an AJAX action to download a file.

A workaround for these issues is included via the jQuery File Download Plugin

The use case for this feature is to download a csv report generated for the selected rows.

bulk_actions do
  bulk_download 'Export Report', bulk_export_report_path
end
def bulk_export_report
  authorize! :export, Post

  @posts = Post.where(id: params[:ids])

  Post.transaction do
    begin
      cookies[:fileDownload] = true

      send_data(PostsExporter.new(@posts).export,
        type: 'text/csv; charset=utf-8; header=present',
        filename: 'posts-export.csv'
      )

      @posts.update_all(exported_at: Time.zone.now)
      return
    rescue => e
      cookies.delete(:fileDownload)
      raise ActiveRecord::Rollback
    end
  end

  render json: { error: 'An error occurred' }
end

bulk_action_content

Blindly inserts content into the dropdown.

bulk_actions do
  bulk_action_content do
    content_tag(:li, 'Something')
  end
end

Don't actually use this.

Charts

Create a Google Chart based on your searched collection, filters and attributes.

No javascript required. Just use the chart do ... end block and return an Array of Arrays.

The first collection, collection is the raw results as returned from the collection do block.

The second collection, searched_collection is the results after the table's search columns have been applied, but irregardless of pagination.

charts do
  chart :breakfast, 'BarChart' do |collection|
    [
      ['Bacon', 10],
      ['Eggs', 20],
      ['Toast', 30]
    ]
  end

  chart :posts_per_day, 'LineChart', label: 'Posts per Day', legend: false do |collection|
    collection.group_by { |post| post.created_at.beginning_of_day }.map do |date, posts|
      [date.strftime('%F'), posts.length]
    end
  end

  chart :posts_per_user, 'ColumnChart' do |collection, searched_collection|
    measured_posts = if search.present?
      ["Posts with #{search.map { |k, v| k.to_s + ' ' + v.to_s }.join(',')}", searched_collection.length]
    else
      ['All Posts', collection.length]
    end

    [['Posts', 'Count'], measured_posts] +
    searched_collection.group_by(&:user).map { |user, posts| [user.last_name, posts.length] }
  end

end

And then render each chart in your view:

<%= render_datatable_chart(@datatable, :breakfast) %>
<%= render_datatable_chart(@datatable, :posts_per_day) %>

or all together

<%= render_datatable_charts(@datatable) %>

All options passed to chart are used to initialize the chart javascript.

By default, the only package that is loaded is corechart, see the config/initializers/effective_datatables.rb file to add more packages.

Inline

Any datatable can be used as an inline datatable, to create, update and destroy resources without leaving the current page.

If your datatable is already working with actions_col and being rendered from an Effective::CrudController controller, all you need to do is change your view from render_datatable(@datatable) to render_datatable(@datatable, inline: true).

Click here for a Inline Live Demo and here for an Inline Code Example (only the thing data model and things_datatable are being used inline)

To use effective_datatables as an inline CRUD builder, you will be relying heavily on effective_resources which is a dependency of this gem. I would also recommend you install effective_developer to get access to some scaffolds and generators. It's not required but I'm gonna use them in this example.

Here is how I build rails models for inline datatable CRUD operations:

  1. Create a new model file app/models/thing.rb:
class Thing < ApplicationRecord
  belongs_to :user

  effective_resource do
    title           :string
    description     :text
    timestamps
  end

  scope :deep, -> { includes(:user) }
  scope :sorted, -> { order(:title) }

  def to_s
    title
  end
end

The effective_resource do block comes from the effective_resources gem and is used to build any permitted_params.

  1. Generate a migration. Run rails generate effective:migration things to create a migration based off the model file then rails db:migrate.

  2. Scaffold the rest. Run rails generate effective:scaffold_controller things which will create:

  • A controller app/controllers/things_controller.rb:
class ThingsController < ApplicationController
  include Effective::CrudController
end

The Effective::CrudController comes from effective_resources gem and handles the standard 7 CRUD actions and member and collection actions. It is opinionated code that follows rails conventions. It considers the routes.rb and ability.rb or other authorization, to find all available actions.

  • A datatable app/datatables/things_datatable.rb:
class ThingsDatatable < Effective::Datatable
  datatable do
    col :title
    col :description
    actions_col
  end

  collection do
    Thing.deep.all
  end
end

This is an ordinary datatable. As long as it's an ActiveRecord collection, inline crud will work.

  • A view partial app/views/things/_thing.html.haml:
%table.table
  %tbody
    %tr
      %th Title
      %td= thing.title
    %tr
      %th Description
      %td= thing.description

This file is what rails uses when you call render(thing) and what datatables uses for the inline show action. It's important that its called _thing.html.

  • A form partial app/views/things/_form.html.haml:
= effective_form_with(model: thing) do |f|
  = f.text_field :title
  = f.text_area :description
  = f.submit

The effective_form_with comes from effective_bootstrap gem and is a drop-in replacement for the newer form_with syntax. It's really good, you should use it, but an ordinary form_with will work here just fine.

This _form.html is an effective gems convention. This file must exist for your resource.

  • A resources entry in config/routes.rb:
Rails.application.routes.draw do
  resources :things do
    post :approve, on: :member
    post :reject, on: :member
  end
end

Above we have resources :things for the 7 crud actions. And we add two more member actions, which datatables will call approve! or reject! on thing.

  1. Render in the view. Create an app/views/things/index.html.haml and call render_datatable(@datatable, inline: true) or `render_inline_datatable(@datatable).
= render_datatable(@datatable, inline: true)

Your datatable should now have New, Show, Edit, Approve and Reject buttons. Click them for inline functionality.

Adding an Ajax member action

To render a member action with an inline datatable:

  • Create a "cool_things.html" template and a "_cool_things.html" partial file. Need both.

  • The links must be inside an actions_col or a col(:thing, col_class: 'col-actions') for the javascript to work.

  • The action itself just needs to be data-remote=true. Try link_to('Show Cool Things', thing_cool_things_path(thing), 'data-remote': true)

Make sure the route and permissions are working:

resources :things do
  get :cool_things, on: :member

and can?(:cool_things, Thing)

Good luck.

Troubleshooting Inline

If things aren't working, try the following:

  • Double check your javascripts:
//= require jquery3
//= require popper
//= require bootstrap
//= require effective_bootstrap
//= require effective_datatables
//= require jquery_ujs

The inline functionality requires one of sprockets jquery_ujs, sprockets rails_ujs or webpack @rails/ujs libraries.

  • Double check your stylesheets:
@import 'bootstrap';
@import 'effective_bootstrap';
@import 'effective_datatables';
  • Make sure your datatable is not being rendered inside a <form>...</form> tag. It will display a javascript console error and won't work.

  • Double check your resources :things are in routes.rb in the same namespace as the controller, and that you have authorization for those actions in ability.rb or whatever your config/initializers/effective_datatables.rb config.authorization_method returns.

A note on how it works

We use good old rails_ujs for all inline actions.

When inline, any of the actions_col actions, as well as the New button, will be changed into data-remote: true actions.

The inline_crud javascript handles fetching the form, or view partial and expanding/collapsing the appropriate row of the datatable.

When an inline action is clicked, effective_datatables will make an AJAX request to the server, which could be received by an Effective::CrudController that will handle the .js format, and respond_with the appropriate rails_ujs .js.erb views.

Extras

The following commands don't quite fit into the DSL, but are present nonetheless.

simple

To render a simple table, without pagination, sorting, filtering, export buttons, per page, and default visibility:

<%= render_datatable(@datatable, simple: true) %>

index

If you just want to render a datatable and nothing else, there is a quick way to skip creating a view:

class PostsController < ApplicationController
  def index
    render_datatable_index PostsDatatable.new(self)
  end
end

will render views/effective/datatables/index with the assigned datatable.

Advanced Search and Sort

The built-in search and ordering can be overridden on a per-column basis.

The only gotcha here is that you must be aware of the type of collection.

With ActiveRecord collection

In the case of a col and an ActiveRecord collection:

collection do
  Post.all
end

datatable do
  col :post_category do |post|
    content_tag(:span, post.post_category, "badge-#{post.post_category}")
  end.search do |collection, term, column, sql_column|
    # collection is an ActiveRecord scoped collection
    # term is the incoming PostCategory ID as per the search
    # column is this column's options Hash
    # sql_column is the column[:sql_column]
    categories = current_user.post_categories.where(id: term.to_i)

    collection.where(post_category_id: categories)  # Must return an ActiveRecord scope
  end.sort do |collection, direction, column, sql_column|
    collection.joins(:post_category).order(:post_category => :title, direction)
  end
end

If you run into issues where collection here is an Array, you're probably using some joins in your collection do ... end block.

If column[:sql_column].blank? then this col has fallen back to being a val.

Try adding col :post_category, sql_column: 'post_categories.title'

With Array collection

And in the case of a col with an Array collection, or any val:

collection do
  Client.all.map do |client|
    [client, client.first_name client.last_name, client.purchased_time()]
  end
end

datatable do
  col :client
  col :first_name
  col :last_name

  col :purchased_time do |duration|
    number_to_duration(duration)
  end.search do |collection, term, column, index|
    # collection is an Array of Arrays
    # term is the incoming value as per the search. "3h30m"
    # column is the column's attributes Hash
    # index is this column's index in the collection
    (hours, minutes) = term.to_s.gsub(/[^0-9|h]/, '').split.map(&:to_i)
    duration = (hours.to_i * 60) + minutes.to_i

    collection.select! { |row| row[index] == duration }  # Must return an Array of Arrays
  end.sort do |collection, direction, column, index|
    if direction == :asc
      collection.sort! { |a, b| a[index] <=> b[index] }
    else
      collection.sort! { |a, b| b[index] <=> a[index] }
    end
  end
end

The search and sort for each column will be merged together to form the final results.

Default search collection

When using a col :comments type belongs_to or has_many column, a search collection for that class will be loaded.

Add the following to your related model to customize the search collection:

class Comment < ApplicationRecord
  scope :datatables_filter, -> { includes(:user) }
end

Datatables will look for a datatables_filter scope, or sorted scope, or fallback to all.

If there are more than 500 max records, the filter will fallback to a as: :string.

Dynamic Column Count

There are some extra steps to be taken if you want to change the number of columns based on filters.

Unfortunately, the DataTables jQuery doesn't support changing columns, so submitting filters needs to be done via POST instead of AJAX.

The following example displays a client column, and one column per month for each month in a date range:

class TimeEntriesPerClientReport < Effective::Datatable

  filters do
    # This instructs the filters form to use a POST, if available, or GET instead of AJAX
    # It posts to the current controller/action, and there are no needed changes in your controller
    changes_columns_count

    filter :start_date, (Time.zone.now - 6.months).beginning_of_month, required: true, label: 'For the month of: ', as: :effective_date_picker
    filter :end_date, Time.zone.now.end_of_month, required: true, label: 'upto and including the whole month of', as: :effective_date_picker
  end

  datatable do
    length :all

    col :client

    selected_months.each do |month|
      col month.strftime('%b %Y'), as: :duration
    end

    actions_col
  end

  collection do
    time_entries = TimeEntry.where(date: filter[:start_date].beginning_of_month...filter[:end_date].end_of_month)
      .group_by { |time_entry| "#{time_entry.client_id}_#{time_entry.created_at.strftime('%b')}" }

    Client.all.map do |client|
      [client] + selected_months.map do |month|
        entries = time_entries["#{client.id}_#{month.strftime('%b')}"] || []

        entries.map { |entry| entry.duration }.sum
      end
    end
  end

  # Returns an array of 2016-Jan-01, 2016-Feb-01 datetimes
  def selected_months
    @selected_months ||= [].tap do |months|
      each_month_between(filter[:start_date].beginning_of_month, filter[:end_date].end_of_month) { |month| months << month }
    end
  end

  # Call with each_month_between(start_date, end_date) { |date| puts date }
  def each_month_between(start_date, end_date, &block)
    while start_date <= end_date
      block.call(start_date)
      start_date = start_date + 1.month
    end
  end
end

Additional Functionality

There are a few other ways to customize the behaviour of effective_datatables

Checking for Empty collection

Check whether the datatable has records by calling @datatable.present? and @datatable.blank?.

Override javascript options

The javascript options used to initialize a datatable can be overriden as follows:

render_datatable(@datatable, input_js: { dom: "<'row'<'col-sm-12'tr>>", autoWidth: true })
render_datatable(@datatable, input_js: { buttons_export_columns: ':visible:not(.col-actions)' })

Please see datatables options for a list of initialization options.

You don't want to actually do this!

Get access to the raw results

After all the searching, sorting and rendering of final results is complete, the server sends back an Array of Arrays to the front end jQuery DataTable

The finalize method provides a hook to process the final collection as an Array of Arrays just before it is convered to JSON.

This final collection is available after searching, sorting and pagination.

As you have full control over the table_column presentation, I can't think of any reason you would actually need or want this:

def finalize(collection)
  collection.each do |row|
    row.each do |column|
      column.gsub!('horse', 'force') if column.kind_of?(String)
    end
  end
end

Render outside of view

You can render a datatable outside the view.

Anything you pass to the rendered method is treated as view/request params.

You can test filters and scopes by passing them here.

post = Post.create!
datatable = PostsDatatable.new.rendered(end_date: Time.zone.now+2.days, current_user_id: 1)

assert_equal 1, datatable.collection.count
assert_equal [post], datatable.collection

Authorization

All authorization checks are handled via the config.authorization_method found in the config/initializers/effective_datatables.rb file.

It is intended for flow through to CanCan or Pundit, but neither of those gems are required.

This method is called by the controller action with the appropriate action and resource

Action will be :index

The resource will be the collection base class, such as Post. This can be overridden:

def collection_class
  NotPost
end

The authorization method is defined in the initializer file:

# As a Proc (with CanCan)
config.authorization_method = Proc.new { |controller, action, resource| authorize!(action, resource) }
# As a Custom Method
config.authorization_method = :my_authorization_method

and then in your application_controller.rb:

def my_authorization_method(action, resource)
  current_user.is?(:admin) || EffectivePunditPolicy.new(current_user, resource).send('#{action}?')
end

or disabled entirely:

config.authorization_method = false

If the method or proc returns false (user is not authorized) an Effective::AccessDenied exception will be raised

You can rescue from this exception by adding the following to your application_controller.rb:

rescue_from Effective::AccessDenied do |exception|
  respond_to do |format|
    format.html { render 'static_pages/access_denied', :status => 403 }
    format.any { render :text => 'Access Denied', :status => 403 }
  end
end

License

MIT License. Copyright Code and Effect Inc.

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Bonus points for test coverage
  6. Create new Pull Request

effective_datatables's People

Contributors

danajanssen avatar jdar avatar jerrodcarpenter avatar koendierckx avatar matt-riemer avatar nerian avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

effective_datatables's Issues

Authorisation

Hi,

So I have got an application that has the following authorisation roles:

  • Admin
  • Owner
  • Member
  • Manager

And my application has to serve multiple clients. So each user has a role that is scoped to a particular client. Classic multi-tenant app.

I have this initialiser:

config.authorization_method = Proc.new { |controller, action, resource| datatable_authorization_method(controller, action, resource) }

And this on my application controller:

  def datatable_authorization_method(controller, action, resource)
    datatable = controller.instance_variable_get(:@datatable)

    return true if datatable.blank? # We are in one of our own controllers; so permissions are handled in BaseController.
    return true if datatable.attributes[:require_role].blank?

    # We are in Effective::DatatablesController
    case datatable.attributes[:require_role]
    when :owner
      return current_user.account_owner?(current_account)
    when :admin
      return current_user.account_admin?(current_account)
    when :manager
      return current_user.account_manager?(current_account)
    when :member
      return current_user.account_member?(current_account)
    else
      raise NotImplementedError.new
    end

And I instantiate an effective datatable like this:

def index
    @contracts_table = ContractsDatatable.new(self, account_id: current_account.id, require_role: :manager)
end

And it works fine, so far. But I think it is a little bit hacky to have that datatable = controller.instance_variable_get(:@datatable). I was wondering if the @datatable could be one of the arguments that are passed to the authorization_method. So that it would be like this:

config.authorization_method = Proc.new { |controller, action, resource, datatable| datatable_authorization_method(controller, action, resource, datatable) }

That way I can be confident that it will not break. What do you think? Or if you could suggest a different alternative.

`authorization_method` does not pass resource/action

Hi,

I have this code on the initializer:

config.authorization_method = :datatable_authorization_method

And this in ApplicationController

def datatable_authorization_method(action, resource)
  params[:attributes][:coworking_space_id] == current_user.coworking_space.id.to_s
end

But the method is being called without the two arguments, so I get wrong number of arguments (given 0, expected 2) for that method.

Defining the method as def datatable_authorization_method removes the error.

So perhaps there is a change to be done here:
https://github.com/code-and-effect/effective_datatables/blob/master/lib/effective_datatables.rb#L25

I am not using Cancan or Pundit.

Some interesting edge case

Hi,

I have another interesting sample:

col :status, sql_column: 'something', sort: false, search: { as: :select, collection: [['collected', :collected], ['pending', :pending], ['failed', :failed]] }  do |charge|
    charge.status.humanize
end.search do |collection, term, column, sql_column|
  case term
  when 'collected'
    collection.collected
  when 'pending'
    collection.pending
  when 'failed'
    collection.failed
  end
end

The :status column is not backed by a database column. It's an artificial value calculated on the model. There are also scopes for each of the possible values.

Normally I would get an array here instead of a collection. But on the search I wanted to be able to use an actual active record relation and not an array. I was getting an array, because the charge doesn't have a column named status (it does have that method though). I got it to give me a relation by adding a sql_column parameter – but notice that the value is 'something', it's not used at all.

I was wondering if this is the best way to do this or you could point to me on a better path. I could be using a filter, but I really think it is better to put this in the column.

Custom search

Hi,

I am trying to implement a custom search method, but the collection I am getting in the search method is an array instead of a scope.

col :member_id, sort: false, label: 'Member', search: { as: :select, collection: members_collection } do |charge|
  link_to charge.member.name, manager_accounting.member_path(current_space, charge.member)
end.search do |collection, term, column, sql_column|
  collection.joins(:invoice).where(invoices: { member_id: term })
end

collection do
  charges = current_space.charges.includes(:payment_method, :invoice, invoice: :member)
end

The collection in the search method holds an array of arrays. But what should be coming out of the collection block is a scope.

This is the type of the charges variable in the collection block:

[2] pry(#<Effective::DatatableDslTool>)> charges.class
=> Charge::ActiveRecord_AssociationRelation

What am I doing wrong?

total_records nested SELECT is slow

Hi Matt!

I've been testing effective_datatables with production data and found that the query made in total_records is quite slow for me, over 4 seconds in my case. There may be some optimization I can make in our db, but I believe it's fundamentally slow because the nested select is selecting all records and all columns in the table.

An example query like:

SELECT COUNT(*) FROM (SELECT ... WHERE (...)) AS datatables_total_count

This query takes about 4100ms.

If I remove the nested select, and perform the same query but just selecting a count:

SELECT COUNT(*) FROM ... WHERE (...)

Then the count query takes about 20ms.

Maybe we can improve total_records query to use activerecord .count when possible, and fallback to the current query when not?

Do you think you could provide an example of when .count does not work? If so maybe I could work on a solution.

Thanks!
Sheldon

Use with Sinatra?

The docs seem quite rails specific. Anyone know if this can be plugged into Sinatra or like frameworks?

thanks!

Sqlite3 SQLite3::SQLException: near "NULLS":

Hello i have run into a issue while trying to use this gem with sqlite3, here is the framework trace:

SQLite3::SQLException: near "NULLS": syntax error: SELECT  "rooms".* FROM "rooms"  ORDER BY "rooms"."id" ASC NULLS LAST LIMIT 25 OFFSET 0
sqlite3 (1.3.11) lib/sqlite3/database.rb:91:in `initialize'
sqlite3 (1.3.11) lib/sqlite3/database.rb:91:in `new'
sqlite3 (1.3.11) lib/sqlite3/database.rb:91:in `prepare'
activerecord (4.2.4) lib/active_record/connection_adapters/sqlite3_adapter.rb:296:in `block in exec_query'
activerecord (4.2.4) lib/active_record/connection_adapters/abstract_adapter.rb:473:in `block in log'
activesupport (4.2.4) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
activerecord (4.2.4) lib/active_record/connection_adapters/abstract_adapter.rb:467:in `log'
activerecord (4.2.4) lib/active_record/connection_adapters/sqlite3_adapter.rb:293:in `exec_query'
activerecord (4.2.4) lib/active_record/connection_adapters/abstract/database_statements.rb:351:in `select'
activerecord (4.2.4) lib/active_record/connection_adapters/abstract/database_statements.rb:32:in `select_all'
activerecord (4.2.4) lib/active_record/connection_adapters/abstract/query_cache.rb:68:in `block in select_all'
activerecord (4.2.4) lib/active_record/connection_adapters/abstract/query_cache.rb:83:in `cache_sql'
activerecord (4.2.4) lib/active_record/connection_adapters/abstract/query_cache.rb:68:in `select_all'
activerecord (4.2.4) lib/active_record/querying.rb:39:in `find_by_sql'
activerecord (4.2.4) lib/active_record/relation.rb:639:in `exec_queries'
activerecord (4.2.4) lib/active_record/relation.rb:515:in `load'
activerecord (4.2.4) lib/active_record/relation.rb:243:in `to_a'
activerecord (4.2.4) lib/active_record/relation/delegation.rb:132:in `method_missing'
activerecord (4.2.4) lib/active_record/relation/delegation.rb:99:in `method_missing'
effective_datatables (2.1.13) app/models/effective/effective_datatable/rendering.rb:106:in `arrayize'
effective_datatables (2.1.13) app/models/effective/effective_datatable/rendering.rb:51:in `table_data'
effective_datatables (2.1.13) app/models/effective/datatable.rb:72:in `to_json'
effective_datatables (2.1.13) app/views/effective/datatables/_datatable.html.haml:17:in `__home_diego__rvm_gems_ruby_______rent_gems_effective_datatables________app_views_effective_datatables__datatable_html_haml__786013744458836956_37199940'
actionview (4.2.4) lib/action_view/template.rb:145:in `block in render'
activesupport (4.2.4) lib/active_support/notifications.rb:166:in `instrument'
actionview (4.2.4) lib/action_view/template.rb:333:in `instrument'
actionview (4.2.4) lib/action_view/template.rb:143:in `render'
actionview (4.2.4) lib/action_view/renderer/partial_renderer.rb:339:in `render_partial'
actionview (4.2.4) lib/action_view/renderer/partial_renderer.rb:310:in `block in render'
actionview (4.2.4) lib/action_view/renderer/abstract_renderer.rb:39:in `block in instrument'
activesupport (4.2.4) lib/active_support/notifications.rb:164:in `block in instrument'
activesupport (4.2.4) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
activesupport (4.2.4) lib/active_support/notifications.rb:164:in `instrument'
actionview (4.2.4) lib/action_view/renderer/abstract_renderer.rb:39:in `instrument'
actionview (4.2.4) lib/action_view/renderer/partial_renderer.rb:309:in `render'
actionview (4.2.4) lib/action_view/renderer/renderer.rb:47:in `render_partial'
actionview (4.2.4) lib/action_view/renderer/renderer.rb:21:in `render'
actionview (4.2.4) lib/action_view/helpers/rendering_helper.rb:32:in `render'
haml (4.0.7) lib/haml/helpers/action_view_mods.rb:12:in `render_with_haml'
effective_datatables (2.1.13) app/helpers/effective_datatables_helper.rb:6:in `render_datatable'
actionview (4.2.4) lib/action_view/template.rb:145:in `block in render'
activesupport (4.2.4) lib/active_support/notifications.rb:166:in `instrument'
actionview (4.2.4) lib/action_view/template.rb:333:in `instrument'
actionview (4.2.4) lib/action_view/template.rb:143:in `render'
actionview (4.2.4) lib/action_view/renderer/template_renderer.rb:54:in `block (2 levels) in render_template'
actionview (4.2.4) lib/action_view/renderer/abstract_renderer.rb:39:in `block in instrument'
activesupport (4.2.4) lib/active_support/notifications.rb:164:in `block in instrument'
activesupport (4.2.4) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
activesupport (4.2.4) lib/active_support/notifications.rb:164:in `instrument'
actionview (4.2.4) lib/action_view/renderer/abstract_renderer.rb:39:in `instrument'
actionview (4.2.4) lib/action_view/renderer/template_renderer.rb:53:in `block in render_template'
actionview (4.2.4) lib/action_view/renderer/template_renderer.rb:61:in `render_with_layout'
actionview (4.2.4) lib/action_view/renderer/template_renderer.rb:52:in `render_template'
actionview (4.2.4) lib/action_view/renderer/template_renderer.rb:14:in `render'
actionview (4.2.4) lib/action_view/renderer/renderer.rb:42:in `render_template'
actionview (4.2.4) lib/action_view/renderer/renderer.rb:23:in `render'
actionview (4.2.4) lib/action_view/rendering.rb:100:in `_render_template'
actionpack (4.2.4) lib/action_controller/metal/streaming.rb:217:in `_render_template'
actionview (4.2.4) lib/action_view/rendering.rb:83:in `render_to_body'
actionpack (4.2.4) lib/action_controller/metal/rendering.rb:32:in `render_to_body'
actionpack (4.2.4) lib/action_controller/metal/renderers.rb:37:in `render_to_body'
actionpack (4.2.4) lib/abstract_controller/rendering.rb:25:in `render'
actionpack (4.2.4) lib/action_controller/metal/rendering.rb:16:in `render'
actionpack (4.2.4) lib/action_controller/metal/instrumentation.rb:44:in `block (2 levels) in render'
activesupport (4.2.4) lib/active_support/core_ext/benchmark.rb:12:in `block in ms'
/home/diego/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/benchmark.rb:303:in `realtime'
activesupport (4.2.4) lib/active_support/core_ext/benchmark.rb:12:in `ms'
actionpack (4.2.4) lib/action_controller/metal/instrumentation.rb:44:in `block in render'
actionpack (4.2.4) lib/action_controller/metal/instrumentation.rb:87:in `cleanup_view_runtime'
activerecord (4.2.4) lib/active_record/railties/controller_runtime.rb:25:in `cleanup_view_runtime'
actionpack (4.2.4) lib/action_controller/metal/instrumentation.rb:43:in `render'
actionpack (4.2.4) lib/action_controller/metal/implicit_render.rb:10:in `default_render'
actionpack (4.2.4) lib/action_controller/metal/implicit_render.rb:5:in `send_action'
actionpack (4.2.4) lib/abstract_controller/base.rb:198:in `process_action'
actionpack (4.2.4) lib/action_controller/metal/rendering.rb:10:in `process_action'
actionpack (4.2.4) lib/abstract_controller/callbacks.rb:20:in `block in process_action'
activesupport (4.2.4) lib/active_support/callbacks.rb:117:in `call'
activesupport (4.2.4) lib/active_support/callbacks.rb:117:in `call'
activesupport (4.2.4) lib/active_support/callbacks.rb:555:in `block (2 levels) in compile'
activesupport (4.2.4) lib/active_support/callbacks.rb:505:in `call'
activesupport (4.2.4) lib/active_support/callbacks.rb:505:in `call'
activesupport (4.2.4) lib/active_support/callbacks.rb:92:in `__run_callbacks__'
activesupport (4.2.4) lib/active_support/callbacks.rb:778:in `_run_process_action_callbacks'
activesupport (4.2.4) lib/active_support/callbacks.rb:81:in `run_callbacks'
actionpack (4.2.4) lib/abstract_controller/callbacks.rb:19:in `process_action'
actionpack (4.2.4) lib/action_controller/metal/rescue.rb:29:in `process_action'
actionpack (4.2.4) lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'
activesupport (4.2.4) lib/active_support/notifications.rb:164:in `block in instrument'
activesupport (4.2.4) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
activesupport (4.2.4) lib/active_support/notifications.rb:164:in `instrument'
actionpack (4.2.4) lib/action_controller/metal/instrumentation.rb:30:in `process_action'
actionpack (4.2.4) lib/action_controller/metal/params_wrapper.rb:250:in `process_action'
activerecord (4.2.4) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
actionpack (4.2.4) lib/abstract_controller/base.rb:137:in `process'
actionview (4.2.4) lib/action_view/rendering.rb:30:in `process'
actionpack (4.2.4) lib/action_controller/metal.rb:196:in `dispatch'
actionpack (4.2.4) lib/action_controller/metal/rack_delegation.rb:13:in `dispatch'
actionpack (4.2.4) lib/action_controller/metal.rb:237:in `block in action'
actionpack (4.2.4) lib/action_dispatch/routing/route_set.rb:76:in `call'
actionpack (4.2.4) lib/action_dispatch/routing/route_set.rb:76:in `dispatch'
actionpack (4.2.4) lib/action_dispatch/routing/route_set.rb:45:in `serve'
actionpack (4.2.4) lib/action_dispatch/journey/router.rb:43:in `block in serve'
actionpack (4.2.4) lib/action_dispatch/journey/router.rb:30:in `each'
actionpack (4.2.4) lib/action_dispatch/journey/router.rb:30:in `serve'
actionpack (4.2.4) lib/action_dispatch/routing/route_set.rb:821:in `call'
rack (1.6.4) lib/rack/etag.rb:24:in `call'
rack (1.6.4) lib/rack/conditionalget.rb:25:in `call'
rack (1.6.4) lib/rack/head.rb:13:in `call'
actionpack (4.2.4) lib/action_dispatch/middleware/params_parser.rb:27:in `call'
actionpack (4.2.4) lib/action_dispatch/middleware/flash.rb:260:in `call'
rack (1.6.4) lib/rack/session/abstract/id.rb:225:in `context'
rack (1.6.4) lib/rack/session/abstract/id.rb:220:in `call'
actionpack (4.2.4) lib/action_dispatch/middleware/cookies.rb:560:in `call'
activerecord (4.2.4) lib/active_record/query_cache.rb:36:in `call'
activerecord (4.2.4) lib/active_record/connection_adapters/abstract/connection_pool.rb:653:in `call'
activerecord (4.2.4) lib/active_record/migration.rb:377:in `call'
actionpack (4.2.4) lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
activesupport (4.2.4) lib/active_support/callbacks.rb:88:in `__run_callbacks__'
activesupport (4.2.4) lib/active_support/callbacks.rb:778:in `_run_call_callbacks'
activesupport (4.2.4) lib/active_support/callbacks.rb:81:in `run_callbacks'
actionpack (4.2.4) lib/action_dispatch/middleware/callbacks.rb:27:in `call'
actionpack (4.2.4) lib/action_dispatch/middleware/reloader.rb:73:in `call'
actionpack (4.2.4) lib/action_dispatch/middleware/remote_ip.rb:78:in `call'
actionpack (4.2.4) lib/action_dispatch/middleware/debug_exceptions.rb:17:in `call'
web-console (2.2.1) lib/web_console/middleware.rb:39:in `call'
actionpack (4.2.4) lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
railties (4.2.4) lib/rails/rack/logger.rb:38:in `call_app'
railties (4.2.4) lib/rails/rack/logger.rb:20:in `block in call'
activesupport (4.2.4) lib/active_support/tagged_logging.rb:68:in `block in tagged'
activesupport (4.2.4) lib/active_support/tagged_logging.rb:26:in `tagged'
activesupport (4.2.4) lib/active_support/tagged_logging.rb:68:in `tagged'
railties (4.2.4) lib/rails/rack/logger.rb:20:in `call'
actionpack (4.2.4) lib/action_dispatch/middleware/request_id.rb:21:in `call'
rack (1.6.4) lib/rack/methodoverride.rb:22:in `call'
rack (1.6.4) lib/rack/runtime.rb:18:in `call'
activesupport (4.2.4) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'
rack (1.6.4) lib/rack/lock.rb:17:in `call'
actionpack (4.2.4) lib/action_dispatch/middleware/static.rb:116:in `call'
rack (1.6.4) lib/rack/sendfile.rb:113:in `call'
railties (4.2.4) lib/rails/engine.rb:518:in `call'
railties (4.2.4) lib/rails/application.rb:165:in `call'
rack (1.6.4) lib/rack/lock.rb:17:in `call'
rack (1.6.4) lib/rack/content_length.rb:15:in `call'
rack (1.6.4) lib/rack/handler/webrick.rb:88:in `service'
/home/diego/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/webrick/httpserver.rb:138:in `service'
/home/diego/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/webrick/httpserver.rb:94:in `run'
/home/diego/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/webrick/server.rb:294:in `block in start_thread'

Best regards! :)

Order by part of the query using quotes instead of backticks makes mysql throw a error.

Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '."id" DESC NULLS LAST LIMIT 25 OFFSET 0' at line 1: SELECT inventories.* FROM inventories ORDER BY "inventories"."id" DESC NULLS LAST LIMIT 25 OFFSET 0

module Effective
 module Datatables
  class Inventories < Effective::Datatable
   datatable do
     default_order :id, :desc
       table_column :id
       table_column :folio
       table_column :status
      end

      def collection
        Inventory.all
      end
     end
   end
end

Bug: grouped_select doesn't support procs.

Hi,

I have this code:

table_column :holder, filter: { as: :grouped_select, collection: Proc.new { holder_collection } } do 
  ...
end

def holder_collection
  {
    'Members' => current_space.members.joins(:payments).map { |p| [ p.name, p.to_global_id.to_s ] },
    'Suppliers' => current_space.suppliers.joins(:payments).map { |p| [ p.name, p.to_global_id.to_s ] }
  }
end

But this fails with:

Grouped collection expecting a Hash {'Posts' => Post.all, 'Events' => Event.all} or a Hash {'Posts' => [['Post A', 1], ['Post B', 2]], 'Events' => [['Event A', 1], ['Event B', 2]]}

Notice that's using a :grouped_select. A :select works fine. In other words, :select can handle Procs, but :grouped_select can't.

I believe it should be supported, right?

One more question, do we really need to use a proc if we are going to have dynamic content (results of a query) or can we just use User.all (for example) without a Proc?

I suspect we need to use a Proc because this class is evaluated just once on code load, so we use a Proc to get up to date data on each request, but I wanted to confirm my understanding.

Bootstrap 4 support?

Hi, I'm using Bootstrap 4 and the datatables UI is broken. Is there any plan for Bootstrap 4 support?

Fetch relational table attributes

How can I fetch grade's id only?

class BatchesDatatable < Effective::Datatable
  datatable do
    col :title
    col :grades

    actions_col
  end

  collection do
    Batch.includes(:grades).all.order(created_at: 'DESC')
  end

end

Feature request

Hi,

Currently the scopes functionality requires a full reload of the page. Wouldn't it be better to avoid a full reload and simply make Datatables reload the page via Ajax? What was the rationale behind that decision? Will you accept a pull request to implement that?

bulk actions not behaving properly

bulk actions always calling show action of the specified controller.

Example:

bulk_actions_column do bulk_action 'Approve all', bulk_approve_tags_path , data: { confirm: 'Approve all selected posts?' } end

If I click Approve all, I am getting request to tags_controller#show action, not tags_controller#bulk_approve action.

Routes file:

resources :tags do collection do post :bulk_approve end end
Am i doing wrong ?

Kindly let me know.

Column Width

Hi,

In the previous version I could set a custom width like this:

table_column :paid, label: 'Status', width: '70px'

But this doesn't work anymore. I get a wrong key error. How do I do this now?

Comparison of Array with Array failed

Hello,

I was trying to perform search & sort on serialized column. The search works appropriate where as the sort gives an error indicating datatables#show (ArgumentError) "comparison of Array with Array failed

The column include the blank string. Is there anyway to solve this error?

Thanks in Advance!!!

Q: how to use gem for serverside part only?

I'd like to render DataTable with my custom JS and HTML (would like to skip your proposed View).
My controller looks like this:

  def index
    #some code...
    render json: Position::OutdoorDatatable.new(self) #this is not working at the moment
  end

effective_datatables file looks like this:

class Position::OutdoorDatatable < Effective::Datatable
  datatable do
    col :name
    col :ptype, as: :jsonb
    col :address,as: :jsonb
    col :id
  end

  collection do
    Position::Outdoor.all
  end
end

and my position/outdoors/_index.html.erb partial looks is this:

<div class="ibox">
  <div class="ibox-content">
    <table
      id="outdoor_table"
      data-source="<%= medium_position_outdoors_path(format: :json) %>"
      data-user="<%= current_user.id %>"
      data-create="<%= current_user.can?(:create, Position::Position) %>"
      data-update="<%= current_user.can?(:update, Position::Position) %>"
      data-destroy="<%= current_user.can?(:destroy, Position::Position) %>"
      cellpadding="0"
      cellspacing="0"
      border="0"
      class="display table table-striped table-bordered table-hover"
      width="100%">
      <thead>
        <tr>
          <th data-priority="1">Name</th>
          <th data-priority="2">Type</th>
          <th data-priority="3">Address</th>
        </tr>
      </thead>
      <tfoot></tfoot>
    </table>
  </div>
</div>
<!-- some more code !-->

The table is initialized by this line in position/outdoors/index.js.coffee
$("#positions_table").html "<%=j render 'index' %>"

At the moment I'm getting an error:

ArgumentError (wrong number of arguments (given 1, expected 0)):
app/controllers/position/outdoors_controller.rb:11:in index

Is there a way for me to keep my views, JS and add just use server side part of your gem? I'd like to keep my index partial to be triggered and then in my JS I have ajax, which gets JSON from server.

Can't verify CSRF token authenticity

Hi,

Started POST "/datatables/users_datatable-633246437562.json" for 79.75.59.52 at 2018-01-26 17:05:24 +0000
Processing by Effective::DatatablesController#show as JSON
  Parameters: {"draw"=>"2", "columns"=>{"0"=>{"data"=>"0", "name"=>"email", "searchable"=>"true", "orderable"=>"true", "search"=>{"value"=>"gm", "regex"=>"false"}, "vis
ible"=>"true"}, "1"=>{"data"=>"1", "name"=>"name", "searchable"=>"true", "orderable"=>"true", "search"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}, "2"=>{"data
"=>"2", "name"=>"eth_address", "searchable"=>"true", "orderable"=>"true", "search"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}, "3"=>{"data"=>"3", "name"=>"kyc
", "searchable"=>"true", "orderable"=>"true", "search"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}, "4"=>{"data"=>"4", "name"=>"whitelisted", "searchable"=>"tr
ue", "orderable"=>"true", "search"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}, "5"=>{"data"=>"5", "name"=>"GCP", "searchable"=>"true", "orderable"=>"true", "s
earch"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}}, "order"=>{"0"=>{"column"=>"0", "dir"=>"asc"}}, "start"=>"0", "length"=>"25", "search"=>{"value"=>"", "rege
x"=>"false"}, "cookie"=>"633246437562", "id"=>"users_datatable-633246437562"}
Can't verify CSRF token authenticity.
Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms)

ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):

I use Devise.
If current_user is admin (when admin attribute is true), I want to authorize the display of the Users datatables.

Within config/initializers/effective_datatables.rb for auth:

config.authorization_method = Proc.new { |controller, action, resource| current_user.admin? }

Within app/datatables/users_datatable.rb:

class UsersDatatable < Effective::Datatable
  datatable do
    col :email
    col :name
    col :eth_address
    col :kyc
    col :whitelisted
    col :GCP
  end

  collection do
    User.all
  end
end

Within application.js:

cat application.js
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require bootstrap-sprockets
//= require_tree .
//= require effective_datatables

Within application.css.scss:

 /*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
 * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any styles
 * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
 * file per style scope.
 *
 *= require_tree .
 *= require_self
 *= require effective_datatables
 */

Any idea? @matt-riemer

Thanks!

State saving

First of all, thank you for this very useful gem.

Is there a way to save the state (filtering, pagination, sort order, ...) of the datatable between multiple requests?

Datatables Editor

Not sure if this is the correct place to ask, but does this gem integrate well with the Editor extension of Datatables?

CMD + A doesn't work when on a filter input.

Hi,

In Mac OS you can click _CMD_ + A to select all text on an input field. But when I do this on one of the text fields for filtering, nothing happens – the CMD keypress is ignored, and a 'A' character is added to the input.

CMD+A is useful when you want to clear the filter in a quick way, so I think the user should not be prevented form doing this.

I suspect EffectiveDatatables is capturing the relevant key events somehow.

Bug: Exceptions from a Proc for a collection parameter does not bubble

Hi,

This code does not fail:

table_column :something, filter: { :as => :select, :collection => Proc.new { raise StandardError.new('hi!') } }

It just gives you a table without the select filter for that particular column. No error.

This makes errors a little bit difficult to find and fix. It's probably some of the rescue clauses on the code. I noticed that most of them are set to rescue all exceptions. Perhaps that could be changed to just rescue StandardErrors – or relevant exceptions for that code section.

Missing 3.3.11 release

Hi,

I updated effective_datatables to 3.3.11 and my datatables stopped working. I took a look this repo to see the changes but master didn't have those new commits. So I dug in the local gem and I noticed that the last pull request (filters stuff) seem to be missing, and that's why my tables are failing.

3.3.11 is not pushed to master, but the release 3.3.11 is there: https://github.com/code-and-effect/effective_datatables/releases

So if I click on it, we can see the tree:

https://github.com/code-and-effect/effective_datatables/commits/063416d95bfcfc9a463724d81b99daa5cd9dff0b

Which has diverged from master:

https://github.com/code-and-effect/effective_datatables/commits/master

Notice the commits "Version 3.3.10" in one tree and the other are different.

The commits on Jan 16, 2018 are missing on the currently released gem.

I guess you started working on new changes without pulling the latest master. Then hit release but the next version on your local (3.3.10) was already released and failed. So you incremented the version, commited and tried releasing again. The gem pushed, but the code didn't, because it conflicted with master. And that's how we got here :)

Ajax requests broken after "Clean up classify"

After updating the effective_datatables gem from version "2.6.11" to "2.12.0" the ajax requests for the datatables in our app are broken.

For a simple datatable such as:

# app/models/effective/datatables/users.rb

module Effective
  module Datatables
    class Users < Effective::Datatable
      datatable do
        table_column :email
        table_column :last_name
        table_column :first_name
        ...
        actions_column
      end

      def collection
        User.all
      end
    end
  end
end

... the ajax request used to call something like:
localhost:3000/datatables/users.json

... but it now calls:
localhost:3000/datatables/effective%2Fdatatables%2Fusers.json

This does not work well with our webserver setup (Apache + Passenger) as the forward slashes (%2F) in the URL result in Apache returning a 404 page.

After a quick look at the commit history the issue is probably related to commit b55291a: Clean up classify.

Suggested solution: avoid using forward slashes in the ajax URLs.

DataTable error: unable to find datatable

As of 3.2.1+ effective_datatables is unable to find the datatable for me.

First load works. But reloading (filtering, refresh) gives:

DataTable error: unable to find datatable
Please refresh the page and try again

The url of the JSON request is: "/datatables/skype_users_datatable--40537094.json?locale=en"

Parameters: {"draw"=>"3", "columns"=>{"0"=>{"data"=>"0", "name"=>"display_name", "searchable"=>"true", "orderable"=>"true", "search"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}, "1"=>{"data"=>"1", "name"=>"sign_in_address", "searchable"=>"true", "orderable"=>"true", "search"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}, "2"=>{"data"=>"2", "name"=>"status", "searchable"=>"true", "orderable"=>"false", "search"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}, "3"=>{"data"=>"3", "name"=>"phone", "searchable"=>"true", "orderable"=>"false", "search"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}, "4"=>{"data"=>"4", "name"=>"office", "searchable"=>"true", "orderable"=>"true", "search"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}, "5"=>{"data"=>"5", "name"=>"department", "searchable"=>"true", "orderable"=>"true", "search"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}, "6"=>{"data"=>"6", "name"=>"_actions", "searchable"=>"true", "orderable"=>"false", "search"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}}, "order"=>{"0"=>{"column"=>"0", "dir"=>"asc"}}, "start"=>"0", "length"=>"25", "search"=>{"value"=>"", "regex"=>"false"}, "cookie"=>"-40537094", "locale"=>"en", "id"=>"skype_users_datatable--40537094"}

table:

Bootstrap4 support

Hey all,

As of effective_datatables 4.0+, this gem will be using Bootstrap4 instead of Bootstrap3 and Rails 5.1+ form_with instead of simple_form.

The 4.x version of effective_datatables is intended to work alongside (the still very much WIP) effective_bootstrap gem.

The bootstrap3 branch, with its 3.x versions, will be maintained and will continue to work with Bootstrap3 & simple_form

The actions_col are not present in the Show/Hide interface.

Hi,

I noticed that the actions_col are not present in the Show/Hide interface, but they do get collapsed, so there is not way to uncollapse them unless you click on the 'Show All button'.

I would suggest:

  1. Collapse action columns the last.
  2. Add the controls to the list of collapsable columns in the UI. Not a high priority for me, though – my issue actions collapsing.

Also, is there a way to disable the responsive behavior globally, or per effective datatable?

I tried adding responsive: 1 but the column still gets to be hidden,

Import css and js without require

Hello. I'm using this gem to write plugin in redmine. Unfortunately i can't put require in application.js( application.css). I'm importing assets with stylesheet_link_tag, javascript_include_tag. Is it possible to import effective_datables assets same way? Thank you for your attention!

Length of table

Hi,

I am trying to have one of the tables default to have 5 rows. I tried to lengthMenu but it doesn't seem to have an effect.

= render_datatable(@invoices_table, { "lengthMenu" => [ 5, 25, 50, 75, 100 ] })

Thoughts?

Polymorphic Column Has No Values

The problem: datatable.table_columns['purchasable']['filter']['values'] is {} when I expect it to be {'Posts' => [['Post A', 1], ['Post B', 2]], 'Events' => [['Event A', 1], ['Event B', 2]]} or something similar.

The values hash is hard coded, however, and makes me think that my statement above is a misunderstanding of what is going on. The hard coded hash is here:

https://github.com/code-and-effect/effective_datatables/blob/master/app/models/effective/effective_datatable/options.rb#L162

If you look at similar cases surrounding that line, the values hash is generated dynamically rather than being static. So, this line might be the source of the issue. When I try to change the line to generate a hash like I've suggested above, I start running into other problems though.

I'm seeing this issue with the following set up:

The model:

class TokenTransaction < ActiveRecord::Base
  belongs_to :purchasable, polymorphic: true
  ...
end

The datatable:

class Effective::Datatables::TokenTransactions < Effective::Datatable
  datatable do
    ...
    table_column :purchasable
    ...
  end

  def collection
    TokenTransaction.all
  end
end

I've got exactly one TokenTransaction record which has an associated purchasable object.

Changing DOM value for all tables

Hi,

I would like to have change the DOM value for all tables. Specifically I want to have the length selector and export buttons at the bottom instead of on the top. I am currently doing this:

= render_datatable(@invoices_table, dom: "<'row'<'col-sm-12'tr>><'row'<'col-sm-6'i><'col-sm-6'p>><'row'<'col-sm-4'l><'col-sm-8'B>>")

But I have many tables, and so it would be better to centralise this value. I think having a config option on the initializer would be good.

Filters outside of Datatable.

Hi,

I have got some complex filters in mind and I would like to create a partial and put a simple_form form there and use it as the filters of a datatable.

In other words, I would like a custom filter toolbar.

Is it possible?

actions_col doesn't work with nested resources

I have a nested resource:

  resources :accounts do
     resources :transactions
  end

I created a datatable model for transactions, but couldn't figure out why the actions where not rendering any icons until I realized it must be the fact that the paths are nested. I worked around it by doing something like this:

    actions_col do |tx|
       link_to '<i class="glyphicon glyphicon-pencil"></i>'.html_safe, edit_account_transaction_path(tx.account_id, tx)
    end

which works fine, but I wondered if there is some way to tell effective_datatables (or effective_resources?) that this is a nested resource so that it 'just works'.
If not, might be nice to have a mention in the docs.

Datatable break when we use it with turbolink

Hi,
Thanks for a great Gem
i am using rails 5 with turbolink ,
effective datatables is working fine initial page load but whenever page transition occur its lost datatables beauty. is there any work around

Gem::InstallError: rack requires Ruby version >= 2.2.2.

Hi!

I'm giving this another shot, because after some testing it appears like the only issue with mysql is the one I ran into earlier. :)

I'm trying to bundle this project, but I receive the following error when I run bundle install:

Gem::InstallError: rack requires Ruby version >= 2.2.2.
An error occurred while installing rack (2.0.1), and Bundler cannot continue.
Make sure that `gem install rack -v '2.0.1'` succeeds before bundling.

I'm using ruby 2.1.0, as per the .ruby-version file in the repo root.

I believe this issue here might be that the gemspec doesn't specify any gem versions, therefore my bundler is possibly bundling different versions than what you have. I can't easily fix this because I don't know what versions of these gems you are using. :S

Do you think you could update the gemspec with the correct gem versions? Thanks!

total_records silently raises and returns 1

Hi there! I am testing your gem out on my project, it looks quite interesting, but I've run into a couple of issues. If you don't mind I'll open them here in Github for your feedback.

The following line (and 129) is silently raising an error and returning 1:

(collection_class.connection.execute("SELECT COUNT(*) FROM (#{collection_sql}) AS datatables_total_count").first.values.first rescue 1).to_i

I believe the cause may be an incompatible version of mysql2 gem.

For me this code returns an Array, therefore calling .values raises an error.

>> collection_class.connection.execute("SELECT COUNT(*) FROM (#{collection_sql}) AS datatables_total_count").first
=> [111]
>> collection_class.connection.execute("SELECT COUNT(*) FROM (#{collection_sql}) AS datatables_total_count").first.values
!! #<NoMethodError: undefined method `values' for [111]:Array>

I'm wondering if this can be simplified by just calling collection.count?

Thanks for reading this!

Some issues with the filters

Hi,

I have this filter:

filter :status, 'unarchived', as: :select, collection: [['Unarchived', 'unarchived'], ['Draft', 'draft'], ['Archived', 'archived'], ['Sent', 'sent'], ['Viewed', 'viewed'], ['Overdue', 'overdue']], label: false, include_blank: false

And this renders this:

captura de pantalla 2018-01-14 a las 13 04 16

Problems:

  1. The label: false is ignored.
  2. The default value ('unarchived') is used in the filters but it is not applied to the form; the unarchived is not selected in the select.
  3. include_blank is also ignored.

What am I doing wrong? 😅

Unnecessary POST requests to server....

Hi,

If we'll keep the cursor in the search field in the data table, it is sending requests to server seamlessly on pressing/holding ALT key in the keyboard.

How to restrict/avoid this.

Thanks in advance.....

Table are saving the state from previous request

Hi!

I noticed that when I change a filter and then wander to another page, and then I come back to the original page, the table will be set up with the changed filter. I would expect it to be pristine; without any filters.

Is there a way to configure this?

It is problematic in my case because one of the filters is a date.

    filters do
      filter :date, I18n.localize(Time.current, format: I18n.t('datepicker.dformat', default: '%d/%m/%Y')), as: :date_picker, label: false, parse: lambda { |term| Time.zone.parse(term).to_date }
    end

So with this, the date will be initialised to today. Then the user can change it to some other day. When the user goes to another page, and then comes back, the datatable uses the second date, not the default one; but strangely the datepicker is initialised with the default one. It is very confusing.

Same thing happens with sorting, and column filters. I would prefer to have a pristine table every-time it is rendered.

Is it posible?

I have got this in my main.js file

$.extend( true, $.fn.dataTable.defaults, { destroy: true, stateSave: false } );

But doesn't seem to be a problem of stateSave. I am guessing this is saved in the cookie.

Thoughts?

Search is taking too long

Hi,

This is fantastic library, I am big fan of this gem. Thanks for wonderful work done by you!

I am having 50,000 rows in my table, where I am using this gem. Everything is smoothly working, but **Search is taking too long _, some times we are getting _Server Time out error. How to improve search functionality.

Is there a better way to use search functionality ? or how to improve search functionality.

Kindly help me to overcome this.

Thanks in advance,
Mallikarjuna.

version 3 error when rendering

Thanks for this gem, it looks really great. Have a problem with 3.0 branch where master branch works ok. Using Rails 4.2.8 and ActiveRecord. Error when rendering view:

.../.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/bundler/gems/effective_datatables-13f24c24b6b2/app/views/effective/datatables/_datatable.html.haml where line #10 raised:

no implicit conversion of nil into String

Perhaps I don't understand the new DSL. User has authorization, and I've even tried turning setting authorization to false.

Here is the model:

  class SpeciesDatatable < Effective::Datatable 
    collection do 
      Species.all
    end

    datatable do
     col :id
     col :genus
     col :species
    end

  end

This is a subset of the columns in the table. Using MySQL (in development) and table has no indexes other that primary key on ID.

View and Controller are as described in readme. Using the generated initializer Same error for render_simple_datatable(@datatable) or render_datatable(@datatable)

I've tried adding

datatable do
   order :id, :desc
....

but get the same error.

Hopefully this is helpful, sorry I'm reporting a problem with no suggested fix. Happy to provide more info. Best of luck on v3

checkbox invisible in bulk action

@matt-riemer

Thanks for this beautiful plugin.

I added the bulk action implementation and when I see the table, the checkboxes are not visible.

It looks like the below css (in materialize.css) is making it invisble.

[type="checkbox"]:not(:checked), [type="checkbox"]:checked {
position: absolute;
left: -9999px;
visibility: hidden;
}

When I remove the position and visibility attributes, I can see the checkbox.

Am I missing anything?

Thanks!!!

Using a custom filter input.

Hi,

Awesome gem, thank for it.

I have a question regarding the filter. I have this code:

array_column :member, filter: { :as => :select, :collection => current_space.members } do |invoice|
  link_to invoice.member.name, manager_accounting.member_path(current_space, invoice.member)
end

As you see, it uses a select. But this list of members could be really big, so I would really like to use a :chosen field – an autocomplete similar to select2. Normally I can create a chosen field with as: :chosen; I am using simple_forms.

I tried replacing the as: :select with as: :chosen , but it just doesn't output anything on the header. No error message, just an head row with just a label and no input field.

How can I do this?

I see the code that handles this is defined at https://github.com/code-and-effect/effective_datatables/blob/master/app/helpers/effective_datatables_private_helper.rb#L82

Maybe I could make a push request that adds a new input type. But maybe in the long term we should extract this so that it is more easily extended. I am thinking of something similar to how SimpleForm allows you to extend the custom input fields.

Q: integration with Yet Another DataTables Column Filter (yadcf), support of JSONB column data filtering

It seems you have created great gem and I'd like to try it. I was wondering, if it would be possible to integrate Yet Another DataTables Column Filter (yadcf) with your gem?

In addition I'm interested, if your gem supports filtering / searching by JSONB column data? For example, in my Position model I have JSONB column data and I would like to search by values of ptype inside it. I can access my data with something like this: Position.data->>'ptype'

Accessing the cookie or attributes

Hi,

I am using a partial to display one of the columns. I need to have access to the attributes that were passed to the EffectiveDatatable, but I am not sure how to do it.

Example:

@members_table = OrganisationMembersDatatable.new(self, organisation_id: current_organisation.id)

In the partial I need to have access to that organisation_id. This works fine when the table is first rendered, since the organisation_id parameter is available as part of the params hash. But when performing a search on the column the params are empty, because – as I understand it – Effective Datatables now uses a cookie to store that info.

How do I get access to that cookie?

I tried this:

In the Effective::DatatablesController I put a binding.pry to take a look at the params:

[2] pry(#<Effective::DatatablesController>)> params
=> <ActionController::Parameters {"draw"=>"3", "columns"=>{"0"=>{"data"=>"0", "name"=>"name", "searchable"=>"true", "orderable"=>"true", "search"=>{"value"=>"sad", "regex"=>"false"}, "visible"=>"true"}, "1"=>{"data"=>"1", "name"=>"tags", "searchable"=>"true", "orderable"=>"false", "search"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}, "2"=>{"data"=>"2", "name"=>"_actions", "searchable"=>"true", "orderable"=>"false", "search"=>{"value"=>"", "regex"=>"false"}, "visible"=>"true"}}, "order"=>{"0"=>{"column"=>"0", "dir"=>"asc"}}, "start"=>"0", "length"=>"25", "search"=>{"value"=>"", "regex"=>"false"}, "cookie"=>"074072723805", "locale"=>"en", "controller"=>"effective/datatables", "action"=>"show", "id"=>"organisation_members_datatable", "format"=>"json"} permitted: false>

I noticed that there is a "cookie"=>"074072723805" parameter, but when I did cookies['074072723805'] it's nil.

So, in a view, how do I get access to the attributes that were passed to the EffectiveDatatable?

Demo available?

I'd love to dig into this gem, however, I'd love to see a demo implementation somewhere. Is one available?

Page Length

class DiscountsDatatable < Effective::Datatable
  datatable do
    length 5

    col :id
    col :title
    col :type

    actions_col
  end

  collection do
    Discount.all.order(created_at: :desc)
  end
end

I recieve: length must be 10, 25, 50, 100, 250, 500, :all. The docs state we can specify the length to be 5 as well. length-link

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.