0

I am following the Microsoft tutorial for creating an Application Customizer. That works great, but now I would like to add some custom things like a curtain menu to my site.

I can't seem to figure out how to add an event listener to an element that is being rendered from a promise. The element doesn't exist initially, so if I try the ID, I get a null error. If I use a class it gives me an inline script error.

I've searched for a solution, but don't see one that applies to what I am doing.

I know this code is a mess, but I've been trying so many different methods, I can't seem to find one that works.

import { override } from '@microsoft/decorators';
import { Log } from '@microsoft/sp-core-library';
import {
  BaseApplicationCustomizer,
  PlaceholderContent,
  PlaceholderName,
  PlaceholderProvider
} from '@microsoft/sp-application-base';
import { Dialog } from '@microsoft/sp-dialog';

import * as strings from 'HideSideNavApplicationCustomizerStrings';
// import * as strings from './myStrings';
import styles from './AppCustomizer.module.scss';
import {escape} from '@microsoft/sp-lodash-subset';
import Placeholder from '@microsoft/sp-application-base/lib/extensibility/placeholder/Placeholder';

const LOG_SOURCE: string = 'HideSideNavApplicationCustomizer';
let openBtn = "openBtn";

export interface IHideSideNavApplicationCustomizerProperties {
  // This is an example; replace with your own property
  testMessage: string;
  Top: string;
  openBtn: string;
}

/** A Custom Action which can be run during execution of a Client Side Application */
export default class HideSideNavApplicationCustomizer
  extends BaseApplicationCustomizer<IHideSideNavApplicationCustomizerProperties> {

    private _topPlaceholder: PlaceholderContent | undefined;

  @override
  public onInit(): Promise<void> {  
    this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
    return Promise.resolve();
  }

  private _renderPlaceHolders(): void {
    console.log('calling _renderPlaceHolders');
    console.log(
      "Available placeholders: ",
      this.context.placeholderProvider.placeholderNames
      .map(name => PlaceholderName[name])
      .join(", ")
      );

    if(!this._topPlaceholder){
      this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(
        PlaceholderName.Top,
        { onDispose: this.onDispose}
      )
    }

    if(!this._topPlaceholder) {
      console.error("The expected placeholder (Top) was not found.");
      return;
    }
    
    if(this.properties){
      let topString: string = `
      <!-- The overlay -->
      <div id="myNav" class="navClose overlay">
      
        <!-- Button to close the overlay navigation -->
        <a href="javascript:void(0)" class="closebtn" onclick="closeNav()">&times;</a>
      
        <!-- Overlay content -->
        <div class="overlay-content">
          <a href="#">About</a>
          <a href="#">Services</a>
          <a href="#">Clients</a>
          <a href="#">Contact</a>
        </div>
      
      </div>
      
      <!-- Use any element to open/show the overlay navigation menu -->
      <span class="navOpen">open</span>

      `;

      if(!topString){
        topString = "(Top property was not defined.)";
      }
    
      if(this._topPlaceholder.domElement){
        this._topPlaceholder.domElement.innerHTML=topString;
        let navState :string = "closed";
        const navClose = document.getElementsByClassName("navClose").item(0);
        this._topPlaceholder.domElement.addEventListener("click", function(e){
          
          if(navState == "closed"){
            navClose.setAttribute(this.style.width, "100%");
            navState = "opened";
          }
          else{
            navClose.setAttribute(this.style.width, "0");
            navState = "closed";
          }        
        });
      } 
    }
  }

  private _onDispose(): void {
    console.log('[HideSideNavApplicationCustomizer._onDispose] Dispose custom top and bottom.')
  }
}
Will73
  • 55
  • 7

1 Answers1

0

So I think I finally figured this out. I wasn't using 'this' correctly after the promise was made, and there were a number of syntax errors with how I was trying to update the attributes on the elements.

Here's my updated code in case anyone is trying to do something similar.

import { override } from '@microsoft/decorators';
import { Log } from '@microsoft/sp-core-library';
import {
  BaseApplicationCustomizer,
  PlaceholderContent,
  PlaceholderName,
  PlaceholderProvider
} from '@microsoft/sp-application-base';
import styles from './AppCustomizer.module.scss';

const LOG_SOURCE: string = 'HideSideNavApplicationCustomizer';
let openBtn = "openBtn";

export interface IHideSideNavApplicationCustomizerProperties {
  // This is an example; replace with your own property
  testMessage: string;
  Top: string;
}

/** A Custom Action which can be run during execution of a Client Side Application */
export default class HideSideNavApplicationCustomizer
  extends BaseApplicationCustomizer<IHideSideNavApplicationCustomizerProperties> {

    private _topPlaceholder: PlaceholderContent | undefined;

  @override
  public onInit(): Promise<void> {  
    this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
    return Promise.resolve();
  }

  private _renderPlaceHolders(): void {
    console.log('calling _renderPlaceHolders');
    console.log(
      "Available placeholders: ",
      this.context.placeholderProvider.placeholderNames
      .map(name => PlaceholderName[name])
      .join(", ")
      );

    if(!this._topPlaceholder){
      this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(
        PlaceholderName.Top,
        { onDispose: this.onDispose}
      )
    }

    if(!this._topPlaceholder) {
      console.error("The expected placeholder (Top) was not found.");
      return;
    }
    
    if(this.properties){
      let topString: string = `
      <!-- The overlay -->
      <div id="nav" class="${styles.overlay}">
      
        <!-- Button to close the overlay navigation -->
        <a href="javascript:void(0)" id="navClose" class="${styles.closebtn}">&times;</a>
      
        <!-- Overlay content -->
        <div class="${styles['overlay-content']}">
          <a href="#">About</a>
          <a href="#">Services</a>
          <a href="#">Clients</a>
          <a href="#">Contact</a>
        </div>
      
      </div>
      
      <!-- Use any element to open/show the overlay navigation menu -->
      <span id="navOpen">open</span>
      `;
    
      if(!topString){
        topString = "(Top property was not defined.)";
      }
    
      if(this._topPlaceholder.domElement){
        const top = this._topPlaceholder.domElement;
        top.innerHTML=topString;
        
        let nav = top.querySelector('#nav');
        let navOpen = top.querySelector('#navOpen');
        let navClose = top.querySelector('#navClose');

        navOpen.addEventListener("click", () => {
          nav.setAttribute("style","width:75%;");
        });

        navClose.addEventListener("click", () => {
          nav.setAttribute("style","width:0%;");
        });
      }
    }
  }

  private _onDispose(): void {
    console.log('[HideSideNavApplicationCustomizer._onDispose] Dispose custom top and bottom.')
  }
}
Will73
  • 55
  • 7