超便利!「WordPressでファイルアップロードをAjaxでやる方法」を JavaScript ビギナーがやってみた

しずみ さんがブログで「WordPressでファイルアップロードをAjaxでやる方法」を紹介してくれました。とても便利、かつ楽しい機能なので未読の方は是非一読ください。動きの具体的な様子はこの記事の末尾でも紹介しています。

以下は JavaScript ビギナーが試してみて、ついでにブロック化した過程を参考文献と共にまとめたものです。どれくらいビギナーかというと見ないで書ける JavaScript は

alert("Hello World");

くらい。でも、なんとかなりました。

sample.php

まず最初から「これでなぜ動くの?」「Submit ボタンは要らないの?」と、つまづきましたが、動きとしては画像ファイルを選択した瞬間にアップロードされるのですね。途中までやってて初めてわかりました。たしかに便利だし、だからこその Ajax。気づけよ、私。

でもこのファイル「sample.php」どこに置けばいいの?

とりあえず public/ の下においてブラウザからアクセスしてみましたが、ウンともスンとも言いません。仕方ないので sample.php というファイルはあきらめて、コードだけ貼り付けることにしました。

  1. 投稿や固定ページを新規作成する
  2. HTMLブロックを挿入する
  3. sample.php の内容を記述する
  4. 投稿を公開する

実行するとこんな感じでボタンができました。クリックするとファイル選択用のダイアログが開きファイル名も自動で表示されます。へー素晴らしい。

functions.php

本来であれば子テーマを作成するか、プラグインにすべきところでしょうが、あくまで実験用のために Twenty Tweny の functions.php ファイル末尾に貼り付けました。良い子は真似してはいけません。
しかし Twenty Twenty の functios.php って 760行もあるのですね、すごいなぁ。

wp_localize_script() という不思議な名前の関数が必要なんだそうです。で、その説明を読んでいると…

日本語Codexは未翻訳のため

すみません、すみません。
現在従来のマニュアル Codex のうちユーザーガイド的なものは「WordPress Support」に、開発者向けドキュメントは「Developer Resources」に移行中です。日本語版でもユーザーガイドは同様に「WordPress サポート」へ以降中ですが、開発者向けドキュメントの翻訳先、翻訳方法が未だに決まっていません。ブロックエディターハンドブックだけはその重要性から訳出を始めましたが、置き場は一時的なものです。どうしましょうかねぇ…。

upload-image.js

次に upload-image.js です。functions.php を見る限り同じディレクトリに置けば良さそうですが、念のため確認します。alert() 便利。

function theme_enqueue_scripts() {
    alert(get_theme_file_uri( 'upload-image.js' ));    // 追加
    wp_enqueue_script( 'handle_name', get_theme_file_uri( 'upload-image.js' ), array( 'jquery' ), filemtime( get_theme_file_path( 'upload-image.js' ) ) );
        :

実行したらエラー

Fatal error: Uncaught Error: Call to undefined function alert() in /var/www/html/wp-content/themes/twentytwenty/functions.php:763

えーなんでーと、悩んで悩んで PHP に alert() がないことに気が付きました。バカすぎる。echo() で置き換え。

function theme_enqueue_scripts() {
    echo(get_theme_file_uri( 'upload-image.js' )); // 再度追加
    wp_enqueue_script( 'handle_name', get_theme_file_uri( 'upload-image.js' ), array( 'jquery' ), filemtime( get_theme_file_path( 'upload-image.js' ) ) );
        :

やっと出ました。やはりfunctions.phpと同じ場所で良さそうです。

http://localhost:8888/wp-content/themes/twentytwenty/upload-image.js

発火しない

でも投稿からファイルを選択してもウンともスンともいいません。JavaScript コンソールにエラーメッセージが出力されていないかを確認します。

  1. ブラウザの画面で右クリックして「検証」(Chrome) または「要素を調査」(FireFox)をクリックする
  2. 「コンソール」タブをクリックする

何も出力されていません。

困ったらもちろんalert()です。

(function ($) {
    $(function () {
    alert("Hello 1");  // 追加 その1
    let image_form = $('.image-form');
    let image_file = image_form.find('.file');

    image_file.on('change', function (e) {
        alert("Hello 2");  // 追加 その2
        // 通常動作を止める
        e.preventDefault();
            :

すると投稿をリロードすると Hello 1 は出力されるのに、画像ファイルを選択しても Hello 2 は出力されないことから on イベントが呼ばれていないことが分かります。

いくつか検索して次のページを見つけました。

jQuery でイベントが発火しないときの簡単な対処法
https://pisuke-code.com/jquery-solution-for-event-unfired/

この記事にあるように動的にボタンが追加されているわけではありませんが(ですよね?)、ドキュメント全体でイベントハンドラを有効にするのは回避策としては良さげです。

(function ($) {
    $(function () {
    alert("Hello 1");  // 追加 その1
    let image_form = $('.image-form');
    let image_file = image_form.find('.file');

    // image_file.on('change', function (e) {    // 削除
    $(document).on('change', function (e) {    // 追加 その3
        alert("Hello 2");  // 追加 その2
        // 通常動作を止める
        e.preventDefault();
            :

画像ファイルを選択すると無事 Hello 2 でました!やった、できた!と思ったけどメディアライブラリは空っぽ。JavaScript コンソールを確認するとエラーが出ていました。

Uncaught TypeError: Cannot read property 'files' of undefined
    at HTMLDocument. (upload-image.js?ver=1591427647:15)
    at HTMLDocument.dispatch (jquery.js?ver=1.12.4-wp:3)
    at HTMLDocument.r.handle (jquery.js?ver=1.12.4-wp:3)

この行で files がないらしい。なんでや。

data.append('async-upload', image_file[0].files[0]);

と考えて、ようやくここで image_file がうまく取得できていないこと、だから最初発火しなかったことがわかりました。見ていくと最初のフォームを作った所が間違っている(泣。だって typenameclassfile なんだもん…という言い訳はやめて修正。

これで最初のコードでも期待したように on イベントが来るようになり、上のエラーも消えました。

ちなみに processDatacontentType の設定については以下の記事が参考になります。

Ajaxを用いてテキストと画像ファイルを投稿する
https://qiita.com/sho012b/items/f2558db5dad97d7e1b1d

モダンな JavaScript

少し引っ掛かったのは以下の文章です。

この部分についてはどの様に書きたいかはお好みで。多分 success とか error で書いても動く (モダンでは無いけど)。

つまりこの書き方はモダンなんですよね。確かに昔 .success とか .error でつながっているコードを見たことがあります。いい機会なので少し勉強。

現在の JavaScript をざっと掴むにはこちらの記事がとても参考になりました。

「モダン JavaScript プログラミングを始めるために知っておきたい技術セット」
https://qiita.com/Go-Noji/items/25a30b8cadef86abc2b0

おじさんが若い時はね$.ajax()のオプションでsuccessとかerrorとか指定していたんだよ(追憶)
https://qiita.com/tonkotsuboy_com/items/0722ad92f370ab0c411b

そして以下の記事を参考にモダンな環境を作ってみました。

「最新版で学ぶwebpack 4入門 – Babel 7でES2020環境の構築(React, Vue, Three.js, jQueryのサンプル付き)」の「webpack+Babel+jQueryの構成を作成しよう」
https://ics.media/entry/16028/#webpack-babel-jquery

以下、Twenty Twenty のディレクトリにて実行します。

$ mkdir src
$ mv upload-image.js src
$ npm install -D webpack webpack-cli babel-loader @babel/core @babel/preset-env
$ npm install jquery

記事中の package.json"build""webpack" に変更し、webpack.config.jsonentry および filenameupload-image.js を指定します。 そしてビルド。

$ npm run build

> twentytwenty@1.0.0 build /Users/zenge/Local Sites/testsite4/app/public/wp-content/themes/twentytwenty
> webpack

Hash: 015ef3ee9ff895eb5e52
Version: webpack 4.43.0
Time: 3846ms
Built at: 2020/06/06 17:49:50
          Asset Size Chunks Chunk Names
upload-image.js 1.57 KiB 0 [emitted] main
Entrypoint main = upload-image.js
[0] ./src/upload-image.js 1.14 KiB {0} [built]
$

これで完成です。

制限事項

しずみさんの記事にも書かれていますがファイルアップロードには権限が必要です。権限の足らないユーザーには該当の権限を付与するだけで済みますが、そもそもサイトにログインしていなければ権限の与えようもなく、この機能を使用できません。実際、ログアウトして試すと以下のエラーが返ります。

error!
jqXHR
{readyState: 4, getResponseHeader: ƒ, getAllResponseHeaders: ƒ, setRequestHeader: ƒ, overrideMimeType: ƒ, …}
textStatus
parsererror
errorThrown
SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse ()
at n.parseJSON (jquery.js?ver=1.12.4-wp:4)
at jQuery.parseJSON (jquery-migrate.js?ver=1.4.1:307)
at Wb (jquery.js?ver=1.12.4-wp:4)
at x (jquery.js?ver=1.12.4-wp:4)
at XMLHttpRequest.c (jquery.js?ver=1.12.4-wp:4)

匿名ユーザーから大量にファイルをアップロードされても困るのでこれはこれでありがたい動きですが、困るシーンもありそうなので注記しました。

まとめ

使い方の手順を紹介すると

  1. sample.php の中身を投稿や固定ページの HTML ブロックの中に記入する。
  2. テーマの functions.php の一番下にコードを貼り付ける。
  3. 同じディレクトリに upload-image.js を置く。

これで投稿や固定ページを開き、ボタンから画像ファイルを選択するだけでメディアライブラリに画像ファイルがアップロードされます。

WordCamp EU 2020 公式わぷー「Okaeri Wapuu」の画像ファイルがアップロードされました。

おまけ – カスタムブロックにしてみる

カスタムブロックにしてみました。手順は以下のとおりです。

まず雛形を create-block パッケージから生成します。

$ cd .../plugins
$ npm init @wordpress/block --namespace atachibana upload-image
$ cd upload-image
$ npm start

upload-image.php の末尾に functions.php からコードをコピーし、パスの指定方法を変更します。

        :
    wp_enqueue_script(
        'handle_name',
        plugins_url( 'build/upload-image.js', __FILE__ ),
        array( 'jquery' ),
        filemtime( "$dir/build/upload-image.js" )
    );
    $data = array(
        'upload_url' => admin_url( 'async-upload.php' ),
        'nonce' => wp_create_nonce( 'media-form' ),
    );
    wp_localize_script( 'handle_name', 'data_name', $data );
}
add_action( 'init', 'atachibana_upload_image_block_init' );

次にupload-image.jsupload-image/build/ ディレクトリの下にコピーします。本当は src/ からかっこよくコンパイルしたくて、index.js に Import を追加し、wp_enqueu_script を削除し、wp_register_script されている index.js にローカライズ関数を適用し、… とやっていたのですがイベントが期待どおりに発火せず諦めました。

気を取り直して src/edit.jsEdit() からフォームを返します。Sample.php の中身を貼り付けますが、JSX ですので classclassName で置き換えます。

export default function Edit({ className }) {
     return (
         <form action="#" className="image-form">
             <input type="file" className="file" name="file" />
         </form>
     );
}

src/save.js も同様です。

export default function save() {
    return (
        <form action="#" className="image-form">
            <input type="file" className="file" name="file" />
        </form>
    );
}

最後に editor.cssstyle.css に雛形用のスタイルが記述されていますので削除します。

以上で完成しました。テストしてみます。

  1. 投稿や固定ページを新規作成する
  2. ブロックインサーターで「ウィジェット」カテゴリーの「Upload Image」ブロックを追加する。
  3. 投稿を公開する

同様に動作しました。コードは GitHub にあります。

おしまい

追記 (2020/6/7) 文字化け修正

記事公開後にしずみさんから文字化けを教えてもらいました。滅多にコードを書かないので気が付きませんでしたが、実体参照がそのまま表示されていました。ありがとうございます。

そのとき教えてもらったプラグインが「Highlighting Code Block」。もういい加減、Crayon Syntax Highlighter はやめよう

「超便利!「WordPressでファイルアップロードをAjaxでやる方法」を JavaScript ビギナーがやってみた」への1件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です