I don't quite understand how helpers work in the view / controllers. I have never used them.
My specific question is: almost all of my views implement AJAX. In most of my controllers, the update.js.coffee and create.js.coffee have some form of the following code:
jQuery ->
<% if #product.errors.any? %>
error_info = '<%= j(render :partial => "shared/errors", :locals => { :record => #product }) %>'
popup error_info
<% else %>
.
.
.
where popup is a javascript function to display some element.
Is there a way to abstract this into a helper? What is the best way to do this? this code is almost exactly the same in every case, except the 2 uses of #product would of course be different depending on the model in question.
If this isn't what helpers are used for, then 1) what are they used for? and 2) what should I be using instead?
Edit: BONUS QUESTION: actually, many of my new, create, edit, and update functions are similar across models. How do you DRY this up? or do you just not worry about it?
The first step is to change the extension of your file to .js.coffee.erb. This let's the asset pipeline know that you want the file to be interpreted with ERB.
The second, optional step is to add custom helpers to Sprokets so that you can call your own methods from your Coffee script files*. Simply create a new helper module and then register it in an initializer:
Sprockets::Context.send :include, CoffeeHelper
*: The assets will not be able to access all the helpers you are used to using because the ERB is run when the assets are compiled rather than as part of an HTTP request. The normal controller/helper setup is not present.
If you want to refactor this code into a rails helper it would be done just like any other helper method, you have to have all the javascript code as a string and your rails helpers will need to return a string.
Rails helpers are there to help refactor logic out of your views so that you can keep your code logic-less as possibly and it is there to allow repeated code to be more dry.
If you find that some code is being repeated across your models, you may also look into refactoring that code into a ruby module in the lib directory and include the module into your models.
Related
Currently, I'm working on a Rails project where I use a Bootstrap-Theme (CSS-Files, JavaScript-Files and so on) which I've included as a Gem in the rails project.
The Markup in the views is dependent on this Bootstrap-Theme.
We have other Rails-Engines included in this project which also depends on the markup of this Gem for the views.
What we want to do is to develop a "Templating"-System.
Lets say we have a defined box, which has a header and a body. Then if the Bootstrap-Theme is included, it attaches the appropriate Bootstrap Markup. If another Theme is used, the appropriate markup will be attached for this Theme.
Right now, if we want to exchange the Bootstrap-Template in the future with a e.g. Zurb-Foundation-Template. Then we're lost, because we have to refactor each and every view and replace the Bootstrap-Like Markup with the Zurb one.
The first idea was to develop this templating in JavaScript which will change the CSS-Classes on document ready, but this is not capable to handle large DOM trees.
Are there any Gems available for this purposes? How can such a system be implemented in Ruby / Rails?
Thanks in advance!
I doubt you'll be able to find a gem that will give you this kind of functionality out of the box. Basically, to achieve this, you'll need to write all your views using a DSL that can be translated into either Bootstrap, Zurb, or some other framework.
This DSL could be implemented as a series of helper methods in a Rails Engine.
For example, your view could look something like this: (Assuming you use ERB as the templating engine)
<%= box do %>
<%= header 'Title of the Box' %>
<%= body do %>
<p>Box content here</p>
<% end %>
<% end %>
The definitions of these methods could look something like this: (Pseudo-code)
def box(options = {})
case EngineName.config.framework.to_sym
when :bootstrap
klass = '..' # the class you want
when :zurb
klass = '...' # the class you want
end
content_tag(:section, class: klass) { yield }
end
In a config/initializers/framework.rb file, you could configure the engine like so:
EngineName.config.framework = :bootstrap # or :zurb
There are a few pros and cons with this approach.
Pros
You can theoretically switch template frameworks without actually having to change your views.
Cons
You have to maintain a separate templating DSL in order to abstract away the framework-specific class names and such. This could be time consuming, especially when the frameworks require very different markup to achieve the same result. If you didn't foresee the future markup, you'd end up needing to change your views to support it anyway.
Personally, I think you should consider just going ahead with one framework and not worry about this. It's unlikely that your template DSL would be future-proof, and so creating one could just be unnecessary effort.
I'd like to use a little bit of simple AJAX in my Rails application. The main problem I have is that inside a script I need to specify the URL where the request will go, which in most cases is a path to some Rails controller action. Anywhere else I'm advised not to write paths explicitly by hand (i.e. not do ugly stuff like '/my_resources/' + resource_id) but use helpers like my_resource_path instead.
But this approach doesn't work well with javascript, since these helpers don't work inside assets/javascripts. I can think of some ugly ways I can bypass the problem, currently I've implemented an extremely ugly workaround which is basically putting something like this inside my view:
<%= javascript_tag "onSubmitQuotePage('#{j escape_javascript(autocomplete_authors_url(''))}');"%>
But I can't imagine Rails developers didn't think of some prettier solution, some right way of doing AJAX.
I can't say I like the way I do this so very happy to hear better ways of doing this.
But I either include the path in a data attribute on a relevant DOM element, or for some static routes I include a <script> block in the layout file that contains relevant paths.
<script>
(function() {
"use strict";
window.myapp || {};
window.myapp.new_order_path = '<%= new_order_path %>';
window.myapp.orders_path = '<%= orders_path %>';
...
}());
</script>
It's by no means a pretty solution, but the cases where I need a route in my JS are rather rare and that way I can then use myapp.new_order_path in my JS when I need it.
Just to add my two cents here: you can use Rails URI helpers to generate some sort of URI templates. E.g., if you have this route defined:
edit_user GET /users/:id/edit(.:format) users#edit
And you call it like edit_user_path(':user_id:'), it will return /users/:user_id:/edit. This way you can generate URI templates to be compiled by javascript. As #Tigraine said, you can include this in a data attribute of some top element, and use it from the client side.
It comes really in situations where you need to generate URIs for AJAX fetched resources: you just serve the resource id and let the javascript compile the URI from the template with a simple string.replace(':user_id:', user_id).
I understand the reasoning behind the rails 3.1 asset pipeline: we compile all the JS in a neat, cacheable file to improve performance. Great we want that.
However, loading everything also means we've got to be very careful that we not use a certain ID or class on multiple pages, if we have some JS attached to it. Or else, the JS will fire on both pages, since its always loaded.
Now, what we want to achieve is the following:
* we want to keep everything in a single JS file (we know how we can load files separately, just don't want that)
* we want to namespace the JS in each controller_name.js so it's only loaded when the appropriate namespace is initialized
* we want to initialize the appropriate namespace by reading the current controller from say a data-attribute on the <body> tag of our layout
The problem is: we don't have a good idea how to implement in JS. Particularly, how should we namespace the JS and then dynamically initialize it based on the contents of a HTML tag..
Any help is greatly appreciated!
Here is a way to namespace everything on a controller/action level
http://www.viget.com/inspire/extending-paul-irishs-comprehensive-dom-ready-execution/
above was inspired by http://paulirish.com/2009/markup-based-unobtrusive-comprehensive-dom-ready-execution/
You basically declare you body as such
<body data-controller="<%= controller_name %>" data-action="<%= action_name %>">
And then these methods are called (which each have a series of methods -- so if you need something on every page, it's in common/init. Or on all users actions, that's on users/init. Or only the users show page? that's users/show.
SITENAME.common.init();
SITENAME.users.init();
SITENAME.users.show();
I've used this and it works very very well.
JsSpace.on('users', {
index: function(){
console.log('index action of users controller');
}
});
that pattern implemented by render controller and action into body attribute then
fetch them and execute the match function.
js-namespace-rails
The new Rails 3.1 asset pipeline is really nice, but since all CoffeeScript (or JavaScript) files get melded down into a single file that is included in every page, it raises this question:
How do I limit the execution of my script to a particular controller or action? Is there a way within my CoffeeScript to know which controller and action was used during the request so that I can put conditional statements in my script?
Or am I approaching this the wrong way altogether?
Trevor Burnham answers this question nicely here: How do I associate a CoffeeScript file with a view?
He says:
There are two common approaches:
Make behavior conditional on the presence of a particular element. For
instance, code to run a signup sheet
should be prefaced with something like
if $('#signup').length > 0
Make behavior conditional on a class on the body element. You can
set the body class using ERB. This is
often desirable for stylesheets as
well. The code would be something like
if $('body').hasClass 'user'
And if you're interested in CoffeeScript, Trevor is working on a book that looks to be very good: http://pragprog.com/titles/tbcoffee/coffeescript
One way to restrict coffeescript to a particular view is to make a custom sprockets file for the javascript in question, similar in format to application.js. Say you call it extras.js.
//= require my_code.js.coffee
Then use javascript_include_tag "extras" to include that code in the views you want, either by making a custom layout for those views, or by using content_for()
BTW, your question stated that the rails pipeline forces you to put all your js assets in one file. That's not true. That's efficient often to avoid multiple round trips, but you can have multiple sprocket files.
Why not to put the javascript for the particular controller as a view on this controller (as they correspond there if are so specific)?
If they are general enaugh you can mark your view with classes, ids or data (html5) and make your javascript look for that (so you can reuse your code).
what i normally do is to have a yield :js under the javascripts in my layout and when I need a specific script it, I load it directly from my view with:
content_for :js do
javascript_include_tag "myscript"
end
If you are using the gon gem for your vars in coffee script you can use this pattern:
Put a flag for every action in the controller:
def index
#gps_coords = GpsCoord.all
# Flag for the CoffeeScript to decide which part to run
gon.index = true;
end
def show
#gps_coord = GpsCoord.find(params[:id])
gon.lat = #gps_coord.latitude
gon.lon = #gps_coord.longitude
gon.show = true;
end
In the correlating coffee script use those flags to distiguish between the both actions:
# index action?
if gon.index?
first_coord = gon.gps_coords[0]
map.setView([first_coord.latitude, first_coord.longitude], 15);
# show action?
if gon.show?
map.setView([gon.lat, gon.lon], 15);
I'm using a modular system of JavaScript files when working in Rails - basically each view will have its own module in a .js file. My issue comes when I need a dynamic, Rails generated string within my JavaScript, for example translation strings and URLs.
Translations are nicely solved using babilu but I'm still stuck on the generation of URLs. I could write something that looked at the routes in the application and generate JavaScript methods which I could pass stuff like IDs of objects.
An alternative would be to pass in the already-generated URL to any functions I was calling, which sounds messy but could be the most flexible alternative.
I don't know that there's any truly pleasing way to do this, but one possibility is to have your server-side code write a small <script> block into the page to declare some variables that your packaged Javascript can discover and use.
<script>
var pageGlobals = {
interestingURL: <% ... url %>,
...
};
</script>
I've done this to keep track of things like image subdirectories that are determined by customer "syndicate" affiliation. The Javascript code just knows that whenever it needs that for a URL, it can just go look in a global object and pick out a standardized variable.
In my experience there tend to be only a small number of such things to communicate to the canned Javascript, so the <script> block tends not to get out of hand. I've buried mine in a page template so I don't even have to think about it with new pages.
Oldish question, but here's another way. The HTML 5 spec allows for custom data- attributes.
In your view:
<button id="myButton" data-url="<%= my_resource_path %>">Click me</button>
Then in your packaged js:
var myurl = $("#myButton").data("url");
See here also.
I don't like this either. The ideal solution to me would be javascript templates. Imagine in the .js file you could do:
var users_path = '<%= users_path %>';
But that would mean the .js files would have to be regenerated in every request (well, one could use caching just like with rails html).
Anyway, what you can also do is put the dynamic stuff in data- attributes. So you can do for example
<%= select_tag :select_something, select_options, 'data-url' => users_url %>
And then read that attribute out in the javascript file. I prefer this over the solution suggested by Pointy.
Edit: Well actually someone implemented the dynamic .js file idea. Seems straight forward enough, just create a javascripts controller and link to its actions via javascript_include_tag: dynamic javascript files