how to access javascript variable in rails view - ruby-on-rails

Is there any way to access javasript variable inside the rails view.
I just set some value of javascript variable and want to access it in rails view.
Thanks

You have to understand that ruby code in your views gets executed on the server - before any javascript on the page gets a change to be executed.
That's why you cannot do stuff like this:
<script>
var js_var = 1;
</script>
<%= get_value_of_js_var_somehow %>
The other way round it works:
<script>
var js_var = <% generate_value_with_ruby %>;
do_something_in_javascript_with_js_var();
</script>

You can pass a javascript variable to rails using AJAX. For example if you want to pass the id for an user to a method in a rails controller from javascript you can execute the following code:
<script>
var id = 1;
<%= remote_function :url => {:controller=>'controller_name', :action=>'method_name'}, :with => "'user_id=' + id" %>
</script>
You will receive the variable through a POST request, as a parameter. You can access it using params[:user_id].
def method_name
if params[:user_id].exists?
u = User.where('id = ?', params[:user_id]).first
end
puts u.path
end
Hope I've answered your question.

If you have this Javascript variable in a higher piece of javascript (per se: the application.js), then you can always just reference it in the view.
#application.js
var someVar = "Hello World";
Then in your view (executed on the client), you could to...
<script type='text/javascript'>
alert(someVar);
</script>
I think we need more specific ellaboration if one of these three posts doesn't answer your question.

Suppose we are having script as follows
<script type="text/javascript">
var a="some value";
document.getElementById('tagid').innerHTML='<%= tag(:div,content_tag(:p,' " +a+ " '), :id=>' " +a+ " ', name=>"somename") %>';
</script>

Related

Ruby on Rails script not being loaded

In my ruby on rails application, I'm generating a view via an ajax call.
I'm using following piece of code.
$("#a_div_id").html("<%= escape_javascript(render 'index')%>");
And the view I'm trying to render is _index.html.erb:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
First Name: <input type="text" ng-model="firstName"><br>
Last Name: <input type="text" ng-model="lastName"><br>
<br>
Full Name: {{firstName + " " + lastName}}
</div>
<script>
alert('first');
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
alert('second');
$scope.firstName = "John";
$scope.lastName = "Doe";
});
alert('third');
</script>
When I render the view, I'm getting only first and third messages. However, when I add this piece of code into dashboard.html.erb rather than rendering it via the ajax code, it perfectly works.
In the first case, I'm getting the following error.
angular.min.js:6 Uncaught Error: [$injector:modulerr] http://errors.angularjs.org/1.4.8/$injector/modulerr?p0=myApp&p1=Error%3A%2…ogleapis.com%2Fajax%2Flibs%2Fangularjs%2F1.4.8%2Fangular.min.js%3A20%3A274)
at angular.min.js:6
at angular.min.js:38
at n (angular.min.js:7)
at g (angular.min.js:37)
at eb (angular.min.js:41)
at c (angular.min.js:19)
at yc (angular.min.js:20)
at Zd (angular.min.js:19)
at HTMLDocument.<anonymous> (angular.min.js:294)
at fire (jquery.self-bd7ddd3….js?body=1:3233)
I'm be at my wits' end, I couln't decide what I'm missing,
Any suggestions,
Thanks.
​
Can you try without escape_javascript.
Also put a debugger like this, this will put a break point when chrome executes the js.
debugger;
$("#a_div_id").html("<%= render 'index' %>");
That would help you see what exactly jquery is trying to add as HTML.
If this doesn't work,
Probably you should take the js in script tag and put it in some method and call that method after you have added index to dom.
$("#a_div_id").html("<%= render 'index' %>");
myMethodToRenderAngular();

Rails 4 - Moving js inline script inside View to JS file

I would like to move some js inline scripts I have on my homepage to a javascript file (in Assets) but there is some complexity due to variables.
home.html.erb
<div>
this is the homepage
</div>
<script>
<% #deal.deal_details.each_with_index do |popin, index| %>
<% index_plus_one = index + 1 %>
function loadInfoPopin() {
var msg;
msg = Messenger().post({
message: '<%= j render partial: "deals/info_popin/info_popin#{ popin['popin_id'] }",
locals: { popin: popin, index: index_plus_one } %>'
});
}
<% end %>
</script>
For the sake of information here is the format of the Deal's column/attribute 'deal_details' (it's a json attribute):
[{"popin_id":"4","text1":"qqq","text2":"sqsq","image1":"sqqs"},{"popin_id":"5","text1":"sqqs","video1":"s"}]
This is an example and you can have as many json block inside the array as possible.
deals/info_popin/info_popin5.html.erb (it's an example)
<div>
<p>cool image</p>
</div>
</div>
Now, how can I move the whole script or at least the function loadInfoPopin() to a javascript file (that is to say away from the view home.html.erb) ?
Thanks
How about moving the loadInfoPopin() function into a separate JS file and altering the function slightly to take in an argument for the html message?
function loadInfoPopin(html_message) {
Messenger().post({
message: html_message
});
Setup the html for the message within the loop before passing into and calling loadInfoPopin.
.js.erb is an option. This way the js files will be parsed before compiling. Not sure though it will work the way you want.
I would recommend to leave the variables in the layout as vars and moving only the static JS parts to the assets.

How to safely embed JSON with </script> in HTML document?

In a Rails 3.1 app, how can I safely embed some JSON data into an HTML document?
Suppose I have this in a controller action:
#tags = [
{name:"tag1", color:"green"},
{name:"</script><b>I can do something bad here</b>", color:"red"}
]
And this in a corresponding view:
<script type="text/javascript" charset="utf-8">
//<![CDATA[
var tags_list = <%= #tags.to_json %>;
// ]]>
</script>
Then I get this in resulting HTML:
var tags_list = [
{"name":"tag1","color":"green"},
{"name":"</script><b>I can do something bad here</b>","color":"red"}
];
which triggers a SyntaxError: Unexpected token & in Chrome
If I remove the Rails' default HTML escaping with <%=raw tags.to_json
%>, then it returns this:
var tags_list = [
{"name":"tag1","color":"green"},
{"name":"</script><b>I can do something bad here</b>","color":"red"}
];
which, of course, breaks the HTML document with </script>.
Can I somehow tell to_json() method to return something more like this:
var tags_list = [
{"name":"tag1","color":"green"},
{"name":"</script><b>I can do something bad here</b>","color":"red"}
];
I asked this question on rubyonrails-talk mailing list, and I understand now that some people think that's a very bad idea to begin with, but in my case it works very nicely, as long as there are no HTML special chars in the data. So I just want to make the string returned by to_json HTML safe and still have JavaScript parse it properly.
UPDATE:
Based on #coreyward comment, I did make it a JS string literal, and that seems to be working great now. Its not quite as elegant of a solution as I was hoping for, but its not too bad either. Here is the code that is working for me:
<% tags = [{name:"tag1", color:"green"}, {name:"</script><b>I can \n\ndo something bad here</b>", color:"red"}] %>
<script type="text/javascript" charset="utf-8">
//<![CDATA[
var tags_list = $.parseJSON('<%=j tags.to_json.html_safe %>');
// ]]>
</script>
which results in:
<script type="text/javascript" charset="utf-8">
//<![CDATA[
var tags_list = $.parseJSON('[{\"name\":\"tag1\",\"color\":\"green\"},{\"name\":\"<\/script><b>I can \\n\\ndo something bad here<\/b>\",\"color\":\"red\"}]');
// ]]>
</script>
Your code using just #tags.to_json works in rails3, if you enable it with:
ActiveSupport.escape_html_entities_in_json = true
Otherwise, your other option is this:
var tags_list = <%= raw #tags.to_json.gsub("</", "<\\/") %>;
This saves the client having to parse the whole thing through $
The proper way in 2019 is to wrap obj.to_json with json_escape function. json_escape is directly intended for escaping specific HTML symbols inside JSON strings. Example below from the documentation:
json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
# => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
json_escape(json)
# => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
JSON.parse(json) == JSON.parse(json_escape(json))
# => true
It seems this page appears on top of Google Search results, that's why I decided to provide a comment with an update :)
btw, this works but is not a good solution in my opinion:
<script type="text/javascript" charset="utf-8">
//<![CDATA[
var tags_list = <%=raw #tags.to_json.gsub('/', '\/') %>;
// ]]>
</script>
I think that if you try this it will work:
var tags_list = "<%== #tags.to_json.gsub('/', '\/') %>";
(Notice the double == and the " ")
For instance with this in app/layouts/application.html.slim:
javascript:
window.translations = #{raw t("js").to_json};
And this in the translations:
js:
name:
must_be_present: Must be present<script>alert(1)</script>
The result will be escaped:
<script>window.translations = {"name":{"must_be_present":"Must be present\u003cscript\u003ealert(1)\u003c/script\u003e"}};</script>

Inline client side validation with MVC and jQuery

I have setup a simple example to show a form inside a jquery UI dialog and wish to enable inline client side validation on that form
I have then added the scripts to my master page
<script type="text/javascript" src="<%: Url.Content( "~/_assets/js/jquery-1.4.3.min.js" )%>"></script>
<script type="text/javascript" src="<%: Url.Content( "~/_assets/js/jquery.validate.min.js" )%>"></script>
<script type="text/javascript" src="<%: Url.Content( "~/_assets/js/MicrosoftMvcJQueryValidation.js" ) %>"></script>
and then I have enabled Client Side Validation through the following code
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm() { %>
<% } %>
Then, I dont know how to enable inline validation for every input so when the user leaves the focus from any of them validation occurs.
The client side validation seems to work only after I have done a submit. But that is not a "client side validation" as the attributes get validated from my server code...
Any suggestion?
Finally I have got through the solution.
First of all, my forms were never binded to validation callbacks provided by the code inside the MicrosoftMvcJQueryValidation.js script. This because I am using jQuery dialogs and the form is inside the dialog while the script included in the master page.
My first attempt toward the solution has been to modify the MicrosoftMvcJQueryValidation.js. In particular I have added a function EnableClientSideValidation() where I moved the code that was in the $(document).ready function as in the following code sample
function EnableClientSideValidation() {
var allFormOptions = window.mvcClientValidationMetadata;
if (allFormOptions) {
while (allFormOptions.length > 0) {
var thisFormOptions = allFormOptions.pop();
__MVC_EnableClientValidation(thisFormOptions);
}
}
}
$(document).ready(function () {
EnableClientSideValidation();
});
Then I have called the same function inside a script block that I have placed in the dialog markup code $(document).ready() function
With the help of firebug I have placed a breakpoint inside the EnableClientSideValidation() function and then experienced the fact that was called only when the main page was ready but not from the dialog. This was due to the fact that I had my "dialog" script block inside the <form>...</form> tag and so the script did not worked.
Code like this
<% using (Html.BeginForm()) { %>
//DIALOG FORM CODE WAS HERE
<script type="text/javascript">
$(document).ready(function () {
EnableClientSideValidation();
});
</script>
<% } %>
has been changed to
<% using (Html.BeginForm()) { %>
//DIALOG FORM CODE WAS HERE
<% } %>
<script type="text/javascript">
$(document).ready(function () {
EnableClientSideValidation();
});
</script>
Finally everything started working! I would like to thanks vandalo and kdawg for helping in finding a solution. There was something still missed but your answers have stimulated my head.
I am posting this for other that can have the same problem.
OK, so here's what I did to get MicrosoftMvcJQueryValidation to work for me in an AJAX/PartialView environment. It's relevant, because essentially both instances (my AJAX/PartialView stuff and your onBlur triggering) require explicit control of when the validation methods are called. I'll try my best to capture everything you need to do, because I ended up having to edit my MicrosoftMvcJQueryValidation.js file to get it AJAX-enabled. However, I don't believe any of my edits are required for what you want.
The key lies in being able to access the validation functions that MicrosoftMvcJQuery generates. Fortunately, it adds it to the form element via a property called validationCallbacks.
In my custom submit function, I access and call these callbacks like this (form is the DOM element, not a jQuery object):
// this taps into the mvc clientside validation functionality.
// this is a roundabout way of calling jquery.validate() as
// that is what's going on the in callback() function
validationCallbacks = form.validationCallbacks;
if (validationCallbacks) {
for (i = 0; i < validationCallbacks.length; i += 1) {
callback = validationCallbacks[i];
if (!callback()) {
// subsequent submit handlers should check for
// this value before executing
event.cancelBubble = true;
return false;
}
}
}
I then have my context-specific submit functions check event.cancelBubble before continuing.
For your case, you could have this code be called on the blur event for each input in your form. Granted, it's not the most efficient solution, as each function in the validationCallbacks array validates the entire form, but it will trigger validation on each blur. (validationCallbacks is an array to support multiple forms that require validation.)
Sorry it's not super specific to your situation, but it should get what you need.
I have my earlier answer about how to manually call the validation callbacks created by MicrosoftMvcJQueryValidation.js, however, there may be a simpler answer. (I'm leaving my first answer as future reference for anyone.)
The options for jQuery's Validation plug-in give you the ability to change which event triggers validation. From http://docs.jquery.com/Plugins/Validation/validate#toptions, we have the following option properties: onsubmit, onfocusout, and onkeyup. You should be able assign these options values appropriately and have jQuery Validation behave like you want.
You MAY need to tweak MicrosoftMvcJQueryValidation.js to allow for the setting of options for when it calls validation. I had to do that with my edited copy.
You can follow this example:
There's a problem with the script in MicrosoftMvcJQueryValidation.js which must be updated.
Change the script MicrosoftMvcValidation.js in the step 3.
Model:
Namespace Models
Public Class Customer
Private _Name As String = ""
<DisplayName("Name")> _
<Required(ErrorMessage:="{0}: Mandatory field.")> _
<StringLength(10, ErrorMessage:="{0}: Max lenght 10.")> _
Public Property Name() As String
Get
Return _Name
End Get
Set(ByVal value As String)
_Name = value
End Set
End Property
Private _Surname As String = ""
<DisplayName("Surname")> _
<Required(ErrorMessage:="{0}: Mandatory field.")> _
<StringLength(10, ErrorMessage:="{0}: Max lenght 10.")> _
Public Property Surname() As String
Get
Return _Surname
End Get
Set(ByVal value As String)
_Surname = value
End Set
End Property
End Class
End Namespace
<%# Page Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage(Of MvcApplication1.Models.Customer)" %>
<%# Import Namespace="MvcApplication1.jQuery" %>
...
<% Html.EnableClientValidation()%>
<% Using (Html.BeginForm())%>
<fieldset id="FormEditSet">
<div>
<div>
<%=Html.LabelFor(Function(m) m.Name)%>
<%=Html.EditorFor(Function(m) m.Name)%>
<%=Html.ValidationMessageFor(Function(m) m.Name, "*")%>
</div>
<div>
<%=Html.LabelFor(Function(m) m.Surname)%>
<%=Html.EditorFor(Function(m) m.Surname)%>
<%=Html.ValidationMessageFor(Function(m) m.Surname, "*")%>
</div>
</div>
</fieldset>
<input type="image" src="<%=Url.Content("~/Content/Images/page_save_big.png")%>"
value="Save" title="Save" style="border: none;" />
<%End Using%>
Html.ValidationSummaryJQuery is a new extension method you have to define (follow the example).
Remember to put the script at the bottom of the page:
<script src="<%=Url.Content("~/Scripts/MicrosoftAjax/MicrosoftMvcJQueryValidation.min.js")%>" type="text/javascript"></script>
You need to bind your input fields to properties in your controller, then use the Required attribute on your properties - see http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx for an example.

yui_editor not loaded when loading the partial via an ajax call

In a Rails app, I am loading a partial via an ajax call. (still using prototype)
The partial is a form that contains a textarea enriched with the yahoo yui_editor (similar to tinyMCE or FCKEditor)
<%= f.text_area :body, :class => 'rich_text_editor', :rows => "15", :style => "width : 90%;" %>
The yui_editor is not loaded and the textarea content is displayed as simple text when the form is loaded via an ajax call.
I tested that the yui_editor is active when the same partial is loaded directly without any ajax calls.
I know this has to do with the fact that the yui_editor javascript is not loaded but I have no idea how to solve this issue
Your help will be very much appreciated
Thanks
You need to start the YUI editor. Since the editor needs the id of the element, you need to specify a unique id in your partial.
See the YUI doc for more on the editor's parameters
Added
Are you adding the div via Ajax? In that case, you need to make the call to the YUI editor library after the div is added. Two ways to do that:
1) Your code which does the insert into the dom (with the results of the Ajax call) needs to explicitly call the YUI editor. Eg your Ajax results could include the element id of the text area, you could already know it in advance, etc.
2) You could include the script for calling the YUI editor in your Ajax results. But then you'll need to run the script(s) in the html after you've added them to the dom.
Setting innerHTML property of an element does NOT run any scripts in the html. But I have a script which does, see below.
The script is based on this SO Question
... do ajax call and get results in <body>
foo_el.innerHTML = body; // add results to the dom
exec_body_scripts(foo_el); // run any scripts in foo_el
//////////////////////////////////
function exec_body_scripts(body_el) {
// Finds and executes scripts in the dialog's body.
// Needed since innerHTML does not run scripts.
// NB: Only looks for scripts that are children or grandchildren of body_el.
// Doesn't look deeper.
function evalScript(elem) {
var data = (elem.text || elem.textContent || elem.innerHTML || "" ),
head = document.getElementsByTagName("head")[0] ||
document.documentElement,
script = document.createElement("script");
script.type = "text/javascript";
try {
script.appendChild(document.createTextNode(data)); // doesn't work on ie
} catch(e) {
// IE has funky script nodes
script.text = data;
}
head.insertBefore(script, head.firstChild);
head.removeChild(script);
};
// main section of function
var scripts = body_el.getElementsByTagName('SCRIPT'), i;
for (i = 0; scripts[i]; i++) {
evalScript(scripts[i]);
}
};
Partial example:
<% el_id = "rte_#{foo.id}"
# foo is the name of an object used by the partial. Using its id
# to ensure a unique id for the element on the page.
# Or use a simple counter "i". But in any case, the el_id must be unique
%>
<%= f.text_area :body, :class => 'rich_text_editor', :rows => "15",
:style => "width : 90%;", :id => el_id %>
<script>
(function() {
var myEditor = new YAHOO.widget.Editor('<%= el_id %>', {
height: '300px',
width: '522px',
dompath: true, //Turns on the bar at the bottom
animate: true //Animates the opening, closing and moving of Editor windows
});
myEditor.render();
})();
</script>

Resources