WordPress標準の検索ブロックやテーマ付属の検索ウィジェットは、検索する語句を入力して「検索」ボタンをクリックすると、別ページで結果が表示されるようになっていますね。
標準的な検索機能かとは思いますが、結果が見つからなかったりすると、また画面を戻って再検索したりしなければならなくて、訪問者目線ではちょっと使いにくい気もします。
そこで今回試しに導入してみたのが、同じ画面に結果が表示され、入力文字に応じてどんどん絞り込まれていくという検索の仕組みです。
こうすると、結果がすぐに確認できるので、目的のものが見つけやすくなり、PV数が伸ばせるかも..知れません。
と、言葉で説明しても分かりにくいので、以下で実際に動作させています(分かりやすくするために背景色などを付けています)。「WordPress」という単独語や「WordPress ブロックテーマ」などの複合語を入力して動作を確認してみてください。
入力している段階から、どんどん候補が絞り込まれるのがお分かりいただけると思います。
動作を分かりやすくするために、後述するコード以外に背景色などのスタイルを別で適用させています。本ページを参考に機能を追加した後で、自身のサイトに合わせて調整してください
最近のサイトではシングルカラム(サイドバーを持たない)のスタイルが流行しつつあるので、こうしたフリーワード検索フォームを設置しておくことで、よりたくさんのページを閲覧してもらえる一助になるかもしれません。
本ページでは、この機能をWordPressのサイトに導入する方法の一例を紹介していきます。
本ページ掲載のコードを使用するときは
本ページで掲載しているコードは、以下に了承した上で使用ください
- コードは商用・非商用問わず自由に使っていただいて構いませんが、コード追加による不具合やトラブルが発生しても当方では一切責任を負いません
- コードは有効化しているテーマのfunctions.php、style.cssなどへ追加することで機能します。それらのファイルへの変更を行うことに不安のある方は使用しないでください
- コードは本ページの公開日時点で私の環境において動作したものです。WordPressバージョン他環境の違いによって動作しないことがあります
- コードは、セキュリティ、コードの正確さなどにおいて完全なものではありません。中には紹介するコードを簡略化するために省略している部分があるものもありますので、ご自身でコードを十分に検証し、必要な部分の編集を行った上で使用するようにしてください
- 掲載しているのは参考コードです。自身の環境に合わせるための編集はご自身で対応いただく必要があります(コメント欄等から質問いただいても基本回答は致しません)
- 掲載しているコードの転載を禁じます(SNSで紹介いただいたり、本ページへのリンクを張っていただくことは大歓迎です)
画面遷移なしでのサイト内検索機能を実装する方法
本ページのコードは、「WordPress Ajax Search without plugin [ Easy ]」で公開されているコードを基本として、以下のような工夫を行ったものです。
- ショートコードでどこにでも検索フォームを設置できるようにした
- 全角スペースを半角スペースとして認識するようにした
- ショートコードがある場合のみ、検索のためのコールバック関数を出力するようにした
- コールバック関数は1行で出力するようにし、最後に読み込まれるようにした
- 途中で「Enter」キーを押しても何も起こらないようにした
- 取得できた件数を表示できるようにした
- 検索結果を公開済みのものに限定した
- 各所の文字列を変数化して、誤編集を防止できるようにした
上記ページのコードと工夫を施した本ページのコードを見比べながら実装していくと、理解が深まるかも知れません。
また、同一画面内で結果を表示する機能を実装するために「Ajax」という機能を利用しています。「Ajax」については以下のページが参考になると思います。
Ajax検索機能を実装するコード
いろいろやった挙句に動かなかった..ということがないよう、まずは完成コードから紹介します。有効化しているテーマのfunctions.phpへ追加して、表示したい場所へ「ajax-text-search」というショートコードを挿入すれば機能すると思います。
貴サイトでうまく動いたら、後述のコード解説を見て理解を深めるといいでしょう。
/***** ajaxテキスト検索 *****/
/*** [ajax-text-search] というショートコードが含まれていればjQueryをフッターへ出力 ***/
function ajax_text_search_script_in_footer() {
$admin_url = admin_url( 'admin-ajax.php', __FILE__ );
$content_before = '<script defer>function text_search_ajax_script_callback(){';
$content_after = '}</script>';
$script_a = "jQuery.ajax({url: '".$admin_url."',type: 'post',data: {action: 'ajax_text_search_action',keyword: jQuery('#keyword').val()},success: function(data) {jQuery('#datafetch').html( data );}});";
$script_b = "jQuery('input#keyword').keyup(function() {if (jQuery(this).val().length > 2) {jQuery('#datafetch').show();} else {jQuery('#datafetch').hide();}});";
global $post;
if( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'ajax-text-search' ) ) {
echo $content_before.$script_a.$script_b.$content_after;
}
}
add_action( 'wp_footer', 'ajax_text_search_script_in_footer' ,9999);
/*** 検索結果の表示 ***/
function text_ajax_search_result(){
//件数の文字列
$result_before = __('Search Results:','');
$result_after = __('Items','');
$no_results = __('No Results Found','');
//入力値から全角スペースを除去
$formtxt = $_POST['keyword'];
$formtxt = str_replace(' ',' ',$formtxt);
//抽出条件の指定
$args = array(
'post_type' => array('post'), //投稿タイプ
'post_status' => 'publish',//公開済みのものに限定
'posts_per_page' => -1,//全項目を表示
's' => esc_attr( $formtxt ),//全角スペースを半角に変換した上でキーワードをセット
'order' => 'DESC',//降順に表示
'orderby' => 'relevance',//一致度で判断
'no_found_rows' => true,//ページャーを使う時はfalseに。
);
$the_query = new WP_Query($args);
$post_count = $the_query->post_count; //実際にそのページで取得した件数
if( $the_query->have_posts() ) {
echo '<p>'.$result_before.' '.$post_count.' '.$result_after.'</p><ul>';
while( $the_query->have_posts() ): $the_query->the_post(); ?>
<li><a href="<?php echo esc_url( get_permalink() ); ?>"><?php the_title();?></a></li>
<?php endwhile;
echo '</ul>';
wp_reset_postdata();
}else{
echo '<p>'.$no_results.'</p>';
}
die();
}
add_action('wp_ajax_ajax_text_search_action' , 'text_ajax_search_result');
add_action('wp_ajax_nopriv_ajax_text_search_action','text_ajax_search_result');
/*** 検索窓と結果の出力を行うショートコード ***/
function ha_ajax_text_search_form_shortcode( ) {
//入力フォームの文字列
$placeholder = __('Please Input Keywords...','');
$submit_text = __('Submit','');
$wait_text = __('Please wait..','');
/*** 「Enter」で送信されないようにする対策 ***/
/* onsubmit="return false;"で誤送信されないようにする */
/* onclick="submit();"でボタンクリック時のみ送信するようにする */
/* 入力項目が1つなのでダミー項目を作って誤送信されないようにする */
$content= '
<div class="ajax-text-search-bar">
<form action="/" method="get" autocomplete="off" onsubmit="return false;">
<div class="ajax-text-search-bar-inner">
<input type="text" class="text-form" name="s" placeholder="'.$placeholder.'" id="keyword" class="input_search" onkeyup="text_search_ajax_script_callback()">
<input type="text" name="dummy" style="display:none;">
<input type="button" class="search-button" onclick="submit();" value="'.$submit_text.'" />
</div>
</form>
<div class="search_result" id="datafetch">
<ul>
<li>'.$wait_text.'</li>
</ul>
</div>
</div>
<style>
/* フォームのスタイル */
input.text-form ,input.search-button {
font-size: 1.2rem;
padding: 4px;
}
input.text-form{
width: 50%;
}
/* 2文字に満たない入力の場合に結果を非表示にする */
div.search_result {
display: none;
}
/* ボタンを非表示にする */
.ajax-search-button{
/*display:none;*/
}
</style>
';
return $content;
}
add_shortcode( 'ajax-text-search', 'ha_ajax_text_search_form_shortcode' );
コードの解説
コードは以下の役割を持つユーザー定義関数で構成されています。
- キーボードの入力によって再検索を行うためのコールバック関数を出力するためのユーザー定義関数
- 検索を実行して、結果を表示させるためのユーザー定義関数
- ショートコードによって検索窓と結果を表示させるためのユーザー定義関数
動作の流れを文字に表すとこんな感じです。
- ショートコードでフォームを表示させる
- フォームに入力が始まったら入力都度コールバック関数を呼び出す
- コールバック関数に基づいて、サイト内のコンテンツから合致するものを抽出する
- 検索窓の下に結果を随時表示させる
次項以降で、各ユーザー定義関数の解説をしていきます。各ユーザー定義関数間で共通して使う文字列(関連のある指定文字)には同じ色のマークを入れています。ご自身でコード改変などをする際には、各コードの関連性を理解しておくとスムーズにできると思います。
キーボードの入力によって再検索を行うためのコールバック関数を出力するためのユーザー定義関数
完成コードの以下の部分を指します。
/*** [ajax-text-search] というショートコードが含まれていればjQueryをフッターへ出力 ***/
function ajax_text_search_script_in_footer() {
$admin_url = admin_url( 'admin-ajax.php', __FILE__ );
$content_before = '<script defer>function text_search_ajax_script_callback(){';
$content_after = '}</script>';
$script_a = "jQuery.ajax({url: '".$admin_url."',type: 'post',data: {action: 'ajax_text_search_action',keyword: jQuery('#keyword').val()},success: function(data) {jQuery('#datafetch').html( data );}});";
$script_b = "jQuery('input#keyword').keyup(function() {if (jQuery(this).val().length > 2) {jQuery('#datafetch').show();} else {jQuery('#datafetch').hide();}});";
global $post;
if( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'ajax-text-search' ) ) {
echo $content_before.$script_a.$script_b.$content_after;
}
}
add_action( 'wp_footer', 'ajax_text_search_script_in_footer' ,9999);
全体としては、jQueryを実行するための「function text_search_ajax_script_callback()」というコールバック関数を、「wp_footer」フックを使ってフロントエンドのフッターへ出力しています。
このコードに使っているjQueryはWordPress標準のライブラリを利用するので、より確実に実行するために優先度を「9999」にして、できるだけ最後に読み込まれるようにしています。
処理としては、「admin_url( ‘admin-ajax.php’, __FILE__ )」という、本来WordPressでは管理画面側で即時(Ajax)更新を行うためのファイルを呼び出した上で、「keyword」というIDを持つ場所に入力があったら、サイトデータとやり取りして「datafetch」というIDを持つ場所に結果を返すことをしています。
また、管理画面上でコードを見やすくするための改行などがそのままフッターへ出力されると、コードが不格好になるのと、本来管理画面側で出力すべき「Ajax」処理をフロントエンドで呼び出していることがセキュリティ的にどう?と感じたので、改行を削除して、HTML上にはすべてを1行で出力させるようにしています。
上記コードのjQueryの部分を改行して整形しなおしたのが以下のコードです。
コードは2つの役割があり、以下のコードは、先ほどのように「Ajax」を使って入力された値に対して結果を返す処理をしています。
jQuery.ajax({
url: '".$admin_url."',
type: 'post',
data: {
action: 'ajax_text_search_action',
keyword: jQuery('#keyword').val()
},
success: function(data) {
jQuery('#datafetch').html( data );
}
});
そして以下のコードで、入力窓に2文字以上入力があったら上のコードの処理をするようにして、いきなり大量の結果が表示されないようにしています(これは参考ページにあった対策です)。
jQuery('input#keyword').keyup(function() {
if (jQuery(this).val().length > 2) {
jQuery('#datafetch').show();
} else {
jQuery('#datafetch').hide();
}
});
最後に、コードを「$content」にまとめたものを以下の条件分岐を使って、ショートコードがある場合のみ出力するようにし、不要なコード出力の防止と、不要なコード出力をすることによるセキュリティ低下の軽減をしています。
global $post;
if( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'ajax-text-search' ) ) {
echo $content_before.$script_a.$script_b.$content_after;
}
検索を実行して、結果を表示させるためのユーザー定義関数
完成コードの以下の部分を指します。
/*** 検索結果の表示 ***/
function text_ajax_search_result(){
//件数の文字列
$result_before = __('Search Results:','');
$result_after = __('Items','');
$no_results = __('No Results Found','');
//入力値から全角スペースを除去
$formtxt = $_POST['keyword'];
$formtxt = str_replace(' ',' ',$formtxt);
//抽出条件の指定
$args = array(
'post_type' => array('post'), //投稿タイプ
'post_status' => 'publish',//公開済みのものに限定
'posts_per_page' => -1,//全項目を表示
's' => esc_attr( $formtxt ),//全角スペースを半角に変換した上でキーワードをセット
'order' => 'DESC',//降順に表示
'orderby' => 'relevance',//一致度で判断
'no_found_rows' => true,//ページャーを使う時はfalseに。
);
$the_query = new WP_Query($args);
$post_count = $the_query->post_count; //実際にそのページで取得した件数
if( $the_query->have_posts() ) {
echo '<p>'.$result_before.' '.$post_count.' '.$result_after.'</p><ul>';
while( $the_query->have_posts() ): $the_query->the_post(); ?>
<li><a href="<?php echo esc_url( post_permalink() ); ?>"><?php the_title();?></a></li>
<?php endwhile;
echo '</ul>';
wp_reset_postdata();
}else{
echo '<p>'.$no_results.'</p>';
}
die();
}
add_action('wp_ajax_ajax_text_search_action' , 'text_ajax_search_result');
add_action('wp_ajax_nopriv_ajax_text_search_action','text_ajax_search_result');
冒頭の「$result_before」「$result_after」はフォーム上に表示される文字列ですので、サイトに合わせて変更できます(テキストドメインを入れて翻訳ファイルを用意すれば多言語にもできます)。
以下の部分で、日本語のようなマルチバイト文字で検索する場合には、検索語句の間に全角スペースを入れることもあり、そのままでは一連の単語と認識されてしまうので、「str_replace」を使って、入力されている全角スペースを半角スペースに変換してからデータの問い合わせをするようにしています。
//入力値から全角スペースを除去
$formtxt = $_POST['keyword'];
$formtxt = str_replace(' ',' ',$formtxt);
あとは「WP_Query」を使って、以下のような条件で結果を取得して、ページのタイトルをリンク付きで表示させるようにしています。
パラメーター | 値 | 説明 |
---|---|---|
post_type | array(‘post’) | 投稿を指定しています。array(‘post’,’カスタム投稿タイプ’)と指定することで、複数の投稿タイプから抽出できます(※1) |
post_status | publish | 抽出したコンテンツの中で「公開」となっているもののみに限定します |
posts_per_page | -1 | ページ送りを使用せず、全件を表示します |
s | $formtxt | 入力されたテキスト中の全角スペースを半角スペースに変換したものを「$formtxt」に変換し、検索文字列として使います |
order | DESC | 抽出された値を降順(この場合は関連度順)で並び替えます |
orderby | relevance | 検索文字列との関連性の強さを基準に抽出します |
no_found_rows | true |
さらに「WP_Query」を一旦「$the_query」変数として格納し、以下のコードで抽出された投稿の件数を「$post_count」として格納し、検索結果に検索されたコンテンツの件数として表示させています。
※1 公開済のすべての投稿タイプを自動で含めるには
上記では、「array(‘post’,’カスタム投稿タイプ’)と指定することで、複数の投稿タイプから抽出できます」としましたが、プログラムを以下の赤アンダーライン部分のようにすることで、フロントエンドで「公開」することになっているすべての投稿タイプを自動で含めることもできます。
/*** 検索結果の表示 ***/
function text_ajax_search_result(){
//件数の文字列
$result_before = __('Search Results:','');
$result_after = __('Items','');
$no_results = __('No Results Found','');
//入力値から全角スペースを除去
$formtxt = $_POST['keyword'];
$formtxt = str_replace(' ',' ',$formtxt);
$custom_post_types_slug = get_post_types(array('public' => true));
//抽出条件の指定
$args = array(
'post_type' => $custom_post_types_slug, //投稿タイプ
'post_status' => 'publish',//公開済みのものに限定
'posts_per_page' => -1,//全項目を表示
's' => esc_attr( $formtxt ),//全角スペースを半角に変換した上でキーワードをセット
'order' => 'DESC',//降順に表示
'orderby' => 'relevance',//一致度で判断
'no_found_rows' => true,//ページャーを使う時はfalseに。
);
$the_query = new WP_Query($args);
$post_count = $the_query->post_count; //実際にそのページで取得した件数
if( $the_query->have_posts() ) {
echo '<p>'.$result_before.' '.$post_count.' '.$result_after.'</p><ul>';
while( $the_query->have_posts() ): $the_query->the_post(); ?>
<li><a href="<?php echo esc_url( post_permalink() ); ?>"><?php the_title();?></a></li>
<?php endwhile;
echo '</ul>';
wp_reset_postdata();
}else{
echo '<p>'.$no_results.'</p>';
}
die();
}
add_action('wp_ajax_ajax_text_search_action' , 'text_ajax_search_result');
add_action('wp_ajax_nopriv_ajax_text_search_action','text_ajax_search_result');
ショートコードによって検索窓と結果を表示させるためのユーザー定義関数
完成コードの以下の部分を指します。
/*** 検索窓と結果の出力を行うショートコード ***/
function ha_ajax_text_search_form_shortcode( ) {
//入力フォームの文字列
$placeholder = __('Please Input Keywords...','');
$submit_text = __('Submit','');
$wait_text = __('Please wait..','');
/*** 「Enter」で送信されないようにする対策 ***/
/* onsubmit="return false;"で誤送信されないようにする */
/* onclick="submit();"でボタンクリック時のみ送信するようにする */
/* 入力項目が1つなのでダミー項目を作って誤送信されないようにする */
$content= '
<div class="ajax-text-search-bar">
<form action="/" method="get" autocomplete="off" onsubmit="return false;">
<div class="ajax-text-search-bar-inner">
<input type="text" class="text-form" name="s" placeholder="'.$placeholder.'" id="keyword" class="input_search" onkeyup="text_search_ajax_script_callback()">
<input type="text" name="dummy" style="display:none;">
<input type="button" class="search-button" onclick="submit();" value="'.$submit_text.'" />
</div>
</form>
<div class="search_result" id="datafetch">
<ul>
<li>'.$wait_text.'</li>
</ul>
</div>
</div>
<style>
/* フォームのスタイル */
input.text-form ,input.search-button {
font-size: 1.2rem;
padding: 4px;
}
input.text-form{
width: 50%;
}
/* 2文字に満たない入力の場合に結果を非表示にする */
div.search_result {
display: none;
}
/* ボタンを非表示にする */
.ajax-search-button{
/*display:none;*/
}
</style>
';
return $content;
}
add_shortcode( 'ajax-text-search', 'ha_ajax_text_search_form_shortcode' );
「add_shortcode」フックを使用し、「ajax-text-search」というショートコードで、検索フォームの出力と、その下に表示される検索結果の一覧を出力ています。
また、以下のようにして入力時に「Enter」キーを押してしまっても何も起こらないようにしています。
この措置をしないと、「Enter」キーを入力した際にデータが送信され、通常の検索結果として検索結果ページへ遷移してしまいます
- formタグに「onsubmit=”return false;」要素を付与して誤送信を防止
- ボタンタグ(imputタグ)に「onclick=”submit();”」要素を追加して、誤送信を防止
- 入力項目が1つのフォームの場合、上記が効かない現象が発生するので、表示しないダミー項目を作る
最後にstyleタグで、基本的なフォームのスタイルを出力しています。
設置方法とコード解説は以上です。「Ajax」を使ったコンテンツ生成については私自身明るくないので、参考となるページを元に、自身でできる「こうなってたらいいな」というものを付加して、本ページのコードを作成しました。
最後に、このコードで大丈夫なの?ということについて触れていますので、気になる方は読んでみてくださいね。
後述 これでいいのか..
一応このページ冒頭でもデモとして機能させている通り、入力窓に何かを入力するとサイト内のコンテンツを検索して、結果をリアルタイムで表示できる機能は実装できた。
私の能力でなかなか実装できなかった機能が実装できて、参考とさせていただいた「WordPress Ajax Search without plugin [ Easy ]」には大変感謝しています。
ただ、キーボードから手が離れたことに対応して、コールバック関数に含まれたjQueryが発火して、WP_Queryで結果を表示させるという仕組み自体には問題ないと思われるものの、フッターにコールバック関数自体を出力してしまうのはどう?という疑問が残りました。
まあ、いろいろなサイトを見て、ajax処理のことに終始しているものが多い中、実際に動く形で解説されていたので、私が行った工夫を含めて紹介しましたが、果たしてこれでいいのか?という疑問は残る形なので、とりあえず動くものを!という方は実装されてもいいかも知れません。
コメントを残す