1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TypeScriptAdvent Calendar 2022

Day 10

Excelデータベースをブラウザで絞り込み検索

Last updated at Posted at 2022-06-06

概要

  • サンプルデータの準備
  • バックエンド実装
  • HTMLテンプレート
  • フロントエンド実装

モチベーション

Excelで管理されているデータベース()のデータをFZFライクに絞り込み検索できるようにします。
全員のPC内にfzfやらターミナルやらxlsx2csvのようなツールが入っていないので、ブラウザの表示を通してサーバー経由でExcelのFuzzy検索をできるようにします。

demo-fzf.gif
FZF本家の実行画面。ファイルを絞り込み検索しています。

完成形

demo-excel-fzf.gif
Excelデータをブラウザ上で絞り込み検索

ディレクトリ構成

.
├── data
│   └── sample.xlsx
├── main.go
├── go.mod
├── go.sum
├── template
│   └── index.tmpl
└── static
    ├── main.js
    ├── main.ts
    ├── node_modules
    ├── package-lock.json
    ├── package.json
    └── tsconfig.json

サンプルデータの準備

Sample excel data for analysisxlsxファイルをいただきました。
ダウンロードしてdata/sample.xlsx へ入れておきます。

バックエンド実装

Go言語のginというフレームワークでRESTFUL APIサーバーを立てます。

また、Excelデータベース()を解析するため、excelizeというライブラリも使用しますので、あらかじめインストールしておきます。

$ go install github.com/gin-gonic/gin
$ go install github.com/xuri/excelize/v2

main.goはこちらです。
簡単なサンプルを目指すため、バックエンドはmain.go の1ファイルのみです。

main.go
package main

import (
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/xuri/excelize/v2"
)

var lines []string

// Excelデータ読み込み
// 時間がかかるとmain関数へなかなか行かない、
// つまりブラウザのページ読み込みがストップしてしまうので、
// init()の中身は非同期処理で裏で読み込みをかけておきます。
// ページを読み込んだ時点でのデータを配信するので、
// 不完全なデータになる場合もあります。
// そういうときは、ブラウザ上でリロードかけると
// 最新のデータを配信してくれます。
func init() {
	go func() {
		path := "./data/sample.xlsx"
		f, _ := excelize.OpenFile(path)
		defer f.Close()
		// 行ごとに読み込み
		// 2次元配列で返す
		rows, _ := f.GetRows("Sheet1")
		lines = make([]string, len(rows))
		for i, row := range rows {
			lines[i] = strings.Join(row, " ")
		}
	}()
}

// サーバー立ち上げ
func main() {
	r := gin.Default()
	r.Static("/static", "./static")
	r.LoadHTMLGlob("template/*.tmpl")
	// エントリポイント
	r.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.tmpl", gin.H{})
	})
	// list形式のJSONを配信するAPI
	r.GET("/list", func(c *gin.Context) {
		c.IndentedJSON(http.StatusOK, lines)
	})
	r.Run()
}

main.goを書いたら、プロジェクトルートディレクトリにて$ go mod init && go mod tidyしてgo.mod, go.sumを生成します。

HTMLテンプレート

フロントエンドはJavaScript(TypeScript)で動作をつけるため、HTMLは簡単なものにします。

template/index.tmpl
<html lang="ja">
<body>
  <label for="search-form">Excel検索</label>
  <!-- ここで検索 -->
  <input type="text" name="search-form" id="search-form" placeholder="検索キーワードを入力" >
  <!-- ここに結果を表示 -->
  <div id="search-result" ></div>
  <script type="module" src="/static/main.js"></script>
</body>
</html>

input要素で検索ワードを入力し、divに結果を表示します。
Goのmain関数の r.LoadHTMLGlob("template/*.tmpl")で読み込み指定しています。
<script type="module" src="/static/main.js"></script>でstatic/main.jsを読み込みます。

フロントエンド実装

Fuzzy検索を実装していきます。
staticにmain.tsを書きます。
staticディレクトリにて、$ tsc --initを実行し、tsconfig.jsonを生成します。
$ npm i --save fzfnode_modulesにfzfをインストールしておきます。

static/main.ts
import { Fzf } from "./node_modules/fzf/dist/fzf.es.js";

main();

async function fetchPath(url: string) {
  return await fetch(url)
    .then((response) => {
      return response.json();
    })
    .catch((response) => {
      return Promise.reject(
        new Error(`{${response.status}: ${response.statusText}`),
      );
    });
}

// ほぼ公式通りの実装
function fzfSearch(list: string[], keyword: string): string[] {
  const fzf = new Fzf(list);
  const entries = fzf.find(keyword);
  const ranking: string[] = entries.map((entry: Fzf) => entry.item);
  return ranking;
}

async function main() {
  const url: URL = new URL(window.location.href);
  // サーバーからExcelの行を取得
  const list = await fetchPath(url.origin + "/list");
  const searchInput = document.getElementById("search-form");
  const resultOutput = document.getElementById("search-result");
  // キーを押すたびにページ内容更新
  searchInput.addEventListener("keyup", () => {
    // 要素クリア
    while (resultOutput.firstChild) {
      resultOutput.removeChild(resultOutput.firstChild);
    }
    // fzf検索
    const result: string[] = fzfSearch(list, searchInput.value);
    // 検索結果をコンソールに表示
    console.log(result);
    // 検索結果を結果要素に表示
    result.map((line: string) => {
      const p = document.createElement("p");
      const text = document.createTextNode(line);
      p.appendChild(text);
      resultOutput.append(p);
    });
  });
}

main.jsがindex.tmplが読み込まれるとmain()関数が呼ばれ、
main.goのAPI localhost:8080/list を呼び出してリスト形式のJSONに格納されたExcelデータを読み込みます。
さらに"search-form" 要素にイベントリスナーを登録し、input要素にキー入力するたびに serchInput.addEventListener()のコールバック関数が実行されます。
これでキー入力のたびに"resultOutput"要素のリセットと、fzfSearch()関数が働き、
fzfSearch()の結果を"resultOutput" 要素にpタグで書き込みます。

main.tsが書けたら$ npx tscを実行し、main.jsを生成します.

実行

$ go run main.goでサーバーを起動し、ブラウザのアドレス欄にlocalhost:8080 で接続します。
最後に、検索窓に適当な文字列を打って、データが絞り込まれて表示されることを確認します。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?