UNPKG

outsystems-ui

Version:
1,025 lines (920 loc) 36.4 kB
// eslint-disable-next-line @typescript-eslint/no-unused-vars namespace OSFramework.OSUI.Patterns.Dropdown.ServerSide { // eslint-disable-next-line @typescript-eslint/naming-convention export class OSUIDropdownServerSide extends Patterns.AbstractParent< OSUIDropdownServerSideConfig, Patterns.DropdownServerSideItem.IDropdownServerSideItem > implements IDropdownServerSide { // Store the HTML element for the DropdownBalloonContainer private _balloonContainerElement: HTMLElement; // Store the Balloon Element private _balloonElem: HTMLElement; // Store the Balloon Class instance private _balloonFeature: Feature.Balloon.IBalloon; // Store all the focusable elements inside footer if it's the case! private _balloonFocusableElemsInFooter: HTMLElement[]; // Store the HTML element for the DropdownBalloonFooter private _balloonFooterElement: HTMLElement; // Store the BalloonOptions container AriaLabel text private _balloonOptionsAriaLabel: string; // Store the HTML element for the Dropdown otpions private _balloonOptionsWrapperElement: HTMLElement; // Store the HTML element for the Search input at Dropdown Balloon private _balloonSearchInputElement: HTMLElement; // Store the HTML element for the DropdownBalloonSearch private _balloonSearchInputWrapperElement: HTMLElement; // Store a Flag property that will help dealing with the focus state at the close moment private _closeDynamically = false; // Custom Balloon Event private _eventBalloonOnToggle: GlobalCallbacks.Generic; // Click On Body private _eventOnBodyClick: GlobalCallbacks.Generic; // Click Event private _eventOnClick: GlobalCallbacks.Generic; private _eventOnClickInputSearch: GlobalCallbacks.Generic; // OnSearchInputBlur Event private _eventOnSearchInputBlur: GlobalCallbacks.Generic; // OnSearchInputFocus Event private _eventOnSearchInputFocus: GlobalCallbacks.Generic; // OnTouchMove Event at the balloon wrapper private _eventOnTouchMove: GlobalCallbacks.Generic; // Keyboard Key Press Event private _eventOnkeyboardPress: GlobalCallbacks.Generic; // Store the Element State, by default is closed! private _isOpen = false; // Platform OnClose Callback private _platformEventOnToggleCallback: GlobalCallbacks.OSGeneric; // Store the HTML element for the Dropdown Select Wrapper private _selectValuesWrapper: HTMLElement; // Store the SelectValuesWrapper AriaLabel text private _selectValuesWrapperAriaLabel: string; // Store the Balloon options to pass to the Balloon Class public balloonOptions: Feature.Balloon.BalloonOptions; constructor(uniqueId: string, configs: JSON) { super(uniqueId, new OSUIDropdownServerSideConfig(configs)); } // Add error message container with a given text private _addErrorMessage(text: string): void { const errorMessageElement = Helper.Dom.ClassSelector( this.selfElement.parentElement, Enum.CssClass.ErrorMessage ); // Check if the element already exist! if (errorMessageElement === undefined) { // Create the wrapper container const textContainer = document.createElement(GlobalEnum.HTMLElement.Div); textContainer.classList.add(Enum.CssClass.ErrorMessage); textContainer.innerHTML = text; this.selfElement.parentElement.appendChild(textContainer); } } // Method to handle the custom BalloonOnToggle callback private _balloonOnToggleCallback(_args: string, e: CustomEvent): void { // If the balloon closed is the one from this pattern, toggle the isOpen if (e.detail.balloonElem === this._balloonElem && e.detail.isOpen !== this._isOpen) { this._close(true); } } // Close the Balloon private _close(isFromBalloonEvent = false): void { if (this._isOpen === false) { return; } if (!isFromBalloonEvent) { this._balloonFeature.close(); } // Update status property this._isOpen = false; // Update the touchMove when pattern is open! this._touchMove(); // Update pattern status! this._updatePatternState(); // Remove event listeners this._unsetEvents(true); } // Method that will return HasNoImplementation Error Info private _hasNoImplementation(): string { throw new Error( `${ErrorCodes.Dropdown.HasNoImplementation.code}: ${ErrorCodes.Dropdown.HasNoImplementation.message}` ); } // Method to move Balloon Options Wrapper to outside of the pattern context private _moveBalloonOptionsWrapper(): void { // Check if BalloonOptions should be moved outside of the pattern context if (this._shouldBalloonOptionsBeMoved()) { // Get the content element where to move the BalloonOptionsWrapper const contentElem = Helper.Dom.ClassSelector(document, GlobalEnum.CssClassElements.Content); // Move the DropdownServerSide ballon element to the content element Helper.Dom.Move(this._balloonElem, contentElem); // Add a custom css selector in order to style it at this new context OSFramework.OSUI.Helper.Dom.Styles.AddClass(this._balloonElem, Enum.CssClass.HasBeenMovedToContent); } } // Close when click outside of pattern private _onBodyClick(eventName: string, event: PointerEvent): void { if (this._isOpen === false) { return; } this._closeDynamically = true; // Check if balloon can be closed // If it's phone, always close, as it is on popup mode // Also prevent closing when clicking at an element inside search icon placeholder const canClose = Helper.DeviceInfo.IsPhone && (event.target as HTMLElement).closest(`.${Enum.CssClass.BalloonSearchIcon}`) === null; if (canClose) { this._close(); } } // A11y keyboard keys private _onKeyboardPressed(event: KeyboardEvent): void { event.stopPropagation(); // Check which element has been key pressed! switch (event.target) { // Check if the SelectValuesWrapper container has been pressed! case this._selectValuesWrapper: // If Escape Key if (event.key === GlobalEnum.Keycodes.Escape) { this._close(); } // If Enter or Space Keys and Single Option if ( event.key === GlobalEnum.Keycodes.Enter || event.key === GlobalEnum.Keycodes.Space || event.key === GlobalEnum.Keycodes.ArrowUp || event.key === GlobalEnum.Keycodes.ArrowDown || event.key === GlobalEnum.Keycodes.Home ) { // Trigger the click in order to be captured also by DOM (body onClick) in order to close other Dropdowns in page if they are open! this._selectValuesWrapper.click(); } break; // Check if the Balloon Options container has been pressed! case this._balloonOptionsWrapperElement: // If Escape Key if (event.key === GlobalEnum.Keycodes.Escape) { this._close(); } // If ArrowDown Key if (event.key === GlobalEnum.Keycodes.ArrowUp) { // Check if search input exist if (this._balloonSearchInputElement) { this._balloonSearchInputElement.focus(); } } else if (event.key === GlobalEnum.Keycodes.ArrowDown) { // If ArrowDown Key // Focus the first option item! event.preventDefault(); this.getChildByIndex(0).setFocus(); // Check if Dropdown should only allow single option selected if (this.configs.AllowMultipleSelection === false) { // Set also the first option item as IsSelected! this.getChildByIndex(0).toggleSelected(); } } break; // Check if the input Search has been pressed! case this._balloonSearchInputElement: // If Escape Key if (event.key === GlobalEnum.Keycodes.Escape) { this._close(); } break; } } // Also Used to manage the balloon height accordingly keyboard is in use due to the way iOS deal with it! private _onSearchInputBlur(): void { Helper.Dom.Styles.RemoveClass(this._balloonElem, Enum.CssClass.SearchInputIsFocused); } // Used to set a stopPropagation when click at search input private _onSearchInputClicked(event: MouseEvent): void { event.stopPropagation(); } // Used to manage the balloon height accordingly keyboard is in use due to the way iOS deal with it! private _onSearchInputFocus(): void { Helper.Dom.Styles.AddClass(this._balloonElem, Enum.CssClass.SearchInputIsFocused); } // Used to apply the logic when user click to open the Dropdown private _onSelectValuesWrapperClicked(): void { // Ensure that dropdown can open or close this._isOpen ? this._close() : this._open(); } // Manage the OnTouchMove action private _onTouchMove(event: TouchEvent): void { if (event.target === this._balloonElem) { event.preventDefault(); } } // Open the Balloon private _open(): void { if (this._isOpen) { return; } this._balloonFeature.open(true); this._closeDynamically = false; this._isOpen = true; // Update the touchMove when pattern is open! this._touchMove(); // Add the isVisible class to body Helper.Dom.Styles.AddClass(document.body, Enum.CssClass.IsVisible); this._updatePatternState(); // Add event listeners Helper.AsyncInvocation(this._setUpEvents.bind(this)); } // Method to deal with the click at a DropdpownOptionItem private _optionItemHasBeenClicked(optionItemId: string): void { const clickedItem = this.getChild(optionItemId); // Check if the given OptionId exist at optionsList if (clickedItem) { // Check if Dropdown should only allow single option selected if (this.configs.AllowMultipleSelection) { // Udpate the Option Item selected State! clickedItem.toggleSelected(); } // Check if Dropdown should only allow single option selected if (this.configs.AllowMultipleSelection === false) { // Close the Dropdown! this._close(); if (clickedItem.IsSelected === false) { clickedItem.toggleSelected(); } } } else { throw new Error( `${ErrorCodes.Dropdown.FailOptionItemClicked}: The ${GlobalEnum.PatternName.DropdownServerSideItem} under uniqueId: '${optionItemId}' does not exist as an OptionItem from ${GlobalEnum.PatternName.Dropdown} with Id: ${this.widgetId}.` ); } } // Method used to deal with the keyPressed navigation between DropdownOptionItems private _optionItemKeyPressed(optionItemId: string): void { // Get the optionItem reference based on the given Id const optionItem = this.getChild(optionItemId); // Check if the given OptionId exist at optionsList if (optionItem !== undefined) { // Get the option item index position const getOptionItemIndex = this.getChildIndex(optionItemId); // Ensure that code wont run if key was not defined! if (optionItem.keyboardTriggeredKey === undefined) { return; } // Check which Keyboard key has been pressed switch (optionItem.keyboardTriggeredKey) { // If Enter or Space Keys trigger as a click event! case GlobalEnum.Keycodes.Enter: case GlobalEnum.Keycodes.Space: // Act like option item has been clicked! this._optionItemHasBeenClicked(optionItemId); break; // ArrowUp case GlobalEnum.Keycodes.ArrowUp: // Check If focused item is not the first one! if (getOptionItemIndex > 0) { this._updateOptionItemFocuStateOnKeyPress(optionItem, getOptionItemIndex - 1); } break; // ArrowDown case GlobalEnum.Keycodes.ArrowDown: if (getOptionItemIndex < this.getChildItems().length - 1) { this._updateOptionItemFocuStateOnKeyPress(optionItem, getOptionItemIndex + 1); } break; // Shift + Tab case GlobalEnum.Keycodes.ShiftTab: // Check if search input exist if (this._balloonSearchInputElement) { this._balloonSearchInputElement.focus(); } break; // Tab case GlobalEnum.Keycodes.Tab: // Check if there are focusable elements inside footer if (this._balloonFocusableElemsInFooter.length > 0) { // Set focus the the first one this._balloonFocusableElemsInFooter[0].focus(); } break; // Escape case GlobalEnum.Keycodes.Escape: // Close Dropdown this._close(); break; } } else { throw new Error( `${ErrorCodes.Dropdown.FailOptionItemKeyPressed}: The ${GlobalEnum.PatternName.DropdownServerSideItem} under uniqueId: '${optionItemId}' does not exist as an OptionItem from ${GlobalEnum.PatternName.Dropdown} with Id: ${this.widgetId}.` ); } } // Method to call the Balloon Class private _setBalloonFeature(): void { this.setBalloonOptions(); this._balloonFeature = new OSFramework.OSUI.Feature.Balloon.Balloon<IDropdownServerSide>( this, this._balloonElem, this.balloonOptions ); // Call the method to move the Balloon Options Wrapper OSFramework.OSUI.Helper.AsyncInvocation(this._moveBalloonOptionsWrapper.bind(this)); } // Method used to add CSS classes to pattern elements private _setCssClasses(): void { // If search input exist add a class to the balloon if (this._balloonSearchInputElement === undefined) { // Needed to style the balloon height once at phone Helper.Dom.Styles.AddClass(this._balloonElem, Enum.CssClass.BalloonHasNotSearchInput); } } // Method to set the initial options on screen load private _setInitialOptions(): void { if (this.configs.IsDisabled) { this.disable(); } } // Method used to store a given DropdownOption into optionItems list, it's triggered by DropdownServerSideItem private _setNewOptionItem(optionItem: Patterns.DropdownServerSideItem.DropdownServerSideItem): void { // Check if the given OptionId has been already added if (this.getChild(optionItem.uniqueId)) { throw new Error( `${ErrorCodes.Dropdown.FailSetNewOptionItem}: There is already a ${GlobalEnum.PatternName.DropdownServerSideItem} under Id: '${optionItem.widgetId}' added to ${GlobalEnum.PatternName.Dropdown} with uniqueId: ${this.uniqueId}.` ); } else { // Store DropDownOption Child Item this.setChild(optionItem); } } // Set Pattern Events private _setUpEvents(): void { // Add OnClick Event to the SelectValuesWrapper this._selectValuesWrapper.addEventListener(GlobalEnum.HTMLEvent.Click, this._eventOnClick); // Add KeyDown Event to the SelectValuesWrapper (A11y - stuff) this._selectValuesWrapper.addEventListener(GlobalEnum.HTMLEvent.keyDown, this._eventOnkeyboardPress); if (this.isBuilt) { // Add KeyDown Event to the BalloonContent (OptionsWrapper) (A11y - stuff) this._balloonOptionsWrapperElement.addEventListener( GlobalEnum.HTMLEvent.keyDown, this._eventOnkeyboardPress ); // If search input exist (A11y - stuff) if (this._balloonSearchInputElement) { this._balloonSearchInputElement.addEventListener( GlobalEnum.HTMLEvent.Click, this._eventOnClickInputSearch ); // Add keyPress event in order to capture Escape key this._balloonSearchInputElement.addEventListener( GlobalEnum.HTMLEvent.keyDown, this._eventOnkeyboardPress ); // Add BlurEvent in order to manage the Balloon Height when at iOS due to keyboard is in use this._balloonSearchInputElement.addEventListener( GlobalEnum.HTMLEvent.Blur, this._eventOnSearchInputBlur ); // Add FocusEvent in order to manage the Balloon Height when at iOS due to keyboard is in use this._balloonSearchInputElement.addEventListener( GlobalEnum.HTMLEvent.Focus, this._eventOnSearchInputFocus ); } // Add the BodyClick callback that will be used Close open Dropdown! Event.DOMEvents.Listeners.GlobalListenerManager.Instance.addHandler( Event.DOMEvents.Listeners.Type.BodyOnClick, this._eventOnBodyClick ); Event.DOMEvents.Listeners.GlobalListenerManager.Instance.addHandler( Event.DOMEvents.Listeners.Type.BalloonOnToggle, this._eventBalloonOnToggle ); } } // Method that will check if the BalloonOptionsWrapper should be moved outside of the pattern context private _shouldBalloonOptionsBeMoved(): boolean { /* NOTE: - When inside BottomSheet the BalloonOptionsWrapper should be moved to the content wrapper due to position issues related with fixed position of the balloon against BottomSheet fixed position. More info at Release Note: ROU-11549 */ // Check if the DropdownServerSide is inside a BottomSheet, Notification, or Sidebar if ( Helper.DeviceInfo.IsPhone && (this.selfElement.closest(Enum.InsidePattern.BottomSheet) || this.selfElement.closest(Enum.InsidePattern.Notification) || this.selfElement.closest(Enum.InsidePattern.Sidebar)) ) { return true; } else { return false; } } // Method used to manage the onTouchMove when we're at mobile devices in order to block the window scroll! // This is an improvement specially to iOS since otherwise it will be able to scroll the pattern when keyboard is open! private _touchMove(): void { // Check if the used browser has TouchMove event and if it's an iOS device if (Helper.DeviceInfo.IsIos && 'ontouchmove' in window) { if (this._isOpen) { this._balloonElem.addEventListener(GlobalEnum.HTMLEvent.TouchMove, this._eventOnTouchMove); } else { this._balloonElem.removeEventListener(GlobalEnum.HTMLEvent.TouchMove, this._eventOnTouchMove); } } } // Mehod used to trigger the _platformEventOnToggleCallback callback! private _triggerToogleCalbackEvent(): void { this.triggerPlatformEventCallback(this._platformEventOnToggleCallback, this._isOpen); } // Remove Pattern Events private _unsetEvents(isUpdate = false): void { if (isUpdate === false) { this._selectValuesWrapper.removeEventListener(GlobalEnum.HTMLEvent.Click, this._eventOnClick); this._selectValuesWrapper.removeEventListener(GlobalEnum.HTMLEvent.keyDown, this._eventOnkeyboardPress); } this._balloonOptionsWrapperElement.removeEventListener( GlobalEnum.HTMLEvent.keyDown, this._eventOnkeyboardPress ); Event.DOMEvents.Listeners.GlobalListenerManager.Instance.addHandler( Event.DOMEvents.Listeners.Type.BalloonOnToggle, this._eventBalloonOnToggle ); Event.DOMEvents.Listeners.GlobalListenerManager.Instance.removeHandler( Event.DOMEvents.Listeners.Type.BodyOnClick, this._eventOnBodyClick ); if (this._balloonSearchInputElement) { this._balloonSearchInputElement.removeEventListener( GlobalEnum.HTMLEvent.Click, this._eventOnClickInputSearch ); this._balloonSearchInputElement.removeEventListener( GlobalEnum.HTMLEvent.keyDown, this._eventOnkeyboardPress ); this._balloonSearchInputElement.removeEventListener( GlobalEnum.HTMLEvent.Blur, this._eventOnSearchInputBlur ); this._balloonSearchInputElement.removeEventListener( GlobalEnum.HTMLEvent.Focus, this._eventOnSearchInputFocus ); } } // Method used to remove a given DropdownOption from optionItems list, it's triggered by DropdownServerSideItem private _unsetNewOptionItem(optionItemId: string): void { // Check if the given OptionId exist at optionsList if (this.getChild(optionItemId)) { // Remove item this.unsetChild(optionItemId); } else { throw new Error( `${ErrorCodes.Dropdown.FailUnsetNewOptionItem}: The ${GlobalEnum.PatternName.DropdownServerSideItem} under uniqueId: '${optionItemId}' does not exist as an OptionItem from ${GlobalEnum.PatternName.Dropdown} with Id: ${this.widgetId}.` ); } } // Method that will be used to set/unset the TabIndex to the DropdownBallon elements according it's opened/closed private _updateBalloonAccessibilityElements(): void { const tabIndexValue = this._isOpen ? Constants.A11YAttributes.States.TabIndexShow : Constants.A11YAttributes.States.TabIndexHidden; // If there is the Search input if (this._balloonSearchInputElement !== undefined) { Helper.A11Y.TabIndex(this._balloonSearchInputElement, tabIndexValue); Helper.A11Y.AriaHidden( this._balloonSearchInputElement, (tabIndexValue === Constants.A11YAttributes.States.TabIndexHidden).toString() ); } // If there are items inside Ballon Footer if (this._balloonFocusableElemsInFooter.length > 0) { // Will handle the tabindex value of the elements inside pattern Helper.A11Y.SetElementsTabIndex(this._isOpen, this._balloonFocusableElemsInFooter); } // Ballon Options Wrapper Helper.A11Y.TabIndex(this._balloonOptionsWrapperElement, tabIndexValue); // If there are items inside Ballon Footer if (this._balloonFocusableElemsInFooter.length > 0) { // Will handle the tabindex value of the elements inside pattern Helper.A11Y.SetElementsTabIndex(this._isOpen, this._balloonFocusableElemsInFooter); } // Update FocusHTML elements attributes if (this._isOpen) { // Ballon Options Wrapper Helper.A11Y.AriaHiddenFalse(this._balloonOptionsWrapperElement); } else { // Remove a11y selector in order to grant it will be updated each time Balloon gets open Helper.Dom.Styles.RemoveClass(this._balloonElem, Constants.HasAccessibilityClass); // Ballon Options Wrapper Helper.A11Y.AriaHiddenTrue(this._balloonOptionsWrapperElement); } } // Method to (un)set option item focus statue private _updateOptionItemFocuStateOnKeyPress( optionItem: Patterns.DropdownServerSideItem.IDropdownServerSideItem, itemIndex: number ): void { // Check if Dropdown should only allow single option selected! if (this.configs.AllowMultipleSelection === false) { // Unset IsSelected to the previous Item optionItem.toggleSelected(); // Set IsSelected to the next item this.getChildByIndex(itemIndex).toggleSelected(); } // Set Blur to the current one! optionItem.setBlur(); // Set Focus to the prev/next one! this.getChildByIndex(itemIndex).setFocus(); } // Method that will update the pattern state private _updatePatternState(): void { // Update the TabIndex for the items inside Balloon this._updateBalloonAccessibilityElements(); // If balloon will open if (this._isOpen) { // Add IsOpened Class! Helper.Dom.Styles.AddClass(this._balloonElem, Enum.CssClass.IsOpened); // Check if inputSearch exist if (this._balloonSearchInputElement) { this._balloonSearchInputElement.focus(); } else { this._balloonOptionsWrapperElement.focus(); } } else { // Remove IsOpened Class Helper.Dom.Styles.RemoveClass(this._balloonElem, Enum.CssClass.IsOpened); // Check if the close will be done by logic instead of user interaction if (this._closeDynamically === false) { // Set focus to the base element this._selectValuesWrapper.focus(); } } // Trigger the toggle callback event this._triggerToogleCalbackEvent(); } /** * Add the Accessibility Attributes values * * @protected * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ protected setA11YProperties(): void { // Update Tabindex Ballon elements this._updateBalloonAccessibilityElements(); // Enabled TabIndex to the SelectValuesWrapper Helper.A11Y.TabIndexTrue(this._selectValuesWrapper); // Set SelectValuesWrapper with button as a role Helper.A11Y.RoleButton(this._selectValuesWrapper); // Set SelectValuesWrapper with aria-haspopup='listbox' Helper.A11Y.AriaHasPopup(this._selectValuesWrapper, Constants.A11YAttributes.Aria.Haspopup.value.Listbox); // Set balloon option items container properties Helper.A11Y.RoleListbox(this._balloonOptionsWrapperElement); Helper.A11Y.TabIndexFalse(this._balloonOptionsWrapperElement); Helper.A11Y.AriaHiddenTrue(this._balloonOptionsWrapperElement); // Check if the Dropdown allow multiselect if (this.configs.AllowMultipleSelection) { // Set the aria-multiselectable attribute to the options wrapper element Helper.A11Y.MultiselectableTrue(this._balloonOptionsWrapperElement); } // Set Aria Label for the SelectValuesWrapper this.setSelectAriaLabel(); // Set Aria Label for the BalloonOptionsWrapper this.setBalloonOptionsAriaLabel(); } /** * Method to set the calbacks * * @protected * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ protected setCallbacks(): void { this._eventOnBodyClick = this._onBodyClick.bind(this); this._eventBalloonOnToggle = this._balloonOnToggleCallback.bind(this); this._eventOnClick = this._onSelectValuesWrapperClicked.bind(this); this._eventOnClickInputSearch = this._onSearchInputClicked.bind(this); this._eventOnkeyboardPress = this._onKeyboardPressed.bind(this); this._eventOnSearchInputBlur = this._onSearchInputBlur.bind(this); this._eventOnSearchInputFocus = this._onSearchInputFocus.bind(this); this._eventOnTouchMove = this._onTouchMove.bind(this); } /** * Method to set the html elements used * * @protected * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ protected setHtmlElements(): void { this._balloonFooterElement = Helper.Dom.ClassSelector(this.selfElement, Enum.CssClass.BalloonFooter); this._balloonFocusableElemsInFooter = Helper.Dom.TagSelectorAll( this._balloonFooterElement, Constants.FocusableElems ); this._balloonSearchInputWrapperElement = Helper.Dom.ClassSelector( this.selfElement, Enum.CssClass.BalloonSearch ); this._balloonSearchInputElement = Helper.Dom.TagSelector( this._balloonSearchInputWrapperElement, GlobalEnum.HTMLElement.Input ); this._balloonContainerElement = Helper.Dom.ClassSelector(this.selfElement, Enum.CssClass.BalloonContainer); this._balloonElem = Helper.Dom.ClassSelector(this.selfElement, Enum.CssClass.Balloon); this._balloonOptionsWrapperElement = Helper.Dom.ClassSelector( this._balloonElem, Enum.CssClass.BalloonContent ); this._selectValuesWrapper = Helper.Dom.ClassSelector(this.selfElement, Enum.CssClass.SelectValuesWrapper); } /** * Unset callbacks that has been assigned to the element * * @protected * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ protected unsetCallbacks(): void { this._eventOnBodyClick = undefined; this._eventBalloonOnToggle = undefined; this._eventOnClick = undefined; this._eventOnClickInputSearch = undefined; this._eventOnkeyboardPress = undefined; this._eventOnSearchInputBlur = undefined; this._eventOnSearchInputFocus = undefined; this._eventOnTouchMove = undefined; this._platformEventOnToggleCallback = undefined; } /** * Method to unset the html elements used * * @protected * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ protected unsetHtmlElements(): void { // unset the local properties this._balloonContainerElement = undefined; this._balloonFocusableElemsInFooter = []; this._balloonFooterElement = undefined; this._balloonOptionsWrapperElement = undefined; this._balloonSearchInputElement = undefined; this._balloonSearchInputWrapperElement = undefined; this._balloonElem = undefined; this._selectValuesWrapper = undefined; } /** * Method used to be notified by a given dropdownOptionId about a given action and act accordingly * * @param childId Dropdown Option Item Id to be stored * @param notifiedTo {Enum.ChildNotifyActionType} triggered notification type * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public beNotifiedByChild( childItem: Patterns.DropdownServerSideItem.DropdownServerSideItem, notifiedTo: Enum.ChildNotifyActionType ): void { switch (notifiedTo) { case Enum.ChildNotifyActionType.Add: this._setNewOptionItem(childItem); break; case Enum.ChildNotifyActionType.Click: this._optionItemHasBeenClicked(childItem.uniqueId); break; case Enum.ChildNotifyActionType.KeyPressed: this._optionItemKeyPressed(childItem.uniqueId); break; case Enum.ChildNotifyActionType.Removed: this._unsetNewOptionItem(childItem.uniqueId); break; default: throw new Error( `${ErrorCodes.Dropdown.FailToSetOptionItemAction}: There no exist a '${notifiedTo}' notification type.` ); } } /** * Method to build the DropdownServerSide * * @memberof OSUIDropdownServerSide */ public build(): void { super.build(); this.setCallbacks(); this.setHtmlElements(); this.setA11YProperties(); this._setUpEvents(); this._setCssClasses(); this._setInitialOptions(); this._setBalloonFeature(); this.finishBuild(); } /** * Update property value from a given property name at OnParametersChange * * @param {string} propertyName the name of the property that will be changed * @param {unknown} propertyValue the new value that should be assigned to the given property name * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public changeProperty(propertyName: string, propertyValue: unknown): void { super.changeProperty(propertyName, propertyValue); if (this.isBuilt) { switch (propertyName) { case Enum.Properties.IsDisabled: propertyValue ? this.disable() : this.enable(); break; } } } /** * Method that will check for all Selected OptionItems and Unselect them * * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public clear(): void { // Get all Selected Items const selectedOptions = this.getChildItems().filter((item) => item.IsSelected); // Go through all the selected option items for (const optionItem of selectedOptions) { // Unselect it! optionItem.toggleSelected(false); } } /** * Method used to close the Dropdown * * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public close(): void { // SetTimeout is needed in order to ensure there is no conflit between OnClickBody and a button click that trigger this method. OSFramework.OSUI.Helper.AsyncInvocation(this._close.bind(this)); } /** * Set pattern with a disable status * * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public disable(): void { // Assign disabled status. Helper.Dom.Attribute.Set(this.selfElement, GlobalEnum.HTMLAttributes.Disabled, ''); // Assign IsDisabled class Helper.Dom.Styles.AddClass(this.selfElement, Enum.CssClass.IsDisabled); // Assign tabindex value on values wrapper Helper.A11Y.TabIndexFalse(this._selectValuesWrapper); } /** * Destroy the Dropdown. * * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public dispose(): void { this._unsetEvents(); this._balloonFeature?.dispose(); this.unsetCallbacks(); this.unsetHtmlElements(); super.dispose(); } /** * Remove disable status from * * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public enable(): void { // Remove disabled status. Helper.Dom.Attribute.Remove(this.selfElement, GlobalEnum.HTMLAttributes.Disabled); // Remove IsDisabled class Helper.Dom.Styles.RemoveClass(this.selfElement, Enum.CssClass.IsDisabled); // Assign tabindex value on values wrapper Helper.A11Y.TabIndexTrue(this._selectValuesWrapper); } /** * This method has no implementation on this context. * * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public getSelectedValues(): string { return this._hasNoImplementation(); } /** * Method used to open the Dropdown * * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public open(): void { // SetTimeout is needed in order to ensure there is no conflit between OnClickBody and a button click that trigger this method. OSFramework.OSUI.Helper.AsyncInvocation(this._open.bind(this)); } /** * Method used to register the provider callback * * @param {string} eventName Event name that will be assigned * @param {GlobalCallbacks.OSGeneric} callback Function name that will be passed as a callback function to the event above * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public registerCallback(eventName: string, callback: GlobalCallbacks.OSGeneric): void { switch (eventName) { case Enum.Events.OnToggle: if (this._platformEventOnToggleCallback === undefined) { this._platformEventOnToggleCallback = callback; } break; default: super.registerCallback(eventName, callback); } } /** * Method to set the Balloon options * * @param {Feature.Balloon.BalloonOptions} [balloonOptions] * @memberof OSUIDropdownServerSide */ public setBalloonOptions(balloonOptions?: Feature.Balloon.BalloonOptions): void { if (balloonOptions !== undefined) { this.balloonOptions = balloonOptions; } else { // Set focus options to pass to the Balloon feature const _focusOptions = { elemToFocusOnOpen: this._selectValuesWrapper, useFocus: true, focusTrapParams: { focusBottomCallback: this.close.bind(this), focusTargetElement: this._balloonContainerElement, focusTopCallback: this.close.bind(this), }, }; this.balloonOptions = { alignment: GlobalEnum.FloatingAlignment.Start, anchorElem: this._selectValuesWrapper, allowedPlacements: [GlobalEnum.FloatingPosition.TopStart, GlobalEnum.FloatingPosition.BottomStart], position: GlobalEnum.FloatingPosition.Auto, shape: GlobalEnum.ShapeTypes.SoftRounded, focusOptions: _focusOptions, useTriggerWidth: true, }; } } /** * Method used to set the balloon AriaLabelText that will be applied to the balloon options container element * * @param value Text to be added */ public setBalloonOptionsAriaLabel(value?: string): void { this._balloonOptionsAriaLabel = value === undefined ? this.configs.balloonOptionsArialabel : value; Helper.A11Y.AriaLabel(this._balloonOptionsWrapperElement, this._balloonOptionsAriaLabel); } /** * This method has no implementation on this context. * * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public setProviderConfigs(): string { return this._hasNoImplementation(); } /** * This method has no implementation on this context. * * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public setProviderEvent(): string { return this._hasNoImplementation(); } /** * Method used to set the AriaLabel text that will be applied to the SelectValuesWrapper "input" element * * @param value Text to be added */ public setSelectAriaLabel(value?: string): void { this._selectValuesWrapperAriaLabel = value === undefined ? this.configs.selectValuesWrapperAriaLabel : value; Helper.A11Y.AriaLabel(this._selectValuesWrapper, this._selectValuesWrapperAriaLabel); } /** * This method has no implementation on this context. * * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public unsetProviderEvent(): string { return this._hasNoImplementation(); } /** * Set the validation status, and also pass the message to show * * @param {boolean} Set if the dropdown is valid or not * @param {string} Text message to be added * @memberof OSFramework.Patterns.Dropdown.ServerSide.OSUIDropdownServerSide */ public validation(isValid: boolean, validationMessage: string): void { if (isValid === false) { Helper.Dom.Styles.AddClass(this.selfElement, Enum.CssClass.NotValid); this._addErrorMessage(validationMessage); } else { Helper.Dom.Styles.RemoveClass(this.selfElement, Enum.CssClass.NotValid); const errorMessageElement = Helper.Dom.ClassSelector( this.selfElement.parentElement, Enum.CssClass.ErrorMessage ); // If error message has been added already, remove it! if (errorMessageElement) { errorMessageElement.remove(); } } } } }