Reputation: 15
I'm trying to implement a font changer for my website with JavaScript and , but the header iteration part won't work.
undefined is not an object (evaluating 'tagElement.style.fontFamily = fontsObj.headerFont')
Here's my code
HTML:
<select name="font-select" id="font-select">
<option value='{"headerFont":"system-ui, -apple-system","bodyFont":"system-ui, -apple-system"}'>System & System</option>
<option value='{"headerFont":"Lato","bodyFont":"Karla"}' selected="selected">Lato & Karla</option>
<option value='{"headerFont":"Lora","bodyFont":"Lato"}'>Lora & Lato</option>
<option value='{"headerFont":"Philosopher","bodyFont":"Mulish"}'>Philosopher & Muli(sh)</option>
</select>
JS:
function changeFontStyle(fonts) {
const headerTags = ["h1", "h2", "h3", "h4", "h5", "h6"];
var bodyText;
const fontsObj = JSON.parse(fonts.value);
// Update headers' font
for (let headerTag in headerTags) {
var tagElements = document.getElementsByTagName(headerTag);
for (let tagElement in tagElements) {
tagElement.style.fontFamily = fontsObj.headerFont;
}
}
// Update body font
bodyText = document.getElementsByTagName("html")[0];
bodyText.style.fontFamily = fontsObj.bodyFont;
}
var fontSelector = document.getElementById("font-select")
fontSelector.onchange = changeFontStyle(fontSelector);
The JSON.parse part seems to work right (as opposed to comments below this answer), at least I can't see any parse errors.
Edit: Newest version of question here
Upvotes: 0
Views: 263
Reputation: 4783
To keep the solution performant we'll use querySelectorAll
instead of getElementsByTagName
to select all the H
tags in one go and loop over them only once to set the new font. Also, as querySelectorAll
return a NodeList
, we will be able to loop through the returned elements using the forEach
method instead of having nested for..in
loop.
The idea is simple:
H
elements using querySelectorAll
body
's fontHere's a live demo:
const fontsDropdown = document.getElementById('font-select'),
headerTags = ["h1", "h2", "h3", "h4", "h5", "h6"],
fontsChanger = () => {
const fontsJson = JSON.parse(fontsDropdown.value);
/** we'll select all the elemnts based on the selector found in the "headerTags" array by concatinating those selector with an "," (we'll have: "h1,h2,h3,h4,h5,h6") */
document.querySelectorAll(headerTags.join(',')).forEach(h => h.style.fontFamily = fontsJson.headerFont);
/** change the body's font also */
document.body.style.fontFamily = fontsJson.bodyFont;
};
/** run on page load so the font change based on the initial selected value in the dropdown */
fontsChanger();
/** listen for the "change" event on the fonts dropdown and call "fontsChanger" function when a change occurs */
fontsDropdown.addEventListener('change', fontsChanger);
<p>Am a P tag</p>
<h1>Am an H1 tag</h1>
<h3>Am an H3 tag</h3>
<h6>Am an H6 tag</h6>
<select name="font-select" id="font-select">
<option value='{"headerFont":"system-ui, -apple-system","bodyFont":"system-ui, -apple-system"}'>System & System</option>
<option value='{"headerFont":"Lato","bodyFont":"Karla"}' selected>Lato & Karla</option>
<option value='{"headerFont":"Lora","bodyFont":"Lato"}'>Lora & Lato</option>
<option value='{"headerFont":"Philosopher","bodyFont":"Mulish"}'>Philosopher & Muli(sh)</option>
</select>
The above demo can still be improved further, especially if you're sure that you won't deal
H
tags that are added dynamically.
Sidenote: not all the devices out there have all the fonts you have specified in the dropdown. It is likely that most of the devices do not have some fonts installed so the change won't occur in that case.
Another sidenote: changing the
H
tags font is useless as they will inherit that change from thebody
. Anyway, I kept your logic in the above demo unchanged.
Upvotes: 1
Reputation: 9308
Your onchange
handler is not being created properly:
fontSelector.onchange = changeFontStyle(fontSelector);
This causes changeFontStyle(fontSelector)
to be executed immediately, and the return value used (incorrectly) as a callback.
Your for (let headerTag in headerTags)
should be for (let headerTag of headerTags)
(of
, not in
). (in
will give you the array indices, not the values that you want.)
Same thing with for (let tagElement in tagElements)
, which should be for (let tagElement of tagElements)
.
See: For loop for HTMLCollection elements
var fontSelector = document.getElementById("font-select")
fontSelector.onchange = changeFontStyle;
function changeFontStyle() {
fonts = fontSelector
const headerTags = ["h1", "h2", "h3", "h4", "h5", "h6"];
var bodyText;
const fontsObj = JSON.parse(fonts.value);
// Update headers' font
for (let headerTag of headerTags) {
var tagElements = document.getElementsByTagName(headerTag);
for (let tagElement of tagElements) {
tagElement.style.fontFamily = fontsObj.headerFont;
}
}
// Update body font
bodyText = document.getElementsByTagName("html")[0];
bodyText.style.fontFamily = fontsObj.bodyFont;
}
<h1>Foo</h1>
<h2>Bar</h2>
<select name="font-select" id="font-select">
<option value='{"headerFont":"system-ui, -apple-system","bodyFont":"system-ui, -apple-system"}'>System & System</option>
<option value='{"headerFont":"Lato","bodyFont":"Karla"}' selected="selected">Lato & Karla</option>
<option value='{"headerFont":"Lora","bodyFont":"Lato"}'>Lora & Lato</option>
<option value='{"headerFont":"Philosopher","bodyFont":"Mulish"}'>Philosopher & Muli(sh)</option>
</select>
Upvotes: 2