Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
232 lines (214 sloc) 22 KB
<!DOCTYPE html>
<html>
<head>
<title>Infinite Scrolling with AJAX - topdan.com</title>
<meta charset="UTF-8">
<meta name="description" content="Ruby on Rails and jQuery team up to allow your visitors to scroll through all your content without initially loading them all.">
<meta name="author" content="Dan Cunning">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@itopdan">
<meta name="twitter:title" content="Infinite Scrolling with AJAX">
<meta name="twitter:description" content="Ruby on Rails and jQuery team up to allow your visitors to scroll through all your content without initially loading them all.">
<meta name="twitter:url" content="http://www.topdan.com/ruby-on-rails/simple-infinite-scrolling.html">
<meta name="og:locale" content="en_US">
<meta name="og:type" content="article">
<meta name="og:url" content="http://www.topdan.com/ruby-on-rails/simple-infinite-scrolling.html">
<meta name="og:title" content="Infinite Scrolling with AJAX">
<meta name="og:description" content="Ruby on Rails and jQuery team up to allow your visitors to scroll through all your content without initially loading them all.">
<link rel="shortcut icon" href="/assets/favicon-e45cdd6cc07e8858a985e6014e38a603.png">
<link rel="stylesheet" media="all" href="/assets/site-d30c732907c1f2982374b8bab9355d72.css"><!--[if lt IE 9]><script src='//html5shim.googlecode.com/svn/trunk/html5.js'></script><![endif]-->
<script type="text/javascript">
if (!document.cookie || document.cookie.indexOf('tracking_off') == -1) {
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-12957077-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
}
</script>
</head>
<body id="simple-infinite-scrolling">
<div class="container bg-black full-width">
<div class="row navigation">
<a id="top"></a> <a href="/ruby-on-rails/finding-your-most-active-users-with-google-analytics.html" class="previous"><span class="fa fa-arrow-left">&nbsp;</span> <span class="desktop">Finding Your Most Active Users</span><span class="mobile">Previous</span></a> <a href="/ruby-on-rails/beware-active-record-callbacks.html" class="next"><span class="desktop">Beware ActiveRecord callbacks</span><span class="mobile">Next</span> <span class="fa fa-arrow-right">&nbsp;</span></a>
<p><a href="/"><span class="fa fa-home">&nbsp;</span></a> &nbsp;»&nbsp;<span class="title desktop"><a href="/ruby-on-rails/index.html">Ruby on Rails</a></span><span class="mobile"><a href="/ruby-on-rails/index.html"><span class="fa fa-folder-open">&nbsp;</span></a></span></p>
</div>
<div class="row bg-white">
<div class="col-md-12">
<div class="width-640 ml-auto mr-auto">
<p class="mt-05e mb-15e ta-right c-ccc">Published by Dan on Jun 16, 2014</p>
<h1 class="center of-yh mb-0"><a href="/ruby-on-rails/simple-infinite-scrolling.html">Infinite Scrolling with AJAX</a></h1>
<p class="mb-0 center">Filed under <a href="/ruby-on-rails/features.html">Features</a></p>
</div>
</div>
</div>
<div class="row bg-white">
<div class="col-md-12">
<div class="width-640 ml-auto mr-auto post">
<!-- post:content:start -->
<ul>
<li>
<a href="#introduction">Introduction</a>
</li>
<li>
<a href="#demo">Demo</a>
</li>
<li>
<a href="#how-it-works">How it Works</a>
</li>
<li>
<a href="#the-code">The Code</a>
</li>
<li>
<a href="#wrapup">Wrap-up</a>
</li>
</ul>
<h2 id="introduction"><a href="#introduction">Introduction</a></h2>
<p>Infinite scrolling appends more content to the end of the page before the viewer gets to it, so they can just keep scrolling unabated and the application doesn't need to initially load a bunch of records only a small percent of viewers will ever scroll down to.</p>
<h2 id="demo"><a href="#demo">Demo</a></h2>
<p>The scrollable pane of movie posters automatically adds more rows of posters as you approach the bottom of the page.</p>
<div id="demo-infinite-scrolling" class="posters b-ccc pt-1e pb-1e pl-1e pr-1e" data-movies-url="/entertainment/movie-recommendations.json" data-per-page="16" data-per-row="4" data-threshold="1000">
<div class="page-row"><img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/23083/thumb_a-quiet-place-2018-0947e99f16b5bb8f292216b87d24d61c.jpg" alt="Thumb a quiet place 2018"> <img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/23236/thumb_a-star-is-born-2018-cd28e41cc4663b88502b5560581436a6.jpg" alt="Thumb a star is born 2018"> <img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/23237/thumb_bad-times-at-the-el-royale-2018-058306ec646c611c43bda2c9d0ba8e56.jpg" alt="Thumb bad times at the el royale 2018"> <img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/22529/thumb_hostiles-2018-df6eb1b34f7841f82d5c5d9cc5bba36f.jpg" alt="Thumb hostiles 2018"></div>
<div class="page-row"><img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/23071/thumb_leave-no-trace-2018-44b2d0cd4edcb4b26b379d798fed6e5d.jpg" alt="Thumb leave no trace 2018"> <img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/22431/thumb_paddington-2-2018-ec278b07b15b64687a79c360f6e8fcb1.jpg" alt="Thumb paddington 2 2018"> <img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/22896/thumb_sorry-to-bother-you-2018-14e324e783293bc6e8c70f482078de2a.jpg" alt="Thumb sorry to bother you 2018"> <img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/22433/thumb_thoroughbreds-2018-6833b95318585760bf974f764a203425.jpg" alt="Thumb thoroughbreds 2018"></div>
<div class="page-row"><img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/22553/thumb_tully-2018-72b5f68434c981e1c4c8827c6baf00d1.jpg" alt="Thumb tully 2018"> <img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/21648/thumb_atomic-blonde-2017-9f8edaab86d0b3013f3725e1973d3e04.jpg" alt="Thumb atomic blonde 2017"> <img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/21555/thumb_baby-driver-2017-4186329656b3576020e96b3ce06c5e4c.jpg" alt="Thumb baby driver 2017"> <img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/21288/thumb_blade-runner-2049-2017-46da9ccc321e46f91973abe665c04344.jpg" alt="Thumb blade runner 2049 2017"></div>
<div class="page-row"><img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/21569/thumb_coco-2017-ae6a365af9ff9cd740ca0c6c39312690.jpg" alt="Thumb coco 2017"> <img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/21166/thumb_gifted-2017-f53f4b9321cc4370318f7a5465b6130a.jpg" alt="Thumb gifted 2017"> <img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/21103/thumb_guardians-of-the-galaxy-vol-2-2017-55ad7aa1c74167eb658badf953d8d0a0.jpg" alt="Thumb guardians of the galaxy vol 2 2017"> <img src="https://s3.amazonaws.com/cdn.topdan.com/movies/posters/20695/thumb_king-arthur-legend-of-the-sword-2017-e696b2eb10b4e146c3fcc14e1e9edd61.jpg" alt="Thumb king arthur legend of the sword 2017"></div>
<p><a href="#" data-action="load-next-page"><span class="fa fa-refresh fa-spin">&nbsp;</span> View next page <span class="fa fa-refresh fa-spin">&nbsp;</span></a></p>
</div>
<p class="fs-12 c-ccc center">This webpage is not hosted by Rails, so the above UX is just a simulation.</p>
<h3 id="how-it-works"><a href="#how-it-works">How it Works</a></h3>
<ul>
<li>Rails renders <a href="#views">the first page and a "View More" link</a> during the initial HTML request.
</li>
<li>The "View More" link is a UI fallback but also lets us know the URL for the next page.</li>
<li>
<a href="#javascript">Javascript</a> watches the page's scrollbar to break a threshold.
</li>
<li>An AJAX request is issued when either the threshold is broken or the user clicks the "View More" link.</li>
<li>Rails returns javascript that inserts the next page and points the "View More" link to the next unloaded page.</li>
</ul>
<h3 id="the-code"><a href="#the-code">The Code</a></h3>
<h3 id="gemfile"><a href="#gemfile"></a></h3>
<div class="highlight">
<pre><span class="c1"># Gemfile</span>
<span class="n">gem</span> <span class="s1">'kaminari'</span>
<span class="c1"># gem 'will_paginate' # another option</span>
</pre>
</div>
<h3 id="controller"><a href="#controller"></a></h3>
<div class="highlight">
<pre><span class="c1"># controllers/posts_controller.rb</span>
<span class="k">class</span> <span class="nc">PostsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">index</span>
<span class="vi">@posts</span> <span class="o">=</span> <span class="no">Post</span><span class="o">.</span><span class="n">order</span><span class="p">(</span><span class="ss">published_at</span><span class="p">:</span> <span class="ss">:desc</span><span class="p">)</span><span class="o">.</span><span class="n">page</span><span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:page</span><span class="o">]</span><span class="p">)</span><span class="o">.</span><span class="n">per</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</pre>
</div>
<h3 id="views"><a href="#views"></a></h3>
<div class="highlight">
<pre><span class="c">&lt;!-- views/posts/index.html.erb --&gt;</span>
<span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"content"</span><span class="nt">&gt;</span>
<span class="cp">&lt;%=</span> <span class="n">render</span><span class="p">(</span><span class="ss">partial</span><span class="p">:</span> <span class="s1">'post'</span><span class="p">,</span> <span class="ss">collection</span><span class="p">:</span> <span class="vi">@posts</span><span class="p">)</span> <span class="cp">%&gt;</span>
<span class="nt">&lt;/div&gt;</span>
<span class="cp">&lt;%</span> <span class="k">unless</span> <span class="vi">@posts</span><span class="o">.</span><span class="n">current_page</span> <span class="o">==</span> <span class="vi">@posts</span><span class="o">.</span><span class="n">total_pages</span> <span class="cp">%&gt;</span>
<span class="nt">&lt;p</span> <span class="na">id=</span><span class="s">"view-more"</span><span class="nt">&gt;</span>
<span class="cp">&lt;%=</span> <span class="n">link_to</span><span class="p">(</span><span class="s1">'View More'</span><span class="p">,</span> <span class="n">url_for</span><span class="p">(</span><span class="ss">page</span><span class="p">:</span> <span class="vi">@posts</span><span class="o">.</span><span class="n">current_page</span> <span class="o">+</span> <span class="mi">1</span><span class="p">),</span> <span class="ss">remote</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span> <span class="cp">%&gt;</span>
<span class="nt">&lt;/p&gt;</span>
<span class="cp">&lt;%</span> <span class="k">end</span> <span class="cp">%&gt;</span>
</pre>
</div>
<div class="highlight">
<pre><span class="c">&lt;!-- views/posts/_post.html.erb --&gt;</span>
<span class="cp">&lt;%=</span> <span class="n">div_for</span><span class="p">(</span><span class="n">post</span><span class="p">)</span> <span class="k">do</span> <span class="cp">%&gt;</span>
<span class="nt">&lt;h2&gt;</span><span class="cp">&lt;%=</span> <span class="n">post</span><span class="o">.</span><span class="n">title</span> <span class="cp">%&gt;</span><span class="nt">&lt;/h2&gt;</span>
<span class="nt">&lt;p&gt;</span><span class="cp">&lt;%=</span> <span class="n">post</span><span class="o">.</span><span class="n">excerpt</span> <span class="cp">%&gt;</span><span class="nt">&lt;/p&gt;</span>
<span class="cp">&lt;%</span> <span class="k">end</span> <span class="cp">%&gt;</span>
</pre>
</div>
<div class="highlight">
<pre><span class="c1">// views/posts/index.js.erb</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'#content'</span><span class="p">).</span><span class="nx">append</span><span class="p">(</span><span class="s2">"&lt;%=j render(partial: 'post', collection: @posts, format: 'html') %&gt;"</span><span class="p">);</span>
<span class="o">&lt;%</span> <span class="k">if</span> <span class="err">@</span><span class="nx">posts</span><span class="p">.</span><span class="nx">current_page</span> <span class="o">==</span> <span class="err">@</span><span class="nx">posts</span><span class="p">.</span><span class="nx">total_pages</span> <span class="o">%&gt;</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'#view-more'</span><span class="p">).</span><span class="nx">remove</span><span class="p">();</span>
<span class="o">&lt;%</span> <span class="k">else</span> <span class="o">%&gt;</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'#view-more a'</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s1">'href'</span><span class="p">,</span> <span class="s1">'&lt;%= url_for(page: @posts.current_page + 1) %&gt;'</span><span class="p">);</span>
<span class="o">&lt;%</span> <span class="nx">end</span> <span class="o">%&gt;</span>
</pre>
</div>
<h3 id="javascript"><a href="#javascript"></a></h3>
<div class="highlight">
<pre><span class="c1"># assets/javascripts/infinite-scroll.js.coffee</span>
<span class="nx">$</span> <span class="nf">-&gt;</span>
<span class="nv">content = </span><span class="nx">$</span><span class="p">(</span><span class="s">'#content'</span><span class="p">)</span> <span class="c1"># where to load new content</span>
<span class="nv">viewMore = </span><span class="nx">$</span><span class="p">(</span><span class="s">'#view-more'</span><span class="p">)</span> <span class="c1"># tag containing the "View More" link</span>
<span class="nv">isLoadingNextPage = </span><span class="kc">false</span> <span class="c1"># keep from loading two pages at once</span>
<span class="nv">lastLoadAt = </span><span class="kc">null</span> <span class="c1"># when you loaded the last page</span>
<span class="nv">minTimeBetweenPages = </span><span class="mi">5000</span> <span class="c1"># milliseconds to wait between loading pages</span>
<span class="nv">loadNextPageAt = </span><span class="mi">1000</span> <span class="c1"># pixels above the bottom</span>
<span class="nv">waitedLongEnoughBetweenPages = </span><span class="nf">-&gt;</span>
<span class="k">return</span> <span class="nx">lastLoadAt</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span> <span class="o">-</span> <span class="nx">lastLoadAt</span> <span class="o">&gt;</span> <span class="nx">minTimeBetweenPages</span>
<span class="nv">approachingBottomOfPage = </span><span class="nf">-&gt;</span>
<span class="k">return</span> <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">clientHeight</span> <span class="o">+</span>
<span class="nx">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nx">scrollTop</span><span class="p">()</span> <span class="o">&lt;</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">offsetHeight</span> <span class="o">-</span> <span class="nx">loadNextPageAt</span>
<span class="nv">nextPage = </span><span class="nf">-&gt;</span>
<span class="nv">url = </span><span class="nx">viewMore</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="s">'a'</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s">'href'</span><span class="p">)</span>
<span class="k">return</span> <span class="k">if</span> <span class="nx">isLoadingNextPage</span> <span class="o">||</span> <span class="o">!</span><span class="nx">url</span>
<span class="nx">viewMore</span><span class="p">.</span><span class="nx">addClass</span><span class="p">(</span><span class="s">'loading'</span><span class="p">)</span>
<span class="nv">isLoadingNextPage = </span><span class="kc">true</span>
<span class="nv">lastLoadAt = </span><span class="k">new</span> <span class="nb">Date</span><span class="p">()</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
<span class="nv">url: </span><span class="nx">url</span><span class="p">,</span>
<span class="nv">method: </span><span class="s">'GET'</span><span class="p">,</span>
<span class="nv">dataType: </span><span class="s">'script'</span><span class="p">,</span>
<span class="nv">success: </span><span class="nf">-&gt;</span>
<span class="nx">viewMore</span><span class="p">.</span><span class="nx">removeClass</span><span class="p">(</span><span class="s">'loading'</span><span class="p">);</span>
<span class="nv">isLoadingNextPage = </span><span class="kc">false</span><span class="p">;</span>
<span class="nv">lastLoadAt = </span><span class="k">new</span> <span class="nb">Date</span><span class="p">();</span>
<span class="p">})</span>
<span class="c1"># watch the scrollbar</span>
<span class="nx">$</span><span class="p">(</span><span class="nb">window</span><span class="p">).</span><span class="nx">scroll</span> <span class="nf">-&gt;</span>
<span class="k">if</span> <span class="nx">approachingBottomOfPage</span><span class="p">()</span> <span class="o">&amp;&</span> <span class="nx">waitedLongEnoughBetweenPages</span><span class="p">()</span>
<span class="nx">nextPage</span><span class="p">()</span>
<span class="c1"># failsafe in case the user gets to the bottom</span>
<span class="c1"># without infinite scrolling taking affect.</span>
<span class="nx">viewMore</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="s">'a'</span><span class="p">).</span><span class="nx">click</span> <span class="nf">(e) -&gt;</span>
<span class="nx">nextPage</span><span class="p">()</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">preventDefaults</span><span class="p">()</span>
</pre>
</div>
<h2 id="wrapup"><a href="#wrapup">Wrap-up</a></h2>
<p>Simple enough? Rails returning javascript simplifies the client-side code significantly. Your HTML won't be exactly like mine, but the jQuery and Rails code should be straight-forward enough for you to tailor it to your own infinite scrolling page.</p><!-- post:content:end -->
</div>
</div>
</div>
<div class="row navigation bb-ccc">
<a href="/ruby-on-rails/finding-your-most-active-users-with-google-analytics.html" class="previous"><span class="fa fa-arrow-left">&nbsp;</span> <span class="desktop">Finding Your Most Active Users</span><span class="mobile">Previous</span></a> <a href="/ruby-on-rails/beware-active-record-callbacks.html" class="next"><span class="desktop">Beware ActiveRecord callbacks</span><span class="mobile">Next</span> <span class="fa fa-arrow-right">&nbsp;</span></a>
<p><a href="/"><span class="fa fa-home">&nbsp;</span></a> &nbsp;»&nbsp;<span class="title desktop"><a href="/ruby-on-rails/index.html">Ruby on Rails</a></span><span class="mobile"><a href="/ruby-on-rails/index.html"><span class="fa fa-folder-open">&nbsp;</span></a></span></p>
</div>
<div class="row introduction pb-2e bb-ccc mb-1e">
<div class="col-md-6 mt-2e center">
<h1 class="mt-0"><a title="Dan Cunning" class="fs-45 fw-normal shadow-silver" href="/">Dan Cunning</a></h1>
<p class="reference mt-05e"><a title="github" class="white" href="https://github.com/topdan"><span class="fa fa-github">&nbsp;</span> GitHub</a> <a title="Twitter" class="white" href="https://www.linkedin.com/in/dancunning"><span class="fa fa-linkedin-square">&nbsp;</span> LinkedIn</a> <a title="Email" class="white" href="mailto:dan@topdan.com"><span class="fa fa-envelope">&nbsp;</span> Email</a></p>
</div>
<div class="col-sm-offset-1 col-md-4 mt-2e">
<img width="75" height="75" class="img-circle fl-left mr-10" src="/assets/dan-79508ca0775ace507f1dc34d151bba0f.jpg" alt="Dan">
<p class="underline-links">I'm a <a href="/ruby-on-rails/index.html">Ruby on Rails</a> contractor from Atlanta GA, focusing on simplicity and usability through solid design. <a class="nowrap" href="/dan-cunning.html">Read more »</a></p>
</div>
</div>
<div class="row fs-12 mt-1e center">
<div class="col-md-6">
<p class="underline-links mb-1e">View Source: <a href="https://github.com/topdan/www/blob/master/ruby-on-rails/simple-infinite-scrolling.html">HTML</a> | <a href="https://github.com/topdan/www/blob/master/ruby-on-rails/simple-infinite-scrolling.json">JSON</a> | <a href="https://github.com/topdan/www/blob/master/ruby-on-rails/simple-infinite-scrolling.html.md">View</a></p>
</div>
<div class="col-md-6">
<p class="mb-1e">© 2012-2019 Dan Cunning. All rights reserved.</p>
</div>
</div>
</div>
<script src="/assets/site-1e22bb3074710f189d8b5da964539ecb.js"></script>
</body>
</html>