Upgrade FocusHelper

pull/6284/head
Oliver Günther 7 years ago
parent c71cc1251a
commit d88dbdb1f7
No known key found for this signature in database
GPG Key ID: 88872239EB414F99
  1. 9
      frontend/app/angular4-modules.ts
  2. 2
      frontend/app/angular4-transition-utils.ts
  3. 110
      frontend/app/components/common/focus/focus-helper.ts
  4. 5
      frontend/app/components/op-context-menu/op-context-menu.service.ts
  5. 5
      frontend/app/components/op-modals/op-modal.service.ts
  6. 5
      frontend/app/components/wp-edit-form/single-view-edit-context.ts
  7. 5
      frontend/app/components/wp-edit-form/table-row-edit-context.ts
  8. 5
      frontend/app/components/wp-edit-form/work-package-edit-field-handler.ts
  9. 4
      frontend/app/components/wp-fast-table/handlers/state/selection-transformer.ts
  10. 5
      frontend/app/components/wp-inline-create/wp-inline-create.component.ts
  11. 2
      frontend/app/components/wp-query-select/wp-query-select-dropdown.component.ts
  12. 4
      frontend/app/components/wp-table/external-configuration/external-query-configuration.service.ts
  13. 26
      frontend/app/ui_components/focus-directive.js
  14. 131
      frontend/app/ui_components/focus-helper.js
  15. 3
      frontend/app/ui_components/index.js

@ -26,7 +26,7 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {APP_INITIALIZER, NgModule} from '@angular/core';
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {UpgradeModule} from '@angular/upgrade/static';
import {FormsModule} from '@angular/forms';
@ -69,12 +69,11 @@ import {
$stateToken,
$timeoutToken,
AutoCompleteHelperServiceToken,
FocusHelperToken,
HookServiceToken,
I18nToken,
TextileServiceToken,
upgradeService,
upgradeServiceWithToken,
upgradeServiceWithToken, WorkPackageServiceToken,
wpDestroyModalToken,
wpMoreMenuServiceToken
} from './angular4-transition-utils';
@ -220,6 +219,7 @@ import {QuerySharingModal} from "core-components/modals/share-modal/query-sharin
import {SaveQueryModal} from "core-components/modals/save-modal/save-query.modal";
import {QuerySharingForm} from "core-components/modals/share-modal/query-sharing-form.component";
import {RenameQueryModal} from "core-components/modals/rename-query-modal/rename-query.modal";
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
@NgModule({
imports: [
@ -243,9 +243,10 @@ import {RenameQueryModal} from "core-components/modals/rename-query-modal/rename
upgradeServiceWithToken('$timeout', $timeoutToken),
upgradeServiceWithToken('$locale', $localeToken),
upgradeServiceWithToken('textileService', TextileServiceToken),
upgradeServiceWithToken('WorkPackageService', WorkPackageServiceToken),
upgradeServiceWithToken('AutoCompleteHelper', AutoCompleteHelperServiceToken),
NotificationsService,
upgradeServiceWithToken('FocusHelper', FocusHelperToken),
FocusHelperService,
PathHelperService,
upgradeServiceWithToken('wpMoreMenuService', wpMoreMenuServiceToken),
TimezoneService,

@ -41,13 +41,13 @@ export const I18nToken = new InjectionToken<op.I18n>('I18n');
export const AutoCompleteHelperServiceToken = new InjectionToken<any>('AutoCompleteHelperServiceToken');
export const TextileServiceToken = new InjectionToken<any>('TextileServiceToken');
export const FocusHelperToken = new InjectionToken<any>('FocusHelper');
export const wpMoreMenuServiceToken = new InjectionToken<any>('wpMoreMenuService');
export const $httpToken = new InjectionToken<any>('$http');
export const wpDestroyModalToken = new InjectionToken<any>('wpDestroyModal');
export const OpContextMenuLocalsToken = new InjectionToken<any>('CONTEXT_MENU_LOCALS');
export const OpModalLocalsToken = new InjectionToken<any>('OP_MODAL_LOCALS');
export const HookServiceToken = new InjectionToken<any>('HookService');
export const WorkPackageServiceToken = new InjectionToken<any>('WorkPackageService');
export function upgradeService(ng1InjectorName:string, providedType:any) {
return {

@ -0,0 +1,110 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
//++
import {opServicesModule} from 'core-app/angular-modules';
import {downgradeInjectable} from '@angular/upgrade/static';
import {Injectable} from '@angular/core';
@Injectable()
export class FocusHelperService {
private minimumOffsetForNewSwitchInMs = 100;
private lastFocusSwitch = -this.minimumOffsetForNewSwitchInMs;
private lastPriority = -1;
private static FOCUSABLE_SELECTORS = ['a, button, :input, [tabindex], select'];
public throttleAndCheckIfAllowedFocusChangeBasedOnTimeout() {
var allowFocusSwitch = (Date.now() - this.lastFocusSwitch) >= this.minimumOffsetForNewSwitchInMs;
// Always update so that a chain of focus-change-requests gets considered as one
this.lastFocusSwitch = Date.now();
return allowFocusSwitch;
}
public checkIfAllowedFocusChange(priority?:any) {
var checkTimeout = this.throttleAndCheckIfAllowedFocusChangeBasedOnTimeout();
if (checkTimeout) {
// new timeout window -> reset priority
this.lastPriority = -1;
} else {
// within timeout window
if (priority > this.lastPriority) {
this.lastPriority = priority;
return true;
}
}
return checkTimeout;
}
public getFocusableElement(element:JQuery) {
var focusser = element.find('input.ui-select-focusser');
if (focusser.length > 0) {
return focusser[0];
}
var focusable = element;
if (!element.is(FocusHelperService.FOCUSABLE_SELECTORS)) {
focusable = element.find(FocusHelperService.FOCUSABLE_SELECTORS);
}
return focusable[0];
}
public focus(element:JQuery) {
var focusable = angular.element(this.getFocusableElement(element)),
$focusable = angular.element(focusable),
isDisabled = $focusable.is('[disabled]');
if (isDisabled && !$focusable.attr('ng-disabled')) {
$focusable.prop('disabled', false);
}
focusable.focus();
if (isDisabled && !$focusable.attr('ng-disabled')) {
$focusable.prop('disabled', true);
}
}
public focusElement(element:JQuery, priority?:any) {
if (!this.checkIfAllowedFocusChange(priority)) {
return;
}
setTimeout(() => {
this.focus(element);
});
}
}
opServicesModule.service('FocusHelper', downgradeInjectable(FocusHelperService));

@ -5,10 +5,11 @@ import {
import {ComponentPortal, DomPortalOutlet, PortalInjector} from "@angular/cdk/portal";
import {TransitionService} from "@uirouter/core";
import {OpContextMenuHandler} from "core-components/op-context-menu/op-context-menu-handler";
import {FocusHelperToken, OpContextMenuLocalsToken} from "core-app/angular4-transition-utils";
import {OpContextMenuLocalsToken} from "core-app/angular4-transition-utils";
import {OpContextMenuLocalsMap} from "core-components/op-context-menu/op-context-menu.types";
import {OPContextMenuComponent} from "core-components/op-context-menu/op-context-menu.component";
import {keyCodes} from 'core-components/common/keyCodes.enum';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
@Injectable()
export class OPContextMenuService {
@ -23,7 +24,7 @@ export class OPContextMenuService {
private isOpening = false;
constructor(private componentFactoryResolver:ComponentFactoryResolver,
@Inject(FocusHelperToken) readonly FocusHelper:any,
readonly FocusHelper:FocusHelperService,
private appRef:ApplicationRef,
private $transitions:TransitionService,
private injector:Injector) {

@ -7,11 +7,12 @@ import {
} from '@angular/core';
import {ComponentPortal, ComponentType, DomPortalOutlet, PortalInjector} from '@angular/cdk/portal';
import {TransitionService} from '@uirouter/core';
import {FocusHelperToken, OpModalLocalsToken} from 'core-app/angular4-transition-utils';
import {OpModalLocalsToken} from 'core-app/angular4-transition-utils';
import {OpModalComponent} from 'core-components/op-modals/op-modal.component';
import {keyCodes} from 'core-components/common/keyCodes.enum';
import {opServicesModule} from "core-app/angular-modules";
import {downgradeInjectable} from "@angular/upgrade/static";
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
@Injectable()
export class OpModalService {
@ -26,7 +27,7 @@ export class OpModalService {
private opening:boolean = false;
constructor(private componentFactoryResolver:ComponentFactoryResolver,
@Inject(FocusHelperToken) readonly FocusHelper:any,
readonly FocusHelper:FocusHelperService,
private appRef:ApplicationRef,
private $transitions:TransitionService,
private injector:Injector) {

@ -36,18 +36,19 @@ import {EditField} from '../wp-edit/wp-edit-field/wp-edit-field.module';
import {WorkPackageNotificationService} from '../wp-edit/wp-notification.service';
import {WorkPackageTableSelection} from '../wp-fast-table/state/wp-table-selection.service';
import {Injector} from '@angular/core';
import {$stateToken, FocusHelperToken} from 'core-app/angular4-transition-utils';
import {$stateToken} from 'core-app/angular4-transition-utils';
import {WorkPackageEditContext} from 'core-components/wp-edit-form/work-package-edit-context';
import {WorkPackageTableRefreshService} from 'core-components/wp-table/wp-table-refresh-request.service';
import {WorkPackageEditForm} from 'core-components/wp-edit-form/work-package-edit-form';
import {WorkPackageEditFieldHandler} from 'core-components/wp-edit-form/work-package-edit-field-handler';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
export class SingleViewEditContext implements WorkPackageEditContext {
// Injections
public wpTableRefresh:WorkPackageTableRefreshService = this.injector.get(WorkPackageTableRefreshService);
public states:States = this.injector.get(States);
public FocusHelper:any = this.injector.get(FocusHelperToken);
public FocusHelper:FocusHelperService = this.injector.get(FocusHelperService);
public templateRenderer:SimpleTemplateRenderer = this.injector.get(SimpleTemplateRenderer);
public $state:StateService = this.injector.get($stateToken);
public wpNotificationsService:WorkPackageNotificationService = this.injector.get(WorkPackageNotificationService);

@ -27,7 +27,7 @@
// ++
import {Injector} from '@angular/core';
import {$qToken, $timeoutToken, FocusHelperToken} from 'core-app/angular4-transition-utils';
import {$qToken, $timeoutToken} from 'core-app/angular4-transition-utils';
import {SimpleTemplateRenderer} from '../angular/simple-template-renderer';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {States} from '../states.service';
@ -38,6 +38,7 @@ import {WorkPackageTableRefreshService} from '../wp-table/wp-table-refresh-reque
import {WorkPackageEditContext} from './work-package-edit-context';
import {WorkPackageEditFieldHandler} from './work-package-edit-field-handler';
import {WorkPackageEditForm} from './work-package-edit-form';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
export class TableRowEditContext implements WorkPackageEditContext {
@ -46,7 +47,7 @@ export class TableRowEditContext implements WorkPackageEditContext {
public wpTableRefresh:WorkPackageTableRefreshService = this.injector.get(WorkPackageTableRefreshService);
public wpTableColumns:WorkPackageTableColumnsService = this.injector.get(WorkPackageTableColumnsService);
public states:States = this.injector.get(States);
public FocusHelper:any = this.injector.get(FocusHelperToken);
public FocusHelper:FocusHelperService = this.injector.get(FocusHelperService);
public $q:ng.IQService = this.injector.get($qToken);
public $timeout:ng.ITimeoutService = this.injector.get($timeoutToken);

@ -30,13 +30,14 @@ import {WorkPackageEditForm} from './work-package-edit-form';
import {EditField} from '../wp-edit/wp-edit-field/wp-edit-field.module';
import {WorkPackageEditContext} from './work-package-edit-context';
import {keyCodes} from '../common/keyCodes.enum';
import {FocusHelperToken, I18nToken} from 'core-app/angular4-transition-utils';
import {I18nToken} from 'core-app/angular4-transition-utils';
import {ConfigurationService} from 'core-components/common/config/configuration.service';
import {Injector} from '@angular/core';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
export class WorkPackageEditFieldHandler {
// Injections
readonly FocusHelper = this.injector.get(FocusHelperToken)
readonly FocusHelper:FocusHelperService = this.injector.get(FocusHelperService)
readonly ConfigurationService = this.injector.get(ConfigurationService);
readonly I18n:op.I18n = this.injector.get(I18nToken);

@ -1,5 +1,4 @@
import {Injector} from '@angular/core';
import {FocusHelperToken} from 'core-app/angular4-transition-utils';
import {WorkPackageTableFocusService} from 'core-components/wp-fast-table/state/wp-table-focus.service';
import {takeUntil} from 'rxjs/operators';
import {tableRowClassName} from '../../builders/rows/single-row-builder';
@ -10,13 +9,14 @@ import {WorkPackageTable} from '../../wp-fast-table';
import {WPTableRowSelectionState} from '../../wp-table.interfaces';
import {OPContextMenuService} from "core-components/op-context-menu/op-context-menu.service";
import {TableState} from 'core-components/wp-table/table-state/table-state';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
export class SelectionTransformer {
public wpTableSelection:WorkPackageTableSelection = this.injector.get(WorkPackageTableSelection);
public wpTableFocus:WorkPackageTableFocusService = this.injector.get(WorkPackageTableFocusService);
public tableState:TableState = this.injector.get(TableState);
public FocusHelper:any = this.injector.get(FocusHelperToken);
public FocusHelper:FocusHelperService = this.injector.get(FocusHelperService);
public opContextMenu:OPContextMenuService = this.injector.get(OPContextMenuService);
constructor(public readonly injector:Injector,

@ -59,7 +59,8 @@ import {
} from './inline-create-row-builder';
import {TableState} from 'core-components/wp-table/table-state/table-state';
import {componentDestroyed} from 'ng2-rx-componentdestroyed';
import {FocusHelperToken, I18nToken} from 'core-app/angular4-transition-utils';
import {I18nToken} from 'core-app/angular4-transition-utils';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
@Component({
selector: '[wpInlineCreate]',
@ -92,7 +93,7 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
constructor(readonly elementRef:ElementRef,
readonly injector:Injector,
@Inject(FocusHelperToken) readonly FocusHelper:any,
readonly FocusHelper:FocusHelperService,
@Inject(I18nToken) readonly I18n:op.I18n,
readonly tableState:TableState,
readonly wpCacheService:WorkPackageCacheService,

@ -34,7 +34,7 @@ import {WorkPackagesListChecksumService} from '../wp-list/wp-list-checksum.servi
import {StateService} from '@uirouter/core';
import {Component, Inject, OnInit} from "@angular/core";
import {
$stateToken, FocusHelperToken, I18nToken,
$stateToken, I18nToken,
OpContextMenuLocalsToken
} from "core-app/angular4-transition-utils";
import {OpContextMenuLocalsMap} from "core-components/op-context-menu/op-context-menu.types";

@ -1,11 +1,11 @@
import {ApplicationRef, ComponentFactoryResolver, Inject, Injectable, InjectionToken, Injector} from '@angular/core';
import {ComponentPortal, DomPortalOutlet, PortalInjector} from '@angular/cdk/portal';
import {TransitionService} from '@uirouter/core';
import {FocusHelperToken} from 'core-app/angular4-transition-utils';
import {OpModalComponent} from 'core-components/op-modals/op-modal.component';
import {ExternalQueryConfigurationComponent} from 'core-components/wp-table/external-configuration/external-query-configuration.component';
import {downgradeInjectable} from '@angular/upgrade/static';
import {opServicesModule} from 'core-app/angular-modules';
import {FocusHelperService} from 'core-components/common/focus/focus-helper';
export const external_table_trigger_class = 'external-table-configuration--container';
export const OpQueryConfigurationLocals = new InjectionToken<any>('OpQueryConfigurationLocals');
@ -20,7 +20,7 @@ export class ExternalQueryConfigurationService {
private _bodyPortalHost:DomPortalOutlet;
constructor(private componentFactoryResolver:ComponentFactoryResolver,
@Inject(FocusHelperToken) readonly FocusHelper:any,
readonly FocusHelper:FocusHelperService,
private appRef:ApplicationRef,
private $transitions:TransitionService,
private injector:Injector) {

@ -38,28 +38,20 @@ module.exports = function(FocusHelper) {
}
function updateFocus(scope, element, attrs) {
if (element.hasClass('ui-select-multiple')) {
return element.find('input.ui-select-search').focus();
}
var condition = (attrs.focus) ? scope.$eval(attrs.focus) : true;
if (condition) {
if (isSelect2Element(attrs)) {
FocusHelper.focusSelect2Element(element);
} else {
var prio = 0;
if (attrs.focusPriority) {
prio = scope.$eval(attrs.focusPriority);
// Special case: Treat 'true' as 1 for convenience
if (prio === true) {
prio = 1;
} else {
prio = Number.parseInt(prio);
}
var prio = 0;
if (attrs.focusPriority) {
prio = scope.$eval(attrs.focusPriority);
// Special case: Treat 'true' as 1 for convenience
if (prio === true) {
prio = 1;
} else {
prio = Number.parseInt(prio);
}
FocusHelper.focusElement(element, prio);
}
FocusHelper.focusElement(element, prio);
}
}

@ -1,131 +0,0 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
//++
// TODO move to UI components
module.exports = function ($timeout, FOCUSABLE_SELECTOR) {
var minimumOffsetForNewSwitchInMs = 100;
var lastFocusSwitch = -minimumOffsetForNewSwitchInMs;
var lastPriority = -1;
function throttleAndCheckIfAllowedFocusChangeBasedOnTimeout() {
var allowFocusSwitch = (Date.now() - lastFocusSwitch) >= minimumOffsetForNewSwitchInMs;
// Always update so that a chain of focus-change-requests gets considered as one
lastFocusSwitch = Date.now();
return allowFocusSwitch;
}
function checkIfAllowedFocusChange(priority) {
var checkTimeout = throttleAndCheckIfAllowedFocusChangeBasedOnTimeout();
if (checkTimeout) {
// new timeout window -> reset priority
lastPriority = -1;
} else {
// within timeout window
if (priority > lastPriority) {
lastPriority = priority;
return true;
}
}
return checkTimeout;
}
var FocusHelper = {
getFocusableElement: function (element) {
var focusser = element.find('input.ui-select-focusser');
if (focusser.length > 0) {
return focusser[0];
}
var focusable = element;
if (!element.is(FOCUSABLE_SELECTOR)) {
focusable = element.find(FOCUSABLE_SELECTOR);
}
return focusable[0];
},
focus: function (element) {
var focusable = angular.element(FocusHelper.getFocusableElement(element)),
$focusable = angular.element(focusable),
isDisabled = $focusable.is('[disabled]');
if (isDisabled && !$focusable.attr('ng-disabled')) {
$focusable.prop('disabled', false);
}
focusable.focus();
if (isDisabled && !$focusable.attr('ng-disabled')) {
$focusable.prop('disabled', true);
}
},
focusElement: function (element, priority) {
if (!checkIfAllowedFocusChange(priority)) {
return;
}
$timeout(function () {
FocusHelper.focus(element);
});
},
focusUiSelect: function (element) {
$timeout(function () {
element.find('.ui-select-match').trigger('click');
});
},
// TODO: remove when select2 is not used
focusSelect2Element: function (element) {
var focusSelect2ElementRecursiv = function (retries) {
$timeout(function () {
element.select2('focus');
var isSelect2Focused = angular.element(document.activeElement).hasClass('select2-input');
if (!isSelect2Focused && retries > 0) {
focusSelect2ElementRecursiv(--retries);
}
});
};
focusSelect2ElementRecursiv(3);
},
};
return FocusHelper;
};

@ -40,9 +40,6 @@ angular.module('openproject.uiComponents')
'FocusHelper',
require('./focus-directive')
])
.constant('FOCUSABLE_SELECTOR', 'a, button, :input, [tabindex], select')
.service('FocusHelper', ['$timeout', 'FOCUSABLE_SELECTOR', require(
'./focus-helper')])
.service('I18n', [require('./i18n')])
.directive('persistentToggle', [
'$timeout',

Loading…
Cancel
Save