ブログ
ニュース & ブログ
サマーノート(Symfony5でsummernoteを使う)
公開日:2020.12.14
「WYSIWYGエディタ」とは、ブログ感覚で、入力ができるエディタの総称で、WordPressなどでお馴染みですよね。
「WebアプリにWYSIWYGエディタを組み込みたいなー。」と思った場合、皆さんは何を使っていますか?
WYSIWYGプロジェクトは数多くあり、どれを使うか迷うところですよね。
- 「このエディタは、多機能だけど、ライセンスの問題があるなぁ。」
- 「手軽に組み込めそうだが、ファイルアップロードとの連携が大変そうだなぁ」
などなど、
今回は、そんな中で、目つけた「summernote(サマーノート)」をご紹介したいと思います。
summernote(サマーノート)とは
summernote(サマーノート)はオープンソースのWYSIWYGエディタです。
ヒロシがいいなと思った点
- MITライセンスで商用利用しやすい。
- 画像ファイルをWYSIWYがBase64エンコードしてくれるので、画像のアップロード処理の実装が不要。
- Bootstrap上で使える(Bootstrapのバージョンは、4でも3でもOK)。
- GitHubスター数も多く、メンテナスもされている。
では、早速ためしてみましょう。
セットアップの前に
「summernote」が必要とする、依存ファイルは「Bootstrap」と「jquery」ですが、これら依存ファイルも含め「Symfony5」の「Encore(Webpack)」を使って「yarn」経由で設置をしてみます。過去に「Encore(Webpack)」と「yarn」についての記事を書いているので、こちらも目を通していただけると幸いです。
クライアントサイドがメインになりますので、サーバサイド(コントローラとかエンティティ)の話は簡単に流したいと思います。まずは、最低限必要な、Entity、FormType、Controller、Viewを用意します。 DBは用意されている前提です。では、始めましょう、オーケイ、レッツ、ビギン!
エンティティとテーブルの用意
まず、Blogの記事を格納するための、「Blog」エンティティをSymfonyの「make」コマンドを使って、対話的に作っていきます。
$ bin/console make:entity
「Blog」エンティティが持つフィールドは「タイトル(title)」と「本文(body)」のみにします。
New property name (press <return> to stop adding fields): > title Field type (enter ? to see all types) [string]: > Field length [255]: > Can this field be null in the database (nullable) (yes/no) [no]: > yes updated: src/Entity/Blog.php Add another property? Enter the property name (or press <return> to stop adding fields): > body Field type (enter ? to see all types) [string]: > text Can this field be null in the database (nullable) (yes/no) [no]: > yes updated: src/Entity/Blog.php
これで、「Blog」エンティティができました。 このまま、「Blog」エンティティと紐づくテーブルを「maker」コマンドでDB上に作成します。
マイグレーションを生成
$ bin/console make:migration
マイグレーションを実行してテーブルが作成されます。
$ bin/console doctrine:migrations:migrate
これで、必要なテーブルができました。
mysql> desc blog; +-------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | title | varchar(255) | YES | | NULL | | | body | longtext | YES | | NULL | | +-------+--------------+------+-----+---------+----------------+ 3 rows in set (0.00 sec)
フォームタイプを作成
次にフォーム要素を作るためのフォームタイプを用意します。 これも、「maker」コマンドを使って、「Blogエンティティ」と紐づけする形で生成します。
$ bin/console make:form The name of the form class (e.g. TinyKangarooType): > BlogType The name of Entity or fully qualified model class name that the new form will be bound to (empty for none): > Blog created: src/Form/BlogType.php
生成されたフォームタイプ「BlogType」に保存ボタンを加えて、次のようしました。
src/Form/BlogType.php
<?php namespace App\Form; use App\Entity\Blog; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; class BlogType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('title') ->add('body') ->add('save', SubmitType::class,[ 'label' => '保存', ]) ; } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => Blog::class, ]); } }
コントローラーとテンプレートの用意
コントローラと、テンプレートも「maker」コマンドを使って生成します。
$ bin/console make:controller Choose a name for your controller class (e.g. FiercePizzaController): > BlogController created: src/Controller/BlogController.php created: templates/blog/index.html.twig
src/Controller/BlogController.php
<?php namespace App\Controller; use App\Entity\Blog; use App\Form\BlogType; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class BlogController extends AbstractController { /** * @Route("/blog", name="blog") */ public function index(Request $request): Response { $entityManager = $this->getDoctrine()->getManager(); $form = $this->createForm(BlogType::class); $form->handleRequest($request); if($form->isSubmitted()){ $blog = $form->getData(); $entityManager->persist($blog); $entityManager->flush(); } return $this->render('blog/index.html.twig', [ 'controller_name' => 'ブログ記事の入力フォーム', 'form' => $form->createView(), ]); } }
ビューは渡されたフォームオブジェクトをそのまま表示しているだけです。
templates/blog/index.html.twig
{% extends 'base.html.twig' %} {% block title %}Hello BlogController!{% endblock %} {% block body %} <style> .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; } .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } </style> <div class="example-wrapper"> <h1>{{ controller_name }}! ✅</h1> {{ form(form) }} </div> {% endblock %}
ここで一旦コミット
サーバサイドの処理はこれでよいので、ここで一旦コミットします。
$ git status On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: migrations/Version20201130120559.php new file: src/Controller/BlogController.php new file: src/Entity/Blog.php new file: src/Form/BlogType.php new file: src/Repository/BlogRepository.php new file: templates/blog/index.html.twig git commit -m "サーバサイドの処理はこれでOK" [master 485d137] サーバサイドの処理はこれでOK 6 files changed, 217 insertions(+) create mode 100644 migrations/Version20201130120559.php create mode 100644 src/Controller/BlogController.php create mode 100644 src/Entity/Blog.php create mode 100644 src/Form/BlogType.php create mode 100644 src/Repository/BlogRepository.php create mode 100644 templates/blog/index.html.twig
ブラウザでアクセス
「http://localhost/blog」にアクセスをするとフォームが表示されます。 「maker」コマンドで自動生成したコードを利用しているので、見栄えが悪く、本文を入力するためのWYSIWYGエディタもありません。 このあとBootstrapとsummernoteでWYSIWYGエディタを入れて見た目を整えていきます。
summernote、Bootstrapを「Webpack Encore」で追加
さて、ここからが本題です。「Webpack Encore」を使ってyarn経由でsummernote、Bootstrapを追加します。依存ファイルは「jquery」と「popper.js」になります。
「Webpack Encore」を使えるようにします。
Composerで「Webpack Encore」をインストール
$ composer require symfony/webpack-encore-bundle
yarnをインストール(グローバルとローカルの双方に)
$ npm install yarn $ npm install -g yarn
yarnで「symfony/webpack-encore」の依存ファイルを追加
$ yarn install
コミット
$ git status On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: .gitignore new file: assets/app.js new file: assets/styles/app.css modified: composer.json modified: composer.lock modified: config/bundles.php new file: config/packages/assets.yaml new file: config/packages/prod/webpack_encore.yaml new file: config/packages/test/webpack_encore.yaml new file: config/packages/webpack_encore.yaml new file: package-lock.json new file: package.json modified: symfony.lock new file: webpack.config.js new file: yarn.lock $ git commit -m "webpack-encore-bundleを追加"
Bootstrapとjqueryの設定
「summernote」が依存する「jquery」「popper.js」「bootstrap」を先に追加しておきます。
$ yarn add jquery popper.js bootstrap --dev
フォームでbootstrap4テーマを使うように設定
config/packages/twig.yaml
twig: default_path: '%kernel.project_dir%/templates' + form_themes: ['bootstrap_4_layout.html.twig']
ベーステンプレートで「webpack-encore」が生成するエントリーポイントを読み込むようにします。
templates/base.html.twig
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>{% block title %}Welcome!{% endblock %}</title> - {% block stylesheets %}{% endblock %} + {% block stylesheets %} + {{ encore_entry_link_tags('app') }} + {% endblock %} </head> <body> {% block body %}{% endblock %} - {% block javascripts %}{% endblock %} + {% block javascripts %} + {{ encore_entry_script_tags('app') }} + {% endblock %} </body> </html>
BootstrapのCSSを読み込み
assets/styles/app.css
+ @import "~bootstrap/dist/css/bootstrap.min.css"; body { background-color: lightgray; }
BootstrapのJavaScriptと、jqueryを読み込み
assets/app.js
+ const $ = require('jquery'); + require('bootstrap'); // any CSS you import will output into a single css file (app.css in this case) import './styles/app.css'; // Need jQuery? Install it with "yarn add jquery", then uncomment to import it. // import $ from 'jquery'; + $(document).ready(function (){ + console.log("jquery is now available!"); + });
コンパイルをしてエントリーポイントの生成
$ yarn encore dev
ブラウザでアクセス
「http://localhost/blog」にアクセスをするとフォームにBootstrapが適用されています。この後は、summernoteを適用していきます。
summernoteを利用
yarnでsummernoteを追加
$ yarn add summernote
summernoteのCSSを読み込み
assets/styles/app.css
@import "~bootstrap/dist/css/bootstrap.min.css"; + @import "~summernote/dist/summernote-bs4.min.css"; body { background-color: lightgray; }
summernoteのJSを読み込み、テキストエリアで利用するように指定。
assets/app.js
const $ = require('jquery'); require('bootstrap'); + require('summernote'); // any CSS you import will output into a single css file (app.css in this case) import './styles/app.css'; // Need jQuery? Install it with "yarn add jquery", then uncomment to import it. // import $ from 'jquery'; $(document).ready(function (){ console.log("jquery is now available!"); + $("textarea").summernote(); });
再度コンパイル
$ yarn encore dev
ブラウザでアクセス
「http://localhost/blog」にアクセスをするとテキストエリアにWYSIWYGが適用されているのが確認できます。
画像をアップロードすると、
Base64に変換されます
これをそのままDBに保存をすれば、画像アップロードが簡単に実現できます。
mysql> select * from blog \G; *************************** 1. row *************************** id: 1 title: テスト記事 body: <p><img src="data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMvaHR0cDovL25zLmFkb2JlLmNvbS....
感想
サマーノートはとても手軽に実装ができます。ただ、画像を多用するような記事の場合、動作がのっそりしてしまいます。また、PDFファイルなど画像以外のファイルをアップロードができません。長所と短所を理解した上で使うには良さそうです。今日はサマーノートを試してみましたが、後日、他のWYSIWYGエディタも試してみたいと思います。