Check element(s) before mutation

82 views Asked by At

I'm working on my first more "complex" jQuery code. As a mutation observer only looks for changes I need to to something on the element with a specified class ("active") on page load and then the same thing again on every other element that get's the class ("active"). Currently I'm writing duplicate code ("Do something x") which surely is not a good practice. That's what I currently have so far.

jQuery(document).ready(function($) {

 var tabs = document.querySelectorAll(".tab-content");
 var active = 'active';

 $(tabs).each(function(index){

  //Check for the active tab on page load
  if($this).hasClass(classtowatch)) { //Do something x }

  //Mutation Observer for getting the active tab if tab changes
  var target = this;
  var config = {attributes: true};
  var prevClassState = target.classList.contains(active);
  var observer = new MutationObserver(function(mutations) {
   mutations.forEach(function(mutation) {
    if(mutation.attributeName == "class"){
     var currentClassState = mutation.target.classList.contains(active);
     if(prevClassState !== currentClassState){
      prevClassState = currentClassState;
              
      // Active Tab  
      if(currentClassState) {               
        // Do Something x
      }
      //Every other tab           
      else {}
      }
     }
    });
  });

  });
});

So my question is is there any way to either run the observation on page load or do my "things" for the active element without duplicating the code for it.

2

There are 2 answers

0
john Smith On

To not write duplicate code you can just define a function.

like:

function doSomething(x) {
  // Do Something x
}

and then you call it whenever needed:

if($this).hasClass(classtowatch)) { 
   doSomething(x) 
}

...

if(currentClassState) {               
  doSomething(x)
}

But probably the best solution would not be to use mutationObserver at all but to "hook into" or "adapt to" whatever does the tabbing functionality. By the classname i can imagine its bootstrap? you can checkout the docs for the api https://getbootstrap.com/docs/5.0/components/navs-tabs/#methods

0
Peter Seliger On

An approach which is most generic would do following ...

  • providing a single handler function as callback to an observer which will execute this callback for every mutation at document.body.

  • in order to keep the resulting performance penalty as low as possible, this handler would be implemented in a way that it exits a early as possible with every not met condition and where the conditions are as follows ...

    • in case of a mutated class-attribute value, one does immediately detect whether the mutation happened within a 'tab-content' classified structure.

      • if so, one can only check whether the mutated target features the 'active' class name.
    • in case of added nodes ...

      • ... one immediately does filter all element nodes ...

      • ... and tries for each node the initialization of tab content behavior in case it is possible.

Thus, for the 2nd scenario of added element-nodes where, for instance, another 'tab-content' classified element gets added to the DOM, one would assure the correct element-related initializations in terms of tab-behavior and all its tab-related attribute-mutations.

Notes

  • In general one identifies specific tasks which the problem can be broken into.

  • Then one does implement each specific task as a single well named function/method.

  • In addition, the OP does not need jQuery at all since the complexity of the tiny part which actually could be covered by jQuery is so low-level that the Selectors API is much better suited for it. The complexity rather comes with the proper mutation handling.

function handleTabContentClick({ target }) {
  const elmTabItem = target.closest('li');
  const elmTabContent = elmTabItem?.closest('.tab-content');

  if (elmTabContent) {

    [...elmTabContent.children]
      .forEach(childNode => childNode.classList.remove('active'));

    elmTabItem.classList.add('active');
    elmTabItem.focus();
  }
}
function initializeTabContent(elmNode) {
  elmNode
    .addEventListener('click', handleTabContentClick);

  console.log(
    'initializing a tab-navigation at ...',
    elmNode.outerHTML,
  );
}

function handleTabContentMutation(mutation) {
  const { addedNodes, attributeName, target } = mutation;

  if (addedNodes.length >= 1) {

    // ... in case of added nodes ...
    [...mutation.addedNodes]
      // ... filter all element nodes ...
      .filter(node => node.nodeType === Node.ELEMENT_NODE)
      .forEach(elmNode =>

        // ... and try for each node the initialization of
        //     tab content behavior in case it is possible.
        elmNode
          .querySelectorAll('.tab-content')
          .forEach(initializeTabContent)
      );

  } else if (
    (attributeName === 'class') &&
    (target.closest('.tab-content') !== null)
  ) {
    // ... in case of a class-attribute which got mutated
    //     within a 'tab-content' classified structure ...

    if (target.matches('.active')) {

      // ... and where the mutated target
      //     features the 'active' class name ...
      console.log(
        'target features the "active" class name ...',
        target.outerHTML,
      );
    } else {
      // ... and where the mutated target does not
      //     feature the 'active' class name ...
      console.log(
        'target does not feature the "active" class name ...',
        target.outerHTML,
      );
    }
  }
}
function initializeTabContentObservation() {
  new MutationObserver(mutationList =>

    mutationList.forEach(handleTabContentMutation)
  )
  .observe(
    document.body, {
      attributes: true, childList: true, subtree: true
    },
  );
}

function main() {

  // - everything what is needed for ...
  
  //   ... initializing a tab-content behavior.
  document
    .querySelectorAll('.tab-content')
    .forEach(initializeTabContent);
  
  //   ... initializing the observation of
  //       any tab-content related mutation.
  initializeTabContentObservation();


  // just for testing the element-node mutation-handling.
  document
    .querySelector('button')
    .addEventListener('click', () =>
      document.body.appendChild(
        document.querySelector('button + nav').cloneNode(true)
      ), {
        once: true,
      }
    );
}
main();
body { margin: 0; }
button, nav, article { width: 50%; }

article { display: none; }
article:target { display: initial; }

nav ul { list-style: none; }
nav a:link, nav a:visited { color: #333; text-decoration: none; }

.tab-content { display: flex; justify-content: center; }

.tab-content li a { display: inline-block; padding: 5px 20px; }
.tab-content li.active a { color: #eee; background-color: #666; }

.as-console-wrapper { left: auto!important; width: 49%; min-height: 100%; }
<button>
  clone the beneath tab-navigation exactly once
</button>

<nav>
  <ul class="tab-content">
    <li><a href="#foo">Foo</a></li>
    <li><a href="#bar">Bar</a></li>
    <li><a href="#baz">Baz</a></li>
  </ul>
</nav>

<article id="foo">
  <h1>FOO</h1>
</article>

<article id="bar">
  <h1>BAR</h1>
</article>

<article id="baz">
  <h1>BAZ</h1>
</article>