Vue.jsでグリッド(table)をクリックするとモーダルで編集させるtips

# まえがき
まだ完全版には遠いですが、役に立ちそうなのでメモ。

・・・作っては見たものの、結局モーダルの中に更にテーブルが必要になって、面倒になり、その画面は後に発見したjson-editorに変更しました。

まだまだVue.jsを完全に理解できた段階まではいかないため、おかしなことをしてる可能性があります。つまり、いっこーにっこー参考程度にしておいてくださいな。


## 前提
jQuery, Bootstrap3, Vue が読み込まれている。
もちろんほとんどの部分はBootstrapナシでもできますが、モーダルのコードは[Vue公式リファレンスのモーダル](https://jp.vuejs.org/v2/examples/modal.html)を元に、クラスはBootstrap3仕様に変更してあります。

新規行を作成する部分は未実装です。消すか作っちゃってください。

# コンポーネント定義
使いたいページから見えるところにコードを入れてください。

まずはHTML内にテンプレート

```html:template

<script type="text/x-template" id="modal-template">
    <transition name="modal">
        <div class="modal modal-mask modal-lg" style="display: block">
            <div class="modal-dialog">
                <div class="modal-content">

                    <div class="modal-header">
                        <slot name="header" class="modal-title">
                        </slot>
                    </div>

                    <div class="modal-body">
                        <slot name="body">
                        </slot>
                    </div>

                    <div class="modal-footer">
                        <slot name="footer">
                            <button type="button" class="btn btn-primary modal-default-button" @click="$emit('close')">
                                Close
                            </button>
                        </slot>
                    </div>
                </div>
            </div>
        </div>
    </transition>
</script>

<script type="text/x-template" id="grid-template">
    <div>
        <table class="editable">
            <thead>
                <tr>
                    <th v-for="key in columns"
                        @click="sortBy(key)"
                        :class="{ active: sortKey == key }">
                        {{ columnNames[key] }}
                        <span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
                        </span>
                    </th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="entry in filteredData"
                    @click="showModal(entry)">
                    <td v-for="key in columns">
                        {{entry[key]}}
                    </td>
                </tr>
                <tr>
                    <td v-bind:colspan="columns.length" @click="newRow" class="new-row">+ 行を追加</td>
                </tr>
            </tbody>
        </table>
        <modal v-if="showEditModal" @close="showEditModal = false">
            <h3 slot="header">{{modalTitle}}</h3>
            <div slot="body">
                <div v-for="key in editItems" class="form-group">
                    <label class="col-xs-2">{{columnNames[key]}}</label><input class="form-control col-xs-10" type="text" v-model="modalItem[key]" />
                </div>
            </div>
        </modal>
    </div>
</script>
```

コンポーネント定義のjavascript (もちろんvue を new する前に）

```javascript:vue-grid-modal-editor.js

        Vue.component('modal', {
            template: '#modal-template'
        })

        Vue.component('edit-grid', {
            template: '#grid-template',
            props: {
                data: Array,
                columnNames: Object,
                columns: Array,
                editItems: Array,
                filterKey: String,
                modalTitle: String
            },
            data: function () {
                var sortOrders = {}
                this.columns.forEach(function (key) {
                    sortOrders[key] = 1
                })
                return {
                    sortKey: '',
                    sortOrders: sortOrders,
                    showEditModal: false,
                    modalItem: {},
                }
            },
            computed: {
                filteredData: function () {
                    var sortKey = this.sortKey
                    var filterKey = this.filterKey && this.filterKey.toLowerCase()
                    var order = this.sortOrders[sortKey] || 1
                    var data = this.data
                    if (filterKey) {
                        data = data.filter(function (row) {
                            return Object.keys(row).some(function (key) {
                                return String(row[key]).toLowerCase().indexOf(filterKey) > -1
                            })
                        })
                    }
                    if (sortKey) {
                        data = data.slice().sort(function (a, b) {
                            a = a[sortKey]
                            b = b[sortKey]
                            return (a === b ? 0 : a > b ? 1 : -1) * order
                        })
                    }
                    return data
                }
            },
            filters: {
            },
            methods: {
                sortBy: function (key) {
                    this.sortKey = key
                    this.sortOrders[key] = this.sortOrders[key] * -1
                },
                showModal: function (entry) {
                    this.showEditModal = true
                    this.modalItem = entry
                },
                newRow: function () {
                    alert("Create New Row!") // 新規行は未実装
                }
            }
        })
```

```css:vue-grid-modal-editor.css

table.editable {
    border: 2px solid #42b983;
    border-radius: 3px;
    background-color: #fff;
}


    table.editable th, td {
        min-width: 120px;
        padding: 10px 20px;
        border: 1px solid #fff;
    }

    table.editable th {
        background-color: #42b983;
        color: rgba(255,255,255,0.66);
        cursor: pointer;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
    }

    table.editable tr {
        background-color: #f9f9f9;
    }
    
    table.editable tbody tr:hover {
        cursor: pointer;
        text-decoration: underline;
        color: black;
        background-color: rgba(255,255,255,0.66);
    }


    table.editable th.active {
        color: #fff;
    }

        table.editable th.active .arrow {
            opacity: 1;
        }

    table.editable .arrow {
        display: inline-block;
        vertical-align: middle;
        width: 0;
        height: 0;
        margin-left: 5px;
        opacity: 0.66;
    }

        table.editable .arrow.asc {
            border-left: 4px solid transparent;
            border-right: 4px solid transparent;
            border-bottom: 4px solid #fff;
        }

        table.editable .arrow.dsc {
            border-left: 4px solid transparent;
            border-right: 4px solid transparent;
            border-top: 4px solid #fff;
        }


.modal-mask {
    position: fixed;
    z-index: 9998;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, .5);
    display: table;
    transition: opacity .3s ease;
}
```


# 使用方法と動作サンプル

[デモ(JSFiddle)](https://jsfiddle.net/pochaneco/c6f6xh4z/20/)を作りました