app = angular.module('plunker', ['ngAnimate', 'infiniteScroll'])
app.factory 'Item', [
'$q', '$timeout',
($q, $timeout) ->
new class Item
id = 0
load: (n, timeout=2000) =>
deferred = $q.defer()
fn = () =>
items = []
for i in [0...n]
items.push
id: id
url: "http://lorempixel.com/100/100?#{id}"
id += 1
deferred.resolve items
$timeout fn, timeout
deferred.promise
]
app.controller 'ItemsCtrl', [
'$scope', '$q', 'Item',
($scope, $q, Item) ->
$scope.items = []
$scope.loading = false
$scope.loadMore = () =>
$scope.loading = true
Item.load(20)
.then (items) =>
Array.prototype.push.apply $scope.items, items
.finally () =>
$scope.loading = false
$scope.options =
disabled: false
threshold: 0.1
return
]
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<link rel="stylesheet" href="style.css" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular-animate.js"></script>
<script src="app.js"></script>
<script src="infinite-scroll.js"></script>
</head>
<body>
<div ng-controller="ItemsCtrl">
<h1>Element</h1>
disabled: <input type="checkbox" ng-model="options.disabled">
<div id="box" infinite-scroll-container>
<ul class="items" infinite-scroll="loadMore()" infinite-scroll-options="options">
<li class="item fade" ng-repeat="item in items track by item.id">
<img ng-src="{{item.url}}">
</li>
<li class="item fade loading" ng-show="loading">Loading...</li>
</ul>
</div>
</div>
<div ng-controller="ItemsCtrl">
<h1>Window</h1>
disabled: <input type="checkbox" ng-model="options.disabled">
<ul class="items" infinite-scroll="loadMore()" infinite-scroll-options="options">
<li class="item fade" ng-repeat="item in items track by item.id">
<img ng-src="{{item.url}}">
</li>
<li class="item fade loading" ng-show="loading">Loading...</li>
</ul>
</div>
</body>
</html>
#box {
width: 100%;
height: 400px;
background-color: #888;
overflow-y: scroll;
}
.items {
list-style-type: none;
margin: 0;
padding: 5px;
overflow: hidden;
}
.item {
float: left;
margin: 5px;
width: 100px;
height: 100px;
background-color: #fdd;
}
.item img {
display: block;
}
.item.loading {
text-align: center;
line-height: 100px;
}
.fade.ng-enter,
.fade.ng-leave {
transition: all 0.3s ease-out;
}
.fade.ng-enter,
.fade.ng-leave-active {
opacity: 0;
position: relative;
top: 10px;
}
.fade.ng-enter.ng-enter-active,
.fade.ng-leave {
opacity: 1;
top: 0;
}
mod = angular.module('infiniteScroll', [])
mod.controller 'InfiniteScrollWindowController', [
'$window', '$document',
($window, $document) ->
window = angular.element $window
document = $document[0]
@getElement = () =>
window
@getHeight= () =>
$window.innerHeight
@getBottom = () =>
$window.pageYOffset + document.documentElement.clientHeight
return
]
mod.controller 'InfiniteScrollContainerController', [
'$window', '$element',
($window, $element) ->
window = angular.element $window
element = $element[0]
@getElement = () =>
$element
@getHeight = () =>
element.clientHeight
@getBottom = () =>
element.getBoundingClientRect().bottom + $window.pageYOffset
return
]
mod.directive 'infiniteScrollContainer', [
'$parse',
($parse) ->
restrict: 'A'
controller: 'InfiniteScrollContainerController'
]
mod.directive 'infiniteScroll', [
'$window', '$parse', '$timeout', '$controller',
($window, $parse, $timeout, $controller) ->
restrict: 'A'
require: ['infiniteScroll', '?^infiniteScrollContainer']
controller: ($scope, $element, $attrs) ->
window = angular.element $window
element = $element[0]
fn = $parse $attrs.infiniteScroll
options =
disabled: false
threshold: 0.1
@options = (opts) =>
if angular.isDefined(opts)
angular.extend options, opts
@check()
options
containerCtrl = null
@setContainerCtrl = (ctrl) =>
containerCtrl = ctrl if angular.isDefined(ctrl)
containerCtrl
@getBottom = () =>
element.getBoundingClientRect().bottom + $window.pageYOffset
@needMore = () =>
return false if options.disabled
elementBottom = @getBottom()
containerBottom = containerCtrl.getBottom()
remaining = elementBottom - containerBottom
console.log "#{elementBottom} - #{containerBottom}"
console.log "#{remaining} <= #{containerCtrl.getHeight() * options.threshold}"
remaining <= (containerCtrl.getHeight() * options.threshold)
lock = false
@check = () =>
if !lock and @needMore()
lock = true
$timeout =>
promise = fn($scope)
promise.then () =>
lock = false
$timeout @check
promise.catch () =>
lock = false
return
link: (scope, element, attrs, ctrls) ->
thisCtrl = ctrls[0]
containerCtrl = ctrls[1] or $controller('InfiniteScrollWindowController')
containerElement = containerCtrl.getElement()
thisCtrl.setContainerCtrl containerCtrl
checker = () =>
thisCtrl.check()
containerElement.on 'scroll', checker
scope.$on '$destroy', () =>
containerElement.off 'scroll', checker
if angular.isDefined(attrs.infiniteScrollOptions)
options = $parse attrs.infiniteScrollOptions
optionsWatch = () => options(scope)
optionsChange = (value) =>
if angular.isDefined(value)
thisCtrl.options(value or {})
scope.$watch optionsWatch, optionsChange, true
else
checker()
return
]