かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Ajaxを使ってWordPressからコンテンツを取得したい。

WordPress製のサイトに独自のコンテンツなどを取得できるAjaxを作成する方法のメモ

  1. 呼び出すアクション名を決める
  2. Ajax送信先をjsから使えるように出力する
  3. Ajaxで呼び出される関数を作成する (PHP)
  4. フロントからAjaxでの呼出しを作成する (javascript)

1. 呼び出すアクション名を決める

アクション名はPHPの関数名やnoce、jsのAjax内などで何度も使うので、どんな仕様にするか決めて仕様に合ったアクション名を先に決めておくのが良いと思います。

今回は my-ajax-action というアクション名にします。

2. Ajax送信先のパスなどの出力

WordPressAjaxのリクエス送信先 /wp-admin/admin-ajax.php のパスやnoneなどの情報をjavascriptグローバル変数として出力する

<?php // function.php
function my_enqueue_scripts() {
  $handle = 'my-script';
  // Ajaxの呼出しが書かれているスクリプトのパス
  $jsFile = 'path/to/myscript.js';
  
  wp_register_script($handle, $jsFile, ['jquery']);
  
  // 1.で決めたアクション名
  $acrion = 'my-ajax-action';
  // 配列をJSのオブジェクトに変換したscriptを出力する
  wp_localize_script($handle, 'MY_AJAX', [
    'api'    => admin_url( 'admin-ajax.php' ),
    'action' => $acrion,
    'nonce'  => wp_create_nonce( $acrion ),
  ]);
  
  wp_enqueue_script($handle);
}
add_action( 'wp_enqueue_scripts', 'my_enqueue_scripts' );

$handleで指定された myscript.js の出力タグの前に次のようなスクリプトが出力されます。

<script type='text/javascript'>
/* <![CDATA[ */
var MY_AJAX = {
  "api": "http:\/\/example.com\/wordpress\/wp-admin\/admin-ajax.php",
  "action": "my-ajax-action",
  "nonce": "生成されたNONCE値"
}
/* ]]> */
</script>

※ 実際には1行で出力されます。グローバル変数になっちゃうので、ちょっとイケてない感あります…

複数のアクションを作成する場合は、アクションとそれに対応したnonceが取得しやすいように配列をネストするなどして工夫してください。

3. 呼び出される関数の作成 (PHP)

3.1 Ajaxから呼び出される関数を定義する

add_action アクションフックで、Ajaxから呼び出される関数を登録する。

<?php // function.php
function my_ajax_event() {
}
add_action( 'wp_ajax_my-ajax-action', 'my_ajax_event' );
add_action( 'wp_ajax_nopriv_my-ajax-action', 'my_ajax_event' );
  • wp_ajax_{action名} … ログインユーザーの時、呼び出される
  • wp_ajax_nopriv_{action名} … 非ログインユーザーの時、呼び出される

フロントから呼び出される場合は、両方のアクションフックを設定する必要がある。
管理画面で使うだけなら、常にログインユーザーなので wp_ajax_{action名} の方だけでOK。

3.2 Ajaxで呼び出される関数の処理を作成する

<?php // function.php
function my_ajax_event() {
  // wp_create_nonce の引数に渡したものと同じ アクション名
  $action = 'my-ajax-action';
  
  // nonceのチェック
  if( check_ajax_referer($action, 'nonce', false) ) {
    $data = [];

    // Ajax(post)のdataから渡される値を使用する場合は $_POST から取得する (適時エスケープ)
    $id = intval( $_POST['post_id'] );
    $name = esc_html( $_POST['name'] );
    
    /* wordpressから色々取得したり、Ajaxで返すデータ作成する */
    
    // 配列をjson形式にエンコード
    $data = json_encode($data);
  } else {
    // エラー
    status_header( '403' );
    $data = 'Forbidden';
  }

  // Ajaxに返す
  header( 'Content-Type: application/json; charset=UTF-8' );
  echo $data;

  die();
}
add_action( 'wp_ajax_my-ajax-action', 'my_ajax_event' );
add_action( 'wp_ajax_nopriv_my-ajax-action', 'my_ajax_event' );

nonceのチェック

check_ajax_referer( $action, $query_arg, $die )
  • $action (string)
    nonceのアクション名
    nonce作成時の wp_create_nonce() に渡したアクション名と同じ文字列を指定する
  • $query_arg (string)
    $_REQUESTで渡される配列の nonce が格納されているキーを指定
    上の例の場合は data: { nonce: 値 } としてAjaxから送ればOK
  • $die (boolean) default: true
    trueの時 nonce が無効なら die() する
    falseの時 nonce のチェック結果を true / false で返す

Ajax(post)のdataで渡される値は$_POSTで取得することができます。
nonceのチェック関数は内部では$_REQUESTから取得している様なので、$_REQUESTの方がget/postどちらでも取得できるで良いのかもしれません。

Ajaxで呼び出される関数の最後に die() がないと返されるデータの最後に0が追加されてしまうので、忘れないようにdieしておく必要があります。

4. Javascriptの作成 (Ajax呼出し)

後はフロントのjavascriptからAjaxで呼び出すだけです。
URLやアクション名は 2. で作成したグローバル変数から使用します。

// global MY_AJAX
"use strict";
$(function() {
  $.ajax({
    url: MY_AJAX.api,
    type: 'post',
    data: {
      // 呼び出すアクション名
      action: MY_AJAX.action,
      // アクションに対応するnonce
      nonce: MY_AJAX.nonce,
      // ▼ その他 渡したいデータがあれば適時 ▼
      post_id: 12345,
      name: 'name'
    }
  })
  .done(function( res ) {
    console.log( res );
  })
  .fail(function( jqXHR, textStatus, errorThrown ) {
    console.log( jqXHR, textStatus, errorThrown, arguments);
  });
});

 
これで、Ajaxを使ってWordPressから独自のコンテンツとか値を取得することができるようになりました。
Ajaxを使うために出力するjavascriptの変数がglobal変数になってしまうのが、やっぱイケてない感じなので、どうにか出来ないものかと思っています。。。


[参考]

世界一わかりやすいWordPress 導入とサイト制作の教科書 (世界一わかりやすい教科書)

世界一わかりやすいWordPress 導入とサイト制作の教科書 (世界一わかりやすい教科書)

JS 配列の重複を取り除きたい

配列の重複した値を削除したい時のメモ。

filterでループさせindexOfで値の重複をチェックするパターン

array.indefOf(value)value自身のインデックスと異なる場合は既に配列内に同じ値があるという判定

var arr = [0, 1, 2, 3, 4.1, true, false, 1, "2", "", 4.2, null, undefined, NaN, false];

function removeDuplicateValue(arr) {
  return arr.filter((val, i, self) => {
    return self.indexOf(val) === i;
  });
}

console.log( removeDuplicateValue1(arr) );
// => [ 0, 1, 2, 3, 4.1, true, false, '2', '', 4.2, null, undefined ]

NaNが消えてしまう...

var a = NaN
console.log( a === a ); // false

NaNは自身を含めどの値であってもイコールにならないっぽいので、array.indexOf(NaN) は常に-1となり上記の方法では消えてしまう様です。
NaNを残すようにするには最初に出てきたNaNの時だけfilterのコールバック関数内でtrueを返すようにすればOKでした。(しかしNaN残して使いたい場合ってあるのかな...

var arr = [0, 1, 2, 3, 4.1, true, false, 1, "2", "", 4.2, null, undefined, NaN, false, NaN];

function removeDuplicateValue(arr) {
  let hasNaN = false;
  return arr.filter((val, i, self) => {
    if(isNaN(val) && !hasNaN ) {
      hasNaN = true;
      return true;
    }
    return self.indexOf(val) === i;
  });
}

console.log( removeDuplicateValue(arr) );
// => [ 0, 1, 2, 3, 4.1, true, false, '2', '', 4.2, null, undefined, NaN ]

Set (ES6) を使うパターン

Set
Set オブジェクトにより、primitive valuesでもオブジェクト参照でも、どんな型でも一意の値を格納します。
Set オブジェクトは値のコレクションです。挿入順に要素を反復することができます。Set内の値は 1度だけ発生します。その値はSetコレクション内で一意です。
[出典] Set - JavaScript | MDN

var arr = [0, 1, 2, 3, 4.1, true, false, 1, "2", "", 4.2, null, undefined, NaN, false, NaN];

var a1 = Array.from( new Set(arr) );
console.log(a1);
// => [ 0, 1, 2, 3, 4.1, true, false, '2', '', 4.2, null, undefined, NaN ]

var a2 = [...new Set(arr)];
console.log(a2);
// => [ 0, 1, 2, 3, 4.1, true, false, '2', '', 4.2, null, undefined, NaN ]

Array.from()
Array.from() メソッドは、配列型 (array-like) オブジェクトや反復可能 (iterable) オブジェクトから新しい Array インスタンスを生成します。
[出典] Array.from() - JavaScript | MDN

new Set()で一意の値の反復可能オブジェクトにしてArray.from()に渡して配列化。

スプレッド演算子 ...
スプレッド演算子 は、複数の引数 (関数呼び出しのため) または複数の要素 (配列リテラルのため)、あるいは複数の値 (分割代入のため) が置かれるところで式が展開されます。
[出典] スプレッド構文 - JavaScript | MDN

配列や反復可能オブジェクトを値で分割展開できる演算子のようです。(スプレッド演算子って名前初めて知りました)
配列の初期化時に反復可能オブジェクトを展開して値として渡して配列化しているようです。
EX

var arr1 = [1,2,3];
var arr2 = [4,5,6];
arr1.push(...arr2); 
arr1; //=> [1, 2, 3, 4, 5, 6];

var set = new Set();
set.add(1);
set.add(2);
set.add(3);
console.log(set); // => Set { 1, 2, 3 }
console.log( [...set] ); // => [ 1, 2, 3 ]

Set()を使えば簡単に重複を削除できるのですが、Array.from...含めてまだサポートしていないブラウザも有るようなのでWEBで本格的に使えるようになるのは使えるようになるのはもう少し先なのかな。と思いました。


[参考]

プログラマ脳を鍛える数学パズル シンプルで高速なコードが書けるようになる70問

プログラマ脳を鍛える数学パズル シンプルで高速なコードが書けるようになる70問

JS 2つの配列の差分を取得したい

  1. 値の差分をチェック
  2. 値とインデックスでチェック

1. 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[8, 2, 8, 4, 7, 9, 11, 8, 10]

2. 
[0, 1, 2, 3, 4, 5, 6]
['', 1, NaN, "3", null, 5, undefined]

3. 
[]
["いちご", "あおい", "らん"]

4. 
[1, "サーバル", 3, "かばん"]
[1, 7, "ボス"]

5. 
["あぶくま", "きそ", "しまかぜ", "ひびき", "わかば", "ながなみ"]
["あぶくま", "きそ", "しまかぜ", "ひびき", "ながなみ"]

1. 2つの配列の値の差分を新しい配列にして返す

いずれかの配列にしか値が存在しない場合を返せばいいので、indexOfで配列内に値が存在するかをチェックして判定。

function getArrayDiff(arr1, arr2) {
  let arr = arr1.concat(arr2);
  return arr.filter((v, i)=> {
    return !(arr1.indexOf(v) !== -1 && arr2.indexOf(v) !== -1);
  });
}

👇 出力結果

1.  
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[8, 2, 8, 4, 7, 9, 11, 8, 10]
// =>  [ 1, 3, 5, 6, 11 ]
2.
[0, 1, 2, 3, 4, 5, 6]
['', 1, NaN, "3", null, 5, undefined]
// => [ 0, 2, 3, 4, 6, '', NaN, '3', null, undefined ]
3. 
[]
["いちご", "あおい", "らん"]
// => [ 'いちご', 'あおい', 'らん' ]
4.
[1, "サーバル", 3, "かばん"]
[1, 7, "ボス"]
// => [ 'サーバル', 3, 'かばん', 7, 'ボス' ]
5.
["あぶくま", "きそ", "しまかぜ", "ひびき", "わかば", "ながなみ"]
["あぶくま", "きそ", "しまかぜ", "ひびき", "ながなみ"]
// => [ 'あぶくま', 'きそ', 'しまかぜ', 'ひびき', 'わかば', 'ながなみ' ]

Array.prototype.includes() - JavaScript | MDN
配列に値があるかBoolean値で返すarray.includes()という関数もあるっぽいけど、ECMAScript 2017 Draft (ECMA-262)って書いてあるからES7なのかな?
 

2. 2つの配列の値の差分をインデックス含めてチェックして返す

2-1. 新旧の比較のように、配列Aと配列Bを比べ配列Aから変化しているものを返す

function getArrayIndexValueDiff(oldArr, newArr) {
  return newArr.filter((v, i)=> oldArr[i] !== v);
}

👇 出力結果

1.  
old: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
new: [8, 2, 8, 4, 7, 9, 11, 8, 10]
// =>  [ 8, 8, 7, 9, 11, 10 ]
2.
old: [0, 1, 2, 3, 4, 5, 6]
new: ['', 1, NaN, "3", null, 5, undefined]
// => [ '', NaN, '3', null, undefined ]
3. 
old: []
new: ["いちご", "あおい", "らん"]
// => [ 'いちご', 'あおい', 'らん' ]
4.
old: [1, "サーバル", 3, "かばん"]
new: [1, 7, "ボス"]
// => [ 7, 'ボス' ]
5.
old: ["あぶくま", "きそ", "しまかぜ", "ひびき", "わかば", "ながなみ"]
new: ["あぶくま", "きそ", "しまかぜ", "ひびき", "ながなみ"]
// => [ 'ながなみ' ]

2-2. お互いの配列で異なっているものを返す

function getArrayIndexValueDiffBoth(arr1, arr2) {
  let _a1 = arr1,
      _a2 = arr2,
      _a2Len,
      res = [[], []],
      reverse = false;

  // ループのベースは要素が多い方の配列にする
  if(_a1.length < _a2.length) {
    _a1 = arr2;
    _a2 = arr1;
    reverse = true;
  }
  _a2Len = _a2.length;

  res[0] = _a1.filter((v, i)=> {
    if(_a2[i] !== v) {
      // 元の配列に存在していない値(undefined)は除外
      if( i < _a2Len) {
        res[1].push( _a2[i] );
      }
      return true;
    }
  });

  if(reverse) {
    res.reverse();
  }
  return res;
}

もう少しエレガントな書き方がありそうな気がする...
👇 出力結果

1. 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[8, 2, 8, 4, 7, 9, 11, 8, 10]
// => [ [ 1, 3, 5, 6, 7, 9, 10 ], [ 8, 8, 7, 9, 11, 10 ] ]
2. 
[0, 1, 2, 3, 4, 5, 6]
['', 1, NaN, "3", null, 5, undefined]
// =>[ [ 0, 2, 3, 4, 6 ], [ '', NaN, '3', null, undefined ] ]
3. 
[]
["いちご", "あおい", "らん"]
// => [ [], [ 'いちご', 'あおい', 'らん' ] ]
4. 
[1, "サーバル", 3, "かばん"]
[1, 7, "ボス"]
// => [ [ 'サーバル', 3, 'かばん' ], [ 7, 'ボス' ] ]
5. 
["あぶくま", "きそ", "しまかぜ", "ひびき", "わかば", "ながなみ"]
["あぶくま", "きそ", "しまかぜ", "ひびき", "ながなみ"]
// => [ [ 'わかば', 'ながなみ' ], [ 'ながなみ' ] ]

 
アロー関数で書いている部分は別にアロー関数でなくても大丈夫なはずです。
差分ってえっちなコトバな印象になってしまったのはきっとインターネッツのせい。


プログラマのための論理パズル 難題を突破する論理思考トレーニング

プログラマのための論理パズル 難題を突破する論理思考トレーニング