diff --git a/src/modules/padding.js b/src/modules/padding.js
index 23e5df8c..ce66e22d 100644
--- a/src/modules/padding.js
+++ b/src/modules/padding.js
@@ -49,28 +49,36 @@ Object.getOwnPropertyNames(CacheProto.prototype).forEach(methodName =>
Cache.prototype[methodName] = CacheProto.prototype[methodName]
);
-export default function Padding(template) {
- let result;
-
+function generateElement(template) {
if(template.nodeType !== Node.ELEMENT_NODE) {
throw new Error('ui-scroll directive requires an Element node for templating the view');
}
-
+ let element;
switch (template.tagName.toLowerCase()) {
case 'dl':
throw new Error(`ui-scroll directive does not support <${template.tagName}> as a repeating tag: ${template.outerHTML}`);
case 'tr':
let table = angular.element('
');
- result = table.find('tr');
+ element = table.find('tr');
break;
case 'li':
- result = angular.element('');
+ element = angular.element('');
break;
default:
- result = angular.element('');
+ element = angular.element('');
}
+ return element;
+}
- result.cache = new Cache();
+class Padding {
+ constructor(template) {
+ this.element = generateElement(template);
+ this.cache = new Cache();
+ }
+
+ height() {
+ return this.element.height.apply(this.element, arguments);
+ }
+}
- return result;
-}
\ No newline at end of file
+export default Padding;
\ No newline at end of file
diff --git a/src/modules/viewport.js b/src/modules/viewport.js
index fe8aaee1..3d384f79 100644
--- a/src/modules/viewport.js
+++ b/src/modules/viewport.js
@@ -25,14 +25,20 @@ export default function Viewport(elementRoutines, buffer, element, viewportContr
createPaddingElements(template) {
topPadding = new Padding(template);
bottomPadding = new Padding(template);
- element.before(topPadding);
- element.after(bottomPadding);
+ element.before(topPadding.element);
+ element.after(bottomPadding.element);
+ topPadding.height(0);
+ bottomPadding.height(0);
},
applyContainerStyle() {
- if (container && container !== viewport) {
+ if (!container) {
+ return true;
+ }
+ if(container !== viewport) {
viewport.css('height', window.getComputedStyle(container[0]).height);
}
+ return viewport.height() > 0;
},
bottomDataPos() {
@@ -54,11 +60,11 @@ export default function Viewport(elementRoutines, buffer, element, viewportContr
},
insertElement(e, sibling) {
- return elementRoutines.insertElement(e, sibling || topPadding);
+ return elementRoutines.insertElement(e, sibling || topPadding.element);
},
insertElementAnimated(e, sibling) {
- return elementRoutines.insertElementAnimated(e, sibling || topPadding);
+ return elementRoutines.insertElementAnimated(e, sibling || topPadding.element);
},
shouldLoadBottom() {
diff --git a/src/ui-scroll.js b/src/ui-scroll.js
index c3341376..1dc0ff03 100644
--- a/src/ui-scroll.js
+++ b/src/ui-scroll.js
@@ -39,9 +39,10 @@ angular.module('ui.scroll', [])
'$injector',
'$rootScope',
'$timeout',
+ '$interval',
'$q',
'$parse',
- function (console, $injector, $rootScope, $timeout, $q, $parse) {
+ function (console, $injector, $rootScope, $timeout, $interval, $q, $parse) {
return {
require: ['?^uiScrollViewport'],
@@ -59,7 +60,7 @@ angular.module('ui.scroll', [])
}
function parseNumericAttr(value, defaultValue) {
- let result = $parse(value)($scope);
+ const result = $parse(value)($scope);
return isNaN(result) ? defaultValue : result;
}
@@ -67,6 +68,8 @@ angular.module('ui.scroll', [])
const BUFFER_DEFAULT = 10;
const PADDING_MIN = 0.3;
const PADDING_DEFAULT = 0.5;
+ const MAX_VIEWPORT_DELAY = 500;
+ const VIEWPORT_POLLING_INTERVAL = 50;
let datasource = null;
const itemName = match[1];
@@ -78,16 +81,16 @@ angular.module('ui.scroll', [])
let ridActual = 0;// current data revision id
let pending = [];
- let elementRoutines = new ElementRoutines($injector, $q);
- let buffer = new ScrollBuffer(elementRoutines, bufferSize);
- let viewport = new Viewport(elementRoutines, buffer, element, viewportController, $rootScope, padding);
- let adapter = new Adapter(viewport, buffer, adjustBuffer, reload, $attr, $parse, $scope);
+ const elementRoutines = new ElementRoutines($injector, $q);
+ const buffer = new ScrollBuffer(elementRoutines, bufferSize);
+ const viewport = new Viewport(elementRoutines, buffer, element, viewportController, $rootScope, padding);
+ const adapter = new Adapter(viewport, buffer, adjustBuffer, reload, $attr, $parse, $scope);
if (viewportController) {
viewportController.adapter = adapter;
}
- let isDatasourceValid = () => angular.isObject(datasource) && angular.isFunction(datasource.get);
+ const isDatasourceValid = () => angular.isObject(datasource) && angular.isFunction(datasource.get);
datasource = $parse(datasourceName)($scope); // try to get datasource on scope
if (!isDatasourceValid()) {
datasource = $injector.get(datasourceName); // try to inject datasource as service
@@ -114,7 +117,7 @@ angular.module('ui.scroll', [])
}
function defineIndexProperty(datasource, propName, propUserName) {
- let descriptor = Object.getOwnPropertyDescriptor(datasource, propName);
+ const descriptor = Object.getOwnPropertyDescriptor(datasource, propName);
if (descriptor && (descriptor.set || descriptor.get)) {
return;
}
@@ -124,7 +127,7 @@ angular.module('ui.scroll', [])
set: (value) => {
getter = value;
buffer[propUserName] = value;
- let topPaddingHeightOld = viewport.topDataPos();
+ const topPaddingHeightOld = viewport.topDataPos();
viewport.adjustPaddings();
if (propName === 'minIndex') {
viewport.onAfterMinIndexSet(topPaddingHeightOld);
@@ -157,6 +160,26 @@ angular.module('ui.scroll', [])
}, success);
};
+ const run = () => {
+ let tryCount = 0;
+ if(!viewport.applyContainerStyle()) {
+ const timer = $interval(() => {
+ tryCount++;
+ if(viewport.applyContainerStyle()) {
+ $interval.cancel(timer);
+ reload();
+ }
+ if(tryCount * VIEWPORT_POLLING_INTERVAL >= MAX_VIEWPORT_DELAY) {
+ $interval.cancel(timer);
+ throw Error(`ui-scroll directive requires a viewport with non-zero height in ${MAX_VIEWPORT_DELAY}ms`);
+ }
+ }, VIEWPORT_POLLING_INTERVAL);
+ }
+ else {
+ reload();
+ }
+ };
+
/**
* Build padding elements
*
@@ -180,10 +203,7 @@ angular.module('ui.scroll', [])
viewport.bind('mousewheel', wheelHandler);
- $timeout(() => {
- viewport.applyContainerStyle();
- reload();
- });
+ run();
/* Private function definitions */
@@ -239,7 +259,7 @@ angular.module('ui.scroll', [])
function createElement(wrapper, insertAfter, insertElement) {
let promises = null;
- let sibling = (insertAfter > 0) ? buffer[insertAfter - 1].element : undefined;
+ const sibling = (insertAfter > 0) ? buffer[insertAfter - 1].element : undefined;
linker((clone, scope) => {
promises = insertElement(clone, sibling);
wrapper.element = clone;
@@ -248,7 +268,7 @@ angular.module('ui.scroll', [])
});
// ui-scroll-grid apply
if (adapter.transform) {
- let tdInitializer = wrapper.scope.uiScrollTdInitializer;
+ const tdInitializer = wrapper.scope.uiScrollTdInitializer;
if (tdInitializer && tdInitializer.linking) {
adapter.transform(wrapper.scope, wrapper.element);
} else {
@@ -459,8 +479,8 @@ angular.module('ui.scroll', [])
function wheelHandler(event) {
if (!adapter.disabled) {
- let scrollTop = viewport[0].scrollTop;
- let yMax = viewport[0].scrollHeight - viewport[0].clientHeight;
+ const scrollTop = viewport[0].scrollTop;
+ const yMax = viewport[0].scrollHeight - viewport[0].clientHeight;
if ((scrollTop === 0 && !buffer.bof) || (scrollTop === yMax && !buffer.eof)) {
event.preventDefault();
diff --git a/test/AssigningSpec.js b/test/AssigningSpec.js
index dd7edcf2..12c2edce 100644
--- a/test/AssigningSpec.js
+++ b/test/AssigningSpec.js
@@ -40,14 +40,13 @@ describe('uiScroll', function () {
};
var executeTest = function(template, scopeSelector, scopeContainer) {
- inject(function($rootScope, $compile, $timeout) {
+ inject(function($rootScope, $compile) {
// build and render
var templateElement = angular.element(template);
var scope = $rootScope.$new();
angular.element(document).find('body').append(templateElement);
$compile(templateElement)(scope);
scope.$apply();
- $timeout.flush();
// find adapter element and scope container
var adapterContainer;
diff --git a/test/VisibilitySwitchingSpec.js b/test/VisibilitySwitchingSpec.js
index 56bf2d6a..5cee1876 100644
--- a/test/VisibilitySwitchingSpec.js
+++ b/test/VisibilitySwitchingSpec.js
@@ -1,79 +1,67 @@
/*global describe, beforeEach, module, it, expect, runTest */
-describe('uiScroll visibility. ', function() {
+describe('uiScroll visibility.', () => {
'use strict';
beforeEach(module('ui.scroll'));
beforeEach(module('ui.scroll.test.datasources'));
- var getScrollSettings = function() {
- return {
- datasource: 'myMultipageDatasource',
- viewportHeight: 200,
- itemHeight: 40,
- bufferSize: 3,
- adapter: 'adapter'
- };
+ const scrollSettings = {
+ datasource: 'myMultipageDatasource',
+ viewportHeight: 200,
+ itemHeight: 40,
+ bufferSize: 3,
+ adapter: 'adapter'
};
- var checkContent = function(rows, count) {
+ const checkContent = (rows, count) => {
+ expect(rows.length).toBe(count);
for (var i = 1; i < count - 1; i++) {
- var row = rows[i];
- expect(row.tagName.toLowerCase()).toBe('div');
- expect(row.innerHTML).toBe(i + ': item' + i);
+ expect(rows[i].innerHTML).toBe(i + ': item' + i);
}
};
- describe('Viewport visibility changing. ', function() {
- var onePackItemsCount = 3 + 2;
- var twoPacksItemsCount = 3 * 3 + 2;
+ const onePackItemsCount = 3 * 1 + 2;
+ const twoPacksItemsCount = 3 * 2 + 2;
+ const threePacksItemsCount = 3 * 3 + 2;
- it('Should create 9 divs with data (+ 2 padding divs).', function() {
- runTest(getScrollSettings(),
- function(viewport) {
- expect(viewport.children().length).toBe(twoPacksItemsCount);
+ describe('Viewport visibility changing\n', () => {
+
+ it('should create 9 divs with data (+ 2 padding divs)', () =>
+ runTest(scrollSettings,
+ (viewport) => {
expect(viewport.scrollTop()).toBe(0);
- expect(viewport.children().css('height')).toBe('0px');
- expect(angular.element(viewport.children()[twoPacksItemsCount - 1]).css('height')).toBe('0px');
- checkContent(viewport.children(), twoPacksItemsCount);
+ checkContent(viewport.children(), threePacksItemsCount);
}
- );
- });
+ )
+ );
- it('Should preserve elements after visibility switched off (display:none).', function() {
- runTest(getScrollSettings(),
- function(viewport, scope) {
+ it('should preserve elements after visibility switched off (display:none)', () =>
+ runTest(scrollSettings,
+ (viewport, scope) => {
viewport.css('display', 'none');
scope.$apply();
- expect(viewport.children().length).toBe(twoPacksItemsCount);
expect(viewport.scrollTop()).toBe(0);
- expect(viewport.children().css('height')).toBe('0px');
- expect(angular.element(viewport.children()[twoPacksItemsCount - 1]).css('height')).toBe('0px');
- checkContent(viewport.children(), twoPacksItemsCount);
+ checkContent(viewport.children(), threePacksItemsCount);
}
- );
- });
-
+ )
+ );
- it('Should only load one batch with visibility switched off (display:none).', function() {
- runTest(getScrollSettings(),
- function(viewport, scope) {
+ it('should only load one batch with visibility switched off (display:none)', () =>
+ runTest(scrollSettings,
+ (viewport, scope) => {
viewport.css('display', 'none');
scope.adapter.reload();
- expect(viewport.children().length).toBe(onePackItemsCount);
expect(viewport.scrollTop()).toBe(0);
- expect(viewport.children().css('height')).toBe('0px');
- expect(angular.element(viewport.children()[onePackItemsCount - 1]).css('height')).toBe('0px');
checkContent(viewport.children(), onePackItemsCount);
}
- );
- });
+ )
+ );
- it('Should load full set after css-visibility switched back on.', function() {
- var scrollSettings = getScrollSettings();
+ it('should load full set after css-visibility switched back on', () =>
runTest(scrollSettings,
- function(viewport, scope, $timeout) {
+ (viewport, scope, $timeout) => {
viewport.css('display', 'none');
scope.adapter.reload();
@@ -81,99 +69,62 @@ describe('uiScroll visibility. ', function() {
scope.$apply();
$timeout.flush();
- let rows = viewport.children();
- expect(rows.length).toBe(twoPacksItemsCount);
expect(viewport.scrollTop()).toBe(0);
- expect(rows.css('height')).toBe('0px');
- expect(angular.element(rows[twoPacksItemsCount - 1]).css('height')).toBe('0px');
- checkContent(rows, onePackItemsCount);
+ checkContent(viewport.children(), threePacksItemsCount);
+ expect(scope.adapter.topVisible).toBe('item1');
}
- );
- });
-
- it('Should load full set after scope-visibility switched back on.', function() {
- var scrollSettings = getScrollSettings();
- scrollSettings.wrapper = {
- start: '',
- end: '
'
- };
- runTest(scrollSettings,
- function(viewport, scope, $timeout) {
+ )
+ );
+
+ it('should load full set after scope-visibility switched back on', () =>
+ runTest(Object.assign({}, scrollSettings, {
+ wrapper: {
+ start: '',
+ end: '
'
+ }
+ }), (viewport, scope) => {
scope.show = false;
- scope.adapter.reload();
scope.$apply();
+ expect(viewport.children().length).toBe(0);
scope.show = true;
scope.$apply();
- $timeout.flush();
-
- let rows = viewport.children().children();
- expect(rows.length).toBe(twoPacksItemsCount);
expect(viewport.scrollTop()).toBe(0);
- expect(rows.css('height')).toBe('0px');
- expect(angular.element(rows[twoPacksItemsCount - 1]).css('height')).toBe('0px');
- checkContent(rows, onePackItemsCount);
+ checkContent(viewport.children().children(), threePacksItemsCount);
}, {
scope: {
show: true
}
}
- );
- });
-
- it('Should stay on the 1st item after the visibility is on (infinite list).', function() {
- var scrollSettings = getScrollSettings();
- scrollSettings.datasource = 'myInfiniteDatasource';
- scrollSettings.topVisible = 'topVisible';
- runTest(scrollSettings,
- function(viewport, scope, $timeout) {
- viewport.css('display', 'none');
- scope.adapter.reload();
- scope.$apply();
-
- viewport.css('display', 'block');
- scope.$apply();
- $timeout.flush();
-
- expect(scope.topVisible).toBe('item1');
- }
- );
- });
+ )
+ );
});
- describe('Items visibility changing. ', function() {
- var scrollSettings = getScrollSettings();
- scrollSettings.itemHeight = '0';
- var onePackItemsCount = 3 + 2;
- var twoPacksItemsCount = 3 * 2 + 2;
-
- it('Should load only one batch with items height = 0', function() {
- runTest(scrollSettings,
- function(viewport) {
+ describe('Items visibility changing\n', () => {
+ it('should load only one batch with items height = 0', () =>
+ runTest(Object.assign({}, scrollSettings, { itemHeight: '0' }),
+ (viewport) => {
expect(viewport.children().length).toBe(onePackItemsCount);
expect(viewport.scrollTop()).toBe(0);
checkContent(viewport.children(), onePackItemsCount);
}
- );
- });
-
- it('Should continue loading after the height of some item switched to non-zero.', function() {
- runTest(scrollSettings,
- function(viewport, scope, $timeout) {
+ )
+ );
+ it('should load one more batch after the height of some item is set to a positive value', () =>
+ runTest(Object.assign({}, scrollSettings, { itemHeight: '0' }),
+ (viewport, scope, $timeout) => {
angular.element(viewport.children()[onePackItemsCount - 2]).css('height', 40);
expect(angular.element(viewport.children()[onePackItemsCount - 2]).css('height')).toBe('40px');
scope.$apply();
$timeout.flush();
- expect(viewport.children().length).toBe(twoPacksItemsCount);
expect(viewport.scrollTop()).toBe(0);
checkContent(viewport.children(), twoPacksItemsCount);
}
- );
- });
+ )
+ );
});
-
});
diff --git a/test/config/karma.conf.files.js b/test/config/karma.conf.files.js
index f8d44881..eb1a1549 100644
--- a/test/config/karma.conf.files.js
+++ b/test/config/karma.conf.files.js
@@ -6,7 +6,7 @@ var files = [
'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-mocks.js',
'../misc/test.css',
'../misc/datasources.js',
- '../misc/scaffolding.js',
+ '../misc/scaffolding*.js',
'../*Spec.js',
{
pattern: scrollerPath + '*.js.map',
diff --git a/test/misc/scaffolding.js b/test/misc/scaffolding.js
index 91b06e5a..a63d00cf 100644
--- a/test/misc/scaffolding.js
+++ b/test/misc/scaffolding.js
@@ -21,34 +21,6 @@ function createHtml(settings) {
'';
}
-function createGridHtml (settings) {
- var viewportStyle = ' style="height:' + (settings.viewportHeight || 200) + 'px"';
- var columns = ['col0', 'col1', 'col2', 'col3'];
-
- var html =
- '' +
- '' +
- '';
- columns.forEach(col => { html +=
- '' + col + ' | ';
- }); html +=
- '
' +
- '' +
- '' +
- '';
- if(settings.rowTemplate) {
- html += settings.rowTemplate;
- } else {
- columns.forEach(col => { html +=
- '{{item.' + col + '}} | ';
- });
- } html +=
- '
' +
- '' +
- '
';
- return html;
-}
-
function finalize(scroller, options = {}, scope, $timeout) {
scroller.remove();
@@ -71,7 +43,6 @@ function runTest(scrollSettings, run, options = {}) {
var compile = function() {
$compile(scroller)(scope);
scope.$apply();
- $timeout.flush();
};
if (typeof options.catch === 'function') {
@@ -92,31 +63,4 @@ function runTest(scrollSettings, run, options = {}) {
}
}
});
-}
-
-function runGridTest(scrollSettings, run, options = {}) {
- inject(function($rootScope, $compile, $window, $timeout) {
- var scroller = angular.element(createGridHtml(scrollSettings));
- var scope = $rootScope.$new();
-
- angular.element(document).find('body').append(scroller);
- var head = angular.element(scroller.children()[0]);
- var body = angular.element(scroller.children()[1]);
-
- if (options.scope) {
- angular.extend(scope, options.scope);
- }
-
- $compile(scroller)(scope);
-
- scope.$apply();
- $timeout.flush();
-
- try {
- run(head, body, scope, $timeout);
- } finally {
- finalize(scroller, options, scope, $timeout);
- }
-
- });
}
\ No newline at end of file
diff --git a/test/misc/scaffoldingGrid.js b/test/misc/scaffoldingGrid.js
new file mode 100644
index 00000000..fd32b7ee
--- /dev/null
+++ b/test/misc/scaffoldingGrid.js
@@ -0,0 +1,61 @@
+function createGridHtml (settings) {
+ var viewportStyle = ' style="height:' + (settings.viewportHeight || 200) + 'px"';
+ var columns = ['col0', 'col1', 'col2', 'col3'];
+
+ var html =
+ '' +
+ '' +
+ '';
+ columns.forEach(col => { html +=
+ '' + col + ' | ';
+ }); html +=
+ '
' +
+ '' +
+ '' +
+ '';
+ if(settings.rowTemplate) {
+ html += settings.rowTemplate;
+ } else {
+ columns.forEach(col => { html +=
+ '{{item.' + col + '}} | ';
+ });
+ } html +=
+ '
' +
+ '' +
+ '
';
+ return html;
+}
+
+function finalize(scroller, options = {}, scope, $timeout) {
+ scroller.remove();
+
+ if (typeof options.cleanupTest === 'function') {
+ options.cleanupTest(scroller, scope, $timeout);
+ }
+}
+
+function runGridTest(scrollSettings, run, options = {}) {
+ inject(function($rootScope, $compile, $window, $timeout) {
+ var scroller = angular.element(createGridHtml(scrollSettings));
+ var scope = $rootScope.$new();
+
+ angular.element(document).find('body').append(scroller);
+ var head = angular.element(scroller.children()[0]);
+ var body = angular.element(scroller.children()[1]);
+
+ if (options.scope) {
+ angular.extend(scope, options.scope);
+ }
+
+ $compile(scroller)(scope);
+
+ scope.$apply();
+ $timeout.flush();
+
+ try {
+ run(head, body, scope, $timeout);
+ } finally {
+ finalize(scroller, options, scope, $timeout);
+ }
+ });
+}
\ No newline at end of file