Chào các bạn
Đây là Chương 2 của series lập trình ứng dụng RESTful, được dịch từ bài AWS上で構築するRESTfulアプリ勉強会~Web開発ワークショップ~【第2回】 của tác giả @k_shimoji , tôi sẽ viết lại theo đúng trình tự và nội dung như bài viết gốc nhưng theo văn phong của người Việt để các bạn dễ hình dung, ngoài ra sẽ có nhiều phần tôi bổ sung thêm cho những bạn sử dụng IDE khác hoặc thêm minh họa cho dễ hiểu, tôi sẽ làm song song với việc dịch để đảm bảo có những hướng dẫn sát với bài học nhất.
Rất mong nhận được những phản hồi quý giá từ các bạn quan tâm.
※ Những tài nguyên cần chuẩn bị
Đăng nhập bằng SSH
Cách đăng nhập bằng SSH các bạn có thể xem bài viết trước của tôi: Các bài học lập trình ứng dụng RESTful ~ Chuyên đề phát triển Web - [ Bài 1] Xây dựng ứng dụng trên WS
Các bạn cần chắc chắn đã làm các bước sau:
Ở bài trước chúng ta đã tạo một EC2 instance để chạy server, chúng ta cần tạo ít API nhất có thể, và hãy chắc chắn rằng:
- EC2 instance đã được bật
- Đã có phần mềm check API ( công cụ POSTMAN)
Nội dung
Bây giờ mình sẽ sử dụng backbone.js và tạo một ứng dụng TODO đơn giản sẽ gọi đến server-side API mà mình đã tạo trước đó.
Tôi sẽ chia Chương này ra các bài học nhỏ, mỗi bài mình sẽ làm dần các chức năng từ cơ bản nhất, đến bài 8 chúng ta sẽ có được ứng dụng TODO này với các chức năng cần thiết.
- Bài 1 Routing
- Bài 2 Kiến thức cơ bản về MVC
- Bài 3 Hiển thị danh sách
- Bài 4 Chức năng thêm công việc mới
- Bài 5 Thay đổi trạng thái checkbox
- Bài 6 Viết chức năng xóa công việc
- Bài 7 Hiển thị trang chi tiết công việc
- Bài 8 Chỉnh sửa công việc ở trang detail (chi tiết)
Trước khi thực hành, chúng ta sẽ tìm hiểu qua về backbone.js
backbone.js là gì ?
Đây là một client-side MVC framework.
Đây là lần đầu bạn dùng backbone, vậy dùng backbone có lợi gì ?
Chúng ta sẽ so sánh với việc không sử dụng libraries và frameworks,
- Nó có thể tạo sẵn một mô hình MVC tại client side
- Giảm bớt sự phức tạp của bố cục source.
- Quản lý source code tốt hơn
※ Đó là những lợi ích của backbone, tuy nhiên nếu bạn không cẩn thận, khi môi trường code bắt đầu lớn dần lên, sẽ có vài rắc rối xảy ra.
Tại sao lại chọn backbone.js?
backbone.js, đặc trưng của nó là một trình quản lý code rất sáng sủa.
Nó chỉ hỗ trợ duy nhất MVC framework, vì vậy đúng như tên gọi của nó ( xương sống ), nó có rất ít thứ không cần thiết, và so sánh với các thư viện khác thì nó dễ học hơn, những thành phần không liên quan hay không có sẵn có thể dễ dàng được cài đặt vào bằng cách sử dụng rất nhiều plug-in (Như marionette
mà bạn sẽ sử dụng ở chương 3).
Hiện tại mình sẽ chỉ tạo 1 app đơn giản tuy nhiên tôi tin rằng nó có thể xử lý tốt với những dự án lớn hơn và đáp ứng tốt mọi nền tảng .
Sau này các bạn có thể sử dụng một framwork nổi tiếng khác là AngularJS , hay một vài người cũng quan tâm đến React.js, việc học qua backbone giúp các bạn nắm được những kiến thức cơ bản nhất của client MVC framework, qua đó dễ dàng chuyển đổi sang các framework khác.
※ React rất có thể là cùng một dạng với backbone (tôi nghĩ vậy...)。
Các thành phần của backbone
Chúng ta sẽ nghiên cứu cụ thể về các thành phần của backbone.
Theo mô hình MVC, những thành phần của backbone được liệt kê tương ứng dưới đây.
|M/V/C| Thành phần của Backbone
------+-----------------------------------|
|Model|Backbone.Model, Backbone.Collection|
|View |Backbone.View, HTML template|
|Controller|Backbone.View, Backbone.Router|
※HTML template không phải là một thành phần của backbone nhưng tôi để vào dòng view cho dễ giải thích sau này.
Cho dễ hình dung thì nó sẽ như thế này
Bây giờ nhìn sẽ khá là khó hiểu nên chúng ta sẽ tìm hiểu từng cái một.
Model
Model bao gồm Backbone.Model và Backbone.Collection.
Nó sẽ làm những việc sau từ controller.
- Backbone.Model
- Gồm các phần tử mình cần xử lý
- Backbone.Collection
- là một bộ các Backbone.Model.
Ngoài ra còn có các sự kiện xảy ra với các model.
Cả 2 đều là trung gian trao đổi dữ liệu từ server và REST API.
View
View là sự liên kết giữa Backbone.View và HTML template, nó nhận các sự kiện từ model để xử lý các hành động, VD: nút Save
nhận sự kiện click, sau đó sẽ thực hiện hành động lưu.
- Backbone.View
- Dùng để hiển thị logic
- HTML templates
- Dùng để hiển thị như HTML trên trình duyệt
Nguyên tắc của View trên MVC là những thành phần chính sẽ được hiển thị bằng cách xử lý những sự thay đổi từ Model.
Backbone.View tôi nghĩ là nó dùng để hiển thị giao diện logic một phần dưới dạng HTML templates, cứ tạm hình dung thế đã.
Controller
Controller là sự liên hệ giữa Backbone.View và Backbone.Router?
Cái khó hiểu bắt đầu xuất hiện rồi đây.
Backbone.View đã xuất hiện trong View giờ lại liệt kê trong Controller?
Giải thích như sau:
- Backbone.View nhận các sự kiện từ DOM ( VD: những thao tác từ người dùng) và thực thi các hàm
- Backbone.Model nhận sự kiện từ Backbone.Collection và thực thi các hàm.
Nói cách khác, Dù tên nó được giới thiệu như là "View" nhưng Backbone.View là:
- Một thành phần của Controller mà điều khiển các danh sách xử lý và thu thập sự kiện
- Một thành phần của View có trách nhiệm cập nhật những nội dung hiển thị vào thời gian có sự thay đổi của Model.
Backbone.Router là:
- Dùng để thay đổi những URL chứa các hàm thực thi
- Backbone.Router thay đổi view bằng cách thu thập những sự kiện và thay đổi URL để sinh ra view mới.
- Backbone.View có thể tạm hiểu là thao tác thu thập sự kiện trên màn hình
Đối với View,
Backbone.View thậm chí vừa là Controller cũng vừa là một View
Những thứ cần chuẩn bị
Đăng nhập SSH
Mỗi lần bạn đăng nhập, nó sẽ tự động bắt đầu từ đăng nhập bằng SSH.
- ssh -i [đường dẫn đến private key] study @ [PublicIP]
Nhập lệnh sau để di chuyển sang thư mục gốc của ứng dụng.
- cd /var/www/study/rest-study
Bắt đầu thôi nào!
Lưu branch đã tạo của git
Ở bài trước mình đã tạo branch vol/01
, trước khi thực hiện những thao tác ở bài 2, mình commit và push lên vol/01
như hướng dẫn ở bài trước, sau đó gộp vào branch master.
####Gộp (merge) branch (vol/01
) đã tạo ở bài 1 với branch master.
-
vào branch
master
bằng câu lệnh
git checkout master
-
vol/01
gộp vớimaster
bằng lệnhgit merge vol/01
[study@ip-172-31-8-2 rest-study]$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
[study@ip-172-31-8-2 rest-study]$ git merge vol/01
Updating d234729..55ef625
Fast-forward
app/Config/routes.php | 7 +++++++
app/Controller/AppController.php | 5 ++++-
app/Controller/TodoListsController.php | 42 ++++++++++++++++++++++++++++++++++++++++++
app/Model/TodoList.php | 6 ++++++
4 files changed, 59 insertions(+), 1
Hiện tại nội dung cuối cùng đã được gán cho branch master.
#Cập nhật lên GitHub bằng lệnh push.
git push origin master
-> điền username, mật khẩu
[study@ip-172-31-8-2 rest-study]$ git push origin master
Username for 'https://github.com': ks-ocean
Password for 'https://ks-ocean@github.com':
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/ks-ocean/rest-study.git
d234729..55ef625 master -> master
Kiểm tra trên giao diện của GitHub
Tại https://github.com/[username]/rest-study/commits/master
、chúng ta có thể chắc chắn việc gộp với branch vol/01
đã thành công.
Tiếp tục đồng bộ với repository bằng cách fork
Repository gốc đã được fork Ở đây
-
Thêm một fork repository tên là
upstream
。git remote add upstream https://github.com/suzukishouten-study/rest-study.git
-
Xem nội dung đã fork của repository
upstream
vừa được tạo.git fetch upstream
-
Xác nhận
git branch -av
[study@ip-172-31-8-2 rest-study]$ git remote add upstream https://github.com/suzukishouten-study/rest-study.git
[study@ip-172-31-8-2 rest-study]$ git fetch upstream
[study@ip-172-31-8-2 rest-study]$ git branch -av
* master 55ef625 vol/01 complete
vol/01 55ef625 vol/01 complete
remotes/origin/HEAD -> origin/master
remotes/origin/master 55ef625 vol/01 complete
remotes/origin/vol/01 55ef625 vol/01 complete
remotes/origin/vol/01-finish 59d8146 add simple api
remotes/upstream/master d234729 first commmit
remotes/upstream/vol/01-finish 59d8146 add simple api
remotes/upstream/vol/02-finish ec54cf5 8 更新処理
Ở phần trên, một branch tên remotes/upstream/vol/02-finish
đã được thêm.
Đây là thứ đã được chuẩn bị để fork những repository sau khi hoàn tất các nội dung vừa rồi.
Nếu bạn checkout branch này, bạn sẽ có thể kiểm tra trạng thái của những hoạt động gần đây trong quá trình xây dựng ứng dụng này.
Ở trên GitHub.
- Danh sách commit của branch
vol / 02-finish
Tại đây - Source và hiển thị dạng Diff của những phần đã hoàn thành tại bài 2 gần đây [Tại đây](https://github.com/suzukishouten-study/rest-study/commit/
47ead20a422510a62722041940bad3d793533a52).
Tạo một branch cho công việc mới.
Như những gì đã làm ở bài trước, tạo branch vol/02
và check out.
-
git branch vol/02
để tạo branch -
git checkout vol/02
để vào sử dụng branch
#(git branch để xác nhận xem đã ở đúng branch đó chưa)
[study@ip-172-31-8-2 rest-study]$ git branch vol/02
[study@ip-172-31-8-2 rest-study]$ git branch
* master
vol/01
vol/02
[study@ip-172-31-8-2 rest-study]$ git checkout vol/02
Switched to branch 'vol/02'
[study@ip-172-31-8-2 rest-study]$ git branch
master
vol/01
* vol/02
[study@ip-172-31-8-2 rest-study]$
Chúng ta đã chuẩn bị xong.
Vào việc thôi!
Sử dụng thư viện đầu tiên
Bây giờ chúng ta cần lấy các thư viện Backbone, underscore, jquery từ trang chủ của chúng ( backbone chạy dựa trên underscore nên mình bắt buộc phải gọi cả nó nữa ).
Các bạn làm theo hướng dẫn sau.
Tạo thư mục
Trước tiên các bạn hãy tạo các thư mục theo đường dẫn như sau bằng lệnh:
-
Tạo thư mục
mkdir app/webroot/js/lib
-
Vào tư mục vừa tạo
cd app/webroot/js/lib
.
Tải về các thư viện
Các bạn có thể tải về bằng cách sử dụng lênh wget
trong terminal.
Hoặc tải theo các URL sau ( đều từ trang chủ )
- backbone
http://backbonejs.org/backbone-min.js
- underscore
http://underscorejs.org/underscore-min.js
- jquery
http://code.jquery.com/jquery-2.1.3.min.js
Còn nếu sử dụng lệnh:
-
wget http://backbonejs.org/backbone-min.js
-
wget http://underscorejs.org/underscore-min.js
-
wget http://code.jquery.com/jquery-2.1.3.min.js
[study@ip-172-31-8-2 lib]$ wget http://backbonejs.org/backbone-min.js
--2015-03-05 11:49:33-- http://backbonejs.org/backbone-min.js
backbonejs.org (backbonejs.org) をDNSに問いあわせています... 192.30.252.154, 192.30.252.153
backbonejs.org (backbonejs.org)|192.30.252.154|:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 302 Found
場所: /backbone-min.js [続く]
--2015-03-05 11:49:34-- http://backbonejs.org/backbone-min.js
backbonejs.org (backbonejs.org)|192.30.252.154|:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 19999 (20K) [application/javascript]
`backbone-min.js' に保存中
100%[================================================================>] 19,999 111KB/s 時間 0.2s
2015-03-05 11:49:34 (111 KB/s) - `backbone-min.js' へ保存完了 [19999/19999]
[study@ip-172-31-8-2 lib]$ wget http://underscorejs.org/underscore-min.js
--2015-03-05 11:49:34-- http://underscorejs.org/underscore-min.js
underscorejs.org (underscorejs.org) をDNSに問いあわせています... 192.30.252.154, 192.30.252.153
underscorejs.org (underscorejs.org)|192.30.252.154|:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 16523 (16K) [application/javascript]
`underscore-min.js' に保存中
100%[================================================================>] 16,523 89.8KB/s 時間 0.2s
2015-03-05 11:49:35 (89.8 KB/s) - `underscore-min.js' へ保存完了 [16523/16523]
[study@ip-172-31-8-2 lib]$ wget http://code.jquery.com/jquery-2.1.3.min.js
--2015-03-05 11:49:35-- http://code.jquery.com/jquery-2.1.3.min.js
code.jquery.com (code.jquery.com) をDNSに問いあわせています... 94.31.29.53, 94.31.29.230
code.jquery.com (code.jquery.com)|94.31.29.53|:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 84320 (82K) [application/x-javascript]
`jquery-2.1.3.min.js' に保存中
100%[================================================================>] 84,320 389KB/s 時間 0.2s
2015-03-05 11:49:35 (389 KB/s) - `jquery-2.1.3.min.js' へ保存完了 [84320/84320]
[study@ip-172-31-8-2 lib]$ ls -l
合計 124
-rw-r--r-- 1 study wheel 19999 2月 4 01:34 backbone-min.js
-rw-r--r-- 1 study wheel 84320 12月 19 00:17 jquery-2.1.3.min.js
-rw-r--r-- 1 study wheel 16523 2月 22 23:14 underscore-min.js
[study@ip-172-31-8-2 lib]$
Như vậy chúng ta đã chuẩn bị sẵn sàng các thư viện.
Sau này, từ Bài 1~8 chúng ta sẽ tiếp tục chình sửa source.
Theo từng bài, các hàm sẽ được thêm dần dần từng chút một và sẽ hoàn tất ở bài 8.
Mỗi bài gồm:
- Mục tiêu sau khi hoàn thành bài học ( Nghĩa là kết quả của bài học ấy, những chức năng sẽ hoàn tất )
- Danh sách các file bạn cần chỉnh sửa ( Sẽ có những file cần sửa, xóa đi hoặc thêm file mới )
- Nội dung chỉnh sửa của từng file và những điểm chú thích.
Thường chúng tôi sẽ chú thích ngay trong bài viết.
※ Ngoài ra trong đoạn code cũng sẽ có các đoạn comment chú thích để các bạn dễ đọc hơn, hãy cố gắng đọc nhé, cố lên !
Chúng ta bắt đầu bài 1 !
Bài 1: Routing
Chúng ta sẽ bước đầu quan sát về sự điều hướng của Backbone.Router.
Mục Tiêu hoạt động của ứng dụng sau khi hoàn thành bài học
Khi bạn truy cập http://PublicIP/rest-study/#todo-lists
Với sự điều hướng của Backbone.Router để chạy đến hàm hiện thông báo "Đây là trang To Do List" kết quả danh danh sách Todo list khi bạn truy cập đường dẫn #todo-lists
.
Còn khi truy cập http://[PublicIP]/rest-study/#todo-lists/1
Nó sẽ hiển thị tường tự như trên ( Đây là chi tiết công việc có id = 1 )
Do sự điều hướng của Backbone.Router, sẽ chạy đến hàm để hiện kết quả khi bạn truy cập #todo-lists/X ví dụ "Thong tin TODO của id = X", với X là ID của công việc mình cần xem.
Những file cần chỉnh sửa
Thao tác | file | Giải thích |
---|---|---|
Chỉnh sửa | app/View/Layouts/default.ctp | HTML template |
Thêm | app/webroot/js/routers/router.js | Router(Controller) |
Thêm | app/webroot/js/app.js | File js chính |
Chi tiết chỉnh sửa từng file và các chú thích
default.ctp
Ở CakePHP, file default.ctp sẽ mặc định hiển thị đầu tien.
Chúng ta sẽ sử dụng HTML template để viết lại nó.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Ứng dụng TODO đơn giản</title>
</head>
<body>
<!-- ① js(library) -->
<script src="js/lib/jquery-2.1.3.min.js" type="text/javascript"></script>
<script src="js/lib/underscore-min.js" type="text/javascript"></script>
<script src="js/lib/backbone-min.js" type="text/javascript"></script>
<!-- js(application) -->
<!-- ② router -->
<script src="js/routers/router.js" type="text/javascript"></script>
<!-- ③ entry point -->
<script src="js/app.js" type="text/javascript"></script>
</body>
</html>
Chú thích ①〜③ tương ứng、
- ① Ứng dụng cần load được cả 3 thư viện jquery, underscore, backbone, 3 dòng này để khai báo chúng.
- ② Gọi file router.js để sử dụng.
- ③ Gọi file app.js để sử dụng.
router.js
Đây là phần sẽ làm nhiệm vụ routing
var app = app || {};
//router
(function(app) {
app.TodoRouter = Backbone.Router.extend({
routes : {
'' : 'todoLists',
'todo-lists' : 'todoLists',
'todo-lists/:id' : 'todoDetail'
},
todoLists : function() {
alert('TODO一list');
},
todoDetail : function(id) {
alert('Thong tin TODO của id = ' + id);
},
});
})(app);
※Chúng ta sử dụng một namespace như sau
var app = app || {};
(function(app) {`
})(app);
Xem giải thích tại đây, cái này là cú pháp mặc định, sau này tất cả các project khác mình đều dùng cú pháp tương tự.
-
app.TodoRouter = Backbone.Router.extend({
Đây là sự khai báo để thừa kế Backbone.Router object của backbone, thao tác này giúp thừa kế những kết quả của object tớiapp.TodoRouter
.
Tiếp đó bạn cũng dễ dàng nhận ra collection, model, cũng được khai báo như vậy ở view, và được khai báo thừa kế từ các object mà backbone có. -
Biến
Routes {}
Tại đây chúng ta sẽ đặt 1 cặp URL và hàm cho nó.
URL dùng để thực thi hàmtodoLists
là#todo-lists
. -
Hàm
TodoLists
Đây là nội dung của hàmtodoLists
được liệt kê trong routes {}. Ở đây chúng ta sẽ chạy một hàm alert() để hiển thị tin nhắn như trên sau khi truy cập URL#todo-lists
. -
Hàm
todoDetail
Đây là hàm hiển thị nội dung chi tiết của công việc cần làm liệt kê ở routes{}.
app.js
Một khi file default.ctp được đọc hết, nghĩa là chương trình đã load hết những thư viện cần thiết và file router.js.
var app = app || {};
//Bat Dau
(function(app) {
var todoRouter = new app.TodoRouter(); // ①
Backbone.history.start(); // ②
})(app);
① Bạn tạo một biến tại app.TodoRouter.
② Tôi đã chạy hàm Backbone.history.start();
ý nghĩa hàm này sẽ được giải thích ở dưới.
#
Với URL ( chính xác hơn là "URL fragment") nó dùng để trỏ đến một đường dẫn trong trang , nhưng nếu nó thay đổi, hashchange
sẽ xảy ra. nếu bạn không giỏi JavaScript, bạn có thể chạy hàm này khi sự kiện hashchange
xuất hiện bằng cách viết một handler cho window.onhashchange
như sau.
window.onhashchange = function (e){
alert('hash change!');
};
Khi sử dụng router các bạn phải chạy đoạn code Backbone.history.start()
dùng để bắt đầu giám sát các sự kiện hashchange
trong backbone, ý nghĩa của nó là để chạy những gì bạn đã viết trước đó trong routes {}
của file router.js.
Tóm tắt nội dung
Các công đoạn của bài này như sau
-
Tạo
app/View/Layouts/default.ctp
như mô tả ở trên. -
Tạo
app/webroot/js/routers/router.js
như mô tả ở trên. -
Tạo
app/webroot/js/app.js
như mô tả ở trên. - Kiểm tra kết quả.
- commit lên Git
Đến bài 2 !
Bài 2: Mô hình hoạt động MVC cơ bản
Chúng ta đã tìm hiểu về cách điều hướng của route ở bài 1
Ở bài 2 này chúng ta sẽ tìm hiểu về cách hoạt động của Model, View, Controller.
Mục tiêu bài học
Với JS thì các bạn sẽ rất khó để kiểm tra kết quả, giá trị các biến... vì nó không hiện ra màn hình, vì vậy có một hàm console.log()
dùng để làm việc đó, chúng ta dùng để quan sát những giá trị cần thiết thông qua Console.
Truy cập http://[PublicIP]/rest-study/#todo-lists. chuột phải chọn Kiểm Tra Phần tử hoặc click vào Google, More Tool và chọn Google Developer Tool để hiển thị công cụ hỗ trợ các phát triển của Google Chrome như ảnh minh họa dưới đây
※ Trước tiên bạn cần phải disable hàm "source map".
Với việc download backbone và underscore, tương ứng với một hàm "source map" nhưng đã được tối giản, nó có thể được truyền vào để tự động set vào Chrome.
Vì nếu bạn không set thì nó sẽ hiện log của các lỗi vào console ( nhưng không phải là những lỗi nghiêm trọng ), bay giờ mình chưa cần dùng đến nó, các bạn nên vào setting ở biểu tượng bánh răng cưa góc trên bên phải và sửa nó thành OFF.
Nếu ô này đã được bỏ check ( trạng thái OFF ) rồi thì đã OK.
Khi kiểm tra kết quả.
Để xóa log các bạn click vào biểu tượng vòng tròn gạch chéo như ảnh minh họa dưới đây và tải lại trang.
Chuyển sang tab console, kết quả chạy hàm console.log()
sẽ xuất hiện.
Tôi sẽ giải thích nội dung của log như sau:
No. | File nguồn | Log |
---|---|---|
1 | router.js:13 | Dùng route để hiện thị TODO list ra View |
2 | todo-collection-view.js:8 | Khởi tạo view cho TODO List |
3 | todo-collection-view.js:14 | Xử lý việc hiển thị view của TODO list |
4 | todo-collection-view.js:16 | Lấy collection |
5 | jquery-2.1.3.min.js | XHR hoàn thành việc load: GET "http://[PublicIP]/rest-study/todo_lists.json". |
6 | todo-collection.js:11 | Phân tích Collection |
7 | todo-model.js:9 | Phân tích Model |
8 | todo-model.js:10 | ▶ Object {TodoList: Object} |
9 | todo-model.js:9 | Phân tích Model |
10 | todo-model.js:10 | ▶ Object {TodoList: Object} |
11 | todo-model.js:9 | Phân tích Model |
12 | todo-model.js:10 | ▶ Object {TodoList: Object} |
Chú thích
※ Ở ví dụ này tôi sẽ tạo 3 công việc trong database.
- Khi bạn truy cập vào rest-study/#todo-lists, route sẽ xử lý và bắt đầu tạo view cho Todo List
- Quá trình khởi tạo Todo List cho View được thực thi, view đã được tạo
- Quá trình hiển thị Todo List lên View bắt đầu
- Thực hiện các thao tác nạp của collections, bắt đầu truy cập server side API
- Giao thức ajax trong jquery được thực thi, Truy cập
http://PublicIP/rest-study/todo_lists.json
với phương thức GET để hoàn tất việc lấy toàn bộ danh sách to do list trong data. - Ở collection, Phân tích những data đã được tổng hợp trong bước 5.
- Phân tích data đầu tiên (model) ở collection (Parse) được thực thi
- Sử dụng những phân tích trong bước 7, (Khi bạn click vào ▶ Đổ dữ liệu.) Thu được các chi tiết dữ liệu
- Phân tích data thứ 2 trong collection.
- Đổ dữ liệu đã thu được ra màn hình.
- Phân tích data thứ 3 trong collection.
- Đổ dữ liệu thu được như bài 8
Để chi tiết hơn, chúng ta sẽ xem qua đặc tả chức năng của mỗi file.
Những file bạn cần chỉnh sửa ###
Thao tác sửa | file | Diễn tả |
---|---|---|
Chỉnh sửa | app/View/Layouts/default.ctp | HTML Template |
Chỉnh sửa | app/webroot/js/routers/router.js | Route (Controller) |
Thêm mới | app/webroot/js/views/todo-collection-view.js | View(Danh sách Todo List) |
Thêm mới | app/webroot/js/collections/todo-collection.js | Collection ( Tập hợp các TODO ) |
Thêm mới | app/webroot/js/models/todo-model.js | Model( Mỗi TODO ) |
Chú thích và chi tiết chỉnh sửa nội dung mỗi file
Dưới đây sẽ show dưới dạng Diff, nó sẽ thông qua các dấu + và - để biểu thị sự thay đổi của file mà chúng ta sẽ xử lý.
- 「+」Đây là những dòng mới cần được thêm vào file ( màu xanh )
- 「-」Đây là dòng đã có trong file và cần bị xóa đi ( màu hồng )
- Những dòng không có dấu cộng hay trừ thì sẽ được giữ nguyên và giữ nguyên thứ tự như trong file gốc, sau khi thêm các nội dung vào thì các bạn lưu ý xóa hết dấu
+
đi thì chương trình mới chạy được nhé.
default.ctp
<script src="js/lib/backbone-min.js" type="text/javascript"></script>
<!-- js(application) -->
+ <!-- model -->
+ <script src="js/models/todo-model.js" type="text/javascript"></script>
+ <!-- collection -->
+ <script src="js/collections/todo-collection.js" type="text/javascript"></script>
+ <!-- view -->
+ <script src="js/views/todo-collection-view.js" type="text/javascript"></script>
<!-- router -->
<script src="js/routers/router.js" type="text/javascript"></script>
<!-- entry point -->
Thêm vào model, collection, view những file sau、
- todo-model.js
- todo-collection.js
- todo-collection-view.js
Chi tiết nội dung chỉnh sửa mỗi file như sau
router.js
'todo-lists/:id' : 'todoDetail'
},
todoLists : function() {
- alert('TODO List');
+ // route cho Todo list của view
+ console.log("Route TODO List");
+ new app.TodoCollectionView();
},
todoDetail : function(id) {
- Xóa dòng alert vì chúng ta không muốn hiển thị alert như ở bài 1 ( alert chỉ là demo rằng hàm đã được chạy ).
- Hiển thị nội dung đã được hiển thị trong log thứ nhất tại
console.log ()
- Khởi tạo app.TodoCollectionView
Thông qua Route, hàm todoLists
đã được thực thi như bài 1, mình sẽ khởi tạo một log output và Todo list cho view. Bây giờ, mình sẽ khởi tạo view cho Todo-list trên file todo-collection-view.js.
todo-collection-view.js
Đây là view tổng hợp danh sách toàn bộ TODO List
var app = app || {};
//Todo list cho view
(function(app) {
app.TodoCollectionView = Backbone.View.extend({
todoCollection : {},
initialize : function() {
console.log("Todo list");
//xem log
this.todoCollection = new app.TodoCollection()
this.render();
},
render : function() {
console.log("Show Todo list");
//Đổ dữ liệu ra collection
console.log("fetch to Collection");
this.todoCollection.fetch();
return this;
},
})
})(app);
- Ở
app.TodoCollectionView()
mình đã thiết lập ở router, mình chạy hàminitialize
.- Xem log lần 2 để xem đã chạy đến dòng đó chưa
- Khai báo
app.TodoCollection
, nó được lưu trữ ở biến nội bộtodoCollection
- Chạy hàm
render()
- Tiến trình trong hàm Render()
- Xem log thứ 3
- TodoCollection để chạy hàm
fetch()
(gọi đến server API của nó)
todo-collection.js
Để xử lý danh sách TODO, chúng ta cần tạo collection ( collection là tập hợp các model - ở đây mỗi công việc TODO là một model).
var app = app || {};
//Collection của Todo list
(function(app) {
app.TodoCollection = Backbone.Collection.extend({
url : '/rest-study/todo_lists.json',
model : app.TodoModel,
parse : function(response) {
//Phan tich collection
console.log("Lay Collection");
return response;
}
});
})(app);
-
Biến
url
Bạn sẽ xác định URL cụ thể của server-side API để truy cập và chạy hàmfetch
để đổ dữ liệu ra. -
Biến
model
Nó sẽ xác định object cụ thể cho model, bao gồm cả collection.
Tại đây, model được include trong collection này sẽ được nhìn thấy tất cả tạiapp.TodoModel
. -
Hàm
parse
Nó sẽ chạy hàmfetch
(Có sẵn trong Backbone.Collection mà mình đã thừa kế), Thực thi hàmparse
sẽ giúp chúng ta lấy data mỗi công việc TODO từ server và tổng hợp chúng ra danh sách bằng jquery.
todo-model.js
Xử lý từng công việc TODO một, nó là một model.
var app = app || {};
//model để đưa giá trị mỗi việc làm vào database
(function(app) {
app.TodoModel = Backbone.Model.extend({
urlRoot : '/rest-study/todo_lists',
parse : function(response) {
//phân tích model
console.log("Parse model");
console.log(response);
return response.TodoList;
}
});
})(app);
-
Biến
urlRoot
Các bạn cần điền đúng URL của API để chạy hàmfetch
, biến này sẽ lưu URL đó -
Hàm
parse
đã giải thích ở trên -
Hàm
parse
Chạy jquery để tổng hợp mỗi các công việc TODO lại và thêm nó vào dãy công việc.
※ Lưu ý quan trọng
Ở hàm parse, có một đoạn return response.TodoList
, nó có ý nghĩa là sau khi thu thập được danh sách toàn bộ TODO list, dữ liêu trên server sẽ được trả về dưới dạng json.
[{
"TodoList": {
"id": "1",
"todo": "TODO 1",
"status": "0"
}
},
{
"TodoList": {
"id": "2",
"todo": "TODO 2",
"status": "0"
}
},
{
"TodoList": {
"id": "3",
"todo": "TODO 3",
"status": "0"
}
}]
Mỗi nội dung review như sau.
"TodoList": {
"id": "1",
"todo": "TODO 1",
"status": "0"
}
Tất cả dữ liệu đã trở thành object của key tên là TodoList
, và đây là nội dung của nó
{
"id": "1",
"todo": "TODO 1",
"status": "0"
}
Chỉ khi return chúng ta mới có thể tổng hợp data một cách thuận tiện nhất.
Nếu không có nó, khi bạn truy cập vào id TodoList.id
bạn sẽ gặp những khó khăn để làm TodoList.status
và ở những thời điểm, khi bạn cần truy cập status .
Khai báo
Bắt đầu khai báo!
-: White_check_mark: app/View/Layouts/default.ctp
sửa như hướng dẫn trên.
-: White_check_mark: app/webroot/js/routers/router.js
sửa như hướng dẫn trên.
-: White_check_mark: app/webroot/js/views/todo-collection-view.js
sửa như hướng dẫn trên.
-: White_check_mark: app/webroot/js/collections/todo-collection.js
sửa như hướng dẫn trên.
-: White_check_mark: app/webroot/js/models/todo-model.js
sửa như hướng dẫn trên.
-: White_check_mark: Kiểm tra kết quả!
-: White_check_mark: Commit lên Git
Chúng ta đến với bài thứ 3.
Bài 3: Hiển thị danh sách
Ở bài 2, bạn đã được tìm hiểu sơ qua về mô hình hoạt động của MVC, và được xác nhận dât đã được xử lý.
Ở bài 3, sẽ hiển thị một danh sách các dữ liệu xử lý.
Mục tiêu hoạt động của ứng dụng sau khi hoàn thành bài học
http://[PublicIP]/rest-story/#todo-lists
Khi bạn truy cập, bạn sẽ thấy một danh sách TODO để sử dụng các con số dưới đây.
Chỉ có nó được hiển thị.
Danh sách tập tin cần chỉnh sửa
Thao tác | file | Diễn tả |
---|---|---|
Chỉnh sửa | app/View/Layouts/default.ctp | HTML template |
Chỉnh sửa | app/webroot/js/views/todo-collection-view.js | xem Todo list) |
Thêm | app/webroot/js/views/todo-item-view.js | View(Hiển thị danh sách Todo) |
Chi tiết chỉnh sửa mỗi tập tin và chú thích
default.ctp
<title>Ứng dụng TODO List đơn giản</title>
</head>
<body>
+ <!-- Nội dung -->
+ <div id="content">
+ </div>
+ <!-- Template của TODO List -->
+ <script type="text/template" id="list-template">
+ <h1>TODO List</h1>
+ <hr>
+ <div>
+ <table border="1" width="350px">
+ <tbody id="todo-lists"></tbody>
+ </table>
+ </div>
+ </script>
+
+ <!-- Moi cong viec se duoc tao vao tempalte nay va chen vao the tbody -->
+ <script type="text/template" id="item-template">
+ <td style="margin:0px">
+ <span class="todo-edit" style="margin:0px"><%- todo %></span>
+ </td>
+ </script>
<!-- js(library) -->
<script src="js/lib/jquery-2.1.3.min.js" type="text/javascript"></script>
@@ -17,6 +37,7 @@
<!-- collection -->
<script src="js/collections/todo-collection.js" type="text/javascript"></script>
<!-- view -->
+ <script src="js/views/todo-item-view.js" type="text/javascript"></script>
<script src="js/views/todo-collection-view.js" type="text/javascript"></script>
<!-- router -->
<script src="js/routers/router.js" type="text/javascript"></script>
- Mỗi TODO
-
<script type="text/template" id="list-template">
Đây là phần bắt đầu template tất cả các công việc TODO
-
- Template cho mỗi công việc TODO
-
<script type="text/template" id="item-template">
Template cho mỗi phần tử, tôi sẽ thêm view cho nó vào `todo-item-view.js.
-
todo-collection-view.js
//Tạo view cho TODO list
(function(app) {
app.TodoCollectionView = Backbone.View.extend({
+ el : '#content',
+ tagName : 'div',
todoCollection : {},
initialize : function() {
- console.log("Khoi tao view cho Todo list");
- //Tạo collection
this.todoCollection = new app.TodoCollection()
+ this.todoCollection.on('add', this.addOne, this);
+ this.$el.html($('#list-template').html());
this.render();
},
render : function() {
- console.log("Thao tac cua viec khoi tao view cho todo list");
- //Đổ dữ liệu vào collection
- console.log("collection fetch");
this.todoCollection.fetch();
return this;
},
+
+ addOne : function(todo) {
+ var itemView = new app.TodoItemView({
+ model : todo
+ });
+ $('#todo-lists').append(itemView.render().el);
+ },
})
})(app);
Xóa đoạn console.log bạn đã thêm vào ở phần 2 và thêm các dòng như trên.
Chú thích:
-
Biến
el
Đây là biến để set thành phần của DOM View.
Phần'#content'
trỏ đến<div id="content">
nằm trong default.ctp . -
Biến
tagName
thẻ HTML tag của biếnel
ở trên, như mình đang viết là thẻdiv
-
Hàm
initialize
-
this.todoCollection.on('add', this.addOne, this);
lắng nghe khi có 1 model được add vào collection thì gọi hàm callback
addOne
để render lại view -
this.$el.html($('#list-template').html());
Mình sẽ vẽ template cho template được trỏ đến từ biến
el
trong file default.ctp của'#list-template'
.
-
-
Hàm
addOne
app.TodoItemView
(View cho mỗi công việc)được khởi tạo, nó sẽ xuất ra danh sách cho trang'#todo-lists'
trong thẻ<tbody>
của template, chúng ta đã vẽ thêm 1 view cho mỗi công việc.
todo-item-view.js
Thêm view cho việc hiển thị mỗi phần tử trong Todo list
var app = app || {};
//Hiển thị từng view cho Todo list
(function(app) {
app.TodoItemView = Backbone.View.extend({
//Tag name của phần tử được thêm vào DOM
tagName : 'tr',
//template
template : _.template($('#item-template').html()),
initialize : function() {
},
render : function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
});
})(app);
-
Biến
tagName
- List được bố trí trong thẻ
<Table>
, mỗi dòng được bố trí bằng thẻ<tr>
nên tagName sẽ làtr
.
- List được bố trí trong thẻ
-
Hàm
render
-
this.$el.html($('#list-template').html());
Đoạn này có ý nghĩa là sẽ sinh ra mã HTML cho phần của biến
el
đã trỏ đến trên kia để vẽ template trong default.ctp khi vào'#list-template'
.
-
-
Hàm
addOne
HàmaddOne
khởi tạo từapp.TodoItemView
(view hiển thị mỗi TODO và hiển thị chúng trong thẻ<tbody>
tại URL'todo-lists'
, nó sẽ vẽ view cho mỗi thành phần và hiển thị trong danh sách.
Tóm tắt nội dung
-
app/View/Layouts/default.ctp
sửa như hướng dẫn trên. -
app/webroot/js/views/todo-collection-view.js
sửa như hướng dẫn trên. -
app/webroot/js/views/todo-item-view.js
sửa như hướng dẫn trên. - Kiểm tra kết quả!
- Commit lên git
Chuyển sang bài 4 !
Bài 4 : Chức năng thêm công việc mới
Chúng ta tiếp tục với việc thêm chức năng thêm TODO cho ứng dụng.
Mục tiêu hoạt động của ứng dụng sau khi hoàn thành bài học
http://[PublicIP]/rest-study/#todo-lists
Khi bạn truy cập vào link http://[PublicIP]/rest-study/#todo-lists , sẽ hiển thị kết quả như như hình bên dưới, với nút "Add TODO" và một khung textarea được thêm vào.
Nhập Công việc thứ 4
và nhấn nút Them
( cái này mình việt hóa cho dễ nhìn )
Những file cần chỉnh sửa
Thao tác | file | ý nghĩa |
---|---|---|
Chỉnh sửa | app/View/Layouts/default.ctp | HTML template |
Chỉnh sửa | app/webroot/js/views/todo-collection-view.js | View (Danh sách Todo list) |
Chi tiết chỉnh sửa các file và chú thích
default.ctp
<!-- template của TODO list -->
<script type="text/template" id="list-template">
<h1>TODO list</h1>
+ <textarea style="width:300px;height:50px"id="new-todo" placeholder="Todo?" autofocus></textarea>
+ <input type="button" id="addTodo" value="Add TODO">
<hr>
<div>
<table border="1" width="350px">
@@ -45,4 +47,4 @@
<script src="js/app.js" type="text/javascript"></script>
</body>
Các bạn thêm như sau
- Text area cho việc nhập công việc TODO
- Button "Them"
todo-collection-view.js
tagName : 'div',
todoCollection : {},
initialize : function() {
this.todoCollection.on('add', this.addOne, this);
this.$el.html($('#list-template').html());
+ this.newTodo = this.$('#new-todo');
this.render();
},
+ events : {
+ 'click #addTodo' : 'onCreateTodo',
+ },
render : function() {
this.todoCollection.fetch();
return this;
},
+ onCreateTodo : function(e) {
+ this.todoCollection.create(this.newAttributes(), {
+ wait : true
+ });
+ this.newTodo.val('');
+ this.todoCollection.fetch();
+ },
addOne : function(todo) {
var itemView = new app.TodoItemView({
model : todo
});
$('#todo-lists').append(itemView.render().el);
},
+ newAttributes : function() {
+ return {
+ todo : this.newTodo.val().trim(),
+ status : 0
+ }
+ }
})
})(app);
- Hàm
initialize
-
this.newTodo = this.$('#new-todo');
$ ('#new-todo') là một text area. Nó sẽ chứa nội dung vào một biến và lưu lại để sử dụng những lần sau.
-
Biến
events
-
'click #addTodo' : 'onCreateTodo'
events{}
của View là việc kết nối những ựu kiện ở DOM với cách xử lý chúng
Đây là 1 phần của view của backbone nhưng cũng phục vụ cho chức năng của Controller nữa.
Tại đây, tôi giao một chức năng cho hàmonCreateTodo
để xử lý sự kiện click vào nút "Add TODO".
-
-
Hàm
onCreateTodo
events{} là việc bắt sự kiện và xử lý dành cho nút "Add TODO"- Chạy hàm
create
của collection, Nó sẽ xem lại data một lần nữa, bây giờ nếu bạn gọi đến API thêm nội dung ( Trường hợp lỗi xảy ra, hoặc giữ liệu không giống, mình vẫn chờ kết quả cuối cùng). Nội dung được thêm thành công, hàmnewAttribute
được tạo. - Khi thêm việc thành công , collection được lấy ra, chúng ta lại có danh sách những TODO mới nhất.
- Chạy hàm
-
Hàm
newAttributes
- todo : Giá trị nhập vào từ input
- status : 0(Chưa hoàn thành)
Chúng ta sẽ trở lại một model mới được tạo ra.
-
Tóm tắt nội dung
Làm theo các bước sau đây
-
Sửa file
app/View/Layouts/default.ctp
như hướng dẫn trên. -
Sửa file
app/webroot/js/views/todo-collection-view.js
như hướng dẫn trên. - Kiểm tra kết quả
- Commit lên Git
Đến với bài 5 nào !
Bài 5: Thay đổi trạng thái checkbox.
Mình sẽ thêm vào một checkbox, mỗi khi bạn làm xong một công việc nào trong list, bạn có thể check vào checkbox đó để đánh dấu đã hoàn tất.
#Mục tiêu sau khi học xong bài này
Khi bạn truy cập vào http://[PublicIP]/rest-study/#todo-lists sẽ hiện giao diện như hình dưới, một checkbox nằm ở bên trái TODO hiện ra. Nó sẽ giao tiếp ngay với server và lưu trạng thái sau khi bạn check, vì vậy bạn có thể thấy nó sẽ vẫn đc check nếu bạn tải lại trang.
Danh sách những file cần chỉnh sửa,
Thao tác | file | Ý nghĩa |
---|---|---|
Chỉnh sửa | app/View/Layouts/default.ctp | HTML template |
Chỉnh sửa | app/webroot/js/models/todo-model.js | モデル(Todo list) |
Chỉnh sửa | app/webroot/js/views/todo-item-view.js | ビュー(Hiển tị chi tiết 1 phần tử của Todo lít) |
Chi tiết chỉnh sửa của mỗi file
default.ctp
<! - (chèn vào phần tbody nội dung dưới đây) đây là template của mỗi dòng TODO ->
<script type="text/template" id="item-template">
+ <td><input type="checkbox" class="toggle" <%- status === '1' ? 'checked' : '' %>></td>
<td style="margin:0px">
<span class="todo-edit" style="margin:0px"><%- todo %></span>
</td>
Nó sẽ chèn một đoạn HTML fragment để hiện thêm một cột checkbox
<% - Status === '1' 'checked':? ''% >>
Đoạn này có nghĩa là nếu ô này được check thì sẽ chuyển status thành 1, còn nếu không thì không thay đổi ( mặc định bằng 0, các bạn có thể kiểm tra trên phpmyadmin )
todo-model.js
console.log("phân tích model");
console.log(response);
return response.TodoList;
+ },
+ toggle : function() {
+ this.set('status', this.get("status") === '1' ? '0' : '1');
+ this.save();
}
});
})(app);
Bạn cần thêm hàm toggle
, mỗi model sẽ sở hữu một trạng thái
- từ 0 thành 1
- từ 1 thành 0
Chúng ta đang viết một chương trình để convert chúng, bạn cần lưu chúng vào database bằng cách gọi server-side API chứa hàm save()
. Những xử lý của view sẽ được gọi trong các hàm xử lý tương ứng ( sau khi các bạn click vào checkbox.
todo-item-view.js
//template
template : _.template($('#item-template').html()),
+ //set việc xử lý sự kiện cho DOM
+ events : {
+ // Khi click vào checkbox
+ 'click .toggle' : 'onStatusToggleClick',
+ },
+
initialize : function() {
},
render : function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
+ onStatusToggleClick : function(e) {
+ this.model.toggle();
+ },
});
})(app);
Biến event
Chúng ta đã viết hàm xử lý onStatusToggleClick
tại chỗ click check box at check box click.
Hàm onStatusToggleClick
Nó được gọi ở thời điểm checkbox được click.
Chạy hàm toggle
của Model, Nếu đổi sang trạng thái khác bạn không thể save.
Tóm tắt nội dung
Bắt đầu nào!
-
Sửa file
app/View/Layouts/default.ctp
theo hướng dẫn trên. -
Sửa file
app/webroot/js/models/todo-model.js
theo hướng dẫn trên. -
Sửa file
app/webroot/js/views/todo-item-view.js
theo hướng dẫn trên. - Kiểm tra lại kết quả
- Commit lên Git
Chuyển sang bài 6.
Bài 6: Viết chức năng xóa công việc
Thêm chức năng xóa những công việc không mong muốn trong TODO list.
Mục tiêu đạt được sau bài học
Sau khi truy cập http://[PublicIP]/rest-study/#todo-lists sẽ hiển thị như ảnh dưới đây, với link xóa những công việc không mong muốn ở bên cạnh tương ứng.
Khi bạn click xóa, TODO 2 sẽ biến mất sau khi reload lại danh sách.
Những file bị thay đổi
Hành động | file | Ý nghĩa |
---|---|---|
Chỉnh sửa | app/View/Layouts/default.ctp | HTML template |
Chỉnh sửa | app/webroot/js/views/todo-item-view.js | View ( Hiển thị từng công việc trong TODO list) |
Nội dung chỉnh sửa mỗi file và chú thích
default.ctp
<td style="margin:0px">
<span class="todo-edit" style="margin:0px"><%- todo %></span>
</td>
+ <td>
+ <a class="remove-link" href="#">Delete</a>
+ </td>
</script>
<!-- js(library) -->
Nó có công dụng thêm các đoạn HTML để hiển thị link cho việc xóa.
todo-item-view.js
events : {
//Khi click vao Checkbox
'click .toggle' : 'onStatusToggleClick',
+ // Khi click nut Xoa
+ 'click .remove-link' : 'onRemoveClick',
},
initialize : function() {
+ this.listenTo(this.model, 'destroy', this.remove);
},
render : function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
onStatusToggleClick : function(e) {
this.model.toggle();
},
+ onRemoveClick : function(e) {
+ this.model.destroy({
+ wait : true
+ });
+ },
});
})(app);
Biến events
Hàm xử lý sự kiện click vào link Xóa là onRemoveClick
.
Hàm initialize
Xóa công việc khi click vào link, tuy nhiên sự kiện destroy
chỉ xảy ra nếu nó thực sự đã bị xóa.
Chúng ta sẽ set một hàm bắt và xử lý sự kiện này là this.remove
.
Hàm remove
là một view cho chính nó, và sẽ xóa tất cả các phần tử DOM được thấy trong biến el, những sự kết hợp với nó và sẽ hủy toàn bộ các sự kiện.
Một việc TODO trên màn hình sẽ bị xóa.
Hàm onRemoveClick
việc thực hiện các chức năng đã được thiết lập trong các sự kiện.
Chúng ta sẽ chạy hàm destroy để xóa phần tử ( model ) này.
Sau khi đã thực sự xóa xong, sự kiện destroy
sẽ xảy ra, view được set ở hàm initialize
sẽ bị xóa.
Tóm tắt nội dung
Các bước dưới đây
-
Chỉnh sửa file
app/View/Layouts/default.ctp
như hướng dẫn trên. -
Chỉnh sửa file
app/webroot/js/views/todo-item-view.js
như hướng dẫn trên. - Kiểm tra kết quả
- Commit lên Git
Sang bài 7 thôi.
Bài 7: Hiển thị trang chi tiết công việc.
Thực hiện chức năng chỉnh sửa nội dung của mỗi TODO trong màn hình detail .
Mục tiêu sau khi kết thúc bài học
Khi truy cập vào http://[PublicIP]/rest-study/#todo-lists sẽ hiện ra những thông tin như ảnh dưới, kèm theo một link xem chi tiết ở bên phải link xóa công việc.
Khi click vào nút chi tiết của TODO3, sẽ được chuyển đến trang thông tin chi tiết với những nội dung như ảnh dưới đây
Khi click Cancel, không thực hiện hành động gì cả mà sẽ quay lại danh sách Todo list
Những file bị thay đổi
Hành động | file | Ý nghĩa |
---|---|---|
Chỉnh sửa | app/View/Layouts/default.ctp | HTMLテンプレート |
Chỉnh sửa | app/webroot/js/routers/router.js | ルータ(Controller) |
Thêm mới | app/webroot/js/views/todo-detail-view.js | View(View chi tiết của mỗi TODO) |
Nội dung chỉnh sửa mỗi file và chú thích
default.ctp
</head>
<body>
<!-- コンテンツ -->
- <div id="content">
- </div>
+ <div id="main"></div>
<!-- Template của TODO list -->
<script type="text/template" id="list-template">
<h1>TODO List</h1>
@@ -29,8 +28,17 @@
</td>
<td>
<a class="remove-link" href="#">Delete</a>
+ <a class="detail-link" href="#todo-lists/<%- id %>">Detail</a>
</td>
</script>
+ <!-- Trang chi tiết -->
+ <script type="text/template" id="detail-template">
+ <h2>Todo #<%- id %></h2>
+ <div>
+ <textarea style="width:300px;height:50px" id="edit-todo" autofocus placeholder="Todo?"><%- todo %></textarea>
+ <input type="button" id="updateCancel" value="Cancel"></input>
+ </div>
+ </script>
<!-- js(library) -->
<script src="js/lib/jquery-2.1.3.min.js" type="text/javascript"></script>
@@ -44,6 +52,7 @@
<script src="js/collections/todo-collection.js" type="text/javascript"></script>
<!-- view -->
<script src="js/views/todo-item-view.js" type="text/javascript"></script>
+ <script src="js/views/todo-detail-view.js" type="text/javascript"></script>
<script src="js/views/todo-collection-view.js" type="text/javascript"></script>
<!-- router -->
<script src="js/routers/router.js" type="text/javascript"></script>
-
Thay đổi
<div id="content">
thành<div id="main">
Chúng ta sẽ nhắc đến nội dung của router.js sau đâu, đầu tiên ta thêm phần
<div id ="content">
vào ngay dưới<div id =" main ">
. -
Thêm link vào màn hình chi tiết
Link của chúng ta làhref ="#todo-lists/<%- id%>"
.
Bây giờ, để khớp với thiết đặt routing trong bài 1 thì link buộc phải là#todo-lists/:id
. -
Thêm template cho trang chi tiết ( trang detail ấy )
Chúng ta cần hiển thị title (id), và thêm một text area và nút Cancel
.
- Xem màn hình hiển thị thêm công việc
Đây là 1 view cho màn hình deltai, chúng ta cần thêm cho todo-detail-view.js
.
router.js
+
+ currentView : false,
+
todoLists : function() {
//Routing cho Todolist
- console.log(" Routing Todo list");
- new app.TodoCollectionView();
+ this.removeCurrentView();
+ this.nextView(app.TodoCollectionView);
},
todoDetail : function(id) {
- alert('View thong tin chi tiet cua id = ' + id);
+ this.removeCurrentView();
+ this.nextView(app.TodoDetailView, id);
},
+
+ nextView : function(View, option) {
+ if (document.getElementById('#content') === null) {
+ $('#main').append('<div id="content"/>');
+ }
+ this.currentView = new View(option);
+ },
+ removeCurrentView : function() {
+ if (this.currentView) {
+ this.currentView.remove();
+ }
+ }
+
});
})(app);
Cho đến bài 6 thì cúng ta vẫn chỉ có một giao diện, chúng ta cần thêm một giao diện cho trang detail nữa.
Sau đó, để thay đổi tiến trình thêm View , chúng ta cần thêm những tiến trình để bỏ View.
Không may là chúng ta đã xóa các console.log
và alert
.
-
Biến
currentView
Đây là một biến để lưu trữ trạng thái của view vừa được hiển thị. -
Hàm
todoLists
Trước tiên chúng ta chỉnh sửa đơn giản
new app.TodoCollectionView ();
để hiển thị TODO list ra View, -
Xóa view đang được hiển thị (
currentView
là một biến trỏ đến view này) ( hàmremoveCurrentView
) -
Để sinh ra TODO List View ( Hàm
nextView
)
Nó sẽ trở thành 2 tiến trình.
-
Hàm
todoDetail
Giống như hàm
todoLists
, chúng ta sẽ chạy hàmremoveCurrentView
và hàmnextView
.
HàmnextView
gồm
-app.TodoDetailView
id
Chúng ta đã đi qua cả 2.
-
Hàm
nextView
Tại đây, chúng ta đã đụng một ít đến nội dung của file default.ctp, chúng ta cần thêm vài hành động cho nội dung.
Nếu không có tí nội dung nào, chúng ta sẽ chèn vào cuối của các phần tử chính.
Kể từ khi xóa xong view của tất cả các thành phần, chúng ta sẽ không tạo thêm view nào nữa.
Trong màn hình chi tiết, tham số view = app.TodoDetailView
, tham số tùy chọn =id
,
app.TodoDetailView tại thời điểm initialize
sẽ thông qua id
.
-
Hàm
removeCurrentView
Bạn cần xóa view được trỏ đến bởi
currentView
, nó sẽ xóa từng nội dung của view.
todo-detail-view.js
var app = app || {};
//Giao diện trang Detail
(function(app) {
app.TodoDetailView = Backbone.View.extend({
el : '#content',
//template
template : _.template($('#detail-template').html()),
//Xử lý sự kiện tại DOM
events : {
//Khi click nút Cancel
'click #updateCancel' : 'onCancelClick',
},
//Khởi tạo
initialize : function(id) {
//Model sinh ra sau khi nhận id tử router
this.model = new app.TodoModel({
id : id
});
//Ngay khi hoàn tất nhận data từ server, nó bắt đầu vẽ
this.listenTo(this.model, 'sync', this.render);
//Model sẽ bị hủy (destroy) khi sự kiện xuất hiện, xóa view của nó
this.listenTo(this.model, 'destroy', this.remove);
//Đổ lại dữ liệu từ DB vào collection
this.model.fetch({
wait : true
});
},
//vẽ giao diện
render : function() {
//Sử dụng template để vẽ bố cục model
this.$el.html(this.template(this.model.toJSON()));
return this;
},
//xử lý sự kiện click nút Cancel
onCancelClick : function() {
Backbone.history.navigate('#todo-lists', true);
},
});
})(app);
Bây giờ mình tạo view cho trang detail.
Chúng ta bỏ qua template
, events
vì nó sẽ lấy những cái mình có từ trước.
- Hàm
initialize
Khi những View này được tạo cho các id
trỏ đến từ router, mình chỉ tạo model cho id
đó mà thôi, vào server API mà bạn đã chạy hàm fetch
ở model mình sẽ lấy được nội dung.
Sự kiện sync
( những thông tin cuối được lấy từ server ) qua hàm render
,
Sự kiện Destroy
, bạn phải chạy hàm remove
cho view của nó đã.
-
Hàm
render
Như bạn đã thiết lập ở hàm
initialize
, nó được gọi là một sự kiện triggersync
và sự kiện này sẽ được hủy bỏ sau khi chạy xong hàmfetch
.
Chúng ta đang vẽ template. -
Hàm
onCancelClick
Chúng ta sẽ viết hàm cho sự kiện ckick vào nút Cancel.
Với hàmBackbone.history.navigate
, chúng ta sẽ chuyển về URL'#todo-lists'
.
Đoạn argument sẽ chuyển thành giá trịtrue
để chuyển đến URL (= như router đang làm việc) không ngay lập tức.
Sau đó router sẽ phản hồi và trở lại màn hình danh sách TODO list.
Tóm tắt nội dung
Các bước dưới đây
-
app/View/Layouts/default.ctp
sửa như trên。 -
app/webroot/js/routers/router.js
sửa như trên。 -
app/webroot/js/views/todo-detail-view.js
sửa như trên。 - Kiểm tra kết quả
- Commit lên Git
Chuyển qua bài cuối cùng : Bài 8
Lesson 8 : Chỉnh sửa công việc ở trang detail (chi tiết)
Thêm nút "Chỉnh sửa" vào trang detail, qua đó mình có thể chỉnh sửa công việc.
Mục tiêu sau khi kết thúc bài học
Khi truy cập vào trang detail của công việc TODO3 thì sẽ hiện ra giao diện như thế này
Trang thông tin chi tiết của công việc sẽ hiển thị có kèm nút Cập nhật thay đổi
.
Nội dung công việc sẽ được thay đổi và hiển thị nội dung mới sau khi load lại danh sách TODO list
Những file bị thay đổi
Hành động | file | Ý nghĩa |
---|---|---|
Chỉnh sửa | app/View/Layouts/default.ctp | HTML Template |
Chỉnh sửa | app/webroot/js/views/todo-detail-view.js | View ( Màn hình view của mỗi công việc TODO) |
Nội dung chỉnh sửa mỗi file và chú thích
default.ctp
<h2>Todo #<%- id %></h2>
<div>
<textarea style="width:300px;height:50px" id="edit-todo" autofocus placeholder="Todo?"><%- todo %></textarea>
+ <input type="button" id="updateTodo" value="Update"></input>
<input type="button" id="updateCancel" value="Cancel"></input>
</div>
</script>
Phần này mình chỉ thêm nút chỉnh sửa
todo-detail-view.js
//Set việc xử lý các sự kiện cho DOM
events : {
+ //Khi click nút Update
+ 'click #updateTodo' : 'onUpdateClick',
//Khi click nút Cancel
'click #updateCancel' : 'onCancelClick',
},
〜Chi tiết các hàm〜
render : function() {
//Sử dụng template để vẽ giao diện model
this.$el.html(this.template(this.model.toJSON()));
+ //Nó sẽ lấy giá trị mới được điền vào chỗ nhập input
+ this.$textBox = this.$('#edit-todo');
return this;
},
+ //Xử lý sự kiện sau khi nhấn cập nhật chỉnh sửa
+ onUpdateClick : function() {
+ //Lấy nội dung từ textbox
+ var todoString = this.$textBox.val();
+ this.model.save({
+ todo : todoString
+ }, {
+ wait : true,
+ silent : true,
+ success : function() {
+ Backbone.history.navigate('#todo-lists', true);
+ }
+ });
+ },
+
//Xử lý sự kiện click vào nút Cancel
onCancelClick : function() {
Backbone.history.navigate('#todo-lists', true);
Đây là việc tạo view cho màn hình detail của mỗi công việc
-
Hàm
onUpdateClick
Mình sẽ cập nhật nội dung mới sau khi người dùng ấn nút Save ( Hàm
save
), sau đó chúng ta trở lại màn hình chính danh sách TODO list bằng đoạnBackbone.history.navigate
, trường hợp nhấn Cancel mình cũng dùng đoạn codeBackbone.history.navigate
để trở lại màn hình chính mà không làm các bước lưu.
Chúng ta để ý phần save một tí
this.model.save({
todo : todoString
}, {
wait : true,
silent : true,
});
Backbone.history.navigate('#todo-lists', true);
Các bạn có thể thấy, ở dưới tôi có viết một hàm success, và ý nghĩa của nó đơn giản là chuyển sang trang danh sách các công việc TODO. Tuy nhiên trước khi chuyển trang mình cần phải có các đoạn code bắt lỗi "VD: nội dung không được phép quá ngắn, quá dài, ký tự đặc biệt..." và ở mỗi trường hợp mình nên có những cảnh báo hay cách xử lý khác. Tuy nhiên tôi chỉ viết duy nhất hàm Success để làm ví dụ.
this.model.save({
todo : todoString
}, {
wait : true,
silent : true,
success : function() { // ***** Ham success *****
Backbone.history.navigate('#todo-lists', true);
}
});
},
Tóm tắt nội dung
-
sửa file
app/View/Layouts/default.ctp
như đã hướng dẫn ở trên. -
sửa file
app/webroot/js/views/todo-detail-view.js
như đã hướng dẫn ở trên. - Kiểm tra kết quả.
- Commit lên Git
-
Sau cùng, các bạn có thể kiểm tra lần cuối nội dung commit của tác giả Tại đây
Chúc các bạn thành công.
Tôi đang chờ và rất mong đợi những ý kiến / phản hồi của các bạn.
Cảm ơn các bạn đã theo dõi Phần 2 của chuyên đề này, tôi sẽ sớm hoàn tất bài số 3 để gửi đến các bạn quan tâm.