もうひとつのリッチエディタ「summernote」を試してみる(jQuery + Bootstrap ベース)

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、インターネットの世界がとてつもなく広いというのは皆さんもよく感じることだと思うのですが、私の場合も「えっ、そんなの聞いたことない❗」と驚くことがあります。

今回ご紹介するのはそんな「もっと早く知りたかった」というテクノロジーで、その名もSummernoteという

リッチエディタ(wysiwyg エディタ)

です。

リッチエディタで有名どころといえば、やはり「CKEditor」と「Quill」だと思いますが、Summernote も第3の選択肢として充分使えそうなので今回ご紹介することにしました。

📝 参考ページ: 【Laravel】シンプルにCMSを実装する(CKEditor 5)

ちなみに Summernote は、jQuery ベースで作成されているので、レガシーな環境でも使いやすいですし、Bootstrap 34にも対応しています👍✨

ということで、今回はリッチエディタ「Summernote」の使い方をご紹介したいと思います。

ぜひ何かの参考になりましたら嬉しいです。😄✨

「アップロード部分は Laravel で
チャチャっとつくります😉」

開発環境: Laravel 8.x、Vue 3

基本的な使い方

では、まずは基本の使い方をご紹介します。(Bootstrap無しバージョンです)

<html>
<head>
    <link href="https://cdn.jsdelivr.net/npm/summernote@0.8.20/dist/summernote-lite.min.css" rel="stylesheet">
</head>
<body>

    <div id="summernote">
        <p>ここが初期テキストになります。</p>
        <p>改行もOK!</p>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-lite.min.js"></script>
    <script>

        $(() => {

            $('#summernote').summernote();

        });

    </script>
</body>
</html>

みなさんご存知のとおり、#に続くIDを指定することでSummernoteを初期化します。

なお、指定したタグの中にテキストを入れておくと、それが初期状態で表示されるようになります。

ちなみに、(2022.01.01現在)Bootstrap無しバージョンでは、テキストが空の場合に行がずれるので、もし気になるようでしたら以下のような初期値にするといいでしょう。

<div id="summernote">
    <p><br></p>
</div>

たったこれだけでリッチエディタが使えるというのは素晴らしいですね👍

Bootstrap 4と一緒に使う

続いて、SummernoteBootstrap 3 & 4 に対応しているので、一緒に使うバージョンもご紹介します。

<html>
    <head>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
        <link href="https://cdn.jsdelivr.net/npm/summernote@0.8.20/dist/summernote-bs4.min.css" rel="stylesheet">
    </head>
    <body>

        <div class="p-5">
            <div id="summernote"></div>
        </div>

        <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/summernote@0.8.20/dist/summernote-bs4.min.js"></script>
        <script>

            $(() => {

                $('#summernote').summernote();

            });

        </script>
    </body>
</html>

先ほどと違うのは、(もちろんですが)Bootstrap.js.cssを読み込んでいるところと、Summernoteの「Bootstrap 専用 JavaScript & CSS」を読み込んでいる部分です。

コードは直接変更しなくていいので、便利ですね👍✨

Bootstrap 5 では微妙にうまくいかないっぽいです

公式ページでは、「Supports Bootstrap 3.x.x to 4.x.x」と書かれているのですが、Bootstrapの最新版はバージョン5系(2022.01.01現在)なので試してみたところ、微妙にうまくいかない部分がありました。

例えば、ポップアップのCSSが効いていない部分があったり、閉じるボタンが機能しないようでした。

おそらくBootstrap 5からはjQueryを使わなくなったので、そのあたりの影響もあるんじゃないかと思います。

Summernote を日本語化する

Summernoteは、初期状態では英語が使用されていますが、簡単に多言語対応できるようにしてくれています。

というこで、日本語に対応させてみましょう。

// 省略

<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.20/src/lang/summernote-ja-JP.js"></script>
<script>

    $(() => {

        $('#summernote').summernote({
            lang: 'ja-JP'
        });

    });

</script>

このように専用のJavaScriptを読み込んで言語の設定をするだけでOKです。

Vue 3と連携してみる

SummernotejQueryベースですが、Vueと連携して使いたいときもあると思います。

そこで、Summernoteにテキストを入力したら、自動的にVueのデータに反映されるようにしてみましょう。

<html>
<head>
    <link href="https://cdn.jsdelivr.net/npm/summernote@0.8.20/dist/summernote-lite.min.css" rel="stylesheet">
</head>
<body>

    <div id="app">

        <div id="summernote" v-html="params.description"></div>

        <br>
        【プレビュー】
        <div v-html="params.description"></div>

    </div>

    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/summernote@0.8.20/dist/summernote-lite.min.js"></script>
    <script src="https://unpkg.com/vue@3.0.11/dist/vue.global.prod.js"></script>
    <script>

        Vue.createApp({
            data() {
                return {
                    params: {
                        description: '<p>初期テキスト</p>'
                    }
                }
            },
            mounted() {

                $('#summernote').summernote({
                    callbacks: {
                        onChange: (contents, $editable) => { // this が使えなくなるのであえて省略形は使ってません

                            this.params.description = contents;

                        }
                    }
                });

            }
        }).mount('#app');

    </script>
</body>
</html>

残念ながら、v-modelで直接バインディングすることはできないので、コールバック関数のonChangeでテキストの変更をVue側へ伝える必要があります。

また、以下のように省略形のメソッドにするとスコープthisが変わってしまうので、あえてこの形にしています。

// ⚠ これはうまくいかない例です

onChange(contents, $editable) => {

    this.params.description = contents; // this はメソッド本体になる

}

画像をアップロードできるようにする

Summernoteは画像のアップロードにも対応していますが、実際に画像を受け取って保存する部分は自分で用意する必要があります。

ということで、この「アップロード部分」をLaravelでつくってみます。
以下のコマンドを実行してください。

php artisan make:controller SummernoteController

すると、ファイルが作成されるので中身を以下のようにしてください。

app/Http/Controllers/SummernoteController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Str;

class SummernoteController extends Controller
{
    public function index()
    {
        return view('summernote.index');
    }

    public function upload_image(Request $request)
    {
        $request->validate([
            'image' => ['required', 'image', 'mimes:jpeg,png', 'max:2048'],
        ]);

        $file = $request->file('image');
        $extension = $file->extension();
        $path = 'public/summernote_images';
        $filename = date('Ymd-His') .'_'. Str::random(5) .'.'. $extension;
        $request->file('image')->storeAs($path, $filename);

        return [
            'result' => true,
            'image_filename' => $filename,
            'image_url' => url('/storage/summernote_images/'. $filename)
        ];
    }
}

なお、アップロード先はpublicフォルダの中になるよう、以下のコマンドを実行して「シンボリックリンク」をつくっておいてください。

php artisan storage:link

次にビューをつくります。

resources/views/summernote/index.blade.php

<html>
    <head>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
        <link href="https://cdn.jsdelivr.net/npm/summernote@0.8.20/dist/summernote-bs4.min.css" rel="stylesheet">
    </head>
    <body>

        <div id="summernote"></div>

        <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/summernote@0.8.20/dist/summernote-bs4.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
        <script>

            $(() => {

                $('#summernote').summernote({
                    callbacks: {
                        onImageUpload(files) {

                            const file = files[0];

                            if(file instanceof File) {

                                const url = '{{ route('summernote.upload_image') }}';
                                let formData = new FormData();
                                formData.append('image', files[0]);

                                axios.post(url, formData)
                                    .then(response => {

                                        if(response.data.result === true) {

                                            const imageUrl = response.data.image_url;
                                            const imageFilename = response.data.image_filename;
                                            $('#summernote').summernote('insertImage', imageUrl, imageFilename);

                                        }

                                    });

                            }

                        }
                    }
                });

            });

        </script>
    </body>
</html>

最後にルートです。

routes/web.php

Route::get('summernote', [SummernoteController::class, 'index'])->name('summernote.index');
Route::post('summernote/upload_image', [SummernoteController::class, 'upload_image'])->name('summernote.upload_image');

これで、以下のように画像がセットできるようになります👍

エディタのデータをゲット&セットする

手動でエディタにデータをゲット&セットするにはsummernote('code')を使います。

// 省略

<button id="get_button" type="button">データをゲット</button>
<button id="set_button" type="button">データをセット</button>

// 省略

<script>

    $(() => {

        $('#summernote').summernote();

        // データをゲット
        $('#get_button').on('click', () => {

            const contents = $('#summernote').summernote('code');
            console.log(contents);

        });

        // データをセット
        $('#set_button').on('click', () => {

            const contents = '<p>これは<b>テスト</b>です!</p>';
            $('#summernote').summernote('code', contents);

        });

    });

</script>

デモをつくりました

せっかくなので、Summernoteのデモページをつくりました。
ぜひご自身で試してみてください。

📝 デモページ

企業様へのご提案

SummernoteはMITライセンスで提供されていますので、完全に無料で利用することができます。(CKEditorは、アクティブユーザーが増えると有料になります)

また、冒頭でも書きましたとおり、jQueryBootstrapを使って動きますので、長らく使用されているシステムにも適用がしやすいと思います。

もし既存システムでリッチエディタを使いたい場合はぜひお問い合わせからご依頼ください。

ぜひよろしくお願いいたします。m(_ _)m

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで、今回は「もうひとつのリッチエディタ」のSummernoteをご紹介しました。

実際に触ってみましたところ、操作感に問題があるようには感じませんでしたし、Bootstrapと親和性が高いの特に業務系システムに向いていると思いました。

また、こういった新しいパッケージやライブラリを使う場合に「GitHubでスターが多いかどうか」というのも判断材料にしますが、Summernoteは(この記事を書いている時点で)10,000スターを超えているので、とても人気があると言っていいんじゃないでしょうか。

ぜひ皆さんもSummernoteを試してみてくださいね。

ではでは〜❗

「久しぶりにゲームをはじめましたが、
飽きてプログラムしてます
面白いから…😂」

このエントリーをはてなブックマークに追加       follow us in feedly