OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openproject/frontend/app/components/wp-table/wp-virtual-scroll.directive.ts

186 lines
5.7 KiB

// -- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {wpDirectivesModule} from "../../angular-modules";
import {scopedObservable, runInScopeDigest} from "../../helpers/angular-rx-utils";
import IScope = angular.IScope;
import IRootElementService = angular.IRootElementService;
import IAnimateProvider = angular.animate.IAnimateProvider;
import ITranscludeFunction = angular.ITranscludeFunction;
import {ReplaySubject, Subject} from "rxjs";
function wpVirtualScrollRow(workPackageTableVirtualScrollService: WorkPackageTableVirtualScrollService) {
return {
restrict: 'A',
link: ($scope: IScope,
$element: IRootElementService,
$attr: any) => {
new RowDisplay($scope, $element, $attr, workPackageTableVirtualScrollService);
}
};
}
wpDirectivesModule.directive('wpVirtualScrollRow', wpVirtualScrollRow);
class RowDisplay {
private index: number;
private viewport: [number, number] = [0, 0];
private watchersEnabled = true;
constructor(private $scope: angular.IScope,
private $element: angular.IRootElementService,
private $attr: any,
private workPackageTableVirtualScrollService: WorkPackageTableVirtualScrollService) {
this.index = $scope.$eval($attr.wpVirtualScrollRow);
scopedObservable($scope, workPackageTableVirtualScrollService.viewportChanges)
.subscribe(vp => {
this.viewport = vp;
this.viewportChanged();
});
workPackageTableVirtualScrollService.requestUpdate();
}
private isRowInViewport() {
const offsetTop = this.$element.offset().top;
const top = this.viewport[0];
const bottom = this.viewport[1];
return (offsetTop > top && offsetTop < bottom);
}
private viewportChanged() {
const enableWatchers = this.isRowInViewport();
if (this.watchersEnabled !== enableWatchers) {
this.adjustWatchers(this.$element, enableWatchers);
}
}
private adjustWatchers(element: JQuery, enableWatchers: boolean) {
this.watchersEnabled = enableWatchers;
const scope: any = angular.element(element).scope();
if (scope === undefined) {
return;
}
// Do not toggle watch state on table rows themselves
// since watchers are needed for, e.g., group collapsing
if (!(element.length > 0 && element[0].tagName === 'TR')) {
this.setWatchState(scope, enableWatchers);
}
angular.forEach(angular.element(element).children(), (child: JQuery) => {
this.adjustWatchers(child, enableWatchers);
});
}
private setWatchState(scope, enableWatchers: boolean) {
if (!enableWatchers) {
if (scope.$$watchers && scope.$$watchers.length > 0) {
scope.__backup_watchers = scope.$$watchers;
scope.$$watchers = [];
}
} else {
if (scope.__backup_watchers && scope.__backup_watchers.length > 0) {
scope.$$watchers = scope.__backup_watchers;
scope.__backup_watchers = [];
}
}
}
}
class WorkPackageTableVirtualScrollService {
public viewportChanges: Subject<[number, number]> = new ReplaySubject<[number, number]>(0);
private requestedUpdateTimeout: any;
/*@ngInject*/
constructor(private $rootScope: angular.IRootScopeService) {
}
updateScrollInfo() {
runInScopeDigest(this.$rootScope, () => {
this.viewportChanges.next([-50, window.innerHeight + 50]);
});
}
requestUpdate() {
this.requestedUpdateTimeout && clearTimeout(this.requestedUpdateTimeout);
this.requestedUpdateTimeout = setTimeout(() => {
this.updateScrollInfo();
}, 1000);
}
}
wpDirectivesModule.service("workPackageTableVirtualScrollService", WorkPackageTableVirtualScrollService);
function wpVirtualScrollTable(workPackageTableVirtualScrollService: WorkPackageTableVirtualScrollService) {
return {
restrict: 'A',
link: ($scope: IScope, $element: IRootElementService, attr: any) => {
// flag to avoid endless loops
let updateActive = false;
const updateScrollInfo = () => {
if (updateActive) {
return;
}
updateActive = true;
try {
workPackageTableVirtualScrollService.updateScrollInfo();
} finally {
updateActive = false;
}
};
let scrollTimeout: any;
$element.on("scroll", () => {
scrollTimeout && clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
updateScrollInfo();
}, 2000);
});
window.addEventListener('resize', () => {
updateScrollInfo();
});
}
};
}
wpDirectivesModule.directive('wpVirtualScrollTable', wpVirtualScrollTable);