ui-scroll
ui-scroll copied to clipboard
Different uiScrollViewport schemes when using as a component
I am trying to use great ui-scroll module inside one of the components. This is a (surprise!) list, but i need to show it both inside window (no viewport) or inside popup (with viewport).
The only way I know to tell ui-scroll about viewport is ui-scroll-viewport attribute. And the only way I can tell Angular about optional attribute is ng-attr- prefix. I want it to be optional attribute, since I hate to copy-paste the same code in ng-switches.
And this is a place when things get interesting. According to https://github.com/angular/angular.js/issues/16441 ui-scroll gets information about ui-scroll-viewport attribute before evaluation of ng-attr-ui-scroll-viewport value.
May be someone will point me to more "right" solution. But for now I had to patch ui-scroll.js file.
- I send the
max-heightstyle inside some param to the component
<ul class="list-group" style="overflow-x: hidden" ng-attr-ui-scroll-viewport="$ctrl.checkRestrictHeight()" ng-style="{'min-height': '1px', 'max-height': $ctrl.checkRestrictHeight()}">
<a class="list-group-item" ui-scroll="survey in $ctrl.itemsSource" start-index="$ctrl.startPosition" buffer-size="30" adapter="$ctrl.scroll"
...
- I check the result of $eval for
ng-attr-ui-scroll-viewportvalue
function Viewport(elementRoutines, buffer, element, viewportController, $rootScope, padding) {
var topPadding = null;
var bottomPadding = null;
//ADD
let ngAttrEval = true;
if (viewportController && viewportController.viewport && viewportController.viewport[0].attributes && viewportController.viewport[0].attributes.hasOwnProperty('ng-attr-ui-scroll-viewport') && !viewportController.viewport[0].attributes.hasOwnProperty('ui-scroll-viewport')) {
ngAttrEval = false;
if (viewportController.scope) {
ngAttrEval = viewportController.scope.$eval(viewportController.viewport[0].attributes['ng-attr-ui-scroll-viewport'].value);
}
}
//CHANGE
var viewport = viewportController && viewportController.viewport && ngAttrEval ? viewportController.viewport : angular.element(window);
var container = viewportController && viewportController.container && ngAttrEval ? viewportController.container : undefined;
var scope = viewportController && viewportController.scope && ngAttrEval ? viewportController.scope : $rootScope;
...
@ornic If you are going to augment Viewport module, an easier approach might be as follows.
Template:
<ul class="list-group" ui-scroll-viewport="$ctrl.checkRestrictHeight()">
Directive:
.directive('uiScrollViewport', function () {
return {
restrict: 'A',
scope: { window: '=uiScrollViewport' }, // new line, assign directive attr value to scope.window
controller: [
// ...
Viewport:
const ctrl = viewportController;
const isViewport = ctrl && ctrl.scope && ctrl.scope.window !== false;
const scope = isViewport ? ctrl.scope : $rootScope;
const viewport = isViewport && ctrl.viewport ? ctrl.viewport : angular.element(window);
const container = isViewport && ctrl.container ? ctrl.container : undefined;
Note, the explicit check is used: !== false -- it is necessary for the case when a user doesn't pass any value to the "ui-scroll-viewport" attribute but expects the Viewport to be present.
This way code looks more readable than in my crude variant. But I found that after defining scope in .directive it is unable to make an adaptor in viewportController.scope, but constantly setting viewport.scope to $rootScope fixed the problem (for me, with using ui-scroll via component). But I assume that in future (more usecases inside one app) this approach may backfire.
UPD: I looked further and it seems that this approach should be Ok, since the only result AFAIK is always using scope of uiScroll directive.
I didn't meet any issues with the Adapter when using the suggested approach.
I also found the very handy method of using full-window scroll: with some block of text before the list. But there is a problem on the way.
This is the easy correction of your example. This div goes before the list
<div style="
height: 300px;
text-align: center;
border: 1px solid lightgreen;
vertical-align: middle;
padding: 100px;
">Hello! I'm 300px of height.</div>
and we limit the list with index -100.
And then we lose ability to correctly identify first and last element:

I'm trying to find the way to overcome this. :)
....
That was... interesting :))) As a result I adjusted *Pos functions with an idea to use container top offset and scrollHeight to compensate:
bottomDataPos: function bottomDataPos() {
var scrollHeight = viewport[0].scrollHeight;
scrollHeight = scrollHeight != null ? scrollHeight : viewport[0].document.documentElement.scrollHeight;
let btmPadding = bottomPadding.height();
if (container && container[0] != viewport[0]) { // the only possible case: viewport = window && container != window
if (btmPadding > 0) { // consider space under the container
return container[0].scrollHeight - btmPadding;
}
scrollHeight -= container.offset().top;
}
return scrollHeight - btmPadding;
},
topDataPos: function topDataPos() {
let topDataPos = topPadding.height();
return topDataPos;
},
bottomVisiblePos: function bottomVisiblePos() {
let viewPortHeight = viewport.outerHeight();
if (container && container[0] != viewport[0]) {
let offset = container.offset().top - viewport.scrollTop();
if (offset > 0) viewPortHeight -= offset;
}
return viewport.topVisiblePos() + viewPortHeight;
},
topVisiblePos: function topVisiblePos() {
let topVisiblePos = viewport.scrollTop();
if (container && container[0] != viewport[0]) {
topVisiblePos -= container.offset().top;
if (topVisiblePos < 0) topVisiblePos = 0; // no negative positions :)
}
return topVisiblePos;
}