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>`;
}
Interactive map link
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.