I have the following HTML dropdown box on the index view of the Search controller. I want to update the view based upon the dropbox selection. I need to pass the selection to the controller somehow.
<!-- index.html rendered from Search/Index -->
<select id="search_params" name="search[params]">
<option value="tacos">tacos</option>
<option value="pizza">pizza</option>
</select>
Search Controller code takes params and spits out a message
def index
query = params[:search]
msg = "Your favorite food is #{query}! OMGWTFBBQ!?!?!?!"
respond_to do |format|
format.html
format.js
end
end
This Javascript should somehow post the dropdown box choice to the controller upon selection:
$("#search_params").change(function() {
var state = $('select#search_params :selected').val();
if(state == "") state="0";
//Send the selection to the controller Search controller somehow
// and then render a new view immediately ?
//I AM NOT GOOD WITH COMPUTER
})
return false;
});
I've looked at dozens of other Rails + JQuery examples, jquery API docs, etc. Can't find an answer to the simple question or I'm just overthinking it.
Anyways, thanks in advance for your help!
~Dan
I'm no Ruby/Rails guy but I know jQuery.
I think you want something like
$("#search_params").change(function() {
var state = $('select#search_params :selected').val();
if(state == "") {
state="0";
}
//Call jQuery AJAX
$.ajax({
url:'/path/to/your/controller/action',
type:'POST',
data:'search=' + state,
success: function(data) {
alert(data);
},
error: function() {
alert('Stuff went wrong');
}
});
return false;
});
Hope this helps, and have a read of the jQuery AJAX doc page.
I'm not really sure how you're returning your data from the controller there either? Is there a corresponding view that renders msg?
Checkout jQuery.ajax() documentation: http://api.jquery.com/jQuery.ajax/
You might need to put this at top of application.js (or this might just be a Rails 2.x requirement):
// Place your application-specific JavaScript functions and classes here
// This file is automatically included by javascript_include_tag :defaults
jQuery.ajaxSetup({
'beforeSend': function(xhr){
xhr.setRequestHeader("Accept", "text/javascript");
var token = $("meta[name='csrf-token']").attr("content");
xhr.setRequestHeader("X-CSRF-Token", token);
}
});
I set jQuery.ajax() to expect raw javascript code in the server response.
I have my normal html template render a partial. Or, all HTML is abstracted to a partial, not a template.
The javascript template will render basically do this:
$("div#some_id").html(
<%= escape_javascript(render(:partial=>'partial_name')) %>
);
Related
I am using Rails ruby 6.1.4 and ruby 2.6.7
I have a form partial that is used for both the new and edit views. There are two select drop-down form elements. The application.js code makes an Ajax call to the controller to get items to populate the 2nd drop-down based on what is selected in the 1st.
For the new view, my code works fine. But, when viewing a record in the edit view, it does not work. It seems to need an id in the path when on the edit view.
When using developer tools in my browser, the console window shows this error when on the edit view:
[XHR] GET http://localhost:3000/fitness/weights/24/exercises?muscle_group=SHOULDERS
As you can see, it wants the id in the path. But I do not need the id to get the exercises for the drop-down. And of course, the 2nd drop-down does not populate.
How can I adjust my route so both the new and edit views work correctly? OR, do I need to change my ajax call? NOTE: when I move the :exercises out of the collection in the routes, then the reverse happens; the new form does not work but the edit form does.
Here is my code:
application.js:
// if edit view is showing
// get the current value of the Muscle Group field
//
var current_muscle_group;
if( $('#fitness_weight_muscle_group') ){
current_muscle_group = $('#fitness_weight_muscle_group').val();
}
if( current_muscle_group != '' && current_muscle_group != 'Select One' && current_muscle_group != null ){
get_exercises(current_muscle_group);
}
// user selects muscle_group from drop-down
// new or edit form
//
$('#fitness_weight_muscle_group').change(function(){
get_exercises($(this).val());
})
// get_exercises
// ajax call to controller#exercises
// to get exercies for the muscle_group
//
function get_exercises(current_muscle_group){
$.ajax({
url: "exercises",
dataType: "json",
data: {muscle_group: current_muscle_group},
success: function(data){
populate_exercise_select(data);
}
});
}
...
Controller
...fitness/weights/controller:
protect_from_forgery except: [:exercises, :past_exercise, :max_weight]
...
def exercises
# run sql to get exercises for muscle group passed in
if params[:muscle_group]
#exercises=Fitness::Weight.select("DISTINCT exercise")
.where(admin_user_id: session[:user_id])
.where(muscle_group: params[:muscle_group].upcase)
return render json: #exercises
end
...
My routes
config/routes:
...
resources :weights do
collection do
get :exercises
...
end
## added this route to satisfy issue with ajax call
## ... to controller action that requires a record id
get :get_edit_exercises
end
...
Solution
I added a new route (see above) to solve the ID in the path issue when viewing the edit form for a record. I added a controller#get_edit_exercises action to match the route. It returns #exercises just like the def exercises does.
I changed application.js to call the new controller#get_edit_exercises action when it was an edit view. If an ID is in the path/url, then it is an edit view.
application.js
// get the exercises
function get_exercises(current_muscle_group){
var url = "exercises";
var current_record_id = get_current_record_id(); // function below
if( current_record_id > 0 ){
// get_exercises is for edit form. it expects an ID
url = "/fitness/weights/" + current_record_id + "/get_edit_exercises";
}
$.ajax({
url: url,
dataType: "json",
data: {muscle_group: current_muscle_group},
success: function(data){
populate_exercise_select(data);
}
})
}
function get_current_record_id() {
var pathname = $(location).attr('pathname');
return pathname.replace(/[^0-9]/g,'');
}
Note: the id was showing twice in the path, but using a full path in url: url solved that. The forward / was also needed.
url = "/fitness/weights/" + current_record_id + "/get_edit_exercises";```
I also added the new ```controller#get_edit_exercises``` to the controller's ```protect_from_forgery except: [...]```
Everything works. I just need to DRY up some code now.
In words, we have a muscle_group that has_many exercises, (and perhaps an exercise has_many muscle_groups), if it's a bidirectional has_many, then it's achieved with muscle_group has_and_belongs_to_many exercises and vice-versa.
So to populate the exercises drop-down, for a given muscle_group, I would suggest an ExerciseController with an index method that can accept a muscle_group id parameter and respond to an ajax request with a list of exercises.
So the routes would be:
resources :muscle_groups do
resources :exercises
end
and the controller:
class ExercisesController < ApplicationController
def index
#exercises = Exercise.joins(exercise_muscles: :muscles)
.where(muscles: { id: params[:muscle_group_id] })
render #exercises # assumes a partial file '_exercise.html.erb' produces the option tag for an exercise dropdown
end
end
I'm trying to implement a tag/category system using tag-it, but I'm unsure the best method I should be using to pass the data back to the model.
Currently what I'm trying to do is using ajax to call add_tag and pass the tagLabel.
// application.js
$("#tags").tagit({
beforeTagAdded: function(event, ui) {
// do something special
$.ajax({
url: "add_tag",
data : {name: ui.tagLabel }
});
}
});
And within my controller, I just add the param and save it.
// posts_controller.rb
def add_tag
#post.category_list.add( params[:name] )
#post.save
end
While this works for existing posts, I can't use this method for new posts.
And it makes my routes.rb file cluttered if I plan to use this method on multiple models.
// routes.rb
get '/posts/:id/add_tag' => 'posts#add_tag', as: :post_add_tag
Thanks for your help.
I have a jquery function which targets a rails form and upon the submit event posts the form data to the appropriate action. The object that’s created, a review, is then returned and via a mustache template is appended to a selected element and rendered:
$(document).ready(function(){
$('.new_review').on('submit', function(event){
event.preventDefault();
var reviewList = $(this).siblings('ul’);
$.post($(this).attr('action'), $(this).serialize(), function(review){
var newReview = Mustache.render($('#review_template').html(), review);
reviewList.append(newReview);
});
});
});
I have in my reviews controller written an if else statement in order to restrict a user (a devise model) to only being able to create one review:
def create
#restaurant = Restaurant.find(params[:restaurant_id])
#review = #restaurant.reviews.new(params[:review].permit(:thoughts, :rating))
if #restaurant.reviews.find_by user_id: current_user.id
flash[:notice] = "You already reviewed this restaurant!”
redirect_to '/restaurants’
else
#review.user = current_user
#review.save
redirect_to '/restaurants' unless request.xhr?
end
end
However I now need to insert a conditional into my jquery to ensure that it only attempts to append a new object if a review object is actually returned after the post action. I’m thinking that this needs to be written into the third argument on the post function but am a bit lost as to how to go about it.
Could anyone whether this is indeed the right way to go about what i’m trying to achieve and how exactly I could write this. Thanks in advance from a JS newbie.
The below now works perfectly with one simple if statement in the post function:
$(document).ready(function(){
$('.new_review').on('submit', function(event){
event.preventDefault();
var reviewList = $(this).siblings('ul');
$.post($(this).attr('action'), $(this).serialize(), function(review){
var newReview = Mustache.render($('#review_template').html(), review);
if review
reviewList.append(newReview);
});
});
});
I'm trying to get data from my controller into a javascript file in a rails application. The html immediately invokes an ajax request using the select option and the organization id as parameters.
In a before_filter I have
def set_org_id
if params[:id].present?
#org_id = klass.find(params[:id]).id
else
#org_id = 0
end
end
And in the js file I have:
$.ajax({ url: "/admin/analytics/filter",
type: 'GET',
data: {
time_frame: selectedId,
organization_id: <%= #org_id %>
}
})
If I hard code a number as the organization_id everything works fine, but when I try to pass the data from the before filter, I end up with no id in the page.
I should add that the index route is admin/analytics/. The filter action is admin/analytics/filter. The user never navigates to that page, only ajax hits the filter route to get relevant data. Could this be that the ajax request is being send before the instance variable is set? If so, what is the proper solution?
Your JS won't be able to access your #instance variablesunless you call them from your controller itself. The problem here is that if you're loading the ajax to access an instance varialbe - which simply won't work.
Let me explain...
JS
Javascript is known as a client side language - meaning it provides you with the ability to access elements in your HTML / DOM with relative impunity. The problem here is that Rails / Ruby, much like PHP, is server-side, and consequently is only able to provide rendered data to your browser
This means that calling the following simply won't work:
data: {
time_frame: selectedId,
organization_id: <%= #org_id %>
}
As explained, the reason for this is that you cannot access the #org_id from your Javascript. Firstly, you don't know if the #org_id variable will be set (in case you want to use the Rails helpers), and secondly, your JS won't be able to access the variable anyway (because it's Rails)
--
Fix
The purest fix for this is to somehow create the data you need in the DOM. I see from your answer that you have set a hidden field for your form. A much better way to do this is to set an HTML5 "data" attribute, or to use an id
You'd be better doing this:
<%= form_tag route_path, data: { org_id: #org_id } %>
This will give you the ability to call:
$(document).on("submit", "form", function(){
$.ajax({
...
data: {
time_frame: selectedId,
organization_id: $(this).data("org_id")
}
});
});
I was able to solve this by passing the data to a hidden field in the html.erb page.
<%= hidden_field_tag('org_id', #org_id) %>
Then in the javascript refer to the data through the selector.
organization_id: $('#org_id').val()
I don't love this, but at least it works.
I'm working on an application in which a certain model is updated from a number of different locations using remote forms. I'm looking for a pattern to dynamically call the right JS callback after updating this model.
With a normal form this can be solved by passing a redirect url in the form itself and then redirecting to this url in the controller. The remote form side is harder: Should I pass a file name? Should I pass an arbitrary string and then switch on that string in a single .js.erb file? Any other suggestions?
Is this just a sign that the application should be restructured to prevent updating the same model from more than one location?
No it's fine If you can call the same controller action from different locations.
Your options:
1) Preferably this controller action can give the same response and will work for the different locations, ex. it just updates a container with a id which is present in all those locations.
2) You noted that redirects made things easy in the past, consider adding the following to your application controller:
def js_redirect_to(path, flash_messages = {})
flash_messages.each { |type, message| flash[type] = message }
respond_to do |format|
format.js { render :js => "window.top.location='#{path}';" }
end
end
This is the same signature as the normal redirect_to, it just allows you to redirect from a js request. Note that if you use turbolinks the js should be 'Turbolinks.visit(url);'.
3) If you really can't handle it generically like the options above, you could pass your JS namespace of the location you are submitting from in the form, and the controller calls the same method for all locations, it's just a different namespace. Ex:
Let say one location is from Pet administration, then in assets pet.js:
var pet = {
load = function() {
your page load js...
},
... more functions...
post_callback = function(html_segment1, html_segment2) {
this is where you handle the form callback for pets...
$('some_element').html(html_segment1);
$('another_element').html(html_segment2);
}
}
Construct more like these for other locations of your app. Using JS namespaces like this is anyway a good idea. Then your form submits a parameter :location => :pet to the controller, which responds with:
... your JS code that all pages should execute...
html_segment1 = "<%= escape_javascript(render 'some_partial') %>";
html_segment2 = "<%= escape_javascript(render 'other_partial') %>";
<%= #location %>.post_callback(html_segment1, html_segment2);
4) Use a widget gem, most popular is apotomo or cells.
5) Just use a case in the controller to render different views.
Hope this helps, let me know if you need clarification.