Skip to content

Instantly share code, notes, and snippets.

@victorbstan
Created August 25, 2012 20:34
Show Gist options
  • Save victorbstan/3470717 to your computer and use it in GitHub Desktop.
Save victorbstan/3470717 to your computer and use it in GitHub Desktop.
Angular JS and Rails 3 Infinite Scroll Pagination
<!--
This is the HTML portion of the infinite scroll/pagination with Rails and AngularJS tutorial
Required libraries for the tutorial and indicated as "Important",
together with implementation specific libraries marked as "Optional"
-->
<!DOCTYPE html>
<html>
<title>Clips</title>
<!-- Important -->
<script src="lib/jquery.js" type="text/javascript"></script>
<!-- Optional -->
<script src="lib/underscore.js" type="text/javascript"></script>
<script src="lib/underscore.string.min.js" type="text/javascript"></script>
<!-- Important -->
<script src="lib/angular.js" type="text/javascript"></script>
<!-- Optional -->
<script src="lib/ng_sanitize.js" type="text/javascript"></script>
<!-- Important -->
<script src="lib/moment.js" type="text/javascript"></script>
<!-- Optional -->
<script src="lib/urlize.js" type="text/javascript"></script>
<script src="lib/truncate.js" type="text/javascript"></script>
<!-- Important: Application files -->
<script src="filters.js" type="text/javascript"></script>
<script src="clips.js" type="text/javascript"></script>
</head>
<!-- hook AngularJS into your HTML by declaring the application name and controller name -->
<!-- if you plan on using the application as a Chrome extension, you need to add "ng-csp" -->
<body ng-app="clipper" ng-controller="clips_controller" ng-csp>
<!-- snip... -->
<!-- AngularJS module knows about "when-scrolled" and "load_data()" is defined in "clips.js" -->
<div id="clips-list" when-scrolled="load_data()">
<!-- AngularJS templating makes use of interpolation of "{{...}}" brackets -->
<!-- You can defined pre-processing filters, i.e.: "filter:query",
if you need to do additional computation on the output data -->
<!-- "ng-repeat" indicated to AngularJS that this bit part of the HTML template can be repeated -->
<div class="clip-content-wrap {{clip.source}}" ng-repeat="clip in clips | filter:query">
<div class="clip-content" data-id="{{clip.id}}">
<span ng-bind-html="clip.body | truncate | urlize"></span>
</div>
<div class="clip-time-well">
<!-- Here I use a filter for the date value -->
<small>{{clip.created_at | time_ago_in_words}}</small>
</div>
</div>
</div>
<!-- snip... -->
</body>
</html>
// This is the JS portion of the AngularJS and Rails infinite scroll tutorial (by making use of pagination)
// SETUP
// development
var DOMAIN = 'http://localhost';
var PORT = 3000;
// API endpoints
// If your application has routes set up in the 'classical' CRUD interface,
// and you respond to .json in your controllers, this should look familiar.
var DATA_SOURCE = '/clips.json';
var DATA_INDEX = '/clips/';
// ANGULAR JS
// App
Clipper = {};
// From the AngularJS website:
// Controllers are the behavior behind the DOM elements.
// AngularJS lets you express the behavior in a clean readable
// form without usual boilerplate of updating the DOM,
// registering callbacks or watching model changes.
function clips_controller($scope, $http) {
var scope = $scope;
var http = $http;
// format date
// implementation detail, can skip
var now = new Date();
now = moment(now.getTime());
Clipper.data_collection = [];
// processing data for output,
// implementation detail, can skip
Clipper.flatten_data_collection = function(){
var result = _.flatten(this.data_collection);
return result;
};
// sort data for output
// implementation detail, can skip
Clipper.sort_by_created_at = function(data){
var sorted_data = data.sort(function(a,b){
var date_a = moment(a.created_at).unix();
var date_b = moment(b.created_at).unix();
return date_a - date_b;
});
return sorted_data.reverse();
};
// data output to view
// give it to angular, it will take care of the rest...
Clipper.output_data = function(data_array){
$scope.clips = Clipper.sort_by_created_at(data_array);
}
// default data to show in clips section when user is not logged-in,
// or service is not accessible, ie.: before remote API call
$scope.clips = [
{body:"Welcome! I am your first clip!", created_at:now}
];
// page number, start at page 1
// will become page parameter sent to Rails API
var counter = 1;
// infinite scroll (load next page)
$scope.load_data = function() {
// load one page at a time,
// strictly speaking, we don't need a loop in this app implementation,
// but it's a left-over from the original tutorial, which treated each call to the load_data method
// as a chance to load a number of individual items, changing the the 'for' loop increment limit
// to a higher number would allow you to batch load items.
// Again, in this case, changing the 'for' loop increment limit to a higher numbers would mean
// loading more than one page at a time; this might be desireable if you have a short number of
// items on each page and you want to buffer by loading more pages at a time, usually not needed.
for (var i = 0; i < 1; i++) {
// get the data from the API, put together the necessary URL
// use the previously defined "counter" as the 'page' parameter for the Rails API
$http.get(DOMAIN+":"+PORT+DATA_INDEX+'page/'+counter+'.json').success(function(data) {
// add API response data to our array of items
Clipper.data_collection.push(data);
// format for output
// suffice to say, here you will perform whatever action you need
// for output preprocessing
// implementation detail, change to whatever you need to do with the received data,
// as long as you remember to give it to AngularJS for output :)
Clipper.output_data(Clipper.flatten_data_collection());
});
counter += 1; // increment page number for next request
}
};
// initial call to load data (load first page)
$scope.load_data();
// the rest of the app...
};
# This is the Rails section of the AngularJS & Rails infinite scroller/pagination tutorial
# This part is really simple, we just need to make sure we respond with the appropriate
# paginatied JSON resource when we get the API call from our AngularJS application
# Gems
gem 'will_paginate', '~> 3.0'
# Routes
get "/clips/page/:page", :controller => "clips", :action => "index"
resources :clips
# Controllers
# GET /clips
# GET /clips.json
# GET /clips/page/2
# GET /clips/page/2.json
def index
respond_to do |format|
@clips = current_user.clips.page(params[:page]).order('created_at DESC')
format.html do
@clips
end
format.json do
render json: @clips
end
end
end
// Define filters and more for your AngularJS app
// Filters can be applied inline in your views/templates HTML to do additional view output processing
angular.module('clipper', ['ngSanitize']).
// Infinit scroll/pagination, this "directive" binds to the view
// in the application HTML, "when-scrolled" is hooked up to "whenScrolled" here
directive('whenScrolled', function() {
return function(scope, elm, attr) {
var raw = elm[0];
// bind function to the 'scroll' event
// we listen to the 'scroll' event and calculate when to call the method attached to "when-scrolled"
// if you recall our HTML page, we have: when-scrolled="load_data()"
// in our application script we defined "load_data()"
elm.bind('scroll', function() {
// calculating the time/space continuum needed to trigger the loading of the next pagination
if (raw.scrollTop + raw.offsetHeight >= raw.scrollHeight) {
// from the AngularJS documentation:
// http://docs.angularjs.org/api/ng.$rootScope.Scope#$apply
// $apply() is used to execute an expression in angular from outside
// of the angular framework. (For example from browser DOM events, setTimeout,
// XHR or third party libraries). Because we are calling into the angular
// framework we need to perform proper scope life-cycle of
// exception handling, executing watches.
scope.$apply(attr.whenScrolled);
}
});
};
}).
// The rest are optional filters,
// implementation specific, can skip
filter('truncate', function() {
return function(input) {
return truncate(input, 140);
};
}).
filter('time_ago_in_words', function() {
return function(input) {
return moment.utc(input).fromNow();
};
}).
filter('urlize', function() {
return function(input) {
return urlize(input, {target:"_blank"});
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment