Custom enumerations

I probably could tell you, that a RepeaterFlex may still be enumerated like you do with regular repeaters or the RepeaterMatrix. But I won't, since I prefer to keep everything modular and better introduce a method to implement custom evaluation of repeater elements in the plugin code.

We may, for example, wish to build a search index over any text elements inside such a repeater. The other examples already show that plugins may use text fields for various uses, only the plugin "knows", which portions of text are worth adding to the index. To circumvent the classic approach using a switch and duplicating plugin logic, we simply add another method to those plugins, which potentially have something to index. A simple implementation would just return all indexable text, but I prefer something more generic, so we go with an object reference. The plugin code may look like this (in FlexUkBodytext.inc):

    public function textindex(Page $pg, object $textIndex)
        {
        $textIndex->index($pg->TextAreaMarkup);
        }

The $textIndex object is expected to implement a method index which is simply called for any indexable textfield. Simple enough.

What about indexing something more interesting like the marker descriptions from our map implementation? Could be done like this (FlexMapBoxSimple.inc):

    public function textindex(Page $pg, object $textIndex)
        {
        $ar = json_decode('{' . $pg->TextArea . '}', true);    // Try decoding
        if(is_array($ar) && isset($ar['marker']))       // Markers defined?
            {
            foreach($ar['marker'] as &$marker)          // Enumerate markers and...
                $textIndex->index($marker['title']);    // ...add titles to the index
            }
        }

To invoke such a custom plugin method for all repeater items, we simply call a method named, well, invoke. In order to do so, we first need an object which implements an index method. There is no baseclass for such an object, thanks to PHP you are completely free, so I'll go with this:

    class TextIndex {
        var $textBlocks;    // Array holding all blocks
        var $sanitizer;     // simplify sanitizer call a little
        var $sanitizerOptions = [
                'maxLength' => 0,             // 16k
                'stripTags' => true,
                'reduceSpace' => true,        // V3.0.105
                'convertEntities' => true,    // V3.0.105
                ];
        function __construct()
            {
            $this->textBlocks = array();
            $this->sanitizer = wire('sanitizer');
            }
        public function getText($delim=' ') { return(implode($delim, $this->textBlocks)); }    // Return all text as concatenated string
        public function getTextBlocks() { return($this->textBlocks); }            // Return individual text blocks
        public function index(string $t)    // Add a string to our index
            {
            $t = $this->sanitizer->text($t, $this->sanitizerOptions );    // strip away any non-text or do whatever required
            $this->textBlocks[] = $t;
            }
        }

There is hopefully little to explain for this class, it just takes strings and add them to an internal array, which may be retrieved either concatenated or as array.

Now retrieving all text from our content RepeaterFlex simply looks like this:

    $ti = new TextIndex();   // new TextIndex instance
    $page->flex_content->invoke('textindex', $ti);   // collect text
    echo '<p>'.$sanitizer->entities($ti->getText()).'</p>';  // do whatever you like

This site's search index is created exactly like this.

On the other hand, if a concatenated string is all you need, then you may omit the object reference in the invoke call and implement the method like this:

    public function gettext(Page $pg)  // object-less variant, must return a string
        {
        return($pg->TextAreaMarkup);
        }

with

   $allText = $page->flex_content->invoke('gettext');

 

prepared in 56ms (content 15ms, header 1ms, Menu 25ms, Footer 14ms)