176 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			176 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<script src="../../dist/vue.global.js"></script>
 | 
						|
 | 
						|
<!-- DemoGrid component template -->
 | 
						|
<script type="text/x-template" id="grid-template">
 | 
						|
  <table v-if="filteredData.length">
 | 
						|
    <thead>
 | 
						|
      <tr>
 | 
						|
        <th v-for="key in columns"
 | 
						|
          @click="sortBy(key)"
 | 
						|
          :class="{ active: state.sortKey == key }">
 | 
						|
          {{ capitalize(key) }}
 | 
						|
          <span class="arrow" :class="state.sortOrders[key] > 0 ? 'asc' : 'dsc'">
 | 
						|
          </span>
 | 
						|
        </th>
 | 
						|
      </tr>
 | 
						|
    </thead>
 | 
						|
    <tbody>
 | 
						|
      <tr v-for="entry in filteredData">
 | 
						|
        <td v-for="key in columns">
 | 
						|
          {{entry[key]}}
 | 
						|
        </td>
 | 
						|
      </tr>
 | 
						|
    </tbody>
 | 
						|
  </table>
 | 
						|
  <p v-else>No matches found.</p>
 | 
						|
</script>
 | 
						|
<!-- DemoGrid component script -->
 | 
						|
<script>
 | 
						|
const { reactive, computed, toRefs } = Vue
 | 
						|
 | 
						|
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)
 | 
						|
 | 
						|
const DemoGrid = {
 | 
						|
  template: '#grid-template',
 | 
						|
  props: {
 | 
						|
    data: Array,
 | 
						|
    columns: Array,
 | 
						|
    filterKey: String
 | 
						|
  },
 | 
						|
  setup(props) {
 | 
						|
    const state = reactive({
 | 
						|
      sortKey: '',
 | 
						|
      sortOrders: props.columns.reduce((o, key) => (o[key] = 1, o), {})
 | 
						|
    })
 | 
						|
 | 
						|
    const filteredData = computed(() => {
 | 
						|
      let { data, filterKey } = props
 | 
						|
      if (filterKey) {
 | 
						|
        filterKey = filterKey.toLowerCase()
 | 
						|
        data = data.filter(row => {
 | 
						|
          return Object.keys(row).some(key => {
 | 
						|
            return String(row[key]).toLowerCase().indexOf(filterKey) > -1
 | 
						|
          })
 | 
						|
        })
 | 
						|
      }
 | 
						|
      const { sortKey } = state
 | 
						|
      if (sortKey) {
 | 
						|
        const order = state.sortOrders[sortKey]
 | 
						|
        data = data.slice().sort((a, b) => {
 | 
						|
          a = a[sortKey]
 | 
						|
          b = b[sortKey]
 | 
						|
          return (a === b ? 0 : a > b ? 1 : -1) * order
 | 
						|
        })
 | 
						|
      }
 | 
						|
      return data
 | 
						|
    })
 | 
						|
 | 
						|
    function sortBy(key) {
 | 
						|
      state.sortKey = key
 | 
						|
      state.sortOrders[key] *= -1
 | 
						|
    }
 | 
						|
 | 
						|
    return {
 | 
						|
      state,
 | 
						|
      filteredData,
 | 
						|
      sortBy,
 | 
						|
      capitalize
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
</script>
 | 
						|
 | 
						|
<!-- App template (in DOM) -->
 | 
						|
<div id="demo">
 | 
						|
  <form id="search">
 | 
						|
    Search <input name="query" v-model="searchQuery">
 | 
						|
  </form>
 | 
						|
  <demo-grid
 | 
						|
    :data="gridData"
 | 
						|
    :columns="gridColumns"
 | 
						|
    :filter-key="searchQuery">
 | 
						|
  </demo-grid>
 | 
						|
</div>
 | 
						|
<!-- App script -->
 | 
						|
<script>
 | 
						|
const App = {
 | 
						|
  components: {
 | 
						|
    DemoGrid
 | 
						|
  },
 | 
						|
  data: {
 | 
						|
    searchQuery: '',
 | 
						|
    gridColumns: ['name', 'power'],
 | 
						|
    gridData: [
 | 
						|
      { name: 'Chuck Norris', power: Infinity },
 | 
						|
      { name: 'Bruce Lee', power: 9000 },
 | 
						|
      { name: 'Jackie Chan', power: 7000 },
 | 
						|
      { name: 'Jet Li', power: 8000 }
 | 
						|
    ]
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
Vue.createApp().mount(App, '#demo')
 | 
						|
</script>
 | 
						|
 | 
						|
<style>
 | 
						|
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: rgba(255,255,255,0.66);
 | 
						|
  cursor: pointer;
 | 
						|
  -webkit-user-select: none;
 | 
						|
  -moz-user-select: none;
 | 
						|
  -ms-user-select: none;
 | 
						|
  user-select: none;
 | 
						|
}
 | 
						|
 | 
						|
td {
 | 
						|
  background-color: #f9f9f9;
 | 
						|
}
 | 
						|
 | 
						|
th, td {
 | 
						|
  min-width: 120px;
 | 
						|
  padding: 10px 20px;
 | 
						|
}
 | 
						|
 | 
						|
th.active {
 | 
						|
  color: #fff;
 | 
						|
}
 | 
						|
 | 
						|
th.active .arrow {
 | 
						|
  opacity: 1;
 | 
						|
}
 | 
						|
 | 
						|
.arrow {
 | 
						|
  display: inline-block;
 | 
						|
  vertical-align: middle;
 | 
						|
  width: 0;
 | 
						|
  height: 0;
 | 
						|
  margin-left: 5px;
 | 
						|
  opacity: 0.66;
 | 
						|
}
 | 
						|
 | 
						|
.arrow.asc {
 | 
						|
  border-left: 4px solid transparent;
 | 
						|
  border-right: 4px solid transparent;
 | 
						|
  border-bottom: 4px solid #fff;
 | 
						|
}
 | 
						|
 | 
						|
.arrow.dsc {
 | 
						|
  border-left: 4px solid transparent;
 | 
						|
  border-right: 4px solid transparent;
 | 
						|
  border-top: 4px solid #fff;
 | 
						|
}
 | 
						|
</style>
 |