How to override OOB footer component in SAP Spartacus and make it collapsible in mobile device?

Follow below steps to override OOB Spartacus footer component:

1. Create custom footer component typescript file(TestFooterNavigationComponent). copy the content/code from OOB FooterNavigationComponent file and paste it in custom component

import { ChangeDetectionStrategyComponent } from '@angular/core';
import { CmsNavigationComponent } from '@spartacus/core';
import { CmsComponentDataNavigationNode } from '@spartacus/storefront';
import { Observable } from 'rxjs/internal/Observable';
import { map } from 'rxjs/operators';

@Component({
  selector: 'app-bdifooter-navigation',
  templateUrl: './bdifooter-navigation.component.html',
  changeDetection:ChangeDetectionStrategy.OnPush
})
export class TestFooterNavigationComponent   {

//using it to display current year for footer copyright text
todaynumber = Date.now();

  node$Observable<NavigationNode> = this.service.getNavigationNode(
    this.componentData.data$
  );

  styleClass$Observable<string> = this.componentData.data$.pipe(
    map((d=> d?.styleClass)
  );

  constructor(
    protected componentDataCmsComponentData<CmsNavigationComponent>,
    protected serviceNavigationService
  ) {}
   
    
  
}

 

2. Create custom footer  component HTML file. Copy the content of OOB footer component HTML located at:

C:\spartacus\spartacus-schematics-2.1.1\projects\storefrontlib\src\cms-components\navigation\footer-navigation\footer-navigation.component.html 

and modify the selector based on custom UI component

<!-- custom UI component which is used to display footer UI. We you can refer it in
next steps below--> 
<!-- component to display custom UI/modified UI -->
 <app-testfooter-navigation-ui
  [node]="node$ | async"
  [ngClass]="styleClass$ | async"
  [iscollapsible]="true"
  [isOpen]="true"
></app-testfooter-navigation-ui> 

<!-- footer Copy right Text -->
<ng-container >
  <div class="cx-notice">
    &copy;  {{ 'testFooter.copyright.beforedate' | cxTranslate | uppercase }}
 {{ today | cxDate: 'yyyy' }}  
{{ 'bdiFooter.copyright.afterdate' | cxTranslate | uppercase }}
  </div>
</ng-container>


3. Create footer navigation Module typescript file and use configModule.withConfig to override OOB FooterNavigationComponent(highlighted in Green below) with Custom component TestFooterNavigationComponent

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ConfigModuleI18nModuleUrlModule } from '@spartacus/core';
import { FooterNavigationModuleGenericLinkModuleIconModuleLinkModuleMediaModuleNavigationModule } from '@spartacus/storefront';
import { BDIFooterNavigationUIComponent } from './bdifooter-navigation-ui.component';
import { BDIFooterNavigationComponent } from './bdifooter-navigation.component';
import { BDISocialMediaNavigationUIComponent } from './bdisocialmedia-navigation-ui.component';
import { BDISocialMediaNavigationComponent } from './bdisocialmedia-navigation.component';



@NgModule({
  declarations: [TestFooterNavigationComponent,TestFooterNavigationUIComponent],
  imports: [
    CommonModule,
    NavigationModule,
    FooterNavigationModule,
    IconModule,
    LinkModule,
    GenericLinkModule,
    UrlModule,
    I18nModule,
    MediaModule,
    ConfigModule.withConfig({
      cmsComponents: {
       FooterNavigationComponent:{
        component:TestFooterNavigationComponent // this is custom component
       },
      }
     })
  ],
  exports: [TestFooterNavigationComponent,TestFooterNavigationUIComponent],
})
export class TestFooterNavigationModule { }


4. Include module in app.module.ts file


@NgModule({
  declarations: [AppComponentPOReportFormComponentMockloginComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    LoginModule,
    ReactiveFormsModule,
    RouterModule,
    UrlModule,
    I18nModule,
    SpinnerModule,
    FormErrorsModule,
    DateTimePickerModule,
    TestFooterNavigationModule, 

    B2cStorefrontModule.withConfig({
      backend: {
        occ: {
          baseUrl: 'https://localhost:9002',
          prefix: '/rest/v2/',


5. Create UI component and copy the content from OOB UI component located at :

C:\spartacus\spartacus-schematics-2.1.1\projects\storefrontlib\src\cms-components\navigation\navigation\navigation-ui.component.ts

and modified as shown below. remove unnecessary code and keep the one which is required.

import { ChangeDetectionStrategyComponentHostBindingInputOnDestroyRenderer2 } from '@angular/core';
import { NavigationNode } from '@spartacus/storefront';


@Component({
selector :'app-bdifooter-navigation-ui',
templateUrl:'./bdifooter-navigation-ui.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TestFooterNavigationUIComponent implements OnDestroy {

   @Input() nodeNavigationNode;
    
   @Input() @HostBinding('class.is-open'isOpen = true;

   @Input() @HostBinding('class.isCollapse'iscollapsibletrue;
 
   private openNodesHTMLElement[] = [];
        
   constructorprivate rendererRenderer2 ) {  }
 
// function which hadles collpsible behaviour in mobile device
   toggleOpen(eventUIEvent): void {
     if (event.type === 'keydown') {
       event.preventDefault();
     }
     const node = <HTMLElement>event.currentTarget;
     if (this.openNodes.includes(node)) {
         this.openNodes = this.openNodes.filter((n=> n !== node);
         this.renderer.removeClass(node'is-open');
     
     } else {
      this.renderer.addClass(node'is-open');
      this.openNodes.push(node);
     
     }
     this.updateClasses(node
     event.stopImmediatePropagation();
     event.stopPropagation();
   }

   private updateClasses(currentNode:HTMLElement): void {
     this.openNodes.forEach((node)=>{
      if(node !== currentNode){
        this.renderer.removeClass(node'is-open');
      }
      } );
      this.openNodes = this.openNodes.filter((n)=> n == currentNode)
    
   }

  ngOnDestroy() {
     this.openNodes = [];
   }
   
   
}


6. create HTML for UI component. copy and paste the content from OOB UI component located at :

C:\spartacus\spartacus-schematics-2.1.1\projects\storefrontlib\src\cms-components\navigation\navigation\navigation-ui.component.html

and modify it based on your requirement



<ng-container *ngFor="let child of node?.children">
  <ng-container *ngTemplateOutlet="nav; context: { node: child, depth: 0 }">
  </ng-container>
</ng-container>

<!-- we generate links in a recursive manner -->
<ng-template #nav let-node="node" let-depth="depth">
  <nav (click)="toggleOpen($event)" >
    <cx-generic-link
      *ngIf="
        node.url && (!node.children || node.children?.length === 0);
        else heading
      "
      [url]="node.url"
      [target]="node.target"
      [style]="node.styleAttributes"
      [class]="node.styleClasses"
    >
      {{ node.title }}
      </cx-generic-link>

    <ng-template #heading>
      <h5 [attr.aria-label]="node.title"  >
        {{ node.title }}
      </h5>
    </ng-template>

    <!-- we add a wrapper to allow for better layout handling in CSS -->
    <div class="wrapper" *ngIf="node.children?.length > 0">
      <cx-generic-link
        *ngIf="node.url"
        [url]="node.url"
        [target]="node.target"
        class="all"
      >
        {{ 'navigation.shopAll' | cxTranslate: { navNode: node.title } }}
      </cx-generic-link>
      <div  class="childs" >
        <ng-container *ngFor="let child of node.children">
          <ng-container
            *ngTemplateOutlet="nav; context: { node: child }"
          >
          </ng-container>
        </ng-container>
      </div>
    </div>
  </nav>
</ng-template>



7. Create SCSS file for you custom component or extend the OOB one

for footer navigation component:

app-testfooter-navigation {
  a {
    font-sizevar(--cx-font-small0.8rem);
    &:hover {
      colorvar(--cx-color-inverse);
      text-decorationunderline;
    }
  }
}

for footer Navigation UI component:

%testfooter-nav-wrapper {
  .wrapper {
    cursordefault;
  }
  // create width of wrapper
  .wrapper[attr='1'] {
    width200px;
  }
  .wrapper[attr='2'] {
    width400px;
  }
  &.isCollapse {
    .wrapper {
      @include media-breakpoint-down(xs) {
        height0;
        overflowhidden;
      }
      //color: #000;
    }
  }
}

%testfooter-nav-heading {
  nav {
    &:focus {
      colorvar(--cx-g-color-primary);
    }
  }

  h5 {
    margin0;
  }
  &.isCollapse {
    h5,
    cx-generic-link {
      displayflex;
      align-itemscenter;
      white-spacenowrap;

      colorcurrentColor;
      @include media-breakpoint-down(xs) {
        &:hover {
          colorvar(--cx-color-primary);
        }
        &:focus {
          z-index1;
          positionrelative;
        }
      }
      a {
        displayblock;
        width100%;
        &:focus {
          z-index1;
          positionrelative;
        }
      }
    }

    @include media-breakpoint-down(xs) {
      // make all first level hedings uppercase in mobile view
      > nav {
        > h5,
        > cx-generic-link {
          text-transformuppercase;
          font-weight600;
        }
      }
      h5 {
        border-bottom1px solid var(--cx-color-light);
        displayflex;
        justify-contentspace-between;

        cursorpointer;
      }

      h5,
      cx-generic-link a {
        padding1rem;
      }
    }

    @include media-breakpoint-up(lg) {
      > nav {
        cursorpointer;
        // top level headings
        > h5 {
          margin-top3px;
          padding20px 15px 10px 0;
        }
        // first level headings will be bold in desktop
        nav > h5 {
          @include type('5');
          cursordefault;

          &:hover {
            colorcurrentColor;
          }
        }
      }
    }
  }

  cx-generic-link.all {
    text-decorationunderline;
  }
}

@include media-breakpoint-up(lg) {
  cx-navigation-ui > nav > cx-generic-link > a {
    padding20px 15px 22px 0;
  }
  nav > div > cx-generic-link {
    padding10px 0;
  }
  div.childs > nav > cx-generic-link > a {
    padding5px 0;
  }
}

%bdifooter-nav-links {
  a {
    colorcurrentColor;
  }
}

app-testfooter-navigation-ui {
  displayflex;

  @extend %testfooter-nav-heading;
  @extend %testfooter-nav-wrapper;
  @extend %testfooter-nav-links;
  nav {
    outlinenone;
  }
  // first level heading / links
  h5 {
    text-transformuppercase;
    @include type('5');
    margin-bottom20px;
  }

  justify-contentcenter;
  @include media-breakpoint-down(sm) {
    flex-directioncolumn;
  }
  > nav {
    margin3vw;
  }

  &.isCollapse {
    @include media-breakpoint-down(xs) {
      > nav {
        displayblock;
        cx-generic-link.all {
          displaynone;
        }
      }
      &.is-open {
        nav.is-open {
          displayinitial;
          cx-generic-link.all {
            displayinitial;
          }
          > .wrapper {
            heightauto;
          }
        }
      }
    }
  }
}

8. Save all changes and run ng serve command to test your changes.


Good Luck.

=========================================================================













Comments

Popular posts from this blog

Hybris / SAP Commerce Cloud Groovy Scripting Job to Generate CSV/Excel Reports and copy to Commerce cloud Blob Storage

Emma's dream - a kids story - By Kavya

Hybris/ SAP commerce Cloud: Retry failed/not sent emails due to SMTP connection issue.