pjax -- must links be inside the pjax-container? - ruby-on-rails

I am using https://github.com/rails/pjax_rails.
I want to have my links inside a "permanent" portion of the page. I.e. in my layout I have
<%= link_to "Some Action", some_action_path %>
Then inside the view:
<div data-pjax-container>Content to be replaced</div>
Here is my javascript where I invoke pjax:
('[data-pjax-container]').pjax('a');
[You may note that this is different than the invocation method in the readme, but as a reported issue points out, the method in the readme doesn't work at all.]
This is not working (the link reloads the entire page).
If I move the link inside the div with the data-pjax-container attribute, it works (the page is not reloaded and only the container is updated).
I have not seen any examples where the link was actually outside of the container. Can anyone tell me how to get this to work?

I was probably focusing too much on the pjax-rails readme (not great). I went to the source (https://github.com/defunkt/jquery-pjax) which led me to changing my js to this:
$(document).pjax('a', '[data-pjax-container]')
..which got me back on the right track.

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..

Why is my Ruby on Rails link_to sending the action twice?

I have a link_to method in my Ruby on Rails application in one of the views, and when I click on it, the controller is set to do a whole bunch of things... But for some reason I'm seeing this GET request twice even when I click on the link one time.
Here's what the link looks like:
<%= link_to image_tag("excel.png"), spreadsheet_technical_report_path(report) %>
Which goes to /technical_report/id/spreadsheet and here's what it looks like in the controller:
def spreadsheet
spreadsheet = GenerateSpreadsheet.generate(params[:id])
send_file spreadsheet
end
I've even replaced the contents of that function with a binding.pry, and it hits it twice! This is so confusing. my whole GenerateSpreadsheet model does a variety of things and takes approximately a minute, and this second request does nothing but double that time.
Can someone please tell me what I'm missing here? I don't have a view set up for this since I want it to just send the user a download prompt (which it's doing) and not necessarily go to a view. I don't even know if not having a view is even relevant here.
JS
To add to the comments, the main issue here would likely be that you've bound some javascript to the page's a elements.
With an absence of remote: true and other hooks, the only thing which would likely cause a double-fire from your link_to is if Javascript is sending an ajax request too.
You mention that you...
removed //= require tree . from my application.js
... whilst good that this fixed the issue, you have to remember that nothing happens with computers without them being told to do it. IE your "link" wouldn't just double-click for the sake of it.
If your JS works when you remove the //require_tree ., you'll want to look at the other JS files you have. There will be one where you're binding to the $("a").on("click" event, which is likely leading to the double-firing of your link.
Thanks to chaitanya saraf's comment, I just removed the "//= require tree ." from my application.js to get this fixed. Once I got rid of this, I added this to my config/initializers/asset.rb file
Rails.application.config.assets.precompile += [/.*\.js/,/.*\.css/]
Got rid of this annoying problem nice and easily.

How do you handle widgets in rails?

StackOverflow, for example, has a user's reputation displayed up top. Clearly, this is grabbed from the database, and it's displayed on every page. Of course, this isn't in every controller action on every page, because that would be incredibly redundant.
How do you handle this kind of situation in rails? The only way I can think of it is to use before_filters to pass the models into the page, but that just seems like abuse of that feature. There seems to be the cells gem that does what I want, but I'd imagine this is a common problem and there must be a simple solution for it in rails without having to resort to plugins or gems.
What you are looking for is the layout. In rails this is where you define headers, footers, and sidebars that frame your site. Look for app/views/layouts/application.html.erb in your generated rails code. Towards the bottom you will see:
<body>
<%= yield %>
</body>
The yield is where rest of the app gets invoked. Everything before and after the yield will appear on every page. So, using your example, you might query the database and set the instance variable #reputation in the application controller:
#reputation = User.find( current_user ).reputation
then display it in the layout like this:
<body>
<%= #reputation %>
<%= yield %>
</body>
This is covered thoroughly in the book "Agile Web Development With Rails". If you are going to develop in Rails I recommend getting the latest edition.
I would just make a partial with the widget in it and render it in the layout(s) where you want it to appear. Let it do whatever it needs to do, eg connect to the db, run some js to connect to an external site, etc.
If you're concerned about optimisation then deal with it when it becomes a problem.
I guess, you can put the code you need into a view helper. And then render some partial, like it was said before, in the layouts where you want it to appear, calling helper's function.
Look here:
Rails view helpers in helper file

Rails Link_To doesn't add a CSS Class correctly

So I'm trying to use link_to to create a link in my Rails app and trying to add a CSS class to certain links. The problem that I have is that when I add my html options to the link_to arguments, the links don't get created and I end up with nothing. Here's my code:
<%=link_to( image_tag(#beetle.icon_img, :width=>30, :alt=>"Beetle", :border=>0) , beetle, :html=>{:class=>"work"}) %>
I also tried variations of this and it still didn't work. For example,
<%=link_to( image_tag(#beetle.icon_img, :width=>30, :alt=>"Beetle", :border=>0) , beetle, :class=>"work") %>
The method also exhibits some strange behavior, which I think might be the culprit. If I go straight to the page, no POST or GET requests, link_to works properly and the links and images render correctly, which is to say that they actually DO render. However, the way that I would like to get to the page is by form POST request in a previous page whose action is the results page I'm trying to get to.
Thanks for any help you can provide!
EDIT: I'm not sure exactly what the problem was, but I solved it by changing the form's method to GET instead of POST.
It's most likely because the POST request is hitting a different method (new), rather than (show). You must supply the proper instance variables to the view. You seem to be referencing both #beetle and beetle. Have a look at all of those variables, since that appears to be the POST problem.

can link_to lead to rendering sth?

i want to render a partial within a view. so that when button MORE is clicked everything stays the same just additional characters are shown. in my case the whole article.
<%= #article1.content[0..300] + "..." %>
<%= link_to "more", ....... %>
i dont know what the right methot would be. somehow i have to explain to rails that when button more is clicked it shows me the whole article. maybe i shouldn't use method link_to ..
thank you in advance for your replys
What you're looking for is link_to_remote or link_to_function.
link_to_remote will be fetching the rest of the article from your controller and replacing/appending to a DOM element with a partial via RJS. This allows you to minimize unnecessary data being sent, and facilitates handling users that have javascript disabled.
With link_to_function, the entire article will be served when the page is loaded, but the everything beyond the first 300 characters will be hidden by CSS. This is easier to set up but sends a lot more data, it also relies on the user having javascript enabled.
Without looking at the source the average user probably couldn't distinguish between the two methods.
Which choice you go with is up to you. Sorry, I haven't got time to provide code examples, but the internet is full of them.
try link_to_function, use truncate for part and insert hidden tag with full text, switch them using javascript in link_to_function

Resources