How to customize a geolocation field in SharePoint list via SPFx

Geolocation field

When working with a SharePoint list that contains a geolocation field, you might have noticed that the geolocation field is rendered as a hyperlink. Definitely useful in some cases, but not very informative.

If you want to see more details about the location, you need to show the additional columns. One disadvantage is that these additional columns consume more space in the list view.

What if you want to show more details about the location in a compact way? This is where a field customizer extension comes in handy.

In this article, I'll only show you how to create a field customizer extension for a geolocation field, I won't focus on how to deploy it to a production environment.

Create a new SPFx project

The SharePoint Framework (SPFx) is Microsoft's development model for building client-side customizations and extensions for SharePoint Online and Microsoft 365. Let's check what are the prerequisites to create a new SPFx project.

Prerequisites

Install Node.js

Install Node.js (LTS version recommended)

Install Gulp

JavaScript-based task runner used to build SPFx projects and to create JavaScript bundles.

npm install gulp-cli --global

Install Yeoman

Yeoman helps you kick-start new SPFx projects, and prescribes best practices and tools to help you stay productive.

npm install yo --global

Install Yeoman SharePoint generator

The Yeoman SharePoint generator is used to create new SPFx projects.

npm install @microsoft/generator-sharepoint --global

Now, open a command prompt in your favorite location, create a new directory for your SPFx project and go to the project directory.

md location-field-customizer
cd location-field-customizer

Create extension

Create a new SPFx extension using the Yeoman SharePoint generator.

yo @microsoft/sharepoint

Set your solution name, Choose the extension component, Select Field Customizer, Set the name of your field customizer and don't select any framework.

Once the project is created, you can open it in your favorite code editor. I will use Visual Studio Code.

Create field customizer

Set up local workbench

First of all we need to modify the serve.json file located in config folder. This is a configuration file used to define how the gulp serve command behaves during local development.

Open the serve.json file and rename InternaFieldName to match the name of your location column (I will use Location name). Update the pageUrl to match your SharePoint site URL for testing.

{
  "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json",
  "port": 4321,
  "https": true,
  "serveConfigurations": {
    "default": {
      "pageUrl": "https://{tenant_name}.sharepoint.com/sites/{site_name}/lists/{list_name}/AllItems.aspx",
      "fieldCustomizers": {
        "Location": {
          "id": "c46c0ab7-2813-4364-89cc-f41700f83893",
          "properties": {
            "sampleText": "Value"
          }
        }
      }
    },
    "geolocation": {
      "pageUrl": "https://{tenant_name}.sharepoint.com/sites/{site_name}/lists/{list_name}/AllItems.aspx",
      "fieldCustomizers": {
        "Location": {
          "id": "c46c0ab7-2813-4364-89cc-f41700f83893",
          "properties": {
            "sampleText": "Value"
          }
        }
      }
    }
  }
}

Modify field customizer code

Open the GeolocationFieldCustomizer.ts file located in src/extensions/geolocation folder. This file contains the main code for your field customizer.

We will update only the onRenderCell method to customize how the geolocation field is rendered. By default, the onRenderCell method looks like this:

public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
  // Use this method to perform your custom cell rendering.
  const text: string = `${this.properties.sampleText}: ${event.fieldValue}`;

  event.domElement.innerText = text;

  event.domElement.classList.add(styles.geolocation);
}

The IFieldCustomizerCellEventParameters interface provides parameters for events related to rendering or disposing of cells in a SharePoint list view. It contains the fieldValue property that holds the value of the field being rendered.

For the geolocation field, the fieldValue is an object with a structure like:

DisplayName: "Microsoft"
EntityType: "LocalBusiness"
LocationUri: "https://www.bingapis.com/api/v6/localbusinesses/YN8046x576897103687762284
Address
  City: "Praha 10"
  CountryOrRegion: "Czechia"
  IsInferred: "False"
  PostalCode: "140 00"
  State: "Capital City of Prague"
  Street: "Vyskočilova 1561/4a"
  Type: "Unknown"
Coordinates
  Latitude: 50.0482
  Longitude: 14.4573

Open the GeolocationFieldCustomizer.module.scss file located in the same folder and update the generated style for the geolocation field.

.geolocation {
  text-align: center;
  padding: 0.5rem;
}

Let's update the onRenderCell method to display more details about the location.

public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
  if (event.fieldValue !== undefined) {
    event.domElement.classList.add(styles.geolocation);

    event.domElement.innerHTML = this.renderMinimalDisplay(event.fieldValue);
  } else {
    event.domElement.innerText = 'No location specified';
  }
}

private renderMinimalDisplay(fieldValue: any): string {
  return `
    <div class='${styles.geolocation}'>
      <strong>${fieldValue.DisplayName}</strong><br>
      ${fieldValue.Address.City}, ${fieldValue.Address.CountryOrRegion}
    </div>`;
}

Run the the gulp serve command to start the local server. It will open the SharePoint list in the default browser. The URL can be found also in the terminal.

Once the page is opened, you need to allow debug scripts by clicking the Load debug scripts button.

Ensure that the list contains a geolocation field named Location. Also ensure the fieldCustomizer query parameter contains Location.

If nothing is changed, try adding &allowSpfxDevFallback=true&sw=bypass to the URL.

The geolocation field should be rendered like this.

Now, you can try to play with the rendering code to display more or less details about the location. I used GitHub Copilot to generate more ways how to render the location.

The prompt is very generic and GitHub Copilot is smart enough to understand the goal:

The fieldValue inside the onRenderCell has the following properties: Address (with properties city, countrOrRegion, postalCode, state, street, type), Coordinates (with properties latitude, longitude), DisplayName, LocationUri and UniqueId. Suggest different approaches to render the location.

The following are examples of how to render the geolocation field generated by GitHub Copilot.

Compact card layout

public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
  if (event.fieldValue !== undefined) {
    event.domElement.classList.add(styles.geolocation);

    event.domElement.innerHTML = this.renderCompactCard(event.fieldValue);
  } else {
    event.domElement.innerText = 'No location specified';
  }
}

private renderCompactCard(fieldValue: any): string {
  return `
    <div class='${styles.geolocation}'>
      <div style="font-weight: bold; margin-bottom: 4px;">${fieldValue.DisplayName}</div>
      <div style="font-size: 0.9em;">
        ${fieldValue.Address.Street}<br>
        ${fieldValue.Address.City} ${fieldValue.Address.PostalCode}<br>
        ${fieldValue.Address.CountryOrRegion}
      </div>
    </div>`;
}

Detailed information card

public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
  if (event.fieldValue !== undefined) {
    event.domElement.classList.add(styles.geolocation);

    event.domElement.innerHTML = this.renderDetailedCard(event.fieldValue);
  } else {
    event.domElement.innerText = 'No location specified';
  }
}

private renderDetailedCard(fieldValue: any): string {
  return `
    <div class='${styles.geolocation}'>
      <div style="font-weight: bold; font-size: 1.1em; margin-bottom: 6px;">
        📍 ${fieldValue.DisplayName}
      </div>
      <div style="font-size: 0.85em; line-height: 1.3;">
        <div><strong>Address:</strong> ${fieldValue.Address.Street}</div>
        <div><strong>City:</strong> ${fieldValue.Address.City} ${fieldValue.Address.PostalCode}</div>
        <div><strong>State:</strong> ${fieldValue.Address.State}</div>
        <div><strong>Country:</strong> ${fieldValue.Address.CountryOrRegion}</div>
        <div><strong>Coordinates:</strong> ${fieldValue.Coordinates.Latitude}, ${fieldValue.Coordinates.Longitude}</div>
      </div>
    </div>`;
  }

public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
  if (event.fieldValue !== undefined) {
    event.domElement.classList.add(styles.geolocation);

    event.domElement.innerHTML = this.renderInteractiveMapLink(event.fieldValue);
  } else {
    event.domElement.innerText = 'No location specified';
  }
}

private renderInteractiveMapLink(fieldValue: any): string {
  const mapUrl = `https://www.google.com/maps?q=${fieldValue.Coordinates.Latitude},${fieldValue.Coordinates.Longitude}`;
  return `
    <div class='${styles.geolocation}'>
      <div style="margin-bottom: 4px;">
        <strong>${fieldValue.DisplayName}</strong>
      </div>
      <div style="font-size: 0.9em; margin-bottom: 6px;">
        ${fieldValue.Address.Street}<br>
        ${fieldValue.Address.City}, ${fieldValue.Address.CountryOrRegion}
      </div>
      <a href="${mapUrl}" target="_blank" style="color: black; text-decoration: underline;">
        🗺️ View on Map
      </a>
    </div>`;
}

Badge style with coordinates

public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
  if (event.fieldValue !== undefined) {
    event.domElement.classList.add(styles.geolocation);

    event.domElement.innerHTML = this.renderBadgeStyle(event.fieldValue);
  } else {
    event.domElement.innerText = 'No location specified';
  }
}

private renderBadgeStyle(fieldValue: any): string {
  return `
    <div class='${styles.geolocation}' style="display: inline-flex; align-items: center; gap: 8px;">
      <span>📍</span>
      <div>
        <div style="font-weight: bold;">${fieldValue.DisplayName}</div>
        <div style="font-size: 0.8em; opacity: 0.9;">
          ${fieldValue.Address.City} (${fieldValue.Coordinates.Latitude.toFixed(4)}, ${fieldValue.Coordinates.Longitude.toFixed(4)})
        </div>
      </div>
    </div>`;
}

Expandable details

public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
  if (event.fieldValue !== undefined) {
    event.domElement.classList.add(styles.geolocation);

    event.domElement.innerHTML = this.renderExpandableDetails(event.fieldValue);
  } else {
    event.domElement.innerText = 'No location specified';
  }
}

private renderExpandableDetails(fieldValue: any): string {
  const uniqueId = `geo-${fieldValue.UniqueId || Math.random().toString(36).substr(2, 9)}`;
  return `
    <div class='${styles.geolocation}'>
      <div style="cursor: pointer;" onclick="document.getElementById('${uniqueId}').style.display = document.getElementById('${uniqueId}').style.display === 'none' ? 'block' : 'none';">
        📍 ${fieldValue.DisplayName} <span style="font-size: 0.8em;">▼</span>
      </div>
      <div id="${uniqueId}" style="display: none; margin-top: 6px; font-size: 0.85em; border-top: 1px solid rgba(255,255,255,0.3); padding-top: 6px;">
        <div>${fieldValue.Address.Street}</div>
        <div>${fieldValue.Address.City} ${fieldValue.Address.PostalCode}</div>
        <div>${fieldValue.Address.State}, ${fieldValue.Address.CountryOrRegion}</div>
        <div>Lat: ${fieldValue.Coordinates.Latitude}, Lng: ${fieldValue.Coordinates.Longitude}</div>
      </div>
    </div>`;
}

Icon-based layout

public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
  if (event.fieldValue !== undefined) {
    event.domElement.classList.add(styles.geolocation);

    event.domElement.innerHTML = this.renderIconBasedLayout(event.fieldValue);
  } else {
    event.domElement.innerText = 'No location specified';
  }
}

private renderIconBasedLayout(fieldValue: any): string {
  return `
    <div class='${styles.geolocation}' style="text-align: left;">
      <div style="display: flex; align-items: center; gap: 6px; margin-bottom: 4px;">
        <span>🏢</span>
        <strong>${fieldValue.DisplayName}</strong>
      </div>
      <div style="display: flex; align-items: center; gap: 6px; font-size: 0.9em;">
        <span>📍</span>
        <span>${fieldValue.Address.City}, ${fieldValue.Address.CountryOrRegion}</span>
      </div>
      <div style="display: flex; align-items: center; gap: 6px; font-size: 0.8em; margin-top: 2px;">
        <span>🌍</span>
        <span>${fieldValue.Coordinates.Latitude.toFixed(4)}, ${fieldValue.Coordinates.Longitude.toFixed(4)}</span>
      </div>
    </div>`;
}

Tooltip style

public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
  if (event.fieldValue !== undefined) {
    event.domElement.classList.add(styles.geolocation);

    event.domElement.innerHTML = this.renderTooltipStyle(event.fieldValue);
  } else {
    event.domElement.innerText = 'No location specified';
  }
}

private renderTooltipStyle(fieldValue: any): string {
  const fullAddress = `${fieldValue.Address.Street}, ${fieldValue.Address.City} ${fieldValue.Address.PostalCode}, ${fieldValue.Address.State}, ${fieldValue.Address.CountryOrRegion}`;
  return `
    <div class='${styles.geolocation}' title="${fullAddress}">
      📍 ${fieldValue.DisplayName}
      <div style="font-size: 0.8em; margin-top: 2px;">
        ${fieldValue.Address.City}
      </div>
    </div>`;
}

Conclusion

In this article, I've shown you how to create a field customizer extension for a geolocation field in a SharePoint list using the SharePoint Framework (SPFx). You can customize the rendering of the geolocation field to display more or less details about the location in a compact way.

Feel free to experiment with different rendering styles to find the one that best suits your needs.

Next time, I'll show you how to deploy the field customizer extension to a production environment.

0
Buy Me a Coffee at ko-fi.com
An error has occurred. This application may no longer respond until reloaded. Reload x