I'm trying to implement BootStrap
's color switcher in Symfony
.
I've added BootStrap
using AssetMapper
and everything is working.
I've then copied the color switcher from BootStrap
's examples page into a twig
template along with it's CSS
and JS
code (as assets
).
I have a Controller
routed to /
whose template includes the color switcher template.
Everything appears to work when I open the page in the browser, however if I add a link in that page - to itself or to a different page, doesn't matter - the color switcher won't work in the new page, unless I press F5
or reload the page in some other fashion.
I've discovered that the JavaScript
code that initializes the color switcher is run on the initial page load or when I reload, but when changing pages by clicking a link it does not execute again on the destination page.
I've tried adding something like:
if (document.readyState !== 'loading') {
console.log('document is already ready, just execute code here');
initThemeSwitcher();
} else {
document.addEventListener('DOMContentLoaded', function () {
console.log('document was not ready, place code here');
initThemeSwitcher();
});
}
and whatching the console, again, only on initial page load or a forced reload will this code run.
So my question is: Why will this code only run when the file is fetched from the server and not on moving to a different page (or same page) by clicking a link?
EDIT Just noticed the same thing happens with Stimulus Controllers
: They work on page load but the moment I follow a link, they stop working until I reload the page (F5
on keyboard for example)
I'm still not sure why adding an EventListener
to DOMContentLoaded
only works on the first page loaded, but I managed to get a workaround.
Instead of using the code "as is" from the bootstrap
site, I made a Stimulus Controller
out of it - with a few changes to the code so it wouldn't use DOMContentLoaded
.
I ended up with the following Stimulus Controller
:
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
'use strict'
connect() {
const preferredTheme = this.getPreferredTheme()
this.setTheme(preferredTheme)
this.showActiveTheme(preferredTheme, false)
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const storedTheme = this.getStoredTheme()
if (storedTheme !== 'light' && storedTheme !== 'dark') {
this.setTheme(this.getPreferredTheme())
}
})
document.querySelectorAll('[data-bs-theme-value]').forEach(toggle => {
toggle.addEventListener('click', () => {
const theme = toggle.getAttribute('data-bs-theme-value')
this.setStoredTheme(theme)
this.setTheme(theme)
this.showActiveTheme(theme, true)
})
})
}
getStoredTheme() {
return localStorage.getItem('theme')
}
setStoredTheme(theme) {
localStorage.setItem('theme', theme)
}
getPreferredTheme() {
const storedTheme = this.getStoredTheme()
if (storedTheme) {
return storedTheme
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
setTheme(theme) {
if (theme === 'auto') {
document.documentElement.setAttribute('data-bs-theme', (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'))
} else {
document.documentElement.setAttribute('data-bs-theme', theme)
}
}
showActiveTheme(theme, focus = false) {
const themeSwitcher = document.querySelector('#bd-theme')
if (!themeSwitcher) {
return
}
const themeSwitcherText = document.querySelector('#bd-theme-text')
const activeThemeIcon = document.querySelector('.theme-icon-active use')
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
const svgOfActiveBtn = btnToActive.querySelector('svg use').getAttribute('href')
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
element.classList.remove('active')
element.setAttribute('aria-pressed', 'false')
})
btnToActive.classList.add('active')
btnToActive.setAttribute('aria-pressed', 'true')
activeThemeIcon.setAttribute('href', svgOfActiveBtn)
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`
themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
if (focus) {
themeSwitcher.focus()
}
}
}