Selecting item dynamically in rails - javascript

i am attempting to do a dynamic select here using rails and jquery. the code is as follows
<div class = "line_items">
<%- calc = Hash[Item.all.map{|p| [p.id, p.quantity]}].to_json %>
<div class = "item"><%= f.collection_select :item_id,Item.all,:id,:title, :prompt => "Select a Item", input_html: {data:{calc: calc} %></div>
<div class ="quantity"> <%= f.text_field :quantity %></div>
/*rest of code*/
</div>
javascript for the view is as follows
jQuery(document).ready(function(){
jQuery('.item').bind('change',function() {
var selectElement = jQuery(this);
var itemSelected = jQuery('.item:selected').val();
var wrapperDivElement = selectElement.parent(".line_items");
var quantity= eval(selectElement.data("calc"))[itemSelected];
jQuery(".quantity", wrapperDivElement).val(quantity);
});
});
when i change the item i am getting the following error
eval(selectElement.data("calc"))[itemSelected] is undefined in firebug. Can anyone point out where i am going wrong? also any better way to get the quantity. i feel the method i am doing is crude. any guidance would be helpful. Thanks in advance.

jQuery(document).ready(function(){
jQuery('.item select').bind('change',function() {
var selectElement = jQuery(this);
var itemSelected = selectElement.val();
var wrapperDivElement = selectElement.parents(".line_items");
var quantity= eval(selectElement.data("calc"))[itemSelected];
jQuery(".quantity input", wrapperDivElement).val(quantity);
});
});
i guess the value of itemSelected was not getting detected, hence the error. the above redefined code should work. But i strongly urge you not to get data like this. its better to do a json call or ajax call to get the related data from the controller. and use parents instead parent in the code:)

Ik think you mixup the reference by index and the reference by ID. I'm no Javascript wizard, but you fill the hash with a list of . Then you get the 'val'-value in the change event. That contains an integer, but that is not the index.
Then you request in Javascript the item with index val, not the item with value val. Maybe javascript cannot distinguish between them because both need an Int...
it's this line:
var quantity= eval(selectElement.data("calc"))[itemSelected];

Related

rails selection quantity javascript get class not working index

What want to do:
I creating ec site by ROR. What I want to do is can select the quantity each items in checkout page(Specifically, make plus and minus button then select the quantity).
The variable are #items and #user_items_quantity and Im adding index number on it.
Then what I mean, getting the (div class) in Javascript which is had index number and I want to edit quantity and show it.
But impossible to get (div class with index) in js each items. I was thinking to roop "for" in js but I can't.
Can you tell me how I work out please?
_user_basket_items.html.erb
<% #items.zip(#user_items_quantity).each.with_index(1) do | (item, uiq), index | %>
 <div class="entry value-minus quantity-minus[#{index}]" id="value-minus"> </div>
 <div class="entry value score-value quantity-score[#{index}]" id="score-value"><%= uiq %></div>
 <div class="entry value-plus active quantity-plus[#{index}]" id="value-plus"> </div>
<% end %>
///
item_quantity.js
var add = document.getElementsByClassName( "quantity-plus" )["#{index}".to_i];
var remove = document.getElementsByClassName( "quantity-minus" )["#{index}".to_i];
var scoreValue = document.getElementsByClassName( "quantity-score" )["#{index}".to_i];
var int = 0;
add.addEventListener('click', function() {
int = parseInt(scoreValue.innerHTML, 10)+1;
scoreValue.innerHTML = int;
});
remove.addEventListener('click', function() {
int = parseInt(scoreValue.innerHTML, 10)-1;
scoreValue.innerHTML = int;
});
the css classes that you are generating looks like this:
<div class="entry value-minus quantity-minus[1]"
or
<div class="entry value-minus quantity-minus[2]"
but your javascript is looking for document.getElementsByClassName( "quantity-minus" ) with a mix of ruby syntax (["#{index}".to_i]) and js. Is your js template parsing ruby? You don't provide how index is initialized.
if it is, I would change the javascript as follows:
var add = document.getElementsByClassName( "quantity-minus[#{index.to_i}]" );
If your template isn't parsing ruby, you have to find another way.
As a side note, in your partial you are iterating over a list and giving static ids to the elements (id="value-minus") you should try avoid doing that

how to store value of select_tag in javascript var?

I want to store the value of the select from a form into my javascript.
This works:
<div id="tshirt-div">
....
</div>
<select id="tshirt-color">
<option value="#fff">White</option>
<option value="#000">Black</option>
<option value="#f00">Red</option>
<option value="#008000">Green</option>
<option value="#ff0">Yellow</option>
</select>
<script>
document.getElementById("tshirt-color").addEventListener("change", function(){
document.getElementById("tshirt-div").style.backgroundColor = this.value;
}, false);
</script>
I am trying to do this:
<%= f.grouped_collection_select :color_id, #items.order(:title), :colors, :title, :id, :title, include_blank: "Select Color", id: "tshirt-color" %>
<script>
document.getElementById("tshirt-color").addEventListener("change", function(){
var bg_color = this.value
document.getElementById("tshirt-div").style.backgroundColor = <%= Color.find(bg_color) %>;
}, false);
</script>
A few of my attempts:
var bg_color = document.getElementById("tshirt-color").value
inside and outside of the function
var bg_color = document.getElementById("tshirt-color").addEventListener("change", function(){
document.getElementById("tshirt-div").style.backgroundColor = <%= Color.find(bg_color.value) %>;
, false);
I have tried storing the var many different ways but each gives me the following error:
undefined local method or variable 'bg_color'
The value of the f.grouped_collection_select is an integer so I want to then find the color title (red, blue, green, etc.) and return that to the ...style.backgroundColor.
How can I store the select from the form, store it as a variable, and then find the color from the Color model?
You can't access JavaScript variables from inside the ERB tags. ERB lies on top of the script. Meaning that when the page request is made to the Rails server it will compile the page so that the ERB is interpreted. Then a plain JavaScript file or HTML file is send to the client. The JavaScript will be executed on the client.
<%= Color.find(bg_color) %>
Is run on the server side and has no access to the JavaScript variable bg_color.
A simple solution is to provide all the colors up front and pick the color from that.
const colors = <%= Color.pluck(:id, :value).to_h.to_json.html_safe %>;
document.getElementById("tshirt-color").addEventListener("change", function(){
document.getElementById("tshirt-div").style.backgroundColor = colors[this.value];
}, false);
If you don't want to provide all colors up front you'll have to set-up an AJAX request that queries the server when selecting a color.
Let me quickly explain <%= Color.pluck(:id, :value).to_h.to_json.html_safe %>.
tmp = Color.pluck(:id, :value) # retrieve the id and value of the colors
#=> [[1, "#fff"], [2, "#000"]]
tmp = tmp.to_h # convert the nested array into a hash
#=> { 1 => "#fff", 2 => "#000" }
tmp = tmp.to_json # convert the hash to a JSON string
#=> '{"1":"#fff", "2":"#000"}'
tmp = tmp.html_safe # mark the string as HTML safe so ERB won't replace characters such as " with "
#=> '{"1":"#fff", "2":"#000"}'
Since JSON is short for JavaScript Object Notation it can simply be read by read as part of JavaScript.
Alternatively you could manually build the options, and provide the color value through a custom attribute. An option could for example look something like this:
<option value="1" data-color-value="#fff">White</option>
Then use the additional attribute to set the color.
const color = this.options[this.selectedIndex].dataset.colorValue;
document.getElementById("tshirt-div").style.backgroundColor = color;
You could always just store the value in a hidden field element and just update the value of it whenever the select changes.
Then do something like
<%= hidden_field_tag 'color_id', '0', onchange: "changeColor()", id: 'selected-color-field'%>
<script>
function changeColor() {
var selectedColor = document.getElementById("selected-color-field").value
// Do whatever
}
</script>

In Rails, how do I query my database from JavaScript so that I can fill in a textbox?

I have a view that uses the select method in Ruby on Rails to return the following code:
<select class="form-control" name="xyz" id="food_id">
<option value="1">Peach</option>
<option value="2">Apple</option>
<option value="3">Orange</option>
</select>
To pass this information over to the JavaScript file, I am basically using a link_to method in my view, such as the following:
<%= link_to "Import text", "#/", :onclick => "fillTextBox()" %>
In the JS file, how can I basically get the name and description of whatever value is selected in the dropdown? This is as far as I've gotten before I got confused:
function fillTextBox(){
var dropdown = document.getElementById('food_id');
var foodID = dropdown[dropdown.selectedIndex].value;
}
I need to do something like:
var foodName = Food.find(foodID).name;
var foodDescription = Food.find(foodID).description;
Is this possible? The rest of the JS function would basically fill in a textbox using foodName and foodDescription, but I first need to be able to query the database for this information in the first place.
Have you ever tried Gon gem?
In your controller:
def some_method
gon.foods = Food.all
end
Then in your js
gon.foods
In your case, you can attach the complete Food collection in the controller and then find the Food by its id in javascript using somethig like this
var food = gon.foods.filter(function(food) {
return food.id === foodID;
})[0];

calling javascript class function from onchange attribute

I have a simple class that has a few methods(for now). I can instantiate it and call the init function, but I cant call another method from the onchange attribute.
here is the class:
var ScheduleViewer = (function() {
var options = {
container: 'trip_stops',
schedule: {}
};
function ScheduleViewer (){};
ScheduleViewer.prototype.init = function(params) {
$.extend(options, params);
// setTimeout(function() {
// ScheduleViewer.addListener(options);
// }, 3000);
this.addListener(options);
}
ScheduleViewer.prototype.getSchedule = function(trip_id) {
var _id = trip_id;
console.log(_id);
}
ScheduleViewer.prototype.addListener = function(options) {
console.log("adding listener");
var _id = options.select_id;
console.log($('#train_select').length);// zero assuming timing issue
$('#'+_id).on('change', function() {
alert("changed");
})
}
return ScheduleViewer;
})();
the call
<div id="trip_stops" class="trip_options">
<% if (trips.length > 0) { %>
<%
var params = {
schedule: schedule
};
var scheduler = new ScheduleViewer();
scheduler.init(params);
%>
<select id="train_select">
<option value="0" selected disabled>Select a train</option>
<% $.each(trips, function(index, val) { %>
<option value="<%= val.trip_id %>">
<%= val.train_num %> Train
</option>
<% }); %>
</select>
<% } else { %>
No trips scheduled.
<% } %>
</div>
Note that Im using jqote for templating.
I get the log from the init call, but then fails in the onchange with Uncaught ReferenceError: scheduler is not defined. Im not sure what Im doing wrong. Any pointers?
Thanks
EDIT: I made an attempt to add a listener that gets called at initialization. Im assuming that because of timing issues the listener is not getting bind. Now this is where my inexperience comes in play I tried to setTimeout but the function call is not happening as is.
I don't know why this may not work, but you shouldn't really use inline styles/functions anyway.
Why don't you try and get the element in your script the apply the onchange function to it.
var scheduler = new ScheduleViewer();
select = document.getElementById('train_select');
scheduler.init(params);
select.onchange = function() {
scheduler.getSchedule(this.value)
};
Try binding the event in javascript and remove the inline event.
var scheduler = new ScheduleViewer();
scheduler.init(params);
$(document).on('change', '#select', function() {
scheduler.getSchedule(this.value)
});
I have never used jQote before, however could it be because of the way jQote handles variable declarations? Try instanciating your scheduler object outside the templating code.
<script>
var scheduler = new ScheduleViewer(),
params = { schedule: schedule };
scheduler.init(params);
</script>
<!-- template code... -->
Also please note that the way you have written your code, all ScheduleViewer instances will share the same options object. I am not sure what you will be doing with this options object, but I guess that's not the desired behaviour.
Finally, like already stated by others, adding event listeners inline is a bad practice and you should register your even handlers programmatically using JavaScript. Either with the native addEventListener function or with helper functions of your favorite library.
I'm not familiar with jqote, but it looks like a scoping problem. If you must use the inline onchange, put the object reference in the window object and access it from there.
window['scheduler'] = new ScheduleViewer();
window['scheduler'].init(params);
And
<select id="train_select" onchange="window['scheduler'].getSchedule(this.value)">
So to solve the issue I ended up adding the class to the window object. I had issues with the listeners as the code that loads the template was not fast enough and the binding was not happening.
The class looks like:
window.ScheduleViewer = (function() {
var options = {
container: 'trip_stops',
schedule: {}
};
this.init = function(params) {
$.extend(options, params);
}
this.getSchedule = function(trip_id) {
var _id = trip_id;
//Do some cool stuff with GTFS data
}
//More functions
return this;
})();
The templated html:
<div id="trip_stops" class="trip_options">
<% if (trips.length > 0) { %>
<%
var params = {
schedule: schedule
};
ScheduleViewer.init(params);
%>
<select id="train_select" onchange="ScheduleViewer.getSchedule(this.value)">
<option value="0" selected disabled>Select a train</option>
<% $.each(trips, function(index, val) { %>
<option value="<%= val.trip_id %>">
<%= val.train_num %> Train
</option>
<% }); %>
</select>
<% } else { %>
No trips scheduled.
<% } %>
</div>
Thank you for all the responses.

Better approach than hidden field to store data in html

I'd like to know if a better approach exists to store data in html content.
At the moment I got some values stored in my html file using hidden field. These values are generated by code behind.
Html:
<input type="hidden" id="hid1" value="generatedValue1" />
<input type="hidden" id="hid2" value="generatedValue2" />
And therefore I get those values on client side using jquery, in order to pass them to an ajax request.
JQuery
$.ajax({
data:{
var1 : $('#hid1').val(),
var2 : $('#hid2').val()
}
);
So is this the correct way to do this, or does it exist a smoother solution to achieve the same result? Since I don't need these values to be posted on page submit the input hiddenis probably gross.
What I usually do is adding the values as data- attributes to the html form:
<form data-field1="generatedValue1" data-field2="generatedValue2">
...
</form>
And then, retrieve them with jQuery:
...
$form = $( my_selector_to_take_the_form );
data:{
var1 : $('form').attr('data-field1'),
var2 : $('form').attr('data-field1')
}
With this, you won't post any hidden field
If you don't need those in a form, then just make them variables in your JavaScript. To output them, encode them via the JavaScriptSerializer class:
<%
// Presumably somewhere in your C# code...
JavaScriptSerializer serializer = new JavaScriptSerializer();
%>
<script>
var hid1 = <%= serializer.Serialize(valueForHid1) %>;
var hid2 = <%= serializer.Serialize(valueForHid2) %>;
</script>
(See note below about globals.)
Using them later:
$.ajax({
data:{
var1 : hid1,
var2 : hid2
}
);
Globals: As shown there, hid1 and hid2 end up as globals (on most browsers, they do when you use hidden fields as well). I recommend not using globals, but instead wrapping everything in scoping functions:
(function() {
var hid1 = <%= serializer.Serialize(valueForHid1) %>;
var hid2 = <%= serializer.Serialize(valueForHid2) %>;
// ....
$.ajax({
data:{
var1 : hid1,
var2 : hid2
}
);
})();
If for some reason you have to use a global, use just one:
var myOneGlobal = {
hid1: <%= serializer.Serialize(valueForHid1) %>,
hid2: <%= serializer.Serialize(valueForHid2) %>
};
Using that later:
$.ajax({
data:{
var1 : myOneGlobal.hid1,
var2 : myOneGlobal.hid2
}
);
You can output an entire object graph to one variable (perhaps myOneGlobal) with the serializer:
<script>
var myOneGlobal = <%= serializer.Serialize(objectWithData) %>;
</script>
You can use the new HTML5 "data" attributes. (http://html5doctor.com/html5-custom-data-attributes/)
Your codebehind section would do something like this:
<ul data-listname="{put name here}">
<li data-key="{put key here}>
Item1
</li>
</ul>
And then in your jQuery you can do:
var firstId = $('li').first().data('id');
var list = $('ul').data('listname');
Make sure to only use lowercase after the data-
I have found, that it will not work correctly otherwise.
You can also set the data like this:
$('#something').data('smthgnelse', 'Hi there');
You should use the HTML5 data attribute.
i.e My Link
You can easy access this attributes i.e with jQuery
$(".mylink").attr("data-YOURKEY");
John Resig explained it well:
http://ejohn.org/blog/html-5-data-attributes/
Please also read the specs from HTML5-Doctor
http://html5doctor.com/html5-custom-data-attributes/
..and if you like to go a bit deeper:
http://www.w3.org/html/wg/drafts/html/master/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes

Categories