app/dxa/dxa-base/dxa-base.component.ts
selector | div[dxa-base] |
styleUrls | ./dxa-base.component.scss |
templateUrl | ./dxa-base.component.html |
Properties |
Methods |
|
HostBindings |
constructor(activatedRoute: ActivatedRoute, titleService: Title, dataService: DataResolverService, publicationService: PublicationService, constants: ConstantsService, rowResolver: RowResolverService, util: UtilService, viewportScroller: ViewportScroller, labelService: LabelService, loadingService: LoadingIndicatorService, cdRef: ChangeDetectorRef)
|
||||||||||||||||||||||||||||||||||||
Defined in app/dxa/dxa-base/dxa-base.component.ts:46
|
||||||||||||||||||||||||||||||||||||
Parameters :
|
class.bg-light-slate |
Defined in app/dxa/dxa-base/dxa-base.component.ts:46
|
Private anchorScrollListener |
anchorScrollListener()
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:79
|
Returns :
void
|
enableBackToTop |
enableBackToTop()
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:96
|
Returns :
void
|
extractPageDetails |
extractPageDetails()
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:216
|
Finds and removes the Page Details component from the header if it exists.
Returns :
any
|
Public fetchPageAndNavigation |
fetchPageAndNavigation()
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:119
|
Returns :
void
|
goToTop |
goToTop()
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:308
|
Returns :
void
|
Private movePageDetailsToTop |
movePageDetailsToTop()
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:229
|
Returns :
void
|
ngOnInit |
ngOnInit()
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:62
|
Returns :
void
|
Private pageDetailsShouldBeMoved |
pageDetailsShouldBeMoved()
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:204
|
Returns :
boolean
|
Private resolveDynamicComponentInRegion | ||||||||
resolveDynamicComponentInRegion(region: any[])
|
||||||||
Defined in app/dxa/dxa-base/dxa-base.component.ts:286
|
||||||||
Resolve each dynamic component in the given region. Updates the @param region when the data has loaded.
Parameters :
Returns :
void
|
Private resolveDynamicComponentsInRow | ||||||||
resolveDynamicComponentsInRow(row: any[])
|
||||||||
Defined in app/dxa/dxa-base/dxa-base.component.ts:243
|
||||||||
Resolve each dynamic component in the given row. Returns an updated row when all components have been resolved.
Parameters :
Returns :
Promise<any>
|
Public setUpRegions |
setUpRegions()
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:142
|
Returns :
void
|
Public activatedRoute |
Type : ActivatedRoute
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:49
|
backToTopLabel |
Type : string
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:34
|
breadcrumbOnPage |
Type : boolean
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:27
|
Public constants |
Type : ConstantsService
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:53
|
darkTopRegion |
Type : boolean
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:26
|
dockToFooter |
Type : number
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:35
|
headerRegion |
Type : []
|
Default value : []
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:37
|
isLoading |
Default value : false
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:30
|
isPdp |
Default value : false
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:32
|
isSupportPage |
Type : boolean
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:28
|
isSystemPage |
Type : boolean
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:29
|
mainRegion |
Type : []
|
Default value : []
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:39
|
navigation |
Defined in app/dxa/dxa-base/dxa-base.component.ts:23
|
newPageDataSubject |
Default value : new Subject<boolean>()
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:43
|
newPageObs |
Default value : this.newPageDataSubject.asObservable()
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:44
|
page |
Defined in app/dxa/dxa-base/dxa-base.component.ts:22
|
regions |
Type : []
|
Default value : []
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:41
|
showScrollTopButton |
Type : boolean
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:31
|
subscriptions |
Type : []
|
Default value : []
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:24
|
topRegion |
Type : []
|
Default value : []
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:38
|
Public util |
Type : UtilService
|
Defined in app/dxa/dxa-base/dxa-base.component.ts:55
|
import { ActivatedRoute } from '@angular/router';
import { Component, OnInit, HostBinding, ChangeDetectorRef } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { DataResolverService } from 'src/app/core/services/data-resolver/data-resolver.service';
import { ConstantsService } from 'src/app/shared/constants/constants.service';
import { RowResolverService } from 'src/app/core/services/row-resolver.service';
import { UtilService } from 'src/app/core/services/util-service/util.service';
import { PublicationService } from 'src/app/core/services/publication-service/publication.service';
import { ViewportScroller } from '@angular/common';
import { debounceTime } from 'rxjs/operators';
import { LabelService } from 'src/app/core/services/label-service/label-service.service';
import { Subject, fromEvent } from 'rxjs';
import { LoadingIndicatorService } from 'src/app/core/services/loading-indicator.service';
@Component({
selector: 'div[dxa-base]',
templateUrl: './dxa-base.component.html',
styleUrls: ['./dxa-base.component.scss']
})
export class DxaBaseComponent implements OnInit {
page;
navigation;
subscriptions = [];
darkTopRegion: boolean;
breadcrumbOnPage: boolean;
isSupportPage: boolean;
isSystemPage: boolean;
isLoading = false;
showScrollTopButton: boolean;
isPdp = false;
backToTopLabel: string;
dockToFooter: number;
headerRegion = [];
topRegion = [];
mainRegion = [];
regions = [];
newPageDataSubject = new Subject<boolean>();
newPageObs = this.newPageDataSubject.asObservable();
@HostBinding('class.bg-light-slate') get bgColor() { return this.isSupportPage; }
constructor(
public activatedRoute: ActivatedRoute,
private titleService: Title,
private dataService: DataResolverService,
private publicationService: PublicationService,
public constants: ConstantsService,
private rowResolver: RowResolverService,
public util: UtilService,
private viewportScroller: ViewportScroller,
private labelService: LabelService,
private loadingService: LoadingIndicatorService,
private cdRef: ChangeDetectorRef
) { }
ngOnInit() {
this.labelService.getLabel('backToTopNav').then(label => this.backToTopLabel = label);
// Loading state for page loading spinner.
this.loadingService.contentLoaderState.subscribe(loadingState => {
this.isLoading = loadingState;
this.cdRef.detectChanges();
});
// Set the title to skf.com here, will be overwritten in the pagedetails entity
this.titleService.setTitle('SKF.com');
this.fetchPageAndNavigation();
this.anchorScrollListener();
this.enableBackToTop();
}
private anchorScrollListener() {
// Scroll to anchor when new page data loads if anchor links exists in URL.
this.viewportScroller.setOffset([0, 30]);
this.newPageObs.subscribe(() => {
const frag = this.activatedRoute.snapshot.fragment;
if (frag) {
// Wait for components to be rendered before scrolling.
setTimeout(() => {
document.getElementById(frag).scrollIntoView({behavior: 'smooth'});
}, 1000);
} else {
// If no anchor tag, scroll to top of page.
this.viewportScroller.scrollToPosition([0, 0]);
}
});
}
enableBackToTop(): void {
fromEvent(document, 'scroll').pipe(debounceTime(100)).subscribe(() => {
this.showScrollTopButton = window.pageYOffset > 200;
const footerObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const rect = entry.target.getBoundingClientRect();
const intersect: number = entry.intersectionRatio === 1 ? rect.height : (entry.intersectionRatio * rect.height);
this.dockToFooter = intersect;
} else {
this.dockToFooter = 0;
}
}, { rootMargin: '0px', threshold: 1.0 });
});
const sole = document.querySelector('.footer-sole-region');
if (sole) {
footerObserver.observe(sole);
}
});
}
public fetchPageAndNavigation(): void {
// Fetch page and navigation data when URL changes
this.subscriptions.push(this.activatedRoute.url.subscribe(res => {
this.isPdp = this.activatedRoute.snapshot.data.isPDP;
if (!this.isPdp) { // Product Detail Pages are not Tridion pages, so we don't try to fetch the page from Tridion.
const url = '/' + res.join('/');
this.loadingService.startLoading();
this.dataService.getPageData(url).then(page => {
this.loadingService.loadingDone();
this.page = page;
this.setUpRegions();
this.newPageDataSubject.next(true);
}, () => {
this.loadingService.loadingDone();
});
}
this.dataService.getNavigationData().then(nav => this.navigation = nav);
}));
}
public setUpRegions() {
this.darkTopRegion = false;
this.breadcrumbOnPage = true;
this.isSupportPage = this.util.extract(this.page, 'MvcData', 'ViewName') === 'supportPage';
// Do not show breadcrumbs on system pages
const re = RegExp(this.publicationService.getPublicationPath() + '/system');
this.isSystemPage = re.test(this.util.extract(this.page, 'Url'));
const regions = this.page.Regions;
const headReg = regions.find(region => region.Name === 'header');
this.headerRegion = headReg ? headReg.Entities : [];
const topReg = regions.find(region => region.Name === 'top');
this.topRegion = topReg ? topReg.Entities : [];
const mainReg = regions.find(region => region.Name === 'main');
this.mainRegion = mainReg ? mainReg.Entities : [];
// Business requirement: If PageDetails is the last or only entity in header region, move it to:
// * TopBannerTop if it's the first entity in the top region.
// * TopBannerMain if top region is empty and it's the first entity in main region.
const topBannerTop = this.util.extract(this.topRegion, [0]); // Extract first entity in topRegion
const topBannerMain = this.util.extract(this.mainRegion, [0]); // Extract first entity in mainRegion
let pageDetailsData;
let topBanner;
const topViewName = this.util.extract(topBannerTop, 'MvcData', 'ViewName');
if (topViewName === 'TopBannerTop' || topViewName === 'TopBannerWSearchTop' || topViewName === 'TopBannerWSearchLargeTop') {
topBanner = topBannerTop;
} else if (this.util.extract(topBannerMain, 'MvcData', 'ViewName') === 'TopBannerMain' && this.topRegion.length === 0) {
topBanner = topBannerMain;
}
if (topBanner) {
pageDetailsData = this.extractPageDetails();
if (pageDetailsData) {
topBanner.PageDetailsData = pageDetailsData;
topBanner.includeBreadcrumb = true;
}
this.breadcrumbOnPage = false;
}
// Code above might move PageDetails if rules apply. That's expected.
if (this.page.MvcData.ViewName === 'contentWTopArea' && this.pageDetailsShouldBeMoved()) {
this.movePageDetailsToTop();
}
this.headerRegion = this.rowResolver.transform(this.headerRegion, 'HEADER');
this.mainRegion = this.rowResolver.transform(this.mainRegion, 'MAIN');
this.topRegion = this.rowResolver.transform(this.topRegion, 'TOP');
// Currently only 'MAIN' has dynamic components.
this.resolveDynamicComponentInRegion(this.mainRegion);
}
/*
These are used to fulfil the business requirement that if the a component using the BodyText [top] component template
is present on the page and if the header region has a component using the PageDetails [header] component template then the component
using the PageDetails [header] component template should be removed from the header region amd added to the top region
*/
private pageDetailsShouldBeMoved(): boolean {
let isPageDetailPresent = false;
let isBodyTextPresent = false;
isBodyTextPresent = this.topRegion.find(entity => entity.MvcData.ViewName === 'BodyText');
isPageDetailPresent = this.headerRegion.find(entity => entity.MvcData.ViewName === 'PageDetails')
|| this.headerRegion.find(entity => entity.MvcData.ViewName === 'PageDetailsHorizontal');
return isPageDetailPresent && isBodyTextPresent;
}
/** Finds and removes the Page Details component from the header if it exists. */
extractPageDetails() {
const headerRegion = this.page.Regions.find(region => region.Name === 'header');
if (headerRegion && headerRegion.Entities.length > 0) {
const pageDetails = this.util.extract(headerRegion, 'Entities', [headerRegion.Entities.length - 1], 'MvcData', 'ViewName');
if (pageDetails === 'PageDetails' || pageDetails === 'PageDetailsHorizontal') {
const pdComponent = headerRegion.Entities.pop();
return pdComponent;
}
}
}
private movePageDetailsToTop(): void {
// delete pageDetails from header region
const pageDetails = this.util.pickOneFromArray(this.headerRegion, entitiy => entitiy.MvcData.ViewName === 'PageDetails')
|| this.util.pickOneFromArray(this.headerRegion, entitiy => entitiy.MvcData.ViewName === 'PageDetailsHorizontal');
// Add it as the first element in the top region
this.topRegion.unshift(pageDetails);
this.darkTopRegion = true;
}
/**
* Resolve each dynamic component in the given row. Returns an updated row when all
* components have been resolved.
* @param row A row in a region.
*/
private resolveDynamicComponentsInRow(row: any[]): Promise<any> {
let finishedEntity = 0;
const newRow = new Array(row.length);
return new Promise<any>((resolve, reject) => {
row.forEach((entity, index) => {
// If the Ids is of the format <number>-<number> then we must resolve it manually. Since it's a dynamic component.
const id = this.util.extract(entity, 'Id') || '';
if (id.includes('-')) {
const arrayIds = id.split('-');
const componentId = arrayIds[0];
const templateId = arrayIds[1];
this.dataService.getDynamicComponent(componentId, templateId).then((resolvedContent) => {
newRow[index] = resolvedContent;
finishedEntity += 1;
// Return updated row when all components have been resolved.
if (finishedEntity === row.length) {
resolve(newRow);
}
}).catch(error => {
// Keep track even if component gives error.
finishedEntity += 1;
if (finishedEntity === row.length) { resolve(newRow); }
console.log(error);
});
} else {
newRow[index] = entity;
finishedEntity += 1;
// Return updated row when all components have been resolved.
if (finishedEntity === row.length) { resolve(newRow); }
}
});
});
}
/**
* Resolve each dynamic component in the given region. Updates the @param region when the data has loaded.
* @param region The region
*/
private resolveDynamicComponentInRegion( region: any[]): void {
region.forEach((row, index) => {
const entity = this.util.extract(row, [0]); // Extract first entity in row
const entityViewName = this.util.extract(entity, 'MvcData', 'ViewName');
// Resolve tab content dynamic components.
if (entityViewName === 'TabMain') {
const entityCopy = Object.assign({}, entity); // Make copy to make sure that re-render occurs.
const innerEntities = entityCopy ? entityCopy.Entities : [];
innerEntities.forEach((innerEntity, i) => {
const tabContnetEntities = innerEntity ? innerEntity.TabContnetEntities : [];
this.resolveDynamicComponentsInRow(tabContnetEntities).then(updated => {
innerEntities[i].TabContnetEntities = updated;
region[index] = [entityCopy];
});
});
} else {
this.resolveDynamicComponentsInRow(row).then(updated => region[index] = updated);
}
});
}
goToTop() {
window.scroll(
{
'behavior': 'smooth',
'left': 0,
'top': 0
}
);
}
}
<div cookies-banner></div>
<nav auth-bar></nav>
<nav top-menu role="navigation" [page]="page" [navigation]="navigation" *ngIf="navigation && !navigation.error"></nav>
<div *ngIf="navigation && navigation.error">NAVIGATION ERROR</div>
<div class="loading" *ngIf="isLoading">
<div class="lds-dual-ring">
<img class="logo" src="v2/assets/img/skf-logo-blue.svg">
</div>
</div>
<button class="btn scroll-top" (click)="goToTop()" [ngClass]="{'show-scrollTop': showScrollTopButton}" [ngStyle]="{'bottom.px': dockToFooter}">
<i class="icon-chevronUp"></i> {{ backToTopLabel }}
</button>
<ng-container *ngIf="page && page.error && !isLoading">
<div class="page-not-found">
<div class="container">
<h1 class="notfound-header">Error</h1>
<h2>Uh-oh, something went wrong.</h2>
<div class="searchbox">
<search-input></search-input>
</div>
</div>
</div>
</ng-container>
<div [hidden]="isLoading">
<div breadcrumb [ngClass]="{'bg-cool-grey': darkTopRegion}" [themeColor]="darkTopRegion ? 'light' : 'dark'" *ngIf="(breadcrumbOnPage && !isSystemPage && !isLoading) || isPdp"></div>
<ng-container *ngIf="!isPdp">
<header id="header" role="banner" *ngIf="headerRegion.length > 0">
<section dxa-entity [entities]="row" [region]="'HEADER'" *ngFor="let row of headerRegion"></section>
</header>
<header id="top" role="banner" *ngIf="topRegion.length > 0"
[ngClass]="{'bg-cool-grey top-region-body-text': darkTopRegion}">
<section dxa-entity [entities]="row" [region]="'TOP'" *ngFor="let row of topRegion"></section>
</header>
<main id="main" role="main" *ngIf="mainRegion.length > 0">
<section dxa-entity [entities]="row" [region]="'MAIN'" *ngFor="let row of mainRegion"></section>
</main>
</ng-container>
<div product-detail-page *ngIf="isPdp"></div>
</div>
<footer footer role="contentinfo"></footer>
./dxa-base.component.scss
@import "src/app/styles/helpers";
@keyframes fadein {
0% { opacity: 0; }
25% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes lds-dual-ring {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
:host {
display: flex;
flex-direction: column;
height: 100%;
position: relative;
overflow-x: hidden;
}
.loading {
animation: fadein 1.25s;
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
> .lds-dual-ring {
position: relative;
$size: 300px;
$color: #0f58d6;
display: flex;
align-items: center;
justify-content: center;
width: $size;
height: $size;
&:after {
content: " ";
display: block;
width: $size;
height: $size;
margin: 1px;
border-radius: 50%;
border: 2px solid #fff;
border-color: $color $color transparent transparent;
animation: lds-dual-ring 0.7s linear infinite;
}
> .logo {
position: absolute;
top: 45%;
left: 25%;
width: 150px;
}
}
}
.scroll-top {
position: fixed;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
bottom: 0;
right: 0;
background-color: $blue;
color: white;
opacity: 0;
transition: opacity .25s ease-in-out, bottom .25s ease-in;
z-index: 1;
padding: calc-rem(12);
font-family: get-font-family(skf-medium);
@include font-size(10);
text-transform: uppercase;
line-height: 1.2;
letter-spacing: calc-rem(1.4);
width: calc-rem(68);
border: none;
outline: none;
box-shadow: none;
@include media-breakpoint-up(md) {
@include font-scale(14, demibold);
line-height: 1.43;
letter-spacing: calc-rem(2);
padding: calc-rem(15);
width: calc-rem(96);
i {
@include font-size(14);
}
}
&:hover {
background-color: $dark-blue;
}
&.show-scrollTop {
opacity: 1;
}
i {
@include font-size(12);
margin: 0;
margin-bottom: calc-rem(4);
width: auto;
}
}
.page-not-found {
background-image:
linear-gradient( rgba(0,0,0,.7), rgba(0,0,0,.7) ),
url('https://images.unsplash.com/photo-1464490997959-0c65eee1cc26?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=a94e63e6f9e5de6163c5343adfe75689&auto=format&fit=crop&w=1050&q=80');
background-size: cover;
background-position: center;
color: white;
h1, h2, h3, h4, h5 { color: white; }
padding: 150px 0;
text-align: center;
.notfound-header {
font-size: 120px;
}
.searchbox {
margin-top: 50px;
max-width: 950px;
margin-left: auto;
margin-right: auto;
}
}