I am trying to use Datatables with Rails 7 and esbuild. For that I need to
Load datatables.net-bs5.js to make the Javascript constructor Datatable available
Run some Javascript in the view to call the Datatable constructor.
But practically it does not work: the js is loaded but the constructor is only available in application.js, not within index.html.erb.
Any idea what to do?
In application.js
import * as bootstrap from "bootstrap"
import DataTable from "datatables.net-bs5"
Just to complete this, in Rails 6 with webpack, the way of working was to modify the environment.js file from the webpack config to make it available in the view.
Classes/functions in bundled packages are usually not made available in the global namespace. The āRails Wayā to solve this is to use a Stimulus controller:
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
connect () {
new DataTable('#example');
}
}
No. When you are using a bundler like esbuild/webpack you cannot use classes/functions from node packages in your HTML. They simply are not available there. They are only available for code running inside your application.js file.
To solve this problem, Rails recommends the first party library Stimulus. In a nutshell, it is designed to replace eventListeners that run on DOMContentLoaded, onclick and other such events. It also will take you less than 1 hour to go through the guidebook I linked and learn it. After you go through the install process you are going to create a file called datable_controller.js and place the code I gave you there.
Then on the page you need data table, you will tell Stimulus to execute the code in that file:
<div id="dataTable" data-controller="data_table">
<!-- HTML of the data table goes here -->
</div>
That said, while this is the āRails Wayā and will make your life easier as you add more JS code (which Iām thinking you will, since you are using a bundler like esbuild), you can ācheatā and just tell esbuild to make DataTable available for you in your html. Chris from GoRails has a video for jQuery, that you can simple adjust for DataTable:
I worked a bit with Simulus but Iām not yet familiar with itā¦ will go deeper. Iām not sure it answers my question as my target is to put the javascript in the .html.erb.
I went through this video, but the solution recommended is quite bad in my opinion: it consists in putting all the Javascript in the assets/javascript folder (application.js and ./src), which is very poor practice in the case of a big project.
Indeed, since every table needs to be initialized and customized individually (define which column has to be sortable, what is the data type, the headerā¦) it would mean that the javascript folder would contain the initializer for maybe 50 different tables.
I have set up a controller that initializes DataTables. This was in Stimulus 1/Rails 5.2, using DataTables as a jquery plugin. With Stimulus 3 I think there are better ways to pass data from the view to the controller.
Also, since this is an old project that used DataTables long before Stimulus was released, Iām still using DataTables as a jQuery plugin. So youāll need to adapt the controller to what Stimulus 3 expects, and change the DataTables init from jquery plugin style to something like what you have listed above.
table_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
static targets = [ "table", "row", "modal" ]
connect() {
// console.log("table controller connected");
this.dt = null; // datatables instance can be plugged in
const controllerName = `stimulus_${this.identifier}`;
this.modalID = `${controllerName}_modal`;
if (this.data.get('lazyload') !== null) {
this.lazyload = new LazyLoad({ elements_selector: `.${this.data.get('lazyload')}` });
}
const event = new CustomEvent("table-controller-connected", {detail: {element: this.element, table: this.tableTarget}});
// fire connected event later so controller can finish connecting events
window.requestAnimationFrame(function() {
window.dispatchEvent(event);
});
}
// actions
setupDataTable(event) {
if (this.dt == null) {
// DataTables is a jquery function
this.dt = $(this.tableTarget).DataTable(event.detail.dt_options);
// console.log("DataTable set up")
if (this.lazyload) {
this.lazyload.update();
}
}
}
replaceAllRows(event) {
if (this.dt == null) { return; }
this.dt.clear();
this.dt.rows.add(event.detail.rows).draw();
if (this.lazyload) {
this.lazyload.update();
}
}
}
The controller also does other things like triggering handling click events to open an edit form in a bootstrap modal, and updating the row after updating the data, but I didnāt include that here. The setupDataTable() and replaceAllRows() functions show how you can interact with your datatable variable in the controller.
Obviously you can use this concept with html table rows as well, but you may have to work with html for row updates too.
So the initialization process Iām using is:
Controller connect() runs, fires custom event
View responds to custom event, builds DataTable init options object, fires custom event to trigger controller action
Controller action setupDataTable() initializes DataTable instance with view-specific options.
With Stimulus 3 you could use values to avoid this event dance if you wanted. You could then use the various values youād provide in the markup to build a DataTables options object in the controllerās connect() function and instantiate the DataTables instance.
Thanks for sharing thisā¦ I had a look at it and tried similar with Stimulus, but basically, it does not solve anything. The challenge is not the integration of Datatables with the .erb, I do it. The challenges comes from jsbundling, which is designed to hide javascript from the view.
The javascript libraries are not hidden from the Stimulus controllers, though. So what that controller does is access DataTables only in the controller, while still allowing you to specify the table-specific configuration options in the view.
I have several listings Iām handling similarly, with a click on the table row to open the edit form in a bootstrap modal, and updating the edited row in the table. So the controller also allows you to extract that functionality as well.
Thanks for the link to the sample database. I have a project Iād like to move away from webpack and I have some listings using the controller and some listings using DataTables from the view so itās useful to know how to get it assigned to the global window namespace.
ānosortā and ānotvisibleā are simple css classes. There is no need for 50 stimulus controllers. Iām controlling some (not all) of the datatables behavior with plain css.
Place the class in <th> and use the class in columDefs as target to control datatables properties. Alternative you can set datatables options with html5-data-attributes like
I can provide a rails example with a generic stimulus controller for datatables may be next weekend, if that helps. Generally I have 3 types of tables: a standard table with buttons, simple table without buttons and a table with buttons and fetching data remote via ajax. All should fit in one stimulus controller with some switches. If one table needs an additional option, i can use html5-data-attributes to add something specific for one table.
Hi @swobspace I would be curious to see your code, both the HTML side and the Stimulus controllerā¦ I tried to make it work but without success.
I dont get error message but basic initialization fails
I get error messages when I pass some configuration parameters to the DataTable constructor
dataTables.bootstrap5.js:27 Uncaught TypeError: Cannot read properties of undefined (reading ādataTableā)
at new module.exports (dataTables.bootstrap5.js:27:23)
at HTMLDocument. (datatables_controller.js:12:19)
I have had some time to review it and play with it. Some comments:
First of all, it works. However, it is not very elegant, for complex initializations I end up creating a lot of attributes to the table and writing the corresponding code in the controller to interpret it. In the end its much heavier and harder to read than putting the initialization code in the Javascript on the erb.
For me an elegant controller should look like:
import { Controller } from ā@hotwired/stimulusā