I need a bit of help with some Ruby array jujitsu.
I have the following array called #tasks:
[#<PivotalTracker::Story:0x007f9d6b8 #id=314, #url="http://www.pivotaltracker.com/", #created_at=#<DateTime: 2012-06-18T20:23:42+00:00 ((2456097j,73422s,0n),+0s,2299161j)>, #accepted_at=nil, #project_id=357031, #name="Test ", #description="This is the description for \"Test\"", #story_type="feature", #estimate=-1, #current_state="unstarted", #requested_by="joe jones", #owned_by=nil, #labels=nil, #jira_id=nil, #jira_url=nil, #other_id=nil, #integration_id=nil, #deadline=nil, #attachments=[]>, #<PivotalTracker::Story:0x007f9d6b8 #id=315, #url="http://www.pivotaltracker.com/", #created_at=#<DateTime: 2012-06-18T20:25:20+00:00 ((2456097j,73520s,0n),+0s,2299161j)>, #accepted_at=nil, #project_id=357031, #name="Test 2", #description="This is the description for \"Test 2\"", #story_type="feature", #estimate=-1, #current_state="unstarted", #requested_by="joe jones", #owned_by=nil, #labels=nil, #jira_id=nil, #jira_url=nil, #other_id=nil, #integration_id=nil, #deadline=nil, #attachments=[]>, #<PivotalTracker::Story:0x007f9d6b8 #id=316, #url="http://www.pivotaltracker.com/story/", #created_at=#<DateTime: 2012-06-18T20:25:26+00:00 ((2456097j,73526s,0n),+0s,2299161j)>, #accepted_at=nil, #project_id=357031, #name="Test 3", #description="Description for Test 3 ", #story_type="feature", #estimate=-1, #current_state="unstarted", #requested_by="joe jones", #owned_by=nil, #labels=nil, #jira_id=nil, #jira_url=nil, #other_id=nil, #integration_id=nil, #deadline=nil, #attachments=[]>]
My end goal is to create a JavaScript array in my .erb view with just the ID values in the above array.
I was thinking of trying something like:
var myJSArray = [<%= #tasks.each { |t| print t.id.to_s+", " } %>];
However, that obviously appends a "," to the end of the string, which is not desirable (i.e. it returns "314, 315, 316,". It also seems like a bit of a hack and not the right way to do it.
Any ideas on how to do this properly?
Thank you!
UPDATE: After some more research on SO, it seems I can do this in two steps:
#ids = #tasks.map { |t| t.id }
and then use that in the view with:
var myJSArray = [<%= #ids.map(&:to_s).join(", ") %>];
Not sure if this is ideal or if it could be done in a single step, though.
var myJSArray = [<%= j #tasks.map(&:id).join(',') %>];
Or you may prefer something like this:
var myJSArray = <%= #tasks.map(&:id).to_json %>;
Use Array#join :
<%=print #task.map {|t| t.id.to_s}.join(', ') %>
Related
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>
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
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];
I have following resourceBundle object in javascript.
var resourceBundle = {
en : {
"EX_ONE" : "Example One",
"EX_TWO" : "Example One",
"EX_THREE" : "Example One"
}
fr : {
"EX_ONE" : "ExampleD Uno",
"EX_TWO" : "Exampled Twono",
"EX_THREE" : "Exampled theree"
}
}
But the thing is, it is very bad practice when ever to add a label, I have to come here, add new key value to all the languages.I mean here is only English and (wrong) French. But what if there are other languages. Can you suggest me a better way, like what we use properties file for jsp and Java.
Put your .properties resource bundle file under classpath (WEB-INF/classess) like this:
resources.properties
EX_ONE=Example One
EX_TWO=ExampleTwo
EX_THREE=Example Three
resources_fr.properties
EX_ONE=ExampleD Uno
EX_TWO=Exampled Twono
EX_THREE=Exampled theree
In your JSP
<%# page language="java" import="java.util.*" %>
<%
ResourceBundle resource = ResourceBundle.getBundle("resources");
// your language is based on current locale set in request/session
String exOne=resource.getString("EX_ONE");
String exTwo=resource.getString("EX_TWO");
%>
Your localized text here:
<%
System.out.println(exOne);
System.out.println(exTwo);
%>
I'm trying to learn about new usages of javascript as a serverside language and as a functional language. Few days ago I heard about node.js and express framework. Then I saw about underscore.js as a set of utility functions. I saw this question on stackoverflow
. It says we can use underscore.js as a template engine. anybody know good tutorials about how to use underscore.js for templating, especially for biginners who have less experience with advanced javascript. Thanks
Everything you need to know about underscore template is here. Only 3 things to keep in mind:
<% %> - to execute some code
<%= %> - to print some value in template
<%- %> - to print some values HTML escaped
That's all about it.
Simple example:
var tpl = _.template("<h1>Some text: <%= foo %></h1>");
then tpl({foo: "blahblah"}) would be rendered to the string <h1>Some text: blahblah</h1>
<!-- Install jQuery and underscore -->
<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script type="text/javascript" src="http://documentcloud.github.com/underscore/underscore-min.js"></script>
<!-- Create your template -->
<script type="foo/bar" id='usageList'>
<table cellspacing='0' cellpadding='0' border='1' >
<thead>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<%
// repeat items
_.each(items,function(item,key,list){
// create variables
var f = item.name.split("").shift().toLowerCase();
%>
<tr>
<!-- use variables -->
<td><%= key %></td>
<td class="<%= f %>">
<!-- use %- to inject un-sanitized user input (see 'Demo of XSS hack') -->
<h3><%- item.name %></h3>
<p><%- item.interests %></p>
</td>
</tr>
<%
});
%>
</tbody>
</table>
</script>
<!-- Create your target -->
<div id="target"></div>
<!-- Write some code to fetch the data and apply template -->
<script type="text/javascript">
var items = [
{name:"Alexander", interests:"creating large empires"},
{name:"Edward", interests:"ha.ckers.org <\nBGSOUND SRC=\"javascript:alert('XSS');\">"},
{name:"..."},
{name:"Yolando", interests:"working out"},
{name:"Zachary", interests:"picking flowers for Angela"}
];
var template = $("#usageList").html();
$("#target").html(_.template(template,{items:items}));
</script>
JsFiddle Thanks #PHearst!
JsFiddle (latest)
JsFiddle List grouped by first letter (complex example w/ images, function calls, sub-templates) fork it! have a blast...
JsFiddle Demo of XSS hack noted by #tarun_telang below
JsFiddle One non-standard method to do sub-templates
In it's simplest form you would use it like:
var html = _.template('<li><%= name %></li>', { name: 'John Smith' });
//html is now '<li>John Smith</li>'
If you're going to be using a template a few times you'll want to compile it so it's faster:
var template = _.template('<li><%= name %></li>');
var html = [];
for (var key in names) {
html += template({ name: names[i] });
}
console.log(html.join('')); //Outputs a string of <li> items
I personally prefer the Mustache style syntax. You can adjust the template token markers to use double curly braces:
_.templateSettings.interpolate = /\{\{(.+?)\}\}/g;
var template = _.template('<li>{{ name }}</li>');
The documentation for templating is partial, I watched the source.
The _.template function has 3 arguments:
String text : the template string
Object data : the evaluation data
Object settings : local settings, the _.templateSettings is the global settings object
If no data (or null) given, than a render function will be returned. It has 1 argument:
Object data : same as the data above
There are 3 regex patterns and 1 static parameter in the settings:
RegExp evaluate : "<%code%>" in template string
RegExp interpolate : "<%=code%>" in template string
RegExp escape : "<%-code%>"
String variable : optional, the name of the data parameter in the template string
The code in an evaluate section will be simply evaluated. You can add string from this section with the __p+="mystring" command to the evaluated template, but this is not recommended (not part of the templating interface), use the interpolate section instead of that. This type of section is for adding blocks like if or for to the template.
The result of the code in the interpolate section will added to the evaluated template. If null given back, then empty string will added.
The escape section escapes html with _.escape on the return value of the given code. So its similar than an _.escape(code) in an interpolate section, but it escapes with \ the whitespace characters like \n before it passes the code to the _.escape. I don't know why is that important, it's in the code, but it works well with the interpolate and _.escape - which doesn't escape the white-space characters - too.
By default the data parameter is passed by a with(data){...} statement, but this kind of evaluating is much slower than the evaluating with named variable. So naming the data with the variable parameter is something good...
For example:
var html = _.template(
"<pre>The \"<% __p+=_.escape(o.text) %>\" is the same<br />" +
"as the \"<%= _.escape(o.text) %>\" and the same<br />" +
"as the \"<%- o.text %>\"</pre>",
{
text: "<b>some text</b> and \n it's a line break"
},
{
variable: "o"
}
);
$("body").html(html);
results
The "<b>some text</b> and
it's a line break" is the same
as the "<b>some text</b> and
it's a line break" and the same
as the "<b>some text</b> and
it's a line break"
You can find here more examples how to use the template and override the default settings:
http://underscorejs.org/#template
By template loading you have many options, but at the end you always have to convert the template into string. You can give it as normal string like the example above, or you can load it from a script tag, and use the .html() function of jquery, or you can load it from a separate file with the tpl plugin of require.js.
Another option to build the dom tree with laconic instead of templating.
I am giving a very simple example
1)
var data = {site:"mysite",name:"john",age:25};
var template = "Welcome you are at <%=site %>.This has been created by <%=name %> whose age is <%=age%>";
var parsedTemplate = _.template(template,data);
console.log(parsedTemplate);
The result would be
Welcome you are at mysite.This has been created by john whose age is 25.
2) This is a template
<script type="text/template" id="template_1">
<% _.each(items,function(item,key,arr) { %>
<li>
<span><%= key %></span>
<span><%= item.name %></span>
<span><%= item.type %></span>
</li>
<% }); %>
</script>
This is html
<div>
<ul id="list_2"></ul>
</div>
This is the javascript code which contains json object and putting template into html
var items = [
{
name:"name1",
type:"type1"
},
{
name:"name1",
type:"type1"
},
{
name:"name1",
type:"type1"
},
{
name:"name1",
type:"type1"
},
{
name:"name1",
type:"type1"
}
];
$(document).ready(function(){
var template = $("#template_1").html();
$("#list_2").html(_.template(template,{items:items}));
});
with express it's so easy. all what you need is to use the consolidate module on node so you need to install it :
npm install consolidate --save
then you should change the default engine to html template by this:
app.set('view engine', 'html');
register the underscore template engine for the html extension:
app.engine('html', require('consolidate').underscore);
it's done !
Now for load for example an template called 'index.html':
res.render('index', { title : 'my first page'});
maybe you will need to install the underscore module.
npm install underscore --save
I hope this helped you!
I wanted to share one more important finding.
use of <%= variable => would result in cross-site scripting vulnerability. So its more safe to use <%- variable -> instead.
We had to replace <%= with <%- to prevent cross-site scripting attacks. Not sure, whether this will it have any impact on the performance
Lodash is also the same
First write a script as follows:
<script type="text/template" id="genTable">
<table cellspacing='0' cellpadding='0' border='1'>
<tr>
<% for(var prop in users[0]){%>
<th><%= prop %> </th>
<% }%>
</tr>
<%_.forEach(users, function(user) { %>
<tr>
<% for(var prop in user){%>
<td><%= user[prop] %> </td>
<% }%>
</tr>
<%})%>
</table>
Now write some simple JS as follows:
var arrOfObjects = [];
for (var s = 0; s < 10; s++) {
var simpleObject = {};
simpleObject.Name = "Name_" + s;
simpleObject.Address = "Address_" + s;
arrOfObjects[s] = simpleObject;
}
var theObject = { 'users': arrOfObjects }
var compiled = _.template($("#genTable").text());
var sigma = compiled({ 'users': myArr });
$(sigma).appendTo("#popup");
Where popoup is a div where you want to generate the table