AJAX :
Asynchronous
JavaScript
And
XML
จริง ๆ AJAX ไม่ใช่ภาษาโปรแกรมใหม่แต่อย่างใด แต่มันคือวิธีเขียนรูปแบบหนึ่ง แล้วมันดียังไง ? มันทำอะไรได้บ้าง ? ถ้าให้พูดเข้าใจแบบง่าย ๆ การเขียน AJAX นั้นเป็นวิธีเขียนที่ทำให้หน้าเว็บนั้นไม่ต้องโหลดขึ้นมาใหม่ทั้งหน้าทุกครั้งที่มีการอัพเดทหน้าใหม่โดยจะโหลดแค่ส่วนที่อัพเดทขึ้นมาใหม่เท่านั้น
แล้วถามว่าแล้วมันดีอย่างไร ? ก็แค่เป็นตัวช่วยที่ทำให้หน้าเว็บและเราไม่ต้องคอยมากดเพื่อรีเฟรชทุกครั้งที่โหลดแค่นั้นเอง ใช่!! แค่นั้นละที่มันทำให้ AJAX ถูกใช้งานอย่างแพร่หลายเพราะมันจะทำให้ User ไม่ต้องมาทนเห็นการโหลดหน้าใหม่ทุกครั้งที่มี action อะไรบางอย่างมันทำให้บางครั้ง User อาจจะรำคาญแล้วปิดเว็บไปเลยก็ได้และบางเว็บ หน้าเว็บนั้นมีอยู่หลายส่วนให้โหลดใหม่ทุกรอบมันก็ค่อนข้างช้า ทำให้การนำเอา AJAX มาใช้จะเป็นประโยชน์ต่อเว็บของเรามากครับ
บทความแนะนำ
ซึ่งบทความนี้เราจะพามาเขียน AJAX โดยใช้ JavaScript แบบไม่ใช้เฟรมเวิร์ค ในการเขียนเพื่อดึงข้อมูลจาก Database ผ่านทาง API มาแสดงบนหน้าเว็บของเราและยังสามารถ Create, Read, Update และ Delete ด้วย Todo ของเราได้โดยผ่าน API ที่เราได้สร้างขึ้นในบทความที่แล้ว
บทความนี้เป็นซีรี่ย์ต่อเนื่องจาก Ep.1ใครที่ยังไม่ได้อ่านยังไม่ได้ทำตามอยากให้ทุกคนลองกลับไปอ่าน EP.1 ก่อนนะครับ ดังลิ้งค์ด้านล่าง
สร้าง Toto App Django REST Framework + Ajax Ep.1
เกริ่นนำกันไปแล้ว ที่นี้เราจะมาเริ่มสร้างและใช้งานทั้งในส่วนของ Front-end และ Back-end รวมไปถึงการ fetch ข้อมูลผ่าน API กันครับ
Creating Frontend App
เราจะทำในโปรเจคท์เดิมที่เราได้สร้างไว้ใน Ep.1 นะครับ เพื่อทำให้เราสามารถจัดการโปรเจคค์ได้ง่ายและเข้าใจได้ง่ายขึ้นเราจะสร้าง App ขึ้นมาใหม่แยกจาก todoapp ตัวเก่าที่ค่อยจัดการ API โดย App ที่สร้างใหม่นี้จะเป็นตัวจัดการในส่วนของ Frontend ก็คือหน้าเว็บที่เอามาแสดงผลให้ user ได้เห็นละครับมาเริ่มสร้าง App กันเลยดีกว่าโดยใช้คำสั่ง
(env) python manage.py startapp todopage
แล้วหลังจากสร้าง App แล้วต้องไม่ลืมที่จะติดตั้งด้วยนะครับ
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'todoapp', 'todopage', # App ที่พึ่งสร้างมาใหม่ ]
โดย todopage นี้จะเป็น App ที่เอาไว้จัดการหน้าบ้านที่โชว์ให้กับ user ได้ดู
ในส่วนของการเก็บไฟล์ที่เป็นไฟล์ HTML เราจะเก็บไว้ในโฟลเดอร์ templates ให้ทุกคนสร้างโฟลเดอร์ขึ้นมาใหม่โดยให้อยู่ในโฟลเดอร์ todopage และในโฟลเดอร์ templates นั้นต้องมีชื่อ app ด้วยดังตัวอย่าง
ในภาพจะเห็นว่าผมได้สร้าง index.html ขึ้นมาแล้วซึ่งไฟล์นี้จะเป็นไฟล์หลักของเราในการเขียนโครงสร้างเว็บ html ของเว็บเราซึ่งจะมีอยู่หน้าเดียวละครับ เพราะเว็บที่เราสร้างไม่ได้ทำงานอะไรเยอะแค่โชว์ todo เอง
อย่างแรกอยากให้ทุกคนทดลองเพิ่มโค้ดลงไปในไฟล์ HTML นิดหน่อยให้มีคำโชว์ขึ้นมาบนเว็บดังตัวอย่าง
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<title>Todo App</title>
</head>
<body>
<h1>StackPython</h1>
</body>
</html>
ตอนนี้เรายังไม่สามารถที่จะโชว์หน้าที่เราสร้างขึ้นมาได้เพราะต้องไปเพิ่ม function ใน views.py เพื่อเรียก templates มาใช้โดยสามารถเขียนโค้ดเพิ่มไปในไฟล์ views.py ได้ดังนี้
from django.shortcuts import render
def todoList(request):
return render(request, 'todopage/index.html')
หลังจากนั้นให้เราสร้างไฟล์ urls.py ขึ้นมาเพื่อเพิ่ม link เชื่อมต่อกับ views.todoList เพิ่มโค้ดลงใน urls.py ดังนี้
#todopage/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.todoList, name='list-todo') ]
#todoapp/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('todo.urls')),
path('', include('todopage.urls')), #new
]
(env) python manage.py runserver
#todopage/templates/todopage <!DOCTYPE html> <html lang="th"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Todo App</title> </head> <body> <div class="header" id="header" > <form id="form"> <h2 >To Do List</h2> <input type="text" name='todo' id='title' placeholder="Add new todo" > <input type="submit" class="addBtn" value="Add"> </form> </div> <div id="todo-list"> <div id="task-1" class="task-suc"> <div class="text-todo"> eat breakfast </div> <span class="close" onclick="">×</span> </div> <div id="task-1" class="task"> <div class="text-todo"> read the book </div> <span class="close" onclick="">×</span> </div> </div> </body> </html>
<!-- ต้อง load static เข้ามาใช้ใน html ของเราโดยเพิ่มบรรทัดนี้ไว้บนสุดของไฟล์ html -->
{% load static %} <html> ... <!-- เพิ่ม Link ที่เป็น css เข้ามาใน head tag ตามนี้เลย --> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Todo App</title> <!-- New --> <link rel="stylesheet" type='text/css' href="{% static 'css/todo.css'%}"> </head> ... </html> <!-- ในบรรทัดสุดท้ายก็เพิ่มไฟล์ script เข้ามาใช้กับหน้านี้ --> <script src="{% static 'js/todo.js'%}"></script>
.container{ display: flex; justify-content: center; } body { margin: 0; min-width: 250px; } * { box-sizing: border-box; } /* ----------------Header---------------- */
input { margin: 0; border: none; border-radius: 0; width: 75%; padding: 10px; float: left; font-size: 16px; } .addBtn { padding: 10px; width: 25%; background: #6B0772; color: #fff; float: left; text-align: center; font-size: 16px; cursor: pointer; transition: 0.3s; border-radius: 0; } .addBtn:hover{ background-color: #AF1281; } .header { background-color: #e6326d; padding: 30px 40px; color: white; text-align: center; } .header:after { content: ""; display: table; clear: both; } /* ----------------Task---------------- */ .task{ width: 100vw; min-width: 250px; cursor: pointer; position: relative; padding: 12px 8px 12px 40px; background: #FC94AF; border-bottom:solid 0.5px #fda9bf; } .task:hover{ background: #fda9bf; } .task-suc{ width: 100vw; min-width: 250px; cursor: pointer; position: relative; padding: 12px 8px 12px 40px; background: #affc94; border-bottom:solid 1px #85F65E; } .text-todo{ font-size: 22px; color: #fff; } .close{ position: absolute; right: 0; top: 0; padding: 12px 30px 12px 30px; font-size: 22px; color: #fff; } .close:hover{ position: absolute; right: 20; top: 0; padding: 12px 30px 12px 30px; font-size: 22px; color: #fff; background-color: #f44336; }
<div id="todo-list"> <div id="task-1" class="task-suc"> <div class="text-todo"> eat breakfast </div> <span class="close" onclick="">×</span> </div> <div id="task-2" class="task"> <div class="text-todo"> read the book </div> <span class="close" onclick="">×</span> </div> </div>
<div id="todo-list">
</div>
buildList() //เรียกใช้ function function buildList() { let url = "http://127.0.0.1:8000/api/todo-list/"; fetch(url) .then((res) => res.json()) .then(function (data) { console.log("Data", data); }); }
buildList() //เรียกใช้ function function buildList() { let task = document.getElementById("todo-list"); // เรียก div ที่มี id = todolist let url = "http://127.0.0.1:8000/api/todo-list/"; fetch(url) .then((res) => res.json()) .then(function (data) { let list = data; for (let i in list) { let task_class = "task"; // กำหนดชื่อ class ของแต่ละ task if (list[i].Completed == true) { task_class = "task-suc"; // ถ้า task ไหนทำเสร็จแล้วให้เปลี่ยนชื่อ class เป็น task-suc } // สร้าง item ที่เป็น div ของแต่ละ task ขึ้นมาแล้วเพิ่มเข้าไปใน todo-list let item = ` <div id="task-${i}" class=${task_class}> <div class="text-todo"> ${list[i].title} </div> <span class="close" onclick="">×</span> </div> `; task.innerHTML += item; }); }
จะเห็นว่าผมได้นำข้อมูลที่ได้มา loop เพื่อดึงข้อมูลออกมาโดยมีการ if ดักไว้ว่าถ้า task ไหนทำเสร็จแล้วก็ให้เปลี่ยน class เป็น task-suc เพื่อแสดงผลให้กับ user ได้ทราบว่า task นี้เสร็จแล้วตามที่เราได้ออกแบบไว้ซึ่งผมสร้าง item ขึ้นมาเพื่อรองรับแต่ละ task โดยจะสร้าง div ขึ้นมาใหม่เหมือนกับที่เราเคยสร้างแต่เราเขียน Script ให้มันสร้างให้ตามข้อมูลที่มีอยู่ใน database แล้วเพิ่มเข้าไปใน div ที่เป็น todo-list บวกเพิ่มตามบรรทัดข้างล่างเลย task.innerHTML += item ทำให้ task ที่อยู่ใน database นั้นมาแสดงบนหน้าเว็บของเรา ( การสร้าง item ขึ้นมาใหม่แบบนี้เป็นการทำ AJAX แล้วครับเพื่อที่จะไม่ต้องโหลดข้อมูลแล้วสร้างใหม่ทุกครังแต่เราจะเอาข้อมูลที่ได้มาใหม่นั้นไปต่อข้อมูลเดิมที่มีอยู่แล้ว ) จากนี้ถ้าเราทดลองรีเฟรชหน้าเว็บก็จะได้ตามนี้ครับ (ถ้าไม่ได้ลองรัน server ขึ้นมาใหม่นะครับ)
let form = document.getElementById("header"); form.addEventListener("submit", function (e) { e.preventDefault(); console.log("Form Submit"); let url = "http://127.0.0.1:8000/api/task-create/"; // นำข้อความที่อยู่ในช่อง input เข้ามาไว้ในตัวแปรชื่อ title let title = document.getElementById("title").value; fetch(url, { method: "POST", headers: { "Content-type": "application/json", "X-CSRFToken": csrftoken, }, // แปลงข้อมูลให้อยู่ในรูป json ที่เป็นเก็บ title เป็น string body: JSON.stringify({ title: title }), }).then(function (res) { // เรียก function ขึ้นมาเพื่อสร้าง List ที่เพิ่มเข้ามาใหม่ลงไปใน todo-list หน้าเว็บของเรา buildList(); // Reset ข้อมูลที่เรากรอกใน form document.getElementById("form").reset(); }); });
function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== "") { const cookies = document.cookie.split(";"); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === name + "=") { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } const csrftoken = getCookie("csrftoken");
แค่นี้เราก็สามารถใช้ csrftoken ได้แล้วโดยเมื่อเรากำหนด headers อะไรเรียบร้อยแล้วพอในส่วนของ body เราก็เพียงแค่นำ input ที่ user ได้กรอกเข้ามาส่งไปผ่าน body และแปลง Input ให้อยู่ในรูปแบบ json แล้วค่อยส่งแค่นั้นเมื่อทำเสร็จแล้ว ( .then คือเมื่อ script นั้นทำงานเสร็จหลังจากนี้จะให้ทำอะไรต่อไป ) เราจะมีการเรียก buildList() ซึ่งเป็น function ที่สร้างเพื่อรองรับการเรียกใช้เพื่อที่จะทำ AJAX อยู่แล้วมันก็จะเพิ่ม List ใหม่ที่มีในส่วนของ task ที่เพิ่มเข้าไปใหม่ด้วยจากนั้นก็ reset form จากนี้เราก็สามารถทดลองกดส่งได้แล้วมาดูผลกันครับว่าเป็นอย่างไร **อย่าลืม Refresh หน้าเว็บใหม่ด้วยนะครับ
function buildList() { ... for (let i in list) { try { document.getElementById(`task-${i}`).remove(); } catch (err) { console.log(err); }
เมื่อเราเพิ่มอันใหม่เข้าไปแล้วจะเห็นว่าเว็บของเรานั้นไม่ refresh อีกแล้วเมื่อเพิ่ม task อันใหม่เข้าไปแล้วก็เคลียร์ task อันเก่าทิ้งแล้วด้วย เห็นไหมครับการทำ AJAX โดยใช้ JavaScript นั้นก็ไม่ได้ยากอย่างที่คิดซึ่งเราแค่ต้องเข้าใจการเพิ่มและการลบวัสดุที่อยู่ในหน้าเว็บของเราแทนการ รีเฟรชหน้าเพื่อให้อัพเดทข้อมูลใหม่ก่อนตามสิ่งที่ผมได้เขียนหวังว่าสามารถทำความเข้าใจได้ไม่มากก็น้อยนะครับ
ยังไม่จบนะครับสำหรับบทเรียนนี้เมื่อเรามีการสร้าง task ขึ้นมาใหม่ได้แล้วเรายังสามารถ Update task ได้ด้วยโดยจะเปลี่ยนสถานะจากยังไม่ทำเป็นทำเสร็จแล้วโดยเราต้องสร้างฟังชั่นขึ้นมาใหม่ในไฟล์ js ซึ่งเป็นฟังชั่นที่เรียกใช้ API ที่เราได้สร้างไว้รองรับการอัพเดทของแต่ละ task แล้วโดยสามารถดึงมาใช้ได้เลยดังนี้
function changeStatus(task) { console.log("Task", task); let completed = !task.Completed; let url = `http://127.0.0.1:8000/api/todo-updates/${task.id}`; fetch(url, { method: "POST", headers: { "Content-type": "application/json", "X-CSRFToken": csrftoken, }, body: JSON.stringify({ title: task.title, Completed: completed }), }).then(function () { buildList(); }); }
โดยเราจะรับ task เข้ามาใน function เพื่อที่จะเปลี่ยนสถานะตรงข้ามกับที่สถานะปัจจุบัน (!task.Completed)
(!task.Completed) เช่นถ้า ยังไม่ทำ => ทำเสร็จแล้ว และ ทำเสร็จแล้ว => ยังไม่ทำ จะเห็นว่าเราก็จะส่ง POST คล้ายๆ กับการสร้าง task เลยแต่ u url เปลี่ยน body เปลี่ยนแค่นั้นเองครับโดย url เราต้องระบุด้วย task ที่จะ update นั้นเป็น id อะไรแล้วก็ใส่เข้าไปใน url ได้เลยตามด้านบนที่ผมใส่ก็จะดึง task.id เข้ามาใส่ใน url แล้วในส่วนของ body นั้นก็จะส่งข้อมูลไปสองส่วนก็คือ title และ Completetd ซึ่งตัว Completetd นี้ละจะเป็นตัวอัพเดทสถานะของแต่ละ task แล้วเมื่อทำงานเสร็จก็เรียกใช้ฟังชั่น buildList() เพื่อที่จะเรียก todo-list อันใหม่ที่มีการอัพเดทแล้วโดยไม่ต้องรีเฟรชหน้าเว็บใหม่ แต่เรายังไม่ได้เพิ่ม changeStatus() เข้าไปยังแต่ละ task เลยไม่งั้นเราก็ไม่สามารถกดใช้ได้โดยผมอยากให้เพื่อเรากดโดน task มันคือการเปลี่ยนสถานะได้เลยเช่น เมื่อกดโดนสีชมพูก็จะเป็นสีเขียนเมื่อกดสีเขียนก็จะเป็นสีชมพู โดยเราต้องไปเพิ่มที่ buildList() โดยเราจะเพิ่มหลังจากที่สร้าง fetch todo list เสร็จแล้วแล้วค่อยเพิ่มโค้ดตามนี้
function buildList() { let task = document.getElementById("todo-list"); let url = "http://127.0.0.1:8000/api/todo-list/"; fetch(url) .then((res) => res.json()) .then(function (data) {...} for (var i in list) { let divTask = document.getElementById(`task-${i}`); // เพิ่ม eventListener ให้แต่ละ task โดยเมื่อ click แล้วก็จะทำตาม function ที่เราเขียนไว้ divTask.addEventListener( "click", (function (item) { return function (ev) { if (ev.target.tagName === "DIV") { changeStatus(item); } }; })(list[i]) // function รับค่าแต่ละ task ที่เก็บอยู่ใน list ); } }); }
ถ้าใครสามารถกดคลิ๊กแล้วเปลี่ยนสีแบบนี้ Script ทำงานปกติครับ
function deleteTask(task) { console.log("delete", task); let url = `http://127.0.0.1:8000/api/task-delete/${task.id}`; fetch(url, { method: "DELETE", headers: { "Content-type": "application/json", "X-CSRFToken": csrftoken, }, }).then(function () { buildList(); }); }
function buildList() { let task = document.getElementById("todo-list"); let url = "http://127.0.0.1:8000/api/todo-list/"; fetch(url) .then((res) => res.json()) .then(function (data) {...} for (var i in list) { let deleteBtn = document.getElementsByClassName("close")[i]; deleteBtn.addEventListener( "click", (function (item) { return function (ev) { if (ev.target.tagName === "SPAN") { deleteTask(item); } }; })(list[i]) ); ... } }
let list_snapshot = []; //สร้างตัวแปรนอก buildList()
function buildList() { let task = document.getElementById("todo-list"); // task.innerHTML = ""; let url = "http://127.0.0.1:8000/api/todo-list/"; fetch(url) .then((res) => res.json()) .then(function (data) { ..... // ดักว่ามีการเปลี่ยนแปลงไหมถ้ามีการเปลี่ยนแปลงก็จะ remove ตัวนั้นออก if (list_snapshot.length > list.length) { for (var i = list.length; i < list_snapshot.length; i++) { document.getElementById(`task-${i}`).remove(); } } // เป็นการ snapshot list เอาไว้เพื่อเอาไว้เช็คได้ list_snapshot = list; for (var i in list) { let deleteBtn = document.getElementsByClassName("close")[i];
.....
กิจกรรมที่กำลังจะมาถึง
ไม่พลาดกิจกรรมเด็ด ๆ ที่น่าสนใจ
Event นี้จะเริ่มขึ้นใน April 25, 2023
รายละเอียดเพิ่มเติม/สมัครเข้าร่วมคอร์สเรียนไพธอนออนไลน์ที่เราได้รวบรวมและได้ย่อยจากประสบการณ์จริงและเพื่อย่นระยะเวลาในการเรียนรู้ ลองผิด ลองถูกด้วยตัวเองมาให้แล้ว เพราะเวลามีค่าเป็นอย่างยิ่ง พร้อมด้วยการซัพพอร์ตอย่างดี