エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

スプレッドシートライブラリHandsontableの1セルで構造化されたデータを扱う

エンジニアリンググループでアンケートを作るためのシステムを開発している岩本です。

エムスリーが実施するアンケートでは、薬剤ごとの処方数や患者数などを入力してもらうために、下記のように表形式で数値入力を大量に入力してもらうことが頻発します。 f:id:cpw:20190328103315p:plain

上図のように5 * 10程度の表となることも珍しくありませんし、それが1アンケート中に複数ページでてきます。 また、入力値のバリデーションを定義しますが、全て同一ではなく、少しだけ異なっているため、全て同一の定義とすることもできません。

アンケートを作成する人は、このバリデーションの定義をしないといけないのですが、普通のWebシステムのインタフェースだとかなり辛いものがあります。 マウスでぽちぽち5 * 10もの定義をひたすら繰り返すことも現実的ではありません。

そこで、スプレッドシートUIライブラリのHandsontableで大量のバリデーション定義を効率化したので、その紹介をします。

Handsontableとは

HandsontableとはスプレッドシートのJavaScriptライブラリです。GoogleのスプレッドシートのようなものをWebシステム上に実装することができます。

なお、バージョン6まではCommercial利用でもCommunity Editionで無償で利用できたのですが、バージョン7から有償プランが必要になってしまいました。

実現したもの

まずは下記の動画を参照ください。 f:id:cpw:20190326170529g:plain

主な機能として下記を実装しています。

  • 個々のセルのバリデーション定義を設定可能
  • バリデーションのコピペが可能。複数セル選択してのコピペも可能
  • コピーしたセルのバリデーション定義を変更することも可能
  • 同一のバリデーションは同一であることが一目でわかる
  • 入力フォームの見た目とバリデーション定義の表の配置を一致

実現方法

上記で実現した機能をどのように実現したかを紹介します。

データ構造

f:id:cpw:20190327111934p:plain

上記のような設定を行った場合、実際のデータは下記のように定義されています。

行1列1のバリデーション定義

        {
          _id: 'cjtqk1ikp000n3g6hjbtjgzw6',
          validationTypeInQuestion: 1,
          numberValidations: [
            {
              _id: 'cjtqk1jb3000o3g6h0s89quda',
              value: '1',
              operator: '=='
            }
          ]
        }

行1列2のバリデーション定義

        {
          _id: 'cjtqke6uw000t3g6h9l8u35ce',
          validationTypeInQuestion: 1,
          numberValidations: [
            {
              _id: 'cjtqke6uw000u3g6h77wua9gp',
              value: '1',
              operator: '=='
            }
          ]
        }

行2列2のバリデーション定義

        {
          _id: 'cjtqkyqah000v3g6h18twvoql',
          validationTypeInQuestion: 2,
          numberValidations: [
            {
              _id: 'cjtqkyr05000w3g6h32fytoct',
              value: '3',
              operator: '<='
            }
          ]
        }

Handsontableに入力するデータは下記のように設定します。

[
  ["cjtqk1ikp000n3g6hjbtjgzw6", "cjtqke6uw000t3g6h9l8u35ce"],
  ["cjtqkyqah000v3g6h18twvoql", null]]
]

各セルのデータはバリデーション定義のIDです。このように構造化されたデータのIDを指定しておけばコピーやペースト処理も通常とあまりかわらずに実行することができます。

データの表示

上記のデータをhandsontableのrendererで表示します。 同じデータはユーザが判断できるように、「制限1」「制限2」などといったラベルを表示することによって実際に値を開かなくてもわかるように工夫しています。 これはvalidationTypeInQuestionに定義されている数字を利用しています。 また、編集ポップオーバーを開くためのボタンもこのrendererで実現します。

  // renderer
  validationRenderer(instance, td, row, col, prop, numberValidationRuleId) {
    const { survey, page, question } = this.props;
    // 制限のルールを取得する
    const numberValidationRule = question.findNumberValidationRule(numberValidationRuleId);
    const cellId = this.findCellId(row, col);
    // 設定ボタン
    const buttonHtml = `<button type="button" class="btn btn-default btn-xs validation-setting-button" data-cell-id="${cellId}">設定</button>`;
    if (numberValidationRule) {
      // 「制限」のラベルを取得する
      const validationType = numberValidationRule.getValidationTypeInQuestion();
      td.innerHTML = `${buttonHtml} 制限${validationType}`;
    } else {
      td.innerHTML = buttonHtml;
    }
    return td;
  }

データの編集

「編集」ボタンをクリックするとポップオーバーを表示し、その中で編集操作を行います。 ポップオーバーを閉じるタイミングでセルの値を作成したバリデーション定義のIDで更新します。

hotInstance.setDataAtCell(row, col, numberValidationRuleId);

このときに、同じ設問内に設定されているバリデーション定義を再計算し、validationTypeInQuestionの値も更新しておきます。

その他にもコピペの処理などいろいろと細かい点はあるものの大筋の流れは以上です。

実現してみた感想

使用感については、イレギュラーなインタフェースなので直感的に使えるかどうか心配ではありましたが、頻繁に大量のバリデーション設定がされており効率と柔軟性を両立できているようです。

一方で実装する観点から見ると、ユーザの操作タイミングでうまく動かない場合もあり実装しづらいです。また要素の高さも調整しないといけなかったり、本来実装したいところじゃないUI部分の工数がかかってしまうことも難点でした。スプレッドシートを実装する場合には本当に必要な箇所に限って利用したほうが良さそうです。

まとめ

  • スプレッドシートのUIはやはり強い
  • 入れ子になったデータもIDを参照することで通常のデータと同様に扱える
  • 開発は大変。本当に必要な箇所だけで使ったほうが良い

We are Hiring

エムスリーでは自身で手を動かし、技術で医療の課題を解決するエンジニアを募集しています。 この記事(or 他の記事も)を読んで興味を持った方はぜひ下記リンクよりご応募ください!もちろん、Tech Talkの参加、登壇もお待ちしています!