The code below generates tables of contents with collapsible subsections from HTML headers. I have done it for a WordPress website, so I can use either PHP or JQuery/JavaScript. I have used PHP to generate the HTML of the ToC and JQuery to collapse/un-collapse the sections.
The code works fine, but I was wondering if it could be simpler/more robust.
PHP fiddle is here: https://paiza.io/projects/fyq6rNEs5H6AO77DkDRRXQ . It outputs HTML code that goes in JSFiddle: https://jsfiddle.net/yu51a5/jasngvfz/37/
PHP code:
$content=" {yu_TOC}<h1>A1</h1>a1<h1>A2</h1>a2 <h2>B1</h2>b1<h2>B2</h2>b2 <h3>C1</h3>c1<h3>C2</h3>c2 <h1>D1</h1>d1 <h2>E1</h2>e1 <h3>F1</h3>f1";
$start_text = strpos($content, '}') + 1;
$toc_title = 'Table Of Contents';
$id_title = /*sanitize_title*/($toc_title);
$index = 1;
$tableOfContents = "";
$old_level = 0;
$maybe_button = "";
// Insert the IDs and create the TOC.
$content = preg_replace_callback('#<(h[1-9])(.*?)>(.*?)</\1>#si', function ($matches) use (&$index, &$tableOfContents, &$old_level, &$id_title, &$toc_title, &$maybe_button) {
$tag = $matches[1];
$toc_entry_title = strip_tags($matches[3]);
$hasId = preg_match('#id=("|”)(.*?)\1[\s>]#si', $matches[2], $matchedIds);
$id = $hasId ? $matchedIds[2] : ($id_title . '-' . $index++ . '-' . /*sanitize_title*/($toc_entry_title));
$new_level = intval($tag[1]);
// <div> is not closed, because we might want to add a button
$tableOfContents_entry = "<div class='item-$tag' id='toc_item_$id'><a href='#$id'>$toc_entry_title</a>";
if ($new_level > $old_level) { // $new_level = $old_level + 1
// adding a button + other html from the previous iteration
$tableOfContents .= $maybe_button . $tableOfContents_entry;
} else {
// close the <div>'s
for ($x = $new_level; $x < $old_level; $x++) {
$tableOfContents_entry = "</div>" . $tableOfContents_entry;
}
$tableOfContents .= "</div>" . $tableOfContents_entry;
}
// creating html in case it is needed at the next iteration
$maybe_button = '<input class="toc_button" id="button_toc_' . $id . '" type="button" value="+" /></div><div class="div_toc_initial" id="div_button_toc_' . $id . '">';
$old_level = $new_level;
return "<$tag $matches[2] id='$id'>$matches[3]</$tag><a href='#toc_item_$id'>↑ Back To $toc_title ↑</a>";
}, $content);
// make sure all divs are closed
$nb_opening_divs = preg_match_all('/<div(.*?)>/i', $tableOfContents, $matches);
$nb_closing_divs = preg_match_all('/<\/div>/i', $tableOfContents, $matches);
for ($x = $nb_closing_divs; $x < $nb_opening_divs; $x++) {
$tableOfContents .= "</div>";
}
$result = "<h1><a id='$id_title'>$toc_title</a></h1>" . '<div>' . $tableOfContents . '</div>' . trim(substr($content, $start_text));
echo $result;
JQuery code:
$('.toc_button').click(function() {
if ($(this).val() === "-") {
$(this).val("+");
} else {
$(this).val("-");
}
$('#div_' + this.id).toggle();
});