/*!
* angular-ui-scroll
* https://github.com/angular-ui/ui-scroll.git
* Version: 1.3.1 -- 2015-08-06T14:43:32.930Z
* License: MIT
*/
(function () {
'use strict';
/*!
globals: angular, window
List of used element methods available in JQuery but not in JQuery Lite
element.before(elem)
element.height()
element.outerHeight(true)
element.height(value) = only for Top/Bottom padding elements
element.scrollTop()
element.scrollTop(value)
*/
angular.module('ui.scroll', []).directive('uiScrollViewport', function() {
return {
controller: [
'$scope', '$element', function(scope, element) {
this.viewport = element;
return this;
}
]
};
}).directive('uiScroll', [
'$log', '$injector', '$rootScope', '$timeout', '$q', '$parse', function(console, $injector, $rootScope, $timeout, $q, $parse) {
var $animate;
if ($injector.has && $injector.has('$animate')) {
$animate = $injector.get('$animate');
}
return {
require: ['?^uiScrollViewport'],
transclude: 'element',
priority: 1000,
terminal: true,
compile: function(elementTemplate, attr, linker) {
return function($scope, element, $attr, controllers) {
var adapter, adapterOnScope, adjustBuffer, applyUpdate, bof, bottomVisiblePos, buffer, bufferPadding, bufferSize, builder, clipBottom, clipTop, datasource, datasourceName, dismissPendingRequests, enqueueFetch, eof, eventListener, fetch, finalize, first, insertElement, insertElementAnimated, insertItem, isDatasourceValid, itemName, loading, log, match, next, pending, reload, removeFromBuffer, removeItem, resizeAndScrollHandler, ridActual, scrollHeight, shouldLoadBottom, shouldLoadTop, topVisible, topVisiblePos, unsupportedMethod, viewport, viewportScope, wheelHandler;
log = console.debug || console.log;
if (!(match = $attr.uiScroll.match(/^\s*(\w+)\s+in\s+([\w\.]+)\s*$/))) {
throw new Error('Expected uiScroll in form of \'_item_ in _datasource_\' but got \' + $attr.uiScroll + \'');
}
itemName = match[1];
datasourceName = match[2];
datasource = $parse(datasourceName)($scope);
isDatasourceValid = function() {
return angular.isObject(datasource) && angular.isFunction(datasource.get);
};
if (!isDatasourceValid()) {
datasource = $injector.get(datasourceName);
if (!isDatasourceValid()) {
throw new Error(datasourceName + ' is not a valid datasource');
}
}
bufferSize = Math.max(3, +$attr.bufferSize || 10);
bufferPadding = function() {
return viewport.outerHeight() * Math.max(0.1, +$attr.padding || 0.1);
};
scrollHeight = function(elem) {
var ref;
return (ref = elem[0].scrollHeight) != null ? ref : elem[0].document.documentElement.scrollHeight;
};
builder = null;
ridActual = 0;
first = 1;
next = 1;
buffer = [];
pending = [];
eof = false;
bof = false;
removeItem = $animate ? angular.version.minor === 2 ? function(wrapper) {
var deferred;
buffer.splice(buffer.indexOf(wrapper), 1);
deferred = $q.defer();
$animate.leave(wrapper.element, function() {
wrapper.scope.$destroy();
return deferred.resolve();
});
return [deferred.promise];
} : function(wrapper) {
buffer.splice(buffer.indexOf(wrapper), 1);
return [
($animate.leave(wrapper.element)).then(function() {
return wrapper.scope.$destroy();
})
];
} : function(wrapper) {
buffer.splice(buffer.indexOf(wrapper), 1);
wrapper.element.remove();
wrapper.scope.$destroy();
return [];
};
insertElement = function(newElement, previousElement) {
element.after.apply(previousElement, newElement);
return [];
};
insertElementAnimated = $animate ? angular.version.minor === 2 ? function(newElement, previousElement) {
var deferred;
deferred = $q.defer();
$animate.enter(newElement, element, previousElement, function() {
return deferred.resolve();
});
return [deferred.promise];
} : function(newElement, previousElement) {
return [$animate.enter(newElement, element, previousElement)];
} : insertElement;
linker($scope.$new(), function(template) {
var bottomPadding, padding, repeaterType, topPadding, viewport;
repeaterType = template[0].localName;
if (repeaterType === 'dl') {
throw new Error('ui-scroll directive does not support <' + template[0].localName + '> as a repeating tag: ' + template[0].outerHTML);
}
if (repeaterType !== 'li' && repeaterType !== 'tr') {
repeaterType = 'div';
}
viewport = controllers[0] && controllers[0].viewport ? controllers[0].viewport : angular.element(window);
viewport.css({
'overflow-y': 'auto',
'display': 'block'
});
padding = function(repeaterType) {
var div, result, table;
switch (repeaterType) {
case 'tr':
table = angular.element('
');
div = table.find('div');
result = table.find('tr');
result.paddingHeight = function() {
return div.height.apply(div, arguments);
};
break;
default:
result = angular.element('<' + repeaterType + '>' + repeaterType + '>');
result.paddingHeight = result.height;
}
return result;
};
topPadding = padding(repeaterType);
element.before(topPadding);
bottomPadding = padding(repeaterType);
element.after(bottomPadding);
$scope.$on('$destroy', function() {
return template.remove();
});
return builder = {
viewport: viewport,
topPadding: function() {
return topPadding.paddingHeight.apply(topPadding, arguments);
},
bottomPadding: function() {
return bottomPadding.paddingHeight.apply(bottomPadding, arguments);
},
bottomDataPos: function() {
return scrollHeight(viewport) - bottomPadding.paddingHeight();
},
topDataPos: function() {
return topPadding.paddingHeight();
},
insertElement: function(e, sibling) {
return insertElement(e, sibling || topPadding);
},
insertElementAnimated: function(e, sibling) {
return insertElementAnimated(e, sibling || topPadding);
}
};
});
viewport = builder.viewport;
viewportScope = viewport.scope() || $rootScope;
topVisible = function(item) {
adapter.topVisible = item.scope[itemName];
adapter.topVisibleElement = item.element;
adapter.topVisibleScope = item.scope;
if ($attr.topVisible) {
$parse($attr.topVisible).assign(viewportScope, adapter.topVisible);
}
if ($attr.topVisibleElement) {
$parse($attr.topVisibleElement).assign(viewportScope, adapter.topVisibleElement);
}
if ($attr.topVisibleScope) {
$parse($attr.topVisibleScope).assign(viewportScope, adapter.topVisibleScope);
}
if (angular.isFunction(datasource.topVisible)) {
return datasource.topVisible(item);
}
};
loading = function(value) {
adapter.isLoading = value;
if ($attr.isLoading) {
$parse($attr.isLoading).assign($scope, value);
}
if (angular.isFunction(datasource.loading)) {
return datasource.loading(value);
}
};
removeFromBuffer = function(start, stop) {
var i, j, ref, ref1;
for (i = j = ref = start, ref1 = stop; ref <= ref1 ? j < ref1 : j > ref1; i = ref <= ref1 ? ++j : --j) {
buffer[i].scope.$destroy();
buffer[i].element.remove();
}
return buffer.splice(start, stop - start);
};
dismissPendingRequests = function() {
ridActual++;
return pending = [];
};
reload = function() {
dismissPendingRequests();
first = 1;
next = 1;
removeFromBuffer(0, buffer.length);
builder.topPadding(0);
builder.bottomPadding(0);
eof = false;
bof = false;
return adjustBuffer(ridActual);
};
bottomVisiblePos = function() {
return viewport.scrollTop() + viewport.outerHeight();
};
topVisiblePos = function() {
return viewport.scrollTop();
};
shouldLoadBottom = function() {
return !eof && builder.bottomDataPos() < bottomVisiblePos() + bufferPadding();
};
clipBottom = function() {
var bottomHeight, i, item, itemHeight, itemTop, j, newRow, overage, ref, rowTop;
bottomHeight = 0;
overage = 0;
for (i = j = ref = buffer.length - 1; ref <= 0 ? j <= 0 : j >= 0; i = ref <= 0 ? ++j : --j) {
item = buffer[i];
itemTop = item.element.offset().top;
newRow = rowTop !== itemTop;
rowTop = itemTop;
if (newRow) {
itemHeight = item.element.outerHeight(true);
}
if (builder.bottomDataPos() - bottomHeight - itemHeight > bottomVisiblePos() + bufferPadding()) {
if (newRow) {
bottomHeight += itemHeight;
}
overage++;
eof = false;
} else {
if (newRow) {
break;
}
overage++;
}
}
if (overage > 0) {
builder.bottomPadding(builder.bottomPadding() + bottomHeight);
removeFromBuffer(buffer.length - overage, buffer.length);
return next -= overage;
}
};
shouldLoadTop = function() {
return !bof && (builder.topDataPos() > topVisiblePos() - bufferPadding());
};
clipTop = function() {
var item, itemHeight, itemTop, j, len, newRow, overage, rowTop, topHeight;
topHeight = 0;
overage = 0;
for (j = 0, len = buffer.length; j < len; j++) {
item = buffer[j];
itemTop = item.element.offset().top;
newRow = rowTop !== itemTop;
rowTop = itemTop;
if (newRow) {
itemHeight = item.element.outerHeight(true);
}
if (builder.topDataPos() + topHeight + itemHeight < topVisiblePos() - bufferPadding()) {
if (newRow) {
topHeight += itemHeight;
}
overage++;
bof = false;
} else {
if (newRow) {
break;
}
overage++;
}
}
if (overage > 0) {
builder.topPadding(builder.topPadding() + topHeight);
removeFromBuffer(0, overage);
return first += overage;
}
};
enqueueFetch = function(rid, direction) {
if (!adapter.isLoading) {
loading(true);
}
if (pending.push(direction) === 1) {
return fetch(rid);
}
};
insertItem = function(operation, item) {
var itemScope, wrapper;
itemScope = $scope.$new();
itemScope[itemName] = item;
wrapper = {
scope: itemScope
};
linker(itemScope, function(clone) {
return wrapper.element = clone;
});
if (operation % 1 === 0) {
wrapper.op = 'insert';
return buffer.splice(operation, 0, wrapper);
} else {
wrapper.op = operation;
switch (operation) {
case 'append':
return buffer.push(wrapper);
case 'prepend':
return buffer.unshift(wrapper);
}
}
};
adjustBuffer = function(rid, finalize) {
var promises, toBePrepended, toBeRemoved;
promises = [];
toBePrepended = [];
toBeRemoved = [];
return $timeout(function() {
var bottomPos, heightIncrement, i, item, itemHeight, itemTop, j, k, l, len, len1, len2, len3, len4, m, n, newRow, rowTop, topHeight, wrapper;
bottomPos = builder.bottomDataPos();
for (i = j = 0, len = buffer.length; j < len; i = ++j) {
wrapper = buffer[i];
switch (wrapper.op) {
case 'prepend':
toBePrepended.unshift(wrapper);
break;
case 'append':
if (i === 0) {
builder.insertElement(wrapper.element);
} else {
builder.insertElement(wrapper.element, buffer[i - 1].element);
}
wrapper.op = 'none';
break;
case 'insert':
if (i === 0) {
promises = promises.concat(builder.insertElementAnimated(wrapper.element));
} else {
promises = promises.concat(builder.insertElementAnimated(wrapper.element, buffer[i - 1].element));
}
wrapper.op = 'none';
break;
case 'remove':
toBeRemoved.push(wrapper);
}
}
for (k = 0, len1 = toBeRemoved.length; k < len1; k++) {
wrapper = toBeRemoved[k];
promises = promises.concat(removeItem(wrapper));
}
builder.bottomPadding(Math.max(0, builder.bottomPadding() - (builder.bottomDataPos() - bottomPos)));
if (toBePrepended.length) {
bottomPos = builder.bottomDataPos();
for (l = 0, len2 = toBePrepended.length; l < len2; l++) {
wrapper = toBePrepended[l];
builder.insertElement(wrapper.element);
wrapper.op = 'none';
}
heightIncrement = builder.bottomDataPos() - bottomPos;
if (builder.topPadding() >= heightIncrement) {
builder.topPadding(builder.topPadding() - heightIncrement);
} else {
viewport.scrollTop(viewport.scrollTop() + heightIncrement);
}
}
for (i = m = 0, len3 = buffer.length; m < len3; i = ++m) {
item = buffer[i];
item.scope.$index = first + i;
}
if (shouldLoadBottom()) {
enqueueFetch(rid, true);
} else {
if (shouldLoadTop()) {
enqueueFetch(rid, false);
}
}
if (finalize) {
finalize(rid);
}
if (pending.length === 0) {
topHeight = 0;
for (n = 0, len4 = buffer.length; n < len4; n++) {
item = buffer[n];
itemTop = item.element.offset().top;
newRow = rowTop !== itemTop;
rowTop = itemTop;
if (newRow) {
itemHeight = item.element.outerHeight(true);
}
if (newRow && (builder.topDataPos() + topHeight + itemHeight < topVisiblePos())) {
topHeight += itemHeight;
} else {
if (newRow) {
topVisible(item);
}
break;
}
}
}
if (promises.length) {
return $q.all(promises).then(function() {
return adjustBuffer(rid);
});
}
});
};
finalize = function(rid) {
return adjustBuffer(rid, function() {
pending.shift();
if (pending.length === 0) {
return loading(false);
} else {
return fetch(rid);
}
});
};
fetch = function(rid) {
var customBufferSize, q, rowTop;
if (pending[0]) {
if (buffer.length && !shouldLoadBottom()) {
return finalize(rid);
} else {
return datasource.get(next, bufferSize, function(result) {
var item, j, len;
if ((rid && rid !== ridActual) || $scope.$$destroyed) {
return;
}
if (result.length < bufferSize) {
eof = true;
builder.bottomPadding(0);
}
if (result.length > 0) {
clipTop();
for (j = 0, len = result.length; j < len; j++) {
item = result[j];
++next;
insertItem('append', item);
}
}
return finalize(rid);
});
}
} else {
if (buffer.length && !shouldLoadTop()) {
return finalize(rid);
} else {
rowTop = buffer[0].element.offset().top;
q = 0;
while (q < buffer.length) {
if (rowTop !== buffer[q].element.offset().top) {
break;
}
q++;
}
customBufferSize = bufferSize % q !== 0 ? bufferSize + q - (bufferSize % q) : bufferSize;
return datasource.get(first - customBufferSize, customBufferSize, function(result) {
var i, j, ref;
if ((rid && rid !== ridActual) || $scope.$$destroyed) {
return;
}
if (result.length < bufferSize) {
bof = true;
builder.topPadding(0);
}
if (result.length > 0) {
if (buffer.length) {
clipBottom();
}
for (i = j = ref = result.length - 1; ref <= 0 ? j <= 0 : j >= 0; i = ref <= 0 ? ++j : --j) {
--first;
insertItem('prepend', result[i]);
}
}
return finalize(rid);
});
}
}
};
resizeAndScrollHandler = function() {
if (!$rootScope.$$phase && !adapter.isLoading) {
adjustBuffer();
return $scope.$apply();
}
};
wheelHandler = function(event) {
var scrollTop, yMax;
scrollTop = viewport[0].scrollTop;
yMax = viewport[0].scrollHeight - viewport[0].clientHeight;
if ((scrollTop === 0 && !bof) || (scrollTop === yMax && !eof)) {
return event.preventDefault();
}
};
viewport.bind('resize', resizeAndScrollHandler);
viewport.bind('scroll', resizeAndScrollHandler);
viewport.bind('mousewheel', wheelHandler);
$scope.$watch(datasource.revision, reload);
$scope.$on('$destroy', function() {
var item, j, len;
for (j = 0, len = buffer.length; j < len; j++) {
item = buffer[j];
item.scope.$destroy();
item.element.remove();
}
viewport.unbind('resize', resizeAndScrollHandler);
viewport.unbind('scroll', resizeAndScrollHandler);
return viewport.unbind('mousewheel', wheelHandler);
});
adapter = {};
adapter.isLoading = false;
adapter.reload = reload;
applyUpdate = function(wrapper, newItems) {
var i, j, keepIt, len, newItem, pos, ref;
if (angular.isArray(newItems)) {
pos = (buffer.indexOf(wrapper)) + 1;
ref = newItems.reverse();
for (i = j = 0, len = ref.length; j < len; i = ++j) {
newItem = ref[i];
if (newItem === wrapper.scope[itemName]) {
keepIt = true;
pos--;
} else {
insertItem(pos, newItem);
}
}
if (!keepIt) {
return wrapper.op = 'remove';
}
}
};
adapter.applyUpdates = function(arg1, arg2) {
var bufferClone, i, j, len, ref, wrapper;
dismissPendingRequests();
if (angular.isFunction(arg1)) {
bufferClone = buffer.slice(0);
for (i = j = 0, len = bufferClone.length; j < len; i = ++j) {
wrapper = bufferClone[i];
applyUpdate(wrapper, arg1(wrapper.scope[itemName], wrapper.scope, wrapper.element));
}
} else {
if (arg1 % 1 === 0) {
if ((0 <= (ref = arg1 - first) && ref < buffer.length)) {
applyUpdate(buffer[arg1 - first], arg2);
}
} else {
throw new Error('applyUpdates - ' + arg1 + ' is not a valid index');
}
}
return adjustBuffer(ridActual);
};
adapter.append = function(newItems) {
var item, j, len;
dismissPendingRequests();
for (j = 0, len = newItems.length; j < len; j++) {
item = newItems[j];
++next;
insertItem('append', item);
}
return adjustBuffer(ridActual);
};
adapter.prepend = function(newItems) {
var item, j, len, ref;
dismissPendingRequests();
ref = newItems.reverse();
for (j = 0, len = ref.length; j < len; j++) {
item = ref[j];
--first;
insertItem('prepend', item);
}
return adjustBuffer(ridActual);
};
if ($attr.adapter) {
adapterOnScope = $parse($attr.adapter)($scope);
if (!adapterOnScope) {
$parse($attr.adapter).assign($scope, {});
adapterOnScope = $parse($attr.adapter)($scope);
}
angular.extend(adapterOnScope, adapter);
adapter = adapterOnScope;
}
unsupportedMethod = function(token) {
throw new Error(token + ' event is no longer supported - use applyUpdates instead');
};
eventListener = datasource.scope ? datasource.scope.$new() : $scope.$new();
eventListener.$on('insert.item', function() {
return unsupportedMethod('insert');
});
eventListener.$on('update.items', function() {
return unsupportedMethod('update');
});
return eventListener.$on('delete.items', function() {
return unsupportedMethod('delete');
});
};
}
};
}
]);
/*
//# sourceURL=src/ui-scroll.js
*/
}());