link: function()

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);
            });
        }