Jump to content

Table of content in text field


MilenKo
 Share

Recommended Posts

Hello again guys.

Reworking my Knowledge theme, I was asked by my colleagues to add a table of content where one can click and be lead straight to the point (anchor to a paragraph or else). I was checking a few hours now as to how can this be done and I am not sure I have found an answer. Now, to better understand the need, the idea is that I have a multi line text field that would be filled up with some text so I am aiming at showing some anchors automatically upon some action from the editor's side or else. So far the only doable way I think could work is to have a keyword, class or other html chunk that would be added to the text and then the whole field content scanned for that specific code/text and added to the anchors link in the sidebar. But if that is the best approach, I am not sure yet.

Btw, I am not looking at just creating anchor to a specific text inside the field, but show a table of contents outside of the echoed field (like in a sidebar or else). Writing this now, I just got another idea of creating a new field where I would have content like:

title, url (where title would be presented as the anchor text and url would be the link to the content) but it gets way more complicated and I am sure there was already someone else who have thought about this and have a more elegant way of achieving the goal.

I attached a demo image, where the content is $page->body and the table of content is the h1/2/3 etc. tags inside of the text field.

table_of_content_processwire.png

Link to comment
Share on other sites

Why not use a repeater field with title and body instead of one single CKEditor field? You could then just iterate over the repeater items, render the anchors when outputting the titles and then iterate again in your TOC to render the links.

  • Like 2
Link to comment
Share on other sites

you achieve that using the simple html dom parser, just have it scan for the anchors;
the Process Documentation module has an example that will do exactly what you need; you can download a copy of the simple html dom parser and put it somewhere you can include it, and then process the body field as is shown in the example...

https://github.com/outflux3/ProcessDocumentation/blob/master/examples/uikit-example.php

  • Like 2
Link to comment
Share on other sites

You can adapt the following Hanna Code example to a function for output anywhere... without dependencies

----------------

Jumplinks Hanna Code

This Hanna code is meant to be used in your body copy. It collects all the headline tags in the text and turns them into anchor jump links while outputting a linked table of contents. It will put the table of contents wherever you type the Hanna code.

$for = str_replace(',', ' ', $for);
$for = explode(' ', $for);
foreach($for as $k => $v) $for[$k] = trim($v);

$for = implode('|', $for);
$anchors = array(); 
$value = $hanna->value; 

if(preg_match_all('{<(' . $for . ')[^>]*>(.+?)</\1>}i', $value, $matches)) {
  foreach($matches[1] as $key => $tag) {
    $text = $matches[2][$key];
    $anchor = $sanitizer->pageName($text, true);
    $anchors[$anchor] = $text; 
    $full = $matches[0][$key]; 
    $value = str_replace($full, "<a name='$anchor' href='#'></a>$full", $value); 
  }  
  $hanna->value = $value; 
}

if(isset($topic)) {
    $topic = $topic; 
    } else {
        $topic = 'Page';
        }

if(count($anchors)) {
  echo "<div class='toc'>";
  echo "<p>On this $topic:</p>";
  echo "<ol class='article-toc'>";
  foreach($anchors as $anchor => $text) {
    echo "<li><a href='$page->url#$anchor'>$text</a></li>";
  }
  echo "</ol>";
  echo "</div>";
} else {
  echo '';
}

Usage Examples:

[[jumplinks]] Locates all h2 and h3 headlines, turns them into anchors, and generates a table of contents. This is the default behavior with no attributes.

[[jumplinks for=h2]] Here we specify a 'for' attribute. It produces the same behavior as above except only anchors h2 headlines.

[[jumplinks for="h2 h4"]] Same as above except only anchors h2 and h4 headlines.

----------------

$for is the heading tags that you want to link in the TOC

$hanna->value is the string with the html content you want to process and update

This script modify the original HTML content to include the new anchors, and create the markup for the TOC. Instead of direct echo the markup, you need to return the output... maybe something like the following code (you need to check if I'm using the PHP best practices because I'm not a coder)

function toc( $heading, $content ) {

	$heading = str_replace( ',', ' ', $heading );
	$heading = explode( ' ', $heading );
	foreach ( $heading as $k => $v )$heading[ $k ] = trim( $v );

	$heading = implode( '|', $heading );
	$anchors = array();

	if ( preg_match_all( '{<(' . $heading . ')[^>]*>(.+?)</\1>}i', $content, $matches ) ) {
		foreach ( $matches[ 1 ] as $key => $tag ) {
			$text = $matches[ 2 ][ $key ];
			$anchor = $sanitizer->pageName( $text, true );
			$anchors[ $anchor ] = $text;
			$full = $matches[ 0 ][ $key ];
			$content = str_replace( $full, "<a name='$anchor' href='#'></a>$full", $content );
		}
	}

	if ( count( $anchors ) ) {
		$toc = "<ul class='article-toc'>";
		foreach ( $anchors as $anchor => $text ) {
			$toc .= "<li><a href='$page->url#$anchor'>$text</a></li>";
		}
		$toc .= "</ul>";
	
		return array( $toc, $content );

	} else {
		return null;
	}
}

..or maybe output only the $anchors array and customize the markup in your template according to your page design

  • Like 7
Link to comment
Share on other sites

Thank you very much guys for all the suggestions. I am amazed again of the options to achieve the task. It seems like @Pixrael has given a complete solution and a similar one got from @Macrura but the @adrian one seems to me to be the easiest and simplest. Besides that it would be a good start to get my hands around HannaCode which I tried in the past but did not have much progress like I had with ModX Revo, so decided to play clean and simple. But it is never too late to add some more experience. Let me see how would that turn and report what was done.

The way I see it, H1 tag would be the main anchor (page title) but I could use H2 for sub-anchors and going down to eventually 3-5. Would you consider a smart move to use several H2-5 tags within the same page from the SEO perspective or I should look for another way to grab them? It seems to me like H-tags would be the best to implement and would be styling the paragraphs of the text the way it is meant, but what is the best and common sense practice?

Link to comment
Share on other sites

Going back quite a few decades, an online document used heading tags (H1...H6) to represent the various parts of that document, and is usually shown similar to an outline format. The <title> tag in html was used as the name of the document for search purposes -- Think of a Thesis title. As such, H1 usually represented each part's title (like a chapter title), while subordinate headings represent the different outlined segments. All search engines back then started with this foundation, and they all currently maintain the ability to extract the desired information from this structure.

A number of enhancements were made to tags, specifically the title parameter. For example,

<H1 title="This is a brief description of the introduction text for search purposes">Introduction Text</H1>

Most every html element makes use of the title attribute, but it is more important for heading elements. It is also used with the newer <article> and <section> tags.

So basically, you wind up with something like

<html>
<head>
<title>How to setup payment gateways</title>
</head>
<body>
<h1 title="Configuring your software to utilize various payment gateways">How to setup payment gateways</h1>
  <p>Somewhere in this text, and before the next heading level, you should address the Heading title attribute text.</p>
  <h2 title="Installing the payment gateway">Installation</h2>
  	<p>Again, for seo purposes, you should ensure your heading title text is addressed in the content section for the current heading.</p>
  		<h3 title="...">Step 1...</h3>
  			<p>Step 1 instructions.</p>
  			...
  <h2 title="...">Quick Setup</h2>
  	<p>...</p>
  		<h3 title="...">Step 1...</h3>
  			<p>Step 1 instructions.</p>
  			...
...
</body>
</html>

Where the H1 tag is the title of the document as displayed to the user, H2 would then be similar to chapter titles, eg, Installation, etc. Now your generated TOC may consist of the H2 tags.

You can construct your documents as you see fit. This is only a recommendation.

[edit] Can't edit the code section :/

[edit 2] Thanks @kongondo

Link to comment
Share on other sites

@adrian I tested out the jumplinks and it worked like a charm. It is, however, not clear, how can I handle when I need to show the TOC not within the same field but in the sidebar. During the test, I put the Hanna code inside the body and it showed the proper values of the TOC based on the for values. So now if I can only get those values and move them outside the field it would be perfect. Presently I am playing with Uikit (getting inspired by the admin theme) so would like to show the page headings in a sidebar for easier navigation instead of having them at the top/bottom of the document and loose focus of them once a visitor starts scrolling or clicks on the hyperlinks.

Link to comment
Share on other sites

9 hours ago, MilenKo said:

@adrian I tested out the jumplinks and it worked like a charm. It is, however, not clear, how can I handle when I need to show the TOC not within the same field but in the sidebar. During the test, I put the Hanna code inside the body and it showed the proper values of the TOC based on the for values. So now if I can only get those values and move them outside the field it would be perfect. Presently I am playing with Uikit (getting inspired by the admin theme) so would like to show the page headings in a sidebar for easier navigation instead of having them at the top/bottom of the document and loose focus of them once a visitor starts scrolling or clicks on the hyperlinks.

I haven't tried, but you should be able to extract the code from that Hanna code and add it to your template file in the sidebar, which now that I scroll back up this post looks to be what @Pixrael did. You should be able to use that function in your sidebar.

Link to comment
Share on other sites

9 hours ago, MilenKo said:

@adrian I tested out the jumplinks and it worked like a charm. It is, however, not clear, how can I handle when I need to show the TOC not within the same field but in the sidebar. During the test, I put the Hanna code inside the body and it showed the proper values of the TOC based on the for values. So now if I can only get those values and move them outside the field it would be perfect. Presently I am playing with Uikit (getting inspired by the admin theme) so would like to show the page headings in a sidebar for easier navigation instead of having them at the top/bottom of the document and loose focus of them once a visitor starts scrolling or clicks on the hyperlinks.

if you check out my solution above, it does do exactly what you need, and in UiKit; it also has the added benefit of being able to add UiKit classes to your lists, tables, and first paragraph...

  • Like 1
Link to comment
Share on other sites

This was tested and works...

_func.php

function toc( $heading, $content ) {

	$heading = str_replace( ',', ' ', $heading );
	$heading = explode( ' ', $heading );
	foreach ( $heading as $k => $v )$heading[ $k ] = trim( $v );

	$heading = implode( '|', $heading );
	$anchors = array();

	if ( preg_match_all( '{<(' . $heading . ')[^>]*>(.+?)</\1>}i', $content, $matches ) ) {
		foreach ( $matches[ 1 ] as $key => $tag ) {
			$text = $matches[ 2 ][ $key ];
			$anchor = wire('sanitizer')->pageName($text, true);
			$anchors[ $anchor ] = $text;
			$full = $matches[ 0 ][ $key ];
			$content = str_replace( $full, "<a name='$anchor' href='#'></a>$full", $content );
		}
	}

	if ( count( $anchors ) ) {
		$toc = "<ul class='article-toc'>";
		$url= wire('page')->url;		
		foreach ( $anchors as $anchor => $text ) {
			$toc .= "<li><a href='$url#$anchor'>$text</a></li>";
		}
		$toc .= "</ul>";
	
		return array( $toc, $content );

	} else {
		return null;
	}
}

article.php (example template file)

<?php $out = toc("h2 h3 h4",$page->body); ?>
<div class="page">
	<div class="main">
		<?=$out[1]?>
	</div>
	<div class="sidebar">
		<h3>Table of Contents</h3>
		<?=$out[0]?>
	</div>
</div>

the previous published function was updated because we need to use wire() in _func.php 

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...