app/shared/top-menu/top-menu.component.ts
selector | [top-menu] |
styleUrls | ./top-menu.component.scss |
templateUrl | ./top-menu.component.html |
Properties |
|
Methods |
Inputs |
HostBindings |
constructor(util: UtilService, labelService: LabelService, publicationService: PublicationService, constants: ConstantsService, resizer: ResizeContentService, idGenerator: IdGeneratorService, router: Router, doc)
|
|||||||||||||||||||||||||||
Defined in app/shared/top-menu/top-menu.component.ts:49
|
|||||||||||||||||||||||||||
Parameters :
|
navigation | |
Defined in app/shared/top-menu/top-menu.component.ts:28
|
page | |
Defined in app/shared/top-menu/top-menu.component.ts:27
|
class |
Defined in app/shared/top-menu/top-menu.component.ts:49
|
actOnResize |
actOnResize()
|
Defined in app/shared/top-menu/top-menu.component.ts:89
|
Returns :
void
|
addNavLevel | |||||||||
addNavLevel(item, e: Event)
|
|||||||||
Parameters :
Returns :
void
|
back | ||||||
back(e: Event)
|
||||||
Parameters :
Returns :
void
|
Private bootstrap |
bootstrap()
|
Returns :
void
|
cleanItem | ||||||
cleanItem(item, depth)
|
||||||
Remove navigation items that should not be shown in MegaMenu
Parameters :
Returns :
void
|
isClickable | ||||
isClickable(item)
|
||||
Parameters :
Returns :
boolean
|
ngOnDestroy |
ngOnDestroy()
|
Defined in app/shared/top-menu/top-menu.component.ts:85
|
Returns :
void
|
ngOnInit |
ngOnInit()
|
Defined in app/shared/top-menu/top-menu.component.ts:62
|
Returns :
void
|
resetLevels |
resetLevels()
|
Returns :
void
|
syncViewWithState |
syncViewWithState()
|
Returns :
void
|
clickableLinksMegaMenuLabel |
Type : string
|
Defined in app/shared/top-menu/top-menu.component.ts:42
|
columns |
Type : []
|
Default value : []
|
Defined in app/shared/top-menu/top-menu.component.ts:45
|
Private componentId |
Default value : this.idGenerator.getId()
|
Defined in app/shared/top-menu/top-menu.component.ts:34
|
homePage |
Type : string
|
Defined in app/shared/top-menu/top-menu.component.ts:40
|
isDesktop |
Type : boolean
|
Defined in app/shared/top-menu/top-menu.component.ts:37
|
isNavbarCollapsed |
Default value : true
|
Defined in app/shared/top-menu/top-menu.component.ts:36
|
megaMenu |
Type : NgbDropdown
|
Decorators :
@ViewChild('megaMenu')
|
Defined in app/shared/top-menu/top-menu.component.ts:47
|
navLevels |
Type : []
|
Default value : []
|
Defined in app/shared/top-menu/top-menu.component.ts:39
|
navToggler |
Type : ElementRef<HTMLElement>
|
Decorators :
@ViewChild('navToggler')
|
Defined in app/shared/top-menu/top-menu.component.ts:30
|
screenSize |
Type : ScreenSize
|
Defined in app/shared/top-menu/top-menu.component.ts:38
|
Private sgUrl |
Type : string
|
Default value : ''
|
Defined in app/shared/top-menu/top-menu.component.ts:33
|
topBar |
Type : []
|
Default value : []
|
Defined in app/shared/top-menu/top-menu.component.ts:44
|
Private Readonly urlSeparator |
Type : string
|
Default value : '/'
|
Defined in app/shared/top-menu/top-menu.component.ts:32
|
viewModel |
Defined in app/shared/top-menu/top-menu.component.ts:41
|
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; }
}
}
}
}
}
}
}
}
}