in kahuna/public/js/components/gu-lazy-table/gu-lazy-table.js [264:355]
link: function (scope, element, attrs, ctrl) {
// Map attributes as Observable streams
const {
guLazyTable: itemsAttr,
guLazyTableLoadRange: loadRangeFn,
guLazyTableCellMinWidth: cellMinWidthAttr,
guLazyTableCellHeight: cellHeightAttr,
guLazyTablePreloadedRows: preloadedRowsAttr
} = attrs;
const items$ = observeCollection$(scope, itemsAttr);
const cellMinWidth$ = observe$(scope, cellMinWidthAttr).map(asInt);
const cellHeight$ = observe$(scope, cellHeightAttr).map(asInt);
const preloadedRows$ = observe$(scope, preloadedRowsAttr).map(asInt);
// Observe events affecting the view
const viewportScrolled$ = Rx.DOM.fromEvent($window, 'scroll').
// delay fine-tuned to roughly match 'slow scrolling'
// speed but not fast scrolling
debounce(80).
startWith({/* init */});
const viewportResized$ = Rx.DOM.fromEvent($window, 'resize').
debounce(100).
startWith({/* init */});
// Element resized (possibly not the viewport, e.g. side-panel expanded)
const elementResized$ = Rx.Observable.fromEventPattern(
handler => addResizeListener(element[0], handler),
handler => removeResizeListener(element[0], handler)
).
startWith({/* init */});
// Model container and viewport properties
// Offset between container top and top of scrolling area (page)
// Read once as we assume it never changes
const containerTop = element[0].getClientRects()[0].top;
const containerWidth$ = combine$(
viewportResized$,
elementResized$,
() => element[0].clientWidth
).shareReplay(1);
const offsetTop$ = viewportScrolled$.map(() => {
// For Chrome we need to read scrollTop on body, for
// other browser it's on the documentElement. Meh.
// https://miketaylr.com/posts/2014/11/document-body-scrollTop.html
return document.body.scrollTop || document.documentElement.scrollTop;
}).shareReplay(1);
const offsetHeight$ = combine$(viewportScrolled$, viewportResized$, () => {
return Math.max(document.documentElement.clientHeight - containerTop, 0);
}).shareReplay(1);
const viewportTop$ = offsetTop$;
const viewportHeight$ = offsetHeight$;
const {viewHeight$, rangeToLoad$, placeholderIndexes$, newScrollTop$} = ctrl.init({
items$, preloadedRows$, cellHeight$, cellMinWidth$,
containerWidth$, viewportTop$, viewportHeight$
});
// Table cells will be absolutely positioned within this container
element.css({position: 'relative'});
subscribe$(scope, rangeToLoad$, range => {
scope.$eval(loadRangeFn, range);
});
subscribe$(scope, placeholderIndexes$, indexes => {
scope.$placeholders = indexes;
});
subscribe$(scope, viewHeight$.distinctUntilChanged(), viewHeight => {
// Delay application until after this cycle, possibly
// with other cell rendering updates
scope.$applyAsync(() => {
element.css('height', viewHeight + 'px');
scope.$emit('gu-lazy-table:height-changed', viewHeight);
});
});
subscribe$(scope, newScrollTop$, newScrollTop => {
// Note: it may be negative or beyond the page height,
// but the browser will normalise that anyway
window.scrollTo(0, newScrollTop);
});
}