If you google "will_paginate" and "ajax", the top result is this blog post: But the original author of will_paginate says to not use this method (bad for SEO/spiders) ...
But I cant get the original authors method to work (his javascript kills all my links). An other gentleman suggests a similar method to mislav's (the original will_paginate author) concept. But I cant get that to work either.
so .... what is the best way to paginate using AJAX, and stay SEO friendly? (for RAILS >2.1)
Tomh's answer is correct. Just for shiggles, I prototyped a quick implementation. Here's a screencast that shows it using Ajax when Javascript is enabled (your users) and still having pretty URLs when Javascript is disabled (Google). And here are a few code snippets to get you rolling on it.
config/routes.rb:
map.connect 'items/:page', :controller => "items", :action => "index", :page => 1
app/controllers/items_controller.rb:
class ItemsController < ApplicationController
def index
#items = Item.paginate(:all, :page => params[:page])
respond_to do |format|
format.html
format.js do
render :update do |page|
page.replace_html :items, :partial => "items"
page << "ajaxifyPagination();"
end
end
end
end
end
app/views/items/index.html.erb:
<h1>Listing items</h1>
<div id="items">
<%= render :partial => "items" %>
</div>
app/views/items/_items.html.erb:
<%= will_paginate #items %>
<table>
<% for item in #items %>
<tr>
<td><%= item.id %></td>
</tr>
<% end %>
</table>
layout:
<%= javascript_include_tag :defaults %>
public/javascripts/application.js:
$(document).ready(function() {
ajaxifyPagination();
});
function ajaxifyPagination() {
$(".pagination a").click(function() {
$.ajax({
type: "GET",
url: $(this).attr("href"),
dataType: "script"
});
return false;
});
}
My example uses jQuery (with jRails), but it's straightforward to do with Prototype as well.
Seo friendly and unobtrusive javascript goes hand in hand. What you can do is the following.
Code the entire site as if only html is enabled (incl your pagination thing)
Use respond_to and serve only the list of items if the request comes in from js
Using onDomReady from whatever library you pick you attempt to catch all pagination links and add an onclick event which triggers an ajax call to that new view and returns the result. You put that result into the container containing the data you are paginating. The onclick then returns false.
To give your users a better user experience you can add some features like active links etc to the same javascript method.
Using this approach the pagination will work for JS and non-js as the non-js users (including Googlebot) will traverse your pagination as normal. Only in the event that the user has javascript enabled, the container with data will be updated with new results.
Unfortunately, I don't think you can use Ajax in the way you want and still stay SEO friendly as far as the paginated content. The problem is that the robots of Google and friends, as far as I know, won't go through your content using XHR requests so they simply won't see that content.
That said, if the paginated items each have their own static, SEO-friendly pages (or are otherwise statically available on your site), the content will still find its way into their engines. This is the way you'll probably want to go.
There is a railscasts on this topic which helped me out http://railscasts.com/episodes/174-pagination-with-ajax
I'm running rails 3.2, so I added the pagination.js there mentioned to app/assets/javascripts folder
pagination.js
$(function() {
$(".pagination a").live("click", function() {
$(".pagination").html("Loading...");
$.getScript(this.href);
return false;
});
});
And then created
home.js.erb
$('#div_tags_list').html('<%= escape_javascript(render partial: '/customersb2b/user_customer_numbers_list').html_safe %>')
$('#receipts_list').html('<%= escape_javascript(render partial: '/customersb2b/feed').html_safe %>')
Since I have two distinct listings on my homepage.
This is all I had to do to put will_paginate working with Ajax.
As for the SEO concerns, well, I don't know much about it, but the URL http://localhost:3000/customers?_=1366372168315&feed_page=1&tags_page=2 still works
There is a great way to do this easily if not worried about spiders. Took me 5 minutes. Check out:
https://github.com/ronalchn/ajax_pagination/wiki/Adding-AJAX-to-will_paginate
If you get an error about a missing 'history' file, install:
https://github.com/wweidendorf/jquery-historyjs
but also be aware of:
rails ajax_pagination couldn't find file 'history'
Related
Note: I am presenting a logic here what I am doing.
What I am doing:
Think about the basic index action where we are listing products and with pagination. Now using remote-true option I have enabled ajax based pagination. So far things works perfectly fine. Take a look on sample code.
Products Controller:
def index
#products = Product.paginate(:order =>"name ASC" ,:page => params[:page], :per_page => 14)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #products }
format.js
end
end
Index.html.erb
<h1>Products</h1>
<div id="products">
<%= render "products/products" %> // products partial is just basic html rendering
</div>
<script>
$(function(){
$('.pagination a').attr('data-remote', 'true')
});
</script>
Index.js.erb
jQuery('#products').html("<%= escape_javascript (render :partial => 'products/products' ) %>");
$('.pagination a').attr('data-remote', 'true');
So whats the problem:
Now I want to enable action caching on this. But index.js.erb file is not manipulating DOM. If I remove the remote-true functionality then things works fine with caching.
For action caching I have added this line on the top of the controller:
caches_action :index, :cache_path => Proc.new { |c| c.params }
Any suggestions?
Update:
Problem is jquery code is not executing. From this question
I found out what's wrong. jQuery actually surround the incoming script with a so that the browser evaluates the incoming code. But the caching mechansim merely saves the code as text and when one re-request, it returns the code as text but not evaluate it. Therefore, one needs to eval the code explicitly
But how to solve this problem??
After some trial and error, I think I have a work around.
Page links
Instead of
$(function() { $('.pagination a').attr('data-remote', 'true') });
use
$(function() {
$('.pagination a').click(function() {
$.ajax({
url: this.href,
dataType: 'script'
});
return false;
});
});
so response created by the app server will be run as javascript
Controller
next, change your caches_action line to
caches_action :index, cache_path: proc { |c| c.params.except(:_).merge(format: request.format) }
since ajax appends a _ params for some sort of timestamp
Hopefully this works :)
I don't see what the issue should be with using remote: true. Someone else suggested to use .ajax instead of remote: true, but that's exactly what the remote functionality does, so there shouldn't be any difference.
The other answer has code that explicitly uses jQuery.ajax, but the only difference in their code compared to what the remote functionality does is that they're specifying an explicit dataType. You can actually do that with remote: true though.
In your HTML link, you just need to specify data-type="script". Or, based on your posted JS, you'd do this:
$(function(){
$('.pagination a').attr('data-remote', 'true').attr('data-type', 'script');
});
EDIT: Also, I wrote more in-depth about the data-type attribute and how it works with Rails here: http://www.alfajango.com/blog/rails-3-remote-links-and-forms-data-type-with-jquery/
I think I found a solution to this problem. I have a controller that has caches_action for an action that uses format.js to fetch some data via ajax, and it was not working out of the box.
I found that, despite the request being transmitted to the server, and the server correctly parsing the request and "rendering" the index.js.erb template, nothing was updating in the DOM. Your solution with $.ajax and dataType:'script' fixed the problem for me, however, I didn't like having to do jquery to bind to a click on a link, which should happen by default... I was able to make it work correctly by changing my link_to to this:
= link_to "click me", user_action_path(params), remote: true, data:{type: 'script'}
Hope this helps!
I've been having the same problem but on my 3.0.3 application with :remote => true I have added :'data-type' =>: script and have been worked fine.
However, in my case, I don't see improvement when loaded by ajax the list.
Thanks for your time!
I get some reports data on my hand and I want to present these data on the web. The view of the HTML will be divided into two parts, the left part and the right part. There's a tree view in the left part consisting of the report names. In the right part presents the contents of the report.
What I want to achieve is when I click the report name on the left part, it will call an Action in the Controller, and passed the report name as parameter. The Action will fetch the data from the database and represent the data in the right part. And now I am stuck on how to realize this kind of view.
I've Googled a lot on the Internet and found Frameset, Partials or Ajax may capable of this. Because I've never developed web applications before and also new to Rails. So can anyone give me some advise or suggestion?
Things I've already known :
I've used Frameset to accomplish a view like this. But I found it needs a lot of .html files and all these .html files are static. And many people don't suggest it at all.
Then I've Googled Partials. But it seems Partials don't call the Action. It directly loads the _partials.html.erb to the main view. And besides, how can I control the layout? Using CSS?
I've never used Ajax.
If you want a fluid, seamless transition between one report and another, you should use both AJAX and Partials.
The way that it works is something like:
Make a left column in the html that has some links
Make the right column inside a partial
Assign the links to jQuery listeners to call the AJAX.
I'll put a bit of code here to show how it works:
Controller:
def index
reports = Report.all
if params[:report_id]
reports = Report.find(params[:report_id]
end
respond_to do |format|
format.html
format.js { render :template => "update_reports" }
end
end
update_reports.js.erb (in the same folder as the report views):
$('#report_viewer').html('<%= escape_javascript render :partial => "report_detail" %>');
In your view:
<div style=float:left>
<ul>
<li><%= link_to "Some report", "#", :class => "ajax" %></li>
</ul>
</div>
<div style=float:right id="report_viewer">
<%= render :partial => "report_detail" %>
</div>
<script type='text/javascript'>
$(document).ready(function() {
$(".ajax").click(function(e) {
$(this).ajax("your route to the action");
}
});
</script>
I think it's basically this, now let me explain a few things:
I don't remember if you have to do this, but in my case I created a new custom route to force the call to the action to be a json call instead of a html one. You can do this by adding :format => "js" to your route
You must name all your partials like "_yourname.html.erb". Rails won't recognize partials without the leading underscore.
In the controller, everything that comes after "format.js" is optional, you don't need to specify the template name, and if you don't Rails will look for the file index.js.erb.
The update_reports.js.erb file is basically a callback javascript that executes to update the current page. It finds the div where the partial is, and updates it rendering a new partial with the new report.
In the view, the link to change the report don't need to be a link at all if you're using the jQuery.click listener, but if it is a link, it must have the href as "#", or else the browser will just try to redirect to that location.
There are several ways to hook your link to the ajax function, I just chose the one I like it better, but you also could have a named function and call it in the html tag "onClick='yourFunction()'".
You need jQuery to call ajax like this. If you're sing Rails 3.0 or lower, you should replace the default Prototype with jQuery, because it's much better (IMHO), but I think prototype also have some ajax features.
It may seem complicated, but once you get the idea of it it'll become simple as writing any other action.
In the js callback file you could also add an animation to smooth the transition, like a fading. Look for the jQuery fade function for more info on this.
This is quite an open question so don't take this answer verbatim, but merely as a guide.
app/views/layouts/reports.html.erb
<!DOCTYPE html>
<html itemscope itemtype="http://schema.org/">
<head>
# omitted
</head>
<body>
<div id="body-container">
<div id="left-column">
<ul id="reports-list">
<% for report in #reports %>
<li><%= link_to report.name, report %></li>
<% end %>
</ul>
</div>
<div id="right-column">
<%= yield %>
</div>
</div>
</body>
</html>
app/controllers/reports_controller.rb
class ReportsController < ApplicationController
before_filter { #reports = Report.all }
def index
end
def show
#report = Report.find(params[:id])
end
def edit
#report = Report.find(params[:id])
end
def new
#report = Report.new
end
def update
# omitted
end
def create
# omitted
end
def destroy
#report = Report.find(params[:id])
end
end
routes.rb
YourApp::Application.routes.draw do
resources :reports
root to: "reports#index"
end
This would achieve the effect your after using just rails, of course adding ajax could add a better user experience.
I followed this tutorial to add AJAX to my Rails3.1 search box(everything works fine). This search box in in a side bar, and I want to display the results in the main column. The problem I encounter is that I don't know how to this properly.
Before AJAX I had a search.html.haml with the code to display the results. However, if I add the render partial call here nothing get displayed(I guess is because the search.html.haml does not get called)
search.html.haml
=#events.count
#search=render 'search'
On the other hand, if I place the render partial call in the application.html.haml. It does display the results, but underneath the actual content of the main column.
application.html.haml
#central
%p#notice= notice
= yield
#search=render 'search'
I guess it is a simple fix, but not sure how to do it.
Thanks in advance!!!
_search.html.haml
-#events.each do |event|
#user_block
#user_block_pic
= image_tag(event.pic.url(:medium), class: 'event_block_image', :alt =>'Event Picture')
....more stuff
search.js.erb
$('#search').html("<%= escape_javascript( render(:partial => 'search') ) %>");
How can I do to get rid of what is on the main column(#central) and display the results in there?
FIXED!!!! NOW WORKING!!!! (since I have to wait hours to be allowed to post an answer I do it here)
I am not sure this is correct coding but worked for me. What I did is to get rid of the middle file search.html.haml(see above)
And now the configuration is this:
application.html.haml
#central
%p#notice= notice
= yield
_search.html.haml
#search
=#events.count
-#events.each do |event|
...stuff
assets/javascript/search.js
$(function(){
$("#search .pagination a").live("click", function(){ #this is for pagination
$.getScript(this.href);
return false;
});
$('#events_search').submit(function () { #this is for the search form
$.get(this.action, $(this).serialize(), null, 'script');
return false;
});
});
views/events/search.js.erb
$('#central').html("<%= escape_javascript( render(:partial => 'search') ) %>");
It works fine for me. Hope this can help others.
Thanks
When you search method gets called from the ajax call it wants to render search.js.erb or haml I guess, but I don't use haml, only erb.
So then you create a search.js.erb, here is an example of my search.js.erb:
$("#user-browser").replaceWith("<%= escape_javascript render(:partial => 'list') %>");
$("#tabs-interval").replaceWith("<%= escape_javascript render(:partial => 'tabs-interval') %>");
So all my erb does is use jquery to find the div's I want to update the html in, using the rendered partials and replaces the html in them.
My partials look like so in erb:
file: list.html.erb:
<div id="user-browser">
<span>more html stuff</span>
</div>
file: tabs-interval.html.erb:
<div id="tabs-interval">
<span>more html stuff</span>
</div>
I can't figure out how to upgrade this code from Rails 2 to Rails 3:
<% remote_form_for(item, :update => 'div_id') do |f| %>
...
I tried this:
<%= form_for :item, :remote => true, :url => { :controller => "items", :action => "create" }, :update => 'div_id' do |f| %>
...
It creates the new item but it fails in updating the content within <div id="div_id"></div> tags. It seems Rails 3 no longer supports the ":update" option for a remote form_for. Any suggestion?
You could use RJS, but that's being deprecated too (and for good reason). The simplified, best-practices way to handle this in Rails 3+ is as follows (assuming jQuery):
# your_view.html.erb
<div id="receiver-id"></div>
<%= form_for :some_model, :remote => true, :id => 'form-id' do |f| %>
...
<% end %>
# application.js (or any other .js loaded on the page)
$(function(){
$('#form-id').bind('ajax:success', function(xhr, data, status){
$('#receiver-id').html(data);
});
});
The ajax:success hook gets called by the jquery-ujs (aka jquery-rails, aka rails-ujs) remote link/form handler. See for yourself. There are lots of other callbacks/hooks available for you to use, too. If you wanted to make this even more flexible, you could use live instead of bind, and bind to a class that dictates where the ouput goes (e.g. "sidebar") and then all remote links/forms with the sidebar class would have their HTML response go to div#sidebar.
The most straightforward way to do this would be to write a javascript view template, e.g. create.js.erb which would look something like this:
$('#div_id').html("<%= escape_javascript(render(#item)) %>");
(depending on your setup, of course, I'm assuming an #item variable and an associated _item partial)
Edit:
coreyward is right. This is the RJS way which is more of the old fashioned Rails 2.x "Rails way". It's probably more familiar, but has issues. Your specific case is one of them, actually, as typically you might bind to an HTML element to update using the record's id (e.g. div #item_1), and in the create case there is no id available beforehand, complicating matters.
Binding via clientside JS eliminates this issue. RJS works in something of a vacuum, making assumptions about the state of the client's HTML and having no access to it.
I know the question is old but I when migrating to Rails 3 I found a pretty good way of doing this, so I thought I would post it here in case anyone else is in a similar solution.
In layouts/update_page.js.erb I put this file:
$j('#<%=#update_div_id||"list_div"%>').html('<%= escape_javascript render(:partial => (#partial_div||"index"), :locals => #local_hash) %>');
This is mainly used for searches that use remote, so in the index action in the controller, I just added the following code.
respond_to do |format|
format.html
format.js {render 'layouts/update_page'}
end
Since remote is being used, it will always try to use javascript first, so it will render the update_page.js.erb file from above. For us, we almost always use the div#list_div on our index pages, so we update that by the default, however if you need to update something different, you can pass in #update_div_id, and if you need to render a different page, you can pass in #partial_div.
To clarify, for a lot of things, it is probably better practice to use the callbacks, but I found this to be a much easier way, when we had to migrate over nearly 100 of these calls.
I can't believe I've been looking four hours for this simple task, but I have.
In Rails 2.3, I could replace one section of a page with this simple code:
render :update do |page|
page.replace_html "div_id", :partial => "new_content",...
end
In Rails 3, Ryan Bates has me writing entire new javascript functions, switching from Prototype (rails default) to jQuery, and otherwise not enjoying life. The other tutes are no more straightforward.
What am I missing? How do we replace a <div> these days?
Thanks, guys. The official answer seems to be that, yes, the team felt simple is the enemy of good and made it more complicated.
The first key is to create a .js.erb file NAMED for the method CALLING the ajax update. So if the index method handles the update, put the raw javascript in index.js.erb. This goes in the views folder.
Second, the code that worked in index.js.erb was
m = $('list_users');
m.innerHTML = "<%= escape_javascript(render :partial => "reload_users") %>";
Then to make the call, add in the respond_to block of the controller method, add:
format.js
Finally, the calling view has:
<%= link_to "Update User List", #reload_users_path, :remote => true %>
By the way, supposedly all the old pages using page.replace will work if you install a plugin. The plugin download page suggests that it broke in the last releases of Rails 3 and has not been fixed. Also, various bloggers will come to your home and birch-switch you if you use it.
The whole RJS stuff makes the javascript inline and makes the dom very obtrusive. Also, by avoiding inline javascript you could open up other possible ways of optimizing you javascript by compressing and caching those files in browser. Thats the reason why RJS is getting out of scope from rails 3. A little bit of getting around with jQuery or Prototype for a day should get you on gears with these kind of small stuff and will help the project on long run.
Do you still have jQuery in there? I'd recommend it over Prototype any day...
If it's still there you can just use the following in your Javascript:
$.get("<%= url_for path/to/partial %>",
function(response) {
$("#div_id").html(response);
});
This gets the partial via AJAX and just dumps it into the div with id div_id.
Hope this helps!
I'm not even sure you need to make an AJAX call to load that partial. I believe that in a js.erb file, a call to render(:partial => object_or_path) will just return a string with all the html, which you can wrap in a jQuery object and append. Example:
$('#div_id').html($('<%= render :partial => #object %>'))
As far as I know, along the same line as the answer above, you can do something like this in your template:
<%= link_to "Update User List", #reload_users_path, :remote => true %>
And in controller, do this:
respond_to do |format|
format.js {
render :text => "alert('reloaded')"
}
end
This way you can have controller "execute" client side JS much the same as as render :update used to do. This is equivalent to doing the following in Rails 2:
render :update do |page|
page << "alert('reloaded')"
end
Is there any reason why this approach is not advisable?
Try this:
page.call "$('#div_id').html", render(:partial => 'new_content')