Cell Renderer Components（ag-Grid）の作り方

#ag-GridでCell Renderer Componentsを作る

[公式ドキュメント](https://www.ag-grid.com/javascript-grid-cell-rendering-components/)からのまとめ。
というかほぼそのまんま。**VueJS**が射程です。

##Cell Renderer Componentsとは
セル描画のふるまいをカスタマイズするコンポーネント。

##Cell Renderer Componentsのインターフェース
[公式ドキュメントより](https://www.ag-grid.com/javascript-grid-cell-rendering-components/#cell-renderer-component)。

```javascript
interface ICellRendererComp {
    // Optional - Params for rendering. The same params that are passed to the cellRenderer function.
    init?(params: ICellRendererParams): void;

    // Mandatory - Return the DOM element of the component, this is what the grid puts into the cell
    getGui(): HTMLElement;

    // Optional - Gets called once by grid after rendering is finished - if your renderer needs to do any cleanup,
    // do it here
    destroy?(): void;

    // Mandatory - Get the cell to refresh. Return true if the refresh succeeded, otherwise return false.
    // If you return false, the grid will remove the component from the DOM and create
    // a new component in it's place with the new values.
    refresh(params: any): boolean;
}
```

このうち、*getGui*と*refresh*は必須。
また、*refresh*以外はただ一度だけ呼ばれ、*refresh*は以下のタイミングで0~n回発生。

* *rowNode.setDataValue(colKey, value)*の呼び出し時
* セル編集時、セル編集完了時
* *api.refreshCells()*の呼び出し時

##ICellRendererParamsのインターフェース

[公式ドキュメントより](https://www.ag-grid.com/javascript-grid-cell-rendering-components/#cell-renderer-component)。

```javascript
interface ICellRendererParams {
    value: any, // value to be rendered
    valueFormatted: any, // value to be rendered formatted
    getValue: ()=> any, // convenience function to get most recent up to date value
    setValue: (value: any) => void, // convenience to set the value
    formatValue: (value: any) => any, // convenience to format a value using the columns formatter
    data: any, // the rows data
    node: RowNode, // row rows row node
    colDef: ColDef, // the cells column definition
    column: Column, // the cells column
    rowIndex: number, // the current index of the row (this changes after filter and sort)
    api: GridApi, // the grid API
    eGridCell: HTMLElement, // the grid's cell, a DOM div element
    eParentOfValue: HTMLElement, // the parent DOM item for the cell renderer, same as eGridCell unless using checkbox selection
    columnApi: ColumnApi, // grid column API
    context: any, // the grid's context
    refreshCell: ()=>void // convenience function to refresh the cell
}
```

##Cell Renderer Componentに引数を渡す
[*cellRendererParams*](https://www.ag-grid.com/javascript-grid-cell-rendering-components/#complementing-cell-renderer-params)を使う。


```javascript
// define cellRenderer to be reused
var myCellRenderer = function(params) {
    return '<span style="color: '+params.color+'">' + params.value + '</span>';
}

// use with a color
colDef.cellRenderer = myCellRenderer;
colDef.cellRendererParams = {
    color: 'guinnessBlack'
}

// use with another color
colDef.cellRenderer = myCellRenderer;
colDef.cellRendererParams = {
    color: 'irishGreen'
}
```

##コンポーネントの登録
「[ag-Gridコンポーネントの登録](https://qiita.com/norisiro/items/08bd36b68518c016e3e6)」

##Cell Renderer Function
コンポーネントを一から実装する代わりに、単純なファンクションだけでもよい。
（これは**ICellRendererComp**インターフェースの*init*と*getGui*に相当？）
引数は*init*呼び出し時と同じ**params**、また、HTML文字列またはDOMオブジェクトを返却する。
*refresh*や*destroy*を実装する必要がなければ使うべし。
※ただし、**React**や**Angular**では対応してない模様。**VueJS**ではできた。
[以下参考](https://www.ag-grid.com/javascript-grid-cell-rendering-components/#cell-renderer-function)。

```javascript
// put the value in bold
colDef.cellRenderer = function(params) {
    return '<b>' + params.value.toUpperCase() + '</b>';
}

// put a tooltip on the value
colDef.cellRenderer = function(params) {
    return '<span title="the tooltip">'+params.value+'</span>';
}

// create a DOM object
colDef.cellRenderer = function(params) {
    var eDiv = document.createElement('div');
    eDiv.innerHTML = '<span class="my-css-class"><button class="btn-simple">Push Me</button></span>';
    var eButton = eDiv.querySelectorAll('.btn-simple')[0];

    eButton.addEventListener('click', function() {
        console.log('button was clicked!!');
    });

    return eDiv;
}
```

##Cell Renderer Instanceにアクセスする
grid APIの*getCellRendererInstances(params)*を使用する。以上。

[シグネチャと引数のインターフェースです。](https://www.ag-grid.com/javascript-grid-cell-rendering-components/#accessing-cell-renderer-instances)

```javascript
// function takes params to identify what cells and returns back a list of cell renderers
function getCellRendererInstances(params: GetCellRendererInstancesParams): ICellRendererComp[];

// params object for the above
interface GetCellRendererInstancesParams {
    // an optional list of row nodes
    rowNodes?: RowNode[];
    // an optional list of columns
    columns?: (string|Column)[];
}
```

使用例。

```javascript
// example - get cell renderer for first row and column 'gold'
var firstRowNode = gridOptions.api.getDisplayedRowAtIndex(0);
var params = { columns: ['gold'], rowNodes: [firstRowNode]};
var instances = gridOptions.api.getCellRendererInstances(params);
if (instances.length > 0) {
    // got it, user must be scrolled so that it exists
    var instance = instances[0];
}
```

##VueJSでCellRendererを実装する
以下の点で例外があるが、**VueJS**でも**ICellRenderer**インターフェースの実装が有効。

* *init*は不要。セルの値は**params**と呼ばれるデータフィールドによって暗黙的に使用可能となっている？とのこと（**this.params**で値にアクセスできる）。
* *getGui*も不要。これは**VueJS**の**template**に相当するため。

###VueJSでCheckBoxのCellRendererを実装する
ChechBoxの[サンプル](https://www.ag-grid.com/javascript-grid-cell-rendering-components/#example-rendering-using-vuejs-components)がなかったのでやってみました。

```vuejs:CheckBoxCell.vue
<template>
    <div>
        <input type="checkbox" 
        :checked="isChecked"
        :disabled="isReadonly"
        @change="onChange"
        >
    </div>
</template>

<script>
import Vue from "vue";

export default Vue.extend({
    data() {
        return {
            isChecked: false,
            isReadonly: false
        }
    },
    methods:{
        /**
         * 更新が行われた際に、セルの値をチェック状態としてセットする。
         */
        refresh(){
            this.isChecked = this.params.getValue()
            return true
        },
        /**
         * changeイベント時にチェック状態をセルの値としてセットする。
         * セルのチェック状態に応じてヘッダーも更新したいため、
         * api.refreshHeader()を呼び出す。
         */
        onChange(){
            this.isChecked = !this.isChecked;
            this.params.setValue(this.isChecked);
            this.params.api.refreshHeader();
        },
    },
    /**
     * コンポーネントパラメータの受け取り。
     */
    beforeMount(){
        this.isChecked = this.params.value
        this.isReadonly = this.params.readonly
    }
})
</script>   

<style scoped>
    div{
        text-align: center;
    }
</style>
```

```vuejs:CheckBoxHeader.vue
<template>
    <div>
        <input 
        v-if="!isReadonly"
        type="checkbox" 
        :checked="isChecked"
        @change="onChange">
        <span>
        {{ params.displayName }}
        </span>
    </div>
</template>

<script>
import Vue from "vue";

export default Vue.extend({
    data() {
        return {
            isChecked: false,
            isReadonly: true
        }
    },
    methods:{
        /**
         * changeイベント時にチェック状態をセルの値に反映する。
         * setDataValueで値の設定を行うことで、
         * CellRendererコンポーネントのrefreshが呼ばれるはず。
         */
        onChange(){
            this.isChecked = !this.isChecked;
            let colId = this.params.column.colId;
            this.params.api.forEachNode(node =>{
                node.setDataValue(colId, this.isChecked);
            });
        },
        /**
         * ヘッダーの初期表示設定。
         */
        initHeader(){
            let isChecked = true;
            let colId = this.params.column.colId;
            this.params.api.forEachNode(node =>{
                if(!node.data[colId]){
                    isChecked = false;
                } 
            });
            this.isChecked = isChecked;
        }
    },
    /**
     * コンポーネントパラメータの受け取り。
     * 及び、ヘッダーの初期表示設定（DOMの更新を待ちうけるためnextTickを使用）。
     */
    beforeMount(){
        this.isReadonly = this.params.readonly
        Vue.nextTick(() => {
            this.initHeader();
        });
    }
})

</script>

```

使用例。

```javascript:~.vue
            this.columnDefs = [
                {
                    headerName: 'Flag', 
                    field: 'flag',
                    headerComponent: "checkboxColumn",
                    headerComponentParams : {readonly: true},
                    cellRenderer: "checkboxCell",
                    cellRendererParams :{readonly: true}
                }
　　　　　　　]
```
以上。