Search code examples
htmljscript

Converting <img> alt text to image caption node


function altTextToImageCaption() {
  let imgs = document.getElementsByTagName('img');
  for (var i = 0; i < imgs.length; i++) {
    var att = imgs[i].attributes.getNamedItem('alt');
    if (!att) continue;
    var alt = att.value;
    if (!alt) continue;
    if (!alt.startsWith('Fig ')) continue;
    var cap = document.createElement('div');
    cap.setAttribute('class', 'imageCaption');
    cap.appendChild(document.createTextNode(alt));
    document.body.insertBefore(cap, imgs[i].nextSibling);
  }
}
<body onload="altTextToImageCaption()">
  <h1>Image Caption No Workie</h1>
  <img src="https://www.easyrgb.com/look/home_logo.png" alt="Fig 1. First caption.">

  <div>
  <img src="https://www.easyrgb.com/look/home_logo.png" alt="Fig 2. Second caption.">
  </div>
</body>

I'm trying to come up with a jscript function which inserts a caption text underneath all images in an html document which contain an alt attribute which starts with "Fig ". Seems to work except when the img node is inside a div or span. In that case there's an exception:

Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node."


Solution

  • I'd suggest changing the line:

    document.body.insertBefore(cap, imgs[i].nextSibling);
    

    to:

    imgs[i].parentNode.insertBefore(cap, imgs[i].nextSibling);
    

    This ensures that the <div> is always appended after the <img> itself, regardless of where it appears in the DOM:

    function altTextToImageCaption() {
      let imgs = document.getElementsByTagName('img');
      for (var i = 0; i < imgs.length; i++) {
        var att = imgs[i].attributes.getNamedItem('alt');
        if (!att) continue;
        var alt = att.value;
        if (!alt) continue;
        if (!alt.startsWith('Fig ')) continue;
        var cap = document.createElement('div');
        cap.setAttribute('class', 'imageCaption');
        cap.appendChild(document.createTextNode(alt));
        imgs[i].parentNode.insertBefore(cap, imgs[i].nextSibling);
      }
    }
    <body onload="altTextToImageCaption()">
      <h1>Image Caption No Workie</h1>
      <img src="https://www.easyrgb.com/look/home_logo.png" alt="Fig 1. First caption.">
    
      <div>
        <img src="https://www.easyrgb.com/look/home_logo.png" alt="Fig 2. Second caption.">
      </div>
    </body>

    However, while that solves the problem I'd also completely rewrite your function to the following – and also use unobtrusive JavaScript – to the following:

    // using an Arrow function to create the function as a const:
    const altTextToImageCaption = () => {
      // using document.querySelectorAll() to retrieve only
      // <img> elements that have an 'alt' attribute that starts
     // with the string "Fig "
      let images = document.querySelectorAll('img[alt^="Fig "]');
    
      // using NodeList.prototype.forEach() to iterate over the
      // NodeList of <img> element-nodes:
      images.forEach(
        // using an anonymous Arrow function:
        (img) => {
          // retrieving the attribute-value of the 'alt'
          // attribute, and removing leading/trailing
          // white-space:
          let alt = img.alt.trim(),
            // creating a <div> element:
            div = document.createElement('div');
    
          // using the Element.classList API to add the
          // 'imageCaption' class-name:
          div.classList.add('imageCaption');
          // using ParentNode.append() to append
          // the string:
          div.append(alt);
    
          // navigating to the <img> element's parent,
          // and using ParentNode.insertBefore() to
          // insert the new ('div') content before the
          // next-sibling of the <img> element:
          img.parentNode.insertBefore(div, img.nextSibling);
        });
    }
    
    // binding the event-handling in JavaScript rather than inline
    // event-binding; here we bind the altTextToImageCaption() function
    // as the event-handler for the 'DOMContentLoaded' event:
    window.addEventListener('DOMContentLoaded', altTextToImageCaption);
    <body>
      <h1>Image Caption No Workie</h1>
      <img src="https://www.easyrgb.com/look/home_logo.png" alt="Fig 1. First caption.">
    
      <div>
        <img src="https://www.easyrgb.com/look/home_logo.png" alt="Fig 2. Second caption.">
      </div>
    </body>

    As a belated edit, I'd also suggest – if you wish to pair an <img> and a caption – that you use a <figure> element, with a <figcaption>:

    // a simple alias for document.createElement() that also allows properties to be
    // passed into the created-element:
    const create = (tag, props) => Object.assign(document.createElement(tag), props);
    // using an Arrow function to create the function as a const:
    const altTextToImageCaption = () => {
      // using document.querySelectorAll() to retrieve only
      // <img> elements that have an 'alt' attribute that starts
      // with the string "Fig "
      let images = document.querySelectorAll('img[alt^="Fig "]');
    
      // using NodeList.prototype.forEach() to iterate over the
      // NodeList of <img> element-nodes:
      images.forEach(
        // using an anonymous Arrow function:
        (img) => {
            // retrieving the content of the 'alt' attribute, removing leading
          // and trailing white-space with String.prototype.trim():
            let altText = img.getAttribute('alt').trim(),
                // creating a <figure> element:
                figure = create('figure'),
              // creating a <figcaption> element, and passing in the
              // text of the 'alt' attribute as the text-content of
              // the created element:
                figCaption = create('figcaption',{textContent: altText});
          
          // using Element.append() to append the <figcaption> to the
          // <figure>:
          figure.append(figCaption);
          // using Element.before() to insert the created <figure>
          // element before the current <img>:
          img.before(figure);
          // using Element.prepend() to insert the <img> element to
          // the <figure> element, as its first-child (moving it from
          // its original position):
          figure.prepend(img);
        });
    }
    
    // binding the event-handling in JavaScript rather than inline
    // event-binding; here we bind the altTextToImageCaption() function
    // as the event-handler for the 'DOMContentLoaded' event:
    window.addEventListener('DOMContentLoaded', altTextToImageCaption);
    <h1>Image Caption No Workie</h1>
    <img src="https://www.easyrgb.com/look/home_logo.png" alt="Fig 1. First caption.">
    
    <div>
      <img src="https://www.easyrgb.com/look/home_logo.png" alt="Fig 2. Second caption.">
    </div>