JavaScript 與 jQuery

第 8 章   AJAX

∗ 同步處理模型 (Synchronous processing model)

▸ 當瀏覽器讀到 <script> 標籤時,會停止載入頁面資料,然後載入 JavaScript 檔案並執行,執行完畢後再繼續資料載入

▸ 如果 JavaScript 程式還需要伺服器提供額外資料時,瀏覽器不僅需要等待 JavaScript 程式載入及執行,也要等待伺服器完成資料傳送,過程中使用者無法操作頁面

∗ 非同步處理模型 (Asynchronous processing model)

▸ 當瀏覽器在等待所需載入的資料時,使用者依舊可以操作頁面元素,以提昇使用者經驗 (User experience, UX)

▸ 亦稱為非阻斷處理模型 (Non-blocking processing model)

▸ 更新部份頁面資料:當伺服器資料送達時,程式只更新該部份的頁面資料,其餘頁面資料不動

✶ 主要技術:AJAX

∗ AJAX (Asynchronous JavaScript and XML)

▸ 僅將部份資料載入網頁,不需要刷新整個頁面,資料載入量少,速度快,使用者不需要等待整個頁面載入,頁面流暢度高

▸ 是單一網頁應用 (Single page web application) 的主要技術

▸ 資料傳送格式:HTML, XML, JSON (JavaScript object notation)

▸ 應用:資料輸入自動完成 (Autocomplete)、購物車產品資料更動、新訊息通知 (彈出視窗)、判斷註冊帳號是否重複...

(1) AJAX 資料格式

∗ AJAX 資料格式有 3 種:

▸ HTML

✶ 優點:簡單、易寫、易呈現 (無需另外處理顯示格式)
✶ 缺點:伺服器需產生 HMTL 格式,不適合非 Web 應用程式

▸ XML (Extensible markup language)

✶ 優點:可表示架構複雜的資料,可使用相同的 DOM 方法
✶ 缺點:格式過於繁雜,需大量程式來處理資料
✶ 範例:
<?xml version="1.0" encoding="utf-8"?>
<sales>
  <sale>
    <product>電視機</product> 
    <location>台中</location>
    <date>2016-6-30</date>
    <price>20000</price>
  </sale>
  <sale>
    <product>冰箱</product> 
    <location>高雄</location>
    <date>2016-8-2</date>
    <price>18000</price>
  </sale>
</sales>

▸ JSON (JavaScript object notation)

✶ 優點:格式簡單,是 JavaScript 常用的資料格式
✶ 缺點:格式嚴謹,對錯誤的包容性小,JSON 也是 JavaScript,因此可產生惡意內容,對於提供資料的網域應有安全驗證
✶ JavaScript 可以將 JSON 資料轉為 JavaScript 物件,也可以由 JavaScript 物件轉為 JSON 資料
✶ 範例:
{
  "product": "電視機",
  "location": "台中",
  "date": "2016-6-30",
  "price": 20000
},
{
  "product": "冰箱",
  "location": "高雄",
  "date": "2016-8-2",
  "price": 18000
}

(2) 利用 jQuery 處理 AJAX 請求與回覆

∗ jQuery 處理 AJAX 請求的方法

方法說明
.load()將 HTML 資料載入某個元素
$.get()利用 HTTP 的 GET 方法發出請求 (Request)
$.post()用 HTTP 的 POST 方法發出請求
$.getJSON()利用 GET 請求載入 JSON 資料
$.getScript()利用 GET 請求載入並執行 JavaScript 資料
$.ajax()可執行各種請求方法,以上方法均為此方法的簡化版

▸ 以上除了 .load() 外,均為 jQuery 的全域方法,因此前方有 $

∗ 利用 .load() 方法載入 HTML 資料

▸ 先利用選擇器選取要更新內容的元素,再發出載入請求

▸ 例如:

 $('#update').load('http://example.com/getData/');
 $('#update').load('http://example.com/getData/#content');
✶ 第 2 例在 URL 裡再加上選擇器,表示僅載入符合該選擇器元素的資料

∗ 練習:利用頁面連結載入不同資料

▸ 下載 assets8.zip,解壓縮後將 ch8.css 置於 ch8/css/ 目錄中,其餘影像均置於 ch8/img/ 目錄中

jq-load.html

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/ch8.css">
</head>
<body>

<header>
  <h1>驚奇巴士</h1>
  <p>上車囉,驚奇巴士將帶你各處欣賞美景,有好吃、有好玩、有好看!</p>
  <nav>
    <a href="jq-load.html" class="current">首頁</a>
    <a href="jq-load2.html">路線</a>
    <a href="jq-load3.html">玩具</a>
  </nav>
</header>

<section id="content">
  <div id="container">
    <h2>刺激有趣!</h2>

    <div class="third">
      <img src="img/home1.jpg" alt="四軸飛行器">
      <p>由 JavaScript 控制的四軸飛行器讓你從上方拍攝你想看的景物</p>
    </div>
    <div class="third">
      <img src="img/home2.jpg" alt="線路板">
      <p>超小電腦:Arduino</p>
    </div>
    <div class="third">
      <img src="img/home3.jpg" alt="飛輪">
      <p>未來的運輸器:飛輪</p>
    </div>
  </div>
</section>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="js/jq-load.js"></script>

</body>
</html>

jq-load2.html (與 jq-load.html類似,僅 <section id="content"> 內容不同)

...
<section id="content">
  <div id="container" class="location"> 
    <h2>巴士在這些地方停留</h2> 
    <div class="event"> 
      <img src="img/map-ca.png" alt="台北"> 
      <p><b>台北</b><br>5月1日</p> 
    </div> 
    <div class="event"> 
      <img src="img/map-tx.png" alt="台中"> 
      <p><b>台中</b><br>5月3日</p> 
    </div> 
    <div class="event"> 
      <img src="img/map-ny.png" alt="高雄"> 
      <p><b>高雄</b><br>5月5日</p> 
    </div> 
  </div>
</section>
...

jq-load3.html (與 jq-load.html類似,僅 <section id="content"> 內容不同)

...
<section id="content">
  <div id="container">
    <h2>科技的魅力</h2>
    <div class="third intro">
      <p>想了解電子電路嗎?</p>
    </div>
    <div class="third">
      <img src="img/toys1.jpg" alt="麵包板">
      <p>電子電路實驗:麵包板</p>
    </div>
    <div class="third">
      <img src="img/toys2.jpg" alt="主機板">
      <p>Arduino的主機板</p>
    </div>
  </div>
</section>
...

jq-load.js

$(document).ready(function() {
  
  $(document).on('click', 'nav a', function() {
    var $this = $(this);
    $('nav a.current').removeClass('current');
    $this.addClass('current');
    $('#content').load($this.attr('href') + ' #content').hide().fadeIn('slow');
    return false;
  });  
  
});
.on('click', 'nav a', ...:綁定「 首頁 」、「 路線 」、與「 玩具 」等連結之點擊事件
✶ 當點擊連結時
# var $this = $(this):事件發生的元素設定給變數 $this
# $('nav a.current').removeClass('current'):移除原先的 current 類別
# $this.addClass('current'):將目前連結加上 current 類別
# 利用 load()取得另一檔案裡的目標資料 (<section id="content"> 的內容),並以淡入的方式載入 #content 元素內
# return false:停止瀏覽器預設行為(不會發出請求)

∗ 練習:AJAX 結合後端 Python+Flask 程式,其功能為前端送出一個數字,後端將其加 5 後傳回

▸ 規劃專案目錄結構如下:

webapps/
   virtualenv/
   workspace/
workspace 子目錄:工作區,包含各個專案
virtualenv 子目錄:虛擬環境 (Virtual environment),安裝各專案所需要的套件

▸ 建立專案目錄

$ cd
$ mkdir webapps webapps/virtualenv webapps/workspace
以上指令:先移到家目錄,然後一次新增三個目錄 ( webapps 一定要放在第一個,然後才能建立子目錄)

▸ 安裝套件:

✶ 虛擬環境相關套件: pip3 與 virtualenv
$ sudo apt install python3-pip
$ sudo pip3 install virtualenv
✶ 建立虛擬環境:移到 virtualenv 目錄,然後建立 ajaxVenv 虛擬環境目錄
$ cd <...>/webapps/virtualenv
$ virtualenv ajaxVenv
✶ 啟用虛擬環境,並在虛擬環境中安裝 flask
$ cd <...>/webapps/virtualenv/ajaxVenv
$ source bin/activate
(ajaxVenv) $ pip install flask
--> 啟用虛擬環境後,提示符號前會出現虛擬環境之名稱 <ajaxVenv>

▸ 建立 ajax 專案:

✶ 建立 <...>/webapps/workspace/ajax 目錄
(ajaxVenv) $ cd <...>/workspace/
(ajaxVenv) $ mkdir ajax
✶ 建立主程式 ajax.py
(ajaxVenv) $ cd ajax
(ajaxVenv) $ gedit ajax.py
from flask import Flask
from flask import render_template
from flask import request

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('ajax.html')

@app.route('/addFive/')
def addFive():
    return str(int(request.args.get('num'))+5)

if __name__ == '__main__':
    app.run()
# URL 為 / 的請求:由 index() 函式處理, 並顯示 ajax.html 檔案
# URL 為 /addFive/ 的請求:由 addFive() 函式處理, 將使用者所傳入的數字加 5 後回覆
# app.run():執行程式
ajax.html 範本:置於專案目錄下的 templates 目錄
(ajaxVenv) $ mkdir templates
(ajaxVenv) $ cd templates
(ajaxVenv) $ gedit ajax.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>

<p>猜數字遊戲</p>
<button type="button">送出隨機數字</button>
<p>送出數字: <span id="sendData"></span></p>
<p>回傳數字: <span id="returnData"></span></p>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function() {

  $(document).on('click', 'button', function() {
    var num = Math.round(Math.random()*100);
 
    $.ajax({
      url: '/addFive/',
      type: 'GET',
      data: {'num': num},
    })
    .done(function(data) {
      $('#sendData').html(num);
      $('#returnData').html(data);
    })
    .fail(function() {
      $('#sendData').html(num);
      $('#returnData').html('失敗');
    });
 
  });
  
});
</script>
</body>
</html>
# $.ajax({ ...:jQuery 之 AJAX 方法
# url: ...:設定請求的 url 網址
# type: 'GET':請求方法為 GET
# data: {'num': num}:將名為 num 的變數及其值傳給伺服器
# .done():如果做完(成功),顯示送出資料及回傳資料
# .fail():如果失敗,顯示送出資料及「失敗」字樣

▸ 執行專案程式 (先啟動虛擬環境)

(ajaxVenv) $ cd <...>/workspace/ajax
(ajaxVenv) $ python ajax.py
* Serving Flask app "ajax" (lazy loading)
* Environment: production
  WARNING: Do not use the development server in a production environment.
  Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

▸ 在瀏覽器網址欄輸入 localhost:5000 (或 127.0.0.1:5000):

ajax.png

▸ 註:POST 請求程式如下

$(document).on('click', 'button', function() {
  var num = Math.round(Math.random()*100);
  var formData = new FormData();
  formData.append('num', num);
  
  $.ajax({
    url: '/addFive/',
    type: 'POST',
    data: formData,
    processData: false,
    contentType: false,
  })
  .done(function(data) {
    ...
  })
  .fail(function() {
    ...
  });
});
var formData = new FormData() :產生 FormData 物件
formData.append(...) :表單附加資料
$.ajax() 參數
# data:送至伺服器的表單資料
# processData: false:資料已符合表單規格,不需要額外處理資料
# contentType: false:資料已符合表單規格,不需要再設定內容型態標頭 (Content type header)

上一章