Include Javascript on Certain Pages in Phoenix Framework Application - javascript

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.

Related

BundleConfig and naming JS variables

I am wondering if it is possible to name raygun variables using the BundleConfig file.
SO in my bundle I add my scripts as so:
bundles.Add(new ScriptBundle("~/master.js").Include(
"~/static/js/json2.js",
"~/static/js/jquery/jquery-ui.min.js",
"~/static/js/jquery/jquery.simplemodal.js",
"~/static/js/jquery/jquery.maskedinput.js",
"~/static/js/jquery/jquery.validate.js",
"~/static/js/jquery/jquery.inlineEditing.js",
"~/static/js/jquery/jquery.contextmenu.js",
"~/Scripts/raygun.js"));
I was wondering if it is possible to at all name my raygun.js reference as rg4js such that i could call it in my JS file where i want to use it. This is attempting to achieve the same thing as what they (Raygun) mention in their documentation by adding the following two script tags in the head and body, respectively.
<script type="text/javascript">
!function(a,b,c,d,e,f,g,h){a.RaygunObject=e,a[e]=a[e]||function(){
(a[e].o=a[e].o||[]).push(arguments)},f=b.createElement(c),g=b.getElementsByTagName(c)[0],
f.async=1,f.src=d,g.parentNode.insertBefore(f,g),h=a.onerror,a.onerror=function(b,c,d,f,g){
h&&h(b,c,d,f,g),g||(g=new Error(b)),a[e].q=a[e].q||[],a[e].q.push({
e:g})}}(window,document,"script","//cdn.raygun.io/raygun4js/raygun.min.js","rg4js");
</script>
<script type="text/javascript">
rg4js('apiKey', 'paste_your_api_key_here');
rg4js('enableCrashReporting', true);
</script>
As you can see, the first script defines the name (rg4js) whilst the latter one then uses it as a function name. Can I somehow achieve a similar thing with BundleConfig.cs, or would i need to instead insert those script tags into the shared views that act as templates for other parts of the website?

How to include simple JavaScript within Hugo

Given the following code:
$('img').mouseenter(function(){
//...
}).mouseleave(function(){
//...
});
I'd like it to be included in my articles. I'd like to avoid editing the theme if possible so to avoid forking etc.
This depends a little on which theme you use. This may be an area where we could do a better job, but do this:
In the theme, look in the
layouts/partials folder.
If you find a header.html or similar, copy this to your local layouts/partials. You can then override the content of this file only. Alternatively you can customize by copying the template used for single pages, often: layouts/_default/single.html.
bep's answer is excellent, but here are some additional details for hugo/frontend newcomers like me
1. Find a place in your HTML where to include the JS
First, one should copy the header.html or footer.html (or similar) of the Hugo theme to your layouts/partials folder. It does not necessarily have to be the header or the footer, but a file that is included in every page on your html (and that's why you would typically use the header.html or footer.html).
I got a theme that had the footer at <theme_folder>\layouts\partials\_shared\footer.html, which I then copied from the theme folder into the project layout folder <project_root>\layouts\partials\_shared\footer.html.
2. Include the script.js in the HTML
Then, I added to the bottom of footer.html
<script defer language="javascript" type="text/javascript" src="{{ "/js/myscripts.js" | urlize | relURL }}"></script>
The defer attribute can improve the page loading time a bit, and "/js/myscripts.js" is the location of my javascripts. The location is path relative to <project_root>\static\. Here are the documentation about relURL and urlize.
The example script file contains just
// myscripts.js
function myFunction(x) {
let d = new Date();
alert("Current datetime: " + d + "\nYou passed in: " + x);
}
3. Use the JS function
This is an example of using the JS function from within Hugo template (any .html belonging to the template):
{{ $somevar := "spam"}}
<button onclick="myFunction( {{ $somevar}} )">Click me</button>
Inline JS
It looks like also inline JS runs just fine; for example, adding
<script>
alert("Script loaded!");
</script>
to a template html file ran just fine. I would use this only for quick testing though, since some scripts might be needed in multiple html files, and adding the same script to multiple files would just increase your overall website filesize.
I copy themes/whatever/layouts/_default/baseof.html to layout/_default/baseof.html and add the following block at the end of the html tag:
{{ block "page-script" . }}{{ end }}
Then I can add
{{- define "page-script" -}}
<script>console.log("Hello!")</script>
{{- end -}}
in my layouts files to put in a script.

Require a JavaScript module inside a single Phoenix template

What is the standard method to require a defined JavaScript module inside of a single Phoenix Template?
I don't want the module required anywhere but inside this one template.
Here is a snippet of the files I am using.
web/static/js/trend_chart.js
let TrendChart = {
//... some JS module code here
}
web/templates/layout/app.html.eex
This has the standard app load/require.
...
<script src="<%= static_path(#conn, "/js/app.js") %>"></script>
<script>require("web/static/js/app")</script>
...
web/templates/page/index.html.eex
<!-- what do i put in this template to require / load the TrendChart module code? -->
<!-- i don't want it required globally, so i don't want to put it in the app.html.eex file -->
Update #1
I'm really looking for a way to have two #inner blocks in the main layout. One for the content, and one for additional JavaScript items to be loaded after the content.
Something like sections in ASP.NET MVC. (I know, I know!)
So the app.html.eex would end up something like this:
...
#inner
...
<script src="<%= static_path(#conn, "/js/app.js") %>"></script>
<script>require("web/static/js/app")</script>
*something here to load page/template specific javascript*
You can save the file to web/static/assets/trend_chart.js then it will be copied to priv/static/trend_chart.js and available from <script src="<%= static_path(#conn, "/trend_chart.js") %>"></script>.
All files saved to the web/static/assets directory are directly copied to priv/static without going through the build phase.

Play Framework template that is actually a JS file

I'd like to have a Play template that is a JS file (as opposed to having <script> tags inside an HTML template). The reason for this is so that the script can be cached. However, I need to create a differences in the script depending on where it's included and hoped to do this with Play's template system. I can already do so if I use embedded scripts, but those can't be cached.
I found an existing question that also asks the same thing, but the answer is totally different (different goals).
That's easy, just... create view with .js extension, i.e.: views/myDynamicScript.scala.js:
#(message: String)
alert('#message');
//Rest of your javascript...
So you can render it with Scala action as:
def myDynamicScript = Action {
Ok(views.js.myDynamicScript.render(Hello Scala!")).as("text/javascript utf-8")
}
or with Java action:
public static Result myDynamicScript() {
return ok(views.js.myDynamicScript.render("Hello Java!"));
}
Create the route to you action (probably you'll want to add some params to it):
GET /my-dynamic-script.js controllers.Application.myDynamicScript()
So you can include it in HTML templite, just like:
<script type='text/javascript' src='#routes.Application.myDynamicScript()'></script>
Optionally:
You can also render the script into your HTML doc, ie by placing this in your <head>...</head> section:
<script type='text/javascript'>
#Html(views.js.myDynamicScript.render("Find me in the head section of HTML doc!").toString())
</script>
Edit: #See also samples for other templates types

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

Categories

Resources