ESL Anchornav

The ESL Anchornav component allows users to quickly jump to specific page content via predefined anchors. The list of anchors is collected from the page dynamically, so any page updates will be processed and the component updates the navigation list.

How it works

The component collects anchors on the page, builds a list of anchors using the user-defined renderer function, and appends it to the inner items container element (it will be an element with esl-anchornav-items attribute). After that, the component observes the position of the collected anchors to detect currently active anchor and marks it with active class marker.

For example, markup may be the following:

<esl-anchornav>Anchors: <nav esl-anchornav-items></nav></esl-anchornav>

If for some reason you do not add an element with this attribute to the component content, it will not be a mistake. A div with the esl-anchornav-items attribute will be created and added to the component content in this case.

You can assign anchors to any element on the page. By default, the component searches for elements with the esl-anchor attribute (i.e. selector [esl-anchor]). Another mandatory requirement for an element is that it must have id and title attributes (the title is the text to be displayed in the list).

If you need custom validation or filtering of collected anchors, override the protected getDataFrom() method. It may return undefined to skip an anchor that does not match your custom contract.

Items renderer

For all collected anchors it is used renderer function which builds the inner content of the anchors list. Here is a default renderer:

ESLAnchornav.setRenderer((data: ESLAnchorData, index: number, anchornav: ESLAnchornav) => `<a class="esl-anchornav-item" href="#${data.id}">${data.title}</a>`);

You can define your own renderer. You can define several renderers with different names and use them on different components.

Hierarchical Navigation

ESL Anchornav supports hierarchical (nested) navigation structures. To enable hierarchy, use the group-by attribute:

<esl-anchornav group-by="level"></esl-anchornav>

When hierarchy is enabled:

Example: Hierarchical Anchors

<!-- Page content with hierarchical anchors -->
<h2 esl-anchor="level: 0" id="section1" title="Section 1">Section 1</h2>
<h3 esl-anchor="level: 1" id="subsection1-1" title="Subsection 1.1">Subsection 1.1</h3>
<h3 esl-anchor="level: 1" id="subsection1-2" title="Subsection 1.2">Subsection 1.2</h3>
<h2 esl-anchor="level: 0" id="section2" title="Section 2">Section 2</h2>

Note: The esl-anchor attribute uses JSON-like syntax to define data properties. For example, esl-anchor="level: 1" sets the level property in the anchor's data object.

Custom Hierarchical Renderer

When rendering hierarchical navigation, you must use nav.renderItem() for nested items to ensure proper registration:

Important: Always use nav.renderItem(child, index) when rendering child items. Direct renderer calls will not register items properly and active state will not work.

Custom Hierarchy Logic

To implement custom hierarchy logic (e.g., using parent property from anchor data), you can register a custom hierarchy builder:

import {ESLAnchornav} from '@exadel/esl/modules/esl-anchornav/core';
import type {ESLAnchorData, ESLAnchornavHierarchyBuilder} from '@exadel/esl/modules/esl-anchornav/core';

const buildByParent: ESLAnchornavHierarchyBuilder = (flatAnchors: ESLAnchorData[]) => {
  // Build hierarchy using 'parent' property from anchor data
  const map = new Map<string, ESLAnchorData>();
  
  flatAnchors.forEach(anchor => {
    anchor.children = [];
    map.set(anchor.id, anchor);
  });
  
  const roots: ESLAnchorData[] = [];
  flatAnchors.forEach(anchor => {
    const parentId = anchor.data.parent; // Access data object
    if (parentId && map.has(parentId)) {
      anchor.parent = parentId;
      map.get(parentId)!.children!.push(anchor);
    } else {
      roots.push(anchor);
    }
  });
  
  return roots;
};

// Register the builder
ESLAnchornav.setHierarchyBuilder('parent', buildByParent);

Then use it with the group-by attribute:

<esl-anchornav group-by="parent"></esl-anchornav>

Markup example:

<h2 esl-anchor id="section1" title="Section 1">Section 1</h2>
<h3 esl-anchor="parent: section1" id="subsection1-1" title="Subsection 1.1">Subsection 1.1</h3>

Alternatively, you can override the buildHierarchy() method in a subclass for component-specific logic.

Empty State Handling

The component automatically sets the empty boolean attribute when no anchors are found. You can use the empty-class attribute to apply CSS classes to handle this state, and empty-class-target to specify which element should receive these classes (defaults to the component itself):

<!-- Hide the component when empty -->
<esl-anchornav empty-class="d-none"></esl-anchornav>

<!-- Hide parent container when navigation is empty -->
<div class="sidebar">
  <esl-anchornav empty-class="d-none" empty-class-target="::parent"></esl-anchornav>
</div>

<!-- Or use CSS with the empty attribute -->
<style>
  esl-anchornav[empty] { display: none; }
</style>

ESLAnchornav

Public API

Attributes | Properties:

Events

ESLAnchornavSticked

To implement the sticky behavior of a component, you can use the ESLAnchornavSticked mixin. Register the mixin and add the esl-anchornav-sticked attribute to the anchor element container.