リッチな WYSIWYG エディタで CKEditor をカスタマイズする。
前提条件
- Rails 5.x
- CKEditor の v4.2.4 を使用
以下、JavaScriptの処理は Rails に限らず使用できる。
不要なボタンを消す
デフォルトの editorConfig を上書きすることで表示項目をカスタマイズできる。
次の例は最低限 WYSIWYG に必要な機能だけ残し、エディタの高さを 380px 固定にした例。
CKEDITOR.editorConfig = function (config) {
config.toolbar_mini = [
{
name: 'paragraph',
groups: ['list', 'indent', 'blocks', 'align', 'bidi'],
items: [
'NumberedList', 'BulletedList',
'-',
'Outdent', 'Indent',
'-',
'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'
]
},
{
name: 'links',
items: [
'Link', 'Unlink'
]
},
{
name: 'basicstyles',
groups: ['basicstyles', 'cleanup'],
items: [
'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript',
'-',
'RemoveFormat'
]
},
{
name: 'insert',
items: [
'Image', 'Table', 'HorizontalRule'
]
}
];
// toolbar_mini が読み込まれる
config.toolbar = "mini";
// 高さを指定
config.height = '380px';
};
i18n
locale ファイルを追加することでボタンなどの表示を i18n 対応できる。
しかし locale ファイルが適用されない箇所もあり、それは後述する。
ja:
ckeditor:
confirm_delete: 'ファイルを削除してもよろしいですか?'
buttons:
cancel: 'キャンセル'
upload: 'アップロード'
delete: '削除'
next: '次へ'
dialog:
title: '画像'
リンク追加ダイアログをカスタマイズ
Link
ボタンをクリックしたときに表示されるダイアログをカスタマイズする。
ダイアログはボタンがクリックされたときに JavaScript で生成される。その生成を hook する dialogDefinition
イベントにコールバックを設定し動的にカスタマイズする必要がある。
ダイアログを取得し、そのタブ内の任意のフィールドを書き換えている。.getContents
で要素を取得したり、 .removeContents
で不要な要素を削除できる。
日本語をハードコーディングしているところは、i18n-jsとか使って i18n 対応した方がよい。
CKEDITOR.on('dialogDefinition', function (ev) {
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
if (dialogName === 'link') {
var infoTab = dialogDefinition.getContents('info');
if (infoTab) {
var urlField = infoTab.get('url');
urlField['default'] = 'www.example.com';
}
if (dialogDefinition.getContents('advanced')) dialogDefinition.removeContents('advanced');
}
}
画像選択ダイアログをカスタマイズ
リンクと同様に JavaScript でカスタマイズする。
CKEDITOR.on('dialogDefinition', function (ev) {
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
•••
if (dialogName === 'image') {
var uploadTab = dialogDefinition.getContents('Upload');
if (uploadTab) {
var upload = uploadTab.get('upload');
if (upload) upload.label = '画像を選択してください';
var uploadButton = uploadTab.get('uploadButton');
if (uploadButton) uploadButton.label = 'アップロード';
}
var infoTab = dialogDefinition.getContents('info');
if (infoTab) {
infoTab.remove('txtAlt');
infoTab.get('txtUrl')['hidden'] = true;
infoTab.remove('txtHSpace');
infoTab.remove('txtVSpace');
infoTab.remove('txtBorder');
infoTab.remove('cmbAlign');
var browse = infoTab.get('browse');
if (browse) browse.label = 'アップロード済みの画像を選択';
}
// remove unnecessary tabs
if (dialogDefinition.getContents('Link')) dialogDefinition.removeContents('Link');
if (dialogDefinition.getContents('advanced')) dialogDefinition.removeContents('advanced');
}
});
画像のサイズ指定方法を変更
デフォルトだと画像のサイズ指定が、インラインの style
タグで記述される。
<img>
の height
width
属性で指定したい場合は次の設定をする。
CKEDITOR.editorConfig = function (config) {
•••
// 画像のサイズ指定をstyleタグではなく,width,height属性で設定
config.disallowedContent = 'img{width,height}';
config.extraAllowedContent = 'img[width,height]';
};
Turbolinks と組み合わせる
turbolinks:load
イベント内で、<%= f.cktext_area :content %>
によってレンダリングされた全ての HTML を初期化する。
//= require ckeditor/init.js.erb
//= require ./config
$(document).on('turbolinks:load', function () {
$('.ckeditor').each(function () {
CKEDITOR.replace($(this).attr('id'))
});
});
Form の Dirty Check をする
CKEditor の form と、それ以外を両方 Dirty Check するコードを示す。
まず CKEDITOR
に hasDirtyForm
メソッドを追加する。
//= require ckeditor/init.js.erb
//= require ./config
CKEDITOR.hasDirtyForm = function () {
var editors = this.instances;
return Object.keys(editors).some(function (key) {
return editors[key].checkDirty();
});
};
turbolinks:load
で Dirty Check を初期化する。
Form が Dirty で、まだ Submit されていない場合にのみ confirm が表示されるようになる。
$(window)
.on('turbolinks:load', function () {
if (!window.app) window.app = {};
window.app.formDirty = false;
window.app.formSubmit = false;
window.app.dirtyMessage = '保存していない内容が失われますが、よろしいですか';
$(':input').change(function () {
if (!window.app.formDirty) window.app.formDirty = true;
});
$('form').submit(function () {
window.app.formSubmit = true;
});
})
.on("page:before-change turbolinks:before-visit", function () {
if (window.app.formDirty || CKEDITOR.hasDirtyForm()) {
return confirm(window.app.dirtyMessage);
}
})
.bind("beforeunload", function (event) {
if (!window.app.formSubmit &&
(window.app.formDirty || CKEDITOR.hasDirtyForm())) {
event.returnValue = window.app.dirtyMessage;
return event.returnValue;
}
});