summernote-ext-tableプラグインを使ってテーブル機能を拡張する

Summernote標準のテーブルではセルの結合ができず機能不足感があるので、前回まででカスタマイズしたSummernoteにsummernote-ext-tableを導入する。

動作確認環境

  • jQuery(3.7.1)
  • Bootstrap(5.3.5)
  • Summernote(0.9.1)
    • summernote-cleaner(1.0.9)
    • summernote-ext-table(0.1)

参考:前回までのソース

summernote-ext-tableの取得と導入

summernote-ext-tableをGitHubのリポジトリから取得し、前回までのカスタマイズソースに各ファイルを追加した。

 root
  |-- css
  |    |-- app.css
  |    |-- summernote-bs5-editor-custom.css
  |    |-- summernote-ext-table.css
  |    |-- font
  |         |-- summernote-ext-table.eot
  |         |-- summernote-ext-table.ttf
  |         |-- summernote-ext-table.woff
  |         |-- summernote-ext-table.woff2
  |-- js
  |    |-- summernote-cleaner.js
  |    |-- summernote-init.js
  |-- index.html

追加したsummernote-ext-tableを使うようにsummernote-init.jsの設定を変更する。

js/summernote-init.js



  // Summernote初期化
  $.fn.initSummernote = function (options) {

    // デフォルトオプション
    const defaultOptions = {
      lang: 'ja-JP',
      fontSizes: ['8', '9', '10', '11', '12', '14', '16', '18', '20', '22', '24', '26', '28', '36', '48', '72'],
      toolbar: [ 
        ['style', ['style']],
        ['font', ['fontsize', 'fontsizeunit', 'forecolor', 'backcolor']],
        ['fontstyle', ['bold', 'italic', 'strikethrough', 'underline', 'subscript', 'superscript', 'paragraph', 'clear']],
        ['para', ['ul', 'ol']],
        ['table', ['jTable']],
        ['insert', ['link', 'picture', 'video']],
        ['cleaner', ['cleaner']],
        ['operation', ['undo', 'redo']],
        ['view', ['codeview', 'fullscreen', 'help']],
      ],
      popover: {
        table: [
          ['merge', ['jMerge']],
          ['style', ['jBackcolor', 'jBorderColor', 'jAlign', 'jAddDeleteRowCol']],
          ['info', ['jTableInfo']],
          ['delete', ['jWidthHeightReset', 'deleteTable']],
        ]
      },
      styleTags: [
        {tag: 'h1', title: '主見出し', style: 'margin: 4px 0;', className: 'headline', value: 'h1'},
        {tag: 'h2', title: '見出し', style: 'margin: 4px 0;', className: 'headline', value: 'h2'},
        {tag: 'h3', title: '小見出し', style: 'margin: 4px 0;', className: 'headline', value: 'h3'},
      ],
      jTable: {
        mergeMode: 'drag'
      },
      cleaner: {
        action: 'both',


その後、index.htmlにsummernote-ext-tableの読み込み設定を追加する。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>Summernoteカスタマイズ</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" type="text/css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/summernote@0.9.1/dist/summernote-bs5.min.css" type="text/css" />
    <link rel="stylesheet" href="./css/summernote-ext-table.css" type="text/css" />
    <link rel="stylesheet" href="./css/app.css" type="text/css" />
    <link rel="stylesheet" href="./css/summernote-bs5-editor-custom.css" type="text/css" />

    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js" type="text/javascript"></script>
    <script src="https://cdn.jsdelivr.net/npm/summernote@0.9.1/dist/summernote-bs5.min.js" type="text/javascript"></script>
    <script src="https://cdn.jsdelivr.net/npm/summernote@0.9.1/dist/lang/summernote-ja-JP.min.js" type="text/javascript"></script>
    <script src="./js/summernote-cleaner.js" type="text/javascript"></script>
    <script src="./js/summernote-ext-table.js" type="text/javascript"></script>
    <script src="./js/summernote-init.js" type="text/javascript"></script>


この状態で利用してみようとしたが、表示がバグってしまっておりまともに使えなかった。
プラグインを修正して使えるようにしていく。

summernote-ext-tableの修正

修正前の事前準備

プラグインのソースにライセンス表記が無いのでGitHubでライセンスを確認し追記する。
また、今回はプラグイン自体に手を入れるため、v0.1aとしてコメントを追加しておいた。

js/summernote-ext-table.js
/*! summernote-ext-table v0.1 | MIT License | github.com/ksy11/summernote-ext-table */
/*!  v0.1a | MIT License | blog.ossan9999.com */
(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);


css/summernote-ext-table.css
/*! summernote-ext-table v0.1 | MIT License | github.com/ksy11/summernote-ext-table */
/*! v0.1a | MIT License | blog.ossan9999.com */
@font-face {
    font-family: "summernoteexttable";
    font-style: normal;
    font-weight: 400;


テーブル挿入ポップアップの表示バグ修正

テーブル挿入にjTableを利用すると、以下のように行数/列数選択のポップアップがバグっており、まともに挿入できない。
これをSummernote標準のtable使用時と同じ感じになるように修正する。

jTable使用時の行数/列数選択

Summernote標準のtable使用時の行数/列数選択

これは行数/列数選択のポップアップのdivタグが自己終了タグ(<div />)になっているため、このような表示になってしまっている。ちゃんと</div>で閉じてあげれば治る。
※公式リポジトリの「/examples/table-plugin.html」では自己終了タグになっていても正しく動いているため、Summernoteなどのバージョンアップのせいかも。

js/summernote-ext-table.js



        context.memo('button.jTable', function () {
            return ui.buttonGroup([
                ui.button({
                    className: 'dropdown-toggle',
                    contents : ui.dropdownButtonContents(ui.icon(options.icons.table), options),
                    tooltip  : lang.table.table,
                    container: options.container,
                    data     : {
                        toggle: 'dropdown',
                    },
                }),
                ui.dropdown({
                    title    : lang.table.table,
                    className: 'note-table',
                    // items    : [
                    //     '<div class="note-dimension-picker">',
                    //     '<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"/>',
                    //     '<div class="note-dimension-picker-highlighted"/>',
                    //     '<div class="note-dimension-picker-unhighlighted"/>',
                    //     '</div>',
                    //     '<div class="note-dimension-display">1 x 1</div>',
                    // ].join(''),
                    items    : [
                        '<div class="note-dimension-picker">',
                        '<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>',
                        '<div class="note-dimension-picker-highlighted"></div>',
                        '<div class="note-dimension-picker-unhighlighted"></div>',
                        '</div>',
                        '<div class="note-dimension-display">1 x 1</div>',
                    ].join(''),
                }),


修正後の表示

カラーパレットの表示バグ修正

summernote-ext-tableプラグインの追加機能として、テーブル挿入後のポップアップでセルの背景色や線の色を変更できる機能があるが、カラーパレットの表示が潰れてしまっている。

修正前の表示

正しく表示できるようスタイルを追加する。

css/summernote-ext-table.css




.note-editor .note-popover .note-color .note-dropdown-menu .note-palette:first-child {
    margin: 0 5px;
}

.note-editor .note-popover .note-color .note-dropdown-menu .note-palette {
    display: inline-block;
    margin: 0;
    width: 160px;
}

.note-editor .note-popover .note-color .note-dropdown-menu .note-palette .note-palette-title {
    border-bottom: 1px solid #eee;
    font-size: 12px;
    margin: 2px 7px;
    text-align: center;
}

.note-editor .note-popover .note-color .note-dropdown-menu .note-palette .note-color-reset:hover, .note-editor .note-popover .note-color .note-dropdown-menu .note-palette .note-color-select:hover {
    background: #eee;
}

.note-editor .note-popover .note-color .note-dropdown-menu .note-palette .note-color-reset, .note-editor .note-popover .note-color .note-dropdown-menu .note-palette .note-color-select {
    border-radius: 5px;
    cursor: pointer;
    font-size: 11px;
    margin: 3px;
    padding: 0 3px;
    width: 100%;
}

.note-editor .note-popover .note-color-palette {
    line-height: 1;
}

.note-editor .note-popover .note-color .note-dropdown-menu .note-palette .note-color-row {
    height: 20px;
}

.note-editor .note-popover .note-color-palette div .note-color-btn {
    border: 0;
    border-radius: 0;
    height: 20px;
    margin: 0;
    padding: 0;
    width: 20px;
}

.note-editor .note-popover .note-color .note-dropdown-menu .note-palette .note-color-select-btn {
    display: none;
}

.note-editor .note-popover .note-color .note-dropdown-menu .note-palette .note-holder-custom .note-color-btn {
    border: 1px solid #eee;
}

.note-popover .note-popover-content >.note-btn-group {
    margin-left: 0;
    margin-right: 5px;
    margin-top: 5px;
}

修正後の表示

ドロップダウンの余白調整

テーブル挿入後のポップアップで行追加/削除などのメニューがドロップダウンで選択できるが、左右の余白が無いので余白を持たせる。

修正前の表示

以下のようにスタイルを修正する。以下の3つのドロップダウンに対して余白を追加した。

  • 結合
  • 文字位置
  • 行追加/削除
css/summernote-ext-table.css



.jtable-cell-split-dropdown {
    /* width: 36px; */
    width: 42px;
    padding-left: 2px;
    min-width: auto !important;
}

.jtable-cell-split-button-group {
    margin: 0px !important;
}

.jtable-add-del-row-col-dropdown {
    /* width: 145px; */
    width: 147px;
    padding-left: 2px;
    min-width: auto !important;
}

.jtable-add-row-col-button-group {
    margin: 0px !important;
}

.jtable-del-row-col-button-group {
    margin: 3px 0px 0px 0px !important;
}

.jtable-align-dropdown {
    /* width: 142px; */
    width: 146px;
    padding-left: 2px;
    padding-right: 2px;
    min-width: auto !important;
}


修正後の表示

セル結合/結合解除のバグ(修正断念)

セル結合/結合解除操作には結構バグが潜んでいそうで少し確認しただけでも以下のバグがあった。

セル結合時のバグ例(謎の列ができる)

セル結合解除時のバグ例(中途半端に結合解除される)

これを直すのは骨が折れるため、以下のようにして結合機能を無効化した。

js/summernote-init.js



  // Summernote初期化
  $.fn.initSummernote = function (options) {

    // デフォルトオプション
    const defaultOptions = {
      lang: 'ja-JP',
      fontSizes: ['8', '9', '10', '11', '12', '14', '16', '18', '20', '22', '24', '26', '28', '36', '48', '72'],
      toolbar: [ 
        ['style', ['style']],
        ['font', ['fontsize', 'fontsizeunit', 'forecolor', 'backcolor']],
        ['fontstyle', ['bold', 'italic', 'strikethrough', 'underline', 'subscript', 'superscript', 'paragraph', 'clear']],
        ['para', ['ul', 'ol']],
        ['table', ['jTable']],
        ['insert', ['link', 'picture', 'video']],
        ['cleaner', ['cleaner']],
        ['operation', ['undo', 'redo']],
        ['view', ['codeview', 'fullscreen', 'help']],
      ],
      popover: {
        table: [
          //['merge', ['jMerge']],
          ['style', ['jBackcolor', 'jBorderColor', 'jAlign', 'jAddDeleteRowCol']],
          ['info', ['jTableInfo']],
          ['delete', ['jWidthHeightReset', 'deleteTable']],
        ]
      },
      styleTags: [
        {tag: 'h1', title: '主見出し', style: 'margin: 4px 0;', className: 'headline', value: 'h1'},
        {tag: 'h2', title: '見出し', style: 'margin: 4px 0;', className: 'headline', value: 'h2'},
        {tag: 'h3', title: '小見出し', style: 'margin: 4px 0;', className: 'headline', value: 'h3'},
      ],
      //jTable: {
      //  mergeMode: 'drag'
      //},
      cleaner: {
        action: 'both',


ドラッグした領域の表示が残るバグ

テーブルのセルをドラッグするとドラッグした領域に色が付くが、色が付いた状態でスクロールバーを動かすと、色が付いた領域が追従してくる。
害はないが気持ち悪いためスクロール時に消えるように修正する(summernote-init.jsで対応)。

以下のようにSummernoteエディタのscrollイベントでドラッグした領域を消すようにした。

js/summernote-init.js



    // オプションをマージ
    settings = $.extend(true, {}, defaultOptions, options);

    // Summernoteを初期化
    $(this).summernote(settings);

    // イベントの登録
		$editor = $(this).next('.note-editor');

    // jTableの範囲選択ブロックが残ってしまう対策(エディタがスクロールした際にブロックを消す)
		$editor.find('.note-editable').on('scroll', function() {
			$editor.find('.jtable-block').removeAttr('style');
		});

    return $(this);
  };
})(jQuery);

日本語化する

以下のツールチップのように中途半端な状態で日本語化されてしまっているので、しっかり日本語化する。

summernote-ext-table.jsには英語、韓国語の言語設定が用意されているため、そこに日本語の設定を追加した。

js/summernote-init.js



    $.extend(true, $.summernote.lang, {
        'en-US': {
            jTable: {
                borderColor    : 'border color',
                merge          : {
                    merge  : 'cell merge',
                    colspan: 'colspan',
                    rowspan: 'rowspan',
                    split  : 'cell split',
                },
                align          : {
                    top     : 'top',
                    middle  : 'middle',
                    bottom  : 'bottom',
                    baseline: 'baseline',
                },
                info           : {
                    info  : 'table info',
                    margin: 'margin'
                },
                apply          : 'apply',
                addDeleteRowCOl: 'Row/Col(Add/Del)',
                areaReset      : 'area Reset',
                message        : '<b>Available after unmerge<br/>current or surrounding cells</br>',
            }
        },
        'ko-KR': {
            jTable: {
                borderColor    : '선색',
                merge          : {
                    merge  : '셀 합치기',
                    colspan: '가로',
                    rowspan: '세로',
                    split  : '셀 나누기',
                },
                align          : {
                    top     : '위쪽 정렬',
                    middle  : '가운데 정렬',
                    bottom  : '아래쪽 정렬',
                    baseline: '기본 정렬',
                },
                info           : {
                    info  : '테이블 정보',
                    margin: '여백'
                },
                apply          : '적용',
                addDeleteRowCOl: '행/열(추가/삭제)',
                areaReset      : '넓이/높이 초기화',
                message        : '<b>현재 또는 주위 셀<br/>병합 해제 후 사용 가능</b>',
            }
        },
        'ja-JP': {
            jTable: {
                borderColor    : '線の色',
                merge          : {
                    merge  : 'セルの結合',
                    colspan: '列数',
                    rowspan: '行数',
                    split  : 'セルの分割',
                },
                align          : {
                    top     : '上揃え',
                    middle  : '中央揃え',
                    bottom  : '下揃え',
                    baseline: 'デフォルト',
                },
                info           : {
                    info  : 'テーブル情報',
                    margin: 'マージン設定'
                },
                apply          : '適用',
                addDeleteRowCOl: '行・列の追加/削除',
                areaReset      : 'サイズのリセット',
                message        : '<div style="width:200px;text-align:left">現在のセルまたは周囲のセルの結合を解除した後に使用可能になります。</div>',
            }
        },
    });
    $.extend(true, $.summernote, {
        plugins: {
            jTable: JTablePlugin,
        },
    });
}));

修正後の表示

カラーパレットの文言不備

線の色のカラーパレットを表示しているのに、タイトルが何故か「背景色」になっている。
また、透明ボタンの文言も「透明」ではなく、フォントのカラーパレット同様、「標準に戻す」が正しいので合わせて修正する。

カラーパレットを表示する関数を以下のように修正した。

js/summernote-init.js



        self.colorPalette = function (className, tooltip, callbackFnc) {
            const isBackground = className === 'note-color-back'; 
            const palletTitle = isBackground ? lang.color.background : lang.jTable.borderColor;
            const colorResetText = isBackground ? lang.color.transparent : lang.color.resetToDefault;
            return ui.buttonGroup({
                className: 'note-color ' + className,
                children : [
                    ui.button({
                        className: 'note-current-color-button',
                        contents : ui.icon(options.icons.font + ' note-recent-color'),
                        tooltip  : tooltip,
                        container: options.container,
                        click    : function (e) {
                            const $button = $(e.currentTarget);
                            const value = $button.attr('data-backColor');
                            callbackFnc(value);
                        },
                        callback : function ($button) {
                            const $recentColor = $button.find('.note-recent-color');
                            $recentColor.css('background-color', className == 'note-color-table-border' ? '#000000' : options.colorButton.backColor);
                            $button.attr('data-backColor', className == 'note-color-table-border' ? '#000000' : options.colorButton.backColor);
                            $recentColor.css('color', 'transparent');
                        },
                    }),
                    ui.button({
                        className: 'dropdown-toggle',
                        contents : ui.dropdownButtonContents('', options),
                        tooltip  : lang.color.more,
                        container: options.container,
                        data     : {
                            toggle: 'dropdown',
                        },
                    }),
                    ui.dropdown({
                        items   : ([
                            '<div class="note-palette">',
                            //'<div class="note-palette-title">' + lang.color.background + '</div>',
                            '<div class="note-palette-title">' + palletTitle + '</div>',
                            '<div>',
                            '<button type="button" class="note-color-reset btn btn-light" data-event="backColor" data-value="inherit">',
                            //lang.color.transparent,
                            colorResetText,
                            '</button>',
                            '</div>',
                            '<div class="note-holder" data-event="backColor"/>',
                            '<div>',
                            // '<button type="button" class="note-color-select btn btn-light" data-event="openPalette" data-value="backColorPicker">',
                            // lang.color.cpSelect,
                            // '</button>',
                            // '<input type="color" id="backColorPicker" class="note-btn note-color-select-btn" value="' + options.colorButton.backColor + '" data-event="backColorPalette">',
                            // '</div>',
                            // '<div class="note-holder-custom" id="backColorPalette" data-event="backColor"/>',
                            // '</div>',
                        ].join('')),
                        callback: function ($dropdown) {


修正後の表示

線の色の設定バグ(標準に戻すボタンが効かない)

先程、文言を「標準に戻す」にした線の色の透明ボタンを押下しても効かなかった。
線の色を変更すると、td, thタグに「border: 1px solid 色」を設定する挙動になっているが、透明ボタンを押してもこれが反映されていないようだ。

線の色を黒に変更後、標準に戻すボタンを押下した際のHTMLの状態

これを正しく反映されるように修正する。また、「1px solid」の指定も不要なのでそれも考慮して修正する。
標準に戻すボタンを押下した際(backColor = ‘inherit’時)に線の色変更時に付与したスタイルを削除するようにした。

js/summernote-init.js



        self.jBorderColor = function (backColor) {
            self.beforeCommand();

            var cell = tableBlock.currentTdEl;
            var $cell = $(cell);
            // $cell.closest('table').find('td, th').css('border', '1px solid ' + backColor);
            var $table = $cell.closest('table');
            if (backColor == 'inherit') {
                $table.css('border-color', '');
                $table.css('outline-color', '');
                $table.find('td, th').css('border-color', '');
            } else {
                $table.css('border-color', backColor);
                $table.css('outline-color', backColor);
                $table.find('td, th').css('border-color', backColor);
            }

            resetTableBlock($cell);

            self.afterCommand();
        };


修正後のHTML状態

Uncaught TypeErrorの対処

セルの色を変更するなど、テーブル周りの操作をしていると「Uncaught TypeError: Cannot read properties of undefined (reading ‘rows’)」というエラーが発生し、選択した操作が実行できない場合がある。

発生場所を調べてみると、createVirtualTable関数の「const rows = domTable.rows;」のところでエラーになってた。どうやらdomTableがnullになってしまうパターンがあるようなので、以下のようにnullをブロックするよう対処した。

js/summernote-init.js



        function createVirtualTable() {
            if (domTable == null) return;
            const rows = domTable.rows;
            for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
                const cells = rows[rowIndex].cells;
                for (let cellIndex = 0; cellIndex < cells.length; cellIndex++) {
                    addCellInfoToVirtual(rows[rowIndex], cells[cellIndex]);
                }
            }
        }


また、createVirtualTable関数の少し下になるcreateMatrixTable関数でも似たようなことをしていたため、念のため同様の処理を追加しておいた。

js/summernote-init.js



        function createMatrixTable() {
            if (domTable == null) return;
            const rows = domTable.rows;
            for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
                const cells = rows[rowIndex].cells;
                for (let cellIndex = 0; cellIndex < cells.length; cellIndex++) {
                    addCellInfoToMatrix(rows[rowIndex], cells[cellIndex]);
                }
            }
        }


修正結果

次回は当プラグインに少し機能を追加しようと思う。

コメント