呼叫總部-jQuery.ajax()
到目前為止,我們已能用jQuery自由自在地操弄網頁元素,但如果網頁只能活在自己的小框框裡,不能即時與伺服器端溝通取得資訊或更新資料,就不能稱為AJAX化的設計。AJAX代表"Asynchronous Javascript And Xml",其中的Asynchronous意味著不採行傳統Postback整個網頁表單送回伺服器的做法,而是透過XmlHttpRequest物件與伺服器溝通,再以Javascript解析伺服器端所傳回的資料。這種做法,避免了傳統Postback期間,網頁會歷經消失、等待、重新建立的過程,給予使用者更流暢的操作感受。
jQuery對與後端溝通的相關作業提供了一系統的函數,其中最基層的函數叫做jQuery.ajax()。如同animate()是彈性最大的基本函數,針對常見的應用方式,則另外又衍生出slideDown/fadeIn/show等簡便函數。jQuery.ajax()也衍生了一系列的常用函數,如jQuery.get(),
jQuery.post(), jQuery.getScript(), jQuery.getJSON(),能更簡便地滿足常見的需求,只有需要進一步控制溝通細節時,才需回頭使用jQuery.ajax()。
首先介紹get(), post(), getScript()與getJSON()這一系列的函數,接收的參數形式很相似,可用的參數有url,
data, callback及type。
url是要連線的伺服器程式網址,由於瀏覽器預設並不允許XmlHttpRequest物件跨網域呼叫另一台伺服器上的程式(主要基於資安考量),因此url的網址多半與網頁在同一主機,寫成相對網址即可。唯一可以突破跨網域限制的是getScript()(或get()時指定type="script"),當發現要存取的網址不在同一台主機時,它會透過在<head>新增<script src="...">的技巧載入Javascript,達成執行遠端網站Javascript程式的效果。
data是發出GET或POST請求時要傳遞的參數,此處可以直接給字串,則字串會被附加在url後方或當成POST內容;或者我們也可以傳入匿名物件,則物件屬性名稱及屬性值會被轉換成字串,例如:
{ a:1, b:2, c:[3,4] }會變成a=1&b=2&c=3&c=4。getScript()不支援data參數,如有需要指定時,請自行在URL中以QueryString方式表示。
callback是呼叫成功時要執行的事件函數,其中會傳入兩個參數:
data及textStatus,其中data是伺服器回傳的結果,視指定的型別不同,可能是字串、XML物件、JSON轉換成的物件等;而textStatus則是狀態字串,正常情況下會傳回success,若資料型別為XML物件而解析XML失敗時則可能得到parsererror。callback只有在傳輸成功時才會被呼叫,若發生錯誤時,並不會產生任何例外,如果要掌握錯誤時的處理,則要回歸改用jQuery.ajax()才能做進階控制。
另外,get()與post()則還多了第四個參數type,type是一個字串,可以為xml, html, script, json, jsonp,
text其中之一,type會決定傳回結果的解析方式。
接下來我們先一一介紹get(), post(), getScript()與getJSON()的使用,最後再介紹ajax()。
【基本應用】
簡單來說,get()就是透過XmlHttpRequest物件,發出一個GET請求,取回網頁執行結果。post()的原理與用途完全相同,只差別在發出的是POST請求。
為了檢視傳送結果,我們先寫一個簡單的ASPX來承接我們的請求:
<%@ Page Language="C#" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
Response.ContentType = "text/plain";
Response.Write("Method: " + Request.HttpMethod + "\n");
Response.Write("QueryString: " + Request.Url.Query + "\n");
Response.Write("PostData: ");
foreach (string key in Request.Form.Keys)
Response.Write("\n" + key + "->" + Request.Form[key]);
Response.End();
}
</script>
ReturnString.aspx將HttpMethod(GET或POST)、QueryString及POST的內容簡單地以純文字顯示出來,讓我們可以用一小段Javascript驗證傳送的結果:
function showResult(data, textStatus) {
alert(data);
}
$.get("../ReturnString.aspx", "a=1&b=2", function(d) { alert(d); });
$.get("../ReturnString.aspx", {a:1, b:2, c:[3,4]}, showResult);
$.post("../ReturnString.aspx", "a=1&b=2", showResult);
$.post("../ReturnString.aspx", {a:1, b:2, c:[3,4]}, showResult);
如下圖,我們在Mini jQuery Lab裡執行,可以由alert看到四次執行的效果:
先前提到get()及post()時可以指定type,適用於伺服器會傳回特定資料型別時。為了驗證指定type的效果,我們再寫另一個ReturnDiffType.aspx:
<%@ Page Language="C#" %>
<script runat="server">
public class Class4Json
{
public string Name;
public int Rating;
}
protected void Page_Load(object sender, EventArgs e)
{
if (Request["t"] == "xml")
{
System.Xml.XmlDocument xd = new System.Xml.XmlDocument();
xd.LoadXml("<Data><Item>AJAX</Item></Data>");
Response.ContentType = "text/xml";
Response.Write(xd.OuterXml);
}
else if (Request["t"] == "json")
{
System.Web.Script.Serialization.JavaScriptSerializer jss =
new System.Web.Script.Serialization.JavaScriptSerializer();
Class4Json darkthread = new Class4Json();
darkthread.Name = "Darkthread";
darkthread.Rating = 99;
Response.ContentType = "text/plain";
Response.Write(jss.Serialize(darkthread));
}
else if (Request["t"] == "html")
{
Response.ContentType = "text/html";
Response.Write("<span style='color:red'>Hello World!</span>");
}
else if (Request["t"] == "script")
{
Response.ContentType = "text/javascript";
Response.Write("alert('Hello World!');");
}
Response.End();
}
</script>
這個ASP.NET網頁提供四種傳回內容,分別是XML、JSON字串、HTML以及Javascript程式。比較有趣的是JSON部分(第20-26列),它會將.NET裡的Class轉成一個JSON字串:
{"Name":"Darkthread","Rating":99}
而這個字串會在Javascript端還原成相對的物件,換句話說,在.NET裡有Name及Rating兩個屬性的darkthread物件,在Javascript中也是一個物件,而且一樣具備.Name、.Rating兩個屬性值。如此將提高Server與Client間資料傳遞程式的可讀性,與以往用XML格式包裝複雜資料相比,更為輕巧方便。
接著我們寫一小段程式來測試不同內容類別的處理,其中提到另一個新函數load()跟AJAX也有點相關,但作用的對象是jQuery物件,我們可以用它從伺服器端程式動態取得HTML內容,變更jQuery物件的html()。
//XML時解析成XMLDOM,如傳回時已宣告ContentType=text/xml,type="xml"可省略
$.get("../ReturnDiffType.aspx?t=xml", {}, function(x) { alert($(x).find("Item").text()); }, "xml");
//JSON時直接解析成物件
$.get("../ReturnDiffType.aspx", {t:"json"}, function(o) { alert(o.Name + "->" + o.Rating); }, "json");
//上述寫法也可寫成以下形式
$.getJSON("../ReturnDiffType.aspx", {t:"json"}, function(o) { alert(o.Name + "->" + o.Rating); });
//Script時直接執行
$.get("../ReturnDiffType.aspx", {t:"script"}, null, "script");
//上述寫法較冗長,故多寫成以下形式
$.getScript("../ReturnDiffType.aspx?t=script", null);
//將HTML內容載入<div id="x"></div>中
$("#x").load("../ReturnDiffType.aspx", {t:"html"}, function(d) { alert('Loaded!'); });
由上述程式我們不難發現,getScript()、getJSON()其實只是特定get()呼叫的簡化寫法而已,更進一步,get()、post()、getJSON()、getScript()也只是特殊ajax()呼叫的簡化寫法,當我們需要做更進階控制時,就要回頭使用ajax()。
【jQuery.ajax()】
jQuery.ajax(options)與前述的簡化型API不同,只有一個參數options,但options有眾多屬性,足以進行精細的控制:
- async
- 設定false時,程式會等到傳輸結束才繼續執行,但等待期間瀏覽器會卡住不動,這就是AJAX的第一個A,Asynchronous(非同步),所要避免的情境,因此我們很少去更動它,多半就採用預設值true,以免變成黑心AJAX。
- beforeSend
- 開始傳送前的呼叫事件,可由this取存完整的options參數,可進行一些前置轉換或檢查,萬一發現不妥,還可return false阻止傳送。
- cache
- 是否接受被Cache下來的讀內容,script/json時預設為false,其餘為true。
- complete
- 呼叫完成後執行的函數(在sucess或error之後執行),有XMLHttpRequest與textStatus兩個參數可用。
- contentType
- 預設為application/x-www-form-urlencoded,原則上不需要修改。
- data
- 要傳送的文字內容,用法已在先前說明過。
- dataFilter
- 可對XMLHttpRequest接收的原始資料進行前置處理,函數會接入data及type兩個參數,將data處理過再return回去即可。
- dataType
- 為字串參數,可設為xml, html, script, json, text或jsonp等。jsonp的用法稍後再做說明
- error
- XMLHttpRequest發生錯誤時呼叫的處理函數,共有XMLHttpRequest, textStatus及errorThrown三個參數。textStatus可能的值有timeout, error, notmodified或parsererror等,若為XMLHttpRequest.send()引發的錯誤,例外物件會放在errornThrown中。
- global
- 是否要啟用共用AJAX處理函數,如: ajaxStart, ajaxStop, ajaxError... 等。預設為true。
- ifModified
- 設為true時,只有伺服器傳回結果有變更(以Last-Modified標頭為準)時才算成功。預設為false。
- jsonp
- 當dataType為jsonp時,這個函數會取代URL中callback=隨機函數名稱中的callback,例如: {jsonp:'onJsonPLoad'}會產生onJsonPLoad=隨機函數名稱的QueryString。
- password
- 指定XMLHttpRequest連線時使用的認證密碼。
- processData
- 一般來說,data中的資料會被轉換成適用於application/x-www-form-urlencoded的格式,如要傳送的是DOMDocument或其他不希望被處理轉換的資料,可將processData設為false。
- scriptCharset
- 適用於dataType=script及jsonp時,可指定script的語系編碼,多用於跨不同網站呼叫時的語系調整。
- success
- XMLHttpRequest呼叫成功時執行,共有data與textStatus兩個參數。即前述get()、post()的callback。
- timeout
- 指定送出請求的逾時期限(以0.001秒為單位),可壓過$.ajaxSetup()所設定的期限,適用在較耗時的請求上。
- type
- 字串參數,可為GET或POST。PUT/DELETE等延伸指令也可使用,但並非所有瀏覽器都支援。
- username
- 指定XMLHttpRequest連線時使用的認證帳號。
- xhr
- 為一callback函數,可自行產生XMLHttpRequest物件後傳回,取代內建的XMLHttpRequest物件。
【AJAX事件】
我們可以在全網頁設定共用的AJAX事件,包含了ajaxStart(有人呼叫ajax()且沒有其他傳輸在處理中)、ajaxSend(xhr.send()前一刻)、ajaxSuccss(傳送成功)、ajaxError(傳送失敗)、ajaxComplete(傳送完成,不論成功或失敗都會執行)、ajaxStop(所有的傳送都結束時觸發)。我們用以下的範例示範如何應用ajaxSend及ajaxComplete在網頁上顯示傳送狀態,並用ajaxError及error捕捉錯誤。
...略...
<script type='text/javascript'>
$(function() {
$.ajaxSetup({ cache: false });
$("#xhrStatus").ajaxSend(function() { $(this).text("傳送中,請稍候..."); });
$("#xhrStatus").ajaxComplete(function() { $(this).text("傳送完成!"); });
$().ajaxError(function(event, XMLHttpRequest, ajaxOptions, thrownError) {
alert("Fail on: " + ajaxOptions.url);
});
$("#btnUpdate").click(function() {
$.get("../Lab3_Delay.aspx", null, function(d) { $("#txtTime").val(d); });
});
$("#btnUpdateFail").click(function() {
$.ajax({
url: "../NOT_Lab3_Delay.aspx",
error: function(xhr, textStatus, errorThrown) { alert(textStatus); }
});
});
});
</script>
...略...
<body>
<div id="xhrStatus"></div>
<input type="button" id="btnUpdate" value="更新時間" />
<input type="button" id="btnUpdateFail" value="更新時間(誤)" />
<input type="text" id="txtTime" />
</body>
【JSONP】
我們都已看過getScript()動態載入Javascript執行,而getScript()可以克服跨網站限制是其一大特色。但在某些情境下,我們希望載入的Javascript可以回頭呼叫我們指定的函數,並將要傳遞的資料以Javascript物件參數方式傳入。例如:
有一個天氣預報的Web API,透過GetWeather.aspx?city=Taiwan的方式會傳回台灣北中南部的天氣資料,如果用Javascript物件陣列表示會像[{Area:"北部",
Report:"晴時多雲 26-31度"}, {Area:"中部", Report:"多雲 24-32度"}, {Area:"南部",
Report:"晴 28-34度"}]。但因為Web
API位於另一個網站,$.get()無法成功將JSON物件取回,所以有另一種做法是我們呼叫時傳入接收函數的名稱,例如: GetWeather.aspx?city=Taiwan&callback=getData,Web
API在產生Script時,會帶入我們指定的函數名稱,則執行的Script變成getData([{Area:"北部", ....
}]),即可將兩邊串起來。
讓我們來做個小測試,先寫一隻ASPX,會依傳入的callback參數,產生一段呼叫該函數的Javascript,並帶入我們要傳遞的匿名物件:
<%@ Page Language="C#" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
string funcName = Request["callback"];
Response.Write(funcName + "({ Name:'Darkthread', Rating: 5 });");
}
</script>
接著我們寫一個$.ajax(),指定dataType="jsonp",加上beforeSend事件觀察url被修改的情況,加上dataFilter事件觀察ASPX傳回的結果,最後由success取得ASPX傳來的匿名物件並顯示出來。
$.ajax({
url:"../Lab5_JSONP.aspx", dataType: "jsonp",
beforeSend: function() { alert(this.url); },
dataFilter: function(data, type) { alert("(" + type + ")" + data); return data; },
success: function(d) { alert(d.Name + "=" + d.Rating); }
});
我們發現url變成了"../Lab5_JSONP.aspx?callback=jsonp1238089172708&_=1238089172722",jsonp1238089172708是由success函數轉化而成的,改成隨機名稱才不會在同時呼叫多次時發生錯亂,而_=1238089172722則用來防止讀到Cache中的版本。
由這個QueryString,Lab5_JSONP.aspx的傳回結果不意外的是jsonp1238089172708({
Name:'Darkthread', Rating: 5 }); 然後我們得到Darkthread=5。
在上述的例子中,ASPX知道由callback=?來取用函數名稱,但若API由第三者提供,指定了如cbfunc=?來讀取函數名稱,我們可使用前面介紹過的ajax參數裡的jsonp屬性指定為cbfunc即可自訂函數名所用的QueryString參數名稱。
【範例檔案下載】