Rails .js.erb templates no longer work with Webpack - javascript

I've just switched my Rails app over to use WebPack to deal with assets etc. It's working well apart from I have some JS templates in my views directory (*.js.erb) . These require jQuery and as jQuery is pulled in as part of my WebPack bundles, it is not working in those templates.
Is there a way to allow those templates to work?

I got this to work in my app by adding the expose-loader package, then adding these two lines to my app/javascript/packs/application.js:
import 'expose-loader?$!jquery';
import 'expose-loader?jQuery!jquery';

Well in order to make things work you need to include jquery using yarn which works with the latest version of rails.
In Rails 5.1 this is done with the new JavaScript packet manager Yarn which you have to install first
sudo apt-get install yarn
Then you can use it to install jQuery:
yarn add jquery
To actually load jquery you have to add this line
//= require rails-ujs
//= require jquery
//= require turbolinks
//= require_tree .
After installing jquery your js.erb file will start working
Refer this article

This is how I use jquery in .js.erb when I use webpack and without asset pipeline.
(I assume that the Rails is created with something like $ rails new MyApp --webpack)
First of, I replace <%= javascript_include_tag 'application' %> with <%= javascript_pack_tag 'application' %> so we use javascript from /app/javascript/pack/application.js instead of /app/assets/javascripts/application.js
Add rails-ujs and jquery by running
$ yarn add rails-ujs jquery
modify /config/webpack/environment.js as following
const { environment } = require('#rails/webpacker')
const webpack = require('webpack')
// Add an additional plugin of your choosing : ProvidePlugin
environment.plugins.prepend(
'Provide',
new webpack.ProvidePlugin({
$: 'jquery/src/jquery',
jquery: 'jquery/src/jquery'
})
)
module.exports = environment
The webpack configuration is reference from here
I make a test page like this...
in /app/views/pages/index.html.erb
<div class="index">
<h1 class="index--title">Index Title</h1>
<div class="index--jquery-test">
<%= link_to "jquery test", pages_index_path, remote: true %>
</div>
</div>
and in /app/views/pages/index.js.erb it has following code, that will run jquery when the link is clicked
$(function(){
console.log('run <%= "JQuery" %> from .js.erb')
})
In /app/javascript/pack/application.js import Rails and call Rails.start() to allow the date-remote in the link to work and to make unobtrusive javascript to call index.js.erb file.
I also try to see if jquery from within pack/application.js file is also work by calling console.log to show a title text that I got from jquery selector $('.index--title').text()
import Rails from 'rails-ujs'
Rails.start()
$(function() {
console.log($('.index--title').text())
})
When run Rails app at, let say, http://localhost:3000/pages/index you should see the 'Index Title' in console windows of the browser. And when click the link you should see 'run JQuery from .js.erb' in the console windows. I hope it works for you.

Related

Rails 6 webpacker $(...).modal is not a function - importing dynamic packs

I have a Rails 6 app in which I am trying to use webpacker. I was using it succcessfully and importing all packs in my application.js file but instead now I just want to import the application.js file that has jquery and bootstrap in it, and dynamically load the correct pack based on the controller.
For instance, my previous configuration was
import "bootstrap"
require("#rails/ujs").start()
require("turbolinks").start()
require("#rails/activestorage").start()
require("channels")
require("jquery")
require("../packs/classrooms")
require("../packs/lunch_choices")
require("../packs/events")
require("../packs/users")
What I'd rather do is remove all packs from the application.js file and just import the correct pack dynamically like so...
Application.html.erb
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag "#{controller_name}", 'data-turbolinks-track': 'reload' %>
If I view the source code when I reload the page and restart the server, I see that the pack does load, however, I get the following error.
$(...).modal is not a function
Since the application file is being loaded first and that's the one that contains jquery, why am I getting this error? This was working when I was including all packs in application.js, but now that I want to split by the pack it does not.
Here is my environments.js file - this has never changed since setting up my app.
const { environment } = require('#rails/webpacker')
const webpack = require('webpack')
environment.plugins.prepend('Provide',
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
jquery: 'jquery',
Popper: ['popper.js', 'default']
})
)
module.exports = environment
Each pack is a separate dependency graph. Without extra configuration, the graphs are not shared. I would guess you’re either accessing a function on the wrong dependency graph or possibly clobbering one instance of jQuery for another in the global scope.
Splitting your code into multiple packs, while possible, is error-prone and works against the way webpack was intended to be used.
Try consolidating everything into just one pack and splitting code with dynamic imports instead. Your “page-specific” code would live in another folder, like app/javascript/pages, and might be imported conditionally, based on the presence of a certain DOM element, for example. This achieves the goal of producing smaller bundles from one pack while keeping all your code in the same dependency graph.
https://webpack.js.org/guides/code-splitting/

Why are my js.erb views not working when using webpacker in Rails 6 with bootstrap?

Im trying to add som simple Ajax to my rails app. I am using Bootstrap with webpack.
My webpack/environment.js file looks like this
const { environment } = require('#rails/webpacker')
const webpack = require('webpack')
environment.plugins.append('Provide',
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
Popper: ['popper.js', 'default']
})
)
module.exports = environment
My javascript/packs/application.js looks like this
require("#rails/ujs").start()
require("turbolinks").start()
require("#rails/activestorage").start()
require("channels")
import 'bootstrap'
import 'src/main'
import 'style/main'
I'm trying to add some Ajax for one of my models in create.js.erb
$("#question-<%= #exam_option.exam_question.id %>-options").append("<%= escape_javascript render 'exam_option', option: #exam_option %>");
$('#add_option_modal').modal('hide');
When I try and add one of my options, I get the console error
ReferenceError: Can't find variable: $
I've searched for a solution and have been unsuccessful.
What am I doing wrong? Thank you!
Edit:
When I added Bootstrap to my app, I followed this guide . When following the guide, I installed bootstrap, jquery and popper.js with yarn
yarn add bootstrap jquery popper.js
The jQuery that Bootstrap uses is working correctly (such as tooltips).
as per #mechnicov's answer, I tried changing my environment.js to
const webpack = require('webpack')
environment.plugins.append('Provide',
new webpack.ProvidePlugin({
$: 'jquery/src/jquery',
jQuery: 'jquery/src/jquery',
Popper: ['popper.js', 'default']
})
)
and I added require("jquery") above import 'bootstrap' in my application.js
when I made those changes, my code in create.js.erb works correctly, but it makes my Bootstrap not function correctly and throw errors such as TypeError: ... $('[data-toggle="tooltip"]').tooltip() undefined.
Edit 2
As per the accepted answer, I changed my application.js to :
require("#rails/ujs").start()
require("turbolinks").start()
require("#rails/activestorage").start()
require("channels")
window.jQuery = window.$ = require('jquery')
import 'bootstrap'
import 'src/main'
import 'style/main'
All seems to be working correctly, but I'm unsure why I need to add it this way? isn't Webpack supposed to do this from my environment.js file?
If someone can explain this, please do.
Can you try below code :-
# javascript/packs/application.js
window.jQuery = window.$ = require('jquery')
OR
# javascript/packs/application.js
require("jquery")
window.jQuery = $;
window.$ = $;
I was also facing same problem but above code was worked for me at that time
Hope this will help you also. :)
All seems to be working correctly, but I'm unsure why I need to add it
this way? isn't Webpack supposed to do this from my environment.js
file? If someone can explain this, please do.
I'm going to answer your question based on the edit you have done in your question, what you have to understand is how webpack works, basically you have:
1 - an entry file which is your source code file (that contains "imports" and/or other javascript code)
2 - Webpack takes that entry file, bundle it, and produces an output file. In your application, this is what you end up using, the final output/bundle.
In Rails, Webpacker is the gem that configures webpack for you out of the box and hides the boring details from you so you can just jump and start using it.
In term of webpacker any file you put in javascript/packs is considered as an entry file, by default we have application.js there, so app/javascript/packs/application.js is your entry file, the output file will have the same name with a hash string attached to it, something like this application-ff14101dff18182f89f6.js
Let's imagine you add another file to app/javascript/packs called admin.js (which is another entry file), what will happen is that Webpacker is configured to make an output file from each entry file. That means after compilation, you will have another file admin-ff14101dff18182f89f6.js as output.
The output files are placed under /packs/js in your Rails application, and you can call them from layout as follows:
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'admin', 'data-turbolinks-track': 'reload' %
You can even use the first tag in your layouts/application.html.erb and the second one in another layout, for example, layouts/admin.html.erb, you don't have to push all your code in one giant application.js (yep you can make your site faster by loading only the necessary scripts).
Now that you understand the basics, it's time to answer your question as we said before Webpacker is using any JS file in app/javascript/packs as an entry file, you can verify this by opening config.webpacker.yml file, and locate the following:
default: &default
source_path: app/javascript
source_entry_path: packs // <------ The location of the entry files
public_root_path: public
public_output_path: packs // <------ The location of the output/bundle
...
So even when you are exposing jQuery in environment.js via the ProvidePlugin, it's meant to be available for the webpack configured source path which is app/javascript only. That explains why you cannot access jquery from app/views/some-view-folder/create.js.erb.
You may ask, can expose the view folder app/views/some-view-folder to webpack somehow? Theoretically yes, by "resolving" that folder which webpacker has a configuration for it (see config/webpacker.yml):
# Additional paths webpack should lookup modules
# ['app/assets', 'engine/foo/app/assets']
resolved_paths: []
It's possible to do something like resolved_paths: ['app/views/some-view-folder'] which means if you import create.js.erb in your application.js webpack will be able to find that file and import it, however practically it doesn't make any sense as create.js.erb needs to be executed at run time and execute other ruby code from your application and it's used to render a view dynamically not for a static purpose like a JS file does (sorry I don't know much about Rails internal but you got the idea)
By doing window.jQuery = window.$ = $ you are exposing jQuery to the window object, which makes it a global, but you can always use a regular ajax request in a file under app/javascript instead of relying on something like remote: true depending on if you want to expose things globally or not.
I hope this was somehow useful, I have written a book for beginners about webpack which you can check on amazon or on apress if that interests you, that will give you a full understanding how webpack works whether you are working with Rails or any other framework :)
You don't load JQuery.
Add it to your node modules:
$ yarn add jquery
Write right path to JQuery in config/webpack/environment.js
const { environment } = require('#rails/webpacker')
const webpack = require('webpack')
environment.plugins.prepend('Provide',
new webpack.ProvidePlugin({
$: 'jquery/src/jquery',
jQuery: 'jquery/src/jquery',
Popper: ['popper.js', 'default']
})
)
module.exports = environment
Add line before bootstrap in javascript/packs/application.js:
require("jquery")
And don't forget about javascript_pack_tag 'application' in main layout view.
Hope this will help you.

Rails Jquery doesn't work on other pages

I believe jQuery doesn't work on other pages than the one you just installed. For example, when I type localhost:3000/, in the '/' directory all jQuery works. But when I click to a link created by Rails as
<%= link_to "Example Link", news_path %>
The page correctly loads, but the jQuery doesn't work. My jQuery codes are as stated:
$(function() {
console.log( "pageloaded" );
....
$('.up').click(function(){
.....
});
});
In Rails 4 turbolinks is active by default.
That means, that $(document).ready() is not executed, when you load a new page.
turbolink fires a new event page:load. You can use this to run your javascript code:
$(document).on('page:load', your_start_function);
There is an excelent rails cast on turbolinks
https://github.com/kossnocorp/jquery.turbolinks
This gem binds jQuery.ready function with Turbolinks listen event page:load, therefore the solution is supposed to be solved if you are using Turbolinks with Rails 4
You need gem 'jquery-rails' in your Gemfile (then bundle install if you're using bundler)
Once that's done, you need to require it in your application.js file:
//= require jquery
Last, you need to ensure that the application.js file is loaded as part of the application template. In application.html.erb you should have the following:
<%= javascript_include_tag "application" %>
I believe that this is all there by default in a Rails install so if you have removed any of these settings you'll need to add them back to get it to work.

Trouble on using AJAX forms

I am using Ruby on Rails 3.1.1 and the jquery-rails 1.0.16 gem. I have an issue on using a form with :remote => true.
In my view file I have:
<%= form_for(#user, :url => user_path(#user), :remote => true) do |f| %>
...
<%= f.submit "Update" %>
<% end %>
When I click on the Update button and I inspect the Firebug Console, I see that two AJAX HTTP requests are performed instead of one as well as it should be. The same problem happens for all forms in my application that are using :remote => true.
What could be the problem? How to fix it?
Note: If I inspect the DOM it seems that in the current page I do not have duplicate of HTML\CSS id values.
P.S. I: I tried to use different browsers and clear them cache but the problem still occurs.
P.S. II: The problem occurs in development mode in localhost (on my local machine). I have not tried yet if it happens in production mode on the remote machine.
UPDATE I
In my application.js file I had
//= require jquery
//= require jquery_ujs
//= require jquery-ui
I tried to remove the require jquery_ujs line and now it seems to work until I run the bundle exec rake assets:precompile command and restart the server. Exactly: if I remove the require jquery_ujs line and I do not run the bundle command it works as well as expected; but if then I run the bundle command the AJAX form submission doesn't work "at all"\"anymore".
Maybe the problem is related to the bundle command that generates fingerprinted files... could be that?
UPDATE II
My filesystem related to JavaScript files is:
app/assets/javascripts/
app/assets/javascripts/application.js
lib/assets/javascripts/
vendor/assets/javascripts/
vendor/assets/javascripts/vendor.js
vendor/assets/javascripts/jquery_plugins/plugin1.js
vendor/assets/javascripts/jquery_plugins/plugin2.js
vendor/assets/javascripts/jquery_plugins/....js
In my app/assets/javascripts/application.js file I have:
//= require jquery
//= require jquery_ujs
//= require jquery-ui
//
//= require_tree .
//
//= require vendor
In my vendor/assets/javascripts/vendor.js file I have:
//= require_directory ./jquery_plugins
If I run the following command
$ bundle exec rake assets:precompile
/<MY_USER_PATH>/.rvm/rubies/ruby-1.9.2-p290/bin/ruby /<MY_USER_PATH>/.rvm/gems/ruby-1.9.2-p290/bin/rake assets:precompile:all RAILS_ENV=production RAILS_GROUPS=assets
/<MY_USER_PATH>/.rvm/rubies/ruby-1.9.2-p290/bin/ruby /U<MY_USER_PATH>/.rvm/gems/ruby-1.9.2-p290/bin/rake assets:precompile:nondigest RAILS_ENV=production RAILS_GROUPS=assets
in the public/assets/ directory it creates those files
application-b63d5946eebe0c8d46e078ef32299fc5.js
application-b63d5946eebe0c8d46e078ef32299fc5.js.gz
application.js
application.js.gz
manifest.yml
...
If I inspect the page HTML code, I can see the following:
<script src="/assets/jquery.js?body=1" type="text/javascript"></script>
<script src="/assets/jquery-ui.js?body=1" type="text/javascript"></script>
<script src="/assets/jquery_ujs.js?body=1" type="text/javascript"></script>
<script src="/assets/jquery_plugins/plugin1.js?body=1" type="text/javascript"></script>
<script src="/assets/jquery_plugins/plugin2.js?body=1" type="text/javascript"></script>
<script src="/assets/jquery_plugins/....js?body=1" type="text/javascript"></script>
<script src="/assets/vendor.js?body=1" type="text/javascript"></script>
<script src="/assets/application.js?body=1" type="text/javascript"></script>
Prior to Rails 3.1, you would include jQuery-ujs by manually adding rails.js to your public/javascripts folder.
With Rails 3.1, you now have a gem 'jquery-rails' line in your Gemfile and a require jquery_ujs line in your app/assets/javascripts/application.js file; there is no need for manually adding the rails.js file because it's already bundled with the gem.
If you upgraded a non-3.1 app to 3.1, you may still have rails.js sitting around, so the UJS stuff is getting run twice. Rather than removing the require line from your application.js file, you should probably just delete the rails.js file instead. (You also may still have the actual jQuery JS file sitting in there too; same thing, it's included automatically by the jquery-rails gem and you can delete it).
UPDATE
I just realized you're precompiling your assets. You don't want to do this in development mode, as when you request /assets/application.js it's probably serving up /public/assets/application.js which includes all the other JS files inside of it. To fix, clear out the public/assets folder and only precompile your assets in production. (See Rails 3.1 remote requests submitting twice)

What can cause Rails 3.1 "= require jquery" to stop working?

I'm porting a Rails 3.0.9 app to Rails 3.1.rc5. My application.js is exactly the same as one generated by Rails 3.1 itself:
// This is a manifest file ...
//
//= require jquery
//= require jquery_ujs
//= require_tree .
But when I run my app and look at the application.js in Firebug or Chrome Developer Tools, all I see is:
// This is a manifest file ...
//
The directives are gone, so it would seem that the file has been processed by Sprockets, but the directives have not been replaced by the contents of jquery et al. There are no errors appearing on the server console or in the logs.
Curiously, when I run a blog app (you know, the canonical tutorial app) it works fine (that is, when I examine application.js in Firebug, it contains the text of jQuery.) This would seem to indicate that something in my app is somehow interfering with Sprockets. Has anyone out there heard of such an issue (and hopefully a workaround)?
Here's my setup:
$ gem list jquery
*** LOCAL GEMS ***
jquery-rails (1.0.12)
$ ruby -v
ruby 1.9.2p290 (2011-07-09) [i386-mingw32]
$ rails -v
Rails 3.1.0.rc5
I'm at a loss as to what might be wrong. I've triple checked my Gemfile; I've run and re-run bundle install and bundle update; I've tried rc3, rc4 and now rc5; I'm running Ruby 1.9.2p290. Any ideas?
One workaround: include JavaScript files with the old-skool tag. For example, in my (Haml) layout:
= javascript_include_tag '/assets/jquery.js'
= javascript_include_tag '/assets/jquery_ujs.js'
= javascript_tag 'jQuery.noConflict();'
The /assets/ prefix tells Rails 3.1.x to look on the asset path, which includes gems, so you'll get the same files as with Sprockets directives. But you won't get concatenation or any other Sprockets preprocessing.
Still looking for better solutions.

Categories

Resources