datatables-factory

July 8, 2026 · View on GitHub

GitHub license Build Status

A Rails engine + JavaScript library that simplifies building DataTables with AJAX, filters, checkboxes, buttons, and context menus. Works alongside ajax-datatables-rails.

Installation

Gemfile:

gem 'datatables-factory'

package.json:

{
  "dependencies": {
    "@jbox-web/datatables-factory": "^1.0.0"
  }
}

Usage

1. Ruby side — declare the table in your view

Use bootstrap_datatables_for (includes Bootstrap 5 classes) or datatables_for (bare):

<% dt = bootstrap_datatables_for(:users, source: users_path, stateSave: true) do |dt| %>
  <% dt.head_for_check_box %>
  <% dt.head_for :name,       label: 'Name',   sortable: true %>
  <% dt.head_for :email,      label: 'Email',  sortable: true %>
  <% dt.head_for :created_at, label: 'Created', sortable: true %>
  <% dt.head_for :actions,    label: '',        sortable: false, searchable: false, colvis: false %>

  <% dt.search_form do |f| %>
    <%= f.text_field :name %>
    <%= f.select    :role %>
    <%= f.render_datatable %>
  <% end %>
<% end %>

head_for options:

OptionDefaultDescription
label''Column header text
sortabletrueEnables column sorting
searchabletrueIncludes column in global search
visibletrueColumn visibility
colvistrueAppears in column visibility toggle
class[]Extra CSS classes
width''Column width

2. JavaScript side — define a datatable class

import { DatatableBase } from '@jbox-web/datatables-factory'

class UsersDatatable extends DatatableBase {}

// Expose on window under the namespace the Ruby side expects
window.datatables = window.datatables || {}
window.datatables.UsersDatatable = UsersDatatable

The Ruby presenter resolves the JS class by building a dotted path from the table id and an optional namespace. For a table declared as datatables_for(:users), it looks for window.datatables.UsersDatatable.

3. Initialize on page load

import { DatatableBase } from '@jbox-web/datatables-factory'

document.addEventListener('DOMContentLoaded', () => {
  DatatableBase.load_datatables()
})

load_datatables() scans for [data-toggle=datatable] elements, resolves the class, and initializes each table.

Filters

Declare filters in the view with search_form. Filter type is set by the form builder method:

MethodFilter typeNotes
f.text_field :colText inputDebounced search
f.select :colSingle selecttom-select by default
f.multi_select :colMulti selecttom-select with tag removal
f.range :colNumber rangeMin/max inputs
f.range_date :colDate rangeRequires jQuery UI or flatpickr

Pre-populating a filter

<% dt.search_field(column_id: 2, filter_type: 'select', populate_with: 'admin') %>

Icon on a filter

<%= f.text_field :name, icon: 'magnifying-glass' %>

Prepends a <span class="input-group-text dtf-filter-icon"><i class="fa-solid fa-magnifying-glass"></i></span> inside the input group.

Select plugin

By default selects use tom-select. Pass filter_plugin: 'select2' to use Select2 instead:

<%= f.select :status, filter_plugin: 'select2' %>

Optional features

Checkboxes

Add dt.head_for_check_box as the first column. The module automatically:

  • renders a "select all" checkbox in the header
  • sends selected / not_selected row ids with every AJAX request
  • handles touch devices (selection restricted to the checkbox column)

Context menu

Add class: 'context-menu' to the table body:

<% dt.body class: 'context-menu', data: { url: context_menu_path } %>

Right-click (or long-press on touch) on any row opens #context-menu. The URL that handles the menu actions is read from the body's data-url attribute. The module handles row selection and touch long-press (500 ms, cancels on scroll).

Buttons

dt.button extend: 'colvis', text: 'Columns'
dt.button extend: 'csv'

Namespacing

For tables inside a module namespace:

bootstrap_datatables_for(:invoices, namespace: [:billing])

Looks for window.datatables.Billing.InvoicesDatatable.

Use js_namespace to override the JS root:

bootstrap_datatables_for(:invoices, js_namespace: 'MyApp')

Looks for window.MyApp.InvoicesDatatable.

HTTP method

The table load request is sent as POST with a JSON body by default. The method is configurable through dtf_options['http_method']:

datatables_for(:users, opts: { dtf_options: { http_method: 'QUERY' } }) do |dt|
  # ...
end

The default remains POST, so leaving the option unset preserves the current behavior. Setting it to QUERY (the safe, cacheable method with a body defined by RFC 10008) is an opt-in that assumes your server and any intermediate proxies accept the method — the body shape is unchanged. This option only affects the table load request, not toolbar button or context-menu actions.

Debugging

Pass ?dtf_debug_log=true or ?dtf_debug_dump=true in the URL to enable console logging. The dtf_options hash is forwarded to the JS side and controls verbosity.

Development

# Ruby (prefer the binstubs in bin/)
bundle install
bin/rspec           # system specs run against spec/dummy (binstub sets BUNDLE_GEMFILE=spec/dummy/Gemfile)
bin/rubocop

# JavaScript
yarn install
yarn webpack        # outputs dist/js/datatables-factory.js