Ajax:修訂版本之間的差異
出自六年制學程
(新頁面: ==檔案上傳== ===前台程式=== <pre><!DOCTYPE html> <html> <head> <title> Ajax JavaScript File Upload Example </title> </head> <body> <!-- HTML5 Input Form Elements --...) |
(→檔案上傳程式範例) |
||
(未顯示同用戶所作出之78次版本) | |||
第 1 行: | 第 1 行: | ||
− | == | + | ===總覽=== |
− | === | + | ====(一)非同步事件==== |
− | + | #JavaScript 一次僅能做一件事情,遇到非同步的事件時,就會將非同步的事件移動到程式碼的最後方,等到所有的原始碼運行完以後才會執行非同步的事件。 | |
− | + | #非同步事件如: | |
− | + | #*setTimeout( ()=>{console.log('非同步事件');} ,秒數); 其中的 setTimeout() | |
− | + | #*fetch(後送目的地,後送選項); | |
− | + | #* Ajax物件.send(後送資料); | |
− | + | ====(二)後送圖解==== | |
− | + | <img src='http://jendo.org/uploadFiles/學習資源/資訊/表單資料後送總覽.png' width='780'/> | |
− | + | ||
− | + | ||
− | + | ====(三)簡易 QA==== | |
− | + | Q: Promise 跟 Ajax 有什麼關係? | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | Ajax 可以向伺服器傳送及取得資料,並且不需要重新整理瀏覽器畫面,這樣可以大幅提升使用者體驗並且減少伺服器負擔(僅處理資料,畫面由前端處理)。 | |
+ | |||
+ | Ajax 是一個透過 JavaScript 技術名稱,用於取得遠端資料;而 Promise 則是一個語法,專門用來處理非同步行為,並不是專門用來處理 Ajax 使用,所以兩者是不同的。 | ||
+ | |||
+ | |||
+ | Q: Promise 與 Async、Await 有什麼關係?<br/>Promise 是用來優化非同步的語法,而 Async、Await 可以 <strong>基於 Promise</strong> 讓非同步的語法的結構類似於「同步語法」,更易讀且好管理。 | ||
+ | |||
+ | |||
+ | Q: 請問 Promise 很常用到嗎?是否一定要學呢?<br/>使用頻率高,必學。 | ||
+ | |||
+ | ===HTTP 中的 Content-Type: multipart/form-data=== | ||
+ | 常見的傳輸格式: | ||
+ | #Content-Type: application/x-www-form-urlencoded 用 變數1=變數值&變數2=變數值 的方式傳資料 | ||
+ | #Content-Type: application/json 代表請求內容是 JSON | ||
+ | #Content-Type: image/png 代表請求內容是圖片檔 | ||
+ | #Content-Type: multipart/form-data 使用 (RFC7578) 規範,用一個請求傳送複數個資料格式,主要用於表單或實作檔案上傳。可以用 HTML 的 form 標籤指示 enctype='multipart/form-data' 屬性(配合 Submit),或 JavaScript 的 FormData 類別(配合 onclick)。 | ||
+ | #*multipart/form-data 也是 HTTP 請求的一種 | ||
+ | #*只要符合格式不用瀏覽器也可以發送請求 | ||
+ | #*請求只是將一坨二進制數傳至伺服器,檔案內容必須在伺服器端解析 | ||
+ | #表單當中使用 GET 方法送出,那麼所有表單的內容都以 url encoded 的方式被傳送。HTML 點擊 Submit 按鈕後會變成「請求目的地?name=變數值&file=變數值」,就算 enctype 指定 multipart/form-data 還是會以「 application/x-www-form-urlencoded」的形式送出。 | ||
+ | 以下先看瀏覽器發送的一個 HTTP POST multipart/form-data 請求:<pre>POST 目的地 HTTP/1.1 | ||
+ | Host: localhost:3000 | ||
+ | |||
+ | Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryFYGn56LlBDLnAkfd | ||
+ | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36 | ||
+ | |||
+ | ------WebKitFormBoundaryFYGn56LlBDLnAkfd | ||
+ | Content-Disposition: form-data; name="name" | ||
+ | |||
+ | Test | ||
+ | ------WebKitFormBoundaryFYGn56LlBDLnAkfd | ||
+ | Content-Disposition: form-data; name="file"; filename="text.txt" | ||
+ | Content-Type: text/plain | ||
+ | |||
+ | Hello World | ||
+ | ------WebKitFormBoundaryFYGn56LlBDLnAkfd--</pre> | ||
+ | |||
+ | '''boundary 的作用與格式:''' | ||
+ | #指示每個資料的界限在哪裡 | ||
+ | #boundary 的格式: | ||
+ | #*開頭是兩個 hypen | ||
+ | #*總長度在 70 以內(不包含 hypen 本身) | ||
+ | #*只接受 ASCII 7bit | ||
+ | #*最後一個 boundary 則會再以兩個 hypen 當作結尾 | ||
+ | #boundary 之下的處理: | ||
+ | #*Content-Disposition: form-data; name="欄名"<br/>空白行<br/>欄位內容 | ||
+ | #*如果是檔案:<br/>Content-Disposition: form-data; name="欄名"; filename="檔名"<br/>Content-Type: text/plain<br/>空白行<br/>欄位內容(同時也是檔案內容) | ||
+ | #*如果是圖片檔或是其他非文字檔(舉 png 圖檔為例):<br/>Content-Disposition: form-data; name="file"; filename="image.png"<br/>Content-Type: image/png<br/>空白行<br/>圖檔二進制內容 | ||
+ | |||
+ | ===FormData 類別=== | ||
+ | ====(一)初始一個 FormData==== | ||
+ | 取得整個表單物件: | ||
+ | <pre>var form = document.getElementById(form的ID);</pre> | ||
+ | 或 | ||
+ | <pre>var form = document.querySelector('form'); // 取第一個 form</pre> | ||
+ | |||
+ | 取得整個表單物件的數據: | ||
+ | <pre>var formData = new FormData(form); // 將 form 數據放入 formData</pre> | ||
+ | 或 | ||
+ | <pre>var formData = new FormData(form); // 造一個空的 formData</pre> | ||
+ | |||
+ | ====(二)FormData 操作與取值的方法==== | ||
+ | #FormData.append(<field_name>, <field_value>, [<filename>]):新增欄位 | ||
+ | #FormData.delete(<field_name>):刪除欄位 | ||
+ | #FormData.set(<field_name>, <field_value>, [<filename>]):更新既存的欄位,尚未建立就新增 | ||
+ | #FormData.get(<field_name>):取得該欄位的第一個值 | ||
+ | #FormData.getAll(<field_name>):回傳陣列,取得該欄位的所有值 | ||
+ | #FormData.entries():取得所有欄位的名稱和值 | ||
+ | #FormData.keys():取得所有欄位的名稱 | ||
+ | #FormData.values():取得所有欄位的值 | ||
+ | #:for(let [key, value] of formData.entries()) { | ||
+ | #: console.log(key+ ', '+ value); | ||
+ | #:} | ||
+ | #FormData.has(<field_name>):檢驗表單中是否有該欄位,回傳 true 或 false | ||
+ | |||
+ | ====(三)fetch 後送請求==== | ||
+ | #fetch(後送的目的地,後送選項) | ||
+ | #後送選項是一個物件,包含用什麼通道送?後送什麼?<br/>fetch(url,{method:'POST',body:FormData物件}) | ||
+ | |||
+ | ===檔案上傳程式範例=== | ||
+ | ====(一)無 form 標籤==== | ||
+ | #不翻頁 | ||
+ | #無法顯示後台程式的回應 | ||
+ | '''前台程式''' | ||
+ | <pre><!DOCTYPE html> | ||
+ | <html> | ||
+ | <head> | ||
+ | <title> Ajax JavaScript File Upload Example </title> | ||
+ | </head> | ||
+ | <body> | ||
+ | <!-- HTML5 Input Form Elements --> | ||
+ | <input id='fileupload' type='file' /> | ||
+ | <button onclick='uploadFile()'> Upload </button> | ||
+ | |||
+ | <!-- Ajax JavaScript File Upload Logic --> | ||
+ | <script> | ||
+ | async function uploadFile() { | ||
+ | let formData = new FormData(); | ||
+ | formData.append('file', fileupload.files[0]); | ||
+ | await fetch('./upload.php', {method: 'POST',body: formData}); | ||
+ | alert('The file has been uploaded successfully.'); | ||
+ | } | ||
+ | </script> | ||
+ | </body> | ||
</html></pre> | </html></pre> | ||
+ | 說明: | ||
+ | #表單元素沒有 form 標籤、沒有 action 指示,完全靠 javascript 的 fetch 指示出被請求的後台程式,執行時也不會將後台程式的回應顯示取代目前的頁 | ||
+ | #兩個表單元素:一個負責讓使用者選上傳檔,另一個負責驅動 javascript 的 uploadFile() 函式 | ||
+ | #*前台的 <button> 與 <input type='button'> 等效,前者可以夾圖,後者只能表現按鈕上的字 | ||
+ | #*type='file' 不需要 name 屬性,只須要 id 屬性讓 javascript 叫用 | ||
+ | #*button 更是連 id 屬性都不用,只要可以 onclick 就行 | ||
+ | #uploadFile() 是一個非同步函式,其中的 fetch 動作,須等伺服器處理完才能繼續往下走 | ||
+ | #async 和 await 相搭配,後者指出要等伺服器處理完的非同步動作。可以使得本應移到程式碼的最後方,等到所有的原始碼運行完以後才會執行的非同步的事件,如同步事件一樣先被執行: | ||
+ | #*async function 用來定義一個非同步函式,讓這個函式雖為非同步,但其內部以「同步的方式」運行「非同步程式碼」。 | ||
+ | #*await 則是可以暫停非同步函式的運行(中止 Promise 的運行),直到非同步動作進入 resolve 或 reject,當接收完回傳值後繼續 | ||
+ | #後送到 $_FILES 通道的變數名由 append 指定,與 input 的 name 屬性無關 | ||
+ | #後送到 $_FILES 通道的檔案實體數據,由 input 的選擇動作決定,所以 <input type='file'> 要留 id 屬性 | ||
+ | #fileupload 和 document.getElementById('fileupload') 等效 | ||
+ | #let 宣告的變數生命週期只在區塊 {…} 之內 | ||
+ | #formData.append(欄位, 欄值); | ||
+ | #fetch(後送請求目的地,後送選項)<br/>後送選項是一個物件,包含怎麼後送、後送什麼兩個項 | ||
+ | #注意時間, alert('The file has been uploaded successfully.'); 會等檔案上傳完才執行 | ||
− | + | '''對應的後台程式 upload.php''' | |
<pre><?php | <pre><?php | ||
− | |||
/* Get the name of the uploaded file */ | /* Get the name of the uploaded file */ | ||
$filename = $_FILES['file']['name']; | $filename = $_FILES['file']['name']; | ||
/* Choose where to save the uploaded file */ | /* Choose where to save the uploaded file */ | ||
− | $location = | + | $location = './upload/'.$filename; |
/* Save the uploaded file to the local filesystem */ | /* Save the uploaded file to the local filesystem */ | ||
− | if ( move_uploaded_file($_FILES['file']['tmp_name'], $location) ) { | + | if(move_uploaded_file($_FILES['file']['tmp_name'],$location)){ |
− | + | echo 'Success'; | |
− | } else { | + | }else{ |
− | + | echo 'Failure'; | |
} | } | ||
+ | ?></pre> | ||
+ | 說明: | ||
+ | #將上傳檔放到 ./upload/ 路徑下 | ||
+ | #並以 echo 回應成功或失敗,但此回應不會反映在前台程式執行上,因為前台沒有要處理回應 | ||
+ | '''整合前後台程式為:''' | ||
+ | <pre><?php | ||
+ | // 前置處理 | ||
+ | if(isset($_FILES['file']['name'])){ | ||
+ | if(move_uploaded_file($_FILES['file']['tmp_name'],'./上傳資料夾/'.$_FILES['file']['name'])){ | ||
+ | echo 'Success'; | ||
+ | }else{ | ||
+ | echo 'Failure'; | ||
+ | } | ||
+ | }else{ | ||
+ | $str="<!DOCTYPE html> | ||
+ | <html> | ||
+ | <head> | ||
+ | <title> Ajax JavaScript File Upload Example </title> | ||
+ | </head> | ||
+ | <body> | ||
+ | <input type='file' id='fileupload' /> | ||
+ | <button onclick='uploadFile()'> Upload </button> | ||
+ | <!--<input type='button' name='button' value='Upload' id='upload-button' onclick='uploadFile()'/>--> | ||
+ | <script> | ||
+ | async function uploadFile() { | ||
+ | let formData = new FormData(); | ||
+ | alert(fileupload); | ||
+ | formData.append('file',fileupload.files[0]); | ||
+ | await fetch('".$_SERVER['PHP_SELF']."',{method:'POST',body:formData}); | ||
+ | alert('The file has been uploaded successfully.'); | ||
+ | } | ||
+ | </script> | ||
+ | </body> | ||
+ | </html>"; | ||
+ | echo $str; | ||
+ | } | ||
+ | ?></pre> | ||
+ | |||
+ | ====(二)無 form 標籤,用 fetch 後送,將後台回應塞入 Div==== | ||
+ | <pre><?php | ||
+ | // 前置處理 | ||
+ | if(isset($_FILES['file']['name'])){ | ||
+ | if(move_uploaded_file($_FILES['file']['tmp_name'],'./上傳資料夾/'.$_FILES['file']['name'])){ | ||
+ | echo 'Success'; | ||
+ | }else{ | ||
+ | echo 'Failure'; | ||
+ | } | ||
+ | }else{ | ||
+ | $str="<!DOCTYPE html> | ||
+ | <html> | ||
+ | <head> | ||
+ | <title> Ajax JavaScript File Upload Example </title> | ||
+ | </head> | ||
+ | <body> | ||
+ | <input type='file' id='fileupload' /> | ||
+ | <button onclick='uploadFile()'> Upload </button> | ||
+ | <!--<input type='button' name='button' value='Upload' id='upload-button' onclick='uploadFile()'/>--> | ||
+ | <script> | ||
+ | async function uploadFile() { | ||
+ | let formData = new FormData(); | ||
+ | formData.append('file',fileupload.files[0]); | ||
+ | await fetch('".$_SERVER['PHP_SELF']."',{method:'POST',body:formData}).then((response)=>{return response.text();}).then((responseText)=>{document.getElementById('list').innerHTML=responseText;}); | ||
+ | } | ||
+ | </script> | ||
+ | <div id='list' name='list'></div> | ||
+ | </body> | ||
+ | </html>"; | ||
+ | echo $str; | ||
+ | } | ||
+ | ?></pre> | ||
+ | 說明: | ||
+ | #fetch 之後取得回應,回應是一個物件(object),然後用 then 方法處理這個物件 | ||
+ | #(response)=>{return response.text();}:將回應物件代入變數 response ,然後執行 {…} 。執行內容就是:對 response 取方法 | ||
+ | text() 再返回。 | ||
+ | #其返回物是一段文字,,然後再用 then 方法處理這個返回物 | ||
+ | #(responseText)=>{document.getElementById('list').innerHTML=responseText;}:將返回物文字片段塞入 ID 為 list 的標籤中。 | ||
+ | |||
+ | ====(三)無 form 標籤,不用 fetch 而改用 ajax 後送,將後台回應塞入 Div==== | ||
+ | 實體檔案在 DS218+/study/uploadAjax.php | ||
+ | <pre><?php | ||
+ | // 前置處理 | ||
+ | if(isset($_FILES['file']['name'])){ | ||
+ | if(move_uploaded_file($_FILES['file']['tmp_name'],'./上傳資料夾/'.$_FILES['file']['name'])){ | ||
+ | echo 'Success'; | ||
+ | }else{ | ||
+ | echo 'Failure'; | ||
+ | } | ||
+ | }else{ | ||
+ | $str="<!DOCTYPE html> | ||
+ | <html> | ||
+ | <head> | ||
+ | <title> Ajax JavaScript File Upload Example </title> | ||
+ | </head> | ||
+ | <body> | ||
+ | <input type='file' id='fileupload' /> | ||
+ | <button onclick='uploadFile()'> Upload </button> | ||
+ | <!--<input type='button' name='button' value='Upload' id='upload-button' onclick='uploadFile()'/>--> | ||
+ | <script> | ||
+ | var ahr; | ||
+ | if(window.XMLHttpRequest){ahr=new XMLHttpRequest();} | ||
+ | else{ahr = new ActiveXObject('Microsoft.XMLHTTP');} | ||
+ | function uploadFile() { | ||
+ | let formData = new FormData(); | ||
+ | formData.append('file',fileupload.files[0]); | ||
+ | ahr.open('POST','".$_SERVER['PHP_SELF']."',true); | ||
+ | ahr.onreadystatechange=function(){ | ||
+ | if(ahr.readyState==4 && ahr.status==200){ | ||
+ | document.getElementById('list').innerHTML=ahr.responseText; | ||
+ | } | ||
+ | } | ||
+ | ahr.send(formData); | ||
+ | } | ||
+ | </script> | ||
+ | <div id='list' name='list'></div> | ||
+ | </body> | ||
+ | </html>"; | ||
+ | echo $str; | ||
+ | } | ||
+ | ?></pre> | ||
+ | 說明: | ||
+ | #造 ajax 請求物件 ahr | ||
+ | #用 ahr.send(formData) | ||
+ | #使用 FormData 後,會將 request 的 Content-Type 設定為「multipart/form-data」 | ||
+ | |||
+ | ====(四)用 iframe 配合 target 使 form 後送不翻頁==== | ||
+ | <pre><?php | ||
+ | // 前置處理 | ||
+ | if(isset($_FILES['file']['name'])){ | ||
+ | if(move_uploaded_file($_FILES['file']['tmp_name'],'./上傳資料夾/'.$_FILES['file']['name'])){ | ||
+ | echo 'Success'; | ||
+ | }else{ | ||
+ | echo 'Failure'; | ||
+ | } | ||
+ | }else{ | ||
+ | $str="<!DOCTYPE html> | ||
+ | <html> | ||
+ | <body> | ||
+ | <form target='list' id='col_form' method='post' enctype='multipart/form-data'> | ||
+ | <input type='file' name='file'/> | ||
+ | <button onclick='uploadFile('col_form','list')'> Upload </button> | ||
+ | </form> | ||
+ | <script> | ||
+ | async function uploadFile(formID,divID){ | ||
+ | let formData = new FormData(document.getElementById(formID)); | ||
+ | await fetch('".$_SERVER['PHP_SELF']."',{method:'POST',body:formData}).then((response)=>{return response.text();}).then((responseText)=>{document.getElementById(divID).innerHTML=responseText;}); | ||
+ | } | ||
+ | </script> | ||
+ | <iframe id='list' name='list'></iframe> | ||
+ | </body> | ||
+ | </html>"; | ||
+ | echo $str; | ||
+ | } | ||
?></pre> | ?></pre> |
2023年3月23日 (四) 17:23的最新修訂版本
總覽
(一)非同步事件
- JavaScript 一次僅能做一件事情,遇到非同步的事件時,就會將非同步的事件移動到程式碼的最後方,等到所有的原始碼運行完以後才會執行非同步的事件。
- 非同步事件如:
- setTimeout( ()=>{console.log('非同步事件');} ,秒數); 其中的 setTimeout()
- fetch(後送目的地,後送選項);
- Ajax物件.send(後送資料);
(二)後送圖解
(三)簡易 QA
Q: Promise 跟 Ajax 有什麼關係?
Ajax 可以向伺服器傳送及取得資料,並且不需要重新整理瀏覽器畫面,這樣可以大幅提升使用者體驗並且減少伺服器負擔(僅處理資料,畫面由前端處理)。
Ajax 是一個透過 JavaScript 技術名稱,用於取得遠端資料;而 Promise 則是一個語法,專門用來處理非同步行為,並不是專門用來處理 Ajax 使用,所以兩者是不同的。
Q: Promise 與 Async、Await 有什麼關係?
Promise 是用來優化非同步的語法,而 Async、Await 可以 基於 Promise 讓非同步的語法的結構類似於「同步語法」,更易讀且好管理。
Q: 請問 Promise 很常用到嗎?是否一定要學呢?
使用頻率高,必學。
HTTP 中的 Content-Type: multipart/form-data
常見的傳輸格式:
- Content-Type: application/x-www-form-urlencoded 用 變數1=變數值&變數2=變數值 的方式傳資料
- Content-Type: application/json 代表請求內容是 JSON
- Content-Type: image/png 代表請求內容是圖片檔
- Content-Type: multipart/form-data 使用 (RFC7578) 規範,用一個請求傳送複數個資料格式,主要用於表單或實作檔案上傳。可以用 HTML 的 form 標籤指示 enctype='multipart/form-data' 屬性(配合 Submit),或 JavaScript 的 FormData 類別(配合 onclick)。
- multipart/form-data 也是 HTTP 請求的一種
- 只要符合格式不用瀏覽器也可以發送請求
- 請求只是將一坨二進制數傳至伺服器,檔案內容必須在伺服器端解析
- 表單當中使用 GET 方法送出,那麼所有表單的內容都以 url encoded 的方式被傳送。HTML 點擊 Submit 按鈕後會變成「請求目的地?name=變數值&file=變數值」,就算 enctype 指定 multipart/form-data 還是會以「 application/x-www-form-urlencoded」的形式送出。
POST 目的地 HTTP/1.1 Host: localhost:3000 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryFYGn56LlBDLnAkfd User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36 ------WebKitFormBoundaryFYGn56LlBDLnAkfd Content-Disposition: form-data; name="name" Test ------WebKitFormBoundaryFYGn56LlBDLnAkfd Content-Disposition: form-data; name="file"; filename="text.txt" Content-Type: text/plain Hello World ------WebKitFormBoundaryFYGn56LlBDLnAkfd--
boundary 的作用與格式:
- 指示每個資料的界限在哪裡
- boundary 的格式:
- 開頭是兩個 hypen
- 總長度在 70 以內(不包含 hypen 本身)
- 只接受 ASCII 7bit
- 最後一個 boundary 則會再以兩個 hypen 當作結尾
- boundary 之下的處理:
- Content-Disposition: form-data; name="欄名"
空白行
欄位內容 - 如果是檔案:
Content-Disposition: form-data; name="欄名"; filename="檔名"
Content-Type: text/plain
空白行
欄位內容(同時也是檔案內容) - 如果是圖片檔或是其他非文字檔(舉 png 圖檔為例):
Content-Disposition: form-data; name="file"; filename="image.png"
Content-Type: image/png
空白行
圖檔二進制內容
- Content-Disposition: form-data; name="欄名"
FormData 類別
(一)初始一個 FormData
取得整個表單物件:
var form = document.getElementById(form的ID);
或
var form = document.querySelector('form'); // 取第一個 form
取得整個表單物件的數據:
var formData = new FormData(form); // 將 form 數據放入 formData
或
var formData = new FormData(form); // 造一個空的 formData
(二)FormData 操作與取值的方法
- FormData.append(<field_name>, <field_value>, [<filename>]):新增欄位
- FormData.delete(<field_name>):刪除欄位
- FormData.set(<field_name>, <field_value>, [<filename>]):更新既存的欄位,尚未建立就新增
- FormData.get(<field_name>):取得該欄位的第一個值
- FormData.getAll(<field_name>):回傳陣列,取得該欄位的所有值
- FormData.entries():取得所有欄位的名稱和值
- FormData.keys():取得所有欄位的名稱
- FormData.values():取得所有欄位的值
- for(let [key, value] of formData.entries()) {
- console.log(key+ ', '+ value);
- }
- FormData.has(<field_name>):檢驗表單中是否有該欄位,回傳 true 或 false
(三)fetch 後送請求
- fetch(後送的目的地,後送選項)
- 後送選項是一個物件,包含用什麼通道送?後送什麼?
fetch(url,{method:'POST',body:FormData物件})
檔案上傳程式範例
(一)無 form 標籤
- 不翻頁
- 無法顯示後台程式的回應
前台程式
<!DOCTYPE html> <html> <head> <title> Ajax JavaScript File Upload Example </title> </head> <body> <!-- HTML5 Input Form Elements --> <input id='fileupload' type='file' /> <button onclick='uploadFile()'> Upload </button> <!-- Ajax JavaScript File Upload Logic --> <script> async function uploadFile() { let formData = new FormData(); formData.append('file', fileupload.files[0]); await fetch('./upload.php', {method: 'POST',body: formData}); alert('The file has been uploaded successfully.'); } </script> </body> </html>
說明:
- 表單元素沒有 form 標籤、沒有 action 指示,完全靠 javascript 的 fetch 指示出被請求的後台程式,執行時也不會將後台程式的回應顯示取代目前的頁
- 兩個表單元素:一個負責讓使用者選上傳檔,另一個負責驅動 javascript 的 uploadFile() 函式
- 前台的 <button> 與 <input type='button'> 等效,前者可以夾圖,後者只能表現按鈕上的字
- type='file' 不需要 name 屬性,只須要 id 屬性讓 javascript 叫用
- button 更是連 id 屬性都不用,只要可以 onclick 就行
- uploadFile() 是一個非同步函式,其中的 fetch 動作,須等伺服器處理完才能繼續往下走
- async 和 await 相搭配,後者指出要等伺服器處理完的非同步動作。可以使得本應移到程式碼的最後方,等到所有的原始碼運行完以後才會執行的非同步的事件,如同步事件一樣先被執行:
- async function 用來定義一個非同步函式,讓這個函式雖為非同步,但其內部以「同步的方式」運行「非同步程式碼」。
- await 則是可以暫停非同步函式的運行(中止 Promise 的運行),直到非同步動作進入 resolve 或 reject,當接收完回傳值後繼續
- 後送到 $_FILES 通道的變數名由 append 指定,與 input 的 name 屬性無關
- 後送到 $_FILES 通道的檔案實體數據,由 input 的選擇動作決定,所以 <input type='file'> 要留 id 屬性
- fileupload 和 document.getElementById('fileupload') 等效
- let 宣告的變數生命週期只在區塊 {…} 之內
- formData.append(欄位, 欄值);
- fetch(後送請求目的地,後送選項)
後送選項是一個物件,包含怎麼後送、後送什麼兩個項 - 注意時間, alert('The file has been uploaded successfully.'); 會等檔案上傳完才執行
對應的後台程式 upload.php
<?php /* Get the name of the uploaded file */ $filename = $_FILES['file']['name']; /* Choose where to save the uploaded file */ $location = './upload/'.$filename; /* Save the uploaded file to the local filesystem */ if(move_uploaded_file($_FILES['file']['tmp_name'],$location)){ echo 'Success'; }else{ echo 'Failure'; } ?>
說明:
- 將上傳檔放到 ./upload/ 路徑下
- 並以 echo 回應成功或失敗,但此回應不會反映在前台程式執行上,因為前台沒有要處理回應
整合前後台程式為:
<?php // 前置處理 if(isset($_FILES['file']['name'])){ if(move_uploaded_file($_FILES['file']['tmp_name'],'./上傳資料夾/'.$_FILES['file']['name'])){ echo 'Success'; }else{ echo 'Failure'; } }else{ $str="<!DOCTYPE html> <html> <head> <title> Ajax JavaScript File Upload Example </title> </head> <body> <input type='file' id='fileupload' /> <button onclick='uploadFile()'> Upload </button> <!--<input type='button' name='button' value='Upload' id='upload-button' onclick='uploadFile()'/>--> <script> async function uploadFile() { let formData = new FormData(); alert(fileupload); formData.append('file',fileupload.files[0]); await fetch('".$_SERVER['PHP_SELF']."',{method:'POST',body:formData}); alert('The file has been uploaded successfully.'); } </script> </body> </html>"; echo $str; } ?>
(二)無 form 標籤,用 fetch 後送,將後台回應塞入 Div
<?php // 前置處理 if(isset($_FILES['file']['name'])){ if(move_uploaded_file($_FILES['file']['tmp_name'],'./上傳資料夾/'.$_FILES['file']['name'])){ echo 'Success'; }else{ echo 'Failure'; } }else{ $str="<!DOCTYPE html> <html> <head> <title> Ajax JavaScript File Upload Example </title> </head> <body> <input type='file' id='fileupload' /> <button onclick='uploadFile()'> Upload </button> <!--<input type='button' name='button' value='Upload' id='upload-button' onclick='uploadFile()'/>--> <script> async function uploadFile() { let formData = new FormData(); formData.append('file',fileupload.files[0]); await fetch('".$_SERVER['PHP_SELF']."',{method:'POST',body:formData}).then((response)=>{return response.text();}).then((responseText)=>{document.getElementById('list').innerHTML=responseText;}); } </script> <div id='list' name='list'></div> </body> </html>"; echo $str; } ?>
說明:
- fetch 之後取得回應,回應是一個物件(object),然後用 then 方法處理這個物件
- (response)=>{return response.text();}:將回應物件代入變數 response ,然後執行 {…} 。執行內容就是:對 response 取方法
text() 再返回。
- 其返回物是一段文字,,然後再用 then 方法處理這個返回物
- (responseText)=>{document.getElementById('list').innerHTML=responseText;}:將返回物文字片段塞入 ID 為 list 的標籤中。
(三)無 form 標籤,不用 fetch 而改用 ajax 後送,將後台回應塞入 Div
實體檔案在 DS218+/study/uploadAjax.php
<?php // 前置處理 if(isset($_FILES['file']['name'])){ if(move_uploaded_file($_FILES['file']['tmp_name'],'./上傳資料夾/'.$_FILES['file']['name'])){ echo 'Success'; }else{ echo 'Failure'; } }else{ $str="<!DOCTYPE html> <html> <head> <title> Ajax JavaScript File Upload Example </title> </head> <body> <input type='file' id='fileupload' /> <button onclick='uploadFile()'> Upload </button> <!--<input type='button' name='button' value='Upload' id='upload-button' onclick='uploadFile()'/>--> <script> var ahr; if(window.XMLHttpRequest){ahr=new XMLHttpRequest();} else{ahr = new ActiveXObject('Microsoft.XMLHTTP');} function uploadFile() { let formData = new FormData(); formData.append('file',fileupload.files[0]); ahr.open('POST','".$_SERVER['PHP_SELF']."',true); ahr.onreadystatechange=function(){ if(ahr.readyState==4 && ahr.status==200){ document.getElementById('list').innerHTML=ahr.responseText; } } ahr.send(formData); } </script> <div id='list' name='list'></div> </body> </html>"; echo $str; } ?>
說明:
- 造 ajax 請求物件 ahr
- 用 ahr.send(formData)
- 使用 FormData 後,會將 request 的 Content-Type 設定為「multipart/form-data」
(四)用 iframe 配合 target 使 form 後送不翻頁
<?php // 前置處理 if(isset($_FILES['file']['name'])){ if(move_uploaded_file($_FILES['file']['tmp_name'],'./上傳資料夾/'.$_FILES['file']['name'])){ echo 'Success'; }else{ echo 'Failure'; } }else{ $str="<!DOCTYPE html> <html> <body> <form target='list' id='col_form' method='post' enctype='multipart/form-data'> <input type='file' name='file'/> <button onclick='uploadFile('col_form','list')'> Upload </button> </form> <script> async function uploadFile(formID,divID){ let formData = new FormData(document.getElementById(formID)); await fetch('".$_SERVER['PHP_SELF']."',{method:'POST',body:formData}).then((response)=>{return response.text();}).then((responseText)=>{document.getElementById(divID).innerHTML=responseText;}); } </script> <iframe id='list' name='list'></iframe> </body> </html>"; echo $str; } ?>