WordPressとajax (2)

WordPressとajax (2)
Werner MoserによるPixabayからの画像

2021.10.19(更新日:2025.02.16)

前回チェックボックスの値をajaxを使って単一の値を取得した。
今回は配列で送り、配列で返すのを試す。ここで重要なのが「JSON」。
意外と面倒な部分や知識不足もあり時間がかかったが、オレオレ式にまとめておく。

今回のサンプル

前回は最上位のタームの数の合計数を出したが、今回は「最上位とその子」を使用する。
なのでカテゴリーでいくつか親タームを作成後、それぞれのタームに子タームをぶら下げる感じになる。
子タームごとの項目をチェックボックスをで選択出来るようにして、それぞれの親の合計数を出すと言うもの。

手短に出来るサンプルとして作成したものなので、前回同様「ajaxでなくてよくない?」と言わないこと。

HTML形成

<?php
/* テンプレート内にでも */
$taxonomy = 'category';
$args = array(
	'orderby' => 'slug',
	'order' => 'ASC',
	'hide_empty' => 0,
	'parent' => 0 // これで親のみ取得
);
$summarize = array();
$terms = get_terms( $taxonomy, $args );
if( !empty( $terms ) ){
	foreach( $terms as $value ){
		$summarize[ $value->slug ][ 'name' ] = $value->name;
		$summarize[ $value->slug ][ 'term_id' ] = $value->term_id;
		// 子ターム取得
		$term_childs = get_term_children( $value->term_id, $taxonomy );
		if( empty( $term_childs ) ){
			$summarize[ $value->slug ][ 'childs' ] = $value->count;
		} else {
			// 子タームがあったら情報を取得し配列へ
			$childs = array();
			foreach( $term_childs as $val ){
				$term = get_term( $val, $taxonomy );
				$childs[ $term->slug ][ 'name' ] = $term->name;
				$childs[ $term->slug ][ 'term_id' ] = $term->term_id;
				$childs[ $term->slug ][ 'count' ] = $term->count;
			}
			$summarize[ $value->slug ][ 'childs' ] = $childs;
		}
	}
}
$lists = '';
if( !empty( $summarize ) ){
	$lists .= '<dl>';
	foreach( $summarize as $keys=>$value ){
		$lists .= '<dt>' . esc_html( $value[ 'name' ] ) . '</dt>' . "\n";
		if( $value[ 'childs' ] > 0 ){
			foreach( $value[ 'childs' ] as $key=>$val ){
				$lists .= '<dd><label>';
				$lists .= '<input type="checkbox" name="' . $keys . '[]" value="' . esc_attr( $val[ 'count' ] ) . '">';
				$lists .= esc_html( $val[ 'name' ] );
				$lists .= '</label></dd>' . "\n";
			}
		}
	}
	$lists .= '</dl>';
}
?>
<?php if( !empty( $lists ) ) : ?>
<form method="get" id="searchform">
	<div>
<?php echo $lists; ?>
</div>
<?php if( !empty( $summarize ) ) : ?>
	<div>
		<ul>
<?php foreach( $summarize as $key=>$value ) : ?>
		<li><?php echo esc_html( $value[ 'name' ] ); ?> : <span id="<?php echo esc_attr( $key ); ?>-count" class="counter">0</span>件</li>
<?php endforeach; ?>
		</ul>
	</div>
<?php endif; ?>
		<div><input type="submit" value="検索"></div>
	</form>
<?php endif; ?>

親タームはdtへ、子タームたちはチェックボックスにてdd内に表示。管理画面でタームを増やしてもオートで形成するようにした。取得した件数は、ul内のliにあるそれぞれ親ターム表示部分へ書き込まれる。
なお孫に関しては関知していない。また各親タームには必ず子タームがあるものとする。

アクションフックで下準備

前回とほぼ同じ。各名称をちょっと変えただけ。

/* functionsなど */
add_action( 'wp_enqueue_scripts', 'my2_enqueue_scripts' );
function my2_enqueue_scripts() {
	$handle = 'sample2_handle'; // 1. JSファイルにhandle名を付ける
	$action = 'sample2-ajax-action'; // 2. Ajaxのアクション名
	$object_name = 'sample2Ajax'; // 3. JSで扱う変数名
	// 4. ajaxの指示を書いたjsファイルの読み込み。外部ファイル
	wp_enqueue_script( $handle, get_template_directory_uri().'/js/script-ajax2.js', array( 'jquery' ) );
	// 5. JS側へAjaxで使う各パラメータを渡す
	wp_localize_script( 
		$handle, 
		$object_name, 
		array(
			'url' => admin_url( 'admin-ajax.php' ),
			'action' => $action,
			'nonce' => wp_create_nonce( $action ),
		) 
	);
	wp_enqueue_script( $handle );
}

JSファイルの制作

/* script-ajax2.js */
(function($) {
	$(function (){

		$("#searchform").change(function() {
			event.preventDefault();
			seachMatchCount();
		});

		function seachMatchCount(){
			//チェックボックスの値を配列に保存
			var term_id = {}; // ※1
			$("#searchform input[type='checkbox']").each(function(){
				var name = $( this ).attr( 'name' );
				name = name.replace( '[]', '' );
				if( !term_id[ name ] ) term_id[ name ] = [];
				if( $( this ).prop('checked') == true ){
					var value = $( this ).val();
					term_id[ name ].push( value );
				}
			});

			$.ajax({
				type: 'POST',
				url: sample2Ajax.url,
				dataType: 'json',
				data: {
					action : sample2Ajax.action,
					nonce: sample2Ajax.nonce,
					terms : JSON.stringify(term_id) // ※2
				},
				cache: false
			}).done( function( data ) {
				// 成功時。出力する部分(処理)
				search_result_count( data );
			}).fail( function( XMLHttpRequest, textStatus, error ){
				// 通信失敗時のコールバック処理
				console.log( 'XMLHttpRequest : ' + XMLHttpRequest.status );
				console.log( 'textStatus : ' + textStatus );
				console.log( 'errorThrown : ' + error.message );
			}).always(function (data) {
				// 常に実行する処理
			});
		}

		function search_result_count( data ){
			if( data.length === 0 ) return;
			var ids = [];
			Object.keys(data).forEach( key => {
				var id = key + '-count';
				$( '#' + id ).html(data[key]);
				ids.push(id);
			});
		}
	});
})(jQuery);

最初につまずいたのが「※1」。JSの連想配列は「{}」で囲むのをスッカリ忘れ「[]」にしてた。
最大のポイントは「※2」。そのまま値を送ると、HTTPステータスは200を返すけれど「textStatus : parsererror」となる。
ちなみに前回は単なる「数字」の配列だったが、今回複数の親タームをキーとした連想配列が送信される。
また選択されたものがない場合でも、合計数を「0」表示にするので空の配列を返すようにしてある。

/* 送信例 */
terms = {
	// 親ターム名 : [ チェックボックス(子ターム)値, チェックボックス(子ターム)値, … ],
	term_name : [ chechbox.value, chechbox.value, … ],
	term_name : [ chechbox.value, chechbox.value, … ],
	︙
}

JSON.stringifyで変換で解決

単純に考えて「そんなもんわからん」と返してきてる訳だから、JSONさんのお口に合うように「JSON.stringify」で変換してあげる。入り組んだものや、単なる配列でも日本語などある場合は使うべし。

アクションフックで処理コード

処理部分だけ違うだけで、基本は前回と同じ。

/* functionsとかに */
add_action( 'wp_ajax_sample2-ajax-action', 'get_match2_count' );
add_action( 'wp_ajax_nopriv_sample2-ajax-action', 'get_match2_count' );

function get_match2_count(){
	$terms = array();
	if( $_SERVER['REQUEST_METHOD'] === 'POST' && check_ajax_referer( 'sample2-ajax-action', 'nonce' ) ) {
		$res = array();
		$terms = filter_input( INPUT_POST, 'terms' );
		if( isset( $terms ) ){
			$terms = stripslashes( $terms ); // ※1
			$array = json_decode( $terms, true ); // ※2
			foreach( $array as $key=>$value ){
				$count = 0;
				foreach( $value as $val ){
					$count = $count + (int)$val;
				}
				$res[ $key ] = $count;
			}
		}
	}
	header("Content-type: application/json; charset=UTF-8");
	echo json_encode( $res, JSON_UNESCAPED_UNICODE ); // ※3
	wp_die();
}

ここでの案の定つまずきポイントが。
まともな値が来てないようなので調べてみるとバックスラッシュでダブルクォートなどがエスケープされていた。
なので「※1」でstripslashesを取り除く。
これでいけるか?と思いきや、戻り値がNULL。そうです「まだJSONさん」でした。
なので「※2」でjson_decodeしPHPの連想配列にする。そのためにも第2引数に「true」をセット。

値に日本語が含まれる場合

json_encodeで日本語はUnicode文字に変換されてしまう。値を表示する場合など困りもの。
その場合「※3」で引数に「JSON_UNESCAPED_UNICODE 」といれてあげると、変換されずにJSON化してくれる。
今回は必要ないけど、大抵忘れるのでおまけでいれてみた。

まとめ

久々にajaxを使う機会があり、ズッポリ忘れたことを見直す上で「サンプルを交えて」試してみた。
整理することの大事さを噛み締めつつの備忘録でした。