WordPressのAdvancedCustomFieldsに「JavaScriptで」デフォルト値を設定する方法

# はじめに

以前、[PHPでWordPressのデフォルト値を設定する方法](https://qiita.com/qwe001/items/2c05a2be5434eb9a092d)を説明しました。
ですが、私の案件は、ページ読み込み時にデフォルト値を設定するのではなく、
特定のカスタムフィールドの値変更をトリガーにして、デフォルト値を設定するというものです。

$\tiny{…いる？}$

そのため、JavaScriptでinput changeイベントを取らなくてはなりません。
でも、ACFのデフォルト値を変更する方法は、PHPでのやり方ならネットにいくらでもありますが、
JavaScriptで書き換える方法が全然載ってないので、備忘録がてら書くことにしました。

# 仕様

* ページ読み込み時は何もしない
* 表示開始日付(open_date)の値が更新された時、表示終了日付(close_date)に一カ月後の値をセットする
* 表示終了日付(close_date)に既に値がある場合、何もしない

# 下準備

まずは、管理画面でだけ使うJavaScriptファイルを用意します。
touchコマンドで生成しておきます

```bash
cd /path/to/wordpress/wp-content/themes/your_theme/
mkdir admin
cd $_
mkdir js
cd $_
touch my_admin_script.js
```

これで `/path/to/wordpress/wp-content/themes/your_theme/admin/js/my_admin_script.js` が生成されました。

次に、管理画面の記事編集画面でだけ、
任意のJavaScriptファイルを呼び出すために以下の記述をします

左サイドバー > 外観 > テーマエディター

右サイドバー > テーマファイル > テーマのための関数 (functions.php)
/path/to/wordpress/wp-content/themes/your_theme/functions.php

```php
/**
 * 指定カテゴリの記事登録OR編集画面かどうかを判定する
 */
function isEventPostPage()
{
    $currentScreen = get_current_screen();

    $isPostPage = ($currentScreen->base == 'post'); // 管理画面 新規登録 または 編集ページか？
    $isValidPostType = ($currentScreen->post_type == 'events'); // 指定カテゴリの登録画面か？

    $isValid = ($isValidPostType && $isPostPage);

    return $isValid;
}

/**
 * WP管理画面 イベントページのみカスタムJSファイルを読み込ませる
 * JavaScriptでデフォルト値を設定するため
 */
function add_js_admin_scripts_post_events()
{
    if(! isEventPostPage()){ // 条件に一致しないページでは読み込ませない
        return false;
    }

    // 条件に一致する場合のみ対象JSファイルを読み込む(要JSファイル作成)
    $file = get_template_directory_uri() . '/admin/js/my_admin_script.js';
    wp_enqueue_script('js-admin-post-events', $file, array('jquery'));
}

add_action('admin_enqueue_scripts', 'add_js_admin_scripts_post_events');
```

上記の記述を追記したら、ファイルを更新を押します。

これで、WordPress管理画面でイベントカテゴリの記事投稿、記事編集画面でだけ
JavaScriptファイルが読み込まれるようになりました。

本当にそのページだけで読み込まれているか、以下の記述をして確認してみましょう

左サイドバー > 外観 > テーマエディター

右サイドバー > テーマファイル > admin > js > my_admin_script.js
/path/to/wordpress/wp-content/themes/your_theme/admin/js/my_admin_script.js

```javascript
jQuery(function($){
  console.log("admin");
});
```

上記の記述を追記したら、ファイルを更新を押します。

WordPress管理画面でイベントカテゴリの記事投稿、記事編集画面で
Chromeの開発者ツールを開き、コンソール画面に「admin」の文字が出ること、
また、それ以外のページ(フロント含む)ではコンソール画面に文字がでなければOKです。

これで下準備が完了しました。
次に、JavaScriptでデフォルト値を設定する方法をご紹介します。

# 実装

左サイドバー > 外観 > テーマエディター

右サイドバー > テーマファイル > admin > js > my_admin_script.js
/path/to/wordpress/wp-content/themes/your_theme/admin/js/my_admin_script.js

```javascript
var ACF_EL_DATE_SOURCE = 'open_date';
var ACF_EL_DATE_TARGET = 'close_date';

/**
 * 指定月をDateインスタンスに加算して返す
 *
 * @param  Date dt Dateインスタンス
 * @param  Integer monthVal 加算する月数 1 -> 1ヶ月加算
 * @return Date nヶ月を加算したDateインスタンス
 */
function addMonth(dt, monthVal)
{
  var newDate = new Date(dt.setMonth(dt.getMonth() + monthVal));
  return newDate;
}

/**
 * ACFインスタンスをカスタムフィールド名から取得する
 *
 * @param  String name カスタムフィールド名 "close_date" など
 * @return Object instance ACFインスタンス
 */
function getAcfInstanceByName(name)
{
  var instance = acf.getFields({name: name}).shift();
  return instance;
}

/**
 * ACFカスタムフィールドの値をカスタムフィールド名から取得する
 *
 * @param  String name カスタムフィールド名 "close_date" など
 * @return mixed val カスタムフィールドの値 "2022-03-16" など
 */
function getAcfValByName(name)
{
  var val = (val = acf.getFields({name: name}).shift()) ? val.val() : '';
  return val;
}

/**
 * ACFカスタムフィールドのデフォルト値(input hiddenの値=POSTされる値)を設定する
 *
 * @param  String name カスタムフィールド名 "close_date" など
 * @param  mixed val カスタムフィールドの値 "2022-03-16" など
 * @return void
 */
function setAcfValByName(name, val)
{
  acf.set(name, val);
}

/**
 * 日付文字列からDateインスタンスを生成する
 *
 * @param  String dateStr 日付文字列 YYYY-MM-DD 形式 "2022-03-16" など
 * @return Date dt Dateインスタンス
 */
function createDateFromYmd(dateStr)
{
  var date = Date.parse(dateStr);
  var dt = new Date(date);
  return dt;
}

/**
 * Dateインスタンスから日付文字列を生成する
 *
 * @param  Date dt Dateインスタンス
 * @return String dateStr 日付文字列 YYYY-MM-DD 形式 "2022-03-16" など
 */
function formatDateToYmd(dt)
{
  var y = dt.getFullYear();
  var m = ('00' + (dt.getMonth()+1)).slice(-2);
  var d = ('00' + dt.getDate()).slice(-2);
  var dateStr = (y + '-' + m + '-' + d);
  return dateStr;
}

/**
 * ACFカスタムフィールドの親要素(div)にクラスを付与する
 * デイトピッカーの表示値を変えるために使用
 *
 * @param  Object instace ACFインスタンス
 * @param  String className クラス名
 * @return void
 */
function addClassToAcfFieldParent(instance, className)
{
  instance.$el.addClass(className);
}

/**
 * ACFカスタムフィールドのデフォルト値(デイトピッカーの表示値)を設定する
 *
 * @param  String name カスタムフィールド名 "close_date" など
 * @param  String dateStr 日付文字列 YYYY-MM-DD 形式 "2022-03-16" など
 * @return void
 */
function setAcfDatePickerValByName(name, dateStr)
{
  var instance = getAcfInstanceByName(name);
  var className = 'tmp_acf_parent_' + name; // e.g. "tmp_acf_parent_close_date"
  addClassToAcfFieldParent(instance, className);

  var target = jQuery('.' + className).find('input[type=text]');
  jQuery(target).addClass('tmp_acf_target_' + name); // e.g. "tmp_acf_target_close_date"
  jQuery(target).datepicker('setDate', dateStr); // set default value to datepicker
}

/**
 * デイトピッカーのデフォルト値を変更する
 *
 * @return boolean
 */
function updateDispCloseDate()
{
  var source = ACF_EL_DATE_SOURCE;
  var target = ACF_EL_DATE_TARGET;

  var srcDate = getAcfValByName(source);
  var tgtDate = getAcfValByName(target);

  if(tgtDate){ // if target value is already set, then cancel
    return false;
  }

  if(! srcDate){ // if source value is empty, then cancel
    return false;
  }

  srcDate = createDateFromYmd(srcDate);

  // add one month to date
  var monthVal = 1;
  var newDate = addMonth(srcDate, monthVal);
  var dateStr = formatDateToYmd(newDate);

  // update hidden value
  setAcfValByName(target, dateStr);

  // Check that the value change worked correctly
  //tgtDate = getAcfValByName(target);
  //console.log(tgtDate);

  // update visible date picker value
  setAcfDatePickerValByName(target, dateStr);

  return true;
}

// run when page loaded
//window.onload = function(){
//  updateDispCloseDate();
//};

// run when source input changed
jQuery(function($){
  var source = ACF_EL_DATE_SOURCE;
  var srcInstance = getAcfInstanceByName(source);

  srcInstance.on('change', 'input[type="text"]', function(e){
    var srcVal = $(this).val();
    //console.log(srcVal);

    updateDispCloseDate();
  });
});
```

上記の記述を丸ごと上書きしたら、ファイルを更新を押します。

イベント記事の新規登録画面に行き、「表示開始日付」を変更して動作確認します。

# 動作確認

Gotcha!!

![2022-03-16-11-23-43.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130352/d04197c2-6aec-7de7-28c6-bee6916e9d7e.gif)

# 注意1 ACFのバージョンは5.7以上にアップデートすること

ACF JavaScript APIを使用するには、ACFのバージョンが「5.7」以上である必要があります。
筆者の環境はもともと 4.4.12 でしたが、 2022年3月時点の最新である 5.12 にアップデートしました。

JavaScript APIの書き方はjQueryに似ていますが、
微妙に異なるので公式ドキュメントを読んでください

https://www.advancedcustomfields.com/resources/javascript-api/

公式ドキュメントの一番上に
「ACF JavaScript APIはバージョン5.7以上で使用できます」とか書いといて欲しかった…

# 注意2 ACFをバージョンアップすると ACF Date and Time Pickerの表示がおかしくなる

「Advanced Custom Fields: Date and Time Picker」を使用していると、
プラグインのアップデートをしたら表示がおかしくなります。
時刻の表示がUnixタイムになるのです。 「2022-03-16 11:00」が「2022-03-16 1647396000」になるみたいな。

そうなったときは、以下の手順で直せます

左サイドバー > カスタムフィールド > フィールドグループ > {任意のフィールドグループ名}

タイプがDate Time Pickerになっているものを、以下のように修正

フィールドタイプ
Date Time Picker -> Time Picker

表示フォーマット
Custom H:i

返り値のフォーマット
Custom H:i

# 注意3 jQueryを読み込ませる時は「$」ではなく「jQuery」インスタンスを使うこと

これはACF関係なくWordPressのお約束ですが…

```
$(function(){ doSomething(); }); // ダメ

jQuery(function($){ doSomething(); }); // OK
```

# 注意4 Wordpressで無条件のtouchコマンドは書かないこと

絶対に、以下のようなコマンドでファイルを生成してはいけません
WEBサーバーが壊れます

```
// このコマンドを使わないでください！サーバーが壊れます！
// Do NOT use this!!! Your Server will be broken.
//add_action('after_setup_theme', function() {
	//$file = get_template_directory_uri() . '/admin/js/my_admin_script.js';
	//touch($file);
//});
```

※本文を読まずにネットからソースコードをコピペしてやらかす人(私)がいると思うので、全文コメントアウトしておきます

これを書くと、5秒毎?にadmin-ajax.phpが動いてファイル作成を行おうとし、
CPU使用率がグングンあがってメモリ不足になり、ページの閲覧も、
FTP接続も、ファイルの操作も「ほぼ」受け付けない状態になります。
vimコマンドでfunction.phpを直接編集しようにも、
「cannot allocate memory」エラーが出てファイルが表示されません。

万が一、上記コマンドを使ってしまった場合、以下の手順で解消できます。

① ブラウザで管理画面を開いていれば全員閉じる

上記アクションはHTTPリクエストがある度に発火します。
つまり、誰かが管理画面ページを開き続けている限り、
ずっとメモリ不足の状況が続きます。
以下の作業をするにも重たくてどうしようもなくなるので、まずはブラウザを閉じましょう。

② WEBサーバーを停止する

```
sudo service httpd stop
```

③ function.phpから問題のある記述を削除する

```
cd /path/to/wordpress/wp-content/themes/your_theme/
sudo vim function.php
```

上のtouchコマンドに関する記述を削除して、上書き保存します

④ WEBサーバーを再開する

```
sudo service httpd start
```

⑤ 動作確認する

# 参考URL

https://www.advancedcustomfields.com/resources/javascript-api/