エクセルLIKEなテーブルフィルタのつくりかた【Javascript】

## 目次
[はじめに](#はじめに)
[filter.js キック(vue created)](#filterjs-キックvue-created)
[tFilterInit関数 解説](#tfilterinit関数-解説)
[tFilterCreate関数 解説](#tfiltercreate関数-解説)
[tFilterAllSet関数 解説](#tfilterallset関数-解説)
[tFilterClick関数 解説](#tfilterclick関数-解説)
[tFilterGo関数 解説](#tfiltergo関数-解説)
[tFilterCancel関数 解説](#tfiltercancel関数-解説)
[tFilterSave関数 解説](#tfiltersave関数-解説)
[tFilterCloseOpen関数 解説](#tfiltercloseopen関数-解説)
[filter.js コード全文](#filterjs-コード全文)
[ソースコード（Git）](#ソースコードgit)
[参考サイト](#参考サイト)

## はじめに
ここでは以下で紹介した家計簿アプリで実装したテーブルフィルタの実装方法について解説します。

https://qiita.com/nate3870/items/97d3017d3d04a109c1bf

開発ツールはvueを用いていて、フロントエンドのほとんどの処理はvueの中で実装しています。
テーブルフィルタについてだけは、区別して実装したかったので、[filter.js](#filterjs-コード全文)にまとめて実装しました。

## filter.js キック（vue created）
ページ読み込み時にtFilterInit関数を実行させるようにvue側で指定しています。
axiosのリクエストで取得したデータベースのデータ（this.oeconomicas）を引数にして実行してます。
```vuejs:Oeconomica.vue
    created: async function () {
        try {
            const oResult = await axios.get("/oeconomica/",{
                params: {
                    year_month: this.year_month
                }
            });
            this.oeconomicas = oResult.data;
            this.registSubscription(); // サブスク自動登録
            this.getCategoryDatas();
            tFilterInit(this.oeconomicas);
        } catch (err) {
            console.log(err);
            alert(JSON.stringify(err));
        }
    },
```
vueのcreatedの仕組みは以下記事を参考ください。

https://qiita.com/tanoken729b/items/5770d4c620163a25ad21

## tFilterInit関数 解説
フィルタ実装のメイン関数です。
ここでは、テーブルのカラムにボタンを表示させる処理が実装されています。
#### フローチャート
```mermaid
%%{init: {'theme':'forest'}}%%
graph TD

1("テーブルオブジェクト読み込み")
2>"カラム行の各列に対してループ"]
3{"thタグに
「cmanFilterBtn」が
定義されている"}
4("該当のカラムのセルにボタンを追加
(htmlを追記)")
5("フィルタを付けた列の場所を記憶")
strend["終了"]

1 --> 2 --> 3
3 --true--> 4 --> 5 --> 2
3 --false--> 2
5 --ループ終了--> strend
```
```javascript:filter.js - tFilterInit(抜粋)
  // --- 「cmanFilterBtn」の定義があるセルを対象とする ------
  if(wTD[j].getAttribute('cmanFilterBtn') !== null){
    // --- フィルタ対象はボタンの次の行から -----------------
    gTfStartRow = i + 1;
    // --- ボタンを追加（画像はsvgを使用） ------------------
    wAddBtn  = '<div class="tfArea">';
    wAddBtn += '<svg class="tfImg" id="tsBtn_'+j+'" onclick="tFilterCloseOpen('+j+')"><path d="M0 0 L9 0 L6 4 L6 8 L3 8 L3 4Z"></path></svg>';
    wAddBtn += '<div class="tfList" id="tfList_'+j+'" style="display:none">';
    wAddBtn += tFilterCreate(j);
    wAddBtn += '</div>';
    wAddBtn += '</div>';
    wTD[j].innerHTML = wTD[j].innerHTML+wAddBtn;
```
###### フィルタを付与するカラムの指定方法
htmlの方で、thタグに`cmanFilterBtn`という属性を追記します。javascript側でそれを認識したら、ボタンを追加する処理に分岐される仕組みです。
次の場合だと、収支とカテゴリの項目にボタンが付けられます。

```vue:cmanFilterBtn定義
<tr>
    <th width="38">曜日</th>
    <th>日付</th>   
    <th cmanFilterBtn>収支</th>
    <th cmanFilterBtn>カテゴリ</th>
    <th>金額(円)</th>
    <th>メモ</th>
    <th width="80">変更</th>
    <th width="80">削除</th>
</tr>
```
###### ボタン描画
ボタンの見た目をsvgを用いて描いています。
`<path d="M0 0 L9 0 L6 4 L6 8 L3 8 L3 4Z"></path>`というコードを指定することで、
エクセルでお馴染みのマーク（逆三角フラスコ？）が描かれる仕組みです。

svgの定義方法は以下のサイトで詳しくまとまっています。

https://developer.mozilla.org/ja/docs/Web/SVG/Attribute/d

##### その他
- `onclick="tFilterCloseOpen('+j+')"`:ボタンがクリック時[tFilterCloseOpen](#tfiltercloseopen関数-解説)関数を実行
- `wAddBtn += tFilterCreate(j);`:フィルタの詳細な設定を[tFilterCreate](#tfiltercreate関数-解説)関数で定義する

## tFilterCreate関数 解説
この関数では、フィルタリスト(フィルタのボタンを押した時に開くメニュー)を定義するhtmlを作成し、実行元([tFilterInit](#tfilterinit関数-解説))に返す処理が実装されています。
#### フローチャート
```mermaid
%%{init: {'theme':'forest'}}%%
graph TD
loop>"指定された列の各セルに対してループ"]
getValToList("配列にセルの値を取得(wItem)")
checkType{"値のデータ型が数値か"}
validateStr("列のセル値全てを文字列として扱う
(wNotNum=1)")
checkType2{"wNotNum=0"}
orderInt("数値で昇順ソート")
orderStr("文字列で昇順ソート")
tfDataAll("「すべて」のチェックボックス作成")
loop2>"取得した列の値(wItem)に対してループ"]
checkDupli{"連想配列wItemSaveのキーに
値がすでに登録されている"}
tfData("チェックボックス作成")
wItemSave("wItemSaveのキーに
値を登録(重複判定用)")
tfInStr("検索用のテキストボックス作成")
button("「OK」「Cancel」ボタンの作成")
return("作成したhtmlを返す")

loop --> getValToList --> checkType -- true --> loop
checkType --false --> validateStr --> loop
checkType　--ループ終了--> checkType2
validateStr　--ループ終了--> checkType2 --true--> orderInt --> tfDataAll
checkType2 --false--> orderStr --> tfDataAll
tfDataAll --> loop2 --> checkDupli --true--> loop2
checkDupli --false--> tfData --> wItemSave--> loop2
wItemSave　--ループ終了--> tfInStr --> button --> return
```

#### 「すべて」のチェックボックスをクリックした時の処理
- [tFilterAllSet](#tfilterallset関数-解説)関数を実行 → 該当の列のチェックボックス全部のチェックをON/OFFさせる
```javascript:「すべて」のチェックボックスのhtml
var wItemId = 'tfData_ALL_'+argCol;
rcList += '<div class="tfMeisai">';
rcList += '<input type="checkbox" id="'+wItemId+'" checked onclick="tFilterAllSet('+argCol+')">';
rcList += '<label for="'+wItemId+'">(すべて)</label>';
rcList += '</div>';
```
#### 通常のチェックボックスをクリックした時の処理
- [tFilterClick](#tfilterclick関数-解説)関数を実行
```javascript:チェックボックスのhtml
wItemId = 'tfData_'+argCol+'_r'+i;
rcList += '<div class="tfMeisai">';
rcList += '<input type="checkbox" id="'+wItemId+'" value="'+wVal+'" checked onclick="tFilterClick('+argCol+')">';
rcList += '<label for="'+wItemId+'">'+( wVal=='' ? '(空白)' : wVal )+'</label>';
rcList += '</div>';
```
#### 「OK」、「Cancel」をクリックした時の処理
- 「OK」クリック時：[tFilterGo](#tfiltergo関数-解説)関数でフィルタ抽出実行
- 「Cancel」クリック時：[tFilterCancel](#tfiltercancel関数-解説)関数でキャンセル時の処理を実行
```javascript:OK,Cancelボタンのhtml
rcList += '<div class="tfBtnArea">';
rcList += '<input type="button" value="OK" onclick="tFilterGo()">';
rcList += '<input type="button" value="Cancel" onclick="tFilterCancel('+argCol+')">';
rcList += '</div>';
```

## tFilterAllSet関数 解説
※ソースコードは[filter.js コード全文](#filterjs-コード全文)を参照
#### 処理概要
- 「すべて」のチェックボックスのチェックを付けた時、文字通り他のすべてのチェックボックスのチェックもつける
- 「すべて」のチェックボックスのチェックを外した時、文字通り他のすべてのチェックボックスのチェックも外す

## tFilterClick関数 解説
※ソースコードは[filter.js コード全文](#filterjs-コード全文)を参照
#### 処理概要
- チェックボックスのチェック数に応じて、「すべて」のチェックのON、OFFを調整する

#### フローチャート
```mermaid
%%{init: {'theme':'forest'}}%%
graph TD
useClick["チェックボックスをクリック"]
countCheckOn("チェックが付いているボックス数を集計")
countCheckOff("チェックが外れているボックス数を集計")
ifAllset{"全てのボックスに
チェックが付いた"}
ifUnset{"一つでもチェック
が外れた"}
checkAll("「すべて」をチェックする")
uncheckAll("「すべて」のチェックを外す")

useClick --> countCheckOn
useClick --> countCheckOff
countCheckOn　--> ifAllset
countCheckOn　--> ifUnset
countCheckOff　--> ifAllset
countCheckOff　--> ifUnset
ifAllset --> checkAll
ifUnset --> uncheckAll
```

## tFilterGo関数 解説
※ソースコードは[filter.js コード全文](#filterjs-コード全文)を参照

フィルタリスト内の検索に応じてテーブルに対しフィルタを実行する関数です。
#### フローチャート
テーブルフィルタ機能それ自体のフローのため、少し多めな感じになりました。。。
```mermaid
%%{init: {'theme':'forest'}}%%
graph TD
userClick("フィルタのOKボタンクリック")
readTable("テーブルオブジェクト読み込み")
loopInRow>"テーブルの各行に対してループ"]
rmTrTag("trタグに「cmanFilterNone」属性があれば属性を外す
(直前のフィルタを一旦全てリセットする)")
loopFilterCol>"フィルタボタンがある各列でループ"]
saveChecked("チェックされている値を保存
(wItemSave)")
unFilterStatus{"「すべて」にチェックが付いている
and
テキストボックスが空"}
rmBgColor(フィルタボタンの色を無色にする)
setBgColor("フィルタボタンの色を黄色にする")
loopFilterCol2>"ループ中の列の各セルについてループ"]
checkCell{"セル値＝wItemSave"}
checkCellText{"セル値＝テキスト検索値
(部分一致)"}
setAttribute("trタグに「cmanFilterNone」属性を付与
→該当の行が非表示になる")
closeFilter[フィルタリストを閉じる]
execFilter["フィルタ実行 or 解除"]

userClick　--> readTable　--> loopInRow --> rmTrTag --> loopInRow
rmTrTag --ループ終了--> loopFilterCol --> saveChecked --> unFilterStatus --true-->rmBgColor--> closeFilter
unFilterStatus --false-->setBgColor --> loopFilterCol2 --> checkCell
checkCell --true-->setAttribute -->　checkCellText
checkCellText --true-->setAttribute -->　loopFilterCol2
checkCell --false--> checkCellText --false-->loopFilterCol2
setAttribute --1列のループ終了--> loopFilterCol
setAttribute --全列のループ終了--> closeFilter --> execFilter

linkStyle 12,13 stroke:blue
linkStyle 14,15 stroke:orange
```

#### 属性付与によるテーブル行非表示の仕組み
上記、フローチャートにあるように、対象のtrタグに「cmanFilterNone」という属性を与えることで、
テーブルの行全体を非表示にし,また境界線を二重線にすることで行と行の間に非表示されたものがあることを示す演出となっています。

この処理は、cssで下記のように規定して実現させています。
```css:style.css
/* === フィルタ非表示 ============================ */
#inOutTable tr[cmanFilterNone]{
    display    : none;
}
/* === フィルタ非表示行と次行の間を二重線にする == */
#inOutTable tr[cmanFilterNone] + tr td{
    border-top : 3px double #777;
}
```
フィルタ実行後の画面です。フィルタを行ったボタンの色が黄色になり、隠された部分は二重線で非表示になっていることがわかるようになっています。
![スクリーンショット 2023-08-12 17.40.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510731/e6ee4867-31e7-c381-b4a7-775857f94d71.png)

## tFilterCancel関数 解説
※ソースコードは[filter.js コード全文](#filterjs-コード全文)を参照
#### 処理概要
- フィルタリストの「cancel」ボタンを押した時実行
- フィルタリストの中の状態を、開いた時の状態に復元させる
→[tFilterSave](#tfiltersave関数-解説)関数を実行させることで状態復元。
- フィルタリストを閉じる

## tFilterSave関数 解説
※ソースコードは[filter.js コード全文](#filterjs-コード全文)を参照

この関数では次のようなことを実現させます。
- フィルタを閉じてもう一度開いた時、直前で設定した状態を維持したまま再表示させる。
→ これ無しだとフィルタを設定しても、また開いた時にデフォルト値に戻ってしまう。
#### フローチャート
```mermaid
%%{init: {'theme':'forest'}}%%
graph TD
gTfListSave["gTfListSave
(パブリック変数)"]
argCheck{"引数＝save"}
tfData_ALL("「すべて」のチェックボックスの値(true/false)を保存")
tfForm("他チェックボックスの値(true/false)を保存")
tfInStr("テキストボックス内の入力文字を保存")
endstr[終了]
argCheck --true--> tfData_ALL --> tfForm --> tfInStr　--> endstr
argCheck --false--> gTfListSave　--> endstr
tfData_ALL -.保存.-> gTfListSave
gTfListSave -.復元.-> tfData_ALL
tfForm -.保存.-> gTfListSave
gTfListSave -.復元.-> tfForm
tfInStr -.保存.-> gTfListSave
gTfListSave -.復元.-> tfInStr
```

## tFilterCloseOpen関数 解説
※ソースコードは[filter.js コード全文](#filterjs-コード全文)を参照

##### 実行タイミング
- フィルタボタンをクリックしたタイミング
- フィルタリストの「OK」、「キャンセル」ボタンを押したタイミング

##### 処理フロー
1. 開いている他のフィルタリストを全て閉じる
2. クリックしたフィルタリストを開く（引数（クリックした列番号）が指定されている場合のみ）
3. [tFilterSave](#tfiltersave関数-解説)関数を実行しフィルタの設定（どこにチェックを付けたか）を保存する。

##### 解説
- フィルタリストの表示非表示はstyleの設定(display none)によって制御している。

https://web-camp.io/magazine/archives/89462

- 言わずもがなかもだが、全てを閉じる処理を入れないと、複数のフィルタが同時に開いてしまう動きになってしまう。この処理を含めることで、別のフィルタを開いている状態で新しいフィルタをクリックしたときに表示が切り替わるようになる。
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510731/16a66b5e-9d01-2930-c8e3-528dd9fa1050.png">

## filter.js コード全文
```javascript:filter.js
//===============================================================
//  フィルタテーブルの共通変数　設定要！
//===============================================================
var gTabldID = 'inOutTable';  // テーブルのエリアのIDを設定
var gTfStartRow = 0;
var gTfColList  = [];             // ボタンが配置されている列番号
var gTfListSave = {};             // フィルタリストの保存状態
var varOeconomicas = '';
const colObj = {
  0:'day_of_week',
  1:'date',
  2:'balance',
  3:'category'
}

//===============================================================
//  オンロードでテーブル初期設定関数をCALL
//===============================================================
// window.onload = function() {
//   tFilterInit();
// }
const setOeconomicas = (oeconomicas) => {
  varOeconomicas = oeconomicas;
}

const tFilterInit = (oeconomicas) =>{
  
  //==============================================================
  //  テーブルの初期設定
  //==============================================================
  var wTABLE = document.getElementById(gTabldID);
  var wTR = wTABLE.rows;
  var wAddBtn = '';
  setOeconomicas(oeconomicas); //セッター
  // ------------------------------------------------------------
  //   テーブル内にフィルタボタンを付ける
  // ------------------------------------------------------------
  for(var i=0; i < wTR.length; i++){
    var wTD = wTABLE.rows[i].cells;

    for(var j=0; j < wTD.length; j++){
      
      // --- 「cmanFilterBtn」の定義があるセルを対象とする ------
      if(wTD[j].getAttribute('cmanFilterBtn') !== null){
  
        // --- フィルタ対象はボタンの次の行から -----------------
        gTfStartRow = i + 1;
  
        // --- ボタンを追加（画像はsvgを使用） ------------------
        wAddBtn  = '<div class="tfArea">';
        wAddBtn += '<svg class="tfImg" id="tsBtn_'+j+'" onclick="tFilterCloseOpen('+j+')"><path d="M0 0 L9 0 L6 4 L6 8 L3 8 L3 4Z"></path></svg>';
        wAddBtn += '<div class="tfList" id="tfList_'+j+'" style="display:none">';
        wAddBtn += tFilterCreate(j);
        wAddBtn += '</div>';
        wAddBtn += '</div>';
        wTD[j].innerHTML = wTD[j].innerHTML+wAddBtn;
  
        // --- フィルタボタンなる列を保存 -----------------------
        gTfColList.push(j);
      }
    }
     // --- ボタンを付けたら以降の行は無視する -------------------
     if(wAddBtn != ''){
       var gSortBtnRow = i;
       break;
    }
  }
}
 
 function tFilterCreate(argCol){
  //==============================================================
  //  指定列のフィルタリスト作成
  //==============================================================
  var wItem     = [];              // クリックされた列の値
  var wNotNum   = 0;               // 1 : 数字でない
  var wItemSave = {};              // フィルタに設定した値がキー
  var rcList    = '';              // 返すフィルタリスト
  var wVal = '';
  // ------------------------------------------------------------
  //  クリックされた列の値を取得する
  //------------------------------------------------------------
  for(var i=gTfStartRow; i < varOeconomicas.length; i++){
    var j = i - gTfStartRow;
    wItem[j] = varOeconomicas[i][colObj[argCol]];
    if(wItem[j].match(/^[-]?[0-9,\.]+$/)){
    }else{
      wNotNum = 1;
    }
  }
   
   // ------------------------------------------------------------
   //  列の値でソートを実行
   // ------------------------------------------------------------
     if(wNotNum == 0){
       wItem.sort(sortNumA);           // 数値で昇順
     }else{
       wItem.sort(sortStrA);           // 文字で昇順
     }
   
   // ------------------------------------------------------------
   //  「すべて」のチェックボックス作成
   // ------------------------------------------------------------
   var wItemId = 'tfData_ALL_'+argCol;
   rcList += '<div class="tfMeisai">';
   rcList += '<input type="checkbox" id="'+wItemId+'" checked onclick="tFilterAllSet('+argCol+')">';
   rcList += '<label for="'+wItemId+'">(すべて)</label>';
   rcList += '</div>';
  
   // ------------------------------------------------------------
   //  列の値でフィルタのチェックボックスを作成する
   //    チェックボックスはformで囲む
   // ------------------------------------------------------------
   rcList += '<form name="tfForm_'+argCol+'">';
  
   for(var i=0; i < wItem.length; i++){
  
     wVal = trim(wItem[i]);
     
     if(wVal in wItemSave){
       // ---値でチェックボックスが作成されている(重複) ----------
     }else{  
       // ---チェックボックスの作成 ------------------------------
       wItemId = 'tfData_'+argCol+'_r'+i;
       rcList += '<div class="tfMeisai">';
       rcList += '<input type="checkbox" id="'+wItemId+'" value="'+wVal+'" checked onclick="tFilterClick('+argCol+')">';
       rcList += '<label for="'+wItemId+'">'+( wVal=='' ? '(空白)' : wVal )+'</label>';
       rcList += '</div>';
  
       // ---重複判定用にチェックボックスの値を保存 --------------
       wItemSave[wVal]='1';
     }
   }
   rcList += '</form>';
   
   // ------------------------------------------------------------
   //  文字抽出のinputを作成
   // ------------------------------------------------------------
   rcList += '<div class="tfInStr">';
   rcList += '<input type="text" placeholder="含む文字抽出" id="tfInStr_'+argCol+'">';
   rcList += '</div>';
  
   // ------------------------------------------------------------
   //  「OK」「Cancel」ボタンの作成
   // ------------------------------------------------------------
   rcList += '<div class="tfBtnArea">';
   rcList += '<input type="button" value="OK" onclick="tFilterGo()">';
   rcList += '<input type="button" value="Cancel" onclick="tFilterCancel('+argCol+')">';
   rcList += '</div>';
   // ------------------------------------------------------------
   //  作成したhtmlを返す
   // ------------------------------------------------------------
   return rcList;
 }
 
 function tFilterClick(argCol){
  //==============================================================
  //  フィルタリストのチェックボックスクリック
  //    「すべて」のチェックボックスと整合性を合わせる
  //==============================================================
   var wForm   = document.forms['tfForm_'+argCol];
   var wCntOn  = 0;
   var wCntOff = 0;
   var wAll    = document.getElementById('tfData_ALL_'+argCol);   // 「すべて」のチェックボックス
  
   // --- 各チェックボックスの状態を集計する ---------------------
   for (var i = 0; i < wForm.elements.length; i++){
     if(wForm.elements[i].type == 'checkbox'){
       if (wForm.elements[i].checked) { wCntOn++;  }
       else                           { wCntOff++; }
     }
   }
  
   // --- 各チェックボックス集計で「すべて」を整備する -----------
   if((wCntOn == 0)||(wCntOff == 0)){
     wAll.checked = true;             // 「すべて」をチェックする
     tFilterAllSet(argCol);           // 各フィルタのチェックする
   }else{
      wAll.checked = false;           // 「すべて」をチェックを外す
   }
 }
 
 function tFilterCancel(argCol){
  //==============================================================
  //  キャンセルボタン押下
  //==============================================================
   tFilterSave(argCol, 'load');    // フィルタ条件の復元
   tFilterCloseOpen('');           // フィルタリストを閉じる
  
 }
 
function tFilterGo(){
  //===============================================================
  //  フィルタの実行
  //===============================================================
  var wTABLE  = document.getElementById(gTabldID);
  var wTR     = wTABLE.rows;
  
  // ------------------------------------------------------------
  //  全ての非表示を一旦クリア
  // ------------------------------------------------------------
  for(var i = 0; i < wTR.length; i++){
    if(wTR[i].getAttribute('cmanFilterNone') !== null){
      wTR[i].removeAttribute('cmanFilterNone');
    }
  }
  // ------------------------------------------------------------
  //  フィルタボタンのある列を繰り返す
  // ------------------------------------------------------------
  for(var wColList = 0; wColList < gTfColList.length; wColList++){
    var wCol       = gTfColList[wColList];
    var wAll       = document.getElementById('tfData_ALL_'+wCol);     // 「すべて」のチェックボックス
    var wItemSave  = {};
    var wFilterBtn =  document.getElementById('tsBtn_'+wCol);
    var wFilterStr =  document.getElementById('tfInStr_'+wCol);
    var wForm      = document.forms['tfForm_'+wCol];
    var wVal = '';

    // -----------------------------------------------------------
    //  チェックボックスの整備（「すべて」の整合性）
    // -----------------------------------------------------------
    for (var i = 0; i < wForm.elements.length; i++){
      if(wForm.elements[i].type == 'checkbox'){
        if (wForm.elements[i].checked) {
          wItemSave[wForm.elements[i].value] = 1;      // チェックされている値を保存
        }
      }
    }
     // -----------------------------------------------------------
     //  フィルタ（非表示）の設定
     // -----------------------------------------------------------
    if((wAll.checked)&&(trim(wFilterStr.value) == '')){
      wFilterBtn.style.backgroundColor = '';              // フィルタなし色
    }
    else{
      wFilterBtn.style.backgroundColor = '#ffff00';       // フィルタあり色
  
      for(var i=gTfStartRow; i < wTR.length; i++){
        wVal = trim(varOeconomicas[i-1][colObj[wCol]]);
        // --- チェックボックス選択によるフィルタ ----------------
        if(!wAll.checked){
          if(wVal in wItemSave){
          }
          else{
            wTR[i].setAttribute('cmanFilterNone','');
          }
        }
  
         // --- 抽出文字によるフィルタ ----------------------------
        if(wFilterStr.value != ''){
          reg = new RegExp(wFilterStr.value);
          if(wVal.match(reg)){
          }
          else{
            wTR[i].setAttribute('cmanFilterNone','');
          }
        }
      }
    }
  }
  tFilterCloseOpen('');
 }
 
 function tFilterSave(argCol, argFunc){
  //==============================================================
  //  フィルタリストの保存または復元
  //==============================================================
   // ---「すべて」のチェックボックス値を保存 ------------------
   var wAllCheck = document.getElementById('tfData_ALL_'+argCol);
   if(argFunc == 'save'){
     gTfListSave[wAllCheck.id] = wAllCheck.checked;
   }else{
     wAllCheck.checked = gTfListSave[wAllCheck.id];
   }
  
   // --- 各チェックボックス値を保存 ---------------------------
   var wForm    = document.forms['tfForm_'+argCol];
   for (var i = 0; i < wForm.elements.length; i++){
     if(wForm.elements[i].type == 'checkbox'){
       if(argFunc == 'save'){
         gTfListSave[wForm.elements[i].id] = wForm.elements[i].checked;
       }else{
         wForm.elements[i].checked = gTfListSave[wForm.elements[i].id];
       }
     }
   }
  
   // --- 含む文字の入力を保存 ---------------------------------
   var wStrInput = document.getElementById('tfInStr_'+argCol);
   if(argFunc == 'save'){
     gTfListSave[wStrInput.id] = wStrInput.value;
   }else{
     wStrInput.value = gTfListSave[wStrInput.id];
   }
 }
 
const tFilterCloseOpen = (argCol) => {
  //==============================================================
  //  フィルタを閉じて開く
  //==============================================================
  // --- フィルタリストを一旦すべて閉じる -----------------------
  for(var i=0; i < gTfColList.length; i++){
    document.getElementById("tfList_"+gTfColList[i]).style.display = 'none';
  }
  // --- 指定された列のフィルタリストを開く ---------------------
  if(argCol != ''){
    document.getElementById("tfList_"+argCol).style.display = '';
    // --- フィルタ条件の保存（キャンセル時に復元するため） -----
    tFilterSave(argCol, 'save');
  
  }
}

function tFilterAllSet(argCol){
//==============================================================
//  「すべて」のチェック状態に合わせて、各チェックをON/OFF
//==============================================================
  var wChecked = false;
  var wForm    = document.forms['tfForm_'+argCol];

  if(document.getElementById('tfData_ALL_'+argCol).checked){
    wChecked = true;
  }

  for (var i = 0; i < wForm.elements.length; i++){
    if(wForm.elements[i].type == 'checkbox'){
      wForm.elements[i].checked = wChecked;
    }
  }
}

function tFilterReset(){
  //==============================================================
  //  フィルタを削除する
  //==============================================================
  let elements = document.querySelectorAll('.tfArea');
  elements.forEach((element) => {
    element.remove();
  });
}
  
 function sortNumA(a, b) {
  //==============================================================
  //  数字のソート関数（昇順）
  //==============================================================
   a = parseInt(a.replace(/,/g, ''));
   b = parseInt(b.replace(/,/g, ''));
  
   return a - b;
 }
 
 function sortStrA(a, b){
  //==============================================================
  //  文字のソート関数（昇順）
  //==============================================================
   a = a.toString().toLowerCase();  // 英大文字小文字を区別しない
   b = b.toString().toLowerCase();
  
   if     (a < b){ return -1; }
   else if(a > b){ return  1; }
   return 0;
 }
 
 function trim(argStr){
  //==============================================================
  //  trim
  //==============================================================
   var rcStr = argStr;
   rcStr	= rcStr.replace(/^[ 　\r\n]+/g, '');
   rcStr	= rcStr.replace(/[ 　\r\n]+$/g, '');
   return rcStr;
 }
```



## ソースコード（Git）
https://github.com/Ayoan/Oeconomica

## 参考サイト
https://webparts.cman.jp/table/filter/