Summary
The button toggles its label and an icon, but storing raw HTML in a data-* attribute causes the markup to be escaped and displayed as text. The fix is to keep the icon element out of the data attribute and swap it using jQuery (or native DOM) instead of trying to embed HTML inside the attribute.
Root Cause
- *`data-
attributes are plain strings**; when you read them with.data()` jQuery returns the literal text. - Inserting that string into
.text()escapes HTML, so<i class="fa fa-caret‑up"></i>is rendered as literal characters. - The original implementation used
.text()to change the button label, which is appropriate for plain text but not for markup.
Why This Happens in Real Systems
- Developers often reuse
data-*attributes to store “display values” because they are convenient. - In large codebases, the same pattern (text‑only data →
.text()) is copied without considering cases where markup is needed. - Browsers treat the value of a
data-*attribute as text, not as HTML, so any markup must be parsed separately.
Real-World Impact
- Users see garbled HTML instead of the expected icon, breaking UI polish.
- Accessibility tools may read the raw markup, causing confusion for screen readers.
- Automated UI tests that check for icons fail, leading to false negatives.
Example or Code (if necessary and relevant)
$('.btn[data-bs-toggle="collapse"]').on('click', function () {
const $btn = $(this);
const original = $btn.data('original') || {
html: $btn.html(),
altHtml: $btn.data('text-alt-html')
};
// Save original state on first click
if (!$btn.data('original')) {
$btn.data('original', original);
}
// Toggle between the two HTML fragments
$btn.html($btn.html() === original.html ? original.altHtml : original.html);
});
How Senior Engineers Fix It
- Store raw HTML in a separate attribute (e.g.,
data-text-alt-html) and use.html()to inject it. - Cache the original HTML on first click to avoid repeatedly reading the DOM.
- Prefer event delegation (
.on('click', selector, handler)) for dynamic content. - Write a utility function that swaps any pair of markup fragments, keeping the toggle logic reusable.
- Add unit tests that verify the button’s innerHTML matches the expected markup after each click.
Why Juniors Miss It
- They assume
.text()works for any content, not recognizing the distinction between text and HTML. - They treat
data-*values as “magic strings” that can contain any markup without considering HTML escaping. - Lack of awareness that jQuery’s
.data()returns a parsed value while.attr()returns the raw attribute string, leading to confusion about which method to use.