LoginSignup
1

posted at

updated at

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

概要

  • サンプルデータの準備
  • バックエンド実装
  • 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 で接続します。
最後に、検索窓に適当な文字列を打って、データが絞り込まれて表示されることを確認します。

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
What you can do with signing up
1