JavaScript CSS3

Javascript 動態網頁程式設計 - 上課教材

動畫互動網頁程式設計 > 課程內容 > 第 10 章 - Ajax 的應用

第 10 章 - Ajax 的應用

上次更新日期 2020/12/07

JavaScript 還有許多不同的應用,其中應用最多的兩個模組,可能就是 Ajax 以及 JQuery 吧!我們這裡先來介紹 Ajax。 Ajax 的應用,其實主要是透過 XMLHttpRequest 這個物件的處理,將需要的 API 網址寫入到 XMLHttpRequest 物件中, 再透過該物件的功能,來取得 API 的回傳值,進一步將回傳值做處理,並用以更新網頁當中某一個區塊的內容。 這動作應用度很廣!大家來瞧瞧!

學習目標:

  1. 了解全網頁更新與區塊更新之間的差別
  2. 了解使用 XMLHttpRequest 物件
  3. 了解透過 XMLHttpRequest 物件的狀態進行額外工作
  4. 了解 responseText 與 responseXML 之間的差異
  5. 實際設計 API 範例程式

10.1: 了解什麼是 Ajax

要理解什麼是 Ajax 之前,可能得要先了解一下網頁程式語言才好。理解了網頁程式語言後,接下來,才能了解什麼是『非同步』的資料傳輸運作方式, 之後講到 Ajax 時,大家就會比較有感覺啊~

  • 網頁程式語言 | 用戶端瀏覽器程式語言與 | 伺服器端程式語言

網頁程式語言因為運作的平台不同,主要分為用戶端瀏覽器上面執行的網頁程式語言,以及伺服器端網頁伺服器軟體上面執行的程式語言兩種。 先來談談伺服器端的程式語言。

一般來說,如果你要連上購物網站,或者是討論區,或者是社群網站,大多需要讀取伺服器的資料庫,這樣才能夠在網站上面留言、購物、刷卡等等, 這都需要伺服器端的網頁程式,進行某些認證機制,以及進行與資料庫溝通的程序。這些程序通常不能開放給讀者看,讀者只能操作網頁上面可應用的元件, 並不能看到實際的程式碼。否則,你的許多動作被抓出來,包括密碼的查詢方法被查到,總是比較可怕一些。

我們舉個 PHP 這種程式語言連動資料庫的情境好了,假如你要連到鳥哥私房菜的討論區,例如底下的連結:

這個討論區會與資料庫連動,所以,每次你連線時,都會有不同的資料顯示 (如果有新的留言時),這是因為 PHP 程式碼會一直去資料庫撈取最新資料來顯示, 所以,你就可以看到最新討論資料。不過,你看到的都是純文字資料,並沒有程式碼在裡面!程式碼都在伺服器端執行。 所以,如果程式碼寫的不夠好,會操死伺服器喔!

那用戶端的瀏覽器程式語言又是怎麼回事呢?其實,最常見的瀏覽器程式語言,就是 JavaScript 啊!伺服器將程式碼以純文字的方式回傳到用戶端, 之後瀏覽器開始分析網頁內容,並開始執行程式腳本。因為需要讓瀏覽器了解如何執行,因此,伺服器傳來的資料,就包括程式碼本身, 而不是程式碼運作後的 HTML 而已。

整體來說,整個用戶端瀏覽器、伺服器端網頁程式語言,以及資料庫系統,大致上可以使用底下的圖示去了解:

Server/Client 的資料傳輸與運作行為
  • 伺服器端設計好網頁伺服器軟體、網頁程式語言、資料庫系統、程式碼所在處等,然後等待用戶的連結
  • 用戶端透過伺服器提供的連結網址 (URL),使用瀏覽器來連線到該網址,要求資料。
  • 伺服器根據 URL 的連結,啟動 PHP 程式碼,連結到資料庫,取得資料庫的數據後,轉成純文字,並搭配 HTML, CSS, JavaScript 或影音資料等, 然後將所有的元件通通送達用戶端,然後就...射後不理,連線中斷。
  • 用戶端瀏覽器接收完所有的檔案資料後,將所有元件載入到瀏覽器,然後開始依據 HTML 的標籤 (tag) 搭配 CSS 的樣式, 將網頁呈現出來,之後開始處理 JavaScript 的程式碼任務。

整個流程大概是這樣。所以,你常聽到某些學校單位裡面,若有多人同時連上校務系統、教學系統、選課系統時,會造成大當機! 那就是右側的 SQL 伺服器不夠力,或者是 PHP 等伺服端網頁程式語言寫的不夠好,因此就容易造成伺服器資源損耗的情況。那如果 JavaScript 程式碼寫錯呢? 舉例來說,之前同學練習迴圈時,如果寫錯迴圈的判斷,會造成瀏覽器感覺像是當機的情境,那就是用戶端資源耗盡,與伺服端就無關了。

因此,站在讓我們的網頁空間能夠提供更好的品質的情況下,將可能會損耗系統資源的動作丟給用戶端瀏覽器,藉以降低伺服器的資源使用量, 會是比較好的思考方向。

  • Ajax 非同步 JavaScript 行為

Ajax 的全名是『 Ajax: Asynchronous Javascript And XML 』,意思是『非同步 JavaScript 與 XML』的意思,所以,跟資料的『非同步運作』, 透過 JavaScript 與 XML 格式有點關係的資料。字面上的意義是如此,那麼到底這個 Ajax 有什麼重要性呢?這就得要談到上面的圖示顯示的內容。

一般來說,如果你的網頁資料非常豐富,因此有影片資料、圖片資料、文字資料、JavaScript 程式碼資料等等,其中影片與圖片資料可能有非常多, 因此,要下載你的這個網頁 (onload) 時,就得要將所有的元件通通下載完畢之後,才能夠開始正常的顯示你的頁面。整個流程就像上面的圖示, 在用戶端要求了 URL 之後,用戶端得要等伺服器將『所有的元件』傳輸到用戶端系統後,瀏覽器才開始正常顯示頁面。

不過,你不會載入網頁後,全部的影片、圖片、資料通通一口氣就全部要看完!當然是『慢慢個別點擊』對吧!而且,有些資料你不見得會想要點擊, 對吧!所以,這樣傳統的全部資料載入才顯示的方法,就險的有點傷腦筋!因為,相當消耗頻寬啊~對 Server 的 loading 來說,真的很可怕!

這時, Ajax 就顯得很重要了!以 W3 school 網站的 Ajax 說明為例,他說, Ajax 可以作到:

  • 更新網頁的資料並不需要全部網頁重新載入 (without reloading)
  • 可以在網頁載入完畢後,在該網頁內部向 server 要求資料
  • 可以在網頁載入完畢後,在該網頁內部取得 server 回傳的資料來更新自己頁面
  • 在背景環境中,傳送資料到伺服器端,不會變動網頁畫面。

W3School 也特別強調,Ajax 並不是一個全新的程式語言,Ajax 只是一個整合了: (1)瀏覽器內建的 XML 的請求物件 (request object), 結合 (2)JavaScript 與 HTML DOM 的顯示特性功能,所以,這只是一個非常有趣的使用方法,全部的用法還是在 JavaScript 的語法當中實行, 只是 Ajax 這個方式,可以讓 Javascript 在背景後台向伺服器要求更新某部份的 div 方塊內容而已,不會整頁翻新!這對使用者體驗來說, 有相當良好的幫助喔!

  • Ajax 怎麼運作的?

從 W3School 的圖示裡面,如下圖所示,我們可以來理解一下 Ajax 的運作:

AJAX 的運作方式

上面的圖示是說,當一整個頁面都載入完成之後,還有其他的資料需要更新時,需要進行的任務有哪些。大致流程是這樣:

  • 左上角:在用戶端瀏覽器上面,點擊或拉動等某個事件 (event) 發生後,瀏覽器會建立一個名為 XMLHttpRequest 的物件, 這個物件會包含想要跟伺服器端額外要求的部份資訊,包好這個物件後,就將該物件在背景傳送到伺服器端,然後, 該網頁可以讓使用者持續使用,而沒有任何的延遲情況發生。
  • 右側的伺服器端:當收到用戶端傳來的這個 XMLHttpRequest 的小程式,伺服器就會依據要求,提供相對的資料,然後回傳給用戶端。
  • 左下角:用戶端瀏覽器在背景接收到這些資料,並且完整接收完畢 (完整接收完畢後,XMLHttpRequest 通常會有個訊息狀態為 200 的情境, 當發生該情境時,瀏覽器就會知道資料接收完畢了) 後,就會透過 JavaScript 預先定義的功能,將該訊息放置到某個元件裡面, 通常是透過 .innerHTML 來更新網頁資料,所以就不會是全部頁面都更新!

了解了基本概念之後,再來讓我們處理一下,第一個 Ajax 的網頁吧!

10.2: 第一個 Ajax 網頁實做

事實上,如果只是靜態的、小小的網頁網站,實在不需要用到 Ajax 的,因為這個 Ajax 與框架 (frame) 完全不一樣, 框架是『載入完整的另一個網頁』,而 Ajax 則是透過 JavaScript 『更新這個網頁的某一個區塊內容』,兩者在應用上完全不同, 資料的設計上也完全不同!因為 Ajax 只是更新網頁當中某個區塊的內容,因此你不應該在該區塊填入完整的網頁資料, 而是拆開個別的資訊放置,才是正常的處理模式。

我們先來實做一個畫面僅有版面設計,以及固定的大按鈕導覽功能的頁面,然後透過該頁面來處理不同子連結的方案:

例題 10-2-1:使用 javascript 的功能,調整傳統單純 CSS 無法處理的方塊問題
  1. 新增名為 unit10-2-1.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 這個檔案內容 加到 unit10-2-1.php 檔案內,並在瀏覽器上檢查輸出的情境。
  3. 讓瀏覽器載入完畢網頁後,就立刻執行 mymain 函數。
  4. 在 mymain() 函數內進行資料的設計:
    • 先取得 content 元素控制權,然後讓 content 這個元素的高度變成初始化 (initial),亦即 content.style.height = 'initial', 因為未來這個函數會反覆被呼叫,必須要讓這個元素的高度回歸原始後,才重新設定!
    • 進行區域變數 subnavY 的設計,內容為取得 subnav 這個元素的 .offsetHeight 數值,且轉為整數型態
    • 進行區域變數 mainY 的設計,內容為取得 main 這個元素的 .offsetHeight 數值,且轉為整數型態
    • 比較一下 mainY 與 subnavY,若 subnavY 大,則區域變數 yy 會是 subnavY,否則 yy 就是 mainY
    • 最終將 content 這個元素的 .style.height 設定為 yy + "px" 即可。
  5. 最後再次查閱網頁時,就會發現,即使使用 position 為 absolute 的情境下,我們也能輕鬆的設計好外層方塊的高度!
原始的圖示結果 使用 javajscript 抓到正確的高度而變更的結果
  • 建立共用的 XMLHttpRequest 物件建立的函數

回想一下上一個小節談到的 Ajax 的第一步,其實是本機瀏覽器上面要產生一個 XMLHttpRequest 的物件,這個物件是所有事情的開端! 但是,這個物件的建立,由於不同瀏覽器的關係,可能產生物件的方式不太一樣。基本的產生方式有幾個:

// 新的瀏覽器都支援的方式
var xhttp = new XMLHttpRequest();

// 較新的微軟 IE 6+ 以上瀏覽器支援的方式
var xhttp = new ActiveXObject("Msxml2.XMLHTTP");

// 最舊的 IE 5 以前版本的瀏覽器
var xhttp = new ActiveXObject("Microsoft.XMLHTTP");

其實,以新的處理方式而言,你只要撰寫上面第一行也就夠了,連 Windows 7 的 IE 都已經是 version 11 了,實在沒什麼必要支援 IE 5 以前的版本。 不過,從 netmarketshare 網站的追蹤發現,其實 IE 在 2020 年還是有大約 5% 的市占率!不能小覷啊~所以,在這個 XMLHttpRequest 的物件產生, 還是來思考一下怎麼處理比較好。一般建議設定一個函數,用該函數來處理這個物件的建置比較好!大致處理方案如下:

function createXHR() {
	// 嘗試用最新的方式建立 XMLHttpRequest 物件
	try { var xhttp = new XMLHttpRequest(); }
	// 若產生問題時,才進行底下的動作,若成功,就執行 return 那行
	catch(err1) {
		// 嘗試用 IE 6+ 建立物件
		try { var xhttp = new ActiveXObject("Msxml2.XMLHTTP"); }
		// 若還是錯誤,才進行底下的方式
		catch(err2) {
			// 嘗試使用 IE 5 相容方式建立物件
			try { var xhttp = new ActiveXObject("Microsoft.XMLHTTP"); }
			// 還錯誤時,就設定 xhttp 是錯誤的
			catch(err3) {
				xhttp = false;
			}
		}
	}
	return xhttp;
}

將這個函數放置在 function.js 檔案中,未來只要載入這個檔案,那麼就能使用底下的方式來呼叫建立 XMLHttpRequest 物件了!

var xhttp = createXHR();
例題 10-2-2:如上所述,建立 function.js 檔案,並將上面的函數丟進去!備用!
  1. 新增名為 function.js 的檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 將上面的函數丟進去即可。
  • 資料整理打包並送出需求

現在,讓我們將這個資料打包之後,送到伺服器去處理!而且需要在背景底下處理喔!一般來說,要將這個 XMLHttpRequest 的物件封裝之後, 並送出給伺服器處理,需要底下的幾個步驟:

// 建立需要的物件,透過剛剛建立的 function.js 裡頭的函數處理
var xhttp = createXHR();

// 向伺服器說明,我們需要的,是那一個額外的檔案、程式碼或其他資料等,這裡我們用 open 方式處理
xhttp.open("GET", "unit10-2-subnav-1.txt", true);

// 設定好在背景底下監視,當上述的檔案讀回來的結果後,才進行後續的處理函數
xhttp.onreadystatechange = getcontent;

// 開始將上述的需求送出
xhttp.send(null);

有玩過網頁表單的都知道,一般取得伺服器資料的基本方式有兩種,一種是從網址列輸入變數資料的 GET 方式,一種是資料量比較大而封裝起來的 POST 方式。 這邊我們使用較為單純的 GET 方式,來向 server 要求一個名為 unit10-2-subnav-1.txt 的檔案。而且先通知瀏覽器, 等等在背景底下嘗試送出要求後,監視這個要求的回傳結果,如果有任何結果,不論正確或錯誤 (onreadystatechange) 時,就使用 getcontent() 函數處理後續的任務,之後就直接送出資料。

那個 unit10-2-subnav-1.txt 檔案,一般大多數都是使用 php 的 API 功能來處理的!我們這裡先單純使用一個文字檔來替代! 此外,我們預計採用 getcontent() 函數來處理後續的任務,那麼這個 getcontent() 的內容,大多數情境又是如何呢?

  • 根據回傳的結果進行資料的處理

我們要先知道資料回傳的狀態,亦即這個檔案: unit10-2-subnav-1.txt 到底是正確下載了?還是無法下載?基本上, onreadystatechange 當中, 可能有 readyState 及 status 兩個屬性,裡頭還有相關的數值說明如下:

屬性值與說明
readyState XMLHttpRequest 回傳值的結果,結果狀態如下:
  1. 要求無法初始化
  2. 伺服器連線已建立連線
  3. 要求已經讓伺服器接收
  4. 下載程序已經開始
  5. 整體程序下載完成
基本上,這個數值就是要求的傳輸過程而已,到達第四步驟時,整體要求才算結束。
status 整體要求流程完成 (上面的數值 4) 後,那到底該流程的結果是如何?就在這裡展示:
  • 200: "OK",代表資料接收成功沒問題
  • 403: "Forbidden",可能是權限不足,或者是伺服器設定禁止讀取該檔案
  • 404: "Page not found",該檔案不存在,所以無法下載。
基本上,這個數值與 Web page 的下載 code 有關,200 就是下載成功的意思。
statusText 你可以使用 status 找到數值回應,也可以使用 statusText 找到文字說明。 基本上,就是回應上面的 OK, Forbidden 等等的文字資料而已,一般少用啦!

當然是程序完成後 (readyState 為 4),以及確定下載完成 (status 為 200) 的情況下,才開始處理資料的放置更新,所以, 通常這個 getcontent() 函數,會這樣處理:

function getcontent() {
	if ( xhttp.readyState == 4 ) {
		if ( xhttp.status == 200 ) {
			subnav.innerHTML = xhttp.responseText;
		} else {
			subnav.innerHTML = '無法取得正確的子導覽清單';
		}
	}
}

如果回傳值是單純的文字資料,可以使用上面範本的 .responseText 這樣的格式來處理,但如果回傳的資料是一整個網頁內容,並不是資料而已, 該如何是好?沒關係,可以透過 .responseXML 來處理!只是透過 .responseXML 處理時,還可以針對某些元素做拆解! 例如,若回傳的資訊裡面有 id 名為 'res' 的資料時,要將它加入某個元素,可能就得要變成這樣:

xhttp.responseType = 'document';
var myxml = xhttp.responseXML;
var res = myxml.getElementById('res');
subnav.appendChild(res);

類似這樣的條件來處理呢!

例題 10-2-3:開始使用 Ajax 進行背景的資料傳輸行為
  1. 將 unit10-2-1.php 另存新檔 unit10-2-3.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 先匯入 js 腳本程式:先在 HTML 裡面的 <head> 當中,加入 function.js 檔案的存取,方式為:
    <script src='function.js'></script>
  3. 假設要匯入的按鈕為第 7 課,因此,請在第 7 課的 button 當中,加入
    onclick='getsubnav("unit07")'
  4. 建立名為 xhttp 的全域變數,先不用給內容。
  5. 開始建立 getsubnav(unit) 函數內容:
    • 讓 xhttp 內容為執行 createXHR() 函數
    • 判斷需要下載的檔案,因此,若 unit 為 unit07 時,就設定區域變數 uniturl 為 unit10-2-unit07.txt 這個檔名
    • 使用 GET 的方式,下載 uniturl 變數指定的檔案
    • 當狀態改變時,就執行 putsubnav 函數 (注意,不能加上小括號喔!)
    • 送出要求。
  6. 開始處理 putsubnav() 函數內容:
    • 設定變數 subnav 為取得 subnav 元素的控制權
    • 判斷 readyState 是否為 4
    • 判斷 status 是否為 200
    • 將 .responseText 帶入到 subnav ID 的 innerHTML 內。
    • 因為有更新 subnav 元素,因此重新執行一次 mymain 函數
  7. 開始處理 unit10-2-unit07.txt 這個檔案內容,請先新增這個檔名,然後將 index.php 內的第七章連結,抓下來貼上此處即可。
按下按鈕後的成品

用這種方式建立網頁的好處是,你可以將第一頁的美工做好之後,未來每個方塊裡面要塞什麼資料,全部都是自己決定, 都透過資料篩選的方式來透過取代的項目處理。你可以看到,在這個範例中,網址列的檔名都沒有改變喔!因為只是『更新』 subnav 的內容而已啊!

10.3: 取得具有 DOM 文件格式的資料

基本上,Ajax 除了可以取得一般正常的純文字資料 (responseText) 之外,實際上還是可以取得完整的網頁文件喔! 然後透過該文件,還能夠進行 DOM 的目錄樹資料查詢,用途非常廣!舉例來說,我們都知道 index.php 裡面有所有章節的連結, 既然都有連結了,為什麼還需要額外抓取資料到不同的檔案去?能不能接從 index.php 將各章節資料分別取得呢?

如前一個小節提到的,我們其實可以透過 javascript 取得的資料格式設定,指定讓資料變成 DOM 格式, 然後依據不同的 id 或者是 name 等方式,就可以擷取部份資料了。來玩一玩下一題,大概就比較清楚怎麼回事喔!

例題 10-3-1:使用既有的 HTML 網頁資料,以 ajax 進行篩選的作業
  1. 將 unit10-2-3.php 另存新檔 unit10-3-1.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 事先處理:打開 index.php 這個檔案,在個別的章節裡面的 ul 標籤,增加 id 識別碼,第一章為 <ul id='unit01'>, 第二章為 <ul id='unit02'>,以此類推,更新到目前的第 10 章的 ul 標籤。這個目的是,我們會透過按鈕, 在 ajax 取得 index.php 之後,依據這個 id 來擷取個別章節的連結,而不用各自獨立編寫文字檔。
  3. 新建 contentXY() 函數,目的在取得 content 元素的高度,內容由 mymain 取得:
    • 將 mymain 函數內的 (1)content 初始化、(2)subnavY 參數、(3)mainY 參數、(4)content 元素高度的判斷,以及 (5)content 元素的高度設定這 5 行移動到這個函數中, 未來更新 content 的高度將直接使用這個函數即可。
  4. 修改 mymain 函數
    • 將上述的 5 行移植到 contentXY 函數中,且在第一行就先執行 contentXY() 函數;
    • 前往 body 的 header 內,將 <button 裡面新增 id 項目,例如第七章就加上『 <button id='unit07'> 』這樣的設定, 這個章節資料可以的話,跟 index.php 內的 id 名稱對應最好。
    • 在 mymain 裡面,設定 mybtn 這個變數,內容為取得 header 元素內的 button 這個標籤名稱 (ByTagName)。 簡易的設定方式如下:
      var mybtn = document.getElementById('header').getElementsByTagName('button');
    • 針對 mybtn 這個陣列,使用 for 迴圈進行設定,增加 onclick 的事件監聽:『 mybtn[i].onclick = getsubnav 』這樣的事件監聽方式。
  5. 修改 getsubnav() 函數:
    • 增加『 unitid = this.id 』的設定,讓被點擊的按鈕的 id 名稱取得為 unitid
    • 主要是增加回應的資料必須是 DOM 文件,因此增加底下在 onreadystatechange 前即可:
      xhttp.responseType = 'document';
    • xhttp.open 的內容,將檔名修訂成為 index.php
  6. 修改 putsubnav() 函數:
    • 設計區域變數 xmldoc ,內容為 xhttp.responseXML 這樣的格式
    • 設計區域變數 res,內容為針對 xmldoc 取得 id 為 unitid (getsubnav 設計的變數),因此 res 也是個物件
    • 針對 subnav 設計 innerHTML 為空值 (將所有的子元素刪除)
    • 針對 subnav 增加子元素 (appendChild) ,子元素設計為 res 這個物件
    • 執行 contentXY() 函數,
使用同一個檔案的處理模式
  • 將文件加入 main 的 div 當中

若以上述的案例來看,左側的超連結看起來也是怪怪的!畢竟點選之後,還是跑去其他頁面了...如果你想要讓網頁的『 body 』內的資料可以保持在右側的方塊中, 那可以重複使用 ajax 的功能來處理即可。

例題 10-3-2:取得額外的文件,直接丟進系統中的某個方塊應用
  1. 將 unit10-3-1.php 另存新檔 unit10-3-2.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 因為預計要修改 subnav 裡面的 a 標籤的點擊情境,所以,在 putsubnav() 函數內,在 subnav 新增子元素後,進行如下的設計:
    • 設定名為 ref 的區域變數,內容於取得 subnav 元素內的 A 標籤控制權
    • 使用 for 迴圈,針對 ref[i] 設定 onclick 的事件監聽,會執行 gethref 的函數
  3. 開始處理 gethref() 函數,這個函數的主要目的,是取得『被點擊的那個 a 標籤內的 href 值』為何, 然後將該資料抓下來後,再想辦法透過 ajax 的動作,將遠端的 url 資料下載,並更新本身網頁內某個位置的資料。
    • 設定全域變數 myurl,其數值應該是『 this.href 』才對喔!
    • 取得這個數值之後,開始執行 getmain() 這個 ajax 函數的呼叫
    • 最後務必要回傳 false!讓 a 的點擊失效才行!
  4. 開始執行 getmain() 函數,主要在進行 ajax 的包裹行為:
    • 同樣使用 xhttp 建立 createXHR()
    • 設定 xhttp 為 document 類型
    • 在 onreadystatechnage 時,執行 putmain 函數
    • 使用 xhttp.open,同樣使用 GET 的方式,但下載的檔名為 myurl 這個變數。
    • 最後直接送出 (send)。
  5. 開始撰寫取得資料的 putmain() 函數:
    • 先設定 main 區域變數,內容為取得網頁內的 main 元素控制權
    • 如果 readyState 為 4 才進行
    • 如果 status 為 200 才進行
    • 同樣使用 xmldoc 取得 xhttp.responseXML 樣式
    • 設定區域變數 res 為 xmldoc 取得 TagName 為 BODY 的內容
    • 先清空 main 的 innerHTML
    • 讓 main 增加子元素為 res[0] !因為是 TagName 的關係!
    • 重複執行 contentXY() 函數
左側點擊後,右側畫面出現!

雖然這樣就可以取得 body 的物件,然後將它貼到右側去,可惜的是, script 的部份目前似乎支援度怪怪的, 而且,我們撰寫的 javascript 很容易衝突,因為函數名稱經常設定一樣...所以針對 javascript 來說, 應該是會有點傷腦筋!如果是純粹的 PHP 等,應該就比較沒問題!

10.4: 一個小 API 界面實做的範例

其實, ajax 的應用並不像上面提供的案例,一般來說,Ajax 的應用,通常是透過某些 API 來實做某個區塊的資料更新才對。 因此,ajax 通常會用額外的一些小程式 (API) 來提供網頁上某個區塊的更新才是。如果你使用了自己本身伺服器的 API 應用, 一般來說,問題不大。但是,如果你使用了不同來源的資料,就很可能會發生部份的問題,那就是可能會產生 CORS 的問題!

  • 分析資料來源網站的情境

先讓我們來實做一下用 ajax 取得外部資料的方法。現在,假設你想要在你網頁的右上方顯示現在即時的溫度與天氣狀態, 最常見的方法,當然就是跑去中央氣象局取得這些資料了。以 2020 年 12 月份的中央氣象局網站為例,如果想要看台南市的各測站的資料, 可以參考底下的網頁:

不過,我們並不是需要全台南的資料,如果只想要靠近崑山科大的氣象站,可以選擇永康區或者是台南測站,我們以永康測站為例, 點選永康測站,亦即如下的網站,就能得到最近 24 小時的測站天氣資料:

仔細觀察一下上述的連結,你會發現到網址列會有 ?ID=46742 這樣的狀態,其實那個數字就是中央氣象局針對各測站所設定的測站代碼, 因此,你只要輸入不同的測站代碼,那就可以得到該測站最近 24 小時的天氣資料了。這也就是說,上面這個網址, 其實就是中央氣象局為自己寫的一個 API 網頁程式喔!

如果你查看上述網頁的原始碼,可以發現到最底下的位置上,有個很特別的 javascript 腳本程式, 長的有點像底下的模樣:

  <script src="/V8/assets/js/observe/OBS_Station.js?v=20201016"></script>
  
  <!--[if lt IE 9]>
    <script src="/V8/assets/plugins/IE/respond.js"></script>
    <script src="/V8/assets/plugins/IE/html5shiv.js"></script>
    <script src="/V8/assets/plugins/IE/placeholder-IE-fixes.js"></script>
    <![endif]-->
</body>
</html> 

那個網址列其實就是取得不同測站數據的重要來源之一!這是因為關鍵字『 observe 』為『觀測』的意思! 點選該網址,你還可以持續發現很多類似網址列的重要分析,其中最重要的應該是 MOD_DIR 以及 MOD_FILE 的資料! 根據該資料,我們可以得到永康測站的 24 小時連續資料,應該可以從底下的網址列直接取得:

然後持續檢查一下上述的原始碼資料,就可以發現到內容就是一堆 <tr> 與 <td> 等表格資料內容! 然後透過類似 temp 與 weather 這兩個 headers 的表格內屬性,就可以得到我們需要的溫度與天氣狀態了。 之後再以 DOM 的分析方式,分別取得相關的資訊之後,應該有辦法將我們需要的資料直接更新到網站上吧? 現在就讓我們來測試看看。

  • 跨站執行 ajax 的問題

我們的目標是:『在網站右上角填入需要的天氣參數』,所以,先來規劃一下 header 裡面的資訊吧!

例題 10-4-1:設計網頁小方塊格式
  1. 將 unit10-3-2.php 另存新檔 unit10-4-1.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 先在 header 的 div 裡面,在所有的 button 後面,新增一個 div, id 設定為 cwb,內容直接寫上『天氣』兩個字即可。
  3. 針對 cwb 的 CSS 設計,大概先設計了:
    • 先給予框線,一般的框線設計即可
    • 因為要讓方塊『黏』在右上角,因此設計位置為絕對位置,然後上方與右方距離為 0px
    • 其餘包括 padding 與字型、字體大小等,請自由設計!自己開心就好!
設計小方塊

接下來,準備開始讓 ajax 發揮功效!其實,很簡單耶!就直接設計 ajax 那兩個函數即可!一個就是 get,一個就是 put, 唯一差別是,需要下載的 url 不一樣而已,實驗看看:

例題 10-4-2:讓小方塊填入中央氣象局的氣象參數資料
  1. 將 unit10-4-1.php 另存新檔 unit10-4-2.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 新增兩個函數,第一個主要是設計 ajax 的包裹,設計為 getcwb() 函數, 內容請由 getmain() 複製過來,但是更改部份資料:
    • 增加 cwburl 變數,內容就是之前談到的永康測站的網址列
    • onreadystatechange 所執行的函數變更名稱為 putcwb
    • open 的內容,網址列修訂為 cwburl 即可。
  3. 撰寫 putcwb() 函數,這個函數的內容先設計為取得 cwb 方塊元素的控制權即可! 其他等等再處理。
  4. 在 mymain() 函數最下方新增一行,主要執行 getcwb() 即可。
跨站處理的 cors 問題

基本上,目前的伺服器預設都是不允許來自非自己網域的 ajax 要求的,這是因為擔心被大量的 ajax 要求服務,導致自己的 loading 增加之外, 也可能會遭受到某些外部的攻擊!你可以上網使用 CORS 與 ajax 這個關鍵字查詢,就會查到一堆介紹的文章! 這裡不再重複說明。

  • 自己撰寫中繼網頁,達到避免 CORS 的問題

如果中央氣象局網站是我們能夠控制的,那麼很簡單,就可以在中央氣象局的 web server 上面,增加放行我們上課用網站的使用權, 那就可以直接連到中央氣象局了。可惜的是,不可能啊!那怎辦?沒關係,既然中央氣象局原本就是給我們查閱資料的, 那,把自己當成用戶端不就行了?這就是中繼站的由來!我們可以透過 PHP 後端平台的設計,來達到這個目的! 簡易的設計如下(假設檔名為 unit10-4-3.php):

<?php
	if ( isset($_GET['sid']) ) {
		$sid = $_GET['sid'];
	} else {
		$sid = "46742";
	}
	$url = "https://www.cwb.gov.tw/V8/C//W/Observe/MOD/24hr/" . $sid . ".html";
	// url 網址列不能含有類似空格的特殊字元喔!
	$data = file_get_contents($url);
?>
<!doctype html>
<html>
<head>
	<meta charset='utf-8' />
</head>
<body>
<?php
	echo $data;
?>
</body>
</html>

基本上,就完全模仿中央氣象局原本的資料格式,然後,請在 index.php 裡面新增一條連結,以例題 10-4-3 來連結到 unit10-4-3.php 檔案, 之後,請查詢中央氣象局的網站測站代碼,要更改測站代碼,直接以『 unit10-4-3.php?sid=46741 』之類的方式來更新即可! 這就是我們撰寫的中繼用小小 API 網頁了!

另外,你需要特別注意的是,上面 file_get_contents 函數內的網址列,不能有空格之類的特殊字元!

  • 繼續處理氣象資料的更新功能

現在我們就已經有個 API 了,因此,使用自己網站的資料,應該就不至於會影響到 CORS 這種跨站的問題了! 現在,讓我們來將溫度與天氣狀態抓下來嵌入到我們的網站上吧!

例題 10-4-4:透過自己網站的中繼站,進行天氣狀態嵌入
  1. 將 unit10-4-2.php 另存新檔 unit10-4-4.php,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 修改 getcwb() 函數,將原本的 cwburl 註解掉,然後:
    • 新增一個區域變數 sid,填寫永康測站為 46742
    • 將 cwburl 改為本地『 "unit10-4-3.php?sid=" + sid 』這樣的格式
  3. 修改 putcwb() 函數,大部分資料由 putmain() 複製過來,然後:
    • xmldoc 為使用 responseXML 的樣式取得
    • 設定 res 為 xmldoc 的 ClassName,名稱為『 tem-C is active 』若不確定時,請前往 API 的結果去查詢
    • 設計 mytmp 區域變數內容為 res[0].innerHTML 的值,這個是氣溫
    • 再次重新取得 xmldoc,同樣使用 XML 的樣式。
    • 重點在取得 img 內的 title (或 alt 皆可),因此使用 TagName 取得 IMG 元素
    • 設定 myweather 區域變數為 res[0].title。
    • 最終讓 cwb.innerHTML 搭配溫度與天氣的狀態來展示即可。
  4. 修改 mymain() 函數:
    • 保留一開始就執行一次 getcwb() 函數
    • 新增一行,讓該函數使用 setInterval 的方式,每 10 分鐘,就是 600 秒,就是 600000 毫秒的樣式重複執行!
利用自製中繼 API 取得中央氣象局溫度與天氣

未來,如果你有任何的資料庫需要讀、寫資料時,又不想要整頁更新,只想部份區塊進行更新,就可以透過設計 API 來處理。 另外,透過 ajax 的功能,你也可以將內容與樣式分開,讓樣式歸前端設計師處理,後端資料交由後端工程師處理, 然後前後端設計師彼此透過某支 API 進行資料的進/出工作,就可以達到分工的效果了!當然,設計這隻 API 的介接工程師, 工作就很繁重!這也是目前很熱門的職業之一喔!

10.5: 課後作業

系上未來的專題,很多時候都需要用到公開的資料庫系統,將上面的資料拿來進行比對或處理。因此,自製重要的 API 與將資料抓下來處理,就顯得很重要了!

  • 10-5-1、實做一個站外的 API 應用

選擇一個你有興趣的課題,例如鐵路時刻表、高鐵時刻表、環保署空氣品質、環保署水資源應用、水利署水庫目前水位、 新聞網站的最新新聞訊息等,反正就是你有興趣的課題,模仿本章課程中的中央氣象局資料取得的方式 (unit10-4-3.php), 製作一個簡易的中繼 API 網頁,用以取得你有興趣的部份網站資訊,而不是全部的資料。

收集資料的過程會比較繁複,請依據本課程的說明,慢慢收集好對方網站的 API 網頁,就可以逐步取得你想要知道的數據了。 沒有特定的主題,除了上面的主題之外,你也可以依據自己有興趣的主題來設計相關的跨站內容取得。

底下的範本,是根據環保署測站分析資料的結果來處理的!

利用自製中繼 API 取得環保署的空氣品質資料
  • 10-5-2、將資料整合到網頁中

請將資料整合到整體網頁中,以本章 unit10-4-4.php 為範例,看看你要將資料結合到什麼地方都可以!不過,如果以上面的資料來看,輸出的格式其實是 JSON 的格式,要轉成我們可以應用的 javascript 元件,可能需要使用『 JSON.parse(output) 』來處理資料的輸出才可以喔!

利用自製中繼 API 取得環保署的空氣品質資料

10.6: 參考資料

本網頁由 VBird@dic.ksu 製作!若內容有涉及著作財產權問題,還請來信告知 dmtsai@mail.ksu.edu.tw!