Originally posted at my blog
Hey there,
This article is about an opinionated way to integrate DataTables.net
into a Rails 6 project with Webpacker
for server-rendered views. I hope this article and code bits can help people with similar use cases in setting thing up.
Premise
Back then on Ruby on Rails, we could just place every js
files in the /assets
directory and Sprockets will compile everything in it. Then, all methods and symbols will be readily available on the global scope to be used on Rails server-rendered views.
When I started a new Rails project at work, I discovered that we can use Webpacker to compile our js
files and leverage the abundance of packages from npm
. Also, I quickly found out that Webpacker does not expose all methods and classes included in all js
files globally, while trying to debug it for hours. π
From my crude understanding of how Webpacker works, as js
files are compiled into individual modules, so do the methods and classes defined are packaged into its very own scope. It means that things do not get leaked into other modules unless being required or imported. The same applies to global scope.
I found ways to import jQuery
at the global scope via a global import but the same approach does not work to expose DataTables.net
at the global scope of $
.
How I want this to work
-
jQuery
andDataTables.net
compiled and initialised atapplication.js
level for ease of maintainance. -
jQuery
and initialisedDataTables.net
instance must exists at global scope for server-rendered views.
Steps
Installing Dependencies
-
Get the necessities
yarn add jquery datatables.net datatables.net-dt
Please refer to https://datatables.net/download/npm to know which relevant
DataTables.net
release to get for your preferred stying framework. -
Get
webpack
loaders
yarn add -D css-loader expose-loader imports-loader
-
Add the following
js
files that defined the packages we want to load.-
jQuery
loader, save atconfig/webpack/loaders/jquery.js
// expose-loader helps us to expose the jquery module as $ and jQuery at // the global object, allowing us to access it at Rails server-rendered // views. module.exports = { test: require.resolve("jquery"), loader: "expose-loader", options: { exposes: ["$", "jQuery"], }, };
-
DataTables.net
loader, save atconfig/webpacker/loaders/datatables.js
// datatables.net relies on the jquery global variable to work. // import-loader helps us to add the necessary require('jquery') so the // jquery variable is available when any datatables.net packages are loaded. // refer to https://webpack.js.org/loaders/imports-loader/ module.exports = { test: /datatables\.net.*/, loader: "imports-loader", options: { // Disables AMD plugin as DataTables.net // checks for AMD before CommonJS. additionalCode: "var define = false;", }, };
-
-
At
config/webpacker/environment.js
, we will append our loaders towebpack
and add aProviderPlugin
.
const { environment } = require("@rails/webpacker"); const webpack = require("webpack"); // import our loaders. const datatables = require("./loaders/datatables"); const jquery = require("./loaders/jquery"); // append them to webpack loaders. environment.loaders.append("datatables", datatables); environment.loaders.append("expose", jquery); // ProviderPlugin helps us to load jQuery when the variables of $ and jQuery // are encountered as free variables at other modules. // Let's say if you want to use Bootstrap 4 and Popper.js. // // Refer here https://webpack.js.org/plugins/provide-plugin/ environment.plugins.append( "Provide", new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", }) ); module.exports = environment;
-
At
app/javascript/packs/application.js
import dt from "datatables.net"; document.addEventListener("turbolinks:load", () => { dt(window, $); });
-
At whatever views you have. Let's say
app/views/members/index.html.slim
.
h1 Member List table#member-table thead tr td Member ID td Family Name td Given Name td Birth Day tbody - @members.each do |member| tr td = member.id td = member.famil_name td = member.given_name td = member.birthdate.strftime('%d %B %Y') / Just slap this in. javascript: document.addEventListener('turbolinks:load', function() { $('#member-table').DataTable(); });
Import 'datatables.net-dt/css/jquery.datatables.css' with your preferred method.
That should do the trick. Now:
- Our
DataTables.net
have the neededjquery
variable within its scope. -
DataTables.net
is initialised atapplication.js
level. - We get to access
$(<query>).DataTable()
at the global scope at any Rails server-rendered views.
Example project
I have setup an example projects with the workings here.
References and citations
The approach in this article is a compilation of solutions from many precursors asking and answering the same question.
I might have missed out some references here.
- https://stackoverflow.com/questions/29080148/expose-jquery-to-real-window-object-with-webpack
- https://github.com/jbox-web/ajax-datatables-rails/blob/master/doc/webpack.md
- https://webpack.js.org/plugins/provide-plugin/#usage-jquery
- https://webpack.js.org/loaders/expose-loader/#root
- https://webpack.js.org/loaders/imports-loader/
- https://stackoverflow.com/questions/57964095/rails-6-webpack-datatables-jquery
- https://datatables.net/forums/discussion/57642/installation-issues-rails-yarn-webpacker
Top comments (3)
Hi, I followed these instructions carefully in a Rails 6 project, but get an error to console, for both imports-loader and expose-loader
Uncaught Error: Module build failed (from ./node_modules/imports-loader/dist/cjs.js): TypeError: this.getOptions is not a function
My package.json has
"dependencies": {
"datatables.net-bs4": "^1.10.23",
"devDependencies": {
"css-loader": "^5.0.1",
"expose-loader": "^2.0.0",
"imports-loader": "^2.0.0",
Do you have any idea why those module builds fail?
Hey there, I believe this is a compability issue iirc the loaders have newer major version release to cater for the new major version release for webpack.
If you can refer to the package.json of my sample project, you might notice the loaders' version is in 1.x.x
github.com/MutatedBread/rails_webp...
I might update the article sometime from now.
I tried using Data Tables and initially found it great. The promise of all those configurations and the api really delivered. But I ran into issues once the queries became complex. I ended up rolling my own - which was surprisingly easy to achieve.