以前咱们学习了Vue.js的一些基础知识,以及如何开发一个组件,然而那些示例的数据都是local的。
在实际的应用中,几乎90%的数据是来源于服务端的,前端和服务端之间的数据交互通常是经过ajax请求来完成的。javascript
提及ajax请求,你们第一时间会想到jQuery。除了拥有强大的DOM处理能力,jQuery提供了较丰富的ajax处理方法,它不只支持基于XMLHttpRequest的ajax请求,也能处理跨域的JSONP请求。html
以前有读者问我,Vue.js能结合其余库一块儿用吗?答案固然是确定的,Vue.js和jQuery一块儿使用基本没有冲突,尽可放心大胆地使用。前端
本文的主要内容以下:vue
本文的服务端程序和客户端程序是部署在不一样服务器上的,本文全部示例请求都是跨域的。
源代码已放到GitHub,若是您以为本篇内容不错,请点个赞,或在GitHub上加个星星!java
在进入本文正题以前,咱们须要先了解一些基础概念(若是你已经对这些基础有所了解,可跳过此段落)。jquery
同源策略(Same-orgin policy)限制了一个源(orgin)中加载脚本或脚本与来自其余源(orgin)中资源的交互方式。
若是两个页面拥有相同的协议(protocol),端口(port)和主机(host),那么这两个页面就属于同一个源(orgin)。git
同源以外的请求均可以称之为跨域请求。
下表给出了相对http://store.company.com/dir/page.html同源检测的示例:github
咱们能够简单粗暴地理解为同一站点下的资源相互访问都是同源的,跨站点的资源访问都是跨域的。web
跨域资源共享(CORS)是一份浏览器技术的规范,提供了Web服务器从不一样网域传来沙盒脚本的方法,以避开浏览器的同源策略,是JSONP模式的现代版。与JSONP不一样,CORS除了支持GET方法之外,还支持其余HTTP方法。用CORS可让网页设计师用通常的XMLHTTPRequest,这种方式的错误处理比JSONP要来的好。另外一方面,JSONP能够在不支持CORS的老旧浏览器上运做,现代的浏览器都支持CORS。ajax
在网页http://caniuse.com/#feat=cors上列出了主流浏览器对CORS的支持状况,包含PC端和移动端的浏览器。
因为同源策略,通常来讲不容许JavaScript跨域访问其余服务器的页面对象,可是HTML的<script>元素是一个例外。利用 <script> 元素的这个开放策略,网页能够获得从其余来源动态产生的 JSON资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并非 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。
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从如下三个方面进行资源定义:
下图展现了RESTful Web Service的执行流程
本文的服务端程序是基于ASP.NET Web API构建的。
在了解了这些基础知识后,咱们就分别来构建服务端程序和客户端程序吧。
若是您是前端开发人员,而且未接触过ASP.NET Web API,能够跳过此段落。
分别执行如下3个命令:
打开VS的Server Explorer,选择刚建立好的数据库,右键New Query,执行如下sql语句:
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
本文的示例仍然是表格组件的CURD,只不过此次我们的数据是从服务端获取的。
在实现数据的CURD以前,咱们先准备好一些组件和Ajax帮助方法。
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组件的做用就在于此。
<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>
基于$.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
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.
如今碰到了请求跨域的问题,结合前面讲的一些概念,咱们大体能够猜到解决跨域请求的两种方式:
这两种跨域解决方案的区别是什么呢?
选择JSONP仍是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请求。
将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) }
刷新页面,请求虽然成功了,但画面没有显示数据,而且弹出了请求错误的消息。
在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组件也能正常显示数据:
添加一个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是不可编辑的。
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 }
给主键列添加连接,绑定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>
在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) } } })
在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>
为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>
使用$watch跟踪data选项show属性的变化,每当关闭对话框时就重置item。
demo.$watch('show', function(newVal, oldVal){ if(!newVal){ this.item = {} } })
为何要这么作呢?由于每次打开对话框,不知道是以新建模式仍是修改模式打开的,若是不重置item,假若先以修改模式打开对话框,再以新建模式打开对话框,新建模式的对话框将会显示上次打开的数据。
在methods选项中添加方法deleteEntry
:
deleteEntry: function(entry) { this.$dispatch('delete-entry', entry) }
调用$.dispatch
向父组件派发事件delete-entry
。
在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来完成这些工做。