Vue.js——基于$.ajax实现数据的跨域增删查改
概述
之前我们学习了 Vue.js 的一些基础知识,以及如何开发一个组件,然而那些示例的数据都是 local 的。
在实际的应用中,几乎 90% 的数据是来源于服务端的,前端和服务端之间的数据交互一般是通过 ajax 请求来完成的。
说起 ajax 请求,大家第一时间会想到 jQuery。除了拥有强大的 DOM 处理能力,jQuery 提供了较丰富的 ajax 处理方法,它不仅支持基于 XMLHttpRequest 的 ajax 请求,也能处理跨域的 JSONP 请求。
之前有读者问我,Vue.js 能结合其他库一起用吗?答案当然是肯定的,Vue.js 和 jQuery 一起使用基本没有冲突,尽可放心大胆地使用。
本文的主要内容如下:
- 同源策略和跨域概念
- 跨域资源共享
- JSONP 概念
- REST Web Services
- 基于 $.ajax 实现跨域 GET 请求
- 基于 $.ajax 实现 JSONP 请求
- 基于 $.ajax 实现跨域 POST 请求
- 基于 $.ajax 实现跨域 PUT 请求
- 基于 $.ajax 实现跨域 DELETE 请求
本文的服务端程序和客户端程序是部署在不同服务器上的,本文所有示例请求都是跨域的。
源代码已放到 GitHub,如果您觉得本篇内容不错,请点个赞,或在 GitHub 上加个星星!
基础概念
在进入本文正题之前,我们需要先了解一些基础概念(如果你已经对这些基础有所了解,可跳过此段落)。
同源策略和跨域概念
同源策略(Same-orgin policy)限制了一个源(orgin)中加载脚本或脚本与来自其他源(orgin)中资源的交互方式。
如果两个页面拥有相同的协议(protocol),端口(port)和主机(host),那么这两个页面就属于同一个源(orgin)。
同源之外的请求都可以称之为跨域请求。
下表给出了相对http://store.company.com/dir/page.html同源检测的示例:
我们可以简单粗暴地理解为同一站点下的资源相互访问都是同源的,跨站点的资源访问都是跨域的。
跨域资源共享
跨域资源共享(CORS)是一份浏览器技术的规范,提供了 Web 服务器从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,是 JSONP 模式的现代版。与 JSONP 不同,CORS 除了支持 GET 方法以外,还支持其他 HTTP 方法。用 CORS 可以让网页设计师用一般的 XMLHTTPRequest,这种方式的错误处理比 JSONP 要来的好。另一方面,JSONP 可以在不支持 CORS 的老旧浏览器上运作,现代的浏览器都支持 CORS。
在网页http://caniuse.com/#feat=cors上列出了主流浏览器对 CORS 的支持情况,包含 PC 端和移动端的浏览器。
JSONP 概念
由于同源策略,一般来说不允许 JavaScript 跨域访问其他服务器的页面对象,但是 HTML 的 <script> 元素是一个例外。利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的 JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。
REST Web Services 简介
HTTP 协议是 Web 的标准之一,HTTP 协议包含一些标准的操作方法,例如:GET, POST, PUT, Delete
等,用这些方法能够实现对 Web 资源的 CURD 操作,下表列出了这些方法的操作定义。
HTTP 方法 | 资源处理 | 说明 |
---|---|---|
GET | 读取资源(Read) | 获取被请求 URI(Request-URI)指定的信息(以实体的格式)。 |
POST | 创建资源(Create) | 在服务器上创建一个新的资源,并返回新资源的 URI。 |
PUT | 更新资源(Update) | 指定 URI 资源存在则更新资源,指定 URI 资源不存在则创建一个新资源。 |
DELETE | 删除资源(Delete) | 删除请求 URI 指定的资源。 |
在 REST 应用中,默认通过 HTTP 协议,并且使用 GET、POST、PUT 和 DELETE 方法对资源进行操作,这样的设计风格和 Web 标准完全契合。
REST 的最佳应用场景是公开服务,使用 HTTP 并遵循 REST 原则的 Web 服务,我们称之为 RESTful Web Service。RESTful Web Service 从以下三个方面进行资源定义:
- 直观简短的资源地址:URI,比如:http://example.com/resources/
- 传输的资源:Web Service 接受与返回的互联网媒体类型,比如 JSON,XML 等
- 对资源的操作:Web Service 在该资源上所支持的一系列请求方法(比如:GET,POST,PUT 或 Delete)
下图展示了 RESTful Web Service 的执行流程
本文的服务端程序是基于 ASP.NET Web API 构建的。
在了解了这些基础知识后,我们就分别来构建服务端程序和客户端程序吧。
服务端环境准备
如果您是前端开发人员,并且未接触过 ASP.NET Web API,可以跳过此段落。
新建 Web API 应用程序
添加 Model, Controller
初始化数据库
分别执行以下 3 个命令:
打开 VS 的 Server Explorer,选择刚创建好的数据库,右键 New Query,执行以下 sql 语句:
INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (1, N'ALFKI', N'Alfreds Futterkiste', N'Maria Anders', N'Sales Representative', N'Obere Str. 57', N'Berlin', NULL, N'12209', N'Germany', N'030-0074321', N'030-0076545', 1) INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (2, N'ANATR', N'Ana Trujillo Emparedados y helados', N'Ana Trujillo', N'Owner', N'Avda. de la Constitución 2222', N'México D.F.', NULL, N'05021', N'Mexico', N'(5) 555-4729', N'(5) 555-3745', 1)INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (3, N'ANTON', N'Antonio Moreno Taquería', N'Antonio Moreno', N'Owner', N'Mataderos 2312', N'México D.F.', NULL, N'05023', N'Mexico', N'(5) 555-3932', NULL, 1)INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (4, N'AROUT', N'Around the Horn', N'Thomas Hardy', N'Sales Representative', N'120 Hanover Sq.', N'London', NULL, N'WA1 1DP', N'UK', N'(171) 555-7788', N'(171) 555-6750', 1)INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (5, N'BERGS', N'Berglunds snabbköp', N'Christina Berglund', N'Order Administrator', N'Berguvsvägen 8', N'Luleå', NULL, N'S-958 22', N'Sweden', N'0921-12 34 65', N'0921-12 34 67', 1) INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (6, N'BLAUS', N'Blauer See Delikatessen', N'Hanna Moos', N'Sales Representative', N'Forsterstr. 57', N'Mannheim', NULL, N'68306', N'Germany', N'0621-08460', N'0621-08924', 1) INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (7, N'BLONP', N'Blondesddsl père et fils', N'Frédérique Citeaux', N'Marketing Manager', N'24, place Kléber', N'Strasbourg', NULL, N'67000', N'France', N'88.60.15.31', N'88.60.15.32', 1) INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (8, N'BOLID', N'Bólido Comidas preparadas', N'Martín Sommer', N'Owner', N'C/ Araquil, 67', N'Madrid', NULL, N'28023', N'Spain', N'(91) 555 22 82', N'(91) 555 91 99', 1)INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (9, N'BONAP', N'Bon app''', N'Laurence Lebihan', N'Owner', N'12, rue des Bouchers', N'Marseille', NULL, N'13008', N'France', N'91.24.45.40', N'91.24.45.41', 1) INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (10, N'BOTTM', N'Bottom-Dollar Markets', N'Elizabeth Lincoln', N'Accounting Manager', N'23 Tsawassen Blvd.', N'Tsawassen', N'BC', N'T2F 8M4', N'Canada', N'(604) 555-4729', N'(604) 555-3745', 1)INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (11, N'BSBEV', N'B''s Beverages', N'Victoria Ashworth', N'Sales Representative', N'Fauntleroy Circus', N'London', NULL, N'EC2 5NT', N'UK', N'(171) 555-1212', NULL, 1)INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (12, N'CACTU', N'Cactus Comidas para llevar', N'Patricio Simpson', N'Sales Agent', N'Cerrito 333', N'Buenos Aires', NULL, N'1010', N'Argentina', N'(1) 135-5555', N'(1) 135-4892', 1)INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (13, N'CENTC', N'Centro comercial Moctezuma', N'Francisco Chang', N'Marketing Manager', N'Sierras de Granada 9993', N'México D.F.', NULL, N'05022', N'Mexico', N'(5) 555-3392', N'(5) 555-7293', 1)INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (14, N'CHOPS', N'Chop-suey Chinese', N'Yang Wang', N'Owner', N'Hauptstr. 29', N'Bern', NULL, N'3012', N'Switzerland', N'0452-076545', NULL, 1) INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (15, N'COMMI', N'Comércio Mineiro', N'Pedro Afonso', N'Sales Associate', N'Av. dos Lusíadas, 23', N'Sao Paulo', N'SP', N'05432-043', N'Brazil', N'(11) 555-7647', NULL, 1)INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (16, N'CONSH', N'Consolidated Holdings', N'Elizabeth Brown', N'Sales Representative', N'Berkeley Gardens 12 Brewery', N'London', NULL, N'WX1 6LT', N'UK', N'(171) 555-2282', N'(171) 555-9199', 1)INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (17, N'DRACD', N'Drachenblut Delikatessen', N'Sven Ottlieb', N'Order Administrator', N'Walserweg 21', N'Aachen', NULL, N'52066', N'Germany', N'0241-039123', N'0241-059428', 1) INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (18, N'DUMON', N'Du monde entier', N'Janine Labrune', N'Owner', N'67, rue des Cinquante Otages', N'Nantes', NULL, N'44000', N'France', N'40.67.88.88', N'40.67.89.89', 1) INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (19, N'EASTC', N'Eastern Connection', N'Ann Devon', N'Sales Agent', N'35 King George', N'London', NULL, N'WX3 6FW', N'UK', N'(171) 555-0297', N'(171) 555-3373', 1)INSERT [dbo].[Customers] ([CustomerId], [CustomerCode], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [CustomerType]) VALUES (20, N'ERNSH', N'Ernst Handel', N'Roland Mendel', N'Sales Manager', N'Kirchgasse 6', N'Graz', NULL, N'8010', N'Austria', N'7675-3425', N'7675-3426', 1)
让 Web API 以 CamelCase 输出 JSON
C# 偏向于 PascalCase 的命名规范,而 JavaScript 则偏向于 camelCase 的命名规范,为了让 JavaScript 接收到的 JSON 数据是 camelCase 的,在 Global.asax 文件中添加以下几行代码:
var formatters = GlobalConfiguration.Configuration.Formatters; var jsonFormatter = formatters.JsonFormatter; var settings = jsonFormatter.SerializerSettings; settings.Formatting = Formatting.Indented; settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
可以在以下地址访问构建好的 Web API:
http://211.149.193.19:8080/Help
访问 Customers 数据:
http://211.149.193.19:8080/api/Customers
创建组件和 AjaxHelper
本文的示例仍然是表格组件的 CURD,只不过这次咱们的数据是从服务端获取的。
在实现数据的 CURD 之前,我们先准备好一些组件和 Ajax 帮助方法。
创建和注册 simple-grid 组件
simple-grid 组件用于绑定和显示数据
<template id="grid-template"> <table> <thead> <tr> <th v-for="col in columns"> {{col | capitalize}} </th> </tr> </thead> <tbody> <tr v-for="(index,entry) in dataList"> <td v-for="col in columns"> {{entry[col]}} </td> </tr> </tbody> </table> </template><script src="js/vue.js"></script>
<script>
Vue.component('simple-grid', {
template: '#grid-template',
props: ['dataList', 'columns']
})
</script>
创建和注册 modal-dialog 组件
数据的新建和编辑将使用模态对话框,modal-dialog 组件的作用就在于此。
<template id="dialog-template"> <div class="dialogs"> <div class="dialog" v-bind:class="{'dialog-active': show}"> <div class="dialog-content"> <div class="close rotate"> <span class="iconfont icon-close" @click="close"></span> </div> <slot name="header"></slot> <slot name="body"></slot> <slot name="footer"></slot> </div> </div> <div class="dialog-overlay"></div> </div> </template><script>
Vue.component('modal-dialog', {
template: '#dialog-template',
props: ['show'],
methods: {
close: function() {
this.show = false
}
}
})
</script>
AjaxHelper
基于 $.ajax 声明一个简单的 AjaxHelper 构造器,AjaxHelper 构造器的原型对象包含 5 个方法,分别用于处理GET, POST, PUT, DELETE和JSONP
请求。
function AjaxHelper() {this.ajax = function(url, type, dataType, data, callback) { $.ajax({ url: url, type: type, dataType: dataType, data: data, success: callback, error: function(xhr, errorType, error) {alert('Ajax request error, errorType:' + errorType + ', error:' + error) } })}} AjaxHelper.prototype.get = function(url, data, callback) {this.ajax(url, 'GET', 'json', data, callback) } AjaxHelper.prototype.post = function(url, data, callback) {this.ajax(url, 'POST', 'json', data, callback) }AjaxHelper.prototype.put = function(url, data, callback) {
this.ajax(url, 'PUT', 'json', data, callback)
}AjaxHelper.prototype.delete = function(url, data, callback) {
this.ajax(url, 'DELETE', 'json', data, callback)
}AjaxHelper.prototype.jsonp = function(url, data, callback) {
this.ajax(url, 'GET', 'jsonp', data, callback)
}AjaxHelper.prototype.constructor = AjaxHelper
实现 GET 请求
发送 get 请求
var ajaxHelper = new AjaxHelper()var demo = new Vue({
el: '#app',
data: {
gridColumns: ['customerId', 'companyName', 'contactName', 'phone'],
gridData: [],
apiUrl: 'http://localhost:15341/api/Customers'
},
ready: function() {
this.getCustomers()
},
methods: {getCustomers: function() { // 定义vm变量,让它指向this,this是当前的Vue实例 var vm = this, callback = function(data) { // 由于函数的作用域,这里不能用this vm.$set('gridData', data) } ajaxHelper.get(vm.apiUrl, null, callback) } }
})
由于客户端和服务端 Web API 是分属于不同站点的,它们是不同的源,这构成了跨域请求。
这时请求是失败的,浏览器会提示一个错误:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1::8020' is therefore not allowed access.
跨域解决方案
现在碰到了请求跨域的问题,结合前面讲的一些概念,我们大致可以猜到解决跨域请求的两种方式:
- 在服务端启用 CORS。
- 让无服务端拥有处理 JSONP 的能力。
这两种跨域解决方案的区别是什么呢?
- JSONP 只支持 GET 请求;CORS 则支持 GET、POST、PUT、DELETE 等标准的 HTTP 方法
- 使用 JSONP 时,服务端要处理客户端请求的 callback 参数("callback" 这个名称是可以指定的);而使用 CORS 则不需要提供这样的处理。
- JSONP 从服务端获取到的是 script 文件;CORS 则是一段 XML 或 JSON 或其他格式的数据
- JSONP 支持 IE8, IE9 复古的浏览器;CORS 则支持现代主流的浏览器
选择 JSONP 还是 CORS?除了极少数的情况,我们都应当选择 CORS 作为最佳的跨域解决方案。
启用 CORS
在 Nuget Package Manager Console 下输入以下命令:
Install-Package Microsoft.AspNet.WebApi.Cors
首先,在 WebApiConfig 中启用 CORS
public static class WebApiConfig {public static void Register(HttpConfiguration config) { // ... config.EnableCors();} }
然后在 CustomersController 上添加 EnableCors 特性,origins、headers 和 methods 都设为 *
[EnableCors(origins: "*", headers: "*", methods: "*")] public class CustomersController : ApiController {}
Web API 的 CORS 配置说明及原理,下面这个地址已经讲得很清楚了:
http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api
刷新页面,现在数据可以正常显示了。
在 Chrome 开发工具的 Network 选项下,可以看到 Response Header 的 Content-Type 是 application/json。
文章开头也说了,CORS 的跨域方式支持现代的主流浏览器,但是不支持 IE9 及以下这些古典的浏览器。
如果我们偏要在 IE9 浏览器中(Vue.js 不支持 IE8 及以下的浏览器,所以不考虑 IE 6,7,8)实现跨域访问,该怎么做呢?
答案是使用 JSONP 请求。
实现 JSONP 请求
将 getCustomers 方法的 get 请求变更为 jsonp 请求:
getCustomers: function() { // 定义 vm 变量,让它指向 this,this 是当前的 Vue 实例 var vm = this, callback = function(data) { // 由于函数的作用域,这里不能用 this vm.$set('gridData', data) } ajaxHelper.jsonp(vm.apiUrl, null, callback) }
刷新页面,请求虽然成功了,但画面没有显示数据,并且弹出了请求错误的消息。
让 WebAPI 支持 JSONP 请求
在 Nuget Package Manager 中输入以下命令:
Install-Package WebApiContrib.Formatting.Jsonp
然后在 Global.asax 的 Application_Start() 方法中注册 JsonpMediaTypeFormatter:
// 注册 JsonpMediaTypeFormatter,让 WebAPI 能够处理 JSONP 请求 config.Formatters.Insert(0, new JsonpMediaTypeFormatter(jsonFormatter));
刷新页面,使用 JSONP 请求也能够正常显示数据了。
注意:使用 JSONP 时,服务端返回的不再是一段 JSON 了,而是一个 script 脚本。
在 IE9 下查看该页面,simple-grid 组件也能正常显示数据:
实现 POST 请求
1. 创建表单对话框
添加一个 Create 按钮,然后使用 modal-dialog 组件创建一个表单对话框:
<div id="app"><!--...已省略--> <div class="container"> <div class="form-group"> <button @click="this.show = true">Create</button> </div> </div> <modal-dialog v-bind:show.sync="show"> <header class="dialog-header" slot="header"> <h1 class="dialog-title">Create New Customer</h1> </header> <div class="dialog-body" slot="body"> <div v-show="item.customerId" class="form-group"> <label>Customer Id</label> <input type="text" v-model="item.customerId" disabled="disabled" /> </div> <div class="form-group"> <label>Company Name</label> <input type="text" v-model="item.companyName" /> </div> <div class="form-group"> <label>Contact Name</label> <input type="text" v-model="item.contactName" /> </div> <div class="form-group"> <label>Phone</label> <input type="text" v-model="item.phone" /> </div> <div class="form-group"> <label></label> <button @click="createCustomer">Save</button> </div> </div> </modal-dialog>
</div>
注意:在新建 Customer 时,由于 item.customerId 为空,所以 item.customerId 关联的表单不会显示;在修改 Customer 时,item.customerId 关联的表单会显示出来。另外,item.customerId 是不可编辑的。
2. 修改 Vue 实例
var demo = new Vue({ el: '#app', data: { show: false, gridColumns: [{ name: 'customerId', isKey: true }, {name: 'companyName'}, {name: 'contactName'}, {name: 'phone'}], gridData: [], apiUrl: 'http://localhost:15341/api/Customers', item: {}}, ready: function() {this.getCustomers() }, methods: { // ... 已省略 createCustomer: function() { var vm = this, callback = function(data) {vm.$set('item', {}) // 添加成功后,重新加载页面数据 vm.getCustomers() } // 将 vm.item 直接 POST 到服务端 ajaxHelper.post(vm.apiUrl, vm.item, callback) this.show = false } } })
修改 Vue 实例的 data 选项:
- 添加
show
属性:用于显示或隐藏表单对话框 - 修改
gridColumns
属性:列包含两个属性,name 表示列名称,isKey 表示列是否为主键列 - 添加
item
属性:用于新增 Customer 或修改 Customer
添加 createCustomer 方法:
createCustomer: function() { var vm = this, callback = function(data) {vm.$set('item', {}) // 添加成功后,重新加载页面数据 vm.getCustomers() } // 将 vm.item 直接 POST 到服务端 ajaxHelper.post(vm.apiUrl, vm.item, callback) this.show = false }
实现 PUT 请求
1. 修改 sample-grid 的 template
给主键列添加链接,绑定 click 事件,事件指向 loadEntry 方法,loadEntry 方法用于加载当前选中的数据。
<template id="grid-template"> <table> <thead> <tr> <th v-for="col in columns"> {{col.name | capitalize}} </th> </tr> </thead> <tbody> <tr v-for="(index,entry) in dataList"> <td v-for="col in columns"> <span v-if="col.isKey"><a href="javascript:void(0)" @click="loadEntry(entry[col.name])">{{entry[col.name] }}</a></span> <span v-else>{{entry[col.name] }}</span> </td> </tr> </tbody> </table> </template>
2. 修改 simple-grid 的 methods 选项
在 simple-grid 的 methods 选项下,添加一个loadEntry
方法,该方法调用 $.dispatch 向父组件派发事件load-entry
,并将 key 作为事件的入参,load-entry
是绑定在父组件的事件名称。
Vue.component('simple-grid', { template: '#grid-template', props: ['dataList', 'columns'], methods: {loadEntry: function(key) {this.$dispatch('load-entry', key) } } })
3. 修改 Vue 实例的 HTML
在 Vue 实例的 simple-grid 标签上绑定自定义事件load-entry
,load-entry
事件指向loadCustomer
方法,loadCustomer
方法用于加载选中的 Customer 数据。
<div id="app"> <!--... 已省略 --> <div class="container"> <simple-grid :data-list="gridData" :columns="gridColumns" v-on:load-entry="loadCustomer"> </simple-grid> </div> <!--... 已省略 --> </div>
我们应将 2 和 3 结合起来看,下图阐述了从点击 simple-grid 数据的链接开始,到最终打开对话框的完整过程:
注意:load-entry
是 Vue 实例的事件,而不是 simple-grid 组件的事件,尽管 load-entry 是写在 simple-grid 标签上的。详情请参考上一篇文章的编译作用域
由于在新建模式和修改模式下标题内容是不同的,因此需要修改 modal-dialog 的slot="header"
部分。
根据item.customerId
是否有值确定修改模式和新建模式,修改模式下显示 "Edit Customer - xxx",新建模式下显示 "Create New Customer"
<modal-dialog v-bind:show.sync="show"><header class="dialog-header" slot="header"> <h1 class="dialog-title">{{ item.customerId ? 'Edit Customer - ' + item.contactName : 'Create New Customer' }}</h1> </header>
</modal-dialog>
4. 修改 Vue 实例
为 data 选项添加title
属性,用于显示对话框的标题。
var demo = new Vue({ el: '#app', data: { // ... 已省略 title: '' // ... 已省略 } // ... 已省略 })
然后追加 3 个方法:loadCustomer
, saveCustomer
和updateCustomer
。
loadCustomer: function(customerId) { var vm = this vm.gridData.forEach(function(item) {if (item.customerId === customerId) { // 使用 $.set 设置 item vm.$set('item', item) return } }), vm.$set('show', true) }, saveCustomer: function() {this.item.customerId ? this.updateCustomer() : this.createCustomer()this.show = false}, updateCustomer: function() { // 定义 vm 变量,让它指向 this,this 是当前的 Vue 实例 var vm = this, callback = function(data) { // 更新成功后,重新加载页面数据 vm.getCustomers() } // 将 vm.item 直接 PUT 到服务端 ajaxHelper.put(vm.apiUrl + '/' + vm.item.customerId, vm.item, callback) }
saveCustomer
方法根据item.customerId
是否有值确定修改模式和新建模式,如果是新建模式则调用createCustomer
方法,如果是修改模式则调用updateCustomer
方法。
另外,需要将 Save 按钮的 click 事件绑定到 saveCustomer 方法。
<div class="dialog-body" slot="body"> <!--... 已省略 --> <div class="form-group"> <label></label> <button @click="saveCustomer">Save</button> </div> <!--... 已省略 --> </div>
5. 添加 $watch
使用 $watch 跟踪 data 选项 show 属性的变化,每当关闭对话框时就重置 item。
demo.$watch('show', function(newVal, oldVal){if(!newVal){this.item = {} } })
为什么要这么做呢?因为每次打开对话框,不知道是以新建模式还是修改模式打开的,如果不重置 item,倘若先以修改模式打开对话框,再以新建模式打开对话框,新建模式的对话框将会显示上次打开的数据。
实现 DELETE 请求
1. 修改 simple-grid 组件
在 methods 选项中添加方法deleteEntry
:
deleteEntry: function(entry) {this.$dispatch('delete-entry', entry) }
调用$.dispatch
向父组件派发事件delete-entry
。
2. 修改 Vue 实例的 HTML
在 simple-grid 标签上绑定自定义事件delete-entry
,该事件指向deleteCustomer
方法。
<div id="app"> <!--... 已省略 --> <div class="container"> <simple-grid :data-list="gridData" :columns="gridColumns" v-on:load-entry="loadCustomer" v-on:delete-entry="deleteCustomer"> </simple-grid> </div> <!--... 已省略 --> </div>
总结
本篇介绍了同源策略、跨域、CORS 和 REST 等概念,并结合 Vue.js 和 $.ajax 实现了一个简单的 CURD 跨域示例。
下一篇,我们将使用 Vue 的插件 vue-resource 来完成这些工作。