I was under the impression that you could put javascript in a view template in Rails 3. For example, if I had this html in views/public/home.html.erb
<div id="block">click</div>
then in views/public/home.js.erb, I thought I could put the following javascript, and then click on html to trigger the javascript. However, when I tested it, I got no results. But if I put the javascript in assets/javascript/application.js, then everything worked fine...Shouldn't it also work if it was in a js template with the same name as the html view?
$(document).ready(function(){
test();
});
function test() {
$("#block").click(function() {
$('#block').hide();
});
}
Ummm. no. It just doesn't work that way. Only one of views/public/home.* will be rendered, depending on the responds type.
Javascript shouldn't be added as a view file (bla.js.erb). They must be put in assets/javascripts or at least in lib or vendor directory.
You must also require them in your application.js file, if you already don't use require_tree.
In this way you won't need to reference the javascript in any way in your view (the application.js will include it for you). Otherwise, you need to specify a layout to insert javascript files in block, because views are rendered after tag.
There are a lot of reason not to put javascript directly in html (except for tests obviusly), read the rails asset pipeline for more information.
When you create a view with a different extension from html.erb that will be used only if your url specify a format with that extension, for example mywebsite/users.json will return eventually a index.json.erb.
For AJAX you would like to return a JSON object, not javascript which is definitely not a correct approach. Remember that you are using a framework and you should follow it's guidelines. If you want to live it's rails, it will be hard to work with it.
You can use javascript_include_tag
If you have the js files source1.js, source2.js in public/javascript then you can include them using
javascript_include_tag 'source1', 'source2'
Related
I am currently working on cleaning up a little web app I've written in Mojolicious. As part of that cleanup I am separating out my javascript into the public directory from my html.ep files.
The problem I have though is I don't seem to be able to reference tag helpers any more, like 'url_for' or even referencing values in the stash such as '<% $stashvalue %>'.
Any idea's on how or if I can do this is very much appreciated.
cheers.
The stuff in the public directory is served statically, optimally by your webserver, not by Mojolicious or Plack, so that file does not get processed by Mojolicious, thus <% $stashvalue %> not meaning anything.
A solution would be to embed those parts of javascript that need to access server side variables in the mojo templates - rather ugly, but less code to be written.
Another one would be to make an ajax call from your javascript files, when they are loaded, and get the values sent by the server - more elegant, but more code to be written.
Another one that I can think of, would be to move those javascript files under a folder that gets processed by Mojolicious and include them parameterized - in your html.ep file that needs that js file, do :
<script type="text/javascript" src="http://example.com/url/served/by/mojo/?param1=<% $stashvalue %>¶m2=<% $stashvalue2 %>"></script>
And, in the controller that responds to /url/served/by/mojo/, render that js file, with the params replaced by the ones from the query. As an alternative, you could store/receive those params also on the session
As usually in Perl, there is more than one way to do it.
What I typically do is encapsulate most of my javascript in function calls or objects in pure javascript files. Then in my templates I include those pure javascript files and use the api that I built in those files from the template, interpolating the server-side variables into the arguments of the functions. You can peruse the code for Galileo to see several examples.
For example, see how this template passes stash values to this pure javascript file's functionality.
I'm finishing my project right now and see that I've got a lot of javascript code on each page. It's not included as a ".js" file, but rather coded in the page itself. I figured it's a bad idea, so now I'm trying to put them all in one .js file and include it in each page.
The only problem I'm facing now is this: Some functions are only called on certain pages and are depending on the inclusion of jquery plugins. Not all pages needs the plugin however.
Example:
On the home page I need to chain the Country dropbox with the City dropbox, thus I need the jchained.js plugin for jquery. The code I need to use is:
$(function(){
$("#city").chained("#country");
});
When I add this function to the .js file, and I open a page where I don't need to chain the dropboxes I get logically an error:
TypeError: $("#city").chained is not a function
So if I understand this correctly, in order to use a .js file with all my different functions for different plugins, I need to include all the plugins to all the pages?
Thanks for your ideas & help.
Personally, I don't think you should worry about including alot of .js files, that's part of web development. Another option, albeit slightly more tedious, is you can make a check for the function to exist (if the plugin .js has been included) and then call it if it does:
if(typeof yourFunctionName == 'function') {
yourFunctionName();
}
It completely depends on how the code is structured or how complex the current code is.
An immediate solution will be,
Give an id to each page (may be on body tag).
Put all the code in a single external JavaScript file.
Execute the code meant for that particular page only if it has required id on that body element.
Something like :
if ( $('body').attr('id') == "home" ) {
/* Add home page JS here */
}
You can try this.
Correct me if I am wrong.
For a Rails 3.1 app, some of my site wide JavaScript is only included when certain real time, instance specific conditions are met. This means I can't put it in the new asset pipeline's application.js because that isn't parsed by erb for embedded Ruby within the current context. Basically, I'm including keyboard shortcuts, based on the current_user that is logged in.
My question: where should this JavaScript with embedded code go, so that the embedded Ruby is still parsed for each page access with the proper context (i.e. current, logged in user)?
The answer seems to just be to put it in the application.html.erb layout view at the bottom, but this seams like I'm hiding away javascript code in a non intuitive location.
I've tried creating an application2.js.erb file, but then I got errors about undefined variables, which I think might be because the asset engine only parses this file once before the output is cached and the scope isn't correct yet for things like current_user.
So, using application.html.erb works just fine here, and this isn't so much a question of how to get it to work functionally. Instead, I'm wondering if there's a more elegant way to incorporate the asset pipeline model here with my requirements and still keep most of my JavaScript in the assets/javascripts directory.
You should try to create app/assets/javascripts/application2.js.erb (or whatever better name you come up with)
And then put something like this in your app/assets/javascripts/application.js:
//= require application2
And then you can have
<%= javascript_include_tag 'application2' %>
wherever you want - for example in your application.html.erb.
Btw, if you want to customize what's included on a per-view basis you might find content_for useful. Check out this screencast
Ok, about unobtrusive js. It will be just a cocept (HAML):
In your view somewhere
# hotkeys are "Ctrl+C", "Ctrl+A"
-current_user.hotkeys.each do |hotkey|
%hotkey{ "data-key" => hotkey.key, "data-behavior" => hotkey.fn }
Then in your application.js
$(document).ready(function(){
if($("hotkey").length > 0){
$("hotkey").each{function(this){
key = $(this).data("key");
fn = $(this).data("behavior");
$(document).bind('keydown', key, fn);
}}
}
})
So just the same JS will extract from HTML hotkeys data and then bind it.
As some people have pointed out, the two options are:
Put your javascript inside the view (and as you say, this doesn't feel quite right).
Put it in a javascript file. Make a conditional inside your view that includes this javascript file if certain conditions are met.
If you need to pass more instance variables from the controller to your javascript, this gem called gon can make your life easier.
This allows you to use the default asset pipeline using the following javascript:
if(gon.conditional){
//your embedded js code here
}
If you want to know more about this gem, checkout this railcast where everything gets explained.
I'd like to split my views in Grails into 2 files a .gsp file and a .js file so that I get a cleaner Javascript separation from my views. So here's an example:
views/index.gsp
views/index.js
views/home/index.jsp
views/home/index.js
But when I simply add the index.js script reference like this:
<script src="index.js" type="text/javascript"></script>
all I get is a 404.
Does anyone knows how to deal with this?
A great benefit would be to have the ability to use view data inside the index.js file to produce the desired content.
Matthias.
Actually, it should be perfectly possible to serve a JS file (or any other file type) as a GSP from your grails-app/views/ directory. The only thing you have to do, is define a suitable URL mapping for those GSPs, e.g.:
"/javascript/home/index"(view:'/home/index.js')
With this URL mapping, you can put your JS code into grails-app/views/home/index.js.gsp (note the trailing .gsp) and you can use any grails tags in your JS source. To ensure that your JS is delivered with the correct content type, you may want to place
<%# page contentType="text/javascript"%>
at the beginning of your GSP.
Unfortunately, the createLink tag doesn't support link rewriting to views, but it should be easy to write your own tag to create those links.
Anyways, keep in mind that this won't have a very positive impact on your app's performance. It's usually better to have static JS files (and also serve them as static resources) while passing dynamic stuff as parameters to JS functions for example. This will also keep you from some headaches wrt. caching etc.
The idea is good, but Grails has this directory structure for a reason. The view folder is intended for a certain artifact type (views)..
You could clone your view folder structure under web-inf, but that gives you more work as I guess the idea behind this is to keep related files close together for convenience reasons.
Even though I'm not to excited about storing Javascript together with the view I loved Robert's idea of hooking into the build process by using build events to copy javascript sources into the right directory! If you decide to go down that road you might as well compress the sources while you're at it. ShrinkSafe is popular library.
I don't think you are allowed to access js inside views/
if you need to do that ... here is the trick
create your js and rename it with myjs.gsp (use "")
iniside _myjs.gsp type you js
... write down you js in here ...
inside you gsp (for example: index.gsp, view.gsp, etc)
type this tag to upload you js
Update 2:
Grails offer the possibility of hooking into the build lifecycle using custom events.
An event handler can be written which synchronises all JavaScript files under grails-app/views with the target folder of web-app/js.
Place the custom code in $PROJECT/scripts/Events.groovy. The PackagingEnd is a good target for the invocation, since it happens right after web.xml is generated.
eventPackagingEnd = { ->
// for each js file under grails-app/views move to web-app/js
}
Update
If you'd like the JavaScript files simply 'meshed' together, you can do that using symlinks, e.g.:
grails-app/views/view1/index.js -> webapp/js/view1/index.js
As far as I know, there is no way of forcing grails to directly serve content which is outside of web-app.
Alternatively, you can inline your JavaScript, but that can have performance implications.
JavaScript files belong under web-app/js.
Then you can reference them using <g:javascript src="index.js" />.
There are essentially 2 places to define JavaScript functions in Grails, directly in a element on the GSP, and within a separate javascript source file under /web-app/js (for example, application.js). We have defined a commonly reused javascript function within application.js, but we also need to be able to generate parts of the function dynamically using groovy code. Unfortunately, ${some groovy code} does not appear to be processed within separate javascript source files.
Is the only way to do this by defining the javascript function within a script tag on a GSP page, or is there a more general solution? Obviously we could define the javascript function in a script tag within a template GSP file which would be reused, but there is a lot of push to keep our javascript functions defined all together in one place (i.e. the external javascript source file). This has performance benefits as well (the javascript source files are usually just downloaded once by each client's browser, instead of reloading the same javascript functions within the source of every html page they visit). I have toyed around with the idea of breaking the function up into static and dynamic pieces, putting the static ones in the external source and putting the dynamic ones in the template GSP, then gluing them together, but this seems like an unnecessary hack.
Any ideas?
(edit: It may sound like the idea of dynamically generating parts of a JavaScript function, which is then downloaded once and used over and over again by the client, would be a bad idea. However, the piece which is "dynamic" only changes perhaps once a week or month, and then only very slightly. Mostly we just want this piece generated off the database, even if only once, instead of hard coded.)
An easy solution to keep your JavaScript unobtrusive is to create a JavaScriptController and map its actions "/js/*" by adding this to your UrlMappings.groovy file:
"/js/$action"{
controller = "javascript"
}
then just create an action for each dynamic JS file you want, include in in your layout <HEAD>, and presto, you've got a JS file that you can insert Grails snippets into! :)
Note: I've found that there's currently a bug in Grails that doesn't map file extensions to content-types properly, so you'll need to include <%# page contentType="text/javascript; UTF-8" %> at the top of your view files.
This is a great solution. I would like to offer a suggestion to use somthing other then a mapping of "/js/$action" because this is no longer going to allow you to access you javascript files in /web-app/js/. All your javascript files would have to be moved to a the directory your controller would point to.
I would use something like
"/dynjs/$action"
This way you still can point to files in the /web-app/js/ files with out conflict and enjoy the benifits of gsp tags in javascript files
Please correct me if I'm wrong.
Or this... have a tag/service/dynamic method that lets tags write out their JS+CSS+whatever else, to a "cache" which is used to build the JS+CSS resources by a different controller.
Full concept here: [http://www.anyware.co.uk/2005/2009/01/19/an-idea-to-give-grails-tags-esp/][1]
If you want to use models created by the controller (that rendered HTML page which reference the Javascript in which you intend to use groovy code) in the Javascript code, then you can use this technique:
This technique does not need to change URL mappings and does not require you to create extra controller.
In your view GSP add javascript as follows:
<script type="text/javascript">
<g:render template="/javascript/yourJavascriptFile"/>
</script>
In views folder create a "javascript" folder. And create a file named:
_yourJavascriptFile.gsp
You can not only use all the GSP code in your _yourJavascriptFile.gsp file, but you can also use all the models created in your controller (that is rendering the view).
NOTE: There is nothing special about javascript folder. You can name it anything you want OR use an existing view folder. This is just a matter of organizing and identifying your HTML spitting GSP from Javascript spitting GSPs. Alternatively, you can use some naming conventions like: _something.js.gsp etc.
Name your scripts like this
/wherever/the/js/files/are/thescript.js.gsp
The gsp code inside will be rendered correctly by grails. This works, but I have no idea if it's considered a Good Idea or not.
There is another way - pass in the generated code into a function that expects closures. Those closures is generated by the program of course. The generated code is of course inlined/script-tagged in the gsp page.
it may or may not work depending on the nature of the code being generated. But i suspect it will work, and if it doesnt, minor tweaking to the coding style of your javascript will definitely make it work. Though, if these 'generated' code doesnt change much, this quite overkill imo.