diff --git a/demo/examples/applyUpdate.html b/demo/examples/applyUpdate.html index 5c3270f3..59479daf 100644 --- a/demo/examples/applyUpdate.html +++ b/demo/examples/applyUpdate.html @@ -3,7 +3,7 @@ Scroller Demo (multiple lists) - + diff --git a/demo/examples/bootstrapWellClass.html b/demo/examples/bootstrapWellClass.html index c662cbff..f17100c2 100644 --- a/demo/examples/bootstrapWellClass.html +++ b/demo/examples/bootstrapWellClass.html @@ -3,7 +3,7 @@ Scroller Demo (bootstrap's well css class) - + diff --git a/demo/examples/customviewport.html b/demo/examples/customviewport.html index 8fa7e77d..26590a87 100644 --- a/demo/examples/customviewport.html +++ b/demo/examples/customviewport.html @@ -3,7 +3,7 @@ Scroller Demo (multiple lists) - + diff --git a/demo/examples/listScroller.html b/demo/examples/listScroller.html index 546d28ef..b197905e 100644 --- a/demo/examples/listScroller.html +++ b/demo/examples/listScroller.html @@ -3,7 +3,7 @@ Scroller Demo (list based) - + diff --git a/demo/examples/persistentScroll.html b/demo/examples/persistentScroll.html index d8402f48..41f9c954 100644 --- a/demo/examples/persistentScroll.html +++ b/demo/examples/persistentScroll.html @@ -3,7 +3,7 @@ Scroller Demo (list based) - + diff --git a/demo/examples/positionedList.html b/demo/examples/positionedList.html index 27d9f367..51feb353 100644 --- a/demo/examples/positionedList.html +++ b/demo/examples/positionedList.html @@ -3,7 +3,7 @@ Scroller Demo (list based) - + diff --git a/demo/examples/repro.html b/demo/examples/repro.html index 01b3a581..28aac529 100644 --- a/demo/examples/repro.html +++ b/demo/examples/repro.html @@ -3,7 +3,7 @@ Scroller Demo (multiple lists) - + diff --git a/demo/examples/scopeDatasource.html b/demo/examples/scopeDatasource.html index 1f22e6af..8c84c7b6 100644 --- a/demo/examples/scopeDatasource.html +++ b/demo/examples/scopeDatasource.html @@ -3,7 +3,7 @@ Scroller Demo (datasource within scope) - + diff --git a/demo/examples/scrollBubblingPrevent.html b/demo/examples/scrollBubblingPrevent.html index 37a0f665..c782a2be 100644 --- a/demo/examples/scrollBubblingPrevent.html +++ b/demo/examples/scrollBubblingPrevent.html @@ -4,7 +4,7 @@ Scroller Demo (scroll bubbles up only on eof/bof) - + diff --git a/demo/examples/tableScroller.html b/demo/examples/tableScroller.html index b8982b6e..0a1effd9 100644 --- a/demo/examples/tableScroller.html +++ b/demo/examples/tableScroller.html @@ -3,7 +3,7 @@ Scroller Demo (table based) - + diff --git a/demo/examples/windowviewport.html b/demo/examples/windowviewport.html index fd3b6bb1..260236f5 100644 --- a/demo/examples/windowviewport.html +++ b/demo/examples/windowviewport.html @@ -3,7 +3,7 @@ Scroller Demo (entire window) - + diff --git a/demo/examples/windowviewportInline.html b/demo/examples/windowviewportInline.html index 4f8e7a49..fc49689d 100644 --- a/demo/examples/windowviewportInline.html +++ b/demo/examples/windowviewportInline.html @@ -3,7 +3,7 @@ Scroller Demo (entire window) - + diff --git a/dist/ui-scroll-jqlite.js b/dist/ui-scroll-jqlite.js index d0f224c8..ddeef73b 100644 --- a/dist/ui-scroll-jqlite.js +++ b/dist/ui-scroll-jqlite.js @@ -1,12 +1,12 @@ -/*! - * angular-ui-scroll - * https://github.com/angular-ui/ui-scroll.git - * Version: 1.3.3 -- 2016-03-04T12:15:03.597Z - * License: MIT - */ - - - (function () { +/*! + * angular-ui-scroll + * https://github.com/angular-ui/ui-scroll.git + * Version: 1.3.3 -- 2016-03-08T19:39:30.954Z + * License: MIT + */ + + + (function () { 'use strict'; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; }(); diff --git a/dist/ui-scroll-jqlite.min.js b/dist/ui-scroll-jqlite.min.js index 9b8c549b..24a078af 100644 --- a/dist/ui-scroll-jqlite.min.js +++ b/dist/ui-scroll-jqlite.min.js @@ -1,7 +1,7 @@ /*! * angular-ui-scroll * https://github.com/angular-ui/ui-scroll.git - * Version: 1.3.3 -- 2016-03-04T12:15:03.597Z + * Version: 1.3.3 -- 2016-03-08T19:39:30.954Z * License: MIT */ !function(){"use strict";var a=function(){function a(a,b){var c=[],d=!0,e=!1,f=void 0;try{for(var g,h=a[Symbol.iterator]();!(d=(g=h.next()).done)&&(c.push(g.value),!b||c.length!==b);d=!0);}catch(i){e=!0,f=i}finally{try{!d&&h["return"]&&h["return"]()}finally{if(e)throw f}}return c}return function(b,c){if(Array.isArray(b))return b;if(Symbol.iterator in Object(b))return a(b,c);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();angular.module("ui.scroll.jqlite",["ui.scroll"]).service("jqLiteExtras",["$log","$window",function(b,c){return{registerFor:function(b){function d(b,c,d){var e=b[0],f=a({top:["scrollTop","pageYOffset","scrollLeft"],left:["scrollLeft","pageXOffset","scrollTop"]}[c],3),g=f[0],h=f[1],i=f[2];return j(e)?angular.isDefined(d)?e.scrollTo(b[i].call(b),d):h in e?e[h]:e.document.documentElement[g]:(angular.isDefined(d)&&(e[g]=d),e[g])}function e(b,c){var d=void 0,e=void 0,f=void 0,h=void 0,k=void 0,l=void 0,m=void 0,n=void 0,o=void 0,p=void 0,q=void 0,r=void 0;if(j(b))return d=document.documentElement[{height:"clientHeight",width:"clientWidth"}[c]],{base:d,padding:0,border:0,margin:0};var s=a({width:[b.offsetWidth,"Left","Right"],height:[b.offsetHeight,"Top","Bottom"]}[c],3);return d=s[0],m=s[1],n=s[2],l=i(b),q=g(b,l["padding"+m])||0,r=g(b,l["padding"+n])||0,e=g(b,l["border"+m+"Width"])||0,f=g(b,l["border"+n+"Width"])||0,h=l["margin"+m],k=l["margin"+n],o=g(b,h)||0,p=g(b,k)||0,{base:d,padding:q+r,border:e+f,margin:o+p}}function f(a,b,c){var d=void 0,f=void 0,g=e(a,b);return g.base>0?{base:g.base-g.padding-g.border,outer:g.base,outerfull:g.base+g.margin}[c]:(d=i(a),f=d[b],(0>f||null===f)&&(f=a.style[b]||0),f=parseFloat(f)||0,{base:f-g.padding-g.border,outer:f,outerfull:f+g.padding+g.border+g.margin}[c])}var g,h,i,j;return h=angular.element.prototype.css,b.prototype.css=function(a,b){var c=this,d=c[0];return d&&3!==d.nodeType&&8!==d.nodeType&&d.style?h.call(c,a,b):void 0},j=function(a){return a&&a.document&&a.location&&a.alert&&a.setInterval},c.getComputedStyle?(i=function(a){return c.getComputedStyle(a,null)},g=function(a,b){return parseFloat(b)}):(i=function(a){return a.currentStyle},g=function(a,b){var c=void 0,d=void 0,e=void 0,f=void 0,g=void 0,h=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,i=new RegExp("^("+h+")(?!px)[a-z%]+$","i");return i.test(b)?(g=a.style,c=g.left,e=a.runtimeStyle,f=e&&e.left,e&&(e.left=g.left),g.left=b,d=g.pixelLeft,g.left=c,f&&(e.left=f),d):parseFloat(b)}),angular.forEach({before:function(a){var b,c,d,e,f,g,h;if(h=this,c=h[0],f=h.parent(),b=f.contents(),b[0]===c)return f.prepend(a);for(d=e=1,g=b.length-1;g>=1?g>=e:e>=g;d=g>=1?++e:--e)if(b[d]===c)return void angular.element(b[d-1]).after(a);throw new Error("invalid DOM structure "+c.outerHTML)},height:function(a){var b;return b=this,angular.isDefined(a)?(angular.isNumber(a)&&(a+="px"),h.call(b,"height",a)):f(this[0],"height","base")},outerHeight:function(a){return f(this[0],"height",a?"outerfull":"outer")},offset:function(a){var b=void 0,c=void 0,d=this,e={top:0,left:0},f=d[0],g=f&&f.ownerDocument;if(arguments.length){if(void 0===a)return d;throw new Error("offset setter method is not implemented")}return g?(b=g.documentElement,null!=f.getBoundingClientRect&&(e=f.getBoundingClientRect()),c=g.defaultView||g.parentWindow,{top:e.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:e.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):void 0},scrollTop:function(a){return d(this,"top",a)},scrollLeft:function(a){return d(this,"left",a)}},function(a,c){return b.prototype[c]?void 0:b.prototype[c]=a})}}}]).run(["$log","$window","jqLiteExtras",function(a,b,c){return b.jQuery?void 0:c.registerFor(angular.element)}])}(); \ No newline at end of file diff --git a/dist/ui-scroll.js b/dist/ui-scroll.js index 62170195..4cf38e5f 100644 --- a/dist/ui-scroll.js +++ b/dist/ui-scroll.js @@ -1,12 +1,12 @@ -/*! - * angular-ui-scroll - * https://github.com/angular-ui/ui-scroll.git - * Version: 1.3.3 -- 2016-03-04T12:15:03.597Z - * License: MIT - */ - - - (function () { +/*! + * angular-ui-scroll + * https://github.com/angular-ui/ui-scroll.git + * Version: 1.3.3 -- 2016-03-08T19:39:30.954Z + * License: MIT + */ + + + (function () { 'use strict'; var _typeof = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === 'function' && obj.constructor === Symbol ? 'symbol' : typeof obj; }; @@ -30,7 +30,7 @@ angular.module('ui.scroll', []).directive('uiScrollViewport', function () { return this; }] }; -}).directive('uiScroll', ['$log', '$injector', '$rootScope', '$timeout', '$q', '$parse', function (console, $injector, $rootScope, $timeout, $q, $parse) { +}).directive('uiScroll', ['$log', '$injector', '$rootScope', '$timeout', '$q', '$parse', '$interval', function (console, $injector, $rootScope, $timeout, $q, $parse, $interval) { var $animate = $injector.has && $injector.has('$animate') ? $injector.get('$animate') : null; var isAngularVersionLessThen1_3 = angular.version.major === 1 && angular.version.minor < 3; //const log = console.debug || console.log; @@ -115,7 +115,7 @@ angular.module('ui.scroll', []).directive('uiScrollViewport', function () { buffer.first = origin; buffer.next = origin; buffer.minIndex = Number.MAX_VALUE; - return buffer.maxIndex = Number.MIN_VALUE; + buffer.maxIndex = Number.MIN_VALUE; } angular.extend(buffer, { @@ -215,28 +215,9 @@ angular.module('ui.scroll', []).directive('uiScrollViewport', function () { return buffer; } - function Padding(template) { - var result = undefined; - var tagName = template.localName; - - switch (tagName) { - case 'dl': - throw new Error('ui-scroll directive does not support <' + tagName + '> as a repeating tag: ' + template.outerHTML); - case 'tr': - var table = angular.element('
'); - result = table.find('tr'); - break; - case 'li': - result = angular.element('
  • '); - break; - default: - result = angular.element('
    '); - } - - return result; - } - function Viewport(buffer, element, controllers, attrs) { + var PADDING_MIN = 0.3; + var PADDING_DEFAULT = 0.5; var topPadding = null; var bottomPadding = null; var averageItemHeight = 0; @@ -254,7 +235,7 @@ angular.module('ui.scroll', []).directive('uiScrollViewport', function () { }; function bufferPadding() { - return viewport.outerHeight() * Math.max(0.1, +attrs.padding || 0.1); // some extra space to initiate preload + return viewport.outerHeight() * Math.max(PADDING_MIN, +attrs.padding || PADDING_DEFAULT); // some extra space to initiate preload } angular.extend(viewport, { @@ -263,6 +244,27 @@ angular.module('ui.scroll', []).directive('uiScrollViewport', function () { bottomPadding = new Padding(template); element.before(topPadding); element.after(bottomPadding); + + function Padding(template) { + var result = undefined; + var tagName = template.localName; + + switch (tagName) { + case 'dl': + throw new Error('ui-scroll directive does not support <' + tagName + '> as a repeating tag: ' + template.outerHTML); + case 'tr': + var table = angular.element('
    '); + result = table.find('tr'); + break; + case 'li': + result = angular.element('
  • '); + break; + default: + result = angular.element('
    '); + } + + return result; + } }, bottomDataPos: function bottomDataPos() { var scrollHeight = viewport[0].scrollHeight; @@ -377,7 +379,13 @@ angular.module('ui.scroll', []).directive('uiScrollViewport', function () { var setTopVisible = $attr.topVisible ? $parse($attr.topVisible).assign : angular.noop; var setTopVisibleElement = $attr.topVisibleElement ? $parse($attr.topVisibleElement).assign : angular.noop; var setTopVisibleScope = $attr.topVisibleScope ? $parse($attr.topVisibleScope).assign : angular.noop; - var setIsLoading = $attr.isLoading ? $parse($attr.isLoading).assign : angular.noop; + var setIsLoading = $attr.isLoading ? setLoadingWithApply : angular.noop; + + function setLoadingWithApply(viewportScope, value) { + return $timeout(function () { + $parse($attr.isLoading).assign(viewportScope, value); + }); + } this.isLoading = false; @@ -572,31 +580,56 @@ angular.module('ui.scroll', []).directive('uiScrollViewport', function () { viewport.createPaddingElements(template[0]); // Destroy template's scope to remove any watchers on it. scope.$destroy(); - // also remove the template when the directive scope is destroyed - $scope.$on('$destroy', function () { - return template.remove(); - }); + // We don't need template anymore. + template.remove(); }); adapter.reload = reload; + var scrolling = false; + var scrollInterval = undefined; + // events and bindings - viewport.bind('resize', resizeAndScrollHandler); - viewport.bind('scroll', resizeAndScrollHandler); + viewport.bind('resize', resizeHandler); + viewport.bind('scroll', scrollHandler); viewport.bind('mousewheel', wheelHandler); - $scope.$watch(datasource.revision, function () { - return reload(); - }); - $scope.$on('$destroy', function () { // clear the buffer. It is necessary to remove the elements and $destroy the scopes buffer.clear(); - viewport.unbind('resize', resizeAndScrollHandler); - viewport.unbind('scroll', resizeAndScrollHandler); + viewport.unbind('resize', resizeHandler); + viewport.unbind('scroll', resizeHandler); viewport.unbind('mousewheel', wheelHandler); + if (scrollInterval) { + scrollInterval.cancel(); + } }); + // update events (deprecated since v1.1.0, unsupported since 1.2.0) + (function () { + var eventListener = datasource.scope ? datasource.scope.$new() : $scope.$new(); + + eventListener.$on('insert.item', function () { + return unsupportedMethod('insert'); + }); + + eventListener.$on('update.items', function () { + return unsupportedMethod('update'); + }); + + eventListener.$on('delete.items', function () { + return unsupportedMethod('delete'); + }); + + function unsupportedMethod(token) { + throw new Error(token + ' event is no longer supported - use applyUpdates instead'); + } + })(); + + reload(); + + /* Functions definitions */ + function dismissPendingRequests() { ridActual++; pending = []; @@ -726,18 +759,21 @@ angular.module('ui.scroll', []).directive('uiScrollViewport', function () { function adjustBuffer(rid) { // We need the item bindings to be processed before we can do adjustment return $timeout(function () { - processBufferedItems(rid); + return adjustBufferWithoutTimeout(rid); + }); + } - if (viewport.shouldLoadBottom()) { - enqueueFetch(rid, true); - } else if (viewport.shouldLoadTop()) { - enqueueFetch(rid, false); - } + function adjustBufferWithoutTimeout(rid) { + processBufferedItems(rid); + if (viewport.shouldLoadBottom()) { + enqueueFetch(rid, true); + } else if (viewport.shouldLoadTop()) { + enqueueFetch(rid, false); + } - if (!pending.length) { - return adapter.calculateProperties(); - } - }); + if (!pending.length) { + return adapter.calculateProperties(); + } } function adjustBufferAfterFetch(rid) { @@ -821,9 +857,20 @@ angular.module('ui.scroll', []).directive('uiScrollViewport', function () { }); } - function resizeAndScrollHandler() { + function scrollHandler() { + scrolling = true; + } + + scrollInterval = $interval(function () { + if (scrolling && !adapter.isLoading) { + scrolling = false; + adjustBufferWithoutTimeout(); + } + }, 100, 0, false); + + function resizeHandler() { if (!$rootScope.$$phase && !adapter.isLoading) { - adjustBuffer(); + adjustBufferWithoutTimeout(); } } @@ -835,27 +882,6 @@ angular.module('ui.scroll', []).directive('uiScrollViewport', function () { event.preventDefault(); } } - - // update events (deprecated since v1.1.0, unsupported since 1.2.0) - (function () { - var eventListener = datasource.scope ? datasource.scope.$new() : $scope.$new(); - - eventListener.$on('insert.item', function () { - return unsupportedMethod('insert'); - }); - - eventListener.$on('update.items', function () { - return unsupportedMethod('update'); - }); - - eventListener.$on('delete.items', function () { - return unsupportedMethod('delete'); - }); - - function unsupportedMethod(token) { - throw new Error(token + ' event is no longer supported - use applyUpdates instead'); - } - })(); }; } }]); diff --git a/dist/ui-scroll.min.js b/dist/ui-scroll.min.js index 07bc905f..1c78af42 100644 --- a/dist/ui-scroll.min.js +++ b/dist/ui-scroll.min.js @@ -1,7 +1,7 @@ /*! * angular-ui-scroll * https://github.com/angular-ui/ui-scroll.git - * Version: 1.3.3 -- 2016-03-04T12:15:03.597Z + * Version: 1.3.3 -- 2016-03-08T19:39:30.954Z * License: MIT */ -!function(){"use strict";var a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol?"symbol":typeof a};angular.module("ui.scroll",[]).directive("uiScrollViewport",function(){return{controller:["$scope","$element",function(a,b){return this.viewport=b,this}]}}).directive("uiScroll",["$log","$injector","$rootScope","$timeout","$q","$parse",function(b,c,d,e,f,g){function h(a,b){return b.after(a),[]}function i(a){return a.element.remove(),a.scope.$destroy(),[]}function j(b,c){if(!q)return h(b,c);if(r){var d=function(){var a=f.defer();return q.enter(b,null,c,function(){return a.resolve()}),{v:[a.promise]}}();if("object"===("undefined"==typeof d?"undefined":a(d)))return d.v}return[q.enter(b,null,c)]}function k(b){if(!q)return i(b);if(r){var c=function(){var a=f.defer();return q.leave(b.element,function(){return b.scope.$destroy(),a.resolve()}),{v:[a.promise]}}();if("object"===("undefined"==typeof c?"undefined":a(c)))return c.v}return[q.leave(b.element).then(function(){return b.scope.$destroy()})]}function l(a,b,c,d){function e(a){return f.eof=!1,f.bof=!1,f.first=a,f.next=a,f.minIndex=Number.MAX_VALUE,f.maxIndex=Number.MIN_VALUE}var f=Object.create(Array.prototype);return angular.extend(f,{size:d,append:function(a){a.forEach(function(a){++f.next,f.insert("append",a)})},prepend:function(a){a.reverse().forEach(function(a){--f.first,f.insert("prepend",a)})},insert:function(d,e){var g=b.$new(),h={item:e,scope:g};if(g[a]=e,c(g,function(a){return h.element=a}),d%1===0)h.op="insert",f.splice(d,0,h);else switch(h.op=d,d){case"append":f.push(h);break;case"prepend":f.unshift(h)}},remove:function(a,b){if(angular.isNumber(a)){for(var c=a;b>c;c++)i(f[c]);return f.splice(a,b-a)}return f.splice(f.indexOf(a),1),k(a)},setUpper:function(){f.maxIndex=f.eof?f.next-1:Math.max(f.next-1,f.maxIndex)},setLower:function(){f.minIndex=f.bof?f.minIndex=f.first:Math.min(f.first,f.minIndex)},syncDatasource:function(a){var b=f.minIndex-Math.min(f.minIndex,a.minIndex||Number.MAX_VALUE);return a.minIndex=f.minIndex-=b,a.maxIndex=f.maxIndex=Math.max(f.maxIndex,a.maxIndex||Number.MIN_VALUE),b},clear:function(){f.remove(0,f.length),e(arguments.length?arguments[0]:1)}}),e(1),f}function m(a){var b=void 0,c=a.localName;switch(c){case"dl":throw new Error("ui-scroll directive does not support <"+c+"> as a repeating tag: "+a.outerHTML);case"tr":var d=angular.element("
    ");b=d.find("tr");break;case"li":b=angular.element("
  • ");break;default:b=angular.element("
    ")}return b}function n(a,b,c,d){function e(){return k.outerHeight()*Math.max(.1,+d.padding||.1)}var f=null,g=null,i=0,k=c[0]&&c[0].viewport?c[0].viewport:angular.element(window);k.css({"overflow-y":"auto",display:"block"});var l=k.offset()?function(){return k.offset()}:function(){return{top:0}};return angular.extend(k,{createPaddingElements:function(a){f=new m(a),g=new m(a),b.before(f),b.after(g)},bottomDataPos:function(){var a=k[0].scrollHeight;return a=null!==a?a:k[0].document.documentElement.scrollHeight,a-g.height()},topDataPos:function(){return f.height()},bottomVisiblePos:function(){return k.scrollTop()+k.outerHeight()},topVisiblePos:function(){return k.scrollTop()},insertElement:function(a,b){return h(a,b||f)},insertElementAnimated:function(a,b){return j(a,b||f)},shouldLoadBottom:function(){return!a.eof&&k.bottomDataPos()=0&&!(a[c].element.offset().top-l().top<=k.outerHeight()+e());c--)b++;b>0&&(a.eof=!1,a.remove(a.length-b,a.length),a.next-=b,k.adjustPadding())},shouldLoadTop:function(){return!a.bof&&k.topDataPos()>k.topVisiblePos()-e()},clipTop:function(){for(var b=0,c=0,d=0;d=-1*e());d++)c+=a[d].element.outerHeight(!0),b++;b>0&&(f.height(f.height()+c),a.bof=!1,a.remove(0,b),a.first+=b)},adjustPadding:function(){if(a.length){var b=a[0].element,c=a[a.length-1].element;return i=(c.offset().top+c.outerHeight(!0)-b.offset().top)/a.length,f.height((a.first-a.minIndex)*i),g.height((a.maxIndex-a.next+1)*i)}},syncDatasource:function(b){if(a.length){var c=a.syncDatasource(b)*i;f.height(f.height()+c),k.scrollTop(k.scrollTop()+c),k.adjustPadding()}},adjustScrollTop:function(a){var b=f.height()-a;b>=0?f.height(b):(f.height(0),k.scrollTop(k.scrollTop()-b))}}),k}function o(a,b,c,e){function f(a,b){if(angular.isArray(b)){var d=void 0,e=c.indexOf(a)+1;b.reverse().forEach(function(b){b===a.item?(d=!0,e--):c.insert(e,b)}),d||(a.op="remove")}}var h=b.scope()||d,i=a.topVisible?g(a.topVisible).assign:angular.noop,j=a.topVisibleElement?g(a.topVisibleElement).assign:angular.noop,k=a.topVisibleScope?g(a.topVisibleScope).assign:angular.noop,l=a.isLoading?g(a.isLoading).assign:angular.noop;this.isLoading=!1,this.applyUpdates=function(a,b){if(angular.isFunction(a))c.slice(0).forEach(function(b){f(b,a(b.item,b.scope,b.element))});else{if(a%1!==0)throw new Error("applyUpdates - "+a+" is not a valid index");var d=a-c.first;d>=0&&d0?G[a-1].element:void 0}var c=!1,d=[],e=[],g=[];if(G.forEach(function(a,f){switch(a.op){case"prepend":e.unshift(a);break;case"append":c=w(a,b(f))||c,a.op="none";break;case"insert":d=d.concat(H.insertElementAnimated(a.element,b(f))),a.op="none";break;case"remove":g.push(a)}}),g.forEach(function(a){return d=d.concat(G.remove(a))}),e.length){var h=0;e.forEach(function(a){c=w(a)||c,a.op="none",h+=a.element.outerHeight(!0)}),H.adjustScrollTop(h)}return G.forEach(function(a,b){return a.scope.$index=G.first+b}),d.length?f.all(d).then(function(){return H.adjustPadding(),y(a)}):(H.adjustPadding(),F.length||H.syncDatasource(D)),c}function y(a){return e(function(){return x(a),H.shouldLoadBottom()?t(a,!0):H.shouldLoadTop()&&t(a,!1),F.length?void 0:I.calculateProperties()})}function z(a){return e(function(){var b=x(a);return H.shouldLoadBottom()&&b?t(a,!0):H.shouldLoadTop()&&(b||F[0])&&t(a,!1),F.shift(),F.length?A(a):(I.loading(!1),I.calculateProperties())})}function A(b){return F[0]?G.length&&!H.shouldLoadBottom()?z(b):J(function(c){return b&&b!==E||a.$$destroyed?void 0:(c.length0&&(H.clipTop(),G.append(c)),G.setUpper(),z(b))}):G.length&&!H.shouldLoadTop()?z(b):K(function(c){return b&&b!==E||a.$$destroyed?void 0:(c.length0&&(G.length&&H.clipBottom(),G.prepend(c)),G.setLower(),z(b))})}function B(){d.$$phase||I.isLoading||y()}function C(a){var b=H[0].scrollTop,c=H[0].scrollHeight-H[0].clientHeight;(0===b&&!G.bof||b===c&&!G.eof)&&a.preventDefault()}q=q||h;var D=function(){function b(){return angular.isObject(d)&&angular.isFunction(d.get)}var d=g(k)(a);if(!b()&&(d=c.get(k),!b()))throw new Error(k+" is not a valid datasource");return d}(),E=0,F=[],G=new l(j,a,q,m),H=new n(G,b,p,i),I=new o(i,H,G,function(){return r(),y(E)}),J=function(){return 2!==D.get.length?function(a){return D.get(G.next,m,a)}:function(a){return D.get({index:G.next,append:G.length?G[G.length-1].item:void 0,count:m},a)}}(),K=function(){return 2!==D.get.length?function(a){return D.get(G.first-m,m,a)}:function(a){return D.get({index:G.first-m,prepend:G.length?G[0].item:void 0,count:m},a)}}();if(i.adapter){var L=g(i.adapter)(a);angular.isObject(L)||(g(i.adapter).assign(a,{}),L=g(i.adapter)(a)),I=angular.extend(L,I)}q(a.$new(),function(b,c){H.createPaddingElements(b[0]),c.$destroy(),a.$on("$destroy",function(){return b.remove()})}),I.reload=s,H.bind("resize",B),H.bind("scroll",B),H.bind("mousewheel",C),a.$watch(D.revision,function(){return s()}),a.$on("$destroy",function(){G.clear(),H.unbind("resize",B),H.unbind("scroll",B),H.unbind("mousewheel",C)}),function(){function b(a){throw new Error(a+" event is no longer supported - use applyUpdates instead")}var c=D.scope?D.scope.$new():a.$new();c.$on("insert.item",function(){return b("insert")}),c.$on("update.items",function(){return b("update")}),c.$on("delete.items",function(){return b("delete")})}()}}var q=c.has&&c.has("$animate")?c.get("$animate"):null,r=1===angular.version.major&&angular.version.minor<3;return{require:["?^uiScrollViewport"],transclude:"element",priority:1e3,terminal:!0,compile:p}}])}(); \ No newline at end of file +!function(){"use strict";var a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol?"symbol":typeof a};angular.module("ui.scroll",[]).directive("uiScrollViewport",function(){return{controller:["$scope","$element",function(a,b){return this.viewport=b,this}]}}).directive("uiScroll",["$log","$injector","$rootScope","$timeout","$q","$parse","$interval",function(b,c,d,e,f,g,h){function i(a,b){return b.after(a),[]}function j(a){return a.element.remove(),a.scope.$destroy(),[]}function k(b,c){if(!q)return i(b,c);if(r){var d=function(){var a=f.defer();return q.enter(b,null,c,function(){return a.resolve()}),{v:[a.promise]}}();if("object"===("undefined"==typeof d?"undefined":a(d)))return d.v}return[q.enter(b,null,c)]}function l(b){if(!q)return j(b);if(r){var c=function(){var a=f.defer();return q.leave(b.element,function(){return b.scope.$destroy(),a.resolve()}),{v:[a.promise]}}();if("object"===("undefined"==typeof c?"undefined":a(c)))return c.v}return[q.leave(b.element).then(function(){return b.scope.$destroy()})]}function m(a,b,c,d){function e(a){f.eof=!1,f.bof=!1,f.first=a,f.next=a,f.minIndex=Number.MAX_VALUE,f.maxIndex=Number.MIN_VALUE}var f=Object.create(Array.prototype);return angular.extend(f,{size:d,append:function(a){a.forEach(function(a){++f.next,f.insert("append",a)})},prepend:function(a){a.reverse().forEach(function(a){--f.first,f.insert("prepend",a)})},insert:function(d,e){var g=b.$new(),h={item:e,scope:g};if(g[a]=e,c(g,function(a){return h.element=a}),d%1===0)h.op="insert",f.splice(d,0,h);else switch(h.op=d,d){case"append":f.push(h);break;case"prepend":f.unshift(h)}},remove:function(a,b){if(angular.isNumber(a)){for(var c=a;b>c;c++)j(f[c]);return f.splice(a,b-a)}return f.splice(f.indexOf(a),1),l(a)},setUpper:function(){f.maxIndex=f.eof?f.next-1:Math.max(f.next-1,f.maxIndex)},setLower:function(){f.minIndex=f.bof?f.minIndex=f.first:Math.min(f.first,f.minIndex)},syncDatasource:function(a){var b=f.minIndex-Math.min(f.minIndex,a.minIndex||Number.MAX_VALUE);return a.minIndex=f.minIndex-=b,a.maxIndex=f.maxIndex=Math.max(f.maxIndex,a.maxIndex||Number.MIN_VALUE),b},clear:function(){f.remove(0,f.length),e(arguments.length?arguments[0]:1)}}),e(1),f}function n(a,b,c,d){function e(){return m.outerHeight()*Math.max(f,+d.padding||g)}var f=.3,g=.5,h=null,j=null,l=0,m=c[0]&&c[0].viewport?c[0].viewport:angular.element(window);m.css({"overflow-y":"auto",display:"block"});var n=m.offset()?function(){return m.offset()}:function(){return{top:0}};return angular.extend(m,{createPaddingElements:function(a){function c(a){var b=void 0,c=a.localName;switch(c){case"dl":throw new Error("ui-scroll directive does not support <"+c+"> as a repeating tag: "+a.outerHTML);case"tr":var d=angular.element("
    ");b=d.find("tr");break;case"li":b=angular.element("
  • ");break;default:b=angular.element("
    ")}return b}h=new c(a),j=new c(a),b.before(h),b.after(j)},bottomDataPos:function(){var a=m[0].scrollHeight;return a=null!==a?a:m[0].document.documentElement.scrollHeight,a-j.height()},topDataPos:function(){return h.height()},bottomVisiblePos:function(){return m.scrollTop()+m.outerHeight()},topVisiblePos:function(){return m.scrollTop()},insertElement:function(a,b){return i(a,b||h)},insertElementAnimated:function(a,b){return k(a,b||h)},shouldLoadBottom:function(){return!a.eof&&m.bottomDataPos()=0&&!(a[c].element.offset().top-n().top<=m.outerHeight()+e());c--)b++;b>0&&(a.eof=!1,a.remove(a.length-b,a.length),a.next-=b,m.adjustPadding())},shouldLoadTop:function(){return!a.bof&&m.topDataPos()>m.topVisiblePos()-e()},clipTop:function(){for(var b=0,c=0,d=0;d=-1*e());d++)c+=a[d].element.outerHeight(!0),b++;b>0&&(h.height(h.height()+c),a.bof=!1,a.remove(0,b),a.first+=b)},adjustPadding:function(){if(a.length){var b=a[0].element,c=a[a.length-1].element;return l=(c.offset().top+c.outerHeight(!0)-b.offset().top)/a.length,h.height((a.first-a.minIndex)*l),j.height((a.maxIndex-a.next+1)*l)}},syncDatasource:function(b){if(a.length){var c=a.syncDatasource(b)*l;h.height(h.height()+c),m.scrollTop(m.scrollTop()+c),m.adjustPadding()}},adjustScrollTop:function(a){var b=h.height()-a;b>=0?h.height(b):(h.height(0),m.scrollTop(m.scrollTop()-b))}}),m}function o(a,b,c,f){function h(b,c){return e(function(){g(a.isLoading).assign(b,c)})}function i(a,b){if(angular.isArray(b)){var d=void 0,e=c.indexOf(a)+1;b.reverse().forEach(function(b){b===a.item?(d=!0,e--):c.insert(e,b)}),d||(a.op="remove")}}var j=b.scope()||d,k=a.topVisible?g(a.topVisible).assign:angular.noop,l=a.topVisibleElement?g(a.topVisibleElement).assign:angular.noop,m=a.topVisibleScope?g(a.topVisibleScope).assign:angular.noop,n=a.isLoading?h:angular.noop;this.isLoading=!1,this.applyUpdates=function(a,b){if(angular.isFunction(a))c.slice(0).forEach(function(b){i(b,a(b.item,b.scope,b.element))});else{if(a%1!==0)throw new Error("applyUpdates - "+a+" is not a valid index");var d=a-c.first;d>=0&&d0?J[a-1].element:void 0}var c=!1,d=[],e=[],g=[];if(J.forEach(function(a,f){switch(a.op){case"prepend":e.unshift(a);break;case"append":c=x(a,b(f))||c,a.op="none";break;case"insert":d=d.concat(K.insertElementAnimated(a.element,b(f))),a.op="none";break;case"remove":g.push(a)}}),g.forEach(function(a){return d=d.concat(J.remove(a))}),e.length){var h=0;e.forEach(function(a){c=x(a)||c,a.op="none",h+=a.element.outerHeight(!0)}),K.adjustScrollTop(h)}return J.forEach(function(a,b){return a.scope.$index=J.first+b}),d.length?f.all(d).then(function(){return K.adjustPadding(),z(a)}):(K.adjustPadding(),I.length||K.syncDatasource(G)),c}function z(a){return e(function(){return A(a)})}function A(a){return y(a),K.shouldLoadBottom()?u(a,!0):K.shouldLoadTop()&&u(a,!1),I.length?void 0:L.calculateProperties()}function B(a){return e(function(){var b=y(a);return K.shouldLoadBottom()&&b?u(a,!0):K.shouldLoadTop()&&(b||I[0])&&u(a,!1),I.shift(),I.length?C(a):(L.loading(!1),L.calculateProperties())})}function C(b){return I[0]?J.length&&!K.shouldLoadBottom()?B(b):M(function(c){return b&&b!==H||a.$$destroyed?void 0:(c.length0&&(K.clipTop(),J.append(c)),J.setUpper(),B(b))}):J.length&&!K.shouldLoadTop()?B(b):N(function(c){return b&&b!==H||a.$$destroyed?void 0:(c.length0&&(J.length&&K.clipBottom(),J.prepend(c)),J.setLower(),B(b))})}function D(){P=!0}function E(){d.$$phase||L.isLoading||A()}function F(a){var b=K[0].scrollTop,c=K[0].scrollHeight-K[0].clientHeight;(0===b&&!J.bof||b===c&&!J.eof)&&a.preventDefault()}r=r||i;var G=function(){function b(){return angular.isObject(d)&&angular.isFunction(d.get)}var d=g(l)(a);if(!b()&&(d=c.get(l),!b()))throw new Error(l+" is not a valid datasource");return d}(),H=0,I=[],J=new m(k,a,r,p),K=new n(J,b,q,j),L=new o(j,K,J,function(){return s(),z(H)}),M=function(){return 2!==G.get.length?function(a){return G.get(J.next,p,a)}:function(a){return G.get({index:J.next,append:J.length?J[J.length-1].item:void 0,count:p},a)}}(),N=function(){return 2!==G.get.length?function(a){return G.get(J.first-p,p,a)}:function(a){return G.get({index:J.first-p,prepend:J.length?J[0].item:void 0,count:p},a)}}();if(j.adapter){var O=g(j.adapter)(a);angular.isObject(O)||(g(j.adapter).assign(a,{}),O=g(j.adapter)(a)),L=angular.extend(O,L)}r(a.$new(),function(a,b){K.createPaddingElements(a[0]),b.$destroy(),a.remove()}),L.reload=t;var P=!1,Q=void 0;K.bind("resize",E),K.bind("scroll",D),K.bind("mousewheel",F),a.$on("$destroy",function(){J.clear(),K.unbind("resize",E),K.unbind("scroll",E),K.unbind("mousewheel",F),Q&&Q.cancel()}),function(){function b(a){throw new Error(a+" event is no longer supported - use applyUpdates instead")}var c=G.scope?G.scope.$new():a.$new();c.$on("insert.item",function(){return b("insert")}),c.$on("update.items",function(){return b("update")}),c.$on("delete.items",function(){return b("delete")})}(),t(),Q=h(function(){P&&!L.isLoading&&(P=!1,A())},100,0,!1)}}var q=c.has&&c.has("$animate")?c.get("$animate"):null,r=1===angular.version.major&&angular.version.minor<3;return{require:["?^uiScrollViewport"],transclude:"element",priority:1e3,terminal:!0,compile:p}}])}(); \ No newline at end of file diff --git a/src/ui-scroll.js b/src/ui-scroll.js index 7bb00e28..504ce378 100644 --- a/src/ui-scroll.js +++ b/src/ui-scroll.js @@ -30,7 +30,8 @@ angular.module('ui.scroll', []) '$timeout', '$q', '$parse', - function (console, $injector, $rootScope, $timeout, $q, $parse) { + '$interval', + function (console, $injector, $rootScope, $timeout, $q, $parse, $interval) { const $animate = ($injector.has && $injector.has('$animate')) ? $injector.get('$animate') : null; const isAngularVersionLessThen1_3 = angular.version.major === 1 && angular.version.minor < 3; //const log = console.debug || console.log; @@ -370,7 +371,13 @@ angular.module('ui.scroll', []) const setTopVisible = $attr.topVisible ? $parse($attr.topVisible).assign : angular.noop; const setTopVisibleElement = $attr.topVisibleElement ? $parse($attr.topVisibleElement).assign : angular.noop; const setTopVisibleScope = $attr.topVisibleScope ? $parse($attr.topVisibleScope).assign : angular.noop; - const setIsLoading = $attr.isLoading ? $parse($attr.isLoading).assign : angular.noop; + const setIsLoading = $attr.isLoading ? setLoadingWithApply : angular.noop; + + function setLoadingWithApply(viewportScope, value) { + return $timeout(() => { + $parse($attr.isLoading).assign(viewportScope, value); + }); + } this.isLoading = false; @@ -561,17 +568,23 @@ angular.module('ui.scroll', []) adapter.reload = reload; + let scrolling = false; + let scrollInterval; + // events and bindings - viewport.bind('resize', resizeAndScrollHandler); - viewport.bind('scroll', resizeAndScrollHandler); + viewport.bind('resize', resizeHandler); + viewport.bind('scroll', scrollHandler); viewport.bind('mousewheel', wheelHandler); $scope.$on('$destroy', () => { // clear the buffer. It is necessary to remove the elements and $destroy the scopes buffer.clear(); - viewport.unbind('resize', resizeAndScrollHandler); - viewport.unbind('scroll', resizeAndScrollHandler); + viewport.unbind('resize', resizeHandler); + viewport.unbind('scroll', resizeHandler); viewport.unbind('mousewheel', wheelHandler); + if(scrollInterval) { + scrollInterval.cancel(); + } }); // update events (deprecated since v1.1.0, unsupported since 1.2.0) @@ -716,18 +729,23 @@ angular.module('ui.scroll', []) function adjustBuffer(rid) { // We need the item bindings to be processed before we can do adjustment return $timeout(() => { - processBufferedItems(rid); + return adjustBufferWithoutTimeout(rid); + }); - if (viewport.shouldLoadBottom()) { - enqueueFetch(rid, true); - } else if (viewport.shouldLoadTop()) { - enqueueFetch(rid, false); - } - if (!pending.length) { - return adapter.calculateProperties(); - } - }); + } + + function adjustBufferWithoutTimeout(rid) { + processBufferedItems(rid); + if (viewport.shouldLoadBottom()) { + enqueueFetch(rid, true); + } else if (viewport.shouldLoadTop()) { + enqueueFetch(rid, false); + } + + if (!pending.length) { + return adapter.calculateProperties(); + } } function adjustBufferAfterFetch(rid) { @@ -810,9 +828,20 @@ angular.module('ui.scroll', []) }); } - function resizeAndScrollHandler() { + function scrollHandler() { + scrolling = true; + } + + scrollInterval = $interval(function() { + if(scrolling && !adapter.isLoading) { + scrolling = false; + adjustBufferWithoutTimeout(); + } + }, 100, 0, false); + + function resizeHandler() { if (!$rootScope.$$phase && !adapter.isLoading) { - adjustBuffer(); + adjustBufferWithoutTimeout(); } } diff --git a/test/BasicTestsSpec.js b/test/BasicTestsSpec.js index f53d6079..f8b3072b 100644 --- a/test/BasicTestsSpec.js +++ b/test/BasicTestsSpec.js @@ -155,10 +155,12 @@ describe('uiScroll', function () { var itemsWithPaddings = itemsLoaded + 2; runTest(scrollSettings, function (viewport) { - viewport.scrollTop(100); - viewport.trigger('scroll'); - inject(function ($timeout) { - $timeout.flush(); + inject(function ($interval) { + viewport.scrollTop(100); + viewport.trigger('scroll'); + + $interval.flush(120); + expect(viewport.children().length).toBe(itemsWithPaddings); expect(viewport.scrollTop()).toBe(100); expect(viewport.children().css('height')).toBe('0px'); @@ -180,46 +182,53 @@ describe('uiScroll', function () { spy = spyOn(myMultipageDatasource, 'get').and.callThrough(); }); runTest(scrollSettings, - function (viewport, scope, $timeout) { - viewport.scrollTop(100); - viewport.trigger('scroll'); - $timeout.flush(); + function (viewport) { + inject(function ($interval) { + viewport.scrollTop(100); + viewport.trigger('scroll'); - expect(spy.calls.all().length).toBe(5); + $interval.flush(120); - expect(spy.calls.all()[0].args[0]).toBe(1); - expect(spy.calls.all()[1].args[0]).toBe(4); - expect(spy.calls.all()[2].args[0]).toBe(7); - expect(spy.calls.all()[3].args[0]).toBe(-2); - expect(spy.calls.all()[4].args[0]).toBe(10); + expect(spy.calls.all().length).toBe(5); + + expect(spy.calls.all()[0].args[0]).toBe(1); + expect(spy.calls.all()[1].args[0]).toBe(4); + expect(spy.calls.all()[2].args[0]).toBe(7); + expect(spy.calls.all()[3].args[0]).toBe(-2); + expect(spy.calls.all()[4].args[0]).toBe(10); + }); } ); }); it('should clip 4 divs from the top and add 3 more divs to the bottom (11 divs total) (+ 2 padding divs)', function () { runTest(scrollSettings, - function (viewport, scope, $timeout) { - var itemsLoaded = 11; - var itemsWithPaddings = itemsLoaded + 2; - var clippedDivs = 4; - viewport.scrollTop(100); - viewport.trigger('scroll'); - $timeout.flush(); - - viewport.scrollTop(400); - viewport.trigger('scroll'); - $timeout.flush(); - - expect(viewport.children().length).toBe(itemsWithPaddings); - expect(viewport.scrollTop()).toBe(280); - expect(viewport.children().css('height')).toBe('160px'); - expect(angular.element(viewport.children()[itemsWithPaddings-1]).css('height')).toBe('0px'); - - for (var i = 1; i <= itemsLoaded; i++) { - var row = viewport.children()[i]; - expect(row.tagName.toLowerCase()).toBe('div'); - expect(row.innerHTML).toBe((i + clippedDivs) + ': item' + (i + clippedDivs)); - } + function (viewport) { + inject(function ($interval) { + var itemsLoaded = 11; + var itemsWithPaddings = itemsLoaded + 2; + var clippedDivs = 4; + + viewport.scrollTop(100); + viewport.trigger('scroll'); + + $interval.flush(120); + + viewport.scrollTop(400); + viewport.trigger('scroll'); + $interval.flush(120); + + expect(viewport.children().length).toBe(itemsWithPaddings); + expect(viewport.scrollTop()).toBe(280); + expect(viewport.children().css('height')).toBe('160px'); + expect(angular.element(viewport.children()[itemsWithPaddings - 1]).css('height')).toBe('0px'); + + for (var i = 1; i <= itemsLoaded; i++) { + var row = viewport.children()[i]; + expect(row.tagName.toLowerCase()).toBe('div'); + expect(row.innerHTML).toBe((i + clippedDivs) + ': item' + (i + clippedDivs)); + } + }); } ); }); @@ -230,57 +239,59 @@ describe('uiScroll', function () { spy = spyOn(myMultipageDatasource, 'get').and.callThrough(); }); runTest(scrollSettings, - function (viewport, scope, $timeout) { - var calls = 6; - - viewport.scrollTop(100); - viewport.trigger('scroll'); - $timeout.flush(); - - viewport.scrollTop(400); - viewport.trigger('scroll'); - $timeout.flush(); + function (viewport) { + inject(function ($interval) { + var calls = 6; - expect(spy.calls.all().length).toBe(calls); - expect(spy.calls.all()[0].args[0]).toBe(1); - expect(spy.calls.all()[1].args[0]).toBe(4); - expect(spy.calls.all()[2].args[0]).toBe(7); - expect(spy.calls.all()[3].args[0]).toBe(-2); - expect(spy.calls.all()[4].args[0]).toBe(10); - expect(spy.calls.all()[5].args[0]).toBe(13); + viewport.scrollTop(100); + viewport.trigger('scroll'); + $interval.flush(100); + + expect(spy.calls.all().length).toBe(calls); + expect(spy.calls.all()[0].args[0]).toBe(1); + expect(spy.calls.all()[1].args[0]).toBe(4); + expect(spy.calls.all()[2].args[0]).toBe(7); + expect(spy.calls.all()[3].args[0]).toBe(-2); + expect(spy.calls.all()[4].args[0]).toBe(10); + expect(spy.calls.all()[5].args[0]).toBe(13); + }); } ); }); it('should re-add 3 divs at the top and clip 2 divs from the bottom (9 divs total) (+ 2 padding divs)', function () { runTest(scrollSettings, - function (viewport, scope, $timeout) { - var flush = $timeout.flush; - var itemsLoaded = 8; - var itemsWithPaddings = itemsLoaded + 2; - - viewport.scrollTop(100); - viewport.trigger('scroll'); - flush(); - - viewport.scrollTop(400); - viewport.trigger('scroll'); - flush(); - - viewport.scrollTop(0); - viewport.trigger('scroll'); - flush(); - - expect(viewport.children().length).toBe(itemsWithPaddings); - expect(viewport.scrollTop()).toBe(0); - expect(viewport.children().css('height')).toBe('0px'); - expect(angular.element(viewport.children()[itemsWithPaddings-1]).css('height')).toBe('280px'); - - for (var i = 1; i <= itemsLoaded; i++) { - var row = viewport.children()[i]; - expect(row.tagName.toLowerCase()).toBe('div'); - expect(row.innerHTML).toBe((i) + ': item' + (i)); - } + function (viewport) { + inject(function ($interval) { + var flush = function () { + $interval.flush(100); + }; + var itemsLoaded = 8; + var itemsWithPaddings = itemsLoaded + 2; + + viewport.scrollTop(100); + viewport.trigger('scroll'); + flush(); + + viewport.scrollTop(400); + viewport.trigger('scroll'); + flush(); + + viewport.scrollTop(0); + viewport.trigger('scroll'); + flush(); + + expect(viewport.children().length).toBe(itemsWithPaddings); + expect(viewport.scrollTop()).toBe(0); + expect(viewport.children().css('height')).toBe('0px'); + expect(angular.element(viewport.children()[itemsWithPaddings - 1]).css('height')).toBe('280px'); + + for (var i = 1; i <= itemsLoaded; i++) { + var row = viewport.children()[i]; + expect(row.tagName.toLowerCase()).toBe('div'); + expect(row.innerHTML).toBe((i) + ': item' + (i)); + } + }); } ); }); @@ -292,31 +303,35 @@ describe('uiScroll', function () { }); runTest(scrollSettings, - function (viewport, scope, $timeout) { - var flush = $timeout.flush; - var totalCallsNumber = 8; - - viewport.scrollTop(100); - viewport.trigger('scroll'); - flush(); + function (viewport) { + inject(function ($interval) { + var flush = function () { + $interval.flush(100); + }; + var totalCallsNumber = 8; - viewport.scrollTop(400); - viewport.trigger('scroll'); - flush(); + viewport.scrollTop(100); + viewport.trigger('scroll'); + flush(); - viewport.scrollTop(0); - viewport.trigger('scroll'); - flush(); + viewport.scrollTop(400); + viewport.trigger('scroll'); + flush(); - expect(spy.calls.all().length).toBe(totalCallsNumber); - expect(spy.calls.all()[0].args[0]).toBe(1); - expect(spy.calls.all()[1].args[0]).toBe(4); - expect(spy.calls.all()[2].args[0]).toBe(7); - expect(spy.calls.all()[3].args[0]).toBe(-2); - expect(spy.calls.all()[4].args[0]).toBe(10); - expect(spy.calls.all()[5].args[0]).toBe(13); - expect(spy.calls.all()[6].args[0]).toBe(2); - expect(spy.calls.all()[7].args[0]).toBe(-1); + viewport.scrollTop(0); + viewport.trigger('scroll'); + flush(); + + expect(spy.calls.all().length).toBe(totalCallsNumber); + expect(spy.calls.all()[0].args[0]).toBe(1); + expect(spy.calls.all()[1].args[0]).toBe(4); + expect(spy.calls.all()[2].args[0]).toBe(7); + expect(spy.calls.all()[3].args[0]).toBe(-2); + expect(spy.calls.all()[4].args[0]).toBe(10); + expect(spy.calls.all()[5].args[0]).toBe(13); + expect(spy.calls.all()[6].args[0]).toBe(2); + expect(spy.calls.all()[7].args[0]).toBe(-1); + }); } ); }); @@ -393,23 +408,24 @@ describe('uiScroll', function () { viewportHeight: viewportHeight, itemHeight: itemHeight }, - function (viewport, scope, $timeout) { - var flush = $timeout.flush; - viewport.scrollTop(viewportHeight + itemHeight); - viewport.trigger('scroll'); - flush(); - viewport.scrollTop(viewportHeight + itemHeight * 2); - viewport.trigger('scroll'); - flush(); - expect(flush).toThrow(); - - expect(spy.calls.all().length).toBe(4); + function (viewport) { + inject(function ($interval) { + var flush = $interval.flush; + viewport.scrollTop(viewportHeight + itemHeight); + viewport.trigger('scroll'); + flush(100); + viewport.scrollTop(viewportHeight + itemHeight * 2); + viewport.trigger('scroll'); + flush(100); + //expect(flush).toThrow(); - expect(spy.calls.all()[0].args[0]).toBe(1); - expect(spy.calls.all()[1].args[0]).toBe(4); //last full - expect(spy.calls.all()[2].args[0]).toBe(-2); - expect(spy.calls.all()[3].args[0]).toBe(6); //empty + expect(spy.calls.all().length).toBe(4); + expect(spy.calls.all()[0].args[0]).toBe(1); + expect(spy.calls.all()[1].args[0]).toBe(4); //last full + expect(spy.calls.all()[2].args[0]).toBe(-2); + expect(spy.calls.all()[3].args[0]).toBe(5); //empty + }); } ); }); @@ -429,30 +445,32 @@ describe('uiScroll', function () { viewportHeight: viewportHeight, itemHeight: itemHeight }, - function (viewport, scope, $timeout) { - var flush = $timeout.flush; + function (viewport) { + inject(function ($interval) { + var flush = $interval.flush; - viewport.scrollTop(0); //first full, scroll to -2 - viewport.trigger('scroll'); - flush(); + viewport.scrollTop(0); //first full, scroll to -2 + viewport.trigger('scroll'); + flush(100); - viewport.scrollTop(0); //last full, scroll to -5, bof is reached - viewport.trigger('scroll'); - flush(); + viewport.scrollTop(0); //last full, scroll to -5, bof is reached + viewport.trigger('scroll'); + flush(100); - expect(flush).toThrow(); - viewport.scrollTop(0); //empty, no scroll occurred (-8) - viewport.trigger('scroll'); - flush(); + //expect(flush).toThrow(); + viewport.scrollTop(0); //empty, no scroll occurred (-8) + viewport.trigger('scroll'); + flush(100); - expect(flush).toThrow(); + //expect(flush).toThrow(); - expect(spy.calls.all().length).toBe(5); - expect(spy.calls.all()[0].args[0]).toBe(1); - expect(spy.calls.all()[1].args[0]).toBe(4); - expect(spy.calls.all()[2].args[0]).toBe(-2); //first full - expect(spy.calls.all()[3].args[0]).toBe(-5); //last full - expect(spy.calls.all()[4].args[0]).toBe(-8); //empty + expect(spy.calls.all().length).toBe(5); + expect(spy.calls.all()[0].args[0]).toBe(1); + expect(spy.calls.all()[1].args[0]).toBe(4); + expect(spy.calls.all()[2].args[0]).toBe(-2); //first full + expect(spy.calls.all()[3].args[0]).toBe(-5); //last full + expect(spy.calls.all()[4].args[0]).toBe(-8); //empty + }); } ); }); @@ -483,40 +501,43 @@ describe('uiScroll', function () { }); runTest(scrollSettings, - function (viewport, scope, $timeout) { - var wheelEventElement = viewport[0]; - var flush = $timeout.flush; + function (viewport) { + inject(function ($interval) { + var wheelEventElement = viewport[0]; - angular.element(document.body).bind('mousewheel', incrementDocumentScrollCount); //spy for wheel-events bubbling + var flush = $interval.flush; - //simulate multiple wheel-scroll events within viewport + angular.element(document.body).bind('mousewheel', incrementDocumentScrollCount); //spy for wheel-events bubbling - wheelEventElement.dispatchEvent(getNewWheelEvent()); //preventDefault will not occurred but the document will not scroll because of viewport will be scrolled - expect(documentScrollBubblingCount).toBe(1); + //simulate multiple wheel-scroll events within viewport - viewport.scrollTop(0); - viewport.trigger('scroll'); + wheelEventElement.dispatchEvent(getNewWheelEvent()); //preventDefault will not occurred but the document will not scroll because of viewport will be scrolled + expect(documentScrollBubblingCount).toBe(1); - wheelEventElement.dispatchEvent(getNewWheelEvent()); //now we are at the top but preventDefault is occurred because of bof will be reached only after next scroll trigger - expect(documentScrollBubblingCount).toBe(2); //here! the only one prevented wheel-event + viewport.scrollTop(0); + viewport.trigger('scroll'); + + wheelEventElement.dispatchEvent(getNewWheelEvent()); //now we are at the top but preventDefault is occurred because of bof will be reached only after next scroll trigger + expect(documentScrollBubblingCount).toBe(2); //here! the only one prevented wheel-event - flush(); + flush(100); - wheelEventElement.dispatchEvent(getNewWheelEvent()); //preventDefault will not occurred but document will not scroll because of viewport will be scrolled - expect(documentScrollBubblingCount).toBe(3); + wheelEventElement.dispatchEvent(getNewWheelEvent()); //preventDefault will not occurred but document will not scroll because of viewport will be scrolled + expect(documentScrollBubblingCount).toBe(3); - viewport.scrollTop(0); - viewport.trigger('scroll'); //bof will be reached right after that + viewport.scrollTop(0); + viewport.trigger('scroll'); //bof will be reached right after that - flush(); + flush(100); - wheelEventElement.dispatchEvent(getNewWheelEvent()); //preventDefault will not occurred because of we are at the top and bof is reached - expect(documentScrollBubblingCount).toBe(4); + wheelEventElement.dispatchEvent(getNewWheelEvent()); //preventDefault will not occurred because of we are at the top and bof is reached + expect(documentScrollBubblingCount).toBe(4); - expect(flush).toThrow(); //there is no new data, bof is reached + //expect(flush).toThrow(); //there is no new data, bof is reached - wheelEventElement.dispatchEvent(getNewWheelEvent()); //preventDefault will not occurred because of we are at the top and bof is reached - expect(documentScrollBubblingCount).toBe(5); + wheelEventElement.dispatchEvent(getNewWheelEvent()); //preventDefault will not occurred because of we are at the top and bof is reached + expect(documentScrollBubblingCount).toBe(5); + }); }, { cleanupTest: function () { @@ -555,24 +576,32 @@ describe('uiScroll', function () { adapter: 'container1.adapter', isLoading: 'container2.isLoading' }, - function (viewport, scope, $timeout) { - var isLoadingChangeCount = 0; - - expect(!!scope.container1 && !!scope.container1.adapter && !!scope.container2).toBe(true); + function (viewport, scope) { + inject(function ($interval) { + var isLoadingChangeCount = 0; + + expect(!!scope.container1 && !!scope.container1.adapter && !!scope.container2).toBe(true); + + scope.$watch('container2.isLoading', function (newValue, oldValue) { + switch (++isLoadingChangeCount) { + case 1: + expect(newValue).toBe(true); + expect(oldValue).toBe(true); + break; + case 2: + expect(newValue).toBe(false); + expect(oldValue).toBe(true); + break; + } + expect(scope.container1.adapter.isLoading).toBe(newValue); + }); + + viewport.scrollTop(100); + viewport.trigger('scroll'); + $interval.flush(100); - scope.$watch('container2.isLoading', function(newValue, oldValue) { - switch(++isLoadingChangeCount) { - case 1: expect(newValue).toBe(true); expect(oldValue).toBe(true); break; - case 2: expect(newValue).toBe(false); expect(oldValue).toBe(true); break; - } - expect(scope.container1.adapter.isLoading).toBe(newValue); + expect(isLoadingChangeCount).toBe(2); }); - - viewport.scrollTop(100); - viewport.trigger('scroll'); - $timeout.flush(); - - expect(isLoadingChangeCount).toBe(2); } ); }); @@ -594,77 +623,83 @@ describe('uiScroll', function () { it('should calculate top padding element\'s height', function () { runTest(scrollSettings, - function (viewport, scope, $timeout) { - var topPaddingElement = angular.element(viewport.children()[0]); - - var scrollDelta = itemHeight * bufferSize; - var limit = 6; // must be > 2 ! - var i; + function (viewport) { + inject(function ($interval) { + var topPaddingElement = angular.element(viewport.children()[0]); - // scroll down (double speed) + expectation - for(i = 0; i < limit; i++) { - viewport.scrollTop(viewport.scrollTop() + 2 * scrollDelta); - viewport.trigger('scroll'); - $timeout.flush(); + var scrollDelta = itemHeight * bufferSize; + var limit = 6; // must be > 2 ! + var i; - expect(topPaddingElement.height()).toBe(itemHeight * bufferSize * (i + 1)); - } + // scroll down (double speed) + expectation + for (i = 0; i < limit; i++) { + viewport.scrollTop(viewport.scrollTop() + 2 * scrollDelta); + viewport.trigger('scroll'); + $interval.flush(100); - // scroll up (normal speed) + expectation - for(i = limit - 1; i >= 0; i--) { - expect(topPaddingElement.height()).toBe(itemHeight * bufferSize * (i + 1)); + expect(topPaddingElement.height()).toBe(itemHeight * bufferSize * (i + 1)); + } - viewport.scrollTop(viewport.scrollTop() - scrollDelta); - viewport.trigger('scroll'); - $timeout.flush(); - } + // scroll up (normal speed) + expectation + for (i = limit - 1; i >= 0; i--) { + expect(topPaddingElement.height()).toBe(itemHeight * bufferSize * (i + 1)); + viewport.scrollTop(viewport.scrollTop() - scrollDelta); + viewport.trigger('scroll'); + $interval.flush(100); + } + }); } ); }); it('should calculate top padding element\'s height with new rows pre-built', function () { runTest(scrollSettings, - function (viewport, scope, $timeout) { - var topPaddingElement = angular.element(viewport.children()[0]); - - var scrollDelta = itemHeight * bufferSize; - var limit = 6; // must be > 2 ! - var i; - - // scroll down, new rows building - for(i = 0; i < limit; i++) { - viewport.scrollTop(viewport.scrollTop() + scrollDelta); - viewport.trigger('scroll'); - $timeout.flush(); - } + function (viewport) { + inject(function ($interval) { + var flush = function () { + $interval.flush(100); + }; + var topPaddingElement = angular.element(viewport.children()[0]); + + var scrollDelta = itemHeight * bufferSize; + var limit = 6; // must be > 2 ! + var i; + + // scroll down, new rows building + for (i = 0; i < limit; i++) { + viewport.scrollTop(viewport.scrollTop() + scrollDelta); + viewport.trigger('scroll'); + flush(); + } - // scroll up, return to the top - for(i = 0; i < limit; i++) { - viewport.scrollTop(viewport.scrollTop() - scrollDelta); - viewport.trigger('scroll'); - $timeout.flush(); - } + // scroll up, return to the top + for (i = 0; i < limit; i++) { + viewport.scrollTop(viewport.scrollTop() - scrollDelta); + viewport.trigger('scroll'); + flush(); + } - // scroll down + expectation - for(i = 0; i < limit; i++) { - viewport.scrollTop(viewport.scrollTop() + scrollDelta); - viewport.trigger('scroll'); - $timeout.flush(); + // scroll down + expectation + for (i = 0; i < limit; i++) { + viewport.scrollTop(viewport.scrollTop() + scrollDelta); + viewport.trigger('scroll'); + flush(); - expect(topPaddingElement.height()).toBe(itemHeight * bufferSize * (i + 1)); - //console.log(topPaddingElement.height() + ' =?= ' + itemHeight * bufferSize * (i + 1)); - } + expect(topPaddingElement.height()).toBe(itemHeight * bufferSize * (i + 1)); + //console.log(topPaddingElement.height() + ' =?= ' + itemHeight * bufferSize * (i + 1)); + } - // scroll up + expectation - for(i = limit - 1; i >= 0; i--) { - expect(topPaddingElement.height()).toBe(itemHeight * bufferSize * (i + 1)); - //console.log(topPaddingElement.height() + ' =?= ' + itemHeight * bufferSize * (i + 1)); + // scroll up + expectation + for (i = limit - 1; i >= 0; i--) { + expect(topPaddingElement.height()).toBe(itemHeight * bufferSize * (i + 1)); + //console.log(topPaddingElement.height() + ' =?= ' + itemHeight * bufferSize * (i + 1)); - viewport.scrollTop(viewport.scrollTop() - scrollDelta); - viewport.trigger('scroll'); - $timeout.flush(); - } + viewport.scrollTop(viewport.scrollTop() - scrollDelta); + viewport.trigger('scroll'); + flush(); + } + }); } ); @@ -672,50 +707,54 @@ describe('uiScroll', function () { it('should calculate bottom padding element\'s height with new rows pre-built', function () { runTest(scrollSettings, - function (viewport, scope, $timeout) { - var bottomPaddingElement = angular.element(viewport.children()[viewport.children().length - 1]); - - var scrollDelta = itemHeight * bufferSize; - var limit = 6; // must be > 2 ! - var i; - - // scroll up, new rows building - for(i = 0; i < limit; i++) { - viewport.scrollTop(viewport.scrollTop() - scrollDelta); - viewport.trigger('scroll'); - $timeout.flush(); - } + function (viewport) { + inject(function ($interval) { + var flush = function () { + $interval.flush(100); + }; + var bottomPaddingElement = angular.element(viewport.children()[viewport.children().length - 1]); + + var scrollDelta = itemHeight * bufferSize; + var limit = 6; // must be > 2 ! + var i; + + // scroll up, new rows building + for (i = 0; i < limit; i++) { + viewport.scrollTop(viewport.scrollTop() - scrollDelta); + viewport.trigger('scroll'); + flush(); + } - // scroll down, return to the bottom - for(i = 0; i < limit; i++) { - viewport.scrollTop(viewport.scrollTop() + scrollDelta); - viewport.trigger('scroll'); - $timeout.flush(); - } + // scroll down, return to the bottom + for (i = 0; i < limit; i++) { + viewport.scrollTop(viewport.scrollTop() + scrollDelta); + viewport.trigger('scroll'); + flush(); + } - // unstable delta passing - viewport.scrollTop(viewport.scrollTop()); + // unstable delta passing + viewport.scrollTop(viewport.scrollTop()); - // scroll up + expectation - for(i = 0; i < limit; i++) { - viewport.scrollTop(viewport.scrollTop() - scrollDelta); - viewport.trigger('scroll'); - $timeout.flush(); + // scroll up + expectation + for (i = 0; i < limit; i++) { + viewport.scrollTop(viewport.scrollTop() - scrollDelta); + viewport.trigger('scroll'); + flush(); - expect(bottomPaddingElement.height()).toBe(itemHeight * bufferSize * (i + 1)); - //console.log(bottomPaddingElement.height() + ' =?= ' + itemHeight * bufferSize * (i + 1)); - } + expect(bottomPaddingElement.height()).toBe(itemHeight * bufferSize * (i + 1)); + //console.log(bottomPaddingElement.height() + ' =?= ' + itemHeight * bufferSize * (i + 1)); + } - // scroll down + expectation - for(i = limit - 1; i >= 0; i--) { - expect(bottomPaddingElement.height()).toBe(itemHeight * bufferSize * (i + 1)); - //console.log(bottomPaddingElement.height() + ' =?= ' + itemHeight * bufferSize * (i + 1)); - - viewport.scrollTop(viewport.scrollTop() + scrollDelta); - viewport.trigger('scroll'); - $timeout.flush(); - } + // scroll down + expectation + for (i = limit - 1; i >= 0; i--) { + expect(bottomPaddingElement.height()).toBe(itemHeight * bufferSize * (i + 1)); + //console.log(bottomPaddingElement.height() + ' =?= ' + itemHeight * bufferSize * (i + 1)); + viewport.scrollTop(viewport.scrollTop() + scrollDelta); + viewport.trigger('scroll'); + flush(); + } + }); } ); });