On most tutorials out there, people tell you to create a js.erb template for every action you want to respond with javascript, which leads to having both an html.erb and a js.erb, for every action I want to work with AJAX, like this:
Is this right? Am I doing something wrong? Because it looks awful to me, there will be at least 20 files in each view folder, by default.
I think you are doing right. You are using Rails’ AJAX helper and it is a good practice. Some advantages of this comparing to the normal way of using AJAX:
JS code is shorter and cleaner, we do not need to write some repeated boring code such as $("form#id").on("submit", function(){}). We just need to write the main code to handle the response data.
Unobtrusive JavaScript: JS code is rendered from server side. It is not shown along with the html.
I think splitting to js.erb files actually makes the code more manageable. It is personal thought though.
I don't know how complex your project is so I am not sure but maybe you can refine to have less partial files. For example, I noticed that you have both delete and destroy actions. Index, new and edit views may not need the partial files. It seems that you also handle Json requests. It also makes the view folder bigger.
Yeah, it looks really awful to me too. But if you're responding to every method with javascript you'll have to create js.erb templates for each them.
Another approach would be, you'd want to respond with json instead of script. Where all your ajax code will remain in the client side javascript, and you'll be responded back with json data.
For eg. lets get data for an particular area
$.ajax({
url: "/areas/23",
dataType: 'json',
method: 'get',
success: function(response){
//OPTION 1
//response will have all the data
//handle the value from the response object in displaying
//OPTION 2
//If you set dataType: 'html' you can receive html partial from server and make use of it directly
$("#show-area").html(response); //response will have content of _show.html.erb
},
error: function(error){
console.log(error); //Print errors if it breaks
}
});
#Controller
def show
respond_to do |format|
#OPTIONS 1
format.json { render json: #area.as_json }
#Or have a json.jbuilder partial if you want to send data selectively, ;) There is no escape from partials/templates
#OPTION 2
format.html { render partial: "areas/show" } # will render _show.html.erb
end
end
That being said, I think it finally comes to personal preference. And your preferences will vary upon different scenarios. You can pick any one of those based on the case. Let me know if it helped.
Related
The last few days I've spent trying to understand different facets of AJAX on rails. After reading some introductions I've managed to get an idea of the rails built-in UJS feature. An example from a small toy app I wrote and where I want to introduce some AJAX-capability...
The controller action looks as follows
class ExpenseListsController < ApplicationController
before_action :require_authentication
...
def create
#expense_list = ExpenseList.new(expense_list_params)
if #expense_list.save
respond_to do |format|
format.html do
flash[:success] = 'Created!'
render :show
end
format.js
end
else
respond_to do |format|
format.html do
#errors = #expense_list.errors
flash[:danger] = 'Something went wrong!'
render :new
end
format.js
end
end
end
...
end
In my view I call the action via the remote: true option
The respective create.js.erb looks like this
var form_field = $('.expense_lists_form');
var expenseLists = $('#expense-lists');
expenseLists.append("<%= j render #expense_list %>");
form_field.slideUp(200);
The template for #expense_list looks like this
.col-xs-12.col-lg-3.col-md-4{id: "expense_list_#{expense_list.id}"}
.panel.panel-default
.panel-heading
= link_to expense_list.name, expense_list_path(expense_list)
.panel-body
.links
= link_to 'Modify list', edit_expense_list_path(expense_list), remote: true
= link_to 'Delete list', expense_list_path(expense_list), method: 'delete', remote: true
.description
%p
- if expense_list.description.present?
= expense_list.description
- else
%i
No description supplied, add one
=link_to 'here', edit_expense_list_path(expense_list)
.email-notification.text-muted
(Email notifications enabled)
.panel-footer
= "Expenses in #{current_month_name}:"
%b
= "#{expense_list.sum_of_exp_in_month(current_month, current_year)}€"
= "(#{expense_list.euros_left_in_month(current_month, current_year)}€ left)" if expense_list.budget_in_euro
It works but to me this idea seems to have some down-sides:
Bloats my file structure by having extra *.js.erb files
Distorts the physical separation of JS and the rest of the codebase
As I use HAML it introduces a new style of coding (ERB)
Now I have two questions:
Every tutorial (I've seen so far) seems to promote this kind of solution for handling AJAX responses in Rails: Why? When I check the code of other, larger rails projects (e.g. Diaspora) I do not seem to find them do it this way - most of them seem to handle it inside plain JS/jQuery via $.ajax({ ... }). So what would be the major advantages of the rails-internal UJS approach?
If the rails UJS-way is preferrable for some reason: How do you organise your code? Create extra directories for the *.js.erb-files?
What would be a good practice to transfer all this stuff to plain javascript files located in my /app/assets/javascript directory and handle the AJAX requests within jQuery there? How would my controller response have to look like in order to respond with the proper portion of HTML to update the DOM with via JS? In other words: How can I respond with a partial that I can handle in Plain Javascript/jQuery?
Thanks in advance!
Andi
So what would be the major advantages of the rails-internal UJS approach?
js.erb is a form of a poor mans Single Page Architecture (SPA).
It makes it just easy enough to return .js responses from your controller which modify the current page and lets you use the rails helpers for templating so that you don't have to use a client side templating system such as handlebars.
Note that this is not really internal to rails. jQuery UJS simple uses the fact that rails can return multiple formats of a resource. You could potentially use this with any MVC frameworks that can serve javascript.
The main advantage is that it is very approachable.
Its just enough for classic syncronous apps that want a few sprinkles of ajax here and there.
It gives developers who think jQuery.load and script tags everywhere is the best thing since sliced bread just enough rope to hang themselves with.
The cons
Violates REST as js.erb views are usually used as procedures to manipulate the current page.
the javascript in a js.erb view is not minified by the assets pipeline as it served per request.
It leads to horrible architecture decisions.
Whats the alternative?
Long before jquery-ujs entered the scene we had already figured out that the best way to do ajax requests is JSON.
So if you want to send a form asynchronously you would do:
$(document).on('submit', '.ajax-form', function(e){
e.preventDefault();
var $form = $(this);
var promise = $.ajax($form.attr('action'), {
accepts: { json: 'application/json' },
data: $form.serialize(),
context: $form,
method: $form.attr('method')
});
promise.done(function(response){
// handle the response
});
});
This way the javascript logic can be concatenated into a single file, and tested separately in javascript testing tools.
Your backend server just responds with simple data and does not concern itself with what the client does with it.
However this does require you to setup some sort of templating on the client side to handle converting JSON to HTML and you need to setup stuff like data bindings to have your form display errors. This leads to code duplication.
This is where SPA frameworks like Ember and Angular come into the picture which do all the templating and rendering in the client.
How can I respond with a partial that I can handle in Plain Javascript/jQuery?
You could create add additional formats which your controller responds to. You could for example register a "text/html-partial" mime type.
Or create additional routes or even use a query param (shudder).
However this is less than ideal for the exact same reason as js.erb - it leads to a crappy API as your controllers will become process instead of resource oriented. You will end up creating ridiculous controller actions just to pass html fragments back to the client.
I have a normal rails app website, but on some pages I need to use AJAX.
I already have some working AJAX Javascript code using jQuery, but so far I haven't used any rails helper to do that, writing strings corresponding to paths manually.
But is there a more convenient way to do it in javascript ? Suppose I have a javascript function which takes an ID as argument, and must call an AJAX action. So far I've been doing it this way
var url = "/tags/tagID"
function getTag(tag_id){
$.get(url.replace("tagID", tag_id) +'.json')
.fail(function(data){
alert('Oops error !');
})
.success(function( data ) {blabla ] )
}
Is it possible to rename the .js to .js.erb and use path helpers ? So I could get rid of this url variable and write
routes.rb
resources :tags
tags.js.erb
$.get(tag_path("tagID").replace("tagID", tag_id)....
Or is there a more convenient way to do this ? I only need very little AJAX, so I don't want to use a frontend framework (Angular, etc.), just jQuery
EDIT My scenario
A user searches for a given tag thanks to an autocomplete searchbar. This searchbar will return the ID somehow.
The user can select several tags this way, and their IDs will be stored in an array. Now, upon clicking a button, I want to send a query to a non-RESTful (with the ID array as parameter) controller action via AJAX. For now I will focus on sending one item at a time (so just one ID string), for it is easier/more reactive.
This action is actually going to look in my models for projects and ingeneers that possess this tag, and return a JSON with formatted results.
Yes, you can use *.js.erb to use Rails helpers. Rails provides some handy helpers to work with Ajax. Normally with rails you can use them by using the the tag remote: true.
In your case something like
<%= link_to 'Tags', tags_path(<tag.id>), remote: true %> (roughly),
Read more about using Rails helpers with Ajax here, and this explains it nicely.
Update
Rails is using CSRF token to validate requests (except GET), so if you are going to use pure HTML/JavaScript, you want to add the token to your request. Have a look at this post on the same.
I agree there is no out-of-the-box way of doing that, but there are few workarounds.
Ok, this could seem a little bit confusing, but the idea is next:
Normal PHP page which receives POST calls, and considering those vars acts in some or other ways. No problem, normal thing.
Some of the aspects which are loaded in this page (info blocks which get some big data from database), independently of POST vars and whatever, take long time to load, so I've decided to dynamically load them, instead of loading everything on loading page time. This means that in the beginning page was rendering and taking so long and right now, it renders fasts and shows some "loading..." blocks with spinning icon on them. Those blocks are basically "$.ajax" calls via POST, once $(document).ready();. Ok? No probe here too. All ok.
And now the problem: some of those blocks shows one or other info accordingly to $_POST received vars. Once this wasn't AJAX, everything worked right. But now, the received from server processed page (which already used the POST vars) to the client, makes new post calls to particular scripts and those scripts doesn't have the PHP POST needed vars.
Obviously you could say do something like:
$.ajax{
...
data: <?php ..............?>
...
}
but I can't, as this is a JS file, none PHP with embedded JS on it.
So the question is:
What can I do?
Which would be a good implementation for this particular solution?
It is not a design fault, as it works nice if it doesn't uses ajax. This is not a previous ajax call that can be made on request call. It is basically loading some page parts dynamically on serving time!
So what?
Thank everyone so much, and I hope you can help me whit it!
PS: I've came up with an idea, and it is basically, before load the main JS file, to create a petite snippet which basically transforms $_POST array to a JS object with all the bars in it. But even being it useful and probably great, I guess it basically fucks up security and obfuscation a lot!
There are numerous options. As you mention, you can pass the data to the client via inline js or dsata attributes, then post the data back in the ajax calls.
This will work fine, but if you have security concerns, then the alternative is to persist the data serverside, and change the php scripts retrieval method:
//main.php - the file that initially loads:
session_start();
$_SESSION['post']=$_POST;
//rest of script
//sidebar.php - the file called by ajax
session_start();
if(!$_POST){
if(isset($_SESSION['post']){
$_POST = $_SESSION['post'];
}
}
//rest of script
This is what I've finally came about:
Changed the "main.js" file to "main.php" file.
On header, changed the to require_once ".../js/main.php"
Now you have all the code block on header, but somehow I guess it's exactly the same?
Now I can put PHP code on """JS""" main file. And right now I can do something like:
var data = [];
//Controlled parameters which sure I'll have and always will add manually.
data.push({ name: "ajax", value: action }); //add more parameters...
data.push({ name: "section", value: section });
//Those "others" POST parameters which can vary depending on the request call.
<?php foreach ($_POST as $k=>$v){ ?>
data.push({ name: "<?php echo $k;?>", value: "<?php echo $v;?>" });
<?php } ?>
...
$.ajax({
type: "POST",
url: "?a="+action,
//data: { ajax: action, section: section },
data: $.param(data),
timeout: 8000 //8 seconds max.
}).done(function( data ) {
...
And that's it!
I've started by creating an object in JS on every view which played with this concept of forwarding POST data. But it was "unmaintainable".
Then I thought that could be good to create a JS snipet after main.js inclusion on header precisely with this "object creation POST data", as all the calls are going to be made on $(document).ready(); time, so sure it will be accessible. You know: if it exists use it if not... blah!
But the I thought that it is not necessary to create a POST object in JS all the time, as long as sometimes probably big data is going to be on server-side, so do not want to put it on client to. You delivery this "post" thing to the object and with time, you forget on this and start to put things on POST no-one should see and it happens that you have'em absolutely visible on JS!
So it seem that your proposed options (SESSIONING the POST via "post" var on session array or by ID screening) should be the most secure in this idea of reusing the info only on server side, but I've found it somehow... "tedious to mantain" too.
So I've finally came up with this idea. And as this is only happening on load time AJAX loaded blocks, this JS data is going to happen on very concret situations and parameters are going to be pretty small. Nothing to worry about I guess.
So questions come to my mind:
You like it? Is a good implementation or you think its a better approach to use the "session" thing?
Security compromising?
To add a script on head with src="..." so dinamically add the JS file to webpage, is the same in terms of time and used resources to add the code directly to head tag (which happens when you do a require_once for a PHP file which contains dinamized JS code)?
I'm running rails 4.0.4. I have a form I'm using a partial in to add a subset of fields. The user has the option to remove those fields and replace them with other fields once they're on the page. I'd like to be able to add those fields back in in the event that they want to undo their decision.
Render isn't available in javascript files (which seems odd for js.erb files). I've gotten as far as using the following to read the text of the file into a variable.
var master_quantity = "<%= data='';File.open("#{Rails.root}/app/views/shared/_a2a_master_quantity.html.erb","r").each_line{|x| data+=x};data %>";
Unfortunately escape_javascript doesn't appear to be available in the asests folder either, so I'm unable to use this string as it breaks the javascript.
Anyone have any suggestions on cleaning the string or alternate methods to get the partial into my javascript?
You could try,
controller = ActionController::Base.new
controller.send(:render_to_string, '/shared/a2a_master_quantity')
Whatever you pass to render_to_string above are params for that method.
You may or may not need that leading '/' for '/shared'.
If I understand your question, and the partial never changes, you won't need to get it from the server. Just hide it in CSS (such as with JQuery's $('#selector').hide() )
On the other hand, if you need Ruby to customize your partial each time the user restores it, you can send an AJAX request from the browser to your server to request a new version of the partial.
Add a line to a suitable controller action that renders your partial:
render :partial => 'a2a_master_quantity' and return if request.xhr?
Make a suitable XML HTTP Request in your JavaScript to get this partial (such as JQuery's $.ajax().)
Then use JavaScript to add it to the DOM in the appropriate place.
This is probably a really dumb question with a simple answer but...
I am working on a project where I need to use AJAX to send/receive information. I am using Ruby on Rails (which I am pretty new to), but rather than using the helper methods or the built in functionality in the 'defaults' Javascript file, I need to do this manually in my own Javascript.
I believe that ultimately I will want to send a request with a JSON object, then have the controller return another JSON object containing the requested information.
The problem is that I cannot seem to find the syntax for sending something to a specific controller method, with parameters, directly from Javascript - everything I Google ends up being tutorials for using the Rails AJAX helper methods.
Depending on the version of Rails you're using, you can do the following:
Install the jrails plugin. This will replace all the Prototype javascript in your application with jQuery. This means you now have access to all the jQuery libraries, but it won't break your existing rails helper stuff (remote_form_for, etc).
Now you can use the jQuery AJAX to make any AJAX requests you want to make. A simple example would be something like below:
//Let's get a specific post by id
$.ajax({
type: "GET",
url: "/posts/123",
success: function(data){
//put the data in the DOM
}
});
Then just add the appropriate respond_to in your controller:
PostsController < ApplicationController
def show
#post = Post.find(params[:id)
respond_to do |w|
w.json { render :json => #post.to_json }
end
end
end
if using jQuery is an option for you, jQuery.ajax will solve your problem.
[and for those who likes to say jQuery is not solution for everything, i know that. i'm just giving him an option].