1. 程式人生 > >深入理解ajax系列第四篇

深入理解ajax系列第四篇

blob selected 單選框 encode 展示 條目 種類型 %20 寫代碼

前面的話

  現代Web應用中頻繁使用的一項功能就是表單數據的序列化,XMLHttpRequest 2級為此定義了FormData類型。FormData為序列化表單以及創建與表單格式相同的數據提供了便利。本文將先介紹表單編碼,然後過渡到表單序列化,最後引出FormData的核心概念

表單編碼

  當用戶提交表單時,表單中的數據(每個表單元素的名字和值)編碼到一個字符串中並隨請求發送。默認情況下,HTML表單通過POST方法發送給服務器,而編碼後的表單數據則用做請求主體

  對表單數據使用的編碼方案相對簡單:對每個表單元素的名字和值執行普通的URL編碼(使用十六進制轉義碼替換特殊字符),使用等號把編碼後的名字和值分開,並使用"&"符號分開名/值對。一個簡單表單的編碼如下所示

find=pizza&zipcode=01234&radius=1km

  表單數據編碼格式有一個正式的MIME類型

application/x-www-form-urlencoded

  當使用POST方法提交這種順序的表單數據時,必須"Content-Type"請求頭為這個值

  [註意]這種類型的編碼並不需要HTML表單,在Ajax應用中,希望發送給服務器的很可能是一個javascript對象

  前面展示的數據變成javascript對象的表單編碼形式可能是:

{find: "pizza", zipcode: 01234, radius: "1km"}

  表單編碼在Web上如此廣泛使用,同時所有服務器端的編程語言都能得到良好的支持,所以非表單數據的表單編碼通常也是容易實現的事情

  下面代碼展示了如何實現對象屬性的表單編碼

技術分享
function encodeFormData(data){
    if (!data) return "";    //一直返回字符串
    var pairs = [];            //用於保存名值對
    for(var name in data){ 
        if (!data.hasOwnProperty(name)){
            continue;        //跳過繼承屬性
        }
        if (typeof data[name] === "function"){
            continue;         //跳過方法 
        }
        var value = data[name].toString();    //把值轉換成字符串
        name = encodeURIComponent(name.replace("%20", "+")); //編碼名字
        value = encodeURIComponent(value.replace("%20", "+"));//編碼值
        pairs.push(name + "=" + value); // 存入名值對 
    }
    return pairs.join(‘&‘); //返回使用‘&‘連接的名值對
} 
技術分享
var data = {name:‘小火柴‘,age:28,sender:‘male‘};
//name=%E5%B0%8F%E7%81%AB%E6%9F%B4&age=28&sender=male    
console.log(encodeFormData(data));    

表單序列化

  隨著Ajax的出現,表單序列化已經成為一種常見需求。在javascript中,可以利用表單字段的type屬性,連同name和value屬性一起實現對表單的序列化。在編寫代碼之前,有必須先搞清楚在表單提交期間,瀏覽器是怎樣將數據發送給服務器的

  1、對表單字段的名稱和值進行URL編碼,使用和號(&)分隔

  2、不發送禁用的表單字段

  3、只發送勾選的復選框和單選按鈕

  4、不發送type為"reset"和"button"的按鈕

  5、多選選擇框中的每個選中的值単獨一個條目

  6、在單擊提交按鈕提交表單的情況下,也會發送提交按鈕;否則,不發送提交按鈕。也包括type為"image"的<input>元素

  7、<select>元素的值,就是選中的<option>元素的value特性的值。如果<option>元素沒有value特性,則是<option>元素的文本值

  在表單序列化過程中,一般不包含任何按鈕字段,因為結果字符串很可能是通過其他方式提交的。除此之外的其他上述規則都應該遵循

  在下面表單序列化serialize()函數中,首先定義了一個名為parts的數組,用於保存將要創建的字符串的各個部分。然後,通過for循環叠代每個表單字段,並將其保存在field變量中。在獲得了一個字段的引用之後,使用switch語句檢測其type屬性

  序列化過程中最麻煩的就是<select>元素,它可能是單選框也可能是多選框。為此,需要遍歷控件中的每一個選項,並在相應選項被選中的情況下向數組中添加一個值。對於單選框,只可能有一個選中項,而多選框則可能有零或多個選中項。這裏的代碼適用於這兩種選擇框,至於可選項的數量則是由瀏覽器控制的。在找到一個選中項之後,需要確定使用什麽值。如果不存在value特性,或者雖然存在該特性,但值為空字符串,都要使用選項的文本來代替。為檢査這個特性,在DOM兼容的瀏覽器中需要使用hasAttribute()方法,而在IE7-中需要使用特性的specified屬性

  如果表單中包含<fieldset>元素,則該元素會出現在元素集合中,但沒有type屬性。因此,如果type屬性未定義,則不需要對其進行序列化。同樣,對於各種按鈕以及文件輸入字段也是如此(文件輸入字段在表單提交過程中包含文件的內容;但是,這個字段是無法模仿的,序列化時一般都要忽略)

  對於單選按鈕和復選框,要檢查其checked屬性是否被設置為false,如果是則退出switch語句。如果checked屬性為true,則繼續執行default語句,即將當前字段的名稱和值進行編碼,然後添加到parts數組中。函數的最後一步,就是使用join()格式化整個字符串,也就是用和號來分隔每一個表單字段

  最後,serialize()函數會以査詢字符串的格式輸出序列化之後的字符串

技術分享
function serialize(form){        
    var parts = [],field = null,option,optValue;
    for (var i=0, len=form.elements.length; i < len; i++){
        field = form.elements[i];
        switch(field.type){
            //單選或多選的<select>控件
            case "select-one":
            case "select-multiple":
                //如果該<select>控件設置為name屬性  
                if (field.name.length){
                    for (var j=0,optLen = field.options.length; j < optLen; j++){
                        //選擇<option>控件
                        option = field.options[j];
                        //如果該<option>控件被選中
                        if (option.selected){
                            optValue = "";
                            //測試<option>控件的value屬性,如果沒有設置,則讀取其text屬性
                            //IE7-瀏覽器不支持hasAttribute()
                            if (option.hasAttribute){
                                optValue = (option.hasAttribute("value") ? option.value : option.text);
                            } else {
                                optValue = (option.attributes["value"].specified ? option.value : option.text);
                            }
                            //將鍵和值分別進行編碼並用‘=‘連接起來
                            parts.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(optValue));
                        }
                    }
                }
                break;               
            case undefined:     //fieldset
            case "file":        //file input
            case "submit":      //submit button
            case "reset":       //reset button
            case "button":      //custom button
                break;                
            case "radio":       //radio button
            case "checkbox":    //checkbox
                //如果沒有選中項
                if (!field.checked){
                    break;
                }                  
            default:              
                if (field.name.length){
                    parts.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(field.value));
                }
        }
    }        
    return parts.join("&");
}
技術分享 技術分享
<form name="form" action="test.php">
    <input type="text" value="123" name="text">
    <select name="color"  multiple>
        <option>紅</option>
        <option>綠</option>
        <option>藍</option>
    </select>
    <input type="radio" name="gender" id="male" value="male"><label for="male">男</label>
    <input type="radio" name="gender" id="female" value="female"><label for="female">女</label>
    <input type="checkbox" name="time" value="12" id="t12"><label for="t12">12小時</label>
    <input type="checkbox" name="time" value="24" id="t24"><label for="t24">24小時</label>
    <button name="btn" type="button">按鈕</button>
</form>
<div id="result"></div>
<script>
var oForm = document.forms.form;
oForm.onchange = function(e){
    e = e || event;
    result.innerHTML = serialize(form);
}
</script>
技術分享

FormData

  當HTML表單同時包含文件上傳元素和其他元素時,瀏覽器不能使用普通的表單編碼而必須使用稱為“multipart/form-data”的特殊Content-Type來用POST方法提交表單。這種編碼包括使用長“邊界”字符串把請求主體分離成多個部分。對於文本數據,手動創建“multipart/form-data”請求主體是可能的,但很復雜

  XMLHttpRequest 2級為此定義了FormData類型。FormData為序列化表單以及創建與表單格式相同的數據(用於通過XHR傳輸)提供了便利

  [註意]IE9-瀏覽器不支持

【構造函數】

new FormData (form? : HTMLFormElement)

  可選參數form表示一個HTML表單元素,可以包含任何形式的表單控件,包括文件輸入框

【append()】

  append()方法用於給當前FormData對象添加一個鍵/值對

void append(DOMString name, Blob value, optional DOMString filename);
void append(DOMString name, DOMString value);

  參數值name表示字段名稱;參數值value表示字段值;參數值filename(可選)用於指定文件的文件名,當value參數被指定為一個Blob對象或者一個File對象時,該文件名會被發送到服務器上,對於Blob對象來說,這個值默認為"blob"

【其他不常用方法】

  get():通過get(key)/getAll(key)來獲取對應的value

  set():通過set(key,value)修改數據,如果指定的key不存在則新增一條,如果存在,則修改對應的value值

  has():通過has(key)來判斷是否對應的key值

  delete():通過delete(key)來刪除數據

  [註意]以上4個不常用方法,IE瀏覽器都不支持

技術分享
var oData1 = new FormData();
console.log(oData1.has(‘a‘));//false
oData1.append(‘a‘,1);
console.log(oData1.has(‘a‘));//true
console.log(oData1.get(‘a‘));//1
oData1.set(‘a‘,2);
oData1.append(‘a‘,1);
console.log(oData1.get(‘a‘));//2
console.log(oData1.getAll(‘a‘));//["2", "1"]
oData1.delete(‘a‘);
console.log(oData1.get(‘a‘));//null
技術分享 技術分享
<form action="#" name="form1">
    <input type="text" value="1" name="a">
</form>
<script>
var oData1 = new FormData(document.forms.form1);
console.log(oData1.has(‘a‘));//true
console.log(oData1.get(‘a‘));//1
oData1.append(‘a‘,2);
console.log(oData1.get(‘a‘));//1
console.log(oData1.getAll(‘a‘));//[‘1‘,‘2‘]
</script>
技術分享

  一般地,我們使用FormData()構造函數創建FormData對象,然後按需多次調用這個對象的append()方法把個體“部分”(可以是字符串、File或Blob對象)添加到請求中。最後,把FormData對象傳遞給send()方法,通過XHR對象將其發送到服務器

  [註意]multipart/form-data只能用於post方式

技術分享
<form action="#" name="form1">
<select name="a">
    <option value="1">1</option>
    <option value="2">2</option>
    <option value="3">3</option>
</select>
</form>
<div id="result"></div>
<script>
var oForm = document.forms.form1;
oForm.onchange = function(){
    //創建xhr對象
    var xhr;
    if(window.XMLHttpRequest){
        xhr = new XMLHttpRequest();
    }else{
        xhr = new ActiveXObject(‘Microsoft.XMLHTTP‘);
    }
    //異步接受響應
    xhr.onreadystatechange = function(){
        if(xhr.readyState == 4){
            if(xhr.status == 200){
                //實際操作
                result.innerHTML = xhr.responseText;
            }
        }
    }
    //發送請求
    xhr.open(‘post‘,‘t1.php‘ ,true);
    xhr.send(new FormData(form1)); 
}
</script>
技術分享

深入理解ajax系列第四篇