demo

by elick

HTML

<script src="https://cdn.jsdelivr.net/vue/1.0.24/vue.js"></script>
	<div id="app">
		<div class="container">
			<div class="form-group">
				<label>Search</label>
				<input type="text" class="search-input" v-model="searchQuery" />
			</div>

		</div>
		<div class="container">
			<simple-grid :data-list="people" :columns="columns" :search-key="searchQuery"></simple-grid>
		</div>
	</div>

	<template id="grid-template">
		<table>
			<thead>
				<tr>
					<th v-for="col in columns" @click="sortBy(col)">
						{{ col.name | capitalize}}
					</th>
					<th>
						Delete
					</th>
				</tr>
			</thead>
			<tbody>
				<tr v-for="(index,entry) in dataList | filterBy searchKey | orderBy sortKey sortOrder">
					<td v-for="col in columns">
						<span v-if="col.isKey"><a href="javascript:void(0)" @click="openEditItemDialog(index,'Edit item '+entry[col.name])">{{ entry[col.name] }}</a></span>
						<span v-else>{{entry[col.name]}}</span>

					</td>
					<td class="text-center">
						<button class="btn-danger" @click="deleteItem(entry)">delete</button>
					</td>
				</tr>
			</tbody>
		</table>
		<!-- 测试不用广播打开对话, test 必须是 :test.sync 同步模式 这样在子组件关闭的时候,父组件也知道状态  可能广播更科学吧 利于解耦 -->
		<!-- 前面是子后面是父  :fields="columns" :fields 子 columns 父-->
		<!--  @create-item="createItem" 这个绑定事件 就和其他不一样了 两个都是父东西 把父事件 create-item 绑定到 createItem 方法 -->
		<!-- 又看遍文档  -@-> 在子组件上监听自定义事件(当子组件触发 “my-event” 时将调用事件处理器) -->
		<!-- 其实是约定,自定义子组件有这么一个事件, 如果子组件触发,发送了这个事件, 父组件就用 **同名** 的事件处理器,处理这个事件的响应 -->
		<!-- 或者这么理解吧 有助于记忆 绑定子发送的事件 @create-item 到父方法 createItem  这样就又是 子:父 -->
		<modal-dialog :mode="mode" :title="title" :fields="columns" :item="item" :test.sync='test' @create-item="createItem" @update-item="updateItem">
		</modal-dialog>
		<div class="container">
			<button class="btn" @click="openNewItemDialog('Create new item')">Create</button>
		</div>

	</template>

	<template id="dialog-template">
		<div id="dialogs">
			<!-- <div class="dialog" :class="{ 'dialog-active' : test }"> -->
			<div...

CSS

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: Helvetica, simhei, Arial, sans-serif;
}

html {
  font-size: 1rem;
}

body {
  margin-top: 100px;
}

table,
td,
th {
  border-collapse: collapse;
  border-spacing: 0
}

table {
  width: 100%;
}

td,
th {
  border: 1px solid #bcbcbc;
  padding: 5px 10px;
}

th {
  padding: 10px;
  font-weight: 400;
  color: #fff;
  background: #0090d3;
  cursor: pointer;
}

tr:nth-of-type(odd) {
  background: #fff
}

tr:nth-of-type(even) {
  background: #eee
}

h1 {
  font-size: 1.5rem;
  margin-bottom: 2rem;
}

input {
  outline: none
}

input[type=text] {
  padding: 3px 6px;
  font-size: 1.2rem;
  border: 1px solid #ccc;
}

input[type=text]:focus {
  border-color: #0090d3;
  transition: .3s ease-in;
}

button {
  display: inline-block;
  box-sizing: border-box;
  padding: 10px 30px;
  background: #0090d3;
  color: #fff;
  border: 1px solid #0090d3;
  border-radius: 3px;
  outline: 0;
  transition: .4s ease-out;
}

button:hover,
button:focus {
  opacity: 0.8;
  cursor: pointer;
  transition: .15s ease-in;
}

#app {
  margin: 0 auto;
  max-width: 640px;
}

.btn-danger {
  padding: 5px 15px;
  border: 1px solid salmon;
  background: salmon;
}

.btn-save {
  border: 1px solid #0090d3;
  background: #0090d3;
}

.btn-close {
  border: 1px solid #ccc;
  background: #ccc;
}

.container {
  padding-left: 15px;
  padding-right: 15px;
  margin: 10px;
}

.search-input {
  width: 80%;
}

.form-group {
  margin: 10px;
}

.form-group > label {
  display: inline-block;
  padding-right: 1rem;
  width: 5rem;
  text-align: right;
}

.form-group > input,
.form-group > select {
  display: inline-block;
  height: 1.8rem;
  line-height: 1.8rem;
}

.text-center {
  text-align: center;
}

.dialog {
  width: 480px;
  position: fixed;
  left: 50%;
  top: 2em;
  transform: translateX(-50%);
  z-index: 2000;
  visibility: hidden;
  backface-visibility: hidden;
  perspective: 1300px;
}

.dialog-active {
  visibility:...

JavaScript

Vue.component('simple-grid', {
			template: '#grid-template',
			props: ['dataList', 'columns', 'searchKey'],
			data: function() {
				return {
					sortKey: '',
					sortOrder: -1,
					mode: 0,
					item: {},
					title: '',
					//currentKey: '',
					test: false //测试是可行的 不过必须是同步状态
				}
			},
			methods: {
				deleteItem: function(entry) {
					var data = this.dataList
					data.forEach(function(item, i) {
						if (item === entry) {
							data.splice(i, 1)
							return
						}
					})
				},
				sortBy: function(key) {
					//alert(key.name)
					this.sortKey = key.name;
					this.sortOrder = this.sortOrder > 0 ? -1 : 1;
				},
				openNewItemDialog: function(title) {
					//测试不用广播打开对话框 //测试是可行的 不过必须是同步状态 //广播利于解耦
					this.test = true;
					//对话框标题
					this.title = title;
					// mode = 1 表示新建模式
					this.mode = 1;
					// 初始化this.item  //原版为空
					this.item = {
						sex: 'Male'
					};
					// 广播事件,showDialog是modal-dialog组件的一个方法,传入参数true表示显示对话框
					//广播比派发简单多了 不用绕一下不错.
					this.$broadcast('showDialog', true);
				},
				openEditItemDialog: function(index, title) {
						// 对话框标题
						this.title = title;
						// 模式2 修改
						this.mode = 2;
						// 修改数据
						this.item = this.dataList[index];
						// 打开对话框
						this.$broadcast('showDialog',true);
						//修改当期key 以便保存的时候用.
						this.currentKey = index;

//下面是曾经测试用的

						// //终于明白API说的 不能检测是啥意思了. 这个可以这么设置,但是页面不能自动刷新
						// //虽然数据变了,但是页面上不显示
						// this.dataList[index] = {
						// 		name: 'elick',
						// 		age: 34,
						// 		sex: 'Male'
						// 	}
						// 	//所以用这种方法可以更新数据数组,那么原本的那些复杂的写法真就不懂干啥用了
						// this.dataList.$set(index, {
						// 	name: 'xwiwi',
						// 	age: '24',
						// 	sex: 'Female'
						// })
						// var aa = this.dataList[index];
						// console.log(aa);
						//

				},
				createItem: function() {
					if(!this.itemExists()){
						alert('这是方法');
						//测试不用广播关闭对话框
						this.test =...