Get all headers and resursively create a tree - javascript

I want to create a tree using headers.
Example:
<h1>Wow</h1>
<h2>Blablablub</h2>
<p>Lorem Ipsum...</p>
<h1>Lalalala</h1>
<p>Lorem Ipsum...</p>
<h1>Ble</h1>
<h2>Test</h2>
<h3>Third</h3>
<p>Lorem Ipsum...</p>
This list should be created:
<ul>
<li>
<a>Wow</a>
<ul>
<li>
<a>Blablablub</a>
</li>
</ul>
</li>
<li>
<a>Lalalala</a>
</li>
<li>
<a>Ble</a>
<ul>
<li>
<a>Test</a>
<ul>
<li>
<a>Third</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
a tags should have a custom id but that isn't important for this question. I tried to do this but I couldn't figure it out. Here's what I tried:
function find_titles(find_num, element, prefix=""){
temp_list = $("<ul></ul>");
element.find(`${prefix}h${find_num}`).each(function(i, object){
let text = $(object).text();
let id = text.replace(/[^0-9a-zA-Z]/gi, "") + random_chars();
$(object).attr("id", id);
if ($(object).next().prop("tagName").toLowerCase() == `h${find_num + 1}`){
console.log($(object));
next_titles = find_titles(find_num + 1, $(object), "+ ")[0].innerHTML;
} else {
next_titles = "";
}
$(`<li>${text}${next_titles}</li>`).appendTo(temp_list);
});
return temp_list;
}
EDIT
This:
<h1>First</h1>
<h2>Second</h2>
<p>Lorem Ipsum</p>
<h3>Third</h3>
Should be normally converted into this:
<ul>
<li>
<a>First</a>
<ul>
<li>
<a>Second</a>
</li>
</ul>
</li>
<li>
<a>Third</a>
</li>
</ul>
I don't care wether the first is a h1 h2 or a h3. In the text it's only important for styling but in the tree it isn't important.

You can first clear your data to get only heading nodes and their number and text. After that you can loop the data and build tree structure based on levels using array and index number for each level.
function tree(data) {
data = Array.from(data).reduce((r, e) => {
const number = e.nodeName.match(/\d+?/g);
if(number) r.push({ text: e.textContent, level: +number })
return r;
}, [])
const result = $("<ul>")
const levels = [
[], result
]
data.forEach(({ level, text }) => {
const li = $("<li>")
const a = $("<a>", { text, href: text })
levels[level + 1] = $('<ul>')
li.append(a)
li.append(levels[level + 1]);
levels[level].append(li)
})
return result;
}
const result = tree($("body > *"));
$("body").html(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>Wow</h1>
<h2>Blablablub</h2>
<p>Lorem Ipsum...</p>
<h1>Lalalala</h1>
<p>Lorem Ipsum...</p>
<h1>Ble</h1>
<h2>Test</h2>
<h3>Third</h3>
<p>Lorem Ipsum...</p>
You could also do this in one reduce method and add to tree if the element is heading.
function tree(data) {
const result = $("<ul>")
const levels = [
[], result
]
Array.from(data).reduce((r, { textContent: text, nodeName }) => {
const number = nodeName.match(/\d+?/g);
const level = number ? +number : null;
if(level) {
const li = $('<li>').append($("<a>", { text, href: text }))
r.push({ level: r[level + 1] = $('<ul>') })
r[level].append(li.append(levels[level + 1]))
}
return r;
}, levels)
return result;
}
const result = tree($("body > *"));
$("body").html(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>Wow</h1>
<h2>Blablablub</h2>
<p>Lorem Ipsum...</p>
<h1>Lalalala</h1>
<p>Lorem Ipsum...</p>
<h1>Ble</h1>
<h2>Test</h2>
<h3>Third</h3>
<p>Lorem Ipsum...</p>

You can iterate through all the H1 elements and then iterate through all the next header elements (all except H1). Here is an example:
const elements = $('h1').map(function() {
let container = $('<li>');
const ret = container;
container.append($('<a>').text($(this).text()));
let next = $(this).next('h2, h3, h4, h5');
while (next.length) {
const tmp = $('<li>');
tmp.append($('<a>').text(next.text()));
container.append(tmp);
container = tmp;
next = next.next('h2, h3, h4, h5');
}
return ret;
}).get();
const parent = $('<ul>');
parent.append(elements);
console.log(parent[0].innerHTML);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>Wow</h1>
<h2>Blablablub</h2>
<p>Lorem Ipsum...</p>
<h1>Lalalala</h1>
<p>Lorem Ipsum...</p>
<h1>Ble</h1>
<h2>Test</h2>
<h3>Third</h3>
<p>Lorem Ipsum...</p>

Using :header selector and tagName property
let $sub, $ul = $('<ul/>')
$(':header').each(function() {
let $this = $(this),
$prev = $this.prev(':header'),
$parent = $prev.length && $prev.prop('tagName') < $this.prop('tagName') ? $sub : $ul
$parent.append('<li><a>' + $this.text() + '</a></li>')
$sub = $('<ul/>').appendTo($parent.find('li:last'))
})
$('body').html($ul)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>Wow</h1>
<h2>Blablablub</h2>
<p>Lorem Ipsum...</p>
<h1>Lalalala</h1>
<p>Lorem Ipsum...</p>
<h1>Ble</h1>
<h2>Test</h2>
<h3>Third</h3>
<h3>Third</h3>
<p>Lorem Ipsum...</p>
<h1>First</h1>
<h2>Second</h2>
<p>Lorem Ipsum</p>
<h3>Third</h3>
<h1>First</h1>
<h2>Second</h2>
<p>Lorem Ipsum</p>
<h3>Third</h3>

Related

Generate Table of content dynamicaly with jquery

How do I generate table of contents dynamically. following is my code but its only for one heading tag h3. I need it to work for all headings.
Here is my following sample format of the post :
<html>
<head></head>
<body>
<div id="tableofcontent"></div>
<div class="entry-content">
<h1 id="Test1">Main Heading</h1>
<p>Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum</p>
<h2 id="Test2"> Sub Heading</h2>
<p>Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum</p>
<h3 id ="Test3">Sub Sub Heading</h3>
<p>Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum</p>
<h4>Sub Sub Heading</h4>
<p>Lorem Ipsum Lorem IpsumLorem IpsumLorem Ipsum</p>
</div>
</body>
</html>
How do I generate table of contents dynamically.
following is my code but its only for one heading tag h3. I need it to work for all headings.
jQuery(document).ready(function($) {
var $aWithId = $('.entry-content h3[id]');
if ($aWithId.length != 0) {
if ($aWithId.length > 0) {
$('#tableofcontent').prepend('<nav class="toc"><h3 class="widget-title">Table of Contents</h3><ol></ol></nav>');
}
}
var $aWithId = $('.entry-content h3[id]');
if ($aWithId.length != 0) {
$('.entry-content').find($aWithId).each(function() {
var $item = $(this);
var $id = $(this).attr('id');
var li = $('<li/>');
var a = $('<a/>', {
text: $item.text(),
href: '#' + $id,
title: $item.text()
});
a.appendTo(li);
$('#tableofcontent .toc ol').append(li);
});
}
});

Is it possible to use querySelectorAll with offsetHeight?

I would like to know if it's possible to get the height of many element with offsetHeight
<div class="container">
<div class="card">
<h4 class="card__title">My Title</h4>
<div class="card__img">
<img src="image.jpg" alt="">
</div>
<div class="card__description">
<p>
Lorem ipsum dolor, sit amet consectetur adipisicing elit.
</p>
</div>
</div>
<div class="card">
<h4 class="card__title">Thailande</h4>
<div class="card__img">
<img src="image.jpg" alt="">
</div>
<div class="card__description">
<p>
Description 2
</p>
</div>
</div>
</div>
I would like to to have a translate on the .card__img of the height of .card__description on a 'mouseover', is it possible to get the offsetHeight using querySelectorAll('.card__description).offsetHeight and using a loop?
I tried but didin't work.
Thanks guys, I did it!
Here is my solution
const cards = document.querySelectorAll('.card')
function cardAnimation () {
for (let i = 0; i < cards.length; i++) {
let card = cards[i]
let cardImg = card.querySelector('.card__img')
let description = card.querySelector('.card__description')
let descriptionHeight = description.offsetHeight
card.addEventListener('mouseover', () => {
description.style.transform = `translateY(0)`;
cardImg.style.transform = `translateY(-${descriptionHeight}px) scale(1.2)`
})
card.addEventListener('mouseleave', () => {
description.style.transform = `translateY(100%)`;
cardImg.style.transform = `translateY(0px) scale(1)`
})
}
}
cardAnimation()

Indicate top heading on table of content jquery

I am making an automatic Table of Content, everything is working fine. I just need to indicated the top heading to be "bold"
jQuery(document).ready(function(){
var ToC =
"<nav role='navigation' class='table-of-contents vNav'>" +
"<ul class='vNav'>";
var newLine, el, title, link;
$(".right_section .intro_title h2, .right_section .section_one_content h2, .right_section .p_section_title h3").each(function() {
el = $(this);
title = el.text();
//link = "#" + el.attr("id");
link = "#" + el.text();
$(this).attr('id', title);
if ($( ".right_section .intro_title h2" )) {
newLine =
"<li class='vNav_heading'>" +
"<a href='" + link + "'>" +
title +
"</a>" +
"</li>";
} else {
newLine =
"<li class='vNav_lists'>" +
"<a href='" + link + "'>" +
title +
"</a>" +
"</li>";
}
ToC += newLine;
});
ToC +=
"</ul>" +
"</nav>";
$(".toc").prepend(ToC);
}); // END DOM
The code above adds a class (.vNav_lists) to all when it should detect (.right_section .intro_title h2) to have a class ".vNav_heading" and the rest should be ".vNav_lists"
This is the HTML markup, this is based on wordpress wysiwyg editor
<div class="left_section">
<div class="toc"></div>
</div>
<div class="right_section">
<div class="intro_title">
<h2>title</h2>
</div>
<p>lorem ipsum dolor</p>
<p>lorem ipsum dolor</p>
<p>lorem ipsum dolor</p>
<div class="section_one_content">
<h2>title</h2>
</div>
<p>lorem ipsum dolor</p>
<p>lorem ipsum dolor</p>
<div class="p_section_title">
<h3>title</h3>
</div>
<p>lorem ipsum dolor</p>
<p>lorem ipsum dolor</p>
<div>
This is the result, but there should be a class .vNav_heading on every top heading.
<div class="toc">
<nav role="navigation" class="table-of-contents vNav">
<ul class="vNav">
<li class="vNav_lists">class should be .vNav_heading</li>
<li class="vNav_lists">Text</li>
<li class="vNav_lists">Text</li>
<li class="vNav_lists">Text</li>
<li class="vNav_lists">class should be .vNav_heading</li>
<li class="vNav_lists">Text</li>
<li class="vNav_lists">Text</li>
</ul>
</nav>
</div>
Hope its understadable, I could not explain it well
You need to check whether parent has class "intro_title"
$(function(){
var ToC =
"<nav role='navigation' class='table-of-contents vNav'>" +
"<ul class='vNav'>";
var newLine, el, title, link;
$(".right_section .intro_title h2, .right_section .section_one_content h2, .right_section .p_section_title h3").each(function() {
el = $(this);
title = el.text();
//link = "#" + el.attr("id");
link = "#" + el.text();
$(this).attr('id', title);
if ($(this).closest(".intro_title").length) {
newLine =
"<li class='vNav_heading'>" +
"<a href='" + link + "'>" +
title +
"</a>" +
"</li>";
} else {
newLine =
"<li class='vNav_lists'>" +
"<a href='" + link + "'>" +
title +
"</a>" +
"</li>";
}
ToC += newLine;
});
ToC +=
"</ul>" +
"</nav>";
console.log(ToC);
$(".toc").prepend(ToC);
}); // END DOM
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="toc">
</div>
<div class="right_section">
<div class="intro_title">
<h2>title</h2>
</div>
<p>lorem ipsum dolor</p>
<p>lorem ipsum dolor</p>
<p>lorem ipsum dolor</p>
<div class="section_one_content">
<h2>title</h2>
</div>
<p>lorem ipsum dolor</p>
<p>lorem ipsum dolor</p>
<div class="p_section_title">
<h3>title</h3>
</div>
<p>lorem ipsum dolor</p>
<p>lorem ipsum dolor</p>
</div>

ES6 - Parse HTML string to Array

I have an HTML formatted string:
let dataString = '<p>Lorem ipsum</p> <figure><img src="" alt=""></figure> <p>Lorem ipsum 2</p> <figure><img src="" alt=""></figure>';
How can I parse this string to get an array of tags as below?
let dataArray = [
'<p>Lorem ipsum</p>',
'<figure><img src="" alt=""></figure>',
'<p>Lorem ipsum 2</p>',
'<figure><img src="" alt=""></figure>',
];
Turn it into a document with DOMParser, then take the children of the body and .map their .outerHTML:
const str = '<p>Lorem ipsum</p> <figure><img src="" alt=""></figure> <p>Lorem ipsum 2</p> <figure><img src="" alt=""></figure>';
const doc = new DOMParser().parseFromString(str, 'text/html');
const arr = [...doc.body.children].map(child => child.outerHTML);
console.log(arr);
(you can also achieve this by creating an element and setting the innerHTML of the element to the string, and then iterating over its children, but that could allow for arbitrary code execution, if the input string isn't trustworthy)
Dom parsing is recommended.
Here using vanilla JS without the DOMParser used in the other answer
let dataString = `<p>Lorem ipsum</p> <figure><img src="" alt=""></figure> <p>Lorem ipsum 2</p> <figure><img src="" alt=""></figure>`;
let domFragment = document.createElement("div");
domFragment.innerHTML = dataString;
const arr = [...domFragment.querySelectorAll("div>p,div>figure")].map(el => el.outerHTML)
console.log(arr)
If you cannot use that, then your SPECIFIC string can be split like this after fixing your nested quotes.
Note any change for example adding a space after the <img..> will break such a script
let dataString = `<p>Lorem ipsum</p> <figure><img src="" alt=""></figure> <p>Lorem ipsum 2</p> <figure><img src="" alt=""></figure>`;
dataString = dataString.replace(/> /g,">|").split("|")
console.log(dataString)
I am not clear with your question. Is that a random string or a html string? The split rule is slice the origin string into html element parts?
If true, I think we can handle it with a dummy element.
For convenient, I use jQuery selector:
let stringToSplit = `<p>Lorem ipsum</p> <figure><img src="" alt=""></figure> <p>Lorem ipsum 2</p> <figure><img src="" alt=""></figure>`
$dummy = $("<div/>"); // create a dummy
$dummy.html(stringToSplit);
var dataArray = [];
var dummyChildren = $dummy.children();
for (var i = 0; i < dummyChildren.length; i++) {
dataArray[i] = dummyChildren[i].outerHTML
}
$dummy = null; // remove from memory
console.log(dataArray)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

javascript removing elements

How to remove such type html elements using javascipt?
<section id="cd-timeline" class="cd-container">
<div class="cd-timeline-block">
<div class="cd-timeline-img cd-location">
<img src="img/cd-icon-location.svg" alt="Location">
</div> <!-- cd-timeline-img -->
<div class="cd-timeline-content">
<h2>Title of section 4</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. .</p>
Read more
<span class="cd-date">Feb 14</span>
</div> <!-- cd-timeline-content -->
</div> <!-- cd-timeline-block -->
</section> <!-- cd-timeline -->
var element = document.getElementById('cd-timeline');
if (element) {
element.parentNode.removeChild(element);
}
You can just use:
document.getElementById("cd-timeline").outerHTML='';
You can use this:
document.getElementById('cd-timeline').remove();
If you would like to remove a element by it's id use:
var elem = document.getElementById("myDiv");
elem.parentNode.removeChild(elem);
If you would like to remove elements by their tag, class, etc. use:
var elems = document.getElementsByTag("myDiv");
for(var i = 0, len = elems.length; i < len; i++) {
if(elems[i] && elems[i].parentElement) {
elems[i].parentElement.removeChild(elems[i]);
}
}

Categories

Resources