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


## 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回】](http://qiita.com/k_shimoji/items/0deaa414bf640b7536bb) 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](http://qiita.com/Takumi_Kasuga/items/d55b8035708652a79547#12-th%E1%BB%AD-truy-c%E1%BA%ADp-b%E1%BA%B1ng-ssh)


## 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](https://qiita-image-store.s3.amazonaws.com/0/94375/ed9cd36f-45e6-f549-f20a-81bc7a913e5a.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



```bash: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](https://qiita-image-store.s3.amazonaws.com/2006/65988/4ac02796-9862-059b-c665-15b220ac2cef.jpeg "github1.jpg")



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

Repository gốc đã được fork [Ở đây](https://github.com/suzukishouten-study/rest-study)

- :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`


```bash:
[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](https://github.com/suzukishouten-study/rest-study/commits/vol/02-finish)
- 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.

- :white_check_mark: `git branch vol/02` để tạo branch
- :white_check_mark: `git checkout vol/02 ` để vào sử dụng branch

```bash: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`

```bash: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](https://qiita-image-store.s3.amazonaws.com/2006/65988/c97fbdf1-6c28-c568-6690-58db81c0b580.jpeg "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](https://qiita-image-store.s3.amazonaws.com/2006/65988/ce3881c0-9983-9025-a46c-9dc005d36508.jpeg "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ó.

```html: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

```js: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](http://qiita.com/mocha/items/c6a626416daaa04d0668), 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` là `#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.

```Javascript: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](https://qiita-image-store.s3.amazonaws.com/0/94375/b9aecf17-1992-337e-0cd0-2be00199e416.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](https://qiita-image-store.s3.amazonaws.com/2006/65988/e42dab38-b25e-f22a-10e0-050cebfce30a.jpeg "lesson2-3.jpg")

Nếu ô này đã được bỏ check ( trạng thái OFF ) rồi thì đã OK.
![lesson2-4.jpg](https://qiita-image-store.s3.amazonaws.com/2006/65988/72314951-7062-8b83-b0e6-7eaffae4e2b1.jpeg "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](https://qiita-image-store.s3.amazonaws.com/2006/65988/da4a0f5a-a1f1-ba97-13b4-409894128f95.jpeg "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

```diff: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

```diff: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

```js: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).

```js: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.

```js: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.

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


```json
"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ó

```json
{
    "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ị.
![lesson3-1.jpg](https://qiita-image-store.s3.amazonaws.com/2006/65988/05e9661d-6105-5bbd-f89b-c6ad2d999552.jpeg "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

```diff: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
```diff: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](https://qiita-image-store.s3.amazonaws.com/2006/65988/ebc3e1dc-fe9e-e704-9193-db1cc669311a.jpeg "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](https://qiita-image-store.s3.amazonaws.com/0/94375/6b5876bc-a063-f6c4-4a7a-ef7f4e8733f4.png)


Thêm công việc thành công.
![2.PNG](https://qiita-image-store.s3.amazonaws.com/0/94375/0f30db85-ddab-5d77-d0e1-63dae34f63a8.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

```diff: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


```diff: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](https://qiita-image-store.s3.amazonaws.com/0/94375/b226270d-3554-28e4-c891-e72238d39795.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

```diff: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


```diff: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

```diff: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](https://qiita-image-store.s3.amazonaws.com/0/94375/44a729c8-d499-9c61-f1f2-e3f726c61188.png)


Khi bạn click xóa, TODO 2 sẽ biến mất sau khi reload lại danh sách.
![xoa2.PNG](https://qiita-image-store.s3.amazonaws.com/0/94375/e9f2fa4b-d287-5dea-002b-f1a5048c2632.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

```diff: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


```diff: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](https://qiita-image-store.s3.amazonaws.com/0/94375/d5ff2214-4974-ba64-2fcc-a53842db13d5.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](https://qiita-image-store.s3.amazonaws.com/0/94375/dbc5e454-cc6b-74bf-3398-9c039abb2df2.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](https://qiita-image-store.s3.amazonaws.com/0/94375/d5ff2214-4974-ba64-2fcc-a53842db13d5.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

```diff: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


```diff: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à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


```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à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 trigger` sync` 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](https://qiita-image-store.s3.amazonaws.com/0/94375/7dce6f9d-7fd6-8e27-910f-abb5887490dd.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](https://qiita-image-store.s3.amazonaws.com/0/94375/369991b3-7dea-1c3b-b77b-661bdd3d5a9d.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

```diff: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


```diff: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í

```js: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ụ.

```js: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](http://qiita.com/k_shimoji/items/d96826702ab6d6b3a1fd)
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.