SOLVED

Debugging SPFx App Customizer

Iron Contributor

I have had great success debugging SPFx web parts using the Chrome extension. Is there a way to get this to work with the framework extensions? I'm trying to build an Application Customizer that builds a menu in the header based on the contents of a SP links list ('OrangeLinks') and having a heck of time figuring out why I can't get it to work.

 

The idea is pretty simple: in the onRender() event I am assigning some html to the _headerPlaceholder.domElement.innerHTML, then calling a method to get the links out of the list which in turn calls a method to use the returned data to construct the html for the header. When all the data has been turned into HTML links, I am assigning that to the <div> that has already been put in the _headerPlaceholder. Here's the code:

  public onRender(): void {
    if (!this._headerPlaceholder) {
      this._headerPlaceholder = this.context.placeholders.tryAttach(
        'PageHeader',
        {
          onDispose: this._onDispose
        });

      // The extension should not assume that the expected placeholder is available.
      if (!this._headerPlaceholder) {
        console.error('The expected placeholder was not found.');
        return;
      }

      if (this.properties) {
        let headerString: string = this.properties.Header;
        if (!headerString) {
          headerString = '(Header property was not defined.)';
      }

      if (this._headerPlaceholder.domElement) {
         this._headerPlaceholder.domElement.innerHTML = `
                 <div class="${styles.app}">
                   <div class="ms-bgColor-orange ms-fontColor-white ${styles.header}">
                     <div id="orangeLinks"></div>
                   </div>
                 </div>`;

      } 
      this.getOrangeLinks();
    }
  }

 private getOrangeLinks(): void {
    pnp.sp.web.lists.getByTitle('OrangeLinks').items.select('URL').get().then((links: any) => {
      this.buildOrangeLinks(links);
    });
  }

  private buildOrangeLinks(links: any): void {
    let linkList: string = ``;
    for (let i: number = 0; i < links.length; i++) {
      //--- if there are already some links, add spaces before this one
      if (i > 0) {
        linkList += `&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;`;
      }
      let link = `<a href="${escape(links[i].URL.Url)}" target="_blank">${escape(links[i].URL.Description)}</a>`;
      linkList += link;
    }
    $('#orangeLinks').html(linkList);
    return;
  }

All I get is blank header.

Empty HeaderEmpty Header

Funny enough, this scenario (render()->pnp data function->html building function & assignment by jQuery) works just fine in a SPFx web part, so something about the scope or timing of execution must be different.  Any thoughts would be most welcome.

 

7 Replies

Hi @Joseph Ackerman,

 

I suspect that 

pnp.sp.web.lists.getByTitle('OrangeLinks').items.select('URL').get().then((links: any) => {
... }

isn't returning any links.

Can you add console.log("started")  at the beginning of the getOrangeLinks method to make sure that the method is called at all.

 

then can you add a console.log(links) to make sure that we get any results returned?

 

 

 

Hello @Pieter Veenstra, Thanks for the reply.

 

It does indeed seem that no links are being returned but it turns out not to be what I thought. I added the console log call as you suggested like so:

 

PnP Call in my codePnP Call in my code

But when I went to the console tab of the inspector window in my browser, I noticed this:

 

Incorrect REST Call generated by PnPIncorrect REST Call generated by PnPFor some reason it seems that the translation from the PnP call to the REST call is adding the SitePages folder to the path; it's now obvious why the call is not returning any items! If I remove the offending folder from the path and just plug the result into the address bar of the browser, you can see that I am returning the items expected (in the default atom format, but it proves that the list is there and will return rows if the call is properly constructed):

 

Data Returned when REST call path is fixedData Returned when REST call path is fixed

So my question now is: why is the PnP.js not making the correct REST call?  Have I missed a step somewhere? Am I making a faulty assumption that pnp.sp.web represents the context of the current default web site? Or is it something else?

 

Thanks for your input, btw.  I've been doing traditional SP Development for 10 years and am still climbing the learning curve on the new "remote" platform.

Can you try without the select. You should get all items coming back. If mot then your list and is incorrect.

>>Can you try without the select. You should get all items coming back. <<

 

The select is not the problem, it's not filtering any rows, only ensuring that I am only bringing back the one column I am actually interested in.  If you look at the ATOM formatted results above, you'll see that I am returning 3 items, but only the URL field for each item.  There are only 3 items in the list.  

 

When I do as you suggest by removing the select, I am still seeing the incorrectly formed REST call:

 

Revised PnP call, per your suggestionRevised PnP call, per your suggestion

REST call still messed upREST call still messed up

I still only get back the same 3 items, I just get a whole lot more fields:

 

MUCH more data...MUCH more data...

So, no, the select is not the issue...still trying figure out why the PnP.js is malforming the REST call...

It looks indeed that the REST Url isn't correct.

 

In my code I tried this:

pnp.sp.web.lists.getByTitle('MyList').items.get().then((items: any) => { 
    return items;
});

and the above worked as expected

 

The below call roughly does the same thing:

 

return this.context.spHttpClient.get(this.context.pageContext.web.absoluteUrl + `/_api/web/lists/GetByTitle('MyList')/items`, SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});

Can you double check that you have the latest version of the pnp modules?

 

I can't see much of a difference between your code and my code.

 

Ran "npm ls" and this was the result:

 

npm ls outputnpm ls output

Should be the latest because I only built this project this week! ;)

best response confirmed by Joseph Ackerman (Iron Contributor)
Solution

@Pieter Veenstra,

 

Apparently there are some additional instuctions required for the PnP JS and SPFx to play nicely with one another.  Thanks to @Patrick Rodgers for pointing me to the missing key described in Using sp pnp js in SharePoint Framework - Establish Context.

 

A single call in the onInit() method override does the trick:

@override
  public onInit(): Promise<void> {
    return super.onInit().then(_ => {
      
      //--- ensure that the current web is used for all pnp calls
      pnp.setup({
        spfxContext: this.context
      });

    });
  }

The REST call is now forming properly. It sure would be nice to have complete, organized documentation all together in ONE PLACE. :)

 

Thanks again for your interest and assistance.

1 best response

Accepted Solutions
best response confirmed by Joseph Ackerman (Iron Contributor)
Solution

@Pieter Veenstra,

 

Apparently there are some additional instuctions required for the PnP JS and SPFx to play nicely with one another.  Thanks to @Patrick Rodgers for pointing me to the missing key described in Using sp pnp js in SharePoint Framework - Establish Context.

 

A single call in the onInit() method override does the trick:

@override
  public onInit(): Promise<void> {
    return super.onInit().then(_ => {
      
      //--- ensure that the current web is used for all pnp calls
      pnp.setup({
        spfxContext: this.context
      });

    });
  }

The REST call is now forming properly. It sure would be nice to have complete, organized documentation all together in ONE PLACE. :)

 

Thanks again for your interest and assistance.

View solution in original post