outsystems-ui
Version:
OutSystems UI Framework
323 lines (291 loc) • 10.9 kB
text/typescript
// eslint-disable-next-line @typescript-eslint/no-unused-vars
namespace OSFramework.OSUI.Patterns.SectionIndex {
/**
* Class that implements the SectionIndex pattern.
*
* @export
* @class SectionIndex
* @extends {AbstractPattern<SectionIndexConfig, SectionIndexItem.ISectionIndexItem>}
* @implements {ISectionIndex}
*/
export class SectionIndex
extends AbstractParent<SectionIndexConfig, SectionIndexItem.ISectionIndexItem>
implements ISectionIndex
{
// Store the current active sectionIndexItem
private _activeSectionIndexItem: SectionIndexItem.ISectionIndexItem;
// Store the contentPaddingTop
private _contentPaddingTop: string | number;
// Store the header size if it's fixed!
private _headerHeight: number;
// Store a flag that will be used to check if the header is fixed!
private _headerIsFixed = true;
// Store the mainContent reference - The one that will have the scroll
private _mainScrollContainerElement: HTMLElement;
// Boolean flag to prevent code from running before the scroll has ended
private _navigateOnClick = false;
// Store the scroll settimeout, to be cleared when needed
private _scrollTimeout: number;
// Store the scroll options to be used on ScrollIntoView
public scrollOptions: ScrollIntoViewOptions = {
behavior: this.configs.SmoothScrolling ? GlobalEnum.ScrollBehavior.Smooth : GlobalEnum.ScrollBehavior.Auto,
inline: GlobalEnum.ScrollPositionBehavior.Start,
block: GlobalEnum.ScrollPositionBehavior.Start,
};
constructor(uniqueId: string, configs: JSON) {
super(uniqueId, new SectionIndexConfig(configs));
}
// Method to add Item to the list
private _addSectionIndexItem(childItem: Patterns.SectionIndexItem.SectionIndexItem): void {
if (this.getChild(childItem.uniqueId)) {
throw new Error(
`${ErrorCodes.SectionIndex.FailSetNewChildItem}: There is already a ${GlobalEnum.PatternName.SectionIndexItem} under Id: '${childItem.widgetId}' added to ${GlobalEnum.PatternName.SectionIndex} with uniqueId: ${this.uniqueId}.`
);
} else {
// Store Child Item
this.setChild(childItem);
}
}
// Method to deal with the click at a SectionIndexItem
private _childItemHasBeenClicked(childId: string): void {
const childReference = this.getChild(childId);
// Check if the given ChildId exist as an child item
if (childReference) {
// Update child status
this._setActiveChildOnClick(childReference);
} else {
throw new Error(
`${ErrorCodes.SectionIndex.FailChildItemClicked}: The ${GlobalEnum.PatternName.SectionIndexItem} under uniqueId: '${childId}' does not exist as an SectionIndexItem from ${GlobalEnum.PatternName.SectionIndex} with Id: ${this.widgetId}.`
);
}
}
// Method to get content paddnig top
private _getContentPaddingTop(): void {
const _mainContent = Helper.Dom.ClassSelector(document, GlobalEnum.CssClassElements.MainContent);
this._contentPaddingTop = _mainContent
? parseFloat(
window.getComputedStyle(_mainContent).getPropertyValue(GlobalEnum.CssProperties.PaddingTop)
)
: 0;
}
// Method to check if header IsFixed and get its height to be used
private _getHeaderSize(): void {
const header = Helper.Dom.ClassSelector(document.body, GlobalEnum.CssClassElements.Header);
this._headerIsFixed = !!Helper.Dom.ClassSelector(document.body, GlobalEnum.CssClassElements.HeaderIsFixed);
// If header exist and it's fixed store its height
if (header) {
this._headerHeight = this._headerIsFixed ? header.offsetHeight : 0;
}
}
// Method used to remove a given SectionIndexItem from sectionIndexItems list, it's triggered by SectionIndexItem
private _removeSectionIndexItem(childId: string): void {
// Check if the given ChildId exist at childList
if (this.getChild(childId)) {
// Remove item
this.unsetChild(childId);
} else {
throw new Error(
`${ErrorCodes.SectionIndex.FailUnsetNewChildItem}: The ${GlobalEnum.PatternName.SectionIndexItem} under uniqueId: '${childId}' does not exist as an SectionIndexItem from ${GlobalEnum.PatternName.SectionIndex} with Id: ${this.widgetId}.`
);
}
}
// Method that will update the IsActive child item status
private _setActiveChildOnClick(child: SectionIndexItem.ISectionIndexItem): void {
// If its clicking on the same item, do nothing
if (child === this._activeSectionIndexItem) {
return;
}
// Clean the timeout
this._navigateOnClick = true;
window.clearTimeout(this._scrollTimeout);
this._scrollTimeout = window.setTimeout(() => {
// Reset flag in order to understand navigation by click has ended!
this._navigateOnClick = false;
}, 1000); // enought time to deal with the scroll
this._updateActiveItem(child);
// Trigger the Scroll navigation
Behaviors.Scroll(child.TargetElement, this.scrollOptions);
}
// Method used to set the IsActive child item at the onBodyScroll
private _setActiveChildOnScroll(child: SectionIndexItem.ISectionIndexItem) {
// Prevent logic happen if the scroll has been triggered by click on the item
if (this._navigateOnClick || this.configs.IsFixed === false) {
return;
}
this._updateActiveItem(child);
}
// Method to set the SectionIndex IsFixed
private _toggleIsFixed(): void {
if (this.configs.IsFixed) {
let headerHeight = 0;
// Get Header height
const hasFixedHeader = Helper.Dom.ClassSelector(
document.body,
GlobalEnum.CssClassElements.HeaderIsFixed
);
if (hasFixedHeader) {
headerHeight =
Helper.Dom.ClassSelector(document, GlobalEnum.CssClassElements.Header).offsetHeight || 0;
}
// Set inline css variable that will affect the pattern sticky position top value
Helper.Dom.Styles.SetStyleAttribute(
this.selfElement,
Enum.CssVariable.TopPosition,
'calc(' + headerHeight + 'px + ' + this._contentPaddingTop + 'px)'
);
// Set the Sticky class
Helper.Dom.Styles.AddClass(this.selfElement, Enum.CssClass.IsSticky);
} else {
// Remove inline added css variable
Helper.Dom.Styles.RemoveStyleAttribute(this.selfElement, Enum.CssVariable.TopPosition);
// Remove the Sticky class
Helper.Dom.Styles.RemoveClass(this.selfElement, Enum.CssClass.IsSticky);
}
}
// Method to remove current active item and set new one
private _updateActiveItem(child: SectionIndexItem.ISectionIndexItem): void {
// Remove old sectionIndexItem as active if exist
if (this._activeSectionIndexItem) {
this._activeSectionIndexItem.unsetIsActive();
}
// Set new sectionIndexItem as active
child.setIsActive();
// Set the current IsActive Child
this._activeSectionIndexItem = child;
}
/**
* This method has no implementation on this pattern context!
*
* @protected
* @memberof OSFramework.Patterns.SectionIndex.SectionIndex
*/
protected setA11YProperties(): void {
console.warn(GlobalEnum.WarningMessages.MethodNotImplemented);
}
/**
* This method has no implementation on this pattern context!
*
* @protected
* @memberof OSFramework.Patterns.SectionIndex.SectionIndex
*/
protected setCallbacks(): void {
console.log(GlobalEnum.WarningMessages.MethodNotImplemented);
}
/**
* Method to set the HTMLElements used
*
* @protected
* @memberof OSFramework.Patterns.SectionIndex.SectionIndex
*/
protected setHtmlElements(): void {
// Check if overlay is enabled => If StatusBar is enabled and if is iOS device
/* With the introduction of the ios-bounce the overflow container change from the .active-screen into .content */
if (
Helper.Dom.Attribute.Has(document.body, GlobalEnum.HTMLAttributes.StatusBar) &&
Helper.DeviceInfo.GetOperatingSystem() === GlobalEnum.MobileOS.IOS
) {
this._mainScrollContainerElement = Helper.Dom.ClassSelector(
document,
GlobalEnum.CssClassElements.Content
);
} else {
this._mainScrollContainerElement = Helper.Dom.ClassSelector(
document,
GlobalEnum.CssClassElements.ActiveScreen
);
}
}
/**
* This method has no implementation on this pattern context!
*
* @protected
* @memberof OSFramework.Patterns.SectionIndex.SectionIndex
*/
protected unsetCallbacks(): void {
console.log(GlobalEnum.WarningMessages.MethodNotImplemented);
}
/**
* Method to unset the HTMLElements used
*
* @protected
* @memberof OSFramework.Patterns.SectionIndex.SectionIndex
*/
protected unsetHtmlElements(): void {
this._mainScrollContainerElement = undefined;
}
/**
* Method used to be notified by a given ChildId about a given action and act accordingly
*
* @param childId Child Item Id to be stored/managed
* @param notifiedTo {Enum.ChildNotifyActionType} triggered notification type
* @memberof OSFramework.Patterns.SectionIndex.SectionIndex
*/
public beNotifiedByChild(
childItem: Patterns.SectionIndexItem.SectionIndexItem,
notifiedTo: Enum.ChildNotifyActionType
): void {
switch (notifiedTo) {
case Enum.ChildNotifyActionType.Add:
this._addSectionIndexItem(childItem);
break;
case Enum.ChildNotifyActionType.Click:
this._childItemHasBeenClicked(childItem.uniqueId);
break;
case Enum.ChildNotifyActionType.Removed:
this._removeSectionIndexItem(childItem.uniqueId);
break;
case Enum.ChildNotifyActionType.Active:
this._setActiveChildOnScroll(this.getChild(childItem.uniqueId));
break;
default:
throw new Error(
`${ErrorCodes.SectionIndex.FailToSetChildItemAction}: There no exist a '${notifiedTo}' notification type.`
);
}
}
/**
* Method to build the SectionIndex.
*
* @memberof OSFramework.Patterns.SectionIndex.SectionIndex
*/
public build(): void {
super.build();
this.setHtmlElements();
this._getContentPaddingTop();
this._getHeaderSize();
this._toggleIsFixed();
this.finishBuild();
}
/**
* Applies the changes of state/value of the configurations.
*
* @param {string} propertyName
* @param {unknown} propertyValue
* @memberof OSFramework.Patterns.SectionIndex.SectionIndex
*/
public changeProperty(propertyName: string, propertyValue: unknown): void {
super.changeProperty(propertyName, propertyValue);
if (this.isBuilt) {
switch (propertyName) {
case Enum.Properties.IsFixed:
this._toggleIsFixed();
break;
case Enum.Properties.SmoothScrolling:
this.scrollOptions.behavior = propertyValue
? GlobalEnum.ScrollBehavior.Smooth
: GlobalEnum.ScrollBehavior.Auto;
}
}
}
/**
* Disposes the current pattern.
*
* @memberof OSFramework.Patterns.SectionIndex.SectionIndex
*/
public dispose(): void {
this.unsetHtmlElements();
//Destroying the base of pattern
super.dispose();
}
}
}