Edit in JSFiddle

<div id="demo">
    <table v-component="grid" v-with="gridOptions"></table>
</div>
<p style="font-size:12px">* You can click on the headers to sort the table.</p>

<script type="text/x-template" id="grid-template">
    <table>
        <thead>
            <tr>
                <th v-repeat="columns" v-on="click:sortBy(key)">{{header}}</th>
            </tr>
        </thead>
        <tbody>
            <tr v-repeat="data">
                <!-- access outer loop's data using $parent -->
                <td v-repeat="columns">{{$parent[key]}}</td>
            </tr>
        </tbody>
    </table>
</script>
// raw data
var data = [
    { name: 'Chuck Norris', power: Infinity },
    { name: 'Bruce Lee', power: 9000 },
    { name: 'Jacky Chang', power: 7000 },
    { name: 'Jet Li', power: 8000 }
]

// register the grid component
// use `replace: true` because without the wrapping <table>
// the template won't be parsed properly
Vue.component('grid', {
    template: '#grid-template',
    replace: true,
    created: function () {
        this.ascending = {}
    },
    methods: {
        sortBy: function (key) {
            var asc = this.ascending[key] = !this.ascending[key]
            this.data.sort(function (a, b) {
                var res = a[key] > b[key]
                if (asc) res = !res
                return res ? 1 : -1
            })
        }
    }
})

// bootstrap the demo
new Vue({
    el: '#demo',
    data: {
        gridOptions: {
            data: data,
            columns: [
                { header: 'Name', key: 'name' },
                { header: 'Power', key: 'power' }
            ]
        }
    }
})
body {
    font-family: Helvetica Neue, Arial, sans-serif;
    font-size: 14px;
    color: #444;
}
table {
    border: 2px solid #42b983;
    border-radius: 3px;
    background-color: #fff;
}
th {
    background-color: #42b983;
    color: #fff;
    cursor: pointer;
}
td {
    background-color: #f3f3f3;
}
th, td {
    padding: 10px 20px;
}