5

New to JS. All of the questions I could find with similar titles to mine were a bit too complicated for me to understand.

Simply put, I am trying to loop through an array and return one result at the end that essentially adds/concatenates all of the values of the array together.

However, I can't seem to get it to work, as the items in the array are blocks of HTML code, rather than simple strings or numbers. Here's a dumbed-down version.

HTML:

<article class="wrapper"><p>Some text 1.</p></article>
<article>No class.</article>
<article class="wrapper"><p>Some text 2.</p></article>
<article>No class.</article>
<article class="wrapper"><p>Some text 3.</p></article>

I am trying to figure out a way to end up with a template literal of this code:

<article class="wrapper"><p>Some text 1.</p></article>
<article class="wrapper"><p>Some text 2.</p></article>
<article class="wrapper"><p>Some text 3.</p></article>

So that I can inject the entire thing as the innerHTML of another element on a different page.

Started with this JS:

articleArray = document.querySelectorAll('.wrapper').outerHTML;
console.log(articleArray)
// This will return an array with the full HTML of the 3 "wrapper" articles as each item

Then tried to create a loop that will take the full HTML of item 1 on first loop and set it as something like an accumulator value. Then on second loop, it would take the full HTML of item 2 and concatenate it directly to item 1, and that would be the new accumulator value. And so on.

I have far more than 3 items for what I'm applying this to in real life, otherwise I could just do something like articleArray[0] + articleArray[1] + articleArray[2].

I tried a million things, but here were the closest attempts:

  1. Logging
var articleArray = document.querySelectorAll('.wrapper').outerHTML;
for (i = 0; i < searchWrappers.length; i++) {
  searchWrapper = articleArray[i];
  console.log(searchWrapper);
}
// Console log brought back 3 objects, but I need to combine them
  1. Concatenating
var articleArray = document.querySelectorAll('.wrapper').outerHTML;
var searchStr = ''
for (i = 0; i < articleArray.length; i++) {
  itemHTML = articleArray[i];
  fullString = searchStr += itemHTML;
  console.log(fullString);
}
// This did concatenate the 3 items, but only as:
// [object HTMLElement][object HTMLElement][object HTMLElement]
// and not the actual HTML
  1. Concatenating and logging
const articleArray = document.querySelectorAll('.wrapper').outerHTML;
const sum = articleArray.reduce((accumulator, value) => 
  $(accumulator).concat($(value)));
console.log(sum);
// This brought back a normal array of the 3 items again

Any help appreciated!

Edit: Thanks for all your responses! I am going to go through them now and testing your solutions on my real code to see what works best.

Katie Reynolds
  • 317
  • 2
  • 16
  • Uh, `outerHTML` returns a string not an array. Were you actually using [`.children`](https://developer.mozilla.org/en-US/docs/Web/API/Element/children)? – Bergi Apr 21 '21 at 00:19
  • @Bergi oops yes you are right, the .outerHTML worked in my real code which is slightly different, but I translated it to my dumbed-down example code here incorrectly! – Katie Reynolds Apr 21 '21 at 01:09
  • I've closed this because it's unclear to me what you're really asking for. It appears you're wanting us to accomplish a task, but you have a bunch of attempts which are clearly broken and don't do what you claim they do. Are you looking for debugging? All of the code you've provided doesn't do what you state it will do (i.e. it all results in errors, rather than what you say you got). – Makyen Apr 21 '21 at 08:45
  • You say you want a "template literal", but template *literals* only exist as actual text within JavaScript source code. Template literals are not a data type. Template literals within your JavaScript source code are converted into strings by the interpreter. They are not something which is easily manipulable. It's not that you would *never* want to generate one (I have some code that does do so, but it's actually generating JavaScript source code). It would be *very* unusual that you actually wanted a template literal. You almost certainly want just a normal a string. – Makyen Apr 21 '21 at 08:46
  • It would also be helpful for you to provide a bit more (i.e. the containing elements and other elements) of the HTML structure which you are attempting to partially clone, as it may be significantly easier to accomplish this in a different manner, rather than the methodology which you have chosen. Basically, it appears this may involve an [XY problem](//www.google.com/search?as_q=XY+problem), which means it might be helpful to step back and look at the actual thing you're attempting to accomplish, rather than asking about the way that you've decided to implement your solution to that problem. – Makyen Apr 21 '21 at 08:53
  • Sorry if I was unclear @Makyen! I’m new to JS so still grasping some concepts. I did receive several working answers to my issue though so even if it was not worded correctly, it seems a few people understood the task I was trying to accomplish. – Katie Reynolds Apr 21 '21 at 12:14

5 Answers5

1

This should help you:

function contains(selector, text) {
    var elements = document.querySelectorAll(selector);
    return [].filter.call(elements, function (element) {
        return RegExp(text).test(element.textContent);
    });
}

var articleArray = contains(".wrapper", 'menu');
// var articleArray = contains(".wrapper", /menu/i); // for case-insensitive
let templateLiteral = ``;
articleArray.forEach(function (article) {
    templateLiteral += article.outerHTML + '\n';
})
console.log(templateLiteral);
article {
    display: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<article class="wrapper"><p>Some text 1. menu</p></article>
<article>No class.</article>
<article class="wrapper"><p>Some text 2. menu</p></article>
<article>No class.</article>
<article class="wrapper"><p>Some text 3.</p></article>


</body>
</html>

The basic idea is to loop through each HTML element and get add the outerHTML of the element to the template literal.

Edit: Thanks to this answer, it helps me achieve the desired effect of finding a vanilla js replacement for :contains in jQuery.

Parzival
  • 2,051
  • 4
  • 14
  • 32
  • 2
    That does not actual construct a template literal i.e. `let templateLiteral = \`\`` just because you used back-ticks. It is still a basic string. – Mr. Polywhirl Apr 20 '21 at 23:57
  • @Mr.Polywhirl The final result is the same. There's no difference between the end result of your answer and mine. Both are of type `string` – Parzival Apr 21 '21 at 00:07
  • 1
    I was just pointing it out. You could have initialized it as `''` or `""` and it would make no difference. – Mr. Polywhirl Apr 21 '21 at 00:25
  • @Parzival I am so close to getting your solution to work, however I'm encountering an error because instead of targeting ```document.querySelectorAll('.wrapper');```, I am targeting only the .wrapper's that contain the word "menu" in their innerHTML, like this: ```$('.wrapper:contains("menu")');```. They technically bring back the same console log array, but for some reason, the jQuery version keeps throwing me an error while the document.querySelect version is correctly returning all the code I need. Is there a vanilla JS way to target the .wrapper elements containing a specific word? – Katie Reynolds Apr 21 '21 at 01:32
  • @KatieReynolds Check out the updated answer :) – Parzival Apr 21 '21 at 01:48
  • @Parzival that worked! I need to study your code a little more to understand the RegExp part, which is something I'm unfamiliar with, but I'm getting what I need now, thank you :) – Katie Reynolds Apr 21 '21 at 01:56
1

You could map the elements to their outer HTML and then join the results.

const htmlString = [...document.querySelectorAll('.wrapper')]
  .map(({ outerHTML }) => outerHTML.trim())
  .join('\n');

console.log(htmlString);
.as-console-wrapper { top: 0; max-height: 100% !important; }

.wrapper { display: none; }
<article class="wrapper">
  <p>Some text 1.</p>
</article>
<article>No class.</article>
<article class="wrapper">
  <p>Some text 2.</p>
</article>
<article>No class.</article>
<article class="wrapper">
  <p>Some text 3.</p>
</article>
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
1

You can create template in the following way, via concatenating each article outerHTML:

var articleArray = document.querySelectorAll('.wrapper');
var template = '';

articleArray.forEach(article => template += article.outerHTML);

console.log(template)

Example in jsbin

if you need to check for extra word (for example 'menu') inside each article, you could add the following condition:

articleArray.forEach(article => {
  if (article.innerText.includes('menu')) {
     template += article.outerHTML.trim() 
  }
});
Andrew Ymaz
  • 1,863
  • 8
  • 12
  • I am close to getting your solution to work, however I'm encountering an error because instead of targeting document.querySelectorAll('.wrapper');, I am targeting only the .wrapper's that contain the word "menu" in their innerHTML, like this: $('.wrapper:contains("menu")');. They technically bring back the same console log array, but for some reason, the jQuery version keeps throwing me an error while the document.querySelect version is correctly returning all the code I need. Is there a vanilla JS way to target the .wrapper elements containing a specific word? – Katie Reynolds Apr 21 '21 at 01:34
  • hey @KatieReynolds you can do that, please check example below: https://jsbin.com/weduvetuhi/edit?html,js,console,output you will need to do extra check for before concatenating the template variable: articleArray.forEach(article => { if (article.innerText.includes('menu')) { template += article.outerHTML.trim() } }); – Andrew Ymaz Apr 21 '21 at 01:53
  • thank you for showing how I would do the extra check! That is easy enough for me to understand :) – Katie Reynolds Apr 21 '21 at 02:00
  • 1
    you are welcome, pro tip is to use `innerText` property which outputs only text of the node without children html (e.g. nodes) – Andrew Ymaz Apr 21 '21 at 02:02
1

You can also fetch only wrappers and push them in new element to make a template:

let result = document.createElement("div")
document.querySelectorAll('.wrapper').forEach(e =>
  result.insertAdjacentElement("beforeend", e))

console.log(result.innerHTML)
<article class="wrapper">
  <p>Some text 1.</p>
</article>
<article>No class.</article>
<article class="wrapper">
  <p>Some text 2.</p>
</article>
<article>No class.</article>
<article class="wrapper">
  <p>Some text 3.</p>
</article>

Then you can use result.innerHTML to transfer all.

Also see: Element.insertAdjacentElement()

EDIT: You will actuality need to use cloning so wrappers does not get removed from DOM:

let result = document.createElement("div")

document.querySelectorAll('.wrapper').forEach(e => {
  let cln = e.cloneNode(true)
  result.insertAdjacentElement("beforeend", cln)
})

console.log(result.innerHTML)
<article class="wrapper">
  <p>Some text 1.</p>
</article>
<article>No class.</article>
<article class="wrapper">
  <p>Some text 2.</p>
</article>
<article>No class.</article>
<article class="wrapper">
  <p>Some text 3.</p>
</article>
ikiK
  • 6,328
  • 4
  • 20
  • 40
  • I really like that insertAdjacentElement property. This solution should work for me, however I'm having a problem because instead of targeting ```document.querySelectorAll('.wrapper');```, I am targeting only the .wrapper's that contain the word "menu", like this: $('.wrapper:contains("menu")');. They technically bring back the same console log array, but for some reason, the jQuery version keeps throwing me an error while the document.querySelect version is correctly returning all the code I need. Is there a vanilla JS way to target the .wrapper elements containing a specific word? – Katie Reynolds Apr 21 '21 at 01:43
  • 1
    Of course, it would just go: `forEach(e => { if (e.innerHTML.includes("menu")){...` You would just need to wrap login inside for each in another if that just does elements content includes words menu. https://www.w3schools.com/Jsref/jsref_includes.asp @KatieReynolds – ikiK Apr 21 '21 at 11:09
  • (offtopic) People do often come here just because they've got a really bad advice or coding habit on w3schools (which has nothing to do with W3C, it's not a community driven website and they sell irrelevant certificates). Rather: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes – Roko C. Buljan Apr 21 '21 at 14:05
  • 1
    @ikiK just wanted to let you know I did end up using part of your solution along with part of another solution to make my code work! ```e.innerHTML.includes("menu")``` is indeed what I needed, in order to avoid using jQuery. Thanks again! – Katie Reynolds Apr 21 '21 at 20:23
1

DOMParser is the right tool if you really have:

"the items in the array are blocks of HTML code"

// I have an array of elements as HTML strings:
const elArray = [
  `<article class="wrapper"><p>Some text 1.</p></article>`,
  `<article>No class.</article>`,
  `<article class="wrapper"><p>Some text 2.</p></article>`,
  `<article>No class.</article>`,
  `<article class="wrapper"><p>Some text 3.</p></article>`,
];

// DOMParser is the right tool for the job!
const DOC = new DOMParser().parseFromString(elArray.join(""), "text/html");

// Use selectors as normal!
const ELs = DOC.querySelectorAll(".wrapper");

// DEMO TIME: retrieve elements and finally
// place them somewhere in the app
document.querySelector("#test").append(...ELs);
<div id="test"></div>

If instead you actually have a NodeList of Elements:

// I am trying to figure out a way to end up
// with a template literal!

const ELs = document.querySelectorAll("article.wrapper"); 
const literal = [...ELs].reduce((s, EL) => s + EL.outerHTML, "");
console.log(literal);
<article class="wrapper"><p>Some text 1.</p></article>
<article>No class.</article>
<article class="wrapper"><p>Some text 2.</p></article>
<article>No class.</article>
<article class="wrapper"><p>Some text 3.</p></article>

Depends on what you're actually building, you need to be aware that when taking items from the DOM and converting them to string you might lose any Element Properties or Events (like "click" etc) assigned to those elements.

In such case you might just want to perhaps simply use Element.append(...myNodesList) to move them to your desired new location in the DOM:

// Let's say they have some event listeners already assigned:
document.querySelectorAll(".wrapper").forEach(EL => EL.addEventListener("click", (ev) => {
  console.log(ev.currentTarget.textContent);
}));

// Get your elements
const ELs = document.querySelectorAll("article.wrapper"); 
// And simply move them somewhere else!
document.querySelector("#test").append(...ELs); 
// Click events are preserved!!
#test { padding: 10px; background: #eee; }
<article class="wrapper"><p>CLICK ME! 1.</p></article>
<article>No class.</article>
<article class="wrapper"><p>CLICK ME! 2.</p></article>
<article>No class.</article>
<article class="wrapper"><p>CLICK ME! 3.</p></article>

<div id="test">
  <!-- Let's say you want to insert your .wrapper elements here! -->
</div>
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313