Vue.js——基於$.ajax實現數據的跨域增刪查改

概述

以前咱們學習了Vue.js的一些基礎知識,以及如何開發一個組件,然而那些示例的數據都是local的。
在實際的應用中,幾乎90%的數據是來源於服務端的,前端和服務端之間的數據交互通常是經過ajax請求來完成的。javascript

提及ajax請求,你們第一時間會想到jQuery。除了擁有強大的DOM處理能力,jQuery提供了較豐富的ajax處理方法,它不只支持基於XMLHttpRequest的ajax請求,也能處理跨域的JSONP請求。html

以前有讀者問我,Vue.js能結合其餘庫一塊兒用嗎?答案固然是確定的,Vue.js和jQuery一塊兒使用基本沒有衝突,儘可放心大膽地使用。前端

本文的主要內容以下:vue

  • 同源策略和跨域概念
  • 跨域資源共享
  • JSONP概念
  • REST Web Services
  • 基於$.ajax實現跨域GET請求
  • 基於$.ajax實現JSONP請求
  • 基於$.ajax實現跨域POST請求
  • 基於$.ajax實現跨域PUT請求
  • 基於$.ajax實現跨域DELETE請求

本文的服務端程序和客戶端程序是部署在不一樣服務器上的,本文全部示例請求都是跨域的。
源代碼已放到GitHub,若是您以爲本篇內容不錯,請點個贊,或在GitHub上加個星星!java

基礎概念

在進入本文正題以前,咱們須要先了解一些基礎概念(若是你已經對這些基礎有所瞭解,可跳過此段落)。jquery

同源策略和跨域概念

同源策略(Same-orgin policy)限制了一個源(orgin)中加載腳本或腳本與來自其餘源(orgin)中資源的交互方式。
若是兩個頁面擁有相同的協議(protocol),端口(port)和主機(host),那麼這兩個頁面就屬於同一個源(orgin)。git

同源以外的請求均可以稱之爲跨域請求。
下表給出了相對http://store.company.com/dir/page.html同源檢測的示例:github

 

URL 結果 緣由
http://store.company.com/dir2/other.html 成功  
http://store.company.com/dir/inner/another.html 成功  
https://store.company.com/secure.html 失敗 協議不一樣
http://store.company.com:81/dir/etc.html 失敗 端口不一樣
http://news.company.com/dir/other.html 失敗 主機名不一樣

 

咱們能夠簡單粗暴地理解爲同一站點下的資源相互訪問都是同源的,跨站點的資源訪問都是跨域的。web

跨域資源共享

跨域資源共享(CORS)是一份瀏覽器技術的規範,提供了Web服務器從不一樣網域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,是JSONP模式的現代版。與JSONP不一樣,CORS除了支持GET方法之外,還支持其餘HTTP方法。用CORS可讓網頁設計師用通常的XMLHTTPRequest,這種方式的錯誤處理比JSONP要來的好。另外一方面,JSONP能夠在不支持CORS的老舊瀏覽器上運做,現代的瀏覽器都支持CORS。ajax

在網頁http://caniuse.com/#feat=cors上列出了主流瀏覽器對CORS的支持狀況,包含PC端和移動端的瀏覽器。

image

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的執行流程

image (1)

本文的服務端程序是基於ASP.NET Web API構建的。
在瞭解了這些基礎知識後,咱們就分別來構建服務端程序和客戶端程序吧。

服務端環境準備

若是您是前端開發人員,而且未接觸過ASP.NET Web API,能夠跳過此段落。

新建Web API應用程序

image

image

image

image

添加Model, Controller

image

image

image

image

image

初始化數據庫

image

分別執行如下3個命令:

image

打開VS的Server Explorer,選擇剛建立好的數據庫,右鍵New Query,執行如下sql語句:

image

 

讓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();

image

image

能夠在如下地址訪問構建好的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.         

image

跨域解決方案

如今碰到了請求跨域的問題,結合前面講的一些概念,咱們大體能夠猜到解決跨域請求的兩種方式:

  • 在服務端啓用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

刷新頁面,如今數據能夠正常顯示了。

image

View Demo

在Chrome開發工具的Network選項下,能夠看到Response Header的Content-Type是application/json。

image

文章開頭也說了,CORS的跨域方式支持現代的主流瀏覽器,可是不支持IE9及如下這些古典的瀏覽器。

image

若是咱們偏要在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) } 

刷新頁面,請求雖然成功了,但畫面沒有顯示數據,而且彈出了請求錯誤的消息。

image

讓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請求也可以正常顯示數據了。

image

image

注意:使用JSONP時,服務端返回的再也不是一段JSON了,而是一個script腳本。

在IE9下查看該頁面,simple-grid組件也能正常顯示數據:

image

View Demo

實現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 }

24

View Demo

實現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-entryload-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數據的連接開始,到最終打開對話框的完整過程:

image

注意: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個方法:loadCustomersaveCustomerupdateCustomer

 

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> 

25

5. 添加$watch

使用$watch跟蹤data選項show屬性的變化,每當關閉對話框時就重置item。

demo.$watch('show', function(newVal, oldVal){ if(!newVal){ this.item = {} } }) 

爲何要這麼作呢?由於每次打開對話框,不知道是以新建模式仍是修改模式打開的,若是不重置item,假若先以修改模式打開對話框,再以新建模式打開對話框,新建模式的對話框將會顯示上次打開的數據。

View Demo

實現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> 

26

View Demo

總結

本篇介紹了同源策略、跨域、CORS和REST等概念,並結合Vue.js和$.ajax實現了一個簡單的CURD跨域示例。下一篇,咱們將使用Vue的插件vue-resource來完成這些工做。

相關文章
相關標籤/搜索