Select & onChange | Ruby on Rails - ruby-on-rails

In my view, I have this form with the select function:
<form id="form_id">
<%= form.select('id','name', #document.informations.find(:all).collect {|u| [u.name] },
options={},{:onChange => 'submit()'}) -%>
</form>
How can I use the selected name in the rest of my view ?
I saw this in other topics:
$('#id_name').val();
But it didn't work for me, it says:
`$(' is not allowed as a global variable name

You're getting that particular error because the $('#id_name').val() is JavaScript, not Ruby, but you've put it within erb tags.
When you visit a page /documents/3 in your browser, Rails will run the code in your controller, and send back some HTML to your browser. That HTML can load CSS and JavaScript, but that's run after your Ruby program has finished - and may not be run at all, depending on the browser. How you use the name of the selected item in your view depends on what you're doing with it.
If you're storing it in the database somewhere, then you should start by getting this working just in Ruby. For instance, if your #document has a selected_informations attribute, you could use that in the rest of your page, and pass it to your form.select to pre-select it in the page. The Rails Guides documentation has more info on this.
If you're not storing it in the database, then you can get the value of your box out with JavaScript whenever it changes. Here's some sample code that prints out the name of the selected item to the JavaScript console whenever a <select> box gets changed. I've included it in <script> tags so you can drop it straight into your view for testing, but you should put it into a dedicated JavaScript file if you adapt it for your project.
<script>
$('select').on('change', function(ev) {
var selected_item = $(ev.currentTarget).val();
console.log("Your select value is " + selected_item);
});
</script>
One final thing to note is that your existing form.select tag is set up to call a method submit() whenever its value gets changed. submit is just a name, so that function could do anything... but I'd guess it's submitting the form whenever an item is selected. This sends a request to your Rails server and refreshes the page, so beware - if you're using an event listener on change to update the current page, you won't see those changes on the new, refreshed page (and should use a server-side solution instead).

Related

React Component not rendered properly with Turbolinks in Rails 5.1

I have a very simple Rails app with a react component that just displays "Hello" in an existing div element in a particular page (let's say the show page).
When I load the related page using its URL, it works. I see Hello on the page.
However, when I'm previously on another page (let's say the index page and then I go to the show page using Turbolinks, well, the component is not rendered, unless I go back and forth again. (going back to the index Page and coming back to the show page)
From here every time I go back and forth, I can say that the view is rendered twice more time.Not only twice but twice more time! (i.e. 2 times then 4, then 6 etc..)
I know that since in the same time I set the content of the div I output a message to the console.
In fact I guess that going back to the index page should still run the component code without the display since the div element is not on the index page. But why in a cumulative manner?
The problems I want to solve are:
To get the code run on the first request of the show page
To block the code from running in other pages (including the index page)
To get the code run once on subsequent requests of the show page
Here the exact steps and code I used (I'll try to be as concise as possible.)
I have a Rails 5.1 app with react installed with:
rails new myapp --webpack=react
I then create a simple Item scaffold to get some pages to play with:
rails generate scaffold Item name
I just add the following div element in the Show page (app/views/items/show.html.erb):
<div id=hello></div>
Webpacker already generated a Hello component (hello_react.jsx) that I modified as following in ordered to use the above div element. I changed the original 'DOMContentLoaded' event:
document.addEventListener('turbolinks:load', () => {
console.log("DOM loaded..");
var element = document.getElementById("hello");
if(element) {
ReactDOM.render(<Hello name="React" />, element)
}
})
I then added the following webpack script tag at the bottom of the previous view (app/views/items/show.html.erb):
<%= javascript_pack_tag("hello_react") %>
I then run the rails server and the webpack-dev-server using foreman start (installed by adding gem 'foreman' in the Gemfile) . Here is the content of the Procfile I used:
web: bin/rails server -b 0.0.0.0 -p 3000
webpack: bin/webpack-dev-server --port 8080 --hot
And here are the steps to follow to reproduce the described behavior:
Load the index page using the URL http://localhost:3000/items
Click New Item to add a new item. Rails redirects to the item's show page at the URL localhost:3000/items/1. Here we can see the Hello React! message. It works well!
Reload the index page using the URL http://localhost:3000/items. The item is displayed as expected.
Reload the show page using the URL http://localhost:3000/items/1. The Hello message is displayed as expected with one console message.
Reload the index page using the URL http://localhost:3000/items
Click to the Show link (should be performed via turbolink). The message is not shown neither the console message.
Click the Back link (should be performed via turbolink) to go to the index page.
Click again to the Show link (should be performed via turbolink). This time the message is well displayed. The console message for its part is shown twice.
From there each time I go back to the index and come back again to the show page displays two more messages at the console each time.
Note: Instead of using (and replacing) a particular div element, if I let the original hello_react file that append a div element, this behavior is even more noticeable.
Edit: Also, if I change the link_to links by including data: {turbolinks: false}. It works well. Just as we loaded the pages using the URLs in the browser address bar.
I don't know what I'm doing wrong..
Any ideas?
Edit: I put the code in the following repo if interested to try this:
https://github.com/sanjibukai/react-turbolinks-test
This is quite a complex issue, and I am afraid I don't think it has a straightforward answer. I will explain as best I can!
To get the code run on the first request of the show page
Your turbolinks:load event handler is not running because your code is run after the turbolinks:load event is triggered. Here is the flow:
User navigates to show page
turbolinks:load triggered
Script in body evaluated
So the turbolinks:load event handler won't be called (and therefore your React component won't be rendered) until the next page load.
To (partly) solve this you could remove the turbolinks:load event listener, and call render directly:
ReactDOM.render(
<Hello name="React" />,
document.body.appendChild(document.createElement('div'))
)
Alternatively you could use <%= content_for … %>/<%= yield %> to insert the script tag in the head. e.g. in your application.html.erb layout
…
<head>
…
<%= yield :javascript_pack %>
…
</head>
…
then in your show.html.erb:
<%= content_for :javascript_pack, javascript_pack_tag('hello_react') %>
In both cases, it is worth nothing that for any HTML you add to the page with JavaScript in a turbolinks:load block, you should remove it on turbolinks:before-cache to prevent duplication issues when revisiting pages. In your case, you might do something like:
var div = document.createElement('div')
ReactDOM.render(
<Hello name="React" />,
document.body.appendChild(div)
)
document.addEventListener('turbolinks:before-cache', function () {
ReactDOM.unmountComponentAtNode(div)
})
Even with all this, you may still encounter duplication issues when revisiting pages. I believe this is to do with the way in which previews are rendered, but I have not been able to fix it without disabling previews.
To get the code run once on subsequent requests of the show page
To block the code from running in other pages (including the index page)
As I have mentioned above, including page-specific scripts dynamically can create difficulties when using Turbolinks. Event listeners in a Turbolinks app behave very differently to that without Turbolinks, where each page gets a new document and therefore the event listeners are removed automatically. Unless you manually remove the event listener (e.g. on turbolinks:before-cache), every visit to that page will add yet another listener. What's more, if Turbolinks has cached that page, a turbolinks:load event will fire twice: once for the cached version, and another for the fresh copy. This is probably why you were seeing it rendered 2, 4, 6 times.
With this in mind, my best advice is to avoid adding page-specific scripts to run page-specific code. Instead, include all your scripts in your application.js manifest file, and use the elements on your page to determine whether a component gets mounted. Your example does something like this in the comments:
document.addEventListener('turbolinks:load', () => {
var element = document.getElementById("hello");
if(element) {
ReactDOM.render(<Hello name="React" />, element)
}
})
If this is included in your application.js, then any page with a #hello element will get the component.
Hope that helps!
I was struggling with similar problem (link_to helper method was changing URL but react content was not loaded; had to refresh page manually to load it properly). After some googling I've found simple workaround on this page.
<%= link_to "Foo", new_rabbit_path(#rabbit), data: { turbolinks: false } %>
Since this causes a full page refresh when the link is clicked, now my react pages are loaded properly. Maybe you will find it useful in your project as well :)
Upon what you said I tested some code.
First, I simply pull out the ReactDOM.render method from the listener as you suggested in your first snippet.
This provide a big step forward since the message is no longer displayed elsewhere (like in the index page) but only in the show page as wanted.
But something interesting happen in the show page. There is no more accumulation of the message as appended div element, which is good. In fact it's even displayed once as wanted. But.. The console message is displayed twice!?
I guess that something related to the caching mechanism is going on here, but since the message is supposed to be appended why it isn't displayed twice as the console message?
Putting aside this issue, this seems to work and I wonder why it's necessary in the first place to put the React rendering after the page is loaded (without Turbolinks there was the DOMContentLoaded event listener)?
I guess that this has do with unexpected rendering by javascript code executed when some DOM elements are yet to be loaded.
Then, I tried your alternative way using <%= content_for … %>/<%= yield %>.
And as you expected this give mitigate results ans some weird behavior.
When I load via the URL the index page and then go to the show page using the Turbolink, it works!
The div message as well as the console message are shown once.
Then if I go back (using Turbolink), the div message is gone and I got the ".. unmounted.." console message as wanted.
But from then on, whenever I go back to the show page, the div and the console message are both never displayed at all.
The only message that's displayed is the ".. unmounted.." console message whenever I go back to the index page.
Worse, if I load the show page using the URL, the div message is not displayed anymore!? The console message is displayed but I got an error regarding the div element (Cannot read property 'appenChild' of null).
I will not deny that I completely ignore what's happening here..
Lastly, I tried your last best advice and simply put the last code snippet in the HTML head.
Since this is jsx code, I don't know how to handle it within the Rails asset pipeline / file structure, so I put my javascript_pack_tag in the html head.
And indeed, this works well.
This time the code is executed everywhere so it makes sense to use page-specific element (as previously intended in the commented code).
The downside, is that this time the code could be messy unless I put all page-specific code inside if statements that test for the presence of the page-specific element.
However since Rails/Webpack has a good code structure, it should be easily manageable to put page-specific code into page-specific jsxfiles.
Nevertheless the benefit is that this time all the page-specific parts are rendered at the same time as the whole page, thus avoiding a display glitch that occurs otherwise.
I didn't address this issue at the first place, but indeed, I would like to know how to get page specific contents rendered at the same time as the whole page.
I don't know if this is possible when combining Turbolink with React (or any other framework).
But in conclusion I leave this question for later on.
Thank you for your contribution Dom..

Struts 2 - Tiles-Result with HTML Anchor

is it somehow possible to add an anchor (or hash) to a Struts 2 Action-URL? To be specific:
I have a html form which can be extended with more fields if the user clicks a button "add more fields". This Button sends a html submit to the backend (Action "thismyaction") where a list object is filled with another set of input fields. The action then returns to a tile "thisismyform" which loads the same jsp as before, where the new fieldset is visible.
(Unfortunately there is no way to achieve that via ajax / JS in this project at the time. I know that you usually add fields that way, but i got the project as it is.)
Each fieldset is counted (fieldset-0, if user adds more fields another set is added "fieldset-1"). The sets always contain the same fields, but enumerated.
What happens here? There is a post to the action which generates another fieldset and redirects back to the same page, where it renders all fieldsets. Important: the result type is "tiles"! I guess this is what makes it difficult.
Now I want to dynamically add an anchor to this URL "thisismyaction.do", like e.g. "thisismyaction.do#fieldset-1". Use Case: User adds another fieldset, post to the action => result type="tiles" => JSP, user sees reload of the page with the new, second fieldset and gets "scrolled" to the second fieldset via the anchor. Is that possible?
I hope I could describe it properly, what I want to achieve... If there are questions, feel free to ask.
Simplest way I can think of right away is
<script type="text/javascript">
window.location.hash = "fieldset-<s:text name="latestIdOfFieldSet" />";
</script>
If you can figure out what's gonna be latestIdOfFieldSet before you render your jsp and set it in your action method. you should get what you are trying to do.

Sending form contents as both html and ajax?

I have an admin form that updates a model via a html submit. I'd like to be able to send the form's contents to an Ajax modal dialog for a 'preview' via a link or button in the admin form.
Is there a way to send the form's contents to the modal dialog via Ajax without breaking the html submit? So far all I can do is get the data into the modal as html which breaks the js rendering. All the Ajax submit examples I find attach to the form which will break the html submit.
Suggestions and/or pointers are appreciated.
We are using Rails 3.2.12 for what it's worth.
I suppose it depends on how you are rendering your modal. If you're doing it server side and just need to get the form values to your ajax controller action you could do something like this with jquery"
$.post(ajaxUrl + "?" + $("#myform").serialize())
to generate a query string of your form values that you could sent to you ajax model.
Or if you're building the modal client side try
$("#myform").serializeArray()
to get an array of name, value pairs
This is what it took to get this to work under Rails 3.2.12
View:
<%= link_to 'Preview Promotion UI', admin_preview_promotion_url, id: :promotion_preview %>
The above link is inside the form do/end.
Javascript in application.js
$("#promotion_preview").live('click', (function (event) {
event.preventDefault();
$.post("/store/admin/promotions/preview",
$(event.currentTarget.parentElement).serialize().replace('=put&', '=post&'),
{},
"script"
);
}));
Rails.js injects some hidden code in the page that sets the type of response to PUT and then gets in the way of the routing. At least in this case simply replacing PUT with POST fixes things.
With this bit of code I can post my form updates as usual and activate a modal dialog "preview" using the form data.

How do I let data in 1 column power the content of the 2nd column in Rails 3?

Say I have a list of users in the left column, generated by <%= current_user.clients %> and the second column is empty by default.
However, when the user clicks on one of the links, the second column becomes populated with the projects associated with that user - without the entire page being reloaded (i.e. using AJAX).
I would also like to continue the process, so when they click on a project from that user, the third column is populated with other things (e.g. the name of images, etc.).
How do I accomplish this in Rails?
I assume you are using Rails 3 and jQuery (I'm not well-versed in prototype). It's easy to switch jQuery for prototype in Rails 3: https://github.com/rails/jquery-ujs
For the link:
Something
Using JavaScript and jQuery, write a function that sucks in links of class first_column_link (please rename to something more reasonable, by the way):
$(function() {
$('.first_column_link').bind('click', function() {
$.getJSON('/clients/' + $(this).attr('data-client-id'), function(data) {
// Populate the second column using the response in data
});
});
});
This doesn't work on browsers that don't support or have otherwise disabled JavaScript. Gracefully degrading would likely be a good idea, but without more context, I can't advise you how to best do it.
<%= link_to_remote current_user.clients, go_to_controller_path %>
Proceed from there.
go_to_controller_path routes to an action which renders javascript to update the 2nd column (probably with a partial).

How to get the foreign key column data when it is used in a select box

I have this ruby on rails code
<%= builder.select(:serving_size_id, '') %>
I have not specified any options on purpose because I set the options in a different way when the page loads (using jQuery and Ajax).
The question: Is there any way I can get the value from the column "serving_size_id" but not change that line? I have a partial which I use it for new and edit and I think it would be sweet if I can do the setting of the selected index in JS.
Any ideas?
I'm not sure I completely understand your question, but if you want to set the value of the select field with JavaScript, you need to obtain the value in JavaScript at some point. I can think of two ways of doing this:
1) When you get the options via AJAX, have the server indicate which one is selected. This can be done by returning HTML <option> tags with selected="selected" set for one of them. To do this, your AJAX request is going to have to provide information about the object this select field is for (so the server can look up the object's current serving_size_id value).
2) When you render the field in your original partial, also render some JavaScript which sets the current value of the field, for example, underneath what you have above:
<%= javascript_tag "var ssid = '#{builder.object.serving_size_id}';" %>
Then, after the options are retrived via AJAX, the ssid variable is checked and the correct option is selected.
using jQuery in rails is easy but a little more difficult than prototype.
ex: "div id="serving_size" class="nice" rel="<%=h num%>">Stuff Goes Here.../div>"
in application.js do the following:
//application.js
$(document).ready(function(){
if($('#serving_size'){
$('#serving_size').live("mouseover",function(){
//we are hovering over specific div id serving size
if($('#serving_size').hasAttr('rel')){
alert($('#serving_size').attr('rel'); //your dynamic rel value, and fire function
}
}
}
if('.nice'){
$('.nice').live("mouseover",function(){
//we are now hovering over any item on page with class nice
if($(this).hasAttr('rel')){
//we are now using jQuery object ref and finding if that obj has attr rel
alert($(this).attr('rel')); // shows dynamic rel value
}
}
}
});
If you use the above code you should be able to do anything you want and fire any custom code from each of your set event callbacks.
The 'live' function in jQuery is great because it can be called on items that will eventually be on the page (eg. if you fill in something with ajax, jQuery will be prepared for that item being in the page)
I hope this help.

Resources