0

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

I7T5
  • 13
  • 3

2 Answers2

2

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>
kmoser
  • 8,780
  • 3
  • 24
  • 40
  • I encountered a new problem when altering my code here: https://www.reddit.com/r/learnprogramming/comments/y1u8au/how_to_iterate_through_all_elements_of_tag_and/?utm_source=share&utm_medium=web2x&context=3. I think querySelectorAll looks cleaner. Safari doesn't throw errors at me but this doesn't work. – I7T5 Oct 12 '22 at 05:33
1

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:

  • listen for the dropdown change
  • select all the H elements using querySelectorAll
  • loop through the returned elements and change their font
  • then change the body's font

Here'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 &amp; System</option>
  <option value='{"headerFont":"Lato","bodyFont":"Karla"}' selected>Lato &amp; Karla</option>
  <option value='{"headerFont":"Lora","bodyFont":"Lato"}'>Lora &amp; Lato</option>
  <option value='{"headerFont":"Philosopher","bodyFont":"Mulish"}'>Philosopher &amp; 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 the body. Anyway, I kept your logic in the above demo unchanged.

ThS
  • 4,597
  • 2
  • 15
  • 27
  • I included the fonts in my with to google fonts css. Sry but can you explain or provide a link to or a technical name for "Another sidenote"??? I don't quite understand. – I7T5 Oct 12 '22 at 13:13
  • @I7T5 basically, when you set a style, in your case `font-family`, on the `body` element which is the parent of your `H` elements (`h1 .. h6`), those elements will inherit the value you set on the `body` and thus will inherit the `font-family` property from the `body`. With that in mind, it's not necessary to change the font for the `H` elements because you're changing that on the `body` and the `H` elements will simply inherit that value from their parent (the `body` element). – ThS Oct 12 '22 at 13:30
  • Thanks! But the font wasn't overridden when I just tested it, which was great cuz now my font changer finally works! Thanks you. This was my first solved question on stack overflow : ) – I7T5 Oct 12 '22 at 13:40
  • @I7T5 Glad I managed to help you even a bit. Feel free to ask for any assistance or clarification you'd need. Meanwhile, if you find my answer *somewhat* helpful, kindly set it as the accepted answer. – ThS Oct 12 '22 at 13:42