SpreadJSデザイナを活用してテンプレート型セルをデザインする

「Excelライク」なスプレッドシートをWeb上で実現するJavaScriptライブラリの「SpreadJS (スプレッドJS)」には、複数のセルからなるセル範囲を単一のセル型として表示する「テンプレート型セル」という機能があります。この機能を使うことで、データソースの複数の項目を1つのセルとして表示することが可能になります。

さらにSpreadJSには「SpreadJSデザイナ」というスタンドアロンのデスクトップアプリケーションが付属しており、スプレッドシートの設計やデータの追加のほか、ファイルを保存し、アプリケーションまたは他のリソース内にロードすることができます。

本記事では、SpreadJSデザイナを活用してテンプレート型セルをデザインし、実行時にそれを読み込んで使用する方法をご紹介します。

開発環境の準備とSpreadJSの参照

この記事では以下の開発環境を使用します。

使用するファイルは次の4つです。

index.htmlページ本体。ページの要素としてSpreadJSを配置します
app.jsSpreadJSを作成するコードを記載します
template.jsSpreadJSデザイナで作成したテンプレート型セル用のテンプレートです
style.css各種ページ要素のスタイル定義を記載します
SpreadJSの機能を使うために必要となるSpreadJSのモジュールなどへの参照設定は「index.html」で行います。
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>テンプレート型セル</title>
    <link href="css/gc.spread.sheets.excel2013white.16.2.6.css" rel="stylesheet" />
    <link href="css/style.css" rel="stylesheet" />
    <script src="scripts/gc.spread.sheets.all.16.2.6.min.js"></script>
    <script src="scripts/resources/gc.spread.sheets.resources.ja.16.2.6.min.js"></script>
    <script src="scripts/app.js"></script>
    <script src="scripts/template.js"></script>
</head>
<body>
    <div id="ss"></div>
</body>
</html>

また、ページ上の要素のスタイルを「styles.css」で定義します。

#ss {
  width: 900px;
  height: 700px;
}

SpreadJSデザイナでテンプレートの作成

ここでは、テンプレートとして使用するA1セルからB3セルまでの属性とシートバインディングについて説明します。ライセンスの適用などのSpreadJSデザイナの基本的な使用方法についてはヘルプをご覧いただければと思います。

SpreadJSデザイナを起動したら、最初にシートタブをダブルクリックしてシート名を「Template」に変更しておきます。また、列の幅と行の高さを次のように変更します。

対象の項目設定値
A列の幅400
B列の幅300
第1行の高さ30
第2行の高さ260
第3行の高さ30
サイズの調整

続いてSpreadJSデザイナの「データ」タブの「データバインディング」グループにある「シートバインディング」をクリックして「フィールドリスト」を表示します。そして、フィールドリストの「データソース」にマウスポインタを配置したときに表示される「+」をクリックして項目を4つ追加します。

追加した項目の属性は以下の表のように設定します。第1項目では、項目にマウスポインタを配置したときに表示される「歯車」のアイコンをクリックして名称を「img」とし、「▼」アイコンをクリックして表示されたリストで「ハイパーリンク」を選択します。

対象の項目名称オプション
第1項目imgハイパーリンク
第2項目name文字列(既定値)
第3項目desc文字列(既定値)
第4項目types文字列(既定値)
フィールドリスト

次に、作成したフィールドリストをセルに関連付けます。まずA1、A2、A3セルを選択し、リボンメニューの「結合して中央揃え」で1つのセルに結合し、結合されたA1セルを選択してからフィールドリストの「img」項目をダブルクリックします。すると、A1セルに[img]と表示され、「img」項目がA1セルに関連付けられます。

B1からB3までのセルについても下記のようにフィールドリストを関連付けます。

セルフィールドリスト
A1(A1、A2、A3セルを結合)img
B1name
B2desc
B3types

さらにセルを右クリックして表示される「セルの書式設定」や「セル型の設定」のメニューから、各セルの属性を次のように設定します。

セル属性設定値
A1「セル型の設定」-「ハイパーリンク型セル」「テキスト」の文字列を削除
B1「文字の配置」-「水平方向」中央揃え
「文字の配置」-「垂直方向」中央揃え
「フォント」-「スタイル」太字
「フォント」-「サイズ」16
B2「文字の配置」-「水平方向」中央揃え
「文字の配置」-「垂直方向」中央揃え
「文字の制御」-「折り返して全体を表示する」チェック
B3「文字の配置」-「水平方向」中央揃え
「文字の配置」-「垂直方向」中央揃え
「フォント」-「スタイル」太字

※ 下の画像は、ハイパーリンク型セルでテキストを削除する箇所を示しています。

ハイパーリンク型セルの設定

さらにテンプレートとするA1からB3までの範囲がわかりやすように青色の罫線で囲うと、以下のようになります。

完成したテンプレート

最後に、作成したテンプレートを「template.js」ファイルとして保存します。このとき「エクスポートするシート」を「Template」に設定しておきます。

エクスポート

なお、JavaScriptファイルはSpreadJSデザイナに読み込めないので、テンプレートを変更・修正するときのためにSpreadJSファイルまたはJSONファイルとしても保存しておくと便利です。

テンプレートの読み込みとテンプレート型セルの作成

「template.js」ファイルを読み込んでテンプレート型セルを作成するコードは次のようになります。この段階では、作成したテンプレート型セルの設定やデータの連結を行う部分がまだ実装されていないので、実行してもSpreadJS上には何も表示されません。
※ ライセンスキーを設定しない場合トライアル版を示すメッセージが表示されます。ライセンスキーの設定方法についてはこちらをご覧ください。

// ライセンスキーとカルチャの設定
GC.Spread.Sheets.LicenseKey = 'ここにSpreadJSのライセンスキーを設定します';
GC.Spread.Common.CultureManager.culture('ja-jp');

// SpreadJSの設定
document.addEventListener("DOMContentLoaded", function () {
    const spread = new GC.Spread.Sheets.Workbook("ss");
    SetTemplateCell(spread);
});

async function SetTemplateCell(spread) {
    // テンプレートの読込とシートの設定
    spread.fromJSON(template);
    spread.addSheet(0, new GC.Spread.Sheets.Worksheet("Render"));
    const templateSheet = spread.getSheet(1);
    templateSheet.setFormatter(0, 0, "=IMAGE(@)"); // 画像の表示

    // テンプレート型セルの作成
    const rangeTemplateCell = new GC.Spread.Sheets.CellTypes.RangeTemplate(
        templateSheet, 0, 0, 3, 2
    );
}

WorkbookクラスのfromJSONメソッドの引数に「template.js」ファイルの拡張子を除いたファイル名を設定することで、SpreadJSデザイナで作成したテンプレートをSpreadJSに読み込みます。読み込んだ「Template」シートを使ってテンプレート型セルを作成します。

WorkbookクラスのaddSheetメソッドで追加している「Render」シートは、このアプリケーションのメインのシートになります。

次の行では「Template」シート(変数名:templateSheet)で第3引数に=IMAGE(@)を設定してsetFormatterメソッドを呼び出していますが、これによってハイパーリンク型セルに保持しているURLから画像を取得して表示できるようになります。ここで設定している「IMAGE」はイメージスパークライン関数です。ハイパーリンク型セルには数式(関数)を設定できないのでsetFormatterメソッドを使って書式として設定しています。なお、この手法はデモのテンプレート型セルで使用しているものです。

テンプレートの設定をすべてSpreadJSデザイナで行いたい場合には次のような方法もあります。この方法ではsetFormatterメソッドを使う必要がなくなります。

  1. 別のセル(XXX)にハイパーリンクのフィールドリストを関連付ける
  2. 画像を表示するセルにはフィールドリストを何も関連付けない
  3. 画像を表示するセルに=IMAGE(XXX)という数式を設定する
  4. 別のセル(XXX)を含む行を非表示にする

最後のところでRangeTemplateクラスのコンストラクタを使って、指定したシートとセル範囲からテンプレート型セルを作成しています。

APIからのデータ取得

テンプレート型セルにデータを連結する前に、その対象となるデータを取得しておきます。

今回は、Web上でポケモンの各種データを公開しているPokéAPIからデータを取得します。このPokéAPIの使用方法については、公開されているドキュメントなどをご参照いただければと思います。
※ ポケットモンスター・ポケモン・Pokémonは任天堂・クリーチャーズ・ゲームフリークの登録商標または商標です。

最初に作成した「app.js」に下記のコードを追加して表示するデータを取得します。

// ライセンスキーとカルチャの設定
GC.Spread.Sheets.LicenseKey = 'ここにSpreadJSのライセンスキーを設定します';
GC.Spread.Common.CultureManager.culture('ja-jp');

const pokemonCount = 100; // 取得するポケモンデータの数
let data = [];// 取得したデータを格納する配列

・・・(中略)・・・

async function SetTemplateCell(spread) {
    // ポケモンデータの取得
    for (let i = 1; i <= pokemonCount; i++) {
        await getPokemon(i);
    }

    // テンプレートの読込とシートの設定
    spread.fromJSON(template);
    ・・・(中略)・・・
}

// PokéAPIデータの取得
async function getPokemon(num) {
    const url = `https://pokeapi.co/api/v2/pokemon/${num}`;
    let res = await fetch(url);
    const pokemon = await res.json();
    const pokemonImg = pokemon.sprites.other["official-artwork"].front_default;
    let pokemonType = await Promise.all(
        pokemon.types.map(async (item) => {
            res = await fetch(item.type.url);
            const typeInfo = await res.json();
            const typeName = typeInfo.names.find(name => name.language.name === "ja").name;
            return typeName;
        })
    );
    pokemonType = `タイプ: ${pokemonType.join(" / ")}`;
    res = await fetch(pokemon.species.url);
    const pokemonSpecies = await res.json();
    const pokemonName = pokemonSpecies.names.find(name => name.language.name === "ja").name;
    const pokemonDesc = pokemonSpecies.flavor_text_entries.find(item => item.language.name === "ja").flavor_text;
    data.push({
        img: pokemonImg,
        name: pokemonName,
        desc: pokemonDesc,
        types: pokemonType
    });
}

上記のgetPokemon関数の最後の部分にあるdata.pushメソッドでポケモンのデータを格納する配列dataにポケモンの画像・名前・説明・タイプを設定しています。配列要素の各プロパティ名は、テンプレート型セルのフィールドリストの項目名になっています。

テンプレート型セルにデータの連結

いよいよテンプレート型セルにデータを連結するときがきました。

データを表示する「Render」シート(変数名:renderSheet)を設定してデータを連結しましょう。具体的なコードは次のとおりです。

・・・(中略)・・・
async function SetTemplateCell(spread) {
    ・・・(中略)・・・
    // テンプレート型セルの作成
    const rangeTemplateCell = new GC.Spread.Sheets.CellTypes.RangeTemplate(
        templateSheet, 0, 0, 3, 2
    );

    // データの設定
    const renderSheet = spread.getSheet(0);
    renderSheet.defaults.rowHeight = 340;
    renderSheet.getCell(0, 0, GC.Spread.Sheets.SheetArea.colHeader).font("bold 18pt Meiryo")
    renderSheet.setRowHeight(0, 40, GC.Spread.Sheets.SheetArea.colHeader);
    renderSheet.autoGenerateColumns = false;
    renderSheet.setDataSource(data);
    const pokemonColumn = {
        displayName: "Pokémon",
        size: templateSheet.getColumnWidth(0) + templateSheet.getColumnWidth(1),
        value: function (item) {
            return item;
        },
        cellType: rangeTemplateCell
    };
    renderSheet.bindColumn(0, pokemonColumn);
}
・・・(中略)・・・

WorksheetクラスのsetDataSourceメソッドにポケモンのデータを格納したdataを渡してデータ連結しています。dataの各要素は、pokemonColumnというIColumnインタフェースのvalueプロパティに設定された関数によってテンプレート型セルに渡されます。また、cellTypeプロパティで列のセル型をテンプレート型セルに設定しています。

このpokemonColumnをWorksheetクラスのbindColumnメソッドの第2引数に渡すことでデータ連結の設定が完了します。

以上でポケモンのデータをSpreadJS上に表示できるようになりました。作成したページを表示すると次のようになります。
※ 画像としての保存は著作権の問題があるので一部マスク処理をしています。実際の動作は後述のデモアプリケーションをご確認ください。

アプリケーションの実行

スクロール単位の調節とローディング画面の追加

このままでもポケモンのデータを表示することはできますが、マウスホイールを操作したときに1項目ごとにスクロールできると便利です。スクロールの動作は、Workbookクラスのoptionsプロパティのスクロール関連オプションを使って変更できます。

また、pokemonCountを200などの大きな数にすると、APIからのデータ取得完了まですごく時間がかかり、SpreadJSのシートが初期状態のままでかなりの時間待たされます。待っている間、ユーザーが不安にならないように、データを読み込んでいることを知らせる仕組みが欲しいところです。ここでは、HTMLに専用の要素を追加してCSSのアニメーション機能を使うことでローディング画面を表示します。また、データの取得が完了した時点で、ローディング画面をフェードアウトするようにapp.jsにコードを追加します。

これらは、本記事の主題ではないので詳しい説明は省きますが、これまでのコードを下記のように変更して動作をご確認いただければと思います。

・・・(中略)・・・
    <div id="loader">
        <div class="caption">データを読み込んでいます。</div>
        <div class="spinner"></div>
    </div>
    <div id="ss"></div>
・・・(中略)・・・
#ss {
  width: 700px;
  height: 600px;
  display: none;
}

#loader {
  width: 100%;
  height: 95vh;
  background-color: #D3A96D;
}

#loader.loaded {
  animation: fadeOut 0.5s forwards;
}

.caption{
  width: fit-content;
  height: 50px;
  margin: 0 auto;
  position: relative;
  top: 60%;
  font-size: large;
}

.spinner {
  width: 80px;
  height: 80px;
  background-color: #697683;
  /* background-color: #00203D; */
  margin: 0 auto;
  position: relative;
  top: 30%;
  -webkit-animation: sk-rotateplane 1.5s infinite ease-in-out;
  animation: sk-rotateplane 1.5s infinite ease-in-out;
}

@keyframes fadeOut {
  from {
    opacity: 1;
  }

  to {
    display: none;
    opacity: 0;
    z-index: -1;
  }
}

@-webkit-keyframes sk-rotateplane {
  0% {
    -webkit-transform: perspective(120px)
  }

  50% {
    -webkit-transform: perspective(120px) rotateY(180deg)
  }

  100% {
    -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg)
  }
}

@keyframes sk-rotateplane {
  0% {
    transform: perspective(120px) rotateX(0deg) rotateY(0deg);
    -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg)
  }

  50% {
    transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
    -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)
  }

  100% {
    transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
    -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
  }
}
・・・(中略)・・・
async function SetTemplateCell(spread) {
    ・・・(中略)・・・
    renderSheet.bindColumn(0, pokemonColumn);

    // スクロール動作の設定:マウスホイールで1行ずつスクロールするように設定
    spread.options.scrollByPixel = true;
    spread.options.scrollPixel = renderSheet.getRowHeight(0) / 5; // deltaY = 5を想定
    spread.options.scrollbarMaxAlign = true;

    // ローディング画面の終了処理
    const loader = document.querySelector("#loader");
    loader.classList.add("loaded");
    const host = spread.getHost();
    host.style.display = "block ";
}
・・・(中略)・・・

再度実行すると以下のようにデータ読込完了まではローディング画面が表示され、さらに1項目ごとにスクロールを行うことができます。

実際の動作は以下のデモアプリケーションでご確認ください(“Run Project”をクリックするとデモが起動します)。

テンプレート型セルの利用方法について解説しているヘルプ、APIリファレンス、およびデモを再掲しますので、ご参考にしていただければと思います。

さいごに

今回はSpreadJSデザイナで作成したテンプレートからテンプレート型セルを生成し、それを使ってWeb上のデータを効率的に表示する方法を紹介いたしました。

SpreadJSはこの他にもフィルタ、表計算、チャート、条件付き書式、ピボットテーブルなどのExcel互換機能を豊富に搭載しています。エンドユーザーに馴染みのあるExcelライクな操作性を提供するSpreadJSをご検討いただけますと幸いです。

製品サイトでは、SpreadJSの機能を手軽に体験できるデモアプリケーションやトライアル版も公開しておりますので、こちらもご確認ください。

また、ご導入前の製品に関するご相談、ご導入後の各種サービスに関するご質問など、お気軽にお問合せください。

\  この記事をシェアする  /