I've seen some similar question, but still don't know how achieve my goal.
I want to do that thing:
Display modal with products represented by divs.
User choose some products (I add .active class to chosen products)
Then I use jQuery to make array of chosen products ids.
In the end I want to create div for each product, which will be include some informations about this product from database. I'm using js .append() to do it. And here is a problem.
This code is in my script tag in proper view.
var chosen_products_array;
$('.product-to-choose').click(function() {
$(this).toggleClass("active");
$(this).find('i').toggleClass("visible");
});
$('#chosen-products-confirm').click(function() {
var chosen_products = $('.product-to-choose.active');
chosen_products_array = jQuery.makeArray( chosen_products );
});
$('#confirm').click(function() {
var textToInsert = '<div><h4>Products:</h4>';
$.each(chosen_products_array, function(count, item) {
var id = $(item).attr('id').substr(8); // id is product_number
textToInsert += '<div><li name="chosen-product"> <%= current_user.products.find(' + id +').name %></li></div>';
});
textToInsert += '</div>';
$('#div-example').append(textToInsert);
});
My controller create action:
def create
#meal = current_user.meals.build(meal_params)
if #meal.save
current_user.type_tags.each do |type_tag|
key = "type#{type_tag.id}"
if params[key]
#meal.type_tags << type_tag # zapis do bazy danych
end
end
flash[:success] = "Create new meal!"
redirect_to meals_path
else
#render 'meals/new'
end
end
This line generates error:
textToInsert += '<div><li name="chosen-product"> <%= current_user.products.find(' + id +').name %></li></div>';
Error:
Couldn't find Product with 'id'=+id+ [WHERE "products"."user_id" = ?]
I know there is a problem in passing js variables to rails erb, but how can I solve it in this case? I tried with gon too.
In my controller, I added:
gon.products = current_user.products
and then try
alert(gon.products)
it's ok - array of objects, but when I try
alert(gon.products.find(1))
it doesn't work.
Have you got any idea how can I put this javascript id variable to erb code? Or maybe is any different solution?
Probably the best would be to add a remote form (it may be hidden) that posts whatever you want (javascript values) and then it is handled by a controller. Then you can render it with js views.
This would probably be the most "rails-like" solution.
Here's some code:
In the routes:
resource :my_form, only: :create
In the view:
<%= form_tag(my_form_path, remote: true, method: :post) do %>
<%= hidden_field_tag :val %>
<% end %>
You alter the val dynamically by javascript, and submit the form whenever you want to.
Then, in the controller you have something like this:
respond_to :js
def create
# do something with params['val']
#my_val = params['val']
end
And you would also add create.js.erb view, where you could add some javascript that can use any of the instance variables created in the controller.
//create.js.erb
$('#someElement').html('<%= #my_val %>');
Let me stress if it is not clear, the form is remote (ajax), so it is sent in the background and the page is not reloaded.
When the browser receives the response, it may contain any javascript you decide to render (the contents of create.js.erb), and then the browser executes that javascript.
So, you can add there any jquery commands with arguments that you changed dynamically by rails, or you could also render html partials and replace any elements with them, like this:
//create.js.erb
$('#my_id').html('<%= j(render partial: 'my_form/_some_partial.html.erb', locals: { my_val: #my_val }) %>');
More details on Working with JavaScript in Rails
UPDATE
Another, inferior but simpler to implement solution would be to always render all products, but have them hidden initially, and then show them by jQuery on demand. You would just need to add appropriate css classes or data attributes to those products, as well as the links you use to select them.
The problem is that erb code is evaluated on the server before the browser even fetches the javascript file, so it doesn't make sense to use this way. It's recommended that you only use erb in your javascript for simple things like asset_path
I think first make your model visible to the browser as js variable(json object) . Some thing like below at the end of your view file(below is the haml syntax):
:javascript
var userProducts = #{#current_user.products.to_json};
Then use it in javascript expression
Related
Is there a way to grab / fetch a variable inside js between two different views? Say I have an index view with does:
<% foo.each do |foo| %>
<%= link_to 'bar', bar_path %>
<% end %>
Then in bar I want to add some jQuery to a specific element with foo.id selector:
$('div#<%= foo.id %>').fadeOut(); // I know the interpolation wouldn't work here
I know I can pass foo.id to bar with my path or send it via the locals hash, but how would I "grab" it with jQuery?
You can easily pass variables to JS using a gem called gon.
gem "gon"
All you have to do is add this gem to you Gemfile and then you can call gon inside any controller like this:
class PaymentsController < ApplicationController
def new
...
gon.controller = "payments"
gon.action = "new"
gon.message = "Hello World!"
...
end
end
And now, at any javascript (in following case, coffeescript) you can:
$ ->
# Check if it is the target controller and action
if gon? && gon.controller is "payments" && gon.action is "new"
alert gon.message
Is there a way to grab / fetch a variable inside js between two
different views
Not without hacking.
JS is an client-side technology, meaning it loads in the browser, not on the server. This is famous for causing so many issues for developers trying to bind to dynamically-named objects, much like what you're trying to do.
There are several ways around this. I'll go through them for you:
Class
The first way is to use a broader identifier, such as element type or class.
You're currently trying to select foo.id, which means you're going to have to pass that data directly to JS somehow. Inefficient.
A much better way would be to use something like this:
$("a.bar").on("click", function(e){
$(this).fadeOut();
});
--
URL
According to this resource, there are no inbuilt ways to pull query strings from JS: How to get the value from the GET parameters?
You may be able to do something like this:
$(document).ready(function(){
foo = window.location.search.substring(1); //Considering you have http://url.com/your/foo/id
$("#" + foo).fadeOut();
});
--
ERB
There is a final alternative, although this only works if you don't precompile your JS (which I'm not sure is actually feasible any more). That is to use ERB directly in your JS, rather like what you've done in the question.
Whilst this works for dynamic references (such as paths), I don't think it can pull data from a controller. Unless, of course, you put the script directly in your view - which goes against the conventions of MVC.
#app/views/bar/show.html.erb
<script>
// your code here
</script>
I think you need to create a js.erb file and in that pass a variable defined in your controller action.
The code insde js.erb would look like this :
<% fields_html = render "fields", :entity => #model %>
$("#fields-list").html("<%= escape_javascript fields_html %>")
Here fields is a partial I have created. :entity is basically passing of the variable value. #model is the variable defined inside controller action. fields-list is the id of the field I want to load using jquery.
I have been searching for a good way to create dynamic dropdowns that are part of a form. The dropdowns need to be populated with data from the database based on the previous dropdown. I have not found a good rails 3/prototype solution. I have seen some partial solutions with jQuery and using prototype legacy helpers. However, I read it is not a good idea to use the legacy helpers so I am trying to do it without them while still using prototype.
I understand this is a large question however, I assume this has already been done several times and if somebody has a good how to it would be much appreciated. If not my plan of attack is use onchange to trigger a javascript call to send a request to the server. Then update the partial, the next select box, with the apropriate options and then repeat.
Immediate Question: How do you generate a ajax page call using prototype? I need to send the result of the first drop down to my controller. The url would be car_infos/update_make/"year".
I have this select tag in my larger form that calls a javascript function.
<%= select_tag 'year', options_from_collection_for_select(#vehicle_years, "year", "year"), {:include_blank => true, :onchange => "submitYear(this.value)" }%>
Thanks for any help as I get started with Rails.
Update: I am generating a request to the server with this javascript:
function submitYear(year){
new Ajax.Request('/udpate_make/'+year, {method: 'get'});
}
However, it doesn't generate the correct url. If I put /car_infos/update_make it generates car_infos/car_infos/udpate_make/. If I take at the /car_infos part it just does /update_make and leaves off the car_infos. I get two or none. Still not sure how to generate the proper url of 'car_infos/update_make/year' I will implement the observe option as mentioned by #Jaryl but still having problems with the url generation. Here is my routes...
resources :car_infos
match 'car_infos/update_make/:year', :controller => 'car_infos', :action => 'update_make'
Update: with another part of the journey
To solve the duplicate part of the url I had to do the following:
function submitYear(year){
new Ajax.Request('update_make/'+year, {method: 'get'});
}
Notice the missing / from the previous example. Still don't know why when putting car_infos in there it duplicates it. but at least I am generating a valid Ajax request to the server now.
Since you are using Rails 3, where legacy helpers are deprecated. You should use something like jQuery UJS. What you do is take out the JS helper codes from your select tag like so:
<%= select_tag 'year', options_from_collection_for_select(#vehicle_years, "year", "year") %>
Then in your JS file (I'm using jQuery here), you add codes that performs the JS function:
$('#year').change(function(e) {
// some AJAX call here using $('#year').val()
});
To do this in Prototype (see docs for observing events), it will be something like this:
Event.observe('#year', 'change', function(event) {
// ...
});
This is what I ended up doing to create my dynamic drop downs with Protoype in case anybody else is going down this path. Please let me know if there is a better way:
Create Routes for appropriate controller, my controller is car_infos
match 'car_infos/update_make/:year', :controller => 'car_infos', :action => 'update_make'
match 'car_infos/update_model/:year/:make', :controller => 'car_infos', :action => 'update_model'
Inside form have first dropdown built and render a partial.
<div id="year_sel">
<%= f.label :car_year %>
<%= select_tag 'year', options_from_collection_for_select(#vehicle_years, "year", "year"), {:include_blank => true, :onchange => "submitYear(this.value)" }%>
</div>
<div id="make_select">
<%= render 'make_sel' %>
</div>
Build the link in javascript in your application.js file:
function submitYear(year){
new Ajax.Request('update_make/'+year, { method: 'get'});
}
Have your .js.rjs file that responds to the ajax request and renders the partial.
page.replace_html('make_select', render('make_sel'))
In your controller function make sure it responds to the .js format.
Hope this helps somebody else since it took me two days to find all.
I'm building a Rails app that uses Pusher to use web sockets to push updates to directly to the client. In javascript:
channel.bind('tweet-create', function(tweet){ //when a tweet is created, execute the following code:
$('#timeline').append("<div class='tweet'><div class='tweeter'>"+tweet.username+"</div>"+tweet.status+"</div>");
});
This is nasty mixing of code and presentation. So the natural solution would be to use a javascript template. Perhaps eco or mustache:
//store this somewhere convenient, perhaps in the view folder:
tweet_view = "<div class='tweet'><div class='tweeter'>{{tweet.username}}</div>{{tweet.status}}</div>"
channel.bind('tweet-create', function(tweet){ //when a tweet is created, execute the following code:
$('#timeline').append(Mustache.to_html(tweet_view, tweet)); //much cleaner
});
This is good and all, except, I'm repeating myself. The mustache template is 99% identical to the ERB templates I already have written to render HTML from the server. The intended output/purpose of the mustache and ERB templates are 100% the same: to turn a tweet object into tweet html.
What is the best way to eliminate this repetition?
UPDATE: Even though I answered my own question, I really want to see other ideas/solutions from other people--hence the bounty!
imo the easiest way to do this would involve using AJAX to update the page when a new tweet is created. This would require creating two files, the first being a standard html.erb file and the second being a js.erb file. The html.erb will be the standard form which can iterate through and display all the tweets after they are pulled from the database. The js.erb file will be your simple javascript to append a new tweet upon creation, i.e.:
$('#timeline').append("<div class='tweet'><div class='tweeter'><%= tweet.username %></div><%= tweet.status %></div>")
In your form for the new tweet you would need to add:
:remote => true
which will enable AJAX. Then in the create action you need to add code like this:
def create
...Processing logic...
respond_to do |format|
format.html { redirect_to tweets_path }
format.js
end
end
In this instance, if you post a tweet with an AJAX enabled form, it would respond to the call by running whatever code is in create.js.erb (which would be the $('#timeline').append code from above). Otherwise it will redirect to wherever you want to send it (in this case 'Index' for tweets). This is imo the DRYest and clearest way to accomplish what you are trying to do.
Thus far, the best solution I found was Isotope.
It lets you write templates using Javascript which can be rendered by both the client and server.
I would render all tweets with Javascript. Instead of rendering the HTML on the server, set the initial data up as JS in the head of your page. When the page loads, render the Tweets with JS.
In your head:
%head
:javascript
window.existingTweets = [{'status' : 'my tweet', 'username' : 'glasner'}];
In a JS file:
$.fn.timeline = function() {
this.extend({
template: "<div class='tweet'><div class='tweeter'>{{tweet.username}}</div>{{tweet.status}}</div>",
push: function(hash){
// have to refer to timeline with global variable
var tweet = Mustache.to_html(timeline.template, hash)
timeline.append(tweet);
}
});
window.timeline = this;
channel.bind('tweet-create', this.push);
// I use Underscore, but you can loop through however you want
_.each(existingTweets,function(hash) {
timeline.push(hash);
});
return this
};
$(document).ready(function() {
$('#timeline').timeline();
});
I haven't tried this, but this just occurred to me as a possible solution:
In your view create a hidden div which contains an example template (I'm using HAML here for brevity):
#tweet-prototype{:style => "display:none"}
= render :partial => Tweet.prototype
Your tweet partial can render a tweet as you do now.
.tweet
.tweeter
= tweet.username
.status
= tweet.status
When creating a tweet prototype you set the fields you want to the js-template replacement syntax, you could definitely dry this up, but I'm including it here in full for example purposes.
# tweet.rb
def self.prototype
Tweet.new{:username => "${tweet.username}", :status => "${tweet.status}"}
end
On the client you'd do something like:
var template = new Template($('#tweet-prototype').html());
template.evaluate(.. your tweet json..);
The last part will be dependent on how you're doing your templating, but it'd be something like that.
As previously stated, I haven't tried this technique, and it's not going to let you do stuff like loops or conditional formatting directly in the template, but you can get around that with some creativity I'm sure.
This isn't that far off what you're looking to do using Isotope, and in a lot of ways is inferior, but it's definitely a simpler solution. Personally I like haml, and try to write as much of my mark up in that as possible, so this would be a better solution for me personally.
I hope this helps!
To be able to share the template between the javascript and rails with a mustache template there is smt_rails: https://github.com/railsware/smt_rails ("Shared mustache templates for rails 3") and also Poirot: https://github.com/olivernn/poirot.
I have a large "data" active record object whose info I filter based on what the user clicks on a page, then I display that filtered data to the user. I was wondering where the best place to put the data-filtering logic is. Right now I do the filtering in the rails controller action, so that in the view I only have to say something like
<script>
var filtered_data = <%= raw #filtered_data %>
alert('first piece of data is: '+filtered_data[0])
var filtered_names = <%= raw #filtered_names %>
</script>
and other such vars.
or, I was considering just passing the whole #data like:
<script>
var data = <%= data.to_json.html_safe %>
var filtered_data = **some js logic to filter data var**
var filtered_names = **more js logic to filter for names, maybe using the jquery .map function, etc **
</script>
which way is better for performance? I would think client-side JS is better, but it's probably not client side anymore since it's a .js.erb? Or would having more logic in the js.erb still help a bit?
Thanks for any insight!
you should put as few logic as possible into your view...
if you filter the data via js, that data is beeing sent to the client (producing traffic).. so it is probably better to do the filtering on the server side.
always try just to display things in your view - this makes your code much more maintainable.
In a Ruby on Rails project I need to have a JavaScript function executed at the end of an Ajax call. My problem is that the values being passed into the JavaScript function being called with page.call are being wrapped up in quotes. Not too much of a problem if you're passing in strings but one of the values is a string representation of a JavaScript Array, i.e. [0,1,2,3].
Here are what I feel are the important snippets of code.
I have an observe_field that watches a select drop down list.
<%= observe_field("project_select_list",
:loading => "Element.show('tree_indicator')",
:complete => "Element.hide('tree_indicator')",
:url => {:controller => "projects", :action => "change_project"},
:with => "id")
%>
In the project_controller
def change_project()
#current_project = Project.find(params[:id])
end
And in change_project.rjs
page.replace_html("project_name", #current_project.name)
page.visual_effect(:highlight, "project_name")
page.call("buildYUITreeView", "project_tree", #current_project.get_directory_contents(#current_project.local_path, 0))
The last value:
#current_project.get_directory_contents(#current_project.local_path, 0))
is what's causing me issues. I just want it to send the value, for example [0,1,2,3], but it's sending "[0,1,2,3]" which is causing the JS to blow up.
This partial does what it's supposed to in that it sends the data and not a string to the JavaScript code that gets put on the page.
<% javascript_tag do -%>
YAHOO.util.Event.addListener("dom:loaded", "load", buildYUITreeView("<%= tree_id %>", <%= project.get_directory_contents(project.local_path, 0) %>));
<% end -%>
With this in mind I'm playing around with using a partial and just rendering it to call the JS function when I need to but that seems like such a hack.
How can I make page.call not wrap the data sent as parameters in quotes, or how should I go about passing this data to the JS function for execution after the Ajax call is complete?
Tom was on the right track, the << method is the way to go because it will insert any javascript directly. You'll want to use that instead of call.
page << "buildUYITreeView('project_tree', #{#current_project.get_directory_contents(#current_project.local_path, 0))})"
You can do the same thing with the YAHOO line as tom showed which should be more efficient than using a partial with a javascript tag.
You could use the << method instead to emit raw javascript:
page << 'YAHOO.util.Event.addListener("dom:loaded", "load", buildYUITreeView("project_tree", '+#current_project.get_directory_contents(#current_project.local_path, 0)+'))'