カスタムフィールドをプラグインなしで自作する(3)

カスタムフィールドをプラグインなしで自作する(3)
Image by Gerd Altmann from Pixabay

2025.02.18(更新日:2025.02.18)

カスタムフィールドの増減、並び替えが出来ると管理画面の可能性が広がる。特にタイトルフィールド、文章フィールドなど複数のフィールドが段落ごとに複製、並び替え可能になるとコンテンツを整理出来るのでクライアントに優しい入力方法を導入出来るのではと思う。

この記事は1年以上経過しています。内容的に古い場合があります。

フィールド複製、並び替えに必要なもの

基本的な流れは今まで行ったものと同じ。使うものはWordpress標準で導入されている「jQuery」を使い以下のようにゴニョゴニョする。

  1. jQueryで追加させるには「append」削除するには「remove」を使用。
  2. jQueryで並び替えには「sortable」を使用。
  3. 保存する際、複製するフィールドを配列にして保存する。

追加する分コード数は多くはなるが、整理してコード書いてあげれば以外と簡単に出来る。

フィールド複製、並び替えフィールドのコード例

「add_meta_box」を使い表示する投稿等を指定する

add_meta_boxを使用して、カスタムフィールドを作成。

// メタボックスを追加
function add_repeating_custom_field_metabox() {
	add_meta_box(
		'custom_paragraph_fields',
		'繰り返し・移動可能フィールド',
		'render_repeating_custom_fields',
		'post', // 投稿タイプ
		'normal',
		'default'
	);
}
add_action('add_meta_boxes', 'add_repeating_custom_field_metabox');

上記は投稿タイプのみでの設定だが、投稿スラッグで分岐したければ「global $post」を使い「$post->post_name」での分岐を入れる。

HTMLを処理

※途中からHTMLを書いているので先頭に「<?php」を入れてあります。要適宜処理。

<?php
function render_repeating_custom_fields($post) {
    wp_nonce_field('selection_paragraph_nonce', 'selection_paragraph_nonce_field');

    // 保存された値を取得
    $repeater_fields = get_post_meta($post->ID, 'custom_paragraph_fields', true);
?>
<div class="postbox">
    <div id="custom-repeater-lists-wrapper">
<?php if (!empty($repeater_fields)) : ?>
<?php foreach ($repeater_fields as $index => $field) : ?>
        <div class="repeater-field-group">
            <div class="feild-text">
                <label>タイトル:</label>
                <input type="text" name="custom_paragraph_fields[<?php echo $index; ?>][text]" value="<?php echo esc_attr($field['text']); ?>">
            </div>
            <div class="feild-textarea">
                <label>コンテンツ:</label>
                <textarea name="custom_paragraph_fields[<?php echo $index; ?>][textarea]"><?php echo esc_textarea($field['textarea']); ?></textarea>
            </div>
            <button type="button" class="remove-field button">削除</button>
        </div>
<?php endforeach; ?>
<?php else : ?>
        <div class="repeater-field-group">
            <div class="feild-text">
                <label>タイトル:</label>
                <input type="text" name="custom_paragraph_fields[0][text]" value="">
            </div>
            <div class="feild-textarea">
                <label>コンテンツ:</label>
                <textarea name="custom_paragraph_fields[0][textarea]"></textarea>
            </div>
            <button type="button" class="remove-field button">削除</button>
        </div>
<?php endif; ?>
    </div>
    <button type="button" id="add-list-repeater-field" class="button">フィールドを追加</button>
</div>
<?php
}

保存された値がある場合と初期段階のフィールドを記述。

JSファイルの読み込み

CSSを使用する際は「wp_enqueue_style」でまとめて設定。

function custom_repeater_fields_scripts($hook) {
    if ('post.php' === $hook || 'post-new.php' === $hook) {
        wp_enqueue_media(); // メディアライブラリ用のスクリプト
        wp_enqueue_script(
            'custom-repeater-fields',
            get_template_directory_uri() . '/custom-repeater-fields.js', //JSファイルの指定。ディレクトリは各自変更すること
            array('jquery'),
            null,
            true
        );
    }
}
add_action('admin_enqueue_scripts', 'custom_repeater_fields_scripts');

JSファイル作成

「custom-repeater-fields.js」としてアップロード。追加フィールドはHTML内の初期値のフィールドをクローンする手もありかも。

jQuery(document).ready(function($) {
    var fieldIndex = ;

    // フィールドを追加
    $('#add-list-repeater-field').on('click', function() {
        var newField = `
<div class="repeater-field-group"> <div class="feild-text"> <label>タイトル:</label> <input type="text" name="custom_paragraph_fields[` + fieldIndex + `][text]"> </div>
<div class="feild-textarea"> <label>コンテンツ:</label> <textarea name="custom_paragraph_fields[` + fieldIndex + `][textarea]"></textarea> </div> <button type="button" class="remove-field button">削除</button> </div>`; $('#custom-repeater-lists-wrapper').append(newField); fieldIndex++; }); // フィールドを削除 $('#custom-repeater-lists-wrapper').on('click', '.remove-field', function(e) { e.preventDefault(); $(this).closest('.repeater-field-group').remove(); }); // ドラッグ&ドロップ(並び替え) $('#custom-repeater-lists-wrapper').sortable({ placeholder: 'sortable-placeholder', update: function() { $('.repeater-field-group').each(function(index) { $(this).find('input, textarea').each(function() { var name = $(this).attr('name').replace(/\[\d+\]/, `[${index}]`); $(this).attr('name', name); }); }); } }); });

メタボックスの値を保存処理するコード

それぞれのフィールドを配列でまとめ、指定のIDで保存する。

function save_selection_paragraph_fields($post_id) {

    // 投稿タイプチェック。以下は必要があれば
    global $post;
    // 投稿スラッグで分けたい場合は$post->post_nameで値参照
    if( $post->post_type !== 'post' ) return;

    // セキュリティチェック
    if (!isset($_POST['selection_paragraph_nonce_field']) || !wp_verify_nonce($_POST['selection_paragraph_nonce_field'], 'selection_paragraph_nonce')) {
        return;
    }

    // 自動保存時は処理をスキップ
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;

    // リビジョンは処理をスキップ(必要であれば)
    if (wp_is_post_revision($post_id)) return;

    $metabox_data = array();

    // 入力されたフィールドのデータを保存
    if (isset($_POST['custom_paragraph_fields']) && is_array($_POST['custom_paragraph_fields'])) {
        $sanitized_fields = array(); // サニタイズ済みデータ格納用
	
        foreach ($_POST['custom_paragraph_fields'] as $field) {
	
            // フィールド構造の検証
            if (!isset($field['text']) || !isset($field['textarea'])) {
                continue; // 必須のキーがない場合は無視
            }
	
            // テキストフィールド
            $text = $field['text'];
            // テキストエリアフィールド
            $textarea = $field['textarea'];
	
            // データを格納
            $sanitized_fields[] = array(
                'text'     => $text,
                'textarea' => $textarea,
            );
        }

        $metabox_data[ 'repeater' ] = $sanitized_fields;

        delete_post_meta($post_id, 'custom_paragraph_fields');
        update_post_meta($post_id, 'custom_paragraph_fields', $metabox_data, 20);
    } else {
        delete_post_meta($post_id, 'custom_paragraph_fields');
    }
}
add_action('save_post', 'save_selection_paragraph_fields');

自作カスタムフィールドまとめ

今回CSSの指定は省いているが、適当にレイアウトを整える必要はあるだろう。
どちらかと言うとクラシックエディタでの活用シーンが多いカスタムフィールド。コードがわからないクライアントには「決まった場所に入力」の方法が一番無難なんだけどね。