Creating a Jumplist With DirWalker

Generate a list of links to jump to document sections and back to the top.

By Bob Ray  |  April 5, 2022  |  6 min read
Creating a Jumplist With DirWalker

In the previous articles—Extending DirWalker and More DirWalker, we’ve been playing with producing a report based on the MODX codebase using the DirWalker class. Often, when you create a page that’s meant to be used as a reference, you want to put a “jumplist” at the top with links to the various parts of the document and insert links throughout the page that let the user jump back to the jumplist. No one wants to code that by hand and edit it each time the page changes.

In this article, we’ll look at how to create the jumplist and the “Back to Top” links automatically. We’ll add that to our previous code that reports on the MODX events in the core and manager directories. We’ll also make the output a tiny bit prettier. The new code is identified in comments.

The Code

As before, this code assumes that you’ve installed the DirWalker package or downloaded the class file. See the note below on where to get DirWalker. Like our previous example, it skips some types of files and directories and only looks in files with '.class' in their names.

require_once MODX_CORE_PATH . 'components/dirwalker/model/dirwalker/dirwalker.class.php';

class MyDirWalker extends DirWalker {

    /* We override this method to process the files as found */
    protected function processFile($dir, $fileName) {
        /* Note that $dir is just the directory with no
           trailing slash so we have to create the full path*/
        $fullPath  = $dir . '/' . $fileName;

        /* These are to make sure we've found them all */
        $trueCount = 0;
        $foundCount = 0;

        /* get the file's content */
        $content = file_get_contents($fullPath);
        /* Set $trueCount -- Only count instances *not*
               preceded by a space */
        preg_match_all('#[^\s]invokeEvent#', $content, $preMatches);
        $trueCount += count($preMatches[0]);

        /* Shorten the path for use in the display */
        $shortPath = str_replace(MODX_CORE_PATH, 'core/', $fullPath);
        $shortPath = str_replace(MODX_MANAGER_PATH, 'manager/', $shortPath);

        $pattern = '#[^\s]invokeEvent\([\'\"]*(\$*[^,"\']+)[\'\"\s]*,?([^;]*;)#s';

        preg_match_all($pattern, $content, $matches, PREG_SET_ORDER);

        if (!empty($matches[0])) {
            foreach($matches as $match){
                $foundCount++;
                if ($match[2] == ');') {
                    $match[2] = 'None';
                }
                $this->files[$shortPath][] = array(
                    'event' => $match[1],
                    'variables' => $match[2],
                );
            }
            /* print a message if we failed to find all of them or found
               too many of them -- Ideally, this never executes */
            if ($trueCount != $foundCount) {
                echo "\n\n" . $shortPath;
                echo "\nTrueCount: " . $trueCount .
                    ' -- ' . 'FoundCount: ' . $foundCount;
            }
        }
    }
}

$output = '';
$dw = new MyDirWalker($modx);
$dw->setIncludes('.class'); /* Remove this for MODX 3 */
$dw->setExcludes('-all,-min,.git,modprocessor');
$dw->setExcludeDirs('cache,.git,packages,components');
$dw->dirWalk(MODX_CORE_PATH, true);
/* *** New - Search manager directory too *** */
$dw->dirWalk(MODX_MANAGER_PATH, true);

$files = $dw->getFiles();

$output = '';

/* *** New - section to produce jumplist *** */
$jumpList = '';
$jumpArray = array();
$tempArray = array();
/* Put the event names in an array so we can sort them */
foreach($files as $file => $events) {
    foreach ($events as $event) {
        $tempArray[] = $event['event'];
    }
}
$jumpArray = array_unique($tempArray, SORT_STRING);
unset($tempArray);
sort($jumpArray);

/* Make the jumplist */
foreach ($jumpArray as $eventName) {
    $jumpList .= '<a href="[[~[[*id]] ]]#' . $eventName . '">' .
        $eventName . '</a> &nbsp;&nbsp;';
}
/* *** New - anchor link for "Back to Top" links below *** */
$output.= '<br /><a name="Top"></a>';

/* *** New - Add jumplist to output *** */
$output .= '&nbsp;&nbsp;' . $jumpList . '<br />';

/* Process files and events */
foreach($files as $file => $events) {
    $output .= "\n#### " . $file . '';
    foreach($events as $event) {
        $e = $event['event'];
        $v = $event['variables'];
        if (strpos($v, 'array') !== false) {
            $v = preg_replace('#^\s*\'#m', '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\'', $v);
            $v = preg_replace('#^\s*\)\);#m', '<br />&nbsp;&nbsp;&nbsp;&nbsp;);', $v);
        } else {
            $v = str_replace(');', '', $v);
        }
        /* *** New - anchor link to event for jumplist *** */
       $output .= '<a name="' . $e . '"></a>';
       $output .= "&nbsp;&nbsp;&nbsp;&nbsp;<span style=\"color:teal;\">Event:</span> <b>" . $e . '</b><br />';
       $output .= "&nbsp;&nbsp;&nbsp;&nbsp;<span style=\"color:teal;\">Variables:</span> " . $v . '';
    }

    /* *** New - Back to Top link at bottom of each section *** */
    $output .= '<a style="font-size:10pt;" href="[[~[[*id]] ]]#top ">Back to Top</a>';
}

return $output;

The Output

You can see the output of the code above (which is much too long to post here) here. It reports all of the event invocations in the MODX core and Manager directories (excluding the few events that fire in add-on components), the files they occur in, and the arguments sent in each call to invokeEvent().

The Jumplist

The first bit of new code is an anchor link that goes at the top of the page:

$output.= '<br/><a name="Top"></a>';

The link above is the target for all the “Back to Top” links we’ll insert at the end of each section of the report. Those links are produced by this line at the end of the code:

$output .= '<a style="font-size:10pt;" href="[[~[[*id]] ]]#top ">Back to Top</a>';

The jumplist itself is first created as a temporary array so we can sort it and eliminate duplicate entries:

foreach($files as $file => $events) {
    foreach ($events as $event) {
        $tempArray[] = $event['event'];
    }
}

Then we sort and eliminate the duplicates:

$jumpArray = array_unique($tempArray, SORT_STRING);
unset($tempArray);
sort($jumpArray);

Next, we create the jumplist itself as a long string and add it to the output after the “Top” anchor link. Notice that the event name link is followed by a regular space followed by two non-breaking spaces. This insures some space between the entries but still lets the output wrap at the end of each line. Using all non-breaking spaces would put most of the jumplist off the right side of the page:

foreach ($jumpArray as $eventName) {
    $jumpList .= '<a href="[[~[[*id]] ]]#' . $eventName . '">' .
        $eventName . '</a> &nbsp;&nbsp;';
}
$output.= '<br/><a name="Top"></a>';
$output .= '&nbsp;&nbsp;' . $jumpList . '<br/>';

Finally, in the code loop that produces the main part of the report, we add a link target for each of the jumplist item links to jump to ($e is the event name):

$output .= '<a name="' . $e . '"></a>';

One Drawback

Our code has one minor flaw. Because we’re using array_unique() on the jumplist, we only have one link to each event, so for events that occur in more than one file there will only be a link to the first instance. This can be solved, but the solution is complex and would slow down the page-load somewhat. Just removing the array_unique() call won’t help, because all the repeated links in the jumplist would still jump to the first instance. To solve it, we’d have add another member to the event array (or a prefix or suffix on the event name) in order to make each link in the jumplist and its target unique. We’ll tackle that in the next (and final) article in this series.

There are very few System Events that are called more than once, and for those, the variables sent are the same in every instance, so our flaw is quite minor, but still an annoyance.

Getting DirWalker

DirWalker is a single class file. You can see it at GitHub.

You can also install it in MODX through Package Manager (though the class does not require MODX) or get it at the MODX Repository. If you install the package, you’ll also get several files showing examples of how to use DirWalker to produce reports containing information gleaned from the MODX Codebase.


Bob Ray is the author of the MODX: The Official Guide and dozens of MODX Extras including QuickEmail, NewsPublisher, SiteCheck, GoRevo, Personalize, EZfaq, MyComponent and many more. His website is Bob’s Guides. It not only includes a plethora of MODX tutorials but there are some really great bread recipes there, as well.