Lightning Communities are used to provide apps and services directly to your customers and partners. What makes them so great is that they allow you to take advantage of a lot of the same platform services and features that you have with your internal org. Part of this is the ability to build awesome Lightning Web Components to give those customers and partners an ideal experience. One way you can achieve this is designing and building custom navigation components that an admin can maintain.

Navigation in communities

If you’ve worked with Lightning Communities in the past, you would have modified the navigation menu and items to drive the available options in the theme’s navigation menu. As of Summer ’20, this functionally has expanded even more to support the tile navigation component. If you’re new to communities, a navigation menu is a set of navigation menu items that allows you to specify a label, type, navigation reference, public availability, and an image.

The available navigation types are:

  • Community page/internal link – a link to a named community page
  • External link – a link directed outside of the community
  • Global action – a link to a page generated to host a global action
  • Menu label – a parent heading for additional navigation menu items
  • Navigational topic – a link to a topic page within the community
  • Salesforce object – a link to an object list page with the ability to specify an object and related list

Building a custom navigation component

Navigation menus are great for declaratively managing a set of a list of navigation menu items that you can access programmatically within a custom component. A very common use case for a custom navigation component is the display of social links on a community. It’s a design pattern seen throughout the web and a great way for a company to promote their social presence. There are quite a few ways that we could achieve this with code, but using a navigation menu will give an admin the ability to manage the menu items using a familiar tool.

End result – social navigation component

To get started, all we need to do is go into the Experience Builder and create a new navigation menu; in this case, we’ve created one called Social Links. In this menu, we can specify the details of the navigation menu items and supply an image.

Accessing the navigation items

You can access the navigation menu items as objects grouped by their parent navigation menu. Here we can use SOQL to query for the navigation menu items that are associated with a parent menu.

 public static List<NavigationMenuItem> getNavigationMenuItems(String menuName) { List<NavigationMenuItem> navigationMenuItems = [ SELECT Label, // The label of the navigation item Type, // The type of navigation item - like we mentioned above Target, // The entity you are navigating to TargetPrefs, // If you are opening in the current or new tab if the type is external DefaultListViewId, // The default list view if the type is object AccessRestriction // The public/authenticated visability settings FROM NavigationMenuItem WHERE NavigationLinkSet.DeveloperName = :menuName // A reference to the parent Menu AND Status = 'Live' // Check that the item is Live and not in preview ORDER BY Position // Order by the positing specified in the editor ]; return navigationMenuItems; }

While this is great for getting the navigation menu items, it doesn’t give us the image references. If your navigation menu doesn’t require images, then this is a great solution for using the items. In this example, we do require the images. Thankfully there’s another option. We can access the navigation items via the Chatter REST API. To access the menu items, we just need to callout to the correct endpoint. This will take the navigation menu items, bring them together with the images, and present us with a unified data structure.

/connect/communities/communityId/navigation-menu/navigation-menu-items

The available paramaters and options associated to the endpoint are:

  • communityId – the Id of the community that the navigation menu is associated to
  • navigationLinkSetId – the navigation link set we are looking to get back
  • addHomeMenuItem – decides if we include the home menu item
  • includeImageUrl – decides if we include the image
  • menuItemTypesToSkip – allows you to supply a comma separated string of types to be skipped
  • publishStatus – filters only the draft or live navigation menu items

If successful, the callout will respond with the following array:

[ { "imageUrl": "/file-asset/twitter_2?v=1", "label": "Twitter", "subMenu": [], "target": "NewWindow", "url": "http://www.twitter.com", "urlType": "ExternalLink" },
]

As you can see, it includes an image url, which is exactly what we need to render the icons in the component. To achieve this, we just need the communityId, navigationSetId, includeImageUrl and lastly the addHomeMenuItem paramaters.

 @AuraEnabled(Cacheable=true) public static Map<String, Object> getConnectNavigationItems(String menuName, String communityId) { // Get the base url for the community to form our callout url String baseUrl = Url.getSalesforceBaseUrl().toExternalForm(); // Query the naviagion link set assigned to the specified menu List<NavigationLinkSet> navigationLinkSets = [ SELECT Id FROM NavigationLinkSet WHERE DeveloperName = :menuName WITH SECURITY_ENFORCED LIMIT 1 ]; // verify that a NavigationLinkSet has been returned NavigationLinkSet navigationLinkSet = (navigationLinkSets.size() == 1) ? navigationLinkSets.get(0) : null; // Construct the URL for the callout to the connect API String restEndpointTemplate = '{0}/services/data/v48.0/connect/communities/{1}/navigation-menu/navigation-menu-items?navigationLinkSetId={2}&includeImageUrl=true&addHomeMenuItem=false'; List<Object> parameters = new List<Object>{ baseUrl, communityId, navigationLinkSet.Id }; String restEndpointFormatted = String.format( restEndpointTemplate, parameters ); // Build the HttpRequest HttpRequest httpRequest = new HttpRequest(); httpRequest.setMethod('GET'); // Use the current users sessionId to authorize the transaction // the guest user must be API enabled String sessionId = UserInfo.getSessionId(); httpRequest.setHeader('Authorization', 'OAuth ' + sessionId); httpRequest.setHeader('Authorization', 'Bearer ' + sessionId); httpRequest.setEndpoint(restEndpointFormatted); Map<String, Object> navigationItems = new Map<String, Object>(); Http http = new Http(); HttpResponse httpResponse = http.send(httpRequest); if (httpResponse.getStatusCode() == 200) { // If the request is successful, desearliaze the response into a Map Map<String, Object> response = (Map<String, Object>) JSON.deserializeUntyped( httpResponse.getBody() ); navigationItems = response; } else { // If the request is unsuccessful, handle the error System.debug(' httpResponse ' + httpResponse.getBody()); throw new CalloutException(httpResponse.getBody()); } return navigationItems; }

We can take the response body from this callout and pass it back to the LWC for rendering.

Bringing the navigation menu to the front end

In order to get the navigation menu items into the LWC, we need to make sure we’re importing all of the modules we need. New in the Summer ’20 release are two community related modules, now available in your components:

To get the Id of the current community:

import communityId from '@salesforce/community/Id';

To get the base path of the current community (for example ‘awesomecommunity.force.com/s/’):

import communityBasePath from '@salesforce/community/basePath';

Looking at our current use case, the social navigation component, we can take advantage of these new features!

import {LightningElement, api, track, wire} from 'lwc'; // We can get the community Id for use in the callout
import communityId from '@salesforce/community/Id'; // Get the base path for navigating to non-named pages
import communityBasePath from '@salesforce/community/basePath'; // The Apex method will allow us to retrieve the items import getNavItems from '@salesforce/apex/NavMenuController.getNavItems'; // Lightning Navigation Service will allow us to navigate to the target
import {NavigationMixin} from "lightning/navigation";

Once we’ve imported everything we need for the component, we can then use a wire decorator to give the component the ability to get the data from the Apex controller reactively.

 @wire(getNavItems, { menuName: '$menuName', communityId: '$communityId' }) wiredNavigationItems({ error, data }) { if (data) { this.menuItems = data.menuItems; } else if (error) { this.error = error; } }

Rendering navigation menu items

Here you can see that in the template, we are iterating over the navigation menu items and providing the image url that was returned from the callout. The image reference/file-name.png can be rendered with the provided reference, as it has the same base path as the community.

<div class="slds-grid slds-align--absolute-center"> <template for:each={menuItems} for:item="item" for:index="i"> <div key={item.i} class="slds-col slds-p-around--small"> <template if:true={item.imageUrl}> <img data-label={item.label} src={item.imageUrl} class="nav-item-content" onclick={navigateToItem} /> </template> </div> </template>
</div>

When a user selects an item, we can handle the click in the JavaScript controller by referencing the data-label attribute that is defined using the dataset property. If you’re new to datasets, they are part of the HTMLOrForeignElement interface that give you the ability to add custom attributes to your html elements. Using data-*=“value” as an attribute will give you access to the string provided as the value when an event is fired.

Navigating to the selected item

The entire purpose of displaying menu items is for a user to click on them and be routed to a new page. Keeping in mind that we have many different page types, references and targets, we’ll need to provide some logic in the controller to point the url in the right direction. Putting the dataset property to good use, when a user clicks one of the menu items, it fires the onclick() event and we can access the data-label=* of the item using event.currentTarget.dataset.label. Once we have access to the menu item, we can determine if the url is internal or external and then broker the action to a separate method.

 navigateToItem(event) { // Get the menu item's label from the target let selectedLabel = event.currentTarget.dataset.label; let item = {}; // Filter the menu items for the selected item let item = this.menuItems.filter( menuItem => menuItem.label === selectedLabel )[0]; // Distribute the action to the relevant mechanisim for navigation if (item.urlType === "ExternalLink") { this.navigateToExternalPage(item); } else if (item.urlType === "InternalLink") { this.navigateToInternalPage(this.baseUrl, item); } }

Navigating to an external url

There are two ways that we can navigate to an external url in communities. We can use the Lightning navigation service to navigate to a standard__webPage page reference that will open in a new tab. The only drawback to this is that we can’t specify a target. This means that in order to open a url in the current window, we are required to use the standard window.open JavaScript method.

 // Navigate to an external link navigateToExternalPage(item) { const url = item.url; if (item.target === "CurrentWindow") { window.open(url, "_self"); } else if (item.target === "NewWindow") { this[NavigationMixin.Navigate]({ type: "standard__webPage", attributes: { url: url } }); } }

Navigating to an internal url

An internal link is compiled down to a url that can be navigated to in the same way as the external link. The format that the url is returned in from the Connect API does not allow us to navigate to a comm__namedPage page reference, therefore we must use the same standard__webPage references that were used for our external links. The difference is that the internal link will always open in the current window, which is the opposite behavior of an external link.

 // Navigate an internal link navigateToInternalPage(item) { // Use the basePath from the Summer '20 module to construct the URL const pageName = this.communityBasePath + item.url; this[NavigationMixin.Navigate]({ type: "standard__webPage", attributes: { url: pageName } }); }

Conclusion

As you can see, navigation menus are a powerful feature within Lightning Communities that allow you to create new navigation experiences that are easily maintainable. We’ve been able to integrate navigation menu items into a LWC and style them in a way that your customers are familiar with. Be sure to check out the github repo and share your ideas for navigation components!

Trailhead
Community Cloud Basics
Project: Building a Custom Theme Layout

Documentation
Community Navigation Menu
Navigation Menu Metadata
Chatter REST API

About the Author

Stephan Chandler-Garcia is a Senior Developer Evangelist at Salesforce. He focuses on Application development, Security and Communities. You can follow him on Twitter @stephanwcg

Subscribe To Our Newsletter

Subscribe To Our Newsletter

Join our mailing list to receive the latest news and updates from our team.

You have Successfully Subscribed!