タグ一覧ページにページ内検索&ハイライト機能を付ける


タグ一覧ページでは、現在1700個近いタグが1ページに表示される。ページネーションをしないのはブラウザのページ内検索機能ですばやく検索したいから。

だけどタグがずらずら並んでいるのもぶっきらぼうなので、フォームに入力した言葉を含むタグをハイライトしたうえで、画面上部にもコピーされるようにした。

下は初期表示時。

こちらが「決」を入力して「タグ検索」をクリックした結果。

おおまかな仕組み

タグクラウドは下記のように段落タグでくるまれている。入力フォームを画面上部に置いて、入力された言葉を含むタグをハイライトする。

<p class="alignwide listfreak_tagcloud" id="lf-alltags">
    <a href="https://listfreak.com/tag/EQ" class="tag-cloud-link tag-link-75 tag-link-position-1" style="font-size: 1.6em;" aria-label="EQ (89個の項目)">EQ</a>

    <a ... ></a>
    ......
</p>

HTML(入力フォーム)

検索語句を入力する部分はブロックエディタで作成。フォームといっても表示されているHTMLのDOMをいじるだけなので<form>タグは使わない。

JavaScript

lf_highlight_words.js というファイルを作った。このファイルを読み込ませる手順は後述。

// タグ検索ボタンのクリックと処理関数を結びつける
document.getElementById('highlight-btn')
    .addEventListener('click', lfHighlight);

function lfHighlight() {
    1. 前処理
    2. 本処理
}

前処理

ページを読み込み直さないので、前回のハイライト結果や見つかった語句数の表示を消す必要がある。

フォームが空であれば処理終了。フォームに語句が入っていたら、本処理へ。

// タグクラウドが格納された <p> を取得
const alltags = document.getElementById( 'lf-alltags' );

// その中で highlight クラスを持つオブジェクトの集合を取得
const highlighted = alltags.getElementsByClassName( 'highlight' );

// 前回の表示結果が残っていたらhighlight クラスを削除
while ( highlighted.length ) {
    highlighted[0].classList.remove( 'highlight' );
}

// 見つかった検索語句の個数を表示する div
const found = document.getElementById( 'lf-found' );

// 前回の表示結果が残っている場合があるのでクリア
while ( found.firstChild ){
    found.removeChild( found.firstChild );
}

// 検索語句を取得(valueはHTMLInputElementのプロパティ)
const searched = document.getElementById( 'search-tag' ).value;

// 検索語句がなければ終了
if ( '' == searched ) {
    return;
}

// 検索語句を正規表現オブジェクトに
const searchedR = new RegExp( searched, 'i' );

// ヒットしたタグを格納する
const foundTags = document.createElement( 'p' );

// 全タグを1つずつ
alltags.childNodes.forEach( function( tag ) {

    // 検索語句が含まれていたら
    if ( searchedR.test( tag.innerText ) ) {

        // 検索窓の下に表示するためにコピー
        foundTags.appendChild( tag.cloneNode() );

        // ハイライト
        tag.classList.add( 'highlight' );

    }
} );

L5-L18: タグ検索ボタンが2回め以降に押された場合、前回のハイライトやヒットタグ一覧をクリアする必要がある。自分の子孫をすべて削除するメソッドが見つからなかったので一つずつ削除。highlighted は HTMLCollection インターフェイス、found は Element オブジェクトを返すので削除のやり方が違う。いずれにせよ逆ダルマ落し的に最初の要素から一つずつ削除した。

本処理

タグはノードの配列として取得できているので、innerText に検索語句を含むノードを抽出してハイライト用のクラスをまとめて追加する……ようなやりかたがあればと思ったが、見つからなかったので forEach で一つずつ。

// 検索語句を正規表現オブジェクトに
const searchedR = new RegExp( searched, 'i' );

// ヒットしたタグを格納する
const foundTags = document.createElement( 'p' );

// 全タグを1つずつ
alltags.childNodes.forEach( function( tag ) {

    // 検索語句が含まれていたら
    if ( searchedR.test( tag.innerText ) ) {

        // 検索窓の下に表示するためにコピー
        foundTags.appendChild( tag.cloneNode( true ) );

        // ハイライト
        tag.classList.add( 'highlight' );

    }
} );

// 見つかった個数を表示
const foundMsg = document.createElement( 'p' );
foundMsg.textContent = foundTags.childNodes.length + ' FOUND.';
found.appendChild( foundMsg );

// 検索語句を含むタグへのリンクを検索窓の下に表示
if( 0 < foundTags.childNodes.length ) {
    found.appendChild( foundTags );
}

CSS

ハイライト用のクラスには下記のように背景色を変える設定をした。

.highlight {
    background-color: yellow;
}

PHP

作成した JavaScript ファイルを登録する。

add_action( 'wp_enqueue_scripts', 'lf_enqueue_highlight_in_page', 11 );
function lf_enqueue_highlight_in_page() {

    // slug が tags でなければリターン
    if ( 'tags' != get_post_field( 'post_name',  get_the_ID() ) ) {
        return;
    }

    wp_enqueue_script(
        'lf_highlight_words',
        home_url().'/ <snip> /lf_highlight_words.js',
        array(), // No Dependency
        '1.0.0', // Version
        true     // In Footer
    );
}

Posted by

on

in category