I have a rails application which is serving up a number of views which include specific data from the database to be manipulated by some javascript, specifically for use with Chartjs.
Options I have explored are:
Use an ajax call to pull the data off the server. I don't like this one because the user isn't changing any data, I have access to all the data when I render the page, there ought to be a better way.
Use <script> tags in the view ERB and simply have ERB interpolate the variable. This seems a little hacky and definitely doesn't take advantage of all my beautiful haml. But it is easy and allows me to put javascript variables on the page which can then be picked up after the document.onload.
Gon. Great gem and very easy. But should this really require a gem?
I feel as though the solution should involve the asset pipeline and some sort of rails interpolation, but I can't seem to make that work very cleanly. What is the elegant rails 4 way for solving this?
Thanks everyone!
There are many ways to pass data from server-side to javascript, I'll list two of the most common (that don't use a gem or other external tools) below:
Use the second method you described, and interpolate ERB tags inside the javsacript. This is ugly, hacky and is not even close to best practice.
Use data attributes. You can modify your HAML to include additional attributes, like "data-my-variable", and access it via javascript (example using jQuery: $(element).data("my-variable")).
The data attribute method is, in my opinion, the cleanest way of doing this, it's exactly the purpose of data attributes.
Now, I don't know HAML, I've only worked with ERB before, but I found this answer by Mandeep, which explains how you can add data attributes to your HAML:
%a{"data-my-variable" => "my data", href: "#"}
OR
%a{href: "#", :data => {:my_variable => "my data"}}
EDIT: Sorry, I found your question along with other, newer, questions, I just assumed it was recent, and not from over a year ago :)
Related
Is there a way to implement if statements inside :javascript filter with HAML in Rails?
I've tried various ways such as
:javascript
$(function(){
- if #booth.greeting_video?
= $('#greeting_video').modal();
But they do not seem to be working at all.
Is there a clean way of implementing this?
The filters in HAML are processed separately from the other code, and the only thing allowed here is the #{} method, which just inserts a Ruby value.
Theoretically you can insert your condition there, and return different values depending on it. And I can't guess what you're trying to do with = $('#greeting_video').modal(); - it looks like javascript, but why is there = sign before, making it look like ruby insertion?
And, it also needs to be said, it's not really a good idea to mix up back-end and fron-end so much. The js variable can be set here, and somewhere in another file the modal would be rendered or not, depending on that variable (just another way suggestion)
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.
Update: after another day of digging
into this issue, I have found that the
current jQuery template lib provides
no way to do this. this article
describes a good approach.
I would still like to hear of any
additional thoughts on doing this. The
article linked above requires that the
returned string of templates be
inserted into the DOM. Seems as though
leaving the DOM out of this would be
ideal, and less overhead on the
browser. Imagine a large page, with
multiple composite templates that may
not get used. Although, maybe because
the templates are wrapped in a script
tag, there is only a single DOM item per template? Come on, let's
hear some thoughts...
Using jQuery template libs, what's the best way to combine multiple, related, relatively small templates together? Do you need a single <script> tag for each individual template? What about in the case of dynamically pulling these templates via AJAX? Can I combine these templates somehow?
Consider the following:
<script id="movieTemplate" type="text/x-jquery-tmpl">
{{tmpl "#titleTemplate"}}
<tr class="detail"><td>Director: ${Director}</td></tr>
</script>
<script id="titleTemplate" type="text/x-jquery-tmpl">
<tr class="title"><td>${Name}</td></tr>
</script>
Now because these two templates are very closely related (and one depends on the other) it would make sense to consolidate these into a single AJAX call, and get them both at once. I have a few ideas, but I'd like to know if there is common/best way to do this? Currently I pull in a chunk of HTML, and then do a .find() to get the specific peice of HTML for a template... e.g.:
var templatePackage = fancyAjaxCalltoGetTemplates();
"templatePackage" might then look like this:
<div id="templatePkg">
<div id="movieTemplate">
{{tmpl "#titleTemplate"}}
<tr class="detail"><td>Director: ${Director}</td></tr>
</div>
<div id="titleTemplate">
<tr class="title"><td>${Name}</td></tr>
</div>
</div>
I could then do:
var titleTemplate = jQuery.template('titleTemplate', $(templatePackage).find('#titleTemplate') );
and
var movieTemplate = jQuery.template('movieTemplate', $(templatePackage).find('#movieTemplate') );
...let me know what you think... what would you do?
I like the referenced article in your update, except the assumption that you can't cache templates unless you insert them into the DOM. From the jQuery.tmpl documentation,
"To cache the template when using markup that is obtained from a string (rather than from inline markup in the page), use $.template( name, markup ) to create a named template for reuse. See jQuery.template()."
Using this, we can build a javascript template management system that allows us to load as many templates at a time as we need while keeping the DOM clean. On the client, keep a hash of template objects by name. You can use your favorite object based javascript pattern here, but I would think the structure could be like this:
templates[templateName] = {
templateMarkup: markupFromServer,
loadedAt: Date.now(),
compiledTemplateFunction: jQuery.template( templateName, markupFromServer )
}
Then use the templates to generate HTML like this:
templates['unique-name'].compiledTemplateFunction(inputData)
Then, build an unload mechanism to free up memory:
function unload(templateName) {
delete templates[templateName];
delete jquery.template[templateName];
}
Most importantly, you now have a method of storing multiple templates so you can make requests like: $.get('/TemplateManagement/Render', arrayOfTemplateNamesToLoad, loadManyTemplatesSuccess) to load multiple templates at a time. The only thing we need is a controller TemplateManagement that will take an array of template names as an input and return JSON that pairs a template name with its markup. There are a few ways to do this but it seems to me the most convenient is to define partial views for each template. In ASP.NET MVC 3, you can use this technique and RenderPartial to emit each template's markup into a JSON response. You can either name the partial views the same as the templates or map the names in some custom way.
OK, I read the article you reference in this post. As I see it, his way is probably one of the best ways to load up the template page(s). The only thing I don't like is the asynchronous problems that could crop up, esp. if you need to immediately do some templating before the async get returns... plus any binding issues that could happen before it returns something. In several projects I have done I use his "ancient" SSI (server side includes), but I just use something even easier like:
<% Response.WriteFile("this_page_template_file.html"); %>
You could put it anywhere where you'd place a tag. Like he says, just put in only the templates you need, or maybe include two templates: one is a "base" template with commonly-used items and the second one would have the page-specific ones with template references {{tmpl}}.
Is this even close to an answer? ;-)
[First off, great question. I love this topic]
I have no experience with the plugin "jquery-template-libs", but there is a particular lightweight javascript template plugins that are becoming almost a standard and plays very nicely with jQuery, which is probably better suited for the task than JTL, Mustache:
https://github.com/janl/mustache.js
It's got something that's called a "partial" which is essentially a way to include smaller templates within another one. Which sounds like it will help you out a lot.
On top of that there is a library called ICanHaz.js:
http://icanhazjs.com/
ICanHaz essentially extends Mustache to include built in functionality for templates and works incredibly well.
Mustache/ICanHaz allow you to add templates by variable, by a json call or by using tags. The choice is yours.
I know this is an old question but you might want to take a look at Closure-Templates. They provide the kind of functionality you're after with the added advantage of being compiled into JavaScript at compile-time instead of at run-time in each user's browser.
If you do decide to look into using them then I'd suggest using plovr for building them.
I am trying to find a way to add content to a div that is defined in my application.html.haml file from individual view files. The reason I want to do this is because I have a sidebar that is always present and is defined in the template, but I would like to have content specific to each page included in the sidebar. Would this be done best with javascript or is there some ruby on rails trick that I can use to make this easier?
I would use the content_for helper (Rails Guide) (API).
In haml, this would look something like:
(layout template)
#sidebar= yield :sidebar
(page template)
-content_for :sidebar do
...your content here
If you work with professional designers who handle the views, it may be easier over the long term to have rather repetitive view code. Some people have a hard time searching through partials and "seeing" how they all fit together. I've found it easier with those people to let them manage the whole shebang and update more than one file if they need to. Optimal? Not to us as programmers, but designers are more often used to seeing most of the HTML in one or three files rather than 20. :)
use partials as much as possible to keep your code DRY
this link helps you
http://guides.rubyonrails.org/layouts_and_rendering.html
First, 2 common (basic) approaches:
# returning from some FoosController method
respond_to do |format|
# 1. render the out a json representation
format.json { render :json => #foo }
# 2. render an RJS template, say update.js.erb
format.js { render }
end
# in update.js.erb
$('#foo').html("<%= escape_javascript(render(#foo)) %>")
These are obviously simple cases but I wanted to illustrate what I'm talking about. I believe that these are also the cases expected by the default responder in rails 3 (either the action-named default template or calling to_#{format} on the resource.)
The Issues
With 1, you have total flexibility on the view side with no worries about the template, but you have to manipulate the DOM directly via javascript. You lose access to helpers, partials, etc.
With 2, you have partials and helpers at your disposal, but you're tied to the one template (by default at least). All your views that make JS calls to FoosController use the same template, which isn't exactly flexible.
Three Other Approaches (none really satisfactory)
1.) Escape partials/helpers I need into javascript beforehand, then inserting them into the page after, using string replacement to tailor them to the results returned (subbing in name, id, etc).
2.) Put view logic in the templates. For example, looking for a particular DOM element and doing one thing if it exists, another if it does not.
3.) Put logic in the controller to render different templates. For example, in a polymorphic belongs to where update might be called for either comments/foo or posts/foo, rendering commnts/foos/update.js.erb versus posts/foos/update.js.erb.
I've used all of these (and probably others I'm not thinking of). Often in the same app, which leads to confusing code. Are there best practices for this sort of thing? It seems like a common enough use-case that you'd want to call controllers via Ajax actions from different views and expect different things to happen (without having to do tedious things like escaping and string-replacing partials and helpers client side).
Any thoughts?
The best practice is to have your Web UI use the RESTful API. That is, get and put resources in JSON format using JavaScript running in the client, just as a third party might get and put resources in JSON format using RestClient. That means you don't have .rjs or .js.erb templates on the server - you might instead have Mustache or Handlebars or jQuery templates, plus the glue JavaScript, embedded in (or statically linked to from) the HTML page originally delivered to the Web client.
Of course, the quick-and-dirty approach of using remote => true, and going to the server and rendering a JavaScript code template to produce JavaScript that will be executed on the client ... is easier. And it was fairly good practice for a while. But we need JavaScript for a data format now, and we have superior capabilities today for pulling data in JSON format and rendering templates within the client.
Not sure if this work for you, but I would do the following:
Generate .js templates using ERB, these templates will be static
The templates could be mustache, jaml, jQuery Templates or something else.
Use a rake task to generate all templates and create static .js files out of them.
Include these static files using , they will be cached by the browser.
Populate the templates with json data and include the generated html.
This assumes that the templates can in fact be static, which depends on the situation at hand. But generally this should work.