coveo-search-ui
Version:
Coveo JavaScript Search Framework
180 lines (152 loc) • 6.21 kB
text/typescript
import { ResponsiveDropdownHeader } from './ResponsiveDropdownHeader';
import { IResponsiveDropdownContent } from './ResponsiveDropdownContent';
import { $$, Dom } from '../../../utils/Dom';
import { EventsUtils } from '../../../utils/EventsUtils';
import * as _ from 'underscore';
import { AccessibleButton } from '../../../utils/AccessibleButton';
import { KeyboardUtils, KEYBOARD } from '../../../utils/KeyboardUtils';
import { InitializationEvents } from '../../../events/InitializationEvents';
import { Assert } from '../../../misc/Assert';
import { l } from '../../../strings/Strings';
export enum ResponsiveDropdownEvent {
OPEN = 'responsiveDropdownOpen',
CLOSE = 'responsiveDropdownClose'
}
type HandlerCall = { handler: Function; context: any };
export class ResponsiveDropdown {
public static TRANSPARENT_BACKGROUND_OPACITY: string = '0.9';
public static DROPDOWN_BACKGROUND_CSS_CLASS_NAME: string = 'coveo-dropdown-background';
public static DROPDOWN_BACKGROUND_ACTIVE_CSS_CLASS_NAME: string = 'coveo-dropdown-background-active';
public isOpened: boolean = false;
private onOpenHandlers: HandlerCall[] = [];
private onCloseHandlers: HandlerCall[] = [];
private popupBackground: Dom;
private popupBackgroundIsEnabled: boolean = true;
private scrollableContainerToLock: HTMLElement = null;
private previousSibling: Dom;
private parent: Dom;
constructor(public dropdownContent: IResponsiveDropdownContent, public dropdownHeader: ResponsiveDropdownHeader, public coveoRoot: Dom) {
Assert.exists(dropdownContent);
Assert.exists(dropdownHeader);
Assert.exists(coveoRoot);
this.popupBackground = this.buildPopupBackground();
this.bindOnClickDropdownHeaderEvent();
this.saveContentPosition();
this.bindOnKeyboardEscapeEvent();
this.bindNukeEvents();
}
private set scrollLocked(lock: boolean) {
if (!this.scrollableContainerToLock) {
return;
}
this.scrollableContainerToLock.style.overflow = lock ? 'hidden' : '';
}
public registerOnOpenHandler(handler: Function, context) {
Assert.exists(handler);
this.onOpenHandlers.push({ handler: handler, context: context });
}
public registerOnCloseHandler(handler: Function, context) {
Assert.exists(handler);
this.onCloseHandlers.push({ handler: handler, context: context });
}
public cleanUp() {
this.close();
this.dropdownHeader.cleanUp();
this.dropdownContent.cleanUp();
this.restoreContentPosition();
}
public open() {
this.isOpened = true;
this.dropdownHeader.open();
this.dropdownContent.positionDropdown();
_.each(this.onOpenHandlers, handlerCall => {
handlerCall.handler.apply(handlerCall.context);
});
this.showPopupBackground();
this.lockScroll();
$$(this.dropdownHeader.element).trigger(ResponsiveDropdownEvent.OPEN);
}
public close() {
this.isOpened = false;
_.each(this.onCloseHandlers, handlerCall => {
handlerCall.handler.apply(handlerCall.context);
});
this.dropdownHeader.close();
this.dropdownContent.hideDropdown();
this.hidePopupBackground();
this.unlockScroll();
$$(this.dropdownHeader.element).trigger(ResponsiveDropdownEvent.CLOSE);
}
public disablePopupBackground() {
this.popupBackgroundIsEnabled = false;
}
public enableScrollLocking(scrollableContainer: HTMLElement) {
this.scrollableContainerToLock = scrollableContainer;
}
private bindOnClickDropdownHeaderEvent() {
new AccessibleButton()
.withElement(this.dropdownHeader.element)
.withSelectAction(() => (this.isOpened ? this.close() : this.open()))
.withLabel(l(this.isOpened ? 'CloseFiltersDropdown' : 'OpenFiltersDropdown'))
.build();
}
private closeIfOpened = () => {
this.isOpened && this.close();
};
private bindOnKeyboardEscapeEvent() {
$$(document.documentElement).on('keyup', KeyboardUtils.keypressAction(KEYBOARD.ESCAPE, this.closeIfOpened));
}
private unbindOnKeyboardEscapeEvent() {
$$(document.documentElement).off('keyup', KeyboardUtils.keypressAction(KEYBOARD.ESCAPE, this.closeIfOpened));
}
private bindNukeEvents() {
$$(this.coveoRoot).on(InitializationEvents.nuke, () => {
this.unbindOnKeyboardEscapeEvent();
});
}
private showPopupBackground() {
if (this.popupBackgroundIsEnabled) {
this.coveoRoot.el.appendChild(this.popupBackground.el);
window.getComputedStyle(this.popupBackground.el).opacity;
this.popupBackground.el.style.opacity = ResponsiveDropdown.TRANSPARENT_BACKGROUND_OPACITY;
this.popupBackground.addClass(ResponsiveDropdown.DROPDOWN_BACKGROUND_ACTIVE_CSS_CLASS_NAME);
}
}
private lockScroll() {
this.scrollLocked = true;
}
private unlockScroll() {
this.scrollLocked = false;
}
private hidePopupBackground() {
if (this.popupBackgroundIsEnabled) {
// forces the browser to reflow the element, so that the transition is applied.
window.getComputedStyle(this.popupBackground.el).opacity;
this.popupBackground.el.style.opacity = '0';
this.popupBackground.removeClass(ResponsiveDropdown.DROPDOWN_BACKGROUND_ACTIVE_CSS_CLASS_NAME);
}
}
private buildPopupBackground(): Dom {
let popupBackground = $$('div', { className: ResponsiveDropdown.DROPDOWN_BACKGROUND_CSS_CLASS_NAME });
EventsUtils.addPrefixedEvent(popupBackground.el, 'TransitionEnd', () => {
if (popupBackground.el.style.opacity == '0') {
popupBackground.detach();
}
});
popupBackground.on('click', () => this.close());
return popupBackground;
}
private saveContentPosition() {
let dropdownContentPreviousSibling = this.dropdownContent.element.el.previousSibling;
let dropdownContentParent = this.dropdownContent.element.el.parentElement;
this.previousSibling = dropdownContentPreviousSibling ? $$(<HTMLElement>dropdownContentPreviousSibling) : null;
this.parent = $$(dropdownContentParent);
}
private restoreContentPosition() {
if (this.previousSibling) {
this.dropdownContent.element.insertAfter(this.previousSibling.el);
} else {
this.parent.prepend(this.dropdownContent.element.el);
}
}
}