I7T5
I7T5

Reputation: 15

How to iterate through all elements of <tag> and change their font?

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

Answers (2)

ThS
ThS

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:

  • 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.

Upvotes: 1

kmoser
kmoser

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

Related Questions