How to render URIs safely in erb - javascript

I have tried a variety of escaping and unescaping options, but don't understand why my erb is not rendering properly.
I have standard picture url in the format:
https://i.ytimg.com/vi/pB-5XG-DbAA/hqdefault.jpg;
entered into my controller as the following:
#whats_hot_last_vid = #whats_hot_last.video.picture
When I try to render it in my js.erb file:
var lastsongImg = <%= #whats_hot_last_vid %>;
Here's how it renders:
var lastsongImg = https://i.ytimg.com/vi/pB-5XG-DbAA/hqdefault.jpg;
From what I can see, it renders '//' in the http address as the beginning of a JS comment, leading to raising various errors like
Uncaught SyntaxError: Unexpected token :
Any ideas?

The best way to deal with variables like this in js blocks in erb is to use .to_json. This works with strings, arrays, and hashes (which it will convert to json objects). Basically it does the "right thing", and you don't need to worry about wrapping extra formatting around it depending on what type of object it is.
//string
var lastsongImg = <%= #whats_hot_last_vid.to_json %>;
// => var lastsongImg = "https://i.ytimg.com/vi/pB-5XG-DbAA/hqdefault.jpg";
//arrays
<% myArray = [1, "abc", 1.6] %>;
var myArrayJs = <%= myArray.to_json %>;
// => var myArrayJs = [1, "abc", 1.6];
//hashes
<% myHash = {:foo => "bar", :chunky => "bacon"} %>;
var myObject = <%= myHash.to_json %>;
// => var myObject = {"chunky":"bacon","foo":"bar"};
EDIT: as a side note, ActiveRecord objects have the to_js method as well, and produce an object like this:
{"car":{"id":123 "color":"red", "make":"Ford", "model": "Mondeo"}}
You can of course overwrite this for particular models if you want.

You have to put qoutes around the string, like this
var lastsongImg = "<%= #whats_hot_last_vid %>";
Hope it helps =)

Related

How to substitute a series of keys into a string being displayed using javascript?

I have a set of templates that contain key phrases denoted by %%key%% (could use different delimiters if these are a problem). In the code presented, the names of the templates are shown in a selector, and when selected, their values are presently being moved into a textarea for display. Before they are displayed, I wish to go through the template and replace each key with the value associated with that key.
I have tried using template.gsub 'key' 'value', template.gsub! 'key' 'value', and even template['key'] = 'value', to no avail. To eliminate other problems, I have tried using simple values for the 'value' and then displaying the result in an alert. If I don't try the replacement, the alert shows the template. If I try any of these attempts, I don't get the alert to show, indicating some kind of javascript error, I suppose. I can't figure out what the error is.
Here is a part of the application_helper.rb:
#----------------------------------------------------------------------------
def render_haml(haml, locals = {})
Haml::Engine.new(haml.strip_heredoc, format: :html5).render(self, locals)
end
#----------------------------------------------------------------------------
def create_template_selector
get_templates()
render_haml <<-HAML
%select{{name: "msg", id: "template_selector"}}
- #t_hash.each do |name,message|
%option{ :value => message }= name
HAML
end
#----------------------------------------------------------------------------
def get_templates()
templates = Template.all
#t_hash = Hash.new
templates.each do |t|
#t_hash[t.name] = t.message
end
end
and this is the view partial _text.html.haml, where the selector is embedded and its selection is presented when changed:
= form_for(#comment, remote: true, html: {id: "#{id_prefix}_new_comment"}) do |f|
= hidden_field_tag "comment[commentable_id]", commentable.id, id: "#{id_prefix}_comment_commentable_id"
= hidden_field_tag "comment[commentable_type]", class_name.classify, id: "#{id_prefix}_comment_commentable_type"
%div
%h3 Select a Template
= create_template_selector
%div
= f.text_area :comment, id: "#{id_prefix}_comment_comment", name:"text_msg"
.buttons
= image_tag("loading.gif", size: :thumb, class: "spinner", style: "display: none;")
= f.submit t(:add_note), id: "#{id_prefix}_comment_submit"
#{t :or}
= link_to(t(:cancel), '#', class: 'cancel')
:javascript
$(document).ready( function() {
$('#template_selector').change(function() {
var data= $('select').find('option:selected').val();
//
// Here is where I put alert(data) and it works
// but,
// var filled = data.gsub '%%first_name%%' 'Ralph'
// followed by alert(filled), shows no alert panel and no error
//
$("##{id_prefix}_comment_comment").val(data);
});
} );
How can I create a set of fill_ins like:
def fill_ins()
#fillins = Hash.new
#fillins['%%first_name%%'] = 'Ralph'
#fillins['%%last_name%%'] = 'Jones'
...
end
and create a function like:
def fill_in(template)
#fi = fill_ins()
#fi.each do 'fkey, fval'
template.gsub! fkey fval
end
end
and have it work?
In :javascript block, you should be writing JavaScript, not Ruby.
String#gsub is a Ruby method.
String.prototype.replace is a JavaScript method.
var filled = data.replace(/%%first_name%%/g, 'Ralph');
EDIT: Forgot that replace with a string only replaces once. Use regular expression with global flag instead.
Also: to pass the data from Ruby to JavaScript, use this pattern in your template:
<script>
const fillIns = <%= fill_ins.to_json %>;
</script>
Then you can either loop that array and run the replace method with each pair (not optimal) — or you can use a regular expression that picks up on the general pattern of the variable:
var filled = data.replace(/%%([^%]+)%%/g, (_, name) => fillIns[name]);

How to pass array from controller to javascript?

I want to pass array (2 dimension) from controller to javascript variable. I use session to pass but this not work. In javascript code I use like this:
var data = '<%= Session["LatLon"] %>';
when run project and use inspect element, there is in data :
how to pass? Can i Pass array with 2 dim with session?
When inserting the value into Session["LatLon"], save it as JSON instead of a C# string array.
string[][] mystringarr = ...
Session["LatLon"] = JsonConvert.SerializeObject(mystringarr);
And in the view use
var data = <%= Session["LatLon"] %>;
So it will generate something like
var data = [["1.0", "1.4"], ["4.6","4.8"]];
Using JSON.NET
http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_JsonConvert_SerializeObject.htm
What you are currently observing is a result of execution .ToString method of Session["LetLon"] object.
What you intent to receive is var data = [[1, 2], [3, 4]];.
So you can simply write a correct stringification of your two-dimensional array. I suggest to write simple extension method:
public static string ToJsString(this string[,] array) {
return Enumerable.Range(0, array.GetLength(0))
.Aggregate(new StringBuilder(),
(sbi, i) => sbi.AppendFormat(i == 0 ? "{0}" : ", {0}",
Enumerable.Range(0, array.GetLength(1))
.Aggregate(new StringBuilder(),
(sbj, j) => sbj.AppendFormat(j == 0 ? "{0}" : ", {0}", array[i,j]),
sbj => string.Format("[{0}]", sbj))),
sb => string.Format("[{0}]", sb));
}
In order to use it write then var data = <%= ((string[,])Session["LatLon"]).ToJsString() %>.

Correct proceedure for JSON/Javascript parsing in Rails?

In Rails, I am querying the database to build a data object for highcharts.
Here is my method from my controller:
def build_data_for_chart
data_array = Array.new
#data_array_as_json = ''
#issues.each {
|issue|
# returns an array of KeyIssue objects for a given issue
given_issue_array = KeyIssues.where(issue: issue).all
a = Array.new
#loop through each element extracting the timedate and % and add to new array
given_issue_array.each {
|entry|
a.push([entry.datetime.utc.to_date, entry.percentage])
}
#build the hash for an individual issue
temp_hash = {:name => issue, :data => a, :animation => false}
#add the individual issue and all its data to a final array that contains all the issues.
data_array.push(temp_hash)
}
#data_array_as_json = data_array.to_json.html_safe
end
Now I am trying to pull it out in a script in my view.
--- script ---
var data = $.parseJSON(<%= #data_array_as_json %>);
--- script ---
When I print to console, I can see the objects and all their data. Also when I print to html the output looks correct:
"[{\"name\":\"ABORIGINAL & NATIVE TITLE ISSUES\",\"data\":[[\"1993-11-01\",32],[\"1994-06-01\",27],[\"1994-09-01\",33],[\"1995-06-01\",26],[\"1995-09-01\",24],[\"1996-01-01\",20],[\"1996-09-01\",27],[\"1997-01-01\",33],[\"1997-06-01\",36],[\"1997-09-01\",36],[\"1998-01-01\",37],[\"1998-05-01\",33],[\"1998-09-01\",31],[\"1999-05-01\",30],[\"1999-09-01\",28],[\"2000-01-01\",30],[\"2000-05-01\",31],[\"2000-09-01\",34],[\"2001-01-01\",32],[\"2001-06-01\",29],[\"2001-09-01\",28],[\"2002-02-01\",25],[\"2002-06-01\",27],[\"2002-10-01\",25],[\"2003-02-01\",24],[\"2003-06-01\",26],[\"2003-10-01\",27],[\"2004-02-01\",27],[\"2004-06-01\",26],[\"2005-06-01\",30],[\"2006-06-01\",27],[\"2007-06-01\",31],[\"2008-07-01\",29]],\"animation\":false}]"
But when I go to print the data variable it is null (obviously due to not being valid input). What am I messing up?
FYI..
I needed to wrap it in single quotes.. to make it work..
$.parseJSON(' <%= #data_array_as_json %> ');
You can try this:
<script type="text/javascript">
var data = <%== data_array.to_json %>
</script>

Ignoring undefined data / vars in an underscore template

Still learning backbone so bear with me;
I'm trying to add a new model with blank fields to a view, but the template I've created has a whole bunch of
<input value="<%= some_value %>" type="whatever" />
Works perfectly fine when fetching data, it populates it and all goes well. The trouble arises when I want to create a new (blank) rendered view, it gives me
Uncaught ReferenceError: some_value is not defined
I can set defaults (I do already for a few that have default values in the db) but that means typing out over 40 of them with blanks; is there a better way of handling this?
I'm fiddling around with the underscore template itself, trying something like <%= if(some_value != undefined){ some_value } %> but that also seems a bit cumbersome.
Pass the template data inside a wrapper object. Missing property access won't throw an error:
So, instead of:
var template = _.template('<%= foo %><%= bar %>');
var model = {foo:'foo'};
var result = template(model); //-> Error
Try:
var template = _.template('<%= model.foo %><%= model.bar %>');
var model = {foo:'foo'};
var result = template({model:model}); //-> "foo"
Actually, you can use arguments inside of your template:
<% if(!_.isUndefined(arguments[0].foo)) { %>
...
<% } %>
No,
There is no actual fix for this due to the way underscore templates are implemented.
See this discussion about it:
I'm afraid that this is simply the way that with(){} works in JS. If the variable isn't declared, it's a ReferenceError. There's nothing we can do about it, while preserving the rest of template behavior.
The only way you can accomplish what you're looking for is to either wrap the object with another object like the other answer suggested, or setting up defaults.
If you check source code for generated template function, you will see something like this:
with (obj||{}) {
...
// model property is used as variable name
...
}
What happens here: at first JS tries to find your property in "obj", which is model (more about with statement). This property is not found in "obj" scope, so JS traverses up and up until global scope and finally throws exception.
So, you can specify your scope directly to fix that:
<input value="<%= obj.some_value %>" type="whatever" />
At least it worked for me.
Actually, you can access vars like initial object properties.
If you'll activate debugger into template, you can find variable "obj", that contains all your data.
So instead of <%= title %> you should write <%= obj.title %>
lodash, an underscore replacement, provides a template function with a built-in solution. It has an option to wrap the data in another object to avoid the "with" statement that causes the error.
Sample usage from the API documentation:
// using the `variable` option to ensure a with-statement isn’t used in the compiled template
var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
compiled.source;
// → function(data) {
// var __t, __p = '';
// __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
// return __p;
// }
A very simple solution: you can ensure that your data collection is normalized, i.e. that all properties are present in each object (with a null value if they are unused). A function like this can help:
function normalizeCollection (collection, properties) {
properties = properties || [];
return _.map(collection, function (obj) {
return _.assign({}, _.zipObject(properties, _.fill(Array(properties.length), null)), obj);
});
}
(Note: _.zipObject and _.fill are available in recent versions of lodash but not underscore)
Use it like this:
var coll = [
{ id: 1, name: "Eisenstein"},
{ id: 2 }
];
var c = normalizeCollection(coll, ["id", "name", "age"]);
// Output =>
// [
// { age: null, id: 1, name: "Eisenstein" },
// { age: null, id: 2, name: null }
// ]
Of course, you don't have to transform your data permanently – just invoke the function on the fly as you call your template rendering function:
var compiled = _.template(""); // Your template string here
// var output = compiled(data); // Instead of this
var output = compiled(normalizeCollection(data)); // Do this
You can abstract #Dmitri's answer further by adding a function to your model and using it in your template.
For example:
Model :
new Model = Backbone.Model.extend({
defaults: {
has_prop: function(prop) {
return _.isUndefined(this[property]) ? false : true;
}
}
});
Template:
<% if(has_prop('property')) { %>
// Property is available
<% } %>
As the comment in his answer suggests this is more extendable.

Access ruby array in javascript

I want to access a Ruby array in javascript. Please tell me the method to do that. My array is holding the result of a sql query.
#contacts = Contact.order("contacts.position ASC")
I am trying to do this....
for(var i=0; i< a; i++)
{
var firstnameV = "<%=Contact.order('contacts.position ASC')[i].first_name%>";
var lastnameV = "<%=Contact.order('contacts.position ASC')[i].last_name%>";
var emailV = "<%=Contact.order('contacts.position ASC')[i].email%>";
var contactV = parseInt("<%=Contact.order('contacts.position ASC')[i].contact_no%>";
var posV = parseInt("<%=Contact.order('contacts.position ASC')[i].position%>";
tx.executeSql('INSERT INTO contact_table (firstname, lastname, email, contact, pos)
VALUES (firstnameV,lastnameV, emailV, contactV, posV)');
}
Quick example of how you can render the value of Ruby variable to JavaScript. Add <%= yield :head %> to head tag in views/layouts/application.html.erb. Then in views/contacts/index.erb (or whatever view you use) add the following:
<%content_for :head do %>
<script type="text/javascript">
window.onload = function() {
alert("First contact in database is <%=Contact.order('contacts.position ASC').first.name%>")
}
</script>
<%end%>
This will alert the first contact name from your database.
You can do this by using the
to_json
method in Ruby
or
render :json => #contacts
Ruby is server side language. JavaScript is mostly (server side also - e.g. node.js) client side. If you want to pass values from Ruby to JS, you could render that value as part of view in script tags or retrieve them via AJAX.

Categories