Home > OS >  replace element, nested elements tag type javascript/jquery
replace element, nested elements tag type javascript/jquery

Time:03-22

I have a <div> wrapped around a set of nested <div> elements; I'd like to present this as a drop-down menu. The element is part of a plugin I'm using on a WordPress website but this should still be adjustable using custom script.

I would like to replace the encasing div with a <ul>, and turn all of the inner <div>s into <li> elements, so I can show this as a drop-down. Either that or a <select>, with nested <option> tags.

Is there a way I can change the HTML tag type , or replace the div, using pure JavaScript, or jQuery?

From:

<div >
   <div ></div>
   <div ></div>
</div>

to:

<ul >
   <li ></li>
   <li ></li>
</ul>

or:

<select >
   <option ></option>
   <option ></option>
</select>

The isolated code: https://codepen.io/bolti95/pen/rNpMdJx

  //div into list
const slots_list = document.createElement("ul");
var slots = document.getElementsByClassName("slots")
console.log(slots)
slots.insertBefore(slots_list, slots.children[0])
.slots {
    width: 100% !important;
    height: max-content;
    border: none !important;
    box-shadow: none !important;
    margin: auto;
}

.slots div a {
    color: #77635A;
    background: none !important;
}
.slots div a:hover {
    background-color: #F7F7F7 !important;
    transition: 0.6s;
}
.availableslot {
    width: 120px;
    height: 55px;
    border-radius: 30px;
    margin-bottom: 25px; 
    margin: 10px;
    border: none !important;
    border-width: 0px;
    text-align: center;
    font-size: 20px !important;
    background-color: #EFE8DA;
    color: #77635A; 
}

.availableslot a {
    margin: 10px;
}

.availableslot:hover {
    outline: 1px solid #5FDEAB;
    background-color: #F7F7F7;
    transition: 0.6;
}
.availableslot a:hover {
    background-color: #F7F7F7;
    color: #5FDEAB;
    transition: 0.6s;
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="<?php bloginfo('charset'); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <div >
      <span>03/22/2022</span>
      <br>
      <div ><a>11:00</a></div>      
      <div ><a>12:00</a></div>
      <div ><a>13:00</a></div>
      <div ><a>14:00</a></div>
      <div ><a>15:00</a></div>
    </div>
  </body>
</html>

CodePudding user response:

To convert the .availableslot div elements to a ul/li list you can use a combination of wrapAll() and replaceWith(), like this:

$('.availableslot').wrapAll('<ul  />').replaceWith(function() {
  return `<li >${this.innerHTML}</li>`
});

$('.availableslot').wrapAll('<ul  />').replaceWith(function() {
  return `<li >${this.innerHTML}</li>`
});
.slots {
  width: 100% !important;
  height: max-content;
  border: none !important;
  box-shadow: none !important;
  margin: auto;
}

.slots div a {
  color: #77635A;
  background: none !important;
}

.slots div a:hover {
  background-color: #F7F7F7 !important;
  transition: 0.6s;
}

.availableslot {
  width: 120px;
  height: 55px;
  border-radius: 30px;
  margin-bottom: 25px;
  margin: 10px;
  border: none !important;
  border-width: 0px;
  text-align: center;
  font-size: 20px !important;
  background-color: #EFE8DA;
  color: #77635A;
}

.availableslot a {
  margin: 10px;
}

.availableslot:hover {
  outline: 1px solid #5FDEAB;
  background-color: #F7F7F7;
  transition: 0.6;
}

.availableslot a:hover {
  background-color: #F7F7F7;
  color: #5FDEAB;
  transition: 0.6s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div >
  <span>03/22/2022</span>
  <br>
  <div ><a>11:00</a></div>
  <div ><a>12:00</a></div>
  <div ><a>13:00</a></div>
  <div ><a>14:00</a></div>
  <div ><a>15:00</a></div>
</div>

The same technique can be used to convert them to a select/option form control:

$('.availableslot').wrapAll('<select />').replaceWith(function() {
  let time = this.querySelector('a').innerText;
  return `<option value="${time}">${time}</option>`;
});

$('.availableslot').wrapAll('<select />').replaceWith(function() {
  let time = this.querySelector('a').innerText;
  return `<option value="${time}">${time}</option>`;
});
.slots {
  width: 100% !important;
  height: max-content;
  border: none !important;
  box-shadow: none !important;
  margin: auto;
}

.slots div a {
  color: #77635A;
  background: none !important;
}

.slots div a:hover {
  background-color: #F7F7F7 !important;
  transition: 0.6s;
}

.availableslot {
  width: 120px;
  height: 55px;
  border-radius: 30px;
  margin-bottom: 25px;
  margin: 10px;
  border: none !important;
  border-width: 0px;
  text-align: center;
  font-size: 20px !important;
  background-color: #EFE8DA;
  color: #77635A;
}

.availableslot a {
  margin: 10px;
}

.availableslot:hover {
  outline: 1px solid #5FDEAB;
  background-color: #F7F7F7;
  transition: 0.6;
}

.availableslot a:hover {
  background-color: #F7F7F7;
  color: #5FDEAB;
  transition: 0.6s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div >
  <span>03/22/2022</span>
  <br>
  <div ><a>11:00</a></div>
  <div ><a>12:00</a></div>
  <div ><a>13:00</a></div>
  <div ><a>14:00</a></div>
  <div ><a>15:00</a></div>
</div>

CodePudding user response:

One approach, using plain JavaScript, is below; explanatory comments are in the code:

// creating a named function, using Arrow syntax, that allows the user to pass in an
// Object of updated preferences:
const replaceWith = (opts) => {
  let settings = {
    // String, sets the element-type to replace the outer wrapper:
    outerTo: 'select',
    // String, sets the element-type to replace the inner wrapper:
    innerTo: 'option',
    // String, CSS selector to select the relevant elements:
    source: '.slots',
    // Boolean, true: replaces the element when its replacement is inserted into the document,
    //          false: hides the element when its replacement is inserted into the document:
    replace: true,
    // Boolean, true: preserves the child-nodes of the inner-elements being replaced,
    //          false: preserves the text-content of the inner-element, but does not
    //                 keep the child-nodes that may have contributed text:
    preserveHTMLChildren: false,
  };

  // while I'm sure there's an easy way to use destructruring to avoid this step, I chose to
  // use this approach, wherein we take the keys of the opts Object passed to the function
  // or - if no Object is passed - we look at the empty Object-literal (to avoid errors),
  // we then iterate over the Array of Object-keys using Array.prototype.forEach(), again
  // using Arrow syntax:
  Object.keys(opts || {}).forEach(
    // the 'key' passes in a reference to the current key of the Array of Object-keys
    // over which we're iterating, and in the function we set the Object-key of the
    // settings Object to the value of the opts Object key-value:
    (key) => settings[key] = opts[key]
  );

  // to reduce unnecessary typing, we alias the document to the D variable:
  let D = document,
    // we create an element according to the desired elements:
    outer = D.createElement(settings.outerTo),
    inner = D.createElement(settings.innerTo),
    // we use document.querySelectorAll(), using the supplied, or default,
    // CSS selector, to retrieve all matching elements:
    sources = [...D.querySelectorAll(settings.source)];

  // we use NodeList.prototype.forEach() to iterate over the NodeList of matching
  // elements returned to the sources variable:
  sources.forEach(
    // using Arrow syntax we pass in a reference to the current Node ('source') of
    // the NodeList:
    (source) => {
      // we clone the created-element for the outer-element's replacement:
      let outerClone = outer.cloneNode();
      // we copy the classList:
            outerClone.classList = source.classList;
      // we use an Array-literal with the spread syntax to create an Array of
      // the child-nodes of the current 'source', and then iterate over that
      // Array of Nodes using Array.prototype.forEach():
      [...source.children].forEach(
        // we pass in a reference to the current child-element of the parent
        // 'source', in the variable named 'child':
        (child) => {
          // we clone the created-element for the inner element:
            let innerClone = inner.cloneNode();
         
          // if settings.preserveHTMLChildren is exactly equal to Boolean true:
          if (settings.preserveHTMLChildren === true) {
            // while there is a firstChild node (whether comment, text, HTMLElement...):
            while (child.firstChild) {
              // we copy that firstChild Node accross to the innerClone element:
              innerClone.append(child.firstChild);
            }
          } else {
            // we update the text-content of the innerClone to be equal to the
            // original text-content:
            innerClone.textContent = child.textContent;
          }
          
          // copying the classList of the existing child to the innerClone:
          innerClone.classList = child.classList;
          // we append the current innerClone to the outerClone:
          outerClone.append(innerClone);
        });
        
      // we then access the current 'source' element's parentNode, and
      // use ParentNode.insertBefore() to insert the newly-created
      // outerClone before the current 'source' element's next-sibling
      // (effectively inserting it after the current 'source' element):
      source.parentNode.insertBefore(outerClone, source.nextSibling);
      
      // if the user wants to replace the element, so settings.replace is
      // exactly-equal to (Boolean) true:
      if (settings.replace === true) {
        // we remove the current 'source' element, using Node.remove():
        source.remove();
      } else {
        // otherwise we set source.hidden to 'true' to hide the element
        // using the 'hidden' HTML attribute (though in the demo I chose
        // to modify the opacity to show that the element does, in fact,
        // remain in the document); this should probably be modified when
        // in use:
        source.style.opacity = 0.4;
        // source.hidden = true;
      }
    })

};

// calling the function:
replaceWith({
 // using a non-default CSS Selector:
 source: '#demo1'
});

// calling the functiona again:
replaceWith({
 // modifying the CSS selector again:
 source: '#demo2',
 // using a <ul> to 'replace' the outer element:
 outerTo: 'ul',
 // using an <li> to wrap the inner contents:
 innerTo: 'li',
 // setting Boolean false, to not replace the original
 // element(s), so inserting the new element(s) and hiding
 // the original(s):
 replace: false,
});

replaceWith({
  source: '#demo3',
  outerTo: 'ul',
  innerTo: 'li',
  // setting preserveHTMLChildren to true means the <span> elements
  // contained within the inner-<div> elements will be preserved when
  // the HTML is modified:
  preserveHTMLChildren: true,
});

replaceWith({
  source: '#demo4',
  // again, setting preserveHTMLChildren to true means the <span> elements
  // contained within the inner-<div> elements will be preserved when
  // the HTML is modified; but while those <span> elements may be present
  // in the DOM (in FF 98/Ubuntu 21.10), they are not styled by the CSS
  // (and an <option> element has no valid child-elements), so this isn't
  // necessarily going to be respected by the browser:
  preserveHTMLChildren: true,
});
*,
::before,
::after {
  box-sizing: border-box;
  font: normal 400 1rem / 1.5 sans-serif;
  list-style-type: none;
  margin: 0;
  padding: 0;
}

.slots {
  border: 1px solid palegreen;
  margin: 1em auto;
  padding: 0.5em;
  width: 20rem;
}

.slots:not([id]) {
  border: 1px solid rebeccapurple;
}

.slots span {
  color: #f90;
}
<div id="demo1" >
   <div >available 1</div>
   <div >available 2</div>
</div>

<div id="demo2" >
   <div >available 3</div>
   <div >available 4</div>
</div>

<div id="demo3" >
   <div >available <span>5</span></div>
   <div >available <span>6</span></div>
</div>

<div id="demo4" >
   <div >available <span>7</span></div>
   <div >available <span>8</span></div>
</div>

JS Fiddle demo.

I have to admit that I'm curious as to why the <select> elements aren't being positioned correctly (in terms of margin-inline: auto), but that's a problem for another time.

If there are any questions then please don't hesitate to leave a comment below.

References:

  • Related