The right way to include page-specific JavaScript in Rails - javascript

I want to include this, for example:
jQuery(document).ready(function($) {
$('#my-modal').modal(options)
});
In one specific place in a rails app. In my case the file is called views/modals/mymodal.html.erb. There and only there.
I can't figure out how to do that without getting it on all pages as would happen if I put it in assets.

These are some useful tricks
#1 with file js
Create file your.js for your javascript
call file your.js on specific your layout
remove //= require_tree . on application.js
add your.js to asset percompile on config/application.rb : config.assets.precompile += %w( your.js )
#2 put into specific file non layout (not recommended)
put your js script with javascript tag on mymodal.html.erb
#3 use if..else..
put your js script into layout/yourlayout.html.erb and use if.. else.. logic.
example :
<% if current_page?(yourspecific_path) %>
<script language="text/javascript">
your javascript here ..
</script>
<% end %>
Read more here about current_page?
Or use request.fullpath to get current full path
example :
<% if request.fullpath == yourspecific_path %>
<script language="text/javascript">
your javascript here ..
</script>
<% end %>
Read more here about request.fullpath
Also you can combine #1 and #3 if you want script put into file .js
cheers

Given the JS file you want to include is named my_modal.js:
Place your JS files placed in assets/javascripts in some directory inside of
assets/javascripts, for example application.
Change line //= require_tree . to //= require_tree application in your application.js (it prevents loading my_modal.js on every page).
Place your my_modal.js in assets/javasctripts
Add my_modal.js to config.assets.precompile array (config assets.precompile += ['my_modal.js']) in your application.rb.
Put javascript_include_tag 'my_modal' in the view you want this file included
You can go to Rails guides for reference.

One of the solution is to insert our javascript in to its own file: Reference Link
For example :
// app/assets/javascripts/alert.js
alert("My example alert box.");
And including this file only in the view we want it to execute:
<%# app/views/page/contact.html.erb %>
<%= javascript_include_tag "alert" %>
<h1>Contact</h1>
<p>This is the contact page</p>
And don’t forget to include your new file in the list of files to be compiled:
# config/environments/production.rb
config.assets.precompile += %w( alert.js )

Late to the party, but for anyone viewing this today I think I've come up with something better.
Punchbox Gem for Rails
With this, you can write code like:
class Post {
controller() {
// Runs on every action
}
index() {
// Runs on just the index action
}
}
Punchbox.on('Posts', Post);
(You can also create an object instead of a class. See the docs)
I've written about this much more in depth here: https://kierancodes.com/blog/page-specific-javascript-in-rails-4

jquery-readyselector is a plugin that "Extends $().ready() to provide a convenient syntax for page-specific script"
Install jquery-readyselector
Create some CSS classes
<body class="<%= controller_name %> <%= action_name %>">
Scope your javascript to your page
$("#my-modal").ready(function() {
$('#my-modal').modal(options)
});
More info in this answer How to load page specific rails 4 js files?

Related

Include Javascript on Certain Pages in Phoenix Framework Application

I've got a bit of Javascript that I only want to include on certain pages in my Phoenix application.
Right now I've got the Javascript inside a script tag in myapp/web/templates/post/form.html.eex.
I understand that I can move the JavaScript to web/static/js/app.js ...but I don't want to include the Javascript on every page (it's only required on 2 specific pages).
What's the best way to load this section of Javascript on certain pages in my application without duplication the code and violating the DRY principle?
1.
Put all that javascript from form.html.eex into its own file (maybe something like js/posts.js).
Add this at the bottom:
export var Post = { run: function() {
// put initializer stuff here
// for example:
// $(document).on('click', '.remove-post', my_remove_post_function)
}}
2.
In your app.html, under <script src="#{static_path(#conn, "/js/app.js")}"></script> add this:
<%= render_existing #view_module, "scripts.html", assigns %>
3.
Then, in your view (probably views/post_view.ex), add a method like this:
def render("scripts.html", _assigns) do
~s{<script>require("web/static/js/posts").Post.run()</script>}
|> raw
end
Conclusion
Now the javascript file post.js will only be loaded when the post view is being used.
Here is one way to achieve this.
The JavaScript you have in the script tag, you move that into a separate file.
You divide your "regular" javascript (to be included in every page) and this custom javascript (to be included in some specific pages) into separate directories. e.g. app/common/standard.js and app/custom/unique.js
You modify your brunch-config.js to as follows:
module.exports = {
files: {
javascripts: {
joinTo: {
'custom.js': /^app[\\\/]common[\\\/][\S*?]\.js/,
'app.js': /^app[\\\/]common[\\\/][\S*?]\.js/
}
}
}
Then you include app.js in all pages,
<script src="<%= static_path(#conn, "/js/app.js") %>"></script>
but custom.js only in page (or layout) templates that need it.
<script src="<%= static_path(#conn, "/js/custom.js") %>"></script>
Another way is to make use of page-specific classes/elements. For example, the following code in app.js will ensure that the code only gets executed on the lesson/show page, since only that page has an element with the id #lesson-container:
import { startLesson } from './lesson/show.ts';
if (document.querySelector('#lesson-container')) {
startLesson();
}
This is based on Gazler's comment on the question and is a slightly more general answer than the one submitted by cmititiuc. You don't strictly need to wrap your page-specific JavaScript code like in that answer, nor do anything beyond import your page-specific file in the page-specific script element.
Layout templates
Use Phoenix.View.render_existing/3 in your layouts like this:
<head>
<%= render_existing #view_module, "scripts.html", assigns %>
</head>
... or this:
<head>
<%= render_existing #view_module, "scripts." <> #view_template, assigns %>
</head>
For the first example, this will render a "scripts.html" template if one exists for the relevant view module.
For the second example, a "scripts." <> #view_template template, e.g. scripts.form.html, will be rendered if it exists.
If the 'scripts' template does NOT exist for a view module, nothing will be output in the page HTML.
View modules
For the first example using render_existing/3 in the layout template, you'd add code like this to the post view module:
def render("scripts.html", _assigns) do
~E(<script src="file.js"></script>)
end
... and for the second you'd add code like this:
def render("scripts.show.html", _assigns) do
~E(<script src="show-file.js"></script>)
end
def render("scripts.index.html", _assigns) do
~E(<script src="index-file.js"></script>)
end
Details
The difference between render_existing and render is that the former won't raise an error if the referenced template does NOT exist (and nothing will be output in the page HTML in that case either).
The ~E sigil provides "HTML safe EEx syntax inside source files" and is similar to (in most cases, or maybe even always) the corresponding code from cmititiuc's answer:
~s{<script>require("web/static/js/posts").Post.run()</script>}
|> raw
Conclusion
In general then, for any page for which you want to import specific JavaScript files via script elements in the page head (or at the end of the body), or link CSS files, or do anything to the page output in a portion thereof otherwise handled by the layout, you'd use render_existing in the layout template as above and then implement appropriate render clauses in the view modules for those pages.
And further, there's no reason why you couldn't use something like both of the two examples above so that, for any view module and its templates, you could both:
Include some script(s) (or CSS files or otherwise manipulate the HTML output of in a layout template) for all the view module templates (but not all templates for the entire app)
Include some script(s) (or ...) for only a single template
<script src="myscripts.js"></script>
Put your code in a new .js file. Include the script tag with a source to the file path in the relevant html files.

Including D3 in an ERB loop

I have a partial called _index.html.erb
<% metric_objects.each do |metric_object| %>
<% if metric_object.histogram.has_key? 0 %>
<%= javascript_include_tag 'iterative_metric_graph.js', metric_object %>
<% else %>
<!--Another type of graph-->
<% end %>
<% end %>
It is supposed to generate a D3 bar graph for each metric_object. When I open the page, I get the error "D3 is not defined" in the console.
iterative_metric_graph.js is a file in my app/assets/javascripts/active_admin folder.
It consists of the javascript code from this page: http://bl.ocks.org/mbostock/3943967 Nothing more than what is inside the script tags right now.
The issue is that D3 is not being included in iterative_metric_graph.js. I could resolve the error by putting the javascript graph code (from the link above) straight into _index.html.erb, and adding <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>, but I would much prefer to render a partial. How can I include D3 (from the web or from a file) in my javascript file?
To include D3 you can just download it and stick it in you app/assets/javascripts directory. Then add the following to your application.js file:
//= require d3.v3.min
Alternatively you could try to put it in vendor/assets/... but there's a little more work involved in getting that to play nice with the asset pipeline. (more info here: Rails asset pipeline: Standard way for including all /vendor/assets/javascripts/?)

Gon doesn't work with .js files in Sinatra

In my head within layout.erb I included <%= include_gon %>
In main.rb I have written
require 'gon-sinatra'
Sinatra::register Gon::Sinatra
gon.test = "Test"
puts gon.test
The console outputs Test as expected.
However when I create a .js file and write something like console.log(gon.test) and I look in Firebug or Chrome Console, it says Reference Error: gon is not defined. Why is this happening? What should I do to fix this?
It turns out I have written <script><%= include_gon %></script> which makes it fail. <%= include_gon %> shouldn't be written inside <script> tags.

Sails.js - How to inject a js file to a specific route?

For example, I have a page /locations/map which I need to include Google Map library, and include a .js file (e.g. location.js) specifically for this page only.
I want to inject these 2 files to after <!--SCRIPTS END--> this line
Is it possible to do this?
NOTE: I was using Sails.js v0.10
Sails uses ejs-locals in its view rendering, so you can accomplish what you want with blocks.
In your layout.ejs file, underneath the <!--SCRIPTS END-->, add (for example):
<%- blocks.localScripts %>
Then in the view you're serving at /locations/map, call the block with your script tag, for example:
<% block('localScripts', '<script src="https://maps.googleapis.com/maps/api/js"></script>') %>
As an alternative, you could put the <!--SCRIPTS--> and <!--SCRIPTS END--> tags in the <head> of your layout, and then add your view-specific scripts directly into your view rather than using blocks. This is a fine option if you don't mind waiting for those linked scripts to load before your page content is displayed.
Scott's answer is the proper way to insert non-global JS into a specific view. Just a little comment, though: the block call from the view should not have the dash. It should be as follows:
<% block('localScripts', '<script src="https://maps.googleapis.com/maps/api/js"></script>') %>
Both calls will work, but using the dash makes the insertion twice; once the view is loaded and previous to the layout render, and then once again when the view is inserted in the rendered base layout. This leads not only to inserting/running unnecessarily twice the same code but also to errors that break your JS code if the inserted script depends on libraries that you have in your base layout (e.g. jQuery, Backbone).
EJS interprets the magic <%- as "insert unescaped". So, -I guess- what this is doing is calling the block() function, which returns our HTML <script> tag. This is replaced where the magic was called but also is executing the block() function inside of it, which is executing the layout block localScripts replacement.
On the other hand, <% means "instruction". I.e., just run this JS piece of code, which is not echoed to the view where is called.
I discover other way to do that
In MapController.js
// you can add as many as you like
res.locals.scripts = [
'//maps.googleapis.com/maps/api/js',
];
return res.view();
In layout.ejs
<!--SCRIPTS-->
<!--SCRIPTS END-->
<!-- Loop through all scripts that passed from controller -->
<% if (scripts) { %>
<% for (i = 0; i < scripts.length; i++) { %>
<script src="<%- scripts[i] %>"></script>
<% } %>
<% } %>
This method allows flexibility to locally serve js files from any page and also prevent any reference errors caused by dependencies.
In pipeline.js insert '!js/local/*.js at the bottom of jsFilesToInject like so:
var jsFilesToInject = [
// Load sails.io before everything else
'js/dependencies/sails.io.js',
// Dependencies like jQuery, or Angular are brought in here
'js/dependencies/jquery-3.3.1.min.js',
'js/dependencies/**/*.js',
// All of the rest of your client-side js files
// will be injected here in no particular order.
'js/**/*.js',
//Ignore local injected scripts
'!js/local/*.js'
];
Create a local folder inside the /assets/js folder ie /assets/js/local/. Place any locally injected scripts in here.
In your master view ejs ie layout.ejs insert <%- blocks.localScripts %> below the SCRIPTS block like this:
<!--SCRIPTS-->
<script src="/js/dependencies/sails.io.js"></script>
<script src="/js/dependencies/jquery-3.3.1.min.js"></script>
<script src="/js/dependencies/bootstrap.min.js"></script>
<script src="/js/dependencies/popper.min.js"></script>
<!--SCRIPTS END-->
<%- blocks.localScripts %>
In your local ejs view (eg. homepage.ejs) insert your localScripts block like this:
<% block('localScripts', '<script src="/js/local/homepage.js"></script>') %>
sails v0.12.14
EDIT
Is this still relevant for Sails v1.0?
My answer is a resounding YES and in my earlier answer I lacked explaining how to get the most out of the Grunt pipeline like clean, coffee, concat, uglify etc... when going into production.
The trick here is to make a local file (there should only be one per page) as small as possible.
Group and name specific your function calls
Save functions as separate files for easy maintenance and group them into folders.
Group bindings and any initialising of global variables into a couple of functions like initThisPageVariables() and initThisPageBindings() so that Grunt can crunch these later.
Set a master function call to run your app startThisPageApp()
Then simply calling the few functions from your local (master) file to get things rolling.
$(window).on('load', function(){
initThisPageVariables();
initThisPageBindings();
$(window).on("resize", function(){
initThisPageVariables();
}).resize();
startThisPageApp();
});
I know this is an old question by I discovered another option.
File:
config\routes.js
Code:
'/': 'HomeController.StartHome'
I set StartHome function in HomeController responsible for managing '/' route.
File:
api\controllers\HomeController.js
Code:
StartHome: function(req, res) {
var template_data = {
"data" : {
"view_name" : "home_view"
}
}
res.view('home_view', template_data)
}
Here, I created an object with some data which is passed to EJS template(to client).
File:
views\layout.ejs
Code:
<% if (data["view_name"] === "home_view") { %>
<script src="script_for_home_view.js"></script>
<% } %>
In my layouts.ejs I created if statement which "enables" script tag depending on on the view I am currently on.
This is how I handle this. I hope it's clear for you.
Sails 0.12.4

In Ruby on Rails, how to make HTML as partials in one file and Javascript in another file to be included last?

A lot of time, in a partial, the HTML (or ERB or HAML) as well as the Javascript is in one file, so when the main file includes these partials, the HTML will be intermixed with Javascript code.
However, it is said that for fast page content display, all Javascropt code should be placed at the end of the HTML file. In this case, is there a proper or standard way to make this happen? Perhaps using 1 partial as HTML, and 1 partial as Javascript, and include 2 different partials (the HTML as the correct place, and the Javascript near the end of the file?)
(This is related to If Javascript code block is not at end of HTML file, but is using jQuery's $(document).ready(function() {...}), will it slow down the page display? )
In your layout file you could put a footer into which you can load your Javascript (typically stuff like analytics that don't need to be loaded early). Then define a content_for in your views which includes your Javascript partial. The result is that that Javascript will be placed at the end of the page.
In the layout:
<div id="footer">
<%= yield :footer %>
</div>
In your views/paritals:
<% content_for :footer %>
<script type="text/javascript">
// Some Javascript
</script>
<% end %>
or
<% content_for :footer %>
<%= render :partial => "path_to_some_javascript" %>
<% end %>

Categories

Resources