LoginSignup
3
3

More than 5 years have passed since last update.

Các bài học lập trình ứng dụng RESTful ~ Chuyên đề phát triển Web - [Chương 2] Tạo ứng dụng TODO list sử dụng Backbone.js

Last updated at Posted at 2015-10-02

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ị

:white_check_mark: Đă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:

  • :white_check_mark: EC2 instance đã được bật
  • :white_check_mark: Đã 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

36fcd1ee-97d5-9264-39bf-e7859fbe820a.jpeg

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.

-:white_check_mark: 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.

-:white_check_mark: 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.

  • :white_check_mark: vào branch master bằng câu lệnh git checkout master
  • :white_check_mark: vol/01 gộp với master bằng lệnh git 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.

:white_check_mark: git push origin master -> điền username, mật khẩu

Ouput
[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

:white_check_mark: 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.

github1.jpg

Tiếp tục đồng bộ với repository bằng cách fork

Repository gốc đã được fork Ở đây

  • :white_check_mark: Thêm một fork repository tên là upstream
    • git remote add upstream https://github.com/suzukishouten-study/rest-study.git
  • :white_check_mark: Xem nội dung đã fork của repository upstream vừa được tạo.
    • git fetch upstream
  • :white_check_mark: 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.

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.

  • :white_check_mark: git branch vol/02 để tạo branch
  • :white_check_mark: git checkout vol/02 để vào sử dụng branch
Output
#(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:

  • :white_check_mark: Tạo thư mục mkdir app/webroot/js/lib
  • :white_check_mark: 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:

  • :white_check_mark: wget http://backbonejs.org/backbone-min.js
  • :white_check_mark: wget http://underscorejs.org/underscore-min.js
  • :white_check_mark: wget http://code.jquery.com/jquery-2.1.3.min.js
Output
[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

Nó sẽ hiển thị như thế này.
lesson1-1.jpg

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

lesson1-2.jpg

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ó.

default.ctp
<!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

router.js
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ới app.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àm todoLists#todo-lists.

  • Hàm TodoLists
    Đây là nội dung của hàm todoLists đượ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.

app.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

  • :white_check_mark: Tạo app/View/Layouts/default.ctp như mô tả ở trên.
  • :white_check_mark: Tạo app/webroot/js/routers/router.js như mô tả ở trên.
  • :white_check_mark: Tạo app/webroot/js/app.js như mô tả ở trên.
  • :white_check_mark: Kiểm tra kết quả.
  • :white_check_mark: 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

Untitled.png

※ 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.

lesson2-3.jpg

Nếu ô này đã được bỏ check ( trạng thái OFF ) rồi thì đã OK.
lesson2-4.jpg

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.

lesson2-2.jpg

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.

  1. 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
  2. Quá trình khởi tạo Todo List cho View được thực thi, view đã được tạo
  3. Quá trình hiển thị Todo List lên View bắt đầu
  4. Thực hiện các thao tác nạp của collections, bắt đầu truy cập server side API
  5. 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.
  6. Ở collection, Phân tích những data đã được tổng hợp trong bước 5.
  7. Phân tích data đầu tiên (model) ở collection (Parse) được thực thi
  8. 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
  9. Phân tích data thứ 2 trong collection.
  10. Đổ dữ liệu đã thu được ra màn hình.
  11. Phân tích data thứ 3 trong collection.
  12. Đổ 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

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

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

todo-collection-view.js
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àm initialize.
    • 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).

todo-collection.js
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àm fetchđể đổ 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ại app.TodoModel.

  • Hàm parse
    Nó sẽ chạy hàm fetch (Có sẵn trong Backbone.Collection mà mình đã thừa kế), Thực thi hàm parse 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.

todo-model.js
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àm fetch, 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àmTodoList.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ị.
lesson3-1.jpg

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

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

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ến el ở 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
js:todo-item-view.js
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`.
  • 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àm addOne 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

  • :white_check_mark: app/View/Layouts/default.ctp 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/views/todo-item-view.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

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.
lesson4-1.jpg

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 )
1.PNG

Thêm công việc thành công.
2.PNG

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

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

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àm onCreateTodo để 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àm newAttribute đượ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.
    • 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

  • :white_check_mark: Sửa file app/View/Layouts/default.ctp như hướng dẫn trên.
  • :white_check_mark: Sửa file app/webroot/js/views/todo-collection-view.js như hướng dẫn trên.
  • :white_check_mark: Kiểm tra kết quả
  • :white_check_mark: 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.

checkbox.png

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

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

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

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!
- :white_check_mark: Sửa file app/View/Layouts/default.ctp theo hướng dẫn trên.
- :white_check_mark: Sửa file app/webroot/js/models/todo-model.js theo hướng dẫn trên.
- :white_check_mark: Sửa file app/webroot/js/views/todo-item-view.js theo hướng dẫn trên.
- :white_check_mark: Kiểm tra lại kết quả
- :white_check_mark: 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.

xoa1.PNG

Khi bạn click xóa, TODO 2 sẽ biến mất sau khi reload lại danh sách.
xoa2.PNG

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

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

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

  • :white_check_mark: Chỉnh sửa file app/View/Layouts/default.ctp như hướng dẫn trên.
  • :white_check_mark: Chỉnh sửa file app/webroot/js/views/todo-item-view.js như hướng dẫn trên.
  • :white_check_mark: Kiểm tra kết quả
  • :white_check_mark: 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.

chitiet.PNG

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

todo3.PNG

Khi click Cancel, không thực hiện hành động gì cả mà sẽ quay lại danh sách Todo list

chitiet.PNG

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

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

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.logalert.

  • 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àm removeCurrentView)

  • Để 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àm removeCurrentView và hàm nextView.
    Hàm nextView 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

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àminitialize

    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àm fetch.
    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àm Backbone.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

  • :white_check_mark: app/View/Layouts/default.ctp sửa như trên。
  • :white_check_mark: app/webroot/js/routers/router.js sửa như trên。
  • :white_check_mark: app/webroot/js/views/todo-detail-view.js sửa như trên。
  • :white_check_mark: Kiểm tra kết quả
  • :white_check_mark: 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.
3ne.PNG

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

3updated.PNG

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

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

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ạn Backbone.history.navigate, trường hợp nhấn Cancel mình cũng dùng đoạn code Backbone.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í

ChucNangSave
    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ụ.

ChucNangSave
    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

  • :white_check_mark: sửa file app/View/Layouts/default.ctp như đã hướng dẫn ở trên.
  • :white_check_mark: sửa file app/webroot/js/views/todo-detail-view.js như đã hướng dẫn ở trên.
  • :white_check_mark: Kiểm tra kết quả.
  • :white_check_mark: Commit lên Git
  • :white_check_mark: 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.

3
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3