File

app/shared/top-menu/top-menu.component.ts

Implements

OnInit OnDestroy

Metadata

selector [top-menu]
styleUrls ./top-menu.component.scss
templateUrl ./top-menu.component.html

Index

Properties
Methods
Inputs
HostBindings

Constructor

constructor(util: UtilService, labelService: LabelService, publicationService: PublicationService, constants: ConstantsService, resizer: ResizeContentService, idGenerator: IdGeneratorService, router: Router, doc)
Parameters :
Name Type Optional
util UtilService No
labelService LabelService No
publicationService PublicationService No
constants ConstantsService No
resizer ResizeContentService No
idGenerator IdGeneratorService No
router Router No
doc No

Inputs

navigation
page

HostBindings

class

Methods

actOnResize
actOnResize()
Returns : void
addNavLevel
addNavLevel(item, e: Event)
Parameters :
Name Type Optional
item No
e Event No
Returns : void
back
back(e: Event)
Parameters :
Name Type Optional
e Event No
Returns : void
Private bootstrap
bootstrap()
Returns : void
cleanItem
cleanItem(item, depth)

Remove navigation items that should not be shown in MegaMenu

Parameters :
Name Optional
item No
depth No
Returns : void
isClickable
isClickable(item)
Parameters :
Name Optional
item No
Returns : boolean
ngOnDestroy
ngOnDestroy()
Returns : void
ngOnInit
ngOnInit()
Returns : void
resetLevels
resetLevels()
Returns : void
syncViewWithState
syncViewWithState()
Returns : void

Properties

clickableLinksMegaMenuLabel
Type : string
columns
Type : []
Default value : []
Private componentId
Default value : this.idGenerator.getId()
homePage
Type : string
isDesktop
Type : boolean
isNavbarCollapsed
Default value : true
megaMenu
Type : NgbDropdown
Decorators :
@ViewChild('megaMenu')
navLevels
Type : []
Default value : []
navToggler
Type : ElementRef<HTMLElement>
Decorators :
@ViewChild('navToggler')
screenSize
Type : ScreenSize
Private sgUrl
Type : string
Default value : ''
topBar
Type : []
Default value : []
Private Readonly urlSeparator
Type : string
Default value : '/'
viewModel
import { DOCUMENT } from '@angular/common';
import { Component, HostBinding, Inject, Input, OnChanges, OnDestroy, OnInit, ViewChild, ElementRef } from '@angular/core';
import { IdGeneratorService } from 'src/app/core/services/id-generator.service';
import { LabelService } from 'src/app/core/services/label-service/label-service.service';
import { PublicationService } from 'src/app/core/services/publication-service/publication.service';
import { ResizeContentService } from 'src/app/core/services/resize-service/resize-content.service';
import { UtilService } from 'src/app/core/services/util-service/util.service';
import { ConstantsService } from '../constants/constants.service';
import { Router } from '@angular/router';
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';

enum ScreenSize {
	XS,
	SM,
	MD,
	LG,
	XL,
	XXL
}
@Component({
	selector: '[top-menu]',
	templateUrl: './top-menu.component.html',
	styleUrls: ['./top-menu.component.scss']
})
export class TopMenuComponent implements OnInit, OnDestroy {

	@Input() page;
	@Input() navigation;

	@ViewChild('navToggler') navToggler: ElementRef<HTMLElement>;

	private readonly urlSeparator: string = '/';
	private sgUrl = '';
	private componentId = this.idGenerator.getId();

	isNavbarCollapsed = true;
	isDesktop: boolean;
	screenSize: ScreenSize;
	navLevels = [];
	homePage: string;
	viewModel;
	clickableLinksMegaMenuLabel: string;

	topBar = [];
	columns = [];

	@ViewChild('megaMenu') megaMenu: NgbDropdown;

	@HostBinding('class') get class() { return 'navbar navbar-expand-lg navbar-dark bg-blue'; }

	constructor(
		private util: UtilService,
		private labelService: LabelService,
		private publicationService: PublicationService,
		private constants: ConstantsService,
		private resizer: ResizeContentService,
		private idGenerator: IdGeneratorService,
		private router: Router,
		@Inject(DOCUMENT) private doc
	) { }

	ngOnInit() {
		this.labelService.getLabel('clickableLinksMegaMenu').then(label => this.clickableLinksMegaMenuLabel = label);
		this.homePage = this.publicationService.getPublicationPath();
		this.resizer.registerCallback(this.componentId, () => this.actOnResize());

		/** Filter out top bar nav items that should not be presented. */
		this.navigation.Items = this.navigation.Items.filter(item => {
			if (this.util.extract(item, 'Metadata', 'topMenuFoldOutOrLink')
				&& item.Type === 'StructureGroup' && item.Visible) {
				return true;
			}
		});

		this.topBar = this.navigation.Items;

		this.navigation.depth = 0;
		this.navigation.Items.forEach(item => {
			this.cleanItem(item, 1);
		});

		this.bootstrap();
	}

	ngOnDestroy(): void {
		this.resizer.destroyCallback(this.componentId);
	}

	actOnResize(): void {
		const width = (window.innerWidth || this.doc.documentElement.clientWidth);
		this.isDesktop = width >= this.constants.BOOTSTRAP_BREAKPOINTS.LG;
		let newSize;

		if (width >= this.constants.BOOTSTRAP_BREAKPOINTS.XXL) {
			newSize = ScreenSize.XXL;
		} else if (width >= this.constants.BOOTSTRAP_BREAKPOINTS.XL) {
			newSize = ScreenSize.XL;
		} else if (width >= this.constants.BOOTSTRAP_BREAKPOINTS.LG) {
			newSize = ScreenSize.LG;
		} else if (width >= this.constants.BOOTSTRAP_BREAKPOINTS.MD) {
			newSize = ScreenSize.MD;
		} else if (width >= this.constants.BOOTSTRAP_BREAKPOINTS.SM) {
			newSize = ScreenSize.SM;
		} else {
			newSize = ScreenSize.XS;
		}

		// If screen size has changed, sync state.
		if (this.screenSize !== newSize) {
			this.screenSize = newSize;
			this.syncViewWithState();
		}
	}

	private bootstrap(): void {
		if (this.page) {
			let url: string = this.util.extract(this.page, 'Url');
			const publicationPath = this.publicationService.getPublicationPath();

			if (url && publicationPath) {
				if (url.startsWith(publicationPath + this.urlSeparator)) {
					url = url.replace(publicationPath + this.urlSeparator, '');
					this.sgUrl = publicationPath + this.urlSeparator + (url.indexOf(this.urlSeparator) > 0 ? url.substring(0, url.indexOf(this.urlSeparator)) : '');
				}
			}
		}

		if (this.navigation) {
			this.navLevels[0] = { Items: this.navigation.Items };
			this.syncViewWithState();
			this.actOnResize();
		}
	}

	/** Remove navigation items that should not be shown in MegaMenu */
	cleanItem(item, depth) {
		item.depth = depth;

		if (item.Items && item.Items.length > 0) {
			item.Items = item.Items.filter(child => {
				const shouldBeShown = (child.Visible && (child.Type === 'StructureGroup')
										&& (child.Metadata.isItemPartOfMegaMenu === 'Yes'));
				// Keep index pages
				const isIndexPage = (child.Items.length === 0 && child.Url === item.Url);

				return  shouldBeShown || isIndexPage ;
			});

			item.Items.forEach(child => {
				this.cleanItem(child, depth + 1);
			});
		}
	}

	resetLevels(): void {
		this.navLevels = this.navLevels.slice(0, 1);
		this.syncViewWithState();

		if (!this.isDesktop) {
			this.isNavbarCollapsed = true;
		}

		this.megaMenu.close();
	}

	addNavLevel(item, e: Event): void {
		this.navLevels = this.navLevels.slice(0, item.depth);
		this.navLevels[item.depth] = item;
		this.syncViewWithState();

		const nextCol = this.util.extract(item, 'Items') || [];
		if (nextCol.length === 1 && (nextCol[0].Url === item.Url)) {
			this.resetLevels();
			this.router.navigate([item.Url]);
		}

		e.preventDefault();
		e.stopPropagation();
	}

	isClickable(item): boolean {
		if (!item.Items) { return false; }
		const clickable = this.util.extract(item, 'Metadata', 'behaviourMegaMenuItem') === 'Clickable link';
		const hasIndexPage = item.Items.some(e => e.Url === item.Url);
		return clickable && hasIndexPage;
	}

	back(e: Event): void {
		this.navLevels.pop();
		this.syncViewWithState();
		e.stopPropagation();
	}

	syncViewWithState() {
		this.viewModel = { columns: {} };

		if (this.screenSize === ScreenSize.XS || this.screenSize === ScreenSize.SM || this.screenSize === ScreenSize.MD) {
			this.viewModel.columns = this.navLevels.slice(-1);
			this.viewModel.columns.length = 1;
			this.viewModel.backChevron = this.navLevels.length > this.viewModel.columns.length;
		} else if (this.screenSize === ScreenSize.LG) {
			this.viewModel.columns = this.navLevels.slice(1).slice(-4);
			this.viewModel.columns.length = 4;
			this.viewModel.backChevron = this.navLevels.length - 1 > this.viewModel.columns.length;
		} else if (this.screenSize === ScreenSize.XL || this.screenSize === ScreenSize.XXL) {
			this.viewModel.columns = this.navLevels.slice(1).slice(-5);
			this.viewModel.columns.length = 5;
			this.viewModel.backChevron = false;
		}

		this.viewModel.megaMenuIsOpen = this.navLevels.length > 1;
	}
}
<div [ngClass]="{'container': isDesktop, 'container-fluid': !isDesktop}">
	<a class="navbar-brand" [routerLink]="homePage">
		<img src="v2/assets/img/skf-logo-white.svg" class="logo" alt="logo">
	</a>
	<div class="btn-group order-lg-2">
		<button class="navbar-toggler" [ngClass]="{'collapsed': isNavbarCollapsed}" type="button" aria-controls="topMenu"
			aria-expanded="false" aria-label="Toggle navigation" (click)="isNavbarCollapsed = !isNavbarCollapsed" #navToggler>
			<span class="icon-bar top-bar"></span>
			<span class="icon-bar middle-bar"></span>
			<span class="icon-bar bottom-bar"></span>
		</button>

		<div top-menu-search [navToggler]="navToggler" [isNavbarCollapsed]="isNavbarCollapsed" class="search"></div>
	</div>

	<div class="collapse navbar-collapse order-lg-1" id="topMenu" [ngbCollapse]="isNavbarCollapsed">
		<ul class="navbar-nav">
			<ng-container *ngFor="let item of topBar">
				<li class="nav-item" *ngIf="item.Metadata.topMenuFoldOutOrLink === 'Link'">
					<a href="#" class="nav-link" (click)="resetLevels" [routerLink]="item.Metadata.topMenuIsAlink" role="link">
						{{item.TitleOverride || item.Title}}
					</a>
				</li>

				<li class="nav-item"
					*ngIf="item.Metadata.topMenuFoldOutOrLink !== 'Link'"
					(click)="addNavLevel(item, $event)"
					[ngClass]="{'selected': item === navLevels[1], 'active': item.Url === sgUrl}"
					ngbDropdown
					#megaMenu="ngbDropdown"
					placement="bottom-left"
					autoClose="outside">
					<a href="#" class="nav-link" role="button" ngbDropdownToggle>
						{{item.TitleOverride || item.Title}}
					</a>

					<div class="dropdown-menu" ngbDropdownMenu>
						<div [ngClass]="{'container': isDesktop, 'container-fluid': !isDesktop}">
							<div class="card-deck">
								<ng-container *ngFor="let col of viewModel.columns; let i = index; let last = last">
									<div class="card" [ngClass]="{ 'fade-in-left': i > 0 }">
										<div class="list-group list-group-flush">
										<ng-container *ngIf="col">
											<div class="list-group-item list-group-item-title" *ngIf="isClickable(col) && (col.depth > 1 || (viewModel.megaMenuIsOpen && col.depth > 0))">
												<ng-container *ngTemplateOutlet="listGroupItemTitle"></ng-container>
												<a (click)="resetLevels()" [routerLink]="col.Url" role="link">
													<h5>{{col.TitleOverride || col.Title}}</h5>
													<p>{{clickableLinksMegaMenuLabel}}</p>
												</a>
											</div>

											<div class="list-group-item list-group-item-title" *ngIf="!isClickable(col) && (col.depth > 1)">
												<ng-container *ngTemplateOutlet="listGroupItemTitle"></ng-container>
												<h5>{{col.TitleOverride || col.Title}}</h5>
											</div>

											<ng-template #listGroupItemTitle>
												<button class="btn btn-back" (click)="back($event)" *ngIf="navLevels.length > 1 && (viewModel.backChevron && last)" role="button">
													<i class="icon-chevronLarge-back"></i>
												</button>
											</ng-template>

											<ng-container *ngFor="let item of col.Items">
												<a href="#" class="list-group-item dropdown-item" (click)="addNavLevel(item, $event)" *ngIf="item.Visible"
													[ngClass]="{
														'selected': item == viewModel.columns[i+1],
														'direct-link': item.Items.length === 1 && isClickable(item)
													}"
												>
													{{item.TitleOverride || item.Title}}
													<i class="icon-chevronLarge-next" *ngIf="!(item.Items.length === 1 && isClickable(item))"></i>
												</a>
											</ng-container>
										</ng-container>
										</div>
									</div>
								</ng-container>
							</div>
						</div>
					</div>
				</li>
			</ng-container>
		</ul>
	</div>
</div>

./top-menu.component.scss

@import "src/app/styles/helpers";

$mobile-breakpoint: 'lg';

:host {
    padding: 0 !important;
    padding-bottom: calc-rem(16);
    box-shadow: $box-shadow-xl;

    @include media-breakpoint-up($mobile-breakpoint) {
        padding-top: 0;
        padding-bottom: 0;
    }
}

.icon-nav-close {
    vertical-align: middle;
    font-size: calc-rem(30);
    margin-right: 0;
    margin-left: 0;
}

.navbar-brand {
    margin-left: calc-rem(24);
    margin-right: calc-rem(40);

    .logo {
        display: block;
        width: 100px;

        @include media-breakpoint-up($mobile-breakpoint) {
            margin-left: 0;
        }
    }
}

.navbar-toggler {
    border-color: transparent !important;
    outline: none;

    .top-bar {
        transform: rotate(45deg);
        transform-origin: 10% 200%;
    }

    .middle-bar {
        opacity: 0;
    }

    .bottom-bar {
        transform: rotate(-45deg);
        transform-origin: 10% -100%;
    }

    &.collapsed .top-bar {
        transform: rotate(0);
    }

    &.collapsed .middle-bar {
        opacity: 1;
    }

    &.collapsed .bottom-bar {
        transform: rotate(0);
    }

    .icon-bar {
        width: 22px;
        height: 2px;
        background-color: $white;
        display: block;
        transition: all 0.2s;
        margin-top: calc-rem(5);

        &:first-of-type {
            margin-top: 0;
        }
    }
}

.navbar-collapse {
    margin-bottom: calc-rem(16);

    @include media-breakpoint-up($mobile-breakpoint) {
        margin-bottom: 0;
    }
}

.navbar-nav {
    margin-top: calc-rem(20);

    @include media-breakpoint-up($mobile-breakpoint) {
        margin-top: 0;
    }

    .nav-item {
        margin-left: calc-rem(10);
        margin-right: calc-rem(10);
        .nav-link {
            padding: calc-rem(10) calc-rem(15);

            @include media-breakpoint-up($mobile-breakpoint) {
                padding: calc-rem(20) calc-rem(20);
            }

            @include font-scale(16, light);
            text-transform: uppercase;
            letter-spacing: calc-rem(0.4);

            &.dropdown-toggle:after {
                border: none;
                margin-left: 0;
            }

            &:hover {
                background-color: $dark-blue;
            }
        }

        &.show {
            @include media-breakpoint-up($mobile-breakpoint) {
                background-color: $white;

                > .nav-link {
                    color: $cool-grey !important;

                    &:hover {
                        background-color: $white;
                    }
                }
            }
        }

        &.dropdown {
            @include media-breakpoint-up($mobile-breakpoint) {
                position: static !important;
            }
        }

        .dropdown-menu {
            position: static !important;
            transform: inherit !important;
            background-color: transparent;
            border: none;
            margin-top: 0;

            @include media-breakpoint-up($mobile-breakpoint) {
                box-shadow: $box-shadow-xl;
                position: absolute !important;
                transform: translate(0, 64px) !important;
                width: 100%;
                background-color: $white;
                border-bottom: 1px solid $light-slate;
            }

            .card-deck {
                @include media-breakpoint-up($mobile-breakpoint) {
                    margin-top: calc-rem(30);
                    margin-bottom: calc-rem(30);
                }

                & .card {
                    border: none;
                    background-color: transparent;

                    .list-group {
                        margin-left: calc-rem(12);
                        margin-right: calc-rem(12);

                        @include media-breakpoint-up($mobile-breakpoint) {
                            margin-left: 0;
                            margin-right: 0;
                        }

                        &-item {
                            display: flex;
                            justify-content: space-between;
                            padding: calc-rem(12);
                            color: $white;
                            background-color: transparent;
                            white-space: normal;
                            border-bottom: 1px solid rgba(255, 255, 255, 0.5); // White, 50% opacity;

                            @include media-breakpoint-up($mobile-breakpoint) {
                                margin-left: calc-rem(12);
                                margin-right: calc-rem(12);
                            }

                            &.selected {
                                @include media-breakpoint-up($mobile-breakpoint) {
                                    background-color: $off-white;
                                }
                            }

                            &.direct-link {
                                &:hover {
                                    text-decoration: underline;
                                }
                            }

                            @include media-breakpoint-up($mobile-breakpoint) {
                                color: $cool-grey;
                                margin-left: 0;
                                margn-right: 0;
                            }

                            .icon-chevronLarge-next {
                                width: auto;
                                margin-right: 0;
                                align-self: center;
                            }

                            .btn.btn-back {
                                padding-left: 0;
                                padding-top: 0;
                                color: $white;
                                align-self: baseline;

                                @include media-breakpoint-up($mobile-breakpoint) {
                                    color: $cool-grey;
                                }
                            }

                            &.dropdown-item {
                                &:hover {
                                    background-color: $dark-blue;
                                    color: $white;
                                }
                            }

                            &-title {
                                justify-content: flex-start;
                                width: 100%;
                                background-color: $dark-blue;

                                a { color: $white; }

                                p {
                                    margin-bottom: 0;
                                    color: $light-blue;
                                    @include font-size(14);
                                }

                                @include media-breakpoint-up($mobile-breakpoint) {
                                    background-color: $extra-light-slate;

                                    a { color: $cool-grey; }

                                    p { color: $cool-grey; }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""