Rails, Javascript & Turbolinks - function not available - javascript

I have a rails app and I am trying to organize my javascript into page specific javascript files and only load it if the page requires it.
I use a 2nd manifest file to load page-specific javascript for my "goal_designer" page.
If I do a 'view page source' on my goal_designer page (after I follow a link t it, not reload it) I confirm that the js file has been added:
<script src="/assets/application.js?body=1" data-turbolinks-track="true"></script>
<script src="/assets/files/goal_designer.js?body=1"></script>
<script src="/assets/goal_designer_bundle.js?body=1"></script>
However, if I try to use a function that is within this file it does not work.
If I reload the page it works fine and the function is available to use.
In my app/assets/files/goal_designer.js I have:
$(document).on('page:change', function(){
hello_there_1();
});
$(document).on('page:load', function(){
hello_there_1();
});
function hello_there_1() {
alert("hello_there_1");
}
hello_there_1 only gets triggered if I reload the page, not if I navigate to it.
My understanding was that the goal_designer.js should be available as it has been added to on this page and the hello_there_1() function should be available but this does not seem to be the case.
So, once the page:loads then the function should trigger.
Another thing - if I look at the assets list in the Chrome debugger (Sources) the goal_designer.js only appears in the list if I reload the page, not if I navigate to it (even though it is in the 'page source' ok).
I am in development mode so I thought all js would be preloaded or available to be referenced. Is this correct?
If I click into a page, and the page-specific js gets added to the head, how can I bind an addEventListener to an object on the page from within this newly loaded file?
FYI - other info - I think I am doing this bit right.
I use this code in my goal_designer.html.erb:
<% content_for :head do %>
<%= javascript_include_tag 'goal_designer_bundle' %>
<% end %>
And this in my application.html.erb:
<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= yield :head %>
In my app/assets/javascripts I have a manifest file, goal_designer_bundle.js, which contains:
//= require ./files/goal_designer

By design, Turbolinks doesn't update the <head> section, only the body. The idea is that your JS and CSS is requested once on initial page load and subsequent requests avoid this by using AJAX and simply replacing the body. Thus, with the way Turbolinks is designed to work, your content_for :head block won't work.
Philosophically your approach is very different from how Turbolinks works so if you plan on using it I would just disable Turbolinks.

Related

Redmine plugins scripting

I'm new to javascript, Redmine and RoR. So far I have read and understood the plugin development tutorial. But when I try to do things on my own they won't work...
If I use this:
<% content_for :header_tags do %>
<%= javascript_include_tag 'script', :plugin => 'my_plugin' %>
<% end %>
it will generate the correct link code on page source but there will be no scripts loaded to redmine_root/public/plugin_assets. Is that supposed to happen?
I would like to make this hello world example work.
But as far as I can understand, it will never work on the whole Redmine app if the scripts don't get loaded to redmine_root/public/plugin_assets.
If someone can help me out with understand why the scripts are not loading and how to properly use scripts under Redmine I would be very grateful.
Is code sample copy-paste from your project code? Or You typed it manually with misprints? % and = signs are missed in some places, must be <%= ... %> or <% ... %>
What the content of redmine_root/public/plugin_assets folder?
It must have plugin folder named same as your plugin, then images, javascripts and stylesheets folders, like here:
redmine_root
...
plugins
your_plugin # your plugin name
other_plugins
...
public
...
plugin_assets
your_plugin # your plugin name, must match with your plugin name in redmine_root/plugins
javascripts
your_script.js
images
stylesheets
other_plugins
In this case javascript_include_tag will works correctly with standart parameters (script and plugin names).

Avoid multiple javascript_include_tag when rendering collections

I am trying to render a collection this way
views/consumer/home/index.html.erb
<%= render #promotions %>
consumer/promotions/_promotion.html.erb
<% content_for :page_js_modules do %>
<%= javascript_include_tag 'consumer/carousels' %>
<% end %>
Since _promotion.html.erb is rendered more than once if there are more than one promotions in #promotions, the javascript tag also gets included more than once and causing issues.
I want to put the javascript tag inside of promotion view only so as to make it modular and hence anyone can use it without worrying about including the pertaining js tags.
Since _promotion.html.erb is rendered more than once if there are more than one promotions in #promotions, the javascript tag also gets included more than once and causing issues.
This is what partials are for, to reuse same code. You can't use (you can but it won't be a good practice) a js tag inside a partial because it will keep on rendering content inside the partial for each record in collection.
I want to put the javascript tag inside of promotion view only so as to make it modular and hence anyone can use it without worrying about including the pertaining js tags.
I think a better approach would be to have a partial inside promotions which would contain your js tag and render promotion partial
#consumer/promotions/_promotion_with_js.html.erb
<% content_for :page_js_modules do %>
<%= javascript_include_tag 'consumer/carousels' %>
<% end %>
<%= render #promotions %>
if there are other use cases where you just want to include js instead of promotions partial then you can always separate out that in another partial and use it.
You probably want to include that script tag in your layout, not in a partial. Layouts are a good place to include scripts and stylesheets. You will incur only one call and the response will be cached. Best to call the script once only and rely on that cache, even if the layout calls it needlessly.

Rails - Incorrect Assets Loading

Occasionally I'll click a link in my application, and the *.css and *.js files that are loaded are referring to the wrong controller. If I refresh the page, this is immediately fixed, but I do not want to tell my users that the occasional page refresh may be required.
This is where the content is loaded in my application layout:
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
= stylesheet_link_tag params[:controller]
= javascript_include_tag 'application', 'data-turbolinks-track' => true
= javascript_include_tag params[:controller]
And where the assets are loaded in my assets.rb initilizer
Rails.application.config.assets.paths << Rails.root.join('vendor', 'assets', 'flash')
%w( comments contact_types ensembles favorite_instruments fields gds gigs instuments
rank_members recruit_statuses scores seasons section_notes sections static students
announcements ).each do |controller|
Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
end
It's strange to me that sometimes it work work and other times, it would not. I assume that I have a gem issue with some of the page loading stuff. I am using turbolinks, spring, and quiet_assets. I don't think any of the other gems touch assets.
TurboLinks intercepts requests and fetches only the body of the document, "seamlessly" substituting it into the DOM. It doesn't touch <head>, so your JavaScript includes are not changing until you fully reload the page and fetch a new document with the appropriate <head>.
TurboLinks (and Rails) assume you're going to have a single bundle of JavaScript, not many small conditionally included JavaScripts. You're going to have to either go the Rails-way, and stop using many small manifests, or turn off TurboLinks.

Confused about how to use vanilla JavaScript with Ruby on Rails

I have two views, one called dashboard and the other called home, both have their respective index.htm.erb files. The corresponding JavaScript files in the assets/javascripts folder do not seem to map to these views. For example, when I run an alert in home.js and then navigate to dashboard.html.erb the alert still runs.
How do I remedy this to get the functionality I am looking for?
I am confused as to how to write page specific raw JavaScript in Rails.
Edit: I could use the public folder but I had some ancillary issues with that and hence why I started using the files in the assets folder.
One of Rails' "opinions" about javascript is that it should be all concatenated into a single file (application.js), minified, and served to the client. This is an effort to minimize the number of requests the client needs to make when accessing your application (the goal is to have only three, one for your html, one for css, and one for javascript [excluding whatever images may be on the page.])
If you look in your application.js, you'll see a note saying that it is a 'manifest' file, and a line that looks like
require_tree .
Which says "load every javascript file in this folder into this file"
In order to load up some page-specific javascript, you'd need to
put a separate javascript file into app/assets/javascripts [call it custom.js, say]
stub out loading that file into the application manifest by writing stub custom in application.js
Include the custom javascript manually in your view (or, more better probably, a layout which renders your view): <%= javascript_include_tag 'custom' %>
However, I'd encourage you to look at whether you really need to separate this javascript, or whether it's a problem that can be solved by simply localizing your script to the page(s) it's intended for, which will keep the same functionality and keep your loads times ever-so-slightly faster.
$('body.some_custom_class').ready(function() {
alert('I'm running on this page!');
});
you can use content_for in your layout
app/views/layouts/application.html.erb
<html>
<head>
<title> ...</title>
....
<%= yield :javascript %>
....
</head>
....
</html>
and on your view app/views/home.html.erb
<% content_for :javascript do %>
<%= javascript_include_tag 'home' %>
<% end %>
other view content
you would also need to look at application.js and remove the //= require tree . line

Why is rails not rendering the .js.erb file?

I have a rails application where I am trying to have jQuery render WITH an HTML file. The point of this is that the jQuery is specific to the page so I don't want it loading for every page by putting it in the header. Here's what I have done to the files
messages_controller.rb
# GET /messages/new
def new
#message = Message.new
#users = User.where('id != ' + current_user.id.to_s)
#if #message.save
#MessageMailer.new_message(#message).deliver
respond_to do |format|
format.html # new.html.erb
format.js
end
#end
end
/messages/new.html.erb
<div class="row">
<div class="large-12 columns">
<div class="panel">
<h1>New message</h1>
<%= render 'form' %>
<%= link_to 'Back', messages_path, :class => "button small" %>
</div>
</div>
</div>
/messages/new.js.erb
$("h1").html('hello'); //for testing that it works
Is there something that I have to add to another file? Is there a request I have to make so it loads both the HTML and .js?
Why this won't work?
I don't think your method is a particularly standard way to accomplish what you want. The reason why your JS template is not being loaded is because your controller is setup to respond with an HTML template, if the browser requests HTML, and a JS template, if the browser requests JS. It will never return both because the browser can only request one or the other.
In other words, the respond_to block in your controller is instructing the controller how to respond to requests for different types of content, not listing all of the types of content that will be sent, on every request.
Solution 1: Load the JS on every page
There are a couple of things to consider here. Firstly, just because JS is not used on a particular page, is not, in itself, a good reason not to load it. Rails, via Sprockets, will concatenate and minify all of your JS into a single file. As long as this file does not change, then it will be cached by a user's browser. This will result in a faster response when your user visits a page that does require this JS, as it won't need to make an extra request in order to get the new JS.
If you are concerned about your JS running on content that it shouldn't, then you should really be guarding against this by adding a unique identifier to the page, or pages, on which you want it to run, then detecting that identifier in your JS. For example:
$("h1.special").html('hello');
Solution 2: Use content_for
However, there are some circumstances in which it might be beneficial to load JS separately. The best way to accomplish that, in my opinion, is to have a content for block that appends content to your head, if an individual view needs it.
So in your application layout:
#layouts/application.html.erb
<head>
#Other head content here
<% if content_for?(:head) %>
<%= yield(:head) %>
<% end %>
#Other head content here
</head>
#messages/new.html.erb
<% content_for :head do $>
<%= javascript_include_tag "messages_new.js" %>
<% end %>
#Other view stuff here
#assets/messages_new.js
$("h1").html('hello');
messages_new.js will then only included, and therefore run, on views where you include that content_for block. It should not appear on any other page.
Solution 3: Just include the javascript in your html
Alternatively, if you don't care if the JS is loaded last, then you can just add the include helper at the bottom of the new.html.erb:
#messages/new.html.erb
#Other view stuff here
<%= javascript_include_tag "messages_new.js" %>
From what i understand, you want to have new.html and new.js to be served when requesting /messages/new.
It will not work, the rails controller will use js or HTML and never both.
HTTP does not work like that. You have only one file that is send in response to an HTTP request and you cannot send 2 at the same time.
The purpose of the respond_to block of the action is to use html for html request (classic browsing) and js when the request is for js, and that's what you use for ajax.
For what you want, you need to add a js reference in your erb, there is no other way.
Sorry I'm late but you can call the JavaScript in "/messages/new.js.erb" by using:
messages/new.html.erb
link_to "Link", "", remote: true
Any time you click that link it will fire "/messages/new.js.erb".
Javascript code will run from your html.erb file if you wrap it in script tags. If it's for testing purposes only and just temporary, this might be a quick and easy way to fire your jquery with your html rather than creating a new, separate file.

Categories