File

app/opc/product-detail-page/product-detail-page.component.ts

Implements

OnInit OnDestroy

Metadata

selector div[product-detail-page]
styleUrls ./product-detail-page.component.scss
templateUrl ./product-detail-page.component.html

Index

Properties
Methods

Constructor

constructor(searchFactory: SearchFactory, util: UtilService, linkService: ComponentLinkService, dataService: DataResolverService, pubService: PublicationService, constants: ConstantsService, rowResolver: RowResolverService, labelService: LabelService, pdfService: PdfDownloadService, loadingService: LoadingIndicatorService, activatedRoute: ActivatedRoute, doc)
Parameters :
Name Type Optional
searchFactory SearchFactory No
util UtilService No
linkService ComponentLinkService No
dataService DataResolverService No
pubService PublicationService No
constants ConstantsService No
rowResolver RowResolverService No
labelService LabelService No
pdfService PdfDownloadService No
loadingService LoadingIndicatorService No
activatedRoute ActivatedRoute No
doc No

Methods

Private addShortcut
addShortcut(shortcuts, position)
Parameters :
Name Optional
shortcuts No
position No
Returns : void
Private findTridionContent
findTridionContent(tableId: string)

Extract correct product spec based on tableId and setup the tridion content for the pdp

Parameters :
Name Type Optional
tableId string No
Returns : void
Private getPageData
getPageData()

Fetch data from parent page

Returns : void
Private getProductData
getProductData()

Do a search with designation from URL

Returns : void
Public goToCadDownload
goToCadDownload()

Scroll to CAD download component.

Returns : void
ngOnDestroy
ngOnDestroy()
Returns : void
ngOnInit
ngOnInit()
Returns : void
Private resolveAutomaticLinks
resolveAutomaticLinks(automaticLinks)
Parameters :
Name Optional
automaticLinks No
Returns : AutomaticButton[]
Private resultSubscription
resultSubscription()

Subscribe to search result

Returns : void
Public setCadDownloadVariables
setCadDownloadVariables(available)

Set CAD download variables

Parameters :
Name Optional
available No
Returns : void

Properties

automaticButtons
Type : AutomaticButton[]
cadAvailable
Default value : true
cadDownloadButtonTitle
Type : string
cadDownloadLabel
Type : string
Public constants
Type : ConstantsService
contentForPDPs
Type : []
Default value : []
designation
Type : string
exportPDFLabel
Type : string
imageUrls
Type : []
Default value : []
information
links
Type : []
Default value : []
navigation
noProductFound
Type : boolean
opcData
opcDoc
opcSearcher
Type : SearchService
page
shortcuts
Type : object
Default value : { ShortcutLink: [] }
subscriptions
Type : []
Default value : []
techSpecLabel
Type : string
tridionData
tridionPageData
Type : []
Default value : []
whereToBuyLabel
Type : string
import { OnInit, Component, OnDestroy, Inject } from '@angular/core';
import { SearchFactory, SearchService, SimpleSearchResult, HttpParams } from 'skf-search-angular-service';
import { UtilService } from 'src/app/core/services/util-service/util.service';
import { DOCUMENT } from '@angular/common';
import { ComponentLinkService } from 'src/app/core/services/component-link-service/component-link.service';
import { DataResolverService } from 'src/app/core/services/data-resolver/data-resolver.service';
import { PublicationService } from 'src/app/core/services/publication-service/publication.service';
import { ConstantsService } from 'src/app/shared/constants/constants.service';
import { RowResolverService } from 'src/app/core/services/row-resolver.service';
import { AppConfig } from '../../app.config';
import { LabelService } from 'src/app/core/services/label-service/label-service.service';
import { PdfDownloadService } from 'src/app/core/services/pdf-download-service/pdf-download.service';
import { LoadingIndicatorService } from 'src/app/core/services/loading-indicator.service';
import { ActivatedRoute } from '@angular/router';

interface AutomaticButton {
	labelText: string;
	onClick: () => void;
}

@Component({
	selector: 'div[product-detail-page]',
	templateUrl: './product-detail-page.component.html',
	styleUrls: ['./product-detail-page.component.scss']
})
export class ProductDetailPageComponent implements OnInit, OnDestroy {

	opcSearcher: SearchService;
	imageUrls = [];
	opcData;
	opcDoc;
	tridionPageData = [];
	tridionData;
	automaticButtons: AutomaticButton[];
	page;
	navigation;
	subscriptions = [];
	shortcuts = {
		ShortcutLink: []
	};
	links = [];
	information;
	contentForPDPs = [];
	noProductFound: boolean;
	designation: string;
	cadAvailable = true;

	// Label
	cadDownloadButtonTitle: string;
	techSpecLabel: string;
	whereToBuyLabel: string;
	exportPDFLabel: string;
	cadDownloadLabel: string;

	constructor(
		private searchFactory: SearchFactory,
		private util: UtilService,
		private linkService: ComponentLinkService,
		private dataService: DataResolverService,
		private pubService: PublicationService,
		public constants: ConstantsService,
		private rowResolver: RowResolverService,
		private labelService: LabelService,
		private pdfService: PdfDownloadService,
		private loadingService: LoadingIndicatorService,
		private activatedRoute: ActivatedRoute,
		@Inject(DOCUMENT) private doc
	) {
		this.opcSearcher = this.searchFactory.get(`opc-${AppConfig.settings.searchEnvironment.name}`);
	}

	ngOnInit(): void {
		this.labelService.getLabel('cadDownloadButtonTitle').then(label => this.cadDownloadButtonTitle = label);
		this.labelService.getLabel('technicalSpecificationOnPDPs').then(label => {
			this.techSpecLabel = label;
			this.addShortcut({
				Title: label,
				LinkedComponentId: 'spec'
			}, 0);
		});
		this.labelService.getLabel('prodSpecWhereToBuyButton').then(label => this.whereToBuyLabel = label);
		this.labelService.getLabel('prodSpecExportPDFbutton').then(label => this.exportPDFLabel = label);
		this.labelService.getLabel('prodSpecCadDownloadButton').then(label => this.cadDownloadLabel = label);

		this.resultSubscription();

		this.getPageData();
		// Fetch navigation data for top menu and breadcrumb
		this.dataService.getNavigationData().then(nav => this.navigation = nav);
	}

	ngOnDestroy(): void {
		this.subscriptions.forEach(sub => {
			if (sub) {
				sub.unsubscribe();
			}
		});
	}

	/** Subscribe to search result */
	private resultSubscription(): void {
		/** Results from search are received here */
		this.subscriptions.push(this.opcSearcher.result.subscribe(res => {
			this.loadingService.loadingDone();
			const result = res as SimpleSearchResult;
			const documents = this.util.extract(result, 'documentList', 'documents') || [];

			if (documents.length > 0) {
				this.opcData = result;
				this.opcDoc = documents[0];
				this.imageUrls.push(this.opcDoc ? this.opcDoc['image_url'] : 'v2/assets/img/no-image.png');

				/**
				 * Need to pass tabled Id since multiple product specs could have
				 * been placed on the parent page and we need to be able to find the correct product spec component
				*/
				const tableId = this.opcDoc.structure_group_id;
				this.findTridionContent(tableId);
			}
			// Variable for showing 'no results' message.
			this.noProductFound = documents.length <= 0;
		}));
	}

	/** Fetch data from parent page */
	private getPageData(): void {
		this.loadingService.startLoading();
		const url = this.activatedRoute.snapshot.url;
		
		// Get url to fetch tridion content from parent page
		const tridionUrl = url.splice(0, url.length - 1);
		const path = tridionUrl.map(segment => segment.path).join('/');
		this.dataService.getPageData(`/${path}`).then(res => {
			this.page = res;
			const mainReg = res['Regions'].find(region => region.Name === 'main');
			this.tridionPageData = mainReg['Entities'];
			this.getProductData();
		});
	}

	/** Extract correct product spec based on tableId and setup the tridion content for the pdp */
	private findTridionContent(tableId: string): void {
		this.tridionPageData.forEach(element => {
			const MvcData = element['MvcData'];
			if (MvcData['ViewName'] === 'ProductSpecMain' && element['ProductTableID'] === tableId) {
				this.tridionData = element;
			}
			// Product spec could be nested inside a tab
			if (MvcData['ViewName'] === 'TabMain') {
				const allTabs = this.util.extract(element, 'Entities');
				const allTabedContent = allTabs.map(tab => this.util.extract(tab, 'TabContnetEntities')).flat();
				const productSpec = allTabedContent.find(
					entity => this.util.extract(entity, 'MvcData', 'ViewName') === 'ProductSpecMain'
						&& this.util.extract(entity, 'ProductTableID') === tableId);

				// If this is not undefined means we found a product spec that matches the desired tableID
				if (productSpec) {
					this.tridionData = productSpec;
				}
			}
		});

		const contentList = this.util.extract(this.tridionData, 'ContentForPDPs');
		this.information = this.util.extract(this.tridionData, 'Information');
		this.automaticButtons = this.resolveAutomaticLinks(this.util.extract(this.tridionData, 'AutoLinks') || []);

		if (contentList) {
			// Remove shortcuts from the contentForPDPs and add it to the shortcuts
			const contentForPDPsTemp = [];
			contentList.forEach(element => {
				const MvcData = element['MvcData'];
				if (MvcData['ViewName'] === 'ShortcutsMain') {
					this.addShortcut(element['ShortcutLink'], 2);
				} else {
					contentForPDPsTemp.push(element);
				}
			});

			this.contentForPDPs = this.rowResolver.transform(contentForPDPsTemp, 'MAIN');
		}
		const linkTargets = this.util.extract(this.tridionData, 'LinkTarget') || [];
		this.links = linkTargets;
		linkTargets.forEach(link => {
			link['title'] = this.linkService.getTitle(link);
		});
	}

	private resolveAutomaticLinks(automaticLinks): AutomaticButton[] {
		// If the editors have included the 'None' value no automatic links should be added
		if (automaticLinks.includes('None')) {
			return [];
		}
		const autoLinks = [];
		// Functionallity for the where to buy button is not yet specified
		const whereToBuyOnClick: () => void = () => {

		};

		const exportPDFOnClick: () => void = () => {
			this.pdfService.download();
		};

		// Functionallity for the cad download button is not yet specified
		const cadDownloadOnClick: () => void = () => {

		};
		const whereToBuy: AutomaticButton = {
			labelText: this.whereToBuyLabel,
			onClick: whereToBuyOnClick
		};
		const exportPDF: AutomaticButton = {
			labelText: this.exportPDFLabel,
			onClick: exportPDFOnClick
		};
		const cadDownload: AutomaticButton = {
			labelText: this.cadDownloadLabel,
			onClick: cadDownloadOnClick
		};

		// Order here is important so that the where to buy is the leftmost button, CAD download is the second and export pdf the last
		if (automaticLinks.includes('Where to buy')) {
			autoLinks.push(whereToBuy);
		}
		if (automaticLinks.includes('CAD download')) {
			autoLinks.push(cadDownload);
		}
		if (automaticLinks.includes('Export to PDF')) {
			autoLinks.push(exportPDF);
		}

		return autoLinks;
	}

	/** Do a search with designation from URL */
	private getProductData(): void {
		const url = this.activatedRoute.snapshot.url;
		this.designation = url[url.length - 1].path.replace('productid-', '');
		
		const language = this.pubService.getLanguage();
		const pubId = this.pubService.getPublicationId();
		const system = this.activatedRoute.snapshot.queryParams['system'] || 'metric';

		const opcString = `designation=${this.designation}&searcher=details&language=${language}&site=${pubId}&system=${system}`;
		this.opcSearcher.doSearchWithParameters({
			params: new HttpParams({ 'fromString': opcString }),
		});
	}

	/** Scroll to CAD download component. */
	public goToCadDownload() {
		const cadComponent = this.doc.getElementById(this.constants.COMPONENT_ID_PREFIX + '-cad-download');

		if (cadComponent) {
			cadComponent.scrollIntoView(true);
		}
	}

	/** Set CAD download variables */
	public setCadDownloadVariables(available) {
		this.cadAvailable = available;
		if (this.cadAvailable) {
			this.addShortcut({
				Title: this.cadDownloadLabel,
				LinkedComponentId: 'cad-download'
			}, 1);
		}
	}

	private addShortcut(shortcuts, position): void {
		this.shortcuts['ShortcutLink'].splice(position, 0, ...shortcuts);

		this.shortcuts = {
			'ShortcutLink': [
				...this.shortcuts['ShortcutLink']
			]
		};
	}
}
<div class="pdp" *ngIf="opcDoc">
    <div class="product-info">
        <div class="container">
            <div class="row">
                <div class="col-md-5 image-container">
                    <div class="prod-info">
                        <h1
                            [ngClass]="{'title': true, 'title--blue': opcDoc.explorer, 'title--triangle': opcDoc.popular_item }">
                            {{opcDoc.title}}</h1>
                        <div *ngIf="opcDoc.popular_item" class="attribute">{{opcDoc.popular_item_label}}</div>
                        <div *ngIf="opcDoc.explorer" class="attribute">{{opcDoc.performance_class}}</div>
                        <div class="category">{{opcDoc.category}}</div>
                    </div>
                    <img class="pdf-picture" [src]="imageUrls[0]" alt="" crossorigin="Anonymous" />
                </div>
                <div class="col-md-7 info">
                    <h1 class="pdf-title"
                        [ngClass]="{'title': true, 'title--blue': opcDoc.explorer, 'title--triangle': opcDoc.popular_item }">
                        {{opcDoc.title}}
                    </h1>
                    <div *ngIf="opcDoc.popular_item" class="attribute">{{opcDoc.popular_item_label}}</div>
                    <div *ngIf="opcDoc.explorer" class="attribute">{{opcDoc.performance_class}}</div>
                    <p class="category pdf-category">{{opcDoc.category}}</p>
                    <div *ngIf="tridionData">
                        <p class="description pdf-description" *ngIf="tridionData.Description">
                            <rich-text-field [body]="tridionData.Description.BodyText" id="pdf-id-description">
                            </rich-text-field>
                        </p>
                        <div class="row">
                            <div class="col-md-6 col-sm-12">
                                <rich-text-field [body]="tridionData?.ColumnOne"></rich-text-field>
                            </div>
                            <div class="col-md-6 col-sm-12" [ngClass]="{'col-offset-md-6': !tridionData.ColumnOne}">
                                <rich-text-field [body]="tridionData?.ColumnTwo"></rich-text-field>
                            </div>
                        </div>
                        <div class="d-flex">
                            <div class="links" *ngIf="links">
                                <button class="mr-1 mb-1 btn btn-green cad-download cta-button"
                                    (click)="goToCadDownload()"
                                    *ngIf="cadAvailable">{{ cadDownloadButtonTitle }}</button>
                                <button class="mr-1 mb-1 btn btn-green cta-button" (click)="autoBtn.onClick()"
                                    *ngFor="let autoBtn of automaticButtons"> {{ autoBtn.labelText }}</button>
                                <a component-link class="component-link btn btn-green cta-button mr-1 mb-1"
                                    [linkObject]="link" *ngFor="let link of links">
                                    {{ link.title }}
                                </a>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <div class="shortcuts" *ngIf="shortcuts">
        <div shortcuts-main [entity]="shortcuts"></div>
    </div>
    <div technical-specification [data]="opcData" *ngIf="opcData"></div>    
</div>

<div class="container space-between" *ngIf="information">
    <rich-text-field [body]="information"> </rich-text-field>
</div>

<div cad-download [designation]="designation" (available)="setCadDownloadVariables($event)" *ngIf="designation && cadAvailable">
</div>

<ng-container *ngIf="contentForPDPs">
    <div dxa-entity [entities]="row" [region]="'MAIN'" *ngFor="let row of contentForPDPs"></div>
</ng-container>

./product-detail-page.component.scss

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

:host {
    height: 100%;
}

.pdp {
    padding-top: 2rem;

    .product-info {
        margin-bottom: calc-rem(50);

        .container {

            .category {
                @include font-scale(18, medium);
                color: $cool-grey;
            }
            
            .attribute
            {
                @include font-size(14);
            }

            .image-container {

                img {
                    display: block;
                    max-height: calc-rem(350);
                    max-width: 100%;
                    margin-left: auto;
                    margin-right: auto;
                }

                .prod-info {
                    @include media-breakpoint-up(md) {
                        display: none;
                    }
                }
            }

            .title { 
                @include font-size(42);
                display: flex;
                align-items: center;
            
                &--blue {
                    color: $dark-blue;
                }

                &--triangle:before {
                    @include font-size(36);
                    content: "\25ba";
                    color: $cool-grey;
                }
            }

            .info {
                display: flex;
                flex-direction: column;

                @media (max-width: map-get($grid-breakpoints, md) - 1) {
                    .title { display: none; }
                    .category { display: none; }
                    .attribute { display: none; }
                }

                .category {
                    margin-bottom: calc-rem(20);
                }

                .description {
                    color: $cool-grey;
                    margin-bottom: calc-rem(40);
                    line-height: 1.78;
                }

                .links {
                    display: flex;
                    flex-wrap: wrap;


                    .cta-button {
                        margin-right: 4px;
                        flex: 0 1 100%;
                        @include media-breakpoint-up(md) {
                            flex: 0 1 calc(50% - 4px);
                        }
                        @include media-breakpoint-up(lg) {
                            flex: 0 1 calc(33.3% - 4px);
                        }


                    }

                }
            }
        }
    }

    .shortcuts {
        background-color: $off-white;
    }
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""