インタラクティブな表をネイティブjavascriptで実装
javascriptの練習をしようと思い, ブラウザで検索・ソートが行える表を実装しました.
サンプルはこちらのURLで確認できます.
準備
まず今回用にjsプロジェクトを作成します.
mkdir interactive-table cd interactive-table npm init
またコンテンツ格納用のフォルダを作成します.
mkdir public mkdir public/style mkdir public/js
HTML/CSS
最低限のhtmlとcssを記述
public/index.html
<!DOCTYPE html> <html> <head> <title>intaractibe table</title> <meta charset="utf-8"> <!-- bootstrap css読み込み --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <link rel="stylesheet" href="style/default.css"> <link rel="shortcut icon" href=""> </head> <body> <div class="contents-wrapper"> <div class="header"> <p>在庫管理</p></br> </div> <div class="form-group"> <label for="item_name">名称</label><button type="button" class="btn btn-primary">検索</button> <input type="text" id="item_name" class="form-control"> </div> <div class="table-wrapper"> <table class="table table-striped"> <thead class="table-dark"> <tr class="row-header"> <th scope="col" data-colName="id">id</th> <th scope="col" data-colName="name">名称</th> <th scope="col" data-colName="unit_price">定価(単位 : 円)</th> <th scope="col" data-colName="stock">在庫</th> </tr> </thead> <tbody class="items"> </tbody> </table> </div> </div> <!-- bootstrap読み込み --> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> <script src="js/app.js"></script> </body> </html>
public/style/default.css
body { font-family: Arial, Helvetica, sans-serif; font-size: 16px; } .header { margin-top: 40px; font-size: 24px; } .contents-wrapper { padding-left: 120px; padding-right: 120px; } button { margin: 10px; } .form-control { width: 300px; }
静的な表を出力
テーブルの全レコードは配列としてTableModel
のdata
として管理します.
また表示するテーブルのレコードはtableItems
で管理するようにし,
TableView
側でtableItems
を元にHTML文字列を組み立てを行い描画すれば表の出力ができます.
public/js/app.js
const items = [ {id:1, name:'apple', unit_price:100, stock:10}, {id:2, name:'orange', unit_price:40, stock:30}, {id:3, name:'grape', unit_price:1000, stock:60}, {id:4, name:'banana', unit_price:300, stock:100}, ] class TableModel { constructor(data) { this.data = data; this.tableItems = data; this._listeneres = {'search':[], 'sort':[]}; } /** * イベントの登録を行う * @param {*} type イベント * @param {*} callback イベントハンドラ */ on(type, callback) { this._listeneres[type].push(callback); } /** * 登録イベントを実行 * @param {*} type イベント */ trigger(type) { this._listeneres[type].forEach(callback => { callback(); }); } } class TableView { constructor(data) { this.tableItems = data; this.model = new TableModel(data); this.tableElement = document.querySelector('.items'); this.init() } createTableItemsHTML(tableItems) { return(tableItems. map((e) => `<tr><th scope="row">${e.id}</th><td>${e.name}</td><td>${e.unit_price}</td><td>${e.stock}</td></tr>`). join('\n')) } init() { this.tableElement.innerHTML = this.createTableItemsHTML(this.tableItems); } } const app = new TableView(items);
表が正しく出力されると, つぎのようになります.
検索ボタン押下時の動作を実装する
検索ボタンを押したときに指定した名称のレコードのみ表示する動作を実装します. この動作のフローは以下になります.
- 検索ボタン押下
- TableModelのtableItemsを更新
- 更新後のtableItemsに基づいてHTMLの表を再描画
TableModelの修正
名前をキーにしてtableItemsを抽出するTableModelのメソッドselectByName
を実装します.
public/js/app.js
/** * tableItemsに対しnameをキーにして抽出を行う * @param {*} name 名称 */ selectByName(name) { this.tableItems = this.data.filter((e) => e.name===name); this.trigger('search'); }
TableViewの修正
TableViewのinitメソッド内部に以下を実装します
- search時にHTMLの表を再描画するメソッドをイベントリスナ-に追加
- 検索ボタン押下時にTableModel.selectByNameを呼び出すイベントの登録
public/js/app.js
class TableView { constructor(data) { this.tableItems = data; this.model = new TableModel(data); this.tableElement = document.querySelector('.items'); this.searchButton = document.getElementsByTagName('button')[0]; this.init() } createTableItemsHTML(tableItems) { return(tableItems. map((e) => `<tr><th scope="row">${e.id}</th><td>${e.name}</td><td>${e.unit_price}</td><td>${e.stock}</td></tr>`). join('\n')) } init() { this.tableElement.innerHTML = this.createTableItemsHTML(this.tableItems); // search時に対して表を再描画するメソッド this.model.on('search', () => { this.tableElement.innerHTML = this.createTableItemsHTML(this.model.tableItems); }) // 検索ボタン押下時にmodelのtableItemsを更新する this.searchButton.addEventListener('click', (e) => { this.model.selectByName(document.getElementById('item_name').value); }) } }
テキストボックスに「apple」と入力して、検索ボタンを押すと1件だけ出力されます.
ソート動作を実装する.
ソート動作の実装は検索機能実装とほとんど同じ流れなので、 修正箇所だけ列挙します.
TableModelの修正
列名に関してtableItemsをソートするTableModelのメソッドsortBy
を実装します.
/** * 引数で指定した列名に関してtableItemsをsortを行う * @param {*} columnName 列名 */ sortBy(columnName) { this.tableItems = this.data.sort((e1, e2) => e1[columnName] - e2[columnName]); this.trigger('sort'); }
TableViewを修正する
TableViewのinitメソッド内部に以下を実装します
- search時にHTMLの表を再描画するメソッドをイベントリスナ-に追加
- 列名押下時にTableModel.sortByを呼び出すイベントの登録
class TableView { constructor(data) { this.tableItems = data; this.model = new TableModel(data); this.tableElement = document.querySelector('.items'); this.searchButton = document.getElementsByTagName('button')[0]; this.tableColumnElements = document.querySelectorAll('.row-header > th'); this.init() } createTableItemsHTML(tableItems) { return(tableItems. map((e) => `<tr><th scope="row">${e.id}</th><td>${e.name}</td><td>${e.unit_price}</td><td>${e.stock}</td></tr>`). join('\n')) } init() { this.tableElement.innerHTML = this.createTableItemsHTML(this.tableItems); // search時に対して再描画するメソッド this.model.on('search', () => { this.tableElement.innerHTML = this.createTableItemsHTML(this.model.tableItems); }) // 検索ボタン押下時にmodelのtableItemsを更新する this.searchButton.addEventListener('click', (e) => { this.model.selectByName(document.getElementById('item_name').value); }) // sort時にmodelのtableItemsを更新する this.model.on('sort', () => { this.tableElement.innerHTML = this.createTableItemsHTML(this.model.tableItems); }) // 列名押下時にmodelのtableItemsを更新する this.tableColumnElements.forEach((colElment) => { colElment.addEventListener('click', (e) => { this.model.sortBy(e.currentTarget.getAttribute('data-colName')); }) }) } }