BREAKING CHANGE: `createApp` API has been adjusted.
  - `createApp()` now accepts the root component, and optionally a props
  object to pass to the root component.
  - `app.mount()` now accepts a single argument (the root container)
  - `app.unmount()` no longer requires arguments.
  New behavior looks like the following:
  ``` js
  const app = createApp(RootComponent)
  app.mount('#app')
  app.unmount()
  ```
		
	
			
		
			
				
	
	
		
			168 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			168 lines
		
	
	
		
			3.3 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: sortKey == key }">
 | |
|           {{ capitalize(key) }}
 | |
|           <span class="arrow" :class="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 DemoGrid = {
 | |
|   template: '#grid-template',
 | |
|   props: {
 | |
|     data: Array,
 | |
|     columns: Array,
 | |
|     filterKey: String
 | |
|   },
 | |
|   data() {
 | |
|     return {
 | |
|       sortKey: '',
 | |
|       sortOrders: this.columns.reduce((o, key) => (o[key] = 1, o), {})
 | |
|     }
 | |
|   },
 | |
|   computed: {
 | |
|     filteredData() {
 | |
|       const sortKey = this.sortKey
 | |
|       const filterKey = this.filterKey && this.filterKey.toLowerCase()
 | |
|       const order = this.sortOrders[sortKey] || 1
 | |
|       let data = this.data
 | |
|       if (filterKey) {
 | |
|         data = data.filter(row => {
 | |
|           return Object.keys(row).some(key => {
 | |
|             return String(row[key]).toLowerCase().indexOf(filterKey) > -1
 | |
|           })
 | |
|         })
 | |
|       }
 | |
|       if (sortKey) {
 | |
|         data = data.slice().sort((a, b) => {
 | |
|           a = a[sortKey]
 | |
|           b = b[sortKey]
 | |
|           return (a === b ? 0 : a > b ? 1 : -1) * order
 | |
|         })
 | |
|       }
 | |
|       return data
 | |
|     }
 | |
|   },
 | |
|   methods: {
 | |
|     sortBy(key) {
 | |
|       this.sortKey = key
 | |
|       this.sortOrders[key] = this.sortOrders[key] * -1
 | |
|     },
 | |
|     capitalize(str) {
 | |
|       return str.charAt(0).toUpperCase() + str.slice(1)
 | |
|     }
 | |
|   }
 | |
| }
 | |
| </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>
 | |
| Vue.createApp({
 | |
|   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 }
 | |
|     ]
 | |
|   })
 | |
| }).mount('#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>
 |