[26575] Control initial focussing through wpTableFocusService (#6064)

https://community.openproject.com/wp/26575

[ci skip]
pull/6067/head
Oliver Günther 7 years ago committed by GitHub
parent 015eede3bd
commit 49838f3b0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      frontend/app/components/modals/wp-destroy-modal/wp-destroy-modal.controller.ts
  2. 48
      frontend/app/components/routing/first-route-service.ts
  3. 7
      frontend/app/components/routing/ui-router.config.ts
  4. 19
      frontend/app/components/routing/wp-details/wp-details.controller.ts
  5. 5
      frontend/app/components/routing/wp-show/wp-show.controller.ts
  6. 5
      frontend/app/components/routing/wp-view-base/wp-view-base.controller.ts
  7. 3
      frontend/app/components/states.service.ts
  8. 4
      frontend/app/components/wp-buttons/wp-details-view-button/wp-details-view-button.directive.ts
  9. 4
      frontend/app/components/wp-buttons/wp-view-button/wp-view-button.directive.ts
  10. 4
      frontend/app/components/wp-edit/wp-edit-field/wp-edit-field-group.directive.ts
  11. 12
      frontend/app/components/wp-fast-table/handlers/row/click-handler.ts
  12. 11
      frontend/app/components/wp-fast-table/handlers/row/double-click-handler.ts
  13. 6
      frontend/app/components/wp-fast-table/handlers/row/wp-state-links-handler.ts
  14. 20
      frontend/app/components/wp-fast-table/handlers/state/hierarchy-transformer.ts
  15. 27
      frontend/app/components/wp-fast-table/handlers/state/selection-transformer.ts
  16. 20
      frontend/app/components/wp-fast-table/helpers/wp-table-row-helpers.ts
  17. 80
      frontend/app/components/wp-fast-table/state/wp-table-focus.service.ts
  18. 50
      frontend/app/components/wp-fast-table/state/wp-table-selection.service.ts
  19. 4
      frontend/app/components/wp-fast-table/wp-table.interfaces.ts
  20. 4
      frontend/app/components/wp-inline-create/wp-inline-create.directive.ts
  21. 4
      spec/features/work_packages/select_work_package_row_spec.rb

@ -33,6 +33,7 @@ import {WorkPackageNotificationService} from '../../wp-edit/wp-notification.serv
import {QueryResource} from '../../api/api-v3/hal-resources/query-resource.service';
import {QueryDmService} from '../../api/api-v3/hal-resource-dms/query-dm.service';
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
export class WorkPackageDestroyModalController {
public text:any;
@ -43,6 +44,7 @@ export class WorkPackageDestroyModalController {
private $state:ng.ui.IStateService,
private states:States,
private WorkPackageService:any,
private wpTableFocus:WorkPackageTableFocusService,
private I18n:op.I18n,
private wpDestroyModal:any) {
@ -84,8 +86,8 @@ export class WorkPackageDestroyModalController {
this.wpDestroyModal.deactivate();
this.WorkPackageService.performBulkDelete(this.workPackages.map(el => el.id), true)
.then(() => {
this.close()
this.states.focusedWorkPackage.clear();
this.close();
this.wpTableFocus.clear();
this.$state.go('work-packages.list');
});
}

@ -0,0 +1,48 @@
// -- 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 {opServicesModule} from '../../angular-modules';
export class FirstRouteService {
public name:string;
public params:any;
constructor() {}
public setIfFirst(state:ng.ui.IState, params:any) {
if (this.name) {
return;
}
this.name = state.name!;
this.params = params;
}
}
opServicesModule.service('firstRoute', FirstRouteService);

@ -27,6 +27,7 @@
// ++
import {openprojectModule} from '../../angular-modules';
import {FirstRouteService} from 'app/components/routing/first-route-service';
const panels = {
get overview() {
@ -198,6 +199,7 @@ openprojectModule
.run(($location:ng.ILocationService,
$rootElement:ng.IRootElementService,
firstRoute:FirstRouteService,
$timeout:ng.ITimeoutService,
$rootScope:ng.IRootScopeService,
$state:ng.ui.IStateService,
@ -236,7 +238,12 @@ openprojectModule
evt.preventDefault();
});
$rootScope.$on('$stateChangeStart', (event, toState, toParams) => {
// We need to distinguish between actions that should run on the initial page load
// (ie. openining a new tab in the details view should focus on the element in the table)
// so we need to know which route we visited initially
firstRoute.setIfFirst(toState, toParams);
$rootScope.$emit('notifications.clearAll');

@ -33,24 +33,29 @@ import {WorkPackageTableSelection} from "../../wp-fast-table/state/wp-table-sele
import {KeepTabService} from "../../wp-panels/keep-tab/keep-tab.service";
import {WorkPackageViewController} from "../wp-view-base/wp-view-base.controller";
import {WorkPackageEditingService} from '../../wp-edit-form/work-package-editing-service';
import {FirstRouteService} from "core-components/routing/first-route-service";
import {WorkPackageTableFocusService} from "core-components/wp-fast-table/state/wp-table-focus.service";
export class WorkPackageDetailsController extends WorkPackageViewController {
constructor(public $scope:ng.IScope,
public states:States,
public firstRoute:FirstRouteService,
public keepTab:KeepTabService,
public wpTableSelection:WorkPackageTableSelection,
public wpTableFocus:WorkPackageTableFocusService,
public $state:ng.ui.IStateService) {
super($scope, $state.params['workPackageId']);
this.observeWorkPackage();
let wpId = $state.params['workPackageId'];
let focusState = this.states.focusedWorkPackage;
let focusedWP = focusState.value;
let focusedWP = this.wpTableFocus.focusedWorkPackage;
if (!focusedWP) {
focusState.putValue(wpId);
this.wpTableSelection.setRowState(wpId, true);
// Focus on the work package if we're the first route
const isFirstRoute = firstRoute.name === 'work-packages.list.details.overview';
const isSameID = firstRoute.params && wpId === firstRoute.params.workPackageI;
this.wpTableFocus.updateFocus(wpId, (isFirstRoute && isSameID));
}
if (this.wpTableSelection.isEmpty) {
@ -59,10 +64,8 @@ export class WorkPackageDetailsController extends WorkPackageViewController {
scopedObservable(
$scope,
this.states.focusedWorkPackage.values$())
.map(wpId => wpId.toString())
.distinctUntilChanged()
.subscribe((newId) => {
this.wpTableFocus.whenChanged()
).subscribe(newId => {
if (wpId !== newId && $state.includes('work-packages.list.details')) {
$state.go(
($state.current.name as string),

@ -33,6 +33,7 @@ import {WorkPackageResourceInterface} from "../../api/api-v3/hal-resources/work-
import {WorkPackageViewController} from "../wp-view-base/wp-view-base.controller";
import {WorkPackagesListChecksumService} from "../../wp-list/wp-list-checksum.service";
import {WorkPackageMoreMenuService} from '../../work-packages/work-package-more-menu.service'
import {WorkPackageTableFocusService} from "core-components/wp-fast-table/state/wp-table-focus.service";
export class WorkPackageShowController extends WorkPackageViewController {
@ -52,6 +53,7 @@ export class WorkPackageShowController extends WorkPackageViewController {
constructor(public $scope:ng.IScope,
public $state:ng.ui.IStateService,
public wpTableFocus:WorkPackageTableFocusService,
protected wpMoreMenuService:WorkPackageMoreMenuService) {
super($scope, $state.params['workPackageId']);
this.observeWorkPackage();
@ -60,6 +62,9 @@ export class WorkPackageShowController extends WorkPackageViewController {
protected init() {
super.init();
// Set Focused WP
this.wpTableFocus.updateFocus(this.workPackage.id);
// initialization
this.wpMoreMenu = new (this.wpMoreMenuService as any)(this.workPackage);

@ -35,6 +35,7 @@ import {KeepTabService} from '../../wp-panels/keep-tab/keep-tab.service';
import {WorkPackageTableRefreshService} from '../../wp-table/wp-table-refresh-request.service';
import {$injectFields} from '../../angular/angular-injector-bridge.functions';
import {WorkPackageEditingService} from '../../wp-edit-form/work-package-editing-service';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
export class WorkPackageViewController {
@ -49,6 +50,7 @@ export class WorkPackageViewController {
protected I18n:op.I18n;
protected wpTableRefresh:WorkPackageTableRefreshService;
protected wpEditing:WorkPackageEditingService;
protected wpTableFocus:WorkPackageTableFocusService;
// Helper promise to detect when the controller has been initialized
// (when a WP has loaded).
@ -67,7 +69,7 @@ export class WorkPackageViewController {
constructor(public $scope:ng.IScope,
protected workPackageId:string) {
$injectFields(this, '$q', '$state', 'keepTab', 'wpCacheService', 'WorkPackageService',
'states', 'wpEditing', 'PathHelper', 'I18n', 'wpTableRefresh');
'states', 'wpEditing', 'PathHelper', 'I18n', 'wpTableRefresh', 'wpTableFocus');
this.initialized = this.$q.defer();
this.initializeTexts();
@ -111,7 +113,6 @@ export class WorkPackageViewController {
// Preselect this work package for future list operations
this.showStaticPagePath = this.PathHelper.workPackagePath(this.workPackage);
this.states.focusedWorkPackage.putValue(this.workPackage.id);
// Listen to tab changes to update the tab label
scopedObservable(this.$scope, this.keepTab.observable).subscribe((tabs:any) => {

@ -37,6 +37,7 @@ import {QuerySortByResource} from './api/api-v3/hal-resources/query-sort-by-reso
import {QueryGroupByResource} from './api/api-v3/hal-resources/query-group-by-resource.service';
import {WPTableRowSelectionState} from './wp-fast-table/wp-table.interfaces';
import {WorkPackageTableRelationColumns} from './wp-fast-table/wp-table-relation-columns';
import {WPFocusState} from 'core-components/wp-fast-table/state/wp-table-focus.service';
export class States extends StatesGroup {
@ -62,7 +63,7 @@ export class States extends StatesGroup {
updates = new UserUpdaterStates(this);
// Current focused work package (e.g, row preselected for details button)
focusedWorkPackage = input<string>();
focusedWorkPackage = input<WPFocusState>();
}

@ -30,6 +30,7 @@ import {wpButtonsModule} from '../../../angular-modules';
import {WorkPackageButtonController, wpButtonDirective} from '../wp-buttons.module';
import {KeepTabService} from '../../wp-panels/keep-tab/keep-tab.service';
import {States} from '../../states.service';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
export class WorkPackageDetailsViewButtonController extends WorkPackageButtonController {
@ -49,6 +50,7 @@ export class WorkPackageDetailsViewButtonController extends WorkPackageButtonCon
public states:States,
public I18n:op.I18n,
public loadingIndicator:any,
public wpTableFocus:WorkPackageTableFocusService,
public keepTab:KeepTabService) {
'ngInject';
super(I18n);
@ -92,7 +94,7 @@ export class WorkPackageDetailsViewButtonController extends WorkPackageButtonCon
public openDetailsView() {
var params = {
workPackageId: this.states.focusedWorkPackage.value,
workPackageId: this.wpTableFocus.focusedWorkPackage,
projectPath: this.projectIdentifier,
};

@ -30,6 +30,7 @@ import {wpButtonsModule} from '../../../angular-modules';
import {WorkPackageNavigationButtonController, wpButtonDirective} from '../wp-buttons.module';
import {KeepTabService} from '../../wp-panels/keep-tab/keep-tab.service';
import {States} from '../../states.service';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
export class WorkPackageViewButtonController extends WorkPackageNavigationButtonController {
public workPackageId:number;
@ -43,6 +44,7 @@ export class WorkPackageViewButtonController extends WorkPackageNavigationButton
public $state:ng.ui.IStateService,
public states:States,
public I18n:op.I18n,
public wpTableFocus:WorkPackageTableFocusService,
public keepTab:KeepTabService) {
'ngInject';
@ -59,7 +61,7 @@ export class WorkPackageViewButtonController extends WorkPackageNavigationButton
public openWorkPackageShowView() {
let args = ['work-packages.new', this.$state.params];
let id = this.$state.params['workPackageId'] || this.workPackageId || this.states.focusedWorkPackage.value;
let id = this.$state.params['workPackageId'] || this.workPackageId || this.wpTableFocus.focusedWorkPackage;
if (!this.$state.is('work-packages.list.new')) {
let params = {

@ -38,6 +38,7 @@ import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-
import {WorkPackageTableSelection} from '../../wp-fast-table/state/wp-table-selection.service';
import {WorkPackageNotificationService} from '../wp-notification.service';
import {WorkPackageCreateService} from './../../wp-create/wp-create.service';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
export class WorkPackageEditFieldGroupController {
public workPackage:WorkPackageResourceInterface;
@ -55,6 +56,7 @@ export class WorkPackageEditFieldGroupController {
protected wpEditing:WorkPackageEditingService,
protected wpNotificationsService:WorkPackageNotificationService,
protected wpTableSelection:WorkPackageTableSelection,
protected wpTableFocus:WorkPackageTableFocusService,
protected $rootScope:ng.IRootScopeService,
protected $window:ng.IWindowService,
protected ConfigurationService:any,
@ -172,7 +174,7 @@ export class WorkPackageEditFieldGroupController {
if (this.successState) {
this.$state.go(this.successState, {workPackageId: savedWorkPackage.id})
.then(() => {
this.wpTableSelection.focusOn(savedWorkPackage.id);
this.wpTableFocus.updateFocus(savedWorkPackage.id);
this.wpNotificationsService.showSave(savedWorkPackage, isInitial);
});
}

@ -7,6 +7,7 @@ import {WorkPackageTableSelection} from '../../state/wp-table-selection.service'
import {tableRowClassName} from '../../builders/rows/single-row-builder';
import {tdClassName} from '../../builders/cell-builder';
import {KeepTabService} from "../../../wp-panels/keep-tab/keep-tab.service";
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
export class RowClickHandler implements TableEventHandler {
// Injections
@ -14,12 +15,13 @@ export class RowClickHandler implements TableEventHandler {
public states:States;
public keepTab:KeepTabService;
public wpTableSelection:WorkPackageTableSelection;
public wpTableFocus:WorkPackageTableFocusService;
private clicks = 0;
private timer:number;
constructor(table:WorkPackageTable) {
$injectFields(this, 'keepTab', '$state', 'states', 'wpTableSelection');
$injectFields(this, 'keepTab', '$state', 'states', 'wpTableSelection', 'wpTableFocus');
}
public get EVENT() {
@ -63,11 +65,7 @@ export class RowClickHandler implements TableEventHandler {
return true;
}
// The current row is the last selected work package
// not matter what other rows are (de-)selected below.
// Thus save that row for the details view button.
let [index, row] = table.findRenderedRow(classIdentifier);
this.states.focusedWorkPackage.putValue(wpId);
// Update single selection if no modifier present
if (!(evt.ctrlKey || evt.metaKey || evt.shiftKey)) {
@ -84,6 +82,10 @@ export class RowClickHandler implements TableEventHandler {
this.wpTableSelection.toggleRow(wpId);
}
// The current row is the last selected work package
// not matter what other rows are (de-)selected below.
// Thus save that row for the details view button.
this.wpTableFocus.updateFocus(wpId);
return false;
}
}

@ -1,20 +1,22 @@
import {debugLog} from '../../../../helpers/debug_output';
import {injectorBridge} from '../../../angular/angular-injector-bridge.functions';
import {$injectFields} from '../../../angular/angular-injector-bridge.functions';
import {WorkPackageTable} from '../../wp-fast-table';
import {States} from '../../../states.service';
import {TableEventHandler} from '../table-handler-registry';
import {WorkPackageTableSelection} from '../../state/wp-table-selection.service';
import {tableRowClassName} from '../../builders/rows/single-row-builder';
import {tdClassName} from '../../builders/cell-builder';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
export class RowDoubleClickHandler implements TableEventHandler {
// Injections
public $state:ng.ui.IStateService;
public states:States;
public wpTableSelection:WorkPackageTableSelection;
public wpTableFocus:WorkPackageTableFocusService;
constructor(table: WorkPackageTable) {
injectorBridge(this);
constructor(table:WorkPackageTable) {
$injectFields(this, '$state', 'states', 'wpTableSelection', 'wpTableFocus');
}
public get EVENT() {
@ -49,7 +51,7 @@ export class RowDoubleClickHandler implements TableEventHandler {
}
// Save the currently focused work package
this.states.focusedWorkPackage.putValue(wpId);
this.wpTableFocus.updateFocus(wpId);
this.$state.go(
'work-packages.show',
@ -60,4 +62,3 @@ export class RowDoubleClickHandler implements TableEventHandler {
}
}
RowDoubleClickHandler.$inject = ['$state', 'states', 'wpTableSelection'];

@ -7,6 +7,7 @@ import {uiStateLinkClass} from '../../builders/ui-state-link-builder';
import {tableRowClassName} from "../../builders/rows/single-row-builder";
import {States} from "../../../states.service";
import {WorkPackageTableSelection} from "../../state/wp-table-selection.service";
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
export class WorkPackageStateLinksHandler implements TableEventHandler {
// Injections
@ -14,9 +15,10 @@ export class WorkPackageStateLinksHandler implements TableEventHandler {
public keepTab:KeepTabService;
public states:States;
public wpTableSelection:WorkPackageTableSelection;
public wpTableFocus:WorkPackageTableFocusService;
constructor(table: WorkPackageTable) {
$injectFields(this, '$state', 'keepTab', 'states', 'wpTableSelection');
$injectFields(this, '$state', 'keepTab', 'states', 'wpTableSelection', 'wpTableFocus');
}
public get EVENT() {
@ -56,7 +58,7 @@ export class WorkPackageStateLinksHandler implements TableEventHandler {
let classIdentifier = row.data('classIdentifier');
let [index, _] = table.findRenderedRow(classIdentifier);
this.states.focusedWorkPackage.putValue(workPackageId);
this.wpTableFocus.updateFocus(workPackageId);
// Update single selection if no modifier present
this.wpTableSelection.setSelection(workPackageId, index);

@ -7,7 +7,7 @@ import {WorkPackageTableHierarchies} from "../../wp-table-hierarchies";
import {indicatorCollapsedClass} from "../../builders/modes/hierarchy/single-hierarchy-row-builder";
import {tableRowClassName} from '../../builders/rows/single-row-builder';
import {debugLog} from '../../../../helpers/debug_output';
import { locateTableRow } from "core-components/wp-fast-table/helpers/wp-table-row-helpers";
import { locateTableRow, scrollTableRowIntoView } from "core-components/wp-fast-table/helpers/wp-table-row-helpers";
export class HierarchyTransformer {
public wpTableHierarchies:WorkPackageTableHierarchiesService;
@ -85,23 +85,7 @@ export class HierarchyTransformer {
// Keep focused on the last element, if any.
// Based on https://stackoverflow.com/a/3782959
if (state.last) {
try {
const element = locateTableRow(state.last);
const container = element.scrollParent();
const containerTop = container.scrollTop();
const containerBottom = containerTop + container.height();
const elemTop = element[0].offsetTop;
const elemBottom = elemTop + element.height();
if (elemTop < containerTop) {
container[0].scrollTop = elemTop;
} else if (elemBottom > containerBottom) {
container[0].scrollTop = elemBottom - container.height();
}
} catch (e) {
console.warn("Can't scroll hierarchy element into view: " + e);
}
scrollTableRowIntoView(state.last);
}

@ -1,42 +1,34 @@
import {injectorBridge} from "../../../angular/angular-injector-bridge.functions";
import {$injectFields} from "../../../angular/angular-injector-bridge.functions";
import {States} from "../../../states.service";
import {tableRowClassName} from "../../builders/rows/single-row-builder";
import {checkedClassName} from "../../builders/ui-state-link-builder";
import {rowId, locateTableRow} from "../../helpers/wp-table-row-helpers";
import {rowId, locateTableRow, scrollTableRowIntoView} from "../../helpers/wp-table-row-helpers";
import {WorkPackageTableSelection} from "../../state/wp-table-selection.service";
import {WorkPackageTable} from "../../wp-fast-table";
import {WPTableRowSelectionState} from "../../wp-table.interfaces";
import {WorkPackageTableFocusService} from "core-components/wp-fast-table/state/wp-table-focus.service";
export class SelectionTransformer {
public wpTableSelection:WorkPackageTableSelection;
public wpTableFocus:WorkPackageTableFocusService;
public states:States;
public FocusHelper:any;
// When first entering the page, the user
// wants to scroll to the focused work package in the table.
// We only want to do this once, so remember when we did the first focus
private hasFocusedOnElement = false;
constructor(table:WorkPackageTable) {
injectorBridge(this);
$injectFields(this, 'wpTableSelection', 'wpTableFocus', 'states', 'FocusHelper');
// Focus a single selection when active
this.states.table.rendered.values$()
.takeUntil(this.states.table.stopAllSubscriptions)
.subscribe(() => {
const singleSelection = this.wpTableSelection.getSingleSelection;
if (singleSelection === null) {
return;
}
if (!this.hasFocusedOnElement) {
this.hasFocusedOnElement = true;
const element = locateTableRow(singleSelection);
this.wpTableFocus.ifShouldFocus((wpId:string) => {
const element = locateTableRow(wpId);
if (element.length) {
element[0].scrollIntoView();
scrollTableRowIntoView(wpId);
this.FocusHelper.focusElement(element, true);
}
}
});
});
@ -75,4 +67,3 @@ export class SelectionTransformer {
}
}
SelectionTransformer.$inject = ['wpTableSelection', 'states', 'FocusHelper'];

@ -10,4 +10,24 @@ export function locateTableRow(workPackageId:string):JQuery {
return jQuery('.' + rowId(workPackageId));
}
export function scrollTableRowIntoView(workPackageId:string):void {
try {
const element = locateTableRow(workPackageId);
const container = element.scrollParent();
const containerTop = container.scrollTop();
const containerBottom = containerTop + container.height();
const elemTop = element[0].offsetTop;
const elemBottom = elemTop + element.height();
if (elemTop < containerTop) {
container[0].scrollTop = elemTop;
} else if (elemBottom > containerBottom) {
container[0].scrollTop = elemBottom - container.height();
}
} catch (e) {
console.warn("Can't scroll row element into view: " + e);
}
}

@ -0,0 +1,80 @@
import {States} from '../../states.service';
import {opServicesModule} from '../../../angular-modules';
import {WorkPackageResource} from '../../api/api-v3/hal-resources/work-package-resource.service';
import {InputState} from 'reactivestates';
import {WorkPackageTableSelection} from 'core-components/wp-fast-table/state/wp-table-selection.service';
export interface WPFocusState {
workPackageId:string;
focusAfterRender:boolean;
}
export class WorkPackageTableFocusService {
public state:InputState<WPFocusState>;
constructor(public states:States,
public wpTableSelection:WorkPackageTableSelection) {
this.state = states.focusedWorkPackage;
this.observeToUpdateFocused();
}
public isFocused(workPackageId:string) {
return this.focusedWorkPackage === workPackageId;
}
public ifShouldFocus(callback:(workPackageId:string) => void) {
const value = this.state.value;
if (value && value.focusAfterRender) {
callback(value.workPackageId);
value.focusAfterRender = false;
this.state.putValue(value, 'Setting focus to false after callback.');
}
}
public get focusedWorkPackage():string|null {
const value = this.state.value;
if (value) {
return value.workPackageId;
}
return null;
}
public clear() {
this.state.clear();
}
public whenChanged() {
return this.state.values$()
.map((val:WPFocusState) => val.workPackageId)
.distinctUntilChanged();
}
public updateFocus(workPackageId:string, setFocusAfterRender:boolean = false) {
// Set the selection to this row, if nothing else is selected.
if (this.wpTableSelection.isEmpty) {
this.wpTableSelection.setRowState(workPackageId, true);
}
this.state.putValue({ workPackageId: workPackageId, focusAfterRender: setFocusAfterRender});
}
/**
* Put the first row that is eligible to be displayed in the details view into
* the focused state if no manual selection has been made yet.
*/
private observeToUpdateFocused() {
this
.states.table.rendered
.values$()
.map(state => _.find(state, (row:any) => row.workPackageId))
.filter(fullRow => !!fullRow && this.wpTableSelection.isEmpty)
.subscribe(fullRow => {
this.updateFocus(fullRow!.workPackageId!);
});
}
}
opServicesModule.service('wpTableFocus', WorkPackageTableFocusService);

@ -15,8 +15,6 @@ export class WorkPackageTableSelection {
if (this.selectionState.isPristine()) {
this.reset();
}
this.observeToUpdateFocused();
}
public isSelected(workPackageId:string) {
@ -84,31 +82,6 @@ export class WorkPackageTableSelection {
return _.size(this.currentState.selected);
}
public get isSingleSelection():boolean {
return this.getSingleSelection !== null;
}
public get getSingleSelection():string|null {
const selected = _.pickBy(this.currentState.selected, (selected:boolean) => selected === true);
const selectedWps = _.keys(selected);
if (selectedWps.length === 1) {
return selectedWps[0];
}
return null;
}
/**
* Switch the current focused work package to the given id,
* setting selection and focus on this WP.
*/
public focusOn(workPackgeId:string) {
let newState = this._emptyState;
newState.selected[workPackgeId] = true;
this.selectionState.putValue(newState);
this.states.focusedWorkPackage.putValue(workPackgeId);
}
/**
* Toggle a single row selection state and update the state.
* @param workPackageId
@ -150,15 +123,19 @@ export class WorkPackageTableSelection {
public setMultiSelectionFrom(rows:RenderedRow[], wpId:string, position:number) {
let state = this.currentState;
if (this.selectionCount === 0) {
// If there are no other selections, it does not matter what the index is
if (this.selectionCount === 0 || state.activeRowIndex === null) {
console.warn(`Selection count is empty, setting ${wpId} to selected.`);
state.selected[wpId] = true;
state.activeRowIndex = position;
} else if (state.activeRowIndex !== null) {
} else {
console.warn(`Active index is ${state.activeRowIndex}`);
let start = Math.min(position, state.activeRowIndex);
let end = Math.max(position, state.activeRowIndex);
rows.forEach((row, i) => {
if (row.workPackageId) {
console.warn(`Setting ${row.workPackageId} ? ${i >= start && i <= end}`);
state.selected[row.workPackageId] = i >= start && i <= end;
}
});
@ -174,21 +151,6 @@ export class WorkPackageTableSelection {
activeRowIndex: null
};
}
/**
* Put the first row that is eligible to be displayed in the details view into
* the focused state if no manual selection has been made yet.
*/
private observeToUpdateFocused() {
this
.states.table.rendered
.values$()
.map(state => _.find(state, (row:any) => row.workPackageId))
.filter(fullRow => !!fullRow && _.isEmpty(this.currentState.selected))
.subscribe(fullRow => {
this.states.focusedWorkPackage.putValue(fullRow!.workPackageId!);
});
}
}
opServicesModule.service('wpTableSelection', WorkPackageTableSelection);

@ -28,8 +28,8 @@ export interface GroupableColumn {
export interface WPTableRowSelectionState {
// Map of selected rows
selected: {[workPackageId: string]: boolean};
selected:{[workPackageId:string]:boolean};
// Index of current selection
// required for shift-offsets
activeRowIndex: number | null;
activeRowIndex:number | null;
}

@ -48,6 +48,7 @@ import {TableRowEditContext} from '../wp-edit-form/table-row-edit-context';
import {WorkPackageChangeset} from '../wp-edit-form/work-package-changeset';
import {WorkPackageEditingService} from '../wp-edit-form/work-package-editing-service';
import {WorkPackageFilterValues} from '../wp-edit-form/work-package-filter-values';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
export class WorkPackageInlineCreateController {
@ -74,6 +75,7 @@ export class WorkPackageInlineCreateController {
public wpCreate:WorkPackageCreateService,
public wpTableColumns:WorkPackageTableColumnsService,
private wpTableFilters:WorkPackageTableFiltersService,
private wpTableFocus:WorkPackageTableFocusService,
private AuthorisationService:any,
private $q:ng.IQService,
private I18n:op.I18n) {
@ -96,7 +98,7 @@ export class WorkPackageInlineCreateController {
this.addWorkPackageRow();
// Focus on the last inserted id
this.states.focusedWorkPackage.putValue(wp.id, 'Added in inline create');
this.wpTableFocus.updateFocus(wp.id);
} else {
// Remove current row
this.table.editing.stopEditing('new');

@ -76,9 +76,9 @@ describe 'Select work package row', type: :feature, js:true, selenium: true do
element = find(".work-package-table--container tr:nth-of-type(#{number}) .wp-table--cell-td.id")
loading_indicator_saveguard
page.driver.browser.action.key_down(:control)
page.driver.browser.action.key_down(:meta)
.click(element.native)
.key_up(:control)
.key_up(:meta)
.perform
end

Loading…
Cancel
Save