blob: eba35a2bd4c00f5dddc608b1d67039696e327d3a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {DomUtils} from "./tobago-utils";
import {Listener, Phase} from "./tobago-listener";
import {Page} from "./tobago-page";
export class Focus {
private static getHidden(): HTMLInputElement {
return document.getElementById(Page.page().id + DomUtils.SUB_COMPONENT_SEP + "lastFocusId") as HTMLInputElement;
}
static setLastFocusId(id: string): void {
this.getHidden().value = id;
}
static getLastFocusId(): string {
return this.getHidden().value;
}
/**
* Sets the focus to the requested element or to the first possible if
* no element is explicitly requested.
*
* The priority order is:
* - error (the first error element gets the focus)
* - auto (the element with the tobago tag attribute focus="true" gets the focus)
* - last (the element from the last request with same id gets the focus, not AJAX)
* - first (the first input element (without tabindex=-1) gets the focus, not AJAX)
*/
static init = function (element: HTMLElement): void {
const activeInputs = Focus.activeInputs(element);
for (const focusable of activeInputs) {
focusable.addEventListener("focus", function (event: FocusEvent): void {
const target = event.target as HTMLElement;
if (target.style.visibility !== "hidden" && target.style.display != "none") {
// remember the last focused element, for later
Focus.setLastFocusId(target.id);
}
});
}
// autofocus in popups doesn't work automatically... so we fix that here
const modals = document.getElementsByClassName("modal");
for (let i = 0; i < modals.length; i++) {
modals.item(i).addEventListener("shown.bs.modal", Focus.initAutoFocus);
}
for (const hasDanger of DomUtils.selfOrElementsByClassName(element, ".has-danger")) {
const activeInputsInsideDanger = Focus.activeInputs(hasDanger);
if (activeInputsInsideDanger.length > 0) {
activeInputsInsideDanger[0].focus();
return;
}
}
const autoFocus = element.querySelector("[autofocus]");
if (autoFocus) {
// nothing to do, because the browser make the work.
return;
}
if (element.parentElement) {
// seems to be AJAX, so end here
return;
}
const lastFocusId = Focus.getLastFocusId();
if (lastFocusId) {
const element = document.getElementById(lastFocusId);
if (element) {
element.focus();
}
return;
}
if (activeInputs.length > 0) {
activeInputs[0].focus();
return;
}
};
private static activeInputs(element: HTMLElement): Array<HTMLElement> {
return DomUtils.selfOrQuerySelectorAll(element,
"input:not([type='hidden']):not([disabled]):not([tabindex='-1'])," +
"select:not([disabled]):not([tabindex='-1'])," +
"textarea:not([disabled]):not([tabindex='-1'])")
.filter((element) =>
element.style.visibility !== "hidden"
&& element.style.display != "none");
}
private static initAutoFocus(event): void {
const modal = event.currentTarget as HTMLElement;
(modal.querySelector("[autofocus]") as HTMLElement).focus();
}
}
Listener.register(Focus.init, Phase.DOCUMENT_READY);
Listener.register(Focus.init, Phase.AFTER_UPDATE);