User and Shared Component Script

I’ve shared a simple user component (‘Collapse Less’). It needs several attribute values to be related, and I couldn’t see how to do that using ‘custom options’ without duplication and risk. Instead, I used a JavaScript load event handler to initialise the components. This seems to work, but I feel guilty about injecting initialisation code into the web site when it should ideally have been done prior to exporting.

I could probably have done this using an export script, but that wouldn’t have worked with preview and probably wouldn’t be suitable for shared components.

Suggestions about a more elegant approach are welcome!

The .js is:

window.addEventListener("load", () => {
    /* Ensure every collapse-less has an id: */
    els = document.getElementsByClassName('collapse-less')
    idIndex = 1     /* used to keep track of auto-generated id values */
    for (el of els) {
        contentEl = el.firstElementChild
        id = contentEl.id
        if (id === '') {    /* need to assign an id */
            id = contentEl.id = 'collapse-less-' + idIndex
            idIndex += 1
        }
        anchorEl = el.lastElementChild
        /* Refer to id where required: */
        anchorEl.href = '#' + id
        anchorEl.setAttribute('aria-controls', id)
        /* Initialise show-more and show-less text: */
        anchorEl.setAttribute('data-show-more-text', anchorEl.textContent)  /* save */
        if (!anchorEl.getAttribute('data-show-less-text'))
            anchorEl.setAttribute('data-show-less-text','Read Less')    /* set default */
        if (contentEl.classList.contains('show'))   /* initial state is expanded */
            anchorEl.innerText = anchorEl.getAttribute('data-show-less-text')
    }
})

function toggleCollapse(el) {
    el.innerText = el.classList.contains('collapsed')? 
        el.getAttribute('data-show-more-text') : el.getAttribute('data-show-less-text')
}

Bootstrap already has a component called collapse that does what your Collapse Less appears to do, it does not however have the more or less function depending on if the collapse is open or closed however, but this would be simple to implement.

My component is based on Collapse. I also wanted the button to be below the content rather than above it, so that users don’t need to scroll back up to hide content.

It would also have been nice if BSS provided a way for user components to implement design-time settings such as the ‘Collapse Show Hide’ buttons that the built-in Collapse component has. Without that, and without a more flexible way of handling custom option values, user components can’t be as powerful as built-in components.

Components can have custom options added to them too, right click and go to “Add Custom Options” these give you more control and allow classes and other attributes to be changed in the options panel

Here is the solution I arrived at. Bootstrap already comes with some event listeners that fire upon clicking on Collapse elements so I used these to listen for when the elements are opened and closed.

document.addEventListener('DOMContentLoaded', function() {
const toggles = document.querySelectorAll('[data-bs-toggle="collapse"][data-opened]');
toggles.forEach(toggle => {
  const openedText = toggle.getAttribute('data-opened');
  const closedText = toggle.textContent.trim();
  const selector = toggle.getAttribute('href') || toggle.dataset.bsTarget;
  const target = document.querySelector(selector);
  if (!target) return;
  target.addEventListener('shown.bs.collapse', () => {
    toggle.textContent = openedText;
    toggle.setAttribute('aria-expanded', 'true');
  });
  target.addEventListener('hidden.bs.collapse', () => {
    toggle.textContent = closedText;
    toggle.setAttribute('aria-expanded', 'false');
  });
});
});

To change the text that shows upon opening a collapse element add the Key data-opened to the button element, then supply the text you wish for it to show.

As for putting the button after the content, the simplest method is to apply the classes d-grid to the Collapse element, then order-first to the Content.

1 Like

Thanks; Ill study that.

I have published this as “Collapse Text Change” in the online library.
Text Change

1 Like

Thanks. How does one find out what events Bootstrap fires? I looked for events but that was fruitless.

If a component has events, they can always be found on the Bootstrap documentation page towards the bottom. These are the ones for Collapse.

Thank you yet again.

It can be done using CSS only, like this:
HTML markup

<div class="custom-collapse">
	<a 	class="btn btn-primary" 
		data-bs-toggle="collapse" 
		aria-expanded="false" 
		aria-controls="collapse-1" 
		href="#collapse-1" 
		role="button">
		<span class="visually-hidden">Toggle content</span>
	</a>
	<div id="collapse-1" class="collapse">
			<p>Collapse content.</p>
	</div>
</div>

CSS

.custom-collapse [aria-expanded="false"]::after {
  content: "Show more";
}

.custom-collapse [aria-expanded="true"]::after {
  content: "Show less";
}

.custom-collapse {
  display: flex;
  flex-direction: column-reverse;
}

.custom-collapse a {
  align-self: flex-start;
  width: auto;
}

Note: Ensure the link has no visible text in the HTML, as the label is added via CSS. Use a visually hidden span for screen readers if needed.

6 Likes

Another great solution!

1 Like

@kuligaposten Thanks. I’ll investigate. It seems much cleaner without using javascript.

In thinking (eventually!) about this, I’m wondering whether this approach is suitable for a library component. It should be allowable to have multiple components of the same type on one page, but hard-coding the id without changing it in JS would result in multiple identical ids on the same page, and attributes referring to those ambiguous ids.

I feel like I’m back where I started, needing some custom JS at page load to make unique ids and correct attribute references to them. I gather that built-in components can have smarts that aren’t available to third-party developers; eg, incrementing id when multiple components of the same type are added to a page.

The component I created which lets the expand collapse component have it’s text change doesn’t have this issue and IDs can be changed on the component.

The way it works is once you click on the button to expand content, the JS listens for the event Bootstrap creates and it gets the Href from the button, which is the ID of the content that becomes expanded. Thus, it doesn’t matter what the ID is and you can have as many of the components on the page as you like.

BSS also automatically names them collapse-1, collapse-2 ect. As you add more to the page if you don’t manually give them IDs.

Is there something you are looking to do that requires such a complex setup?

1 Like

My apologies; you’re exactly right, of course. I didn’t realise that BSS was smart enough to change ids (and references thereto) if need be.