Các bài học lập trình ứng dụng RESTful ~ Chuyên đề phát triển Web - [Chương 3] Backbone sẽ thuật tiện hơn với Marionette.js!


## :large_blue_circle: Lời nói đầu

Trải qua 2 bài viết trước, chúng ta đã hiểu cách vận hành của mô hình MVC trong backbone và sử dụng backbone để tạo ra ứng dụng đầu tiên là app TODO List trên AWS, ở bài viết thứ 3 này chúng ta sẽ tìm hiểu thêm về backbone và một bộ thư viện thú vị tên là Marionette.js, bài viết số 3 được dịch từ [Marionette.jsでBackboneをもっと便利に！ - AWS上で構築するRESTfulアプリ勉強会～Web開発ワークショップ～【第3回】マニュアル](http://qiita.com/k_shimoji/items/dc9f1f8cff3a965c0d97) của tác giả @k_shimoji .

Ở bài viết này, chúng ta sẽ sử dụng thêm thư viện Marionette.js.
Backbone khá là rắc rối, bây giờ chúng ta sẽ nhờ sự trợ giúp của thư viện Marionette để giảm bớt sự cồng kềnh của chương trình và xử lý chúng.

## :large_blue_circle: Tổng quan về Marionette

### Marionette.js là gì ?

Nó sẽ hỗ trợ backbone trong quá trình cấu trúc hệ thống, như tên gọi của nó vậy ( Marionette = Con rối ), Mô hình MVC Model trong app client chỉ như một xương sống, và chúng ta cần viết rất nhiêu đoạn code cho ứng dụng, tuy nhiên sẽ xảy ra những trường hợp gọi là "Mọi lần đều dùng một đoạn code" như trong chương trình mà chúng ta đã tạo ở bài trước, Ví dụ:
- Một phần của đoạn code chuyển đổi View
- Phần quản lý các View lồng nhau
- Đoạn code trong việc lấy data từ server để Render ra View

Và như vậy, khi bạn sử dụng Marionette, chúng ta sẽ quản lý tốt hơn và tránh việc sử dụng các đoạn code tương tự nhau.
Thêm vào đó, khi quy mô ứng dụng trở nên lớn hơn, với chỉ backbone thì bạn phải quản lý quá nhiều View: View định nghĩa cho các layout tổng thể, định nghĩa các event, hiển thị collection, hiển thị các model...
Marionete đã hỗ trợ, cấu trúc View bằng cách sử dụng nhiều View và phân chia các hàm vào các khu vực tùy theo vai trò để dễ đọc, sẽ giúp bảo trì tốt hơn sau này.
Một đặc điểm thuận tiện nữa là `UI`, Thời gian này mình chưa đụng đến nó, đó là những `Triggers` và `Behaviors` với những hàm hữu ích trong đó.
Có rất nhiều plug-in cho Backbone, tuy nhiên cái đầu tiên các bạn nên sử dụng có lẽ là Marionette.

### Giới thiệu về các Object trong Marionette

Marionette được gọi trong MVC, nó có chức năng của controller và view.
Những Object được cung cấp trong backbone chắc chắn phải thay thế để có khả năng chạy được trong Marionette. Object của Marionette để sử dụng hiện tại sẽ giống như trong bảng dưới đây, chi tiết mỗi hàm của object tôi sẽ nói sau, đầu tiên chúng ta chỉ cần quan tâm đến:


#### Hệ thống Controller

Marionette Object       | Tổng quan
---------------------------|:-----------------------------------------
Marionette.Application     |Đây là điểm bắt đầu cho một ứng dụng sử dụng Marionette
Marionette.AppRouter       |Làm công việc Routing
Marionette.Controller      |Controller

#### Hệ thống View

Marionette Object      | Tổng quan
---------------------------|:-----------------------------------------
Marionette.Application     |Chứa những định nghĩa, xác định các khu vực (region)
Marionette.Region          |Mỗi đối tượng dại diện cho một region.
Marionette.LayoutView      |Tập hợp các view của một layout
Marionette.CollectionView  |View để hiển thị các Collection thích hợp
Marionette.CompositeView   |Hiển thị collection
Marionette.ItemView        |Hiển thị từng Model

Vị trí của các hàm trong mỗi view

View                       |Hàm region |Tác động đến template
---------------------------|:-----------|:--
Marionette.Application     |Có            |Không
Marionette.LayoutView      |Không         |Có
Marionette.CollectionView  |Không         |Không
Marionette.CompositeView   |Không         |Có
Marionette.ItemView        |Không         |Có


#### Mối quan hệ giữa các object trong backbone

Mối quan hệ giữa Backbone object và Marionette object tương ứng như sau

Backbone                  |Marionette|
--------------------------|:-------------
-                         |Marionette.Application|
Backbone.Router           |Marionette.AppRouter<br/>Marionette.Controller|
Backbone.View             |Marionette.Region<br/>Marionette.LayoutView<br/>Marionette.CollectionView<br/>Marionette.CompositeView<br/>Marionette.ItemView<br/>|


## :large_blue_circle: Nội dung phần này

Chương trình chúng ta đã làm ở bài trước bây giờ mình sẽ viết lại và sử dụng các object của Marionette.js.

:warning: Kết quả sẽ không khác gì cả, mình chỉ thay đổi cấu trúc code bên trong.

Đầu tiên là phần chuẩn bị:

## :large_blue_circle: Chuẩn bị

### Login bằng SSH

Các bạn đăng nhập qua putty hoặc gõ lệnh sau vào terminal.
- :white_check_mark: `ssh -i [đường dẫn đến file private key] study@[serverのPublicIP]`

Chạy lệnh `cd` để vào thư mục mình đang làm việc `rest-study`.

- :white_check_mark: `cd /var/www/study/rest-study`

Bắt đầu thôi !

### Sao lưu branch của git

Ở chương trước, chúng ta làm việc ở branch `vol/02`. Sau khi hoàn thành bài học , branch `vol/02` được push lên Github, vì vậy việc đầu tiên bạn cần làm là gộp branch này với branch `master` để nhận kết quả cuối cùng. Để giữ phiên bản cũ hoạt động, bạn nên sửa đường dẫn của nó bằng cách tạo một folder mới và lưu nó vào đó.

:warning: Nội dung của phần 1 là bạn phải hoàn thành hết các điều kiện trên, hãy ghi nhớ.

- Đầu tiên bạn cần phải vào branch `master`.

:white_check_mark:`git checkout master`

Gộp branch `master` với branch `vol/02` để nhận kết quả của bài viết trước.

:white_check_mark:`git merge vol/02`

Đã gộp xong !



#### Tạo branch cho phiên làm việc này.
Vẫn là những thao tác như cũ.

Chúng ta tạo branch `vol/03` và checkout nó.
: warning: Một khi đã tạo branch tên `vol/03` từ branch `master`, đầu tiên hãy chắc chắn bạn đang ở branch `master`.

- :white_check_mark: `git branch` để chắc chắn bạn đang ở branch `master`
- :white_check_mark: Nếu bạn đang không ở `master`, nhập lệnh `git checkout` để checkout vào `master`

Tạo branch `vol/03` bằng các lệnh dưới đây

- :white_check_mark: `git branch vol/03` Tạo branch
- :white_check_mark: `git checkout vol/03` chuyển đến branch `vol/03` vừa tạo


#### Xác nhận

Có một lệnh giúp kiểm tra log các comit

- :white_check_mark: hãy kiểm tra log các commit bằng `git log` để xác nhận các thay đổi

Kết quả sẽ ok nếu hiển thị commit ngay trước đó.

Sự chuẩn bị về branch đã xong.
:warning: `git branch -a` và bạn xem tất cả các branch sẽ thấy hiện `vol/03-finish` chứa tất cả các source của các giai đoạn nội dung đã hoàn thành, bạn hãy tham khảo để xem sự commit của mỗi bài học qua URL sau
[Commits · suzukishouten-study/rest-study](https://github.com/suzukishouten-study/rest-study/commits/vol/03-finish)


## :large_blue_circle: Tải thư viện Marionette

Tải về từ trang chủ của Marionette [Marionette.js – The Backbone Framework](http://marionettejs.com/)。

Ở dưới sẽ có link download, bạn đăng nhập vào server bằng SSH và sẽ thấy lệnh wget.
Hướng dẫn chạy wget bạn có thể tham khảo ở [Bài viết trước](http://qiita.com/Takumi_Kasuga/items/adea7d28c2c8b541b29a).

- :white_check_mark: Mình sẽ tải nó vào `/var/www/study/rest-study/app/webroot/js/lib`

```bash
cd /var/www/study/rest-study/app/webroot/js/lib
wget http://marionettejs.com/downloads/backbone.marionette.min.js
```

Bây giờ thì bạn đã sẵn sàng!
Để hiểu chức năng của mỗi object trong Marionette,  hãy xem kỹ hơn một tí.
Đầu tiên là bài 1, mình sẽ đặt tên là "Entry point - routing - controller", chương trình mà chúng ta đã tạo ở bài trước.
- Marionette.Application
- Marionette.AppRouter
- Marionette.Controller

Chúng ta cần  xử lý cả 3 file trên


## :large_blue_circle: Bài 1 "entry point - routing - controller"

Lợi ích đầu tiên của việc sử dụng Marionette mà tôi thấy  là phần view, nhưng tạm thời giữ không thay đổi những Entry point-routing-controller trước đây vì
Đây không phải là điểm mạnh của Marionette, chúng ta cần làm như sau:

- Thay đổi entry point
- Tách biệt router và controller
Chúng ta sẽ sửa đổi chương trình hiện tại và sử dụng những Marionette object dưới đây,

- Marionette.Application
- Marionette.AppRouter
- Marionette.Controller



###Những file cần sửa nội dung

Thao tác|file|tổng quan|
|---|:-------------------------------|:-------------|
|Sửa|app/View/Layouts/default.ctp    |HTML template|
|Thêm|app/webroot/js/main.js          |Nơi bắt đầu ứng dụng - entry point|
|Sửa|app/webroot/js/app.js           |Ứng dụng (Marionettte.Application)|
|Sửa|app/webroot/js/routers/router.js|Router (Marionette.AppRouter)|
|Thêm| app/webroot/js/routers/controller.js|Controller|

	
#### default.ctp

Đầu tiên là file này

```diff:default.ctp
        <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>
+       <script src="js/lib/backbone.marionette.min.js" type="text/javascript"></script>

        <!-- js(application) -->


 
        <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>
+       <!--   controller   -->
+       <script src="js/routers/controller.js" type="text/javascript"></script>
        <!--   router   -->
        <script src="js/routers/router.js" type="text/javascript"></script>
-       <!--   entry point   -->
+       <!--   application   -->
        <script src="js/app.js" type="text/javascript"></script>
+       <!--   entry point   -->
+       <script src="js/main.js" type="text/javascript"></script>

 </body>
 </html>
```

- Đây là phần đọc các file js mới được thêm vào, gồm có:
	- `js/lib/backbone.marionette.min.js` - Thư viện Marionette.
	- `js/routers/controller.js` - Nó được hỗ trợ bởi Marionette, controller của chương trình, file này mình sẽ tạo sau.
	- `js/app.js` - Được hỗ trợ bởi Marionette, sự thực thi của các object trong ứng dụng.
		- :warning: Trước khi sửa chúng ta nên nhớ đây là entry point, chúng ta cần sửa sau khi chuyển main.js trở thành entry point đã.
	- `js/main.js` - Entry Point, mình sẽ tạo sau đây.


#### main.js

Trước đây, `app.js` là entry point. Với bài học mới này, mình sẽ chia file này thành 2 phần.

- main.js - Nó sẽ trở thành entry point.
- app.js - Nó sẽ trở thành object thừa kế `Marionette.Application`

```js:main.js
var app = app || {};

//Bắt đầu
(function(app) {
	app.application = new app.Application();
	app.application.start();
})(app);
```

Biến app.Application (như được định nghĩa ở app.js), chỉ dùng để chạy hàm `start`.

#### app.js

Những gì chúng ta sẽ làm với `app.js` được tóm tắt bằng 2 dòng dưới đây:

- Các điều hướng của router
- Chạy `Backbone.history.start()` để giám sát các sự kiện「hashchange」


```js:App.jsCũ
var app = app || {};

//Bắt đầu
(function(app) {
	var todoRouter = new app.TodoRouter();
	Backbone.history.start();
})(app);
```

Khi bạn sử dụng Marionette, những quá trình được thực hiện trong các object được thừa kế từ `Marionette.Application`, vì vậy chỉ có 1 định nghĩa của object trong `app.js`, khai báo và thực hiện là sự biên dịch từ `main.js`.
Những object trong các mô tả trên của file `main.js` ( những entry point và các file ) sẽ được khai báo và thực hiện.


```diff:app.js
 //Bắt đầu
 (function(app) {
-       var todoRouter = new app.TodoRouter();
-       Backbone.history.start();
+       app.Application = Backbone.Marionette.Application.extend({
+               initialize : function(){
+                       new app.TodoRouter();
+               },
+
+               onStart : function(){
+                       Backbone.history.start();
+               },
+       });
 })(app);
```

Bạn sẽ làm khác trước một chút.

- Khởi tạo - Chạy Hàm `initialize`.
	- Tại đây router bắt đầu điều hướng.
- Hàm bắt đầu - Chạy hàm `onStart`.
	- Hàm này giúp mình giám sát sự kiện `hashchange`.


#### router.js

Thay đổi router để kế thừa `Marionette.AppRouter`.
Viết 1 chuẩn duy nhất của routing trong router,  thực thi các hàm mà bạn đã viết trong Controller.

```diff:router.js
 //router
 (function(app) {
-       app.TodoRouter = Backbone.Router.extend({
-               routes : {
+       app.TodoRouter = Backbone.Marionette.AppRouter.extend({
+               //Tạo Controller
+               controller: new app.TodoController(),
+               //Cấu hình Routing
+               appRoutes : {
                        ''                      : 'todoLists',
                        'todo-lists'            : 'todoLists',
                        'todo-lists/:id'        : 'todoDetail'
                },
-
-               currentView : false,
-
-               todoLists : function() {
-                       //Chuyển đến View của danh sách TODO
-                       this.removeCurrentView();
-                       this.nextView(app.TodoCollectionView);
-               },
-
-               todoDetail : function(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);
```


- Tất cả sự thực thi của 1 hàm để chạy sau khi routing nhằm chuyển controller và hủy router. 
- `controller: new app.TodoController()`
	- Khởi tạo Controller 


#### controller.js

Chúng ta phải di chuyển hàm cần thực thi sau khi routing vào đây, nội dung như ở dưới

```js:controller.js
var app = app || {};

//controller
(function(app) {
	app.TodoController = Backbone.Marionette.Controller.extend({

		currentView : false,

		todoLists : function() {
			//Thêm routing cho view của TODO list
			this.removeCurrentView();
			this.nextView(app.TodoCollectionView);
		},

		todoDetail : function(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);
```


#### Tóm tắt nội dung

- :white_check_mark: `app/View/Layouts/default.ctp` sửa như trên.
- :white_check_mark: `app/webroot/js/main.js` tạo mới với nội dung như trên.
- :white_check_mark: `app/webroot/js/app.js` 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/routers/controller.js` tạo mới với nội dung như trên.
- :white_check_mark: Kiểm tra kết quả.
- :white_check_mark: Commit lên Git.

**Link đến source hoàn chỉnh ( Github )**
- [default.ctp](https://github.com/suzukishouten-study/rest-study/blob/41f6e5b08feb74352e527994141c40dfb247add8/app/View/Layouts/default.ctp)
- [main.js](https://github.com/suzukishouten-study/rest-study/blob/41f6e5b08feb74352e527994141c40dfb247add8/app/webroot/js/main.js)
- [app.js](https://github.com/suzukishouten-study/rest-study/blob/41f6e5b08feb74352e527994141c40dfb247add8/app/webroot/js/app.js)
- [router.js](https://github.com/suzukishouten-study/rest-study/blob/41f6e5b08feb74352e527994141c40dfb247add8/app/webroot/js/routers/router.js)
- [controller.js](https://github.com/suzukishouten-study/rest-study/blob/41f6e5b08feb74352e527994141c40dfb247add8/app/webroot/js/routers/controller.js)

Chuyển sang bài 2 thôi.
Marionette sẽ xử lý phần view của mình cho sáng sủa dễ đọc hơn.

## :large_blue_circle: Bài 2: Áp dụng view của Marionette vào màn hình TODO list.

Marionette mạnh nhất trong việckhởi tạo View.
Đầu tiên mình sẽ sửa giao diện TODO list.
Như trên mình đã nói thì Marionette hỗ trợ 4 loại view, nhưng ở đây mình sẽ chỉ áp dụng những loại sau.
- LayoutView
- CompositeView
- ItemView

Qua đó mình sẽ sử dụng hàm Region mà `Marionette.Application` và `LayoutView` có sẵn.
Bây giờ mình sẽ xem thêm chi tiết về vai trò của từng View.

### Mối quan hệ giữa Region và View


#### View

「View」được biết đến là là đơn vị nhỏ nhất hiển thị một dữ liệu nào đó, VD hiện tại khi hoàn thành bài trước chúng ta có các view sau.

- View hiển thị TODO 1（Model 1）
- View hiển thị TODO list

Điều đó có nghĩa là, Để hiển thị Model 1 thì cần có 1 view tương ứng được điều khiển ở Collection
.
図Khi nào thì điều này xảy ra.

![view.png](https://qiita-image-store.s3.amazonaws.com/2006/65988/4732e3de-9fd1-f6a9-0bf4-371ea2a1d3a2.png "view.png")


#### Region

"Region" là một khu vực sẽ hiển thị view.
Với hàm Region, chúng ta có thể thay thế view để hiển thị ( chuyển giao diện )
Mặc dù ở lần trước mình đã phá hủy View trên chính nó, nhưng với hàm region, bạn có thể chuyển view qua lại trên Marionette.
Đặc trưng này mình sẽ sử dụng khi chuyển đổi qua lại giữa TODO list view và TODO Detail view.
Chúng ta sẽ xem bài 3 với việc chuyển đổi ( switching )


**Ảnh minh họa về sự thay đổi**

![region.png](https://qiita-image-store.s3.amazonaws.com/2006/65988/52216d42-d333-eeb0-2eba-014edb368c23.png "region.png")

Tôi chưa thêm gì vào ứng dụng TODO list này cả, nhưng khi object cần hiển thị có số lượng ngày càng lớn, lúc đó bạn cần phải chuẩn bị rất nhiều vùng để hiển thị view của mỗi vai trò.

**Sử dụng nhiều region**

Đặc điểm của Region là có thể được tạo ra từ Marionette.Application trong object và Marionette.LayoutView.

Một mô hình điển hình như sau
- Marionette.Application sẽ tạo một region bao bên ngoài hầu hết các thành phần của ứng dụng.
	- Đặt LayoutView vào region
		- View mà hiển thị dữ liệu và các button hay những thứ tương tự sẽ là LayoutView
		- Cũng tạo thêm nhiều Region ở LayoutView
			- View mà hiển thị dữ liệu và các button hay những thứ tương tự sẽ ở LayoutView
			- Cũng tạo thêm nhiều Region ở LayoutView

Đây gọi là cấu trúc lồng nhau.

:warning: View được đặt vào region sẽ luôn được viết để sử dụng LayoutView, tuy nhiên sẽ có vài trang không cần sử dụng region này nên sẽ tốt hơn nếu đặt nó trực tiếp vào CompositeView và ItemView chứ không phải LayoutView. Cái này gọi là cơ sở case-by-case.
Cụ thể hơn, bạn hãy xem hình ảnh dưới đây.

![table.png](https://qiita-image-store.s3.amazonaws.com/0/94375/fcb57d63-e4e9-c9f8-b146-536644be2c29.png)



- Để quản lý các region dưới bằng Marionette.Application object.
	- Region hiển thị Header
		- Nó sẽ thêm vào một View hiển thị header.
	- Region hiển thị content 
		- Để chuyển đổi view có thể dùng được cả bên trong (LayoutView Hiển thị content X / LayoutView Hiển thị content Y )
	- Region hiển thị footer
		- Nó sẽ thêm vào một View hiển thị footer.
- LayoutView Hiển thị content X 
	- Region hiển thị menu button
		- Nó sẽ thêm những menu botton để hiển thị trên LayoutView
	- Region hiển thị danh sách dữ liệu
		- Nó sẽ chèn danh sách dữ liệu vào và hiển thị trên LayoutView
	- Region hiển thị button chuyển trang
		- Nó sẽ chèn vào một button chuyển trang để hiển thị trong LayoutView
		- 
〜Dưới đây tương tự như vậy〜

### Chú ý

Chú ý này để giúp bạn hiểu hơn vai trò của mỗi View trong Marionette như dưới đây.

1. Region
2. LayoutView
3. CompositeView
4. ItemView

Dưới đây sẽ là một cấu trúc lồng nhau với thứ tự như trên.

![todo-list.png](https://qiita-image-store.s3.amazonaws.com/0/65988/733b4339-acf3-04ce-ca5f-42b6d8733356.png "todo-list.png")


Bây giờ mình sẽ bắt đầu sửa các file trong chương trình với các hướng dẫn dưới đây

###Những file cần sửa nội dung

Thao tác|file|Mô tả|
|---|:-------------------------------|:-------------|
|Sửa|app/View/Layouts/default.ctp    |HTML template|
|Sửa|app/webroot/js/app.js           |Ứng dụng (Marionettte.Application)|
|Sửa|app/webroot/js/routers/controller.js|Controller|
|Thêm mới|app/webroot/js/views/todo-layout-view.js|View(LayoutView:Todo-list Layout)|
|Thêm mới|app/webroot/js/views/todo-composite-view.js|View(CompositeView: Danh sách Todo-list)|
|Sửa|app/webroot/js/views/todo-item-view.js|View (ItemView:Hiển thị mỗi TODO trong TODO-List)|
|Xóa|app/webroot/js/views/todo-collection-view.js|View (Danh sách Todo list)|


#### default.ctp


Nơi các template của LayoutView tăng lên.

```diff:default.ctp
 <body>
 	<!-- Nội dung -->
 	<div id="main"></div>
-	<!-- Template của TODO-list -->
-	<script type="text/template" id="list-template">
+
+	<!-- TODO list layout templates -->
+	<script type="text/template" id="todo-layout-template">
 	<h1>TODO List</h1>
+	<div id="todo-lists"></div>
+	</script>
+
+	<!-- Template của TODO list -->
+	<script type="text/template" id="todo-composite-template">
 	<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">
-			<tbody id="todo-lists"></tbody>
+			<tbody></tbody>
 		</table>
 	</div>
 	</script>
 
 	<!-- template của Mỗi dòng TODO (Cái được thêm vào phần tbody trên kia) -->
-	<script type="text/template" id="item-template">
+	<script type="text/template" id="todo-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>



 		<a class="detail-link" href="#todo-lists/<%- id %>">Detail</a>
 	</td>
 	</script>

 	<!-- Màn hình detail -->
 	<script type="text/template" id="detail-template">
 	<h2>Todo #<%- id %></h2>



 	<!--   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>
+	<script src="js/views/todo-composite-view.js" type="text/javascript"></script>
+	<script src="js/views/todo-layout-view.js" type="text/javascript"></script>
 	<!--   controller   -->
 	<script src="js/routers/controller.js" type="text/javascript"></script>
 	<!--   router   -->
```

- Template của TODO list được chia ra thành các phần nhỏ để sử dụng một phần của CompositeView và cả để sử dụng trong LayoutView.
	- Tất cả template cho TODO list, cho layout, cho list, sẽ chỉ có 1 và ID thay đổi tương tương ứng như dưới đây.
		- Cho Layout-> `todo-layout-template`
		- Cho list-> `todo-composite-template`
		- Cho mỗi việc trong list -> `todo-item-template `
- LayoutView chứa thẻ của các phần trong region, đó sẽ là `todo-lists` id (view cho danh sách hiển thị sẽ được load ).

- View cho danh sách TODO list (`todo-collection-view.js`) sẽ được sửa thành ` todo-composite-view.js`.
- View cho layout, mình sẽ tạo file mới `js/views/todo-layout-view.js`.

#### app.js


```diff:app.js
 		onStart : function(){
 			Backbone.history.start();
 		},
+
+		regions : {
+			mainRegion : '#main'
+		}
+
 	});
```

- `Marionette.Applicaton` đã có, chúng ta đã thêm cấu hình của hàm region. Cụ thể hơn thẻ `<= div id "main"></ div>` của `default.ctp` đã sẵn sàng để xử lý các region.

#### controller.js
Khi vẽ hoặc hủy 1 view sử dụng hàm region của Marionette thì code của phần đó sẽ bị xóa trong chương trình để không chạy nữa.

```diff:controller.js
 (function(app) {
 	app.TodoController = Backbone.Marionette.Controller.extend({
 
-		currentView : false,
-
 		todoLists : function() {
-			//chuyển đến view của danh sách TODO
-			this.removeCurrentView();
-			this.nextView(app.TodoCollectionView);
+			//routing view cho TODO layout
+			this.nextView(app.TodoLayoutView);
 		},
 
 		todoDetail : function(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);
+			app.application.mainRegion.show(new View(option));
 		},
-		removeCurrentView : function() {
-			if (this.currentView) {
-				this.currentView.remove();
-			}
-		}
 	});
 })(app);

```

- Hàm removeCurrentView（Xóa）
	- Bằng hàm region của Marionette, bạn sẽ quản lý được sự gia tăng của view bằng cách xóa những view không sử dụng nữa, nó sẽ xóa các định nghĩa và phần gọi của view mà bạn đang làm việc trên nó bằng hàm `removeCurrentView`.
- Biến nextView
	- Mặc dù từ đầu mình chỉ làm việc tạo view sử dụng đặc điểm Region, giờ mình sẽ thay đổi thành "tạo 1 view, region view ấy như một argument để show ra"
	
#### todo-layout-view.js

Đây là là layout cho view, vai trò của nó như sau
- Định nghĩa Region
- Đọc collection
	- Việc đọc Collection đã được thực hiện ở `todo-collection-view.js` gốc, vì vậy bạn sẽ làm việc ở file này.
	
```js:todo-layout-view.js
var app = app || {};

//View layout cho TODO list
(function(app) {
	app.TodoLayoutView = Backbone.Marionette.LayoutView.extend({
		//template
		template: '#todo-layout-template',

		regions : {
			listRegion : '#todo-lists',
		},

		onRender : function(){
			var todoCollection = new app.TodoCollection();
			this.listenTo(todoCollection , 'reset', this.showTodoList, this);
			todoCollection.fetch({reset : true});
		},

		showTodoList : function(todoCollection){
			this.listRegion.show( new app.TodoCompositeView({
				collection : todoCollection
			}));
		},

	});
})(app);
```

- Biến template
	- Biến `Template` giữ cài đặt của ID cho Template, nó sẽ tự động được áp dụng một template mà Marionette được trỏ đến bởi ID trong thời gian vẽ.
- Biến regions
	- Giúp xác định ID của các tag để sử dụng như một region. Tại đây bạn sẽ không thể xác định được vùng hiển thị danh sách `# todo-lists`.
- Hàm onRender
	- Nó sẽ thực thi ở tất cả các view của Marionette và tự động chạy khi view được sinh ra.
	- Tại đây chúng ta sẽ get dữ liệu của collection, mình phải chạy hàm `showTodoList` tại thời điểm lấy xong dữ liệu.
- Hàm showTodoList
	- `This.listenTo` được xác định trong `onRender` qua hàm (todoCollection, 'reset', this.showTodoList, this); `, nó được thực thi tại thời điểm sự kiện `reset` của collection được thực hiện ( nội dung của collection được làm mới).
	- Biến `regions` được set bởi ` listRegion`, và được trỏ đến bởi hàm `show`. Chạy qua object mới của `app.TodoCompositeView` biểu thức của hàm `show`, nó được set như là 1 view ở `listRegion`.


#### todo-composite-view.js


Có một view mà được hiển thị ở region trong `listRegion` ở `todo-layout-view.js`bên trên.
Bây giờ mình sẽ sử dụng CompositeView.

Mình cần chú ý đến các thành phần `childView`,`childViewContainer`và `ui`.

button "Add Todo" và các hàm xử lý sự kiện đều được viết ở `todo-collection-view.js` .

```
var app = app || {};

//View cho TODO list
(function(app) {
	app.TodoCompositeView = Backbone.Marionette.CompositeView.extend({
		template: '#todo-composite-template',

		childView : app.TodoItemView,

		childViewContainer : 'tbody',

		ui : {
			addTodo : '#addTodo',
			newTodo : '#new-todo'
		},

		events : {
			'click @ui.addTodo' : 'onCreateTodo',
		},

		initialize: function(){
			_.bindAll( this, 'onCreatedSuccess' );
		},

		onCreateTodo : function(e) {
			this.collection.create(this.newAttributes(), {
		          silent:  true ,
		          success: this.onCreatedSuccess
			});
			this.ui.newTodo.val('');
		},

		newAttributes : function() {
			return {
				todo : this.ui.newTodo.val().trim(),
				status : 0
			};
		},

		onCreatedSuccess : function(){
			this.collection.fetch({ reset : true });
		},

	});
})(app);
```

- Biến childView
	- Trong `CompositeView` mình xác định `ItemView` như là một view con. Ở backbone nguyên thủy mình sẽ diễn tả việc vẽ các view con trong view cha, trong khi với việc sử dụng `CompositeView` mình sẽ không cần viết như vậy quá lộn xộn mà chương trình sẽ chạy tốt đơn giản chỉ cần xác định ItemView là view con.
- Biến childViewContainer
	- Đây là container để vẽ những ItemView thành những view con, bạn sẽ xác định ID của các thành phần HTML trên template. Chúng ta đã xác định được thẻ `tbody`.
- Biến `ui`
	- Biến `UI` là một biến có sẵn trong Marionette. Cú pháp của nó là "tên-biến: ID trên HTML DOM", với view này bạn có thể dễ dàng quản lý tên biến mà bạn chỉ định.
		- Nếu bạn muốn chỉ định lựa chọn của biến trong sự kiện, bạn sẽ có thể xác định được định dạng của nó bằng `@ui.tên-biến`. Tại đây tôi sử dụng `'click @ui.addTodo': 'onCreateTodo'`.

- `Todo-collection-view.js` đã được mô tả, Các hàm xử lý sự kiện `onCreateTodo`, `newAttributes`,` onCreatedSuccess` sẽ được viết ngay sau đây.

#### todo-item-view.js

Chúng ta áp dụng vào ItemView
Tương tự thì bạn có thể sử dụng biến `ui` ở `Todo-composite-view.js`.
Mình sẽ xóa phần vẽ template `ItemView` hiện tại.

```diff:todo-item-view.js
 //Hiển thị mỗi view của TODO List
 (function(app) {
-	app.TodoItemView = Backbone.View.extend({
+	app.TodoItemView = Backbone.Marionette.ItemView.extend({
 		//Thẻ html của phần tử được thêm vào DOM
 		tagName : 'tr',
 
 		//template
-		template : _.template($('#item-template').html()),
+		template : '#todo-item-template',
+
+		ui : {
+			checkBox : '.toggle',
+			removeLink : '.remove-link'
+		},
 
 		//Xử lý các sự kiện trong DOM
 		events : {
 			//Click vào checkbox
-			'click .toggle' : 'onStatusToggleClick',
+			'click @ui.checkBox' : 'onStatusToggleClick',
 			//Khi click vào nút Xóa
-			'click .remove-link' : 'onRemoveClick',
+			'click @ui.removeLink' : '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 UI, Biến events
	- Mình đã viết lại `todo-composite-view.js` cho tốt hơn.
- Hàm initialize, Hàm render
	- ItemView sẽ làm công việc vẽ.

#### todo-collection-view.js

Để phù hợp với chỉnh sửa, tên file của view cần phù hợp với tên view của Marionette. vì vậy để sử dụng `CompositeView`,
thì `Todo-collection-view.js` phải bị xóa.



#### Tóm tắt bài học

Các bước

- :white_check_mark: `app/View/Layouts/default.ctp` sửa như hướng dẫn trên. 
- :white_check_mark: `app/webroot/js/app.js` sửa như hướng dẫn trên. 
- :white_check_mark: `app/webroot/js/routers/controller.js` sửa như hướng dẫn trên. 
- :white_check_mark: `app/webroot/js/views/todo-layout-view.js` thêm mới với nội dungn như trên.
- :white_check_mark: `app/webroot/js/views/todo-composite-view.js` thêm mới với nội dungn như 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: `app/webroot/js/views/todo-collection-view.js` xóa đi。
- :white_check_mark: Kiểm tra kết quả
- :white_check_mark: Commit lên Git

**Link tham khảo source sau khi đã hoàn thành**
- [default.ctp](https://github.com/suzukishouten-study/rest-study/blob/2d2f9bbb5c8414186e0b83e57772e1699c9b88d2/app/View/Layouts/default.ctp)
- [app.js](https://github.com/suzukishouten-study/rest-study/blob/2d2f9bbb5c8414186e0b83e57772e1699c9b88d2/app/webroot/js/app.js)
- [controller.js](https://github.com/suzukishouten-study/rest-study/blob/2d2f9bbb5c8414186e0b83e57772e1699c9b88d2/app/webroot/js/routers/controller.js)
- [todo-layout-view.js](https://github.com/suzukishouten-study/rest-study/blob/2d2f9bbb5c8414186e0b83e57772e1699c9b88d2/app/webroot/js/views/todo-layout-view.js)
- [todo-composite-view.js](https://github.com/suzukishouten-study/rest-study/blob/2d2f9bbb5c8414186e0b83e57772e1699c9b88d2/app/webroot/js/views/todo-composite-view.js)
- [todo-item-view.js](https://github.com/suzukishouten-study/rest-study/blob/2d2f9bbb5c8414186e0b83e57772e1699c9b88d2/app/webroot/js/views/todo-item-view.js)


Chuyển sang bài 3

## :large_blue_circle: Bài 3: Áp dụng view của Marionette vào màn hình TODO detail

Ở màn hình TODO detail, bạn có thể sửa bằng cách áp dụng view của Marionette.
Tôi nghĩ rằng nó có thể sửa gần như toàn bộ nội dung của bài 2.

###Những file cần sửa nội dung

Thao tác|file| Ý nghĩa |
|---|:-------------------------------|:-------------|
|Sửa|app/View/Layouts/default.ctp    |HTM template|
|Sửa|app/webroot/js/routers/controller.js|Controller|
|Thêm mới|app/webroot/js/views/todo-detail-layout-view.js|View (LayoutView: Layout của màn hình chi tiết mỗi công việc TODO)|
|Thêm mới|app/webroot/js/views/todo-detail-item-view.js |View(ItemView: view từng TODO)|
|Xóa|app/webroot/js/views/todo-detail-view.js |View(trang detal của mỗi TODO)|



#### default.ctp

Màn hình detail đã được thực hiện ở View, nhưng tôi sẽ chia nó thành LayoutView và ItemView


```diff:default.ctp
 	</td>
 	</script>
 
-	<!-- màn hình detail -->
-	<script type="text/template" id="detail-template">
+	<!-- layout template của màn hình detail -->
+	<script type="text/template" id="todo-detail-layout-template">
+	<div id="todo-item"></div>
+	</script>

+	<!-- Nội dung trang detail -->
+	<script type="text/template" id="todo-detail-item-template">
 	<h2>Todo #<%- id %></h2>
 	<div>
 	<textarea style="width:300px;height:50px" id="edit-todo" autofocus placeholder="Todo?"><%- todo %></textarea>



 	<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-detail-item-view.js" type="text/javascript"></script>
+	<script src="js/views/todo-detail-layout-view.js" type="text/javascript"></script>
 	<script src="js/views/todo-composite-view.js" type="text/javascript"></script>
 	<script src="js/views/todo-layout-view.js" type="text/javascript"></script>
 	<!--   controller   -->
```

-template của trang detail sẽ được chia thành các phần để sử dụng và ItemView sẽ được sử dụng trongLayoutView.
	- Dùng LayoutView -> `todo-detail-layout-template`
	- Dùng ItemView  -> `todo-detail-item-template`
- View dùng cho layout, thêm tag của các phần trong region, ID của `todo-item` ( Tại đây thì view của trang detail (ItemView) sẽ được load).
- View cho trang detail (`todo-detail-view.js`) đã được thay đổi thành ` todo-detail-item-view.js`.
- View cho layout, bạn cần thêm file `js/views/todo-detail-layout-view.js`.


#### controller.js

Mình sẽ thay đổi phương pháp thông qua ID của view.

```diff:controller.js
 		},
 
 		todoDetail : function(id) {
-			this.nextView(app.TodoDetailView, id);
+			this.nextView(app.TodoDetailLayoutView, {modelId : id});
 		},
 
 		nextView : function(View, option) {
 			app.application.mainRegion.show(new View(option));
 		},
 
 	});
 })(app);
 
```

- Hàm `todoDetail`
	- Bạn đang chạy hàm `nextView`, nhưng nó đã thông qua ID từ trước và đây là biểu thức thứ 2. Thay đổi thành `{modelId: id}` và bạn thông qua object. Nó đã được sử dụng như là một biểu thức của hàm khởi tạo gốc ở phần View, nó được thay đổi để trỏ đến View của biến `options` để sử dụng.
	
#### todo-detail-layout-view.js

Đây là view dành cho layout.
Vai trò của nó như sau

- Định nghĩa Region
- Đọc model
	- Việc đọc model được thự hiện ở file `todo-detail-view.js` gốc, vì vậy mình làm việc ở đây.

```javascript:todo-detail-layout-view.js

var app = app || {};

//Layout View của trang Detail
(function(app) {
	app.TodoDetailLayoutView = Backbone.Marionette.LayoutView.extend({
		//Template
		template : '#todo-detail-layout-template',

		regions : {
			itemRegion : '#todo-item',
		},

		onRender : function() {
			var todoModel = new app.TodoModel({
				id : this.options.modelId
			});
			//Tại thời điểm nhận hết data từ server sẽ bắt đầu hiển thị các item
			this.listenTo(todoModel, 'sync', this.showItem, this);
			//Tổng hợp dữ liệu từ server
			todoModel.fetch({
				wait : true
			});
		},

		showItem : function(todoModel) {
			this.itemRegion.show( new app.TodoDetailItemView({
				model : todoModel
			}));
		},

	});
})(app);
```

- Biến regions
	- Bạn sẽ xác định `#todo-item` như là một region.
- Hàm onRender
	- `Xác định `this.options.modelId`, bạn lấy ID của model được xác định trong `controller.js`.
		- Object được xác định tại thời điểm sinh ra View , trong View, có thể trỏ đến như là `this.options`.
- Việc gọi các view con từ database khác với giữa `collection` và `model`, nhưng tôi sẽ viết theo cùng một cách.

#### todo-detail-item-view.js

Áp dụng vào ItemView

```diff:todo-detail-item-view.js
var app = app || {};

//View trang detail
(function(app) {
	app.TodoDetailItemView = Backbone.Marionette.ItemView.extend({

		//Template
		template: "#todo-detail-item-template",

		ui : {
			todoStatus   : '#edit-todo',
			updateButton : '#updateTodo',
			cancelButton : '#updateCancel'
		},

		//Xử lý các sự kiện trong DOM
		events : {
			//Click nút chỉnh sửa
			'click @ui.updateButton' : 'onUpdateClick',
			//Click nút Cancel
			'click @ui.cancelButton' : 'onCancelClick',
		},

		//Khởi tạo
		initialize: function(){
			_.bindAll( this, 'onSaveSuccess' );
		},

		//Xử lý sự kiện click nút Update
		onUpdateClick : function() {
			//Lấy ký tự từ checkbox
			var todoString = this.ui.todoStatus.val();
			this.model.save({
				todo : todoString
			}, {
				silent : true,
				success : this.onSaveSuccess,
			});
		},

		//Xử lý sự kiện click nút Cancel
		onCancelClick : function() {
			this.backTodoLists();
		},

		//Sửa thành công
		onSaveSuccess : function() {
			this.backTodoLists();
		},

		//Trở lại màn hình danh sách TODO
		backTodoLists : function() {
			Backbone.history.navigate('#todo-lists', true);
		}

	});
})(app);
```

Chỉ liệt kê những phần sửa vì những phần còn lại nội dung không khác gì bài 2 nên tôi sẽ bỏ qua.


#### todo-collection-view.js

Vì tên view cần khớp với tên view của Marionette, Mình sẽ xóa file `todo-detail-view.js`.


#### Tóm tắt bài học


- :white_check_mark: `app/View/Layouts/default.ctp` Sửa theo hướng dẫn trên
- :white_check_mark: `app/webroot/js/routers/controller.js` Sửa theo hướng dẫn trên
- :white_check_mark: `app/webroot/js/views/todo-detail-layout-view.js` tạo file theo hướng dẫn trên.
- :white_check_mark: `app/webroot/js/views/todo-detail-item-view.js`tạo file theo hướng dẫn trên.
- :white_check_mark: Xóa file `app/webroot/js/views/todo-detail-view.js` .
- :white_check_mark: Xác nhận kết quả
- :white_check_mark: Commit lên Git


**Tài nguyên tham khảo khi hoàn tất (GitHub**
- [default.ctp](https://github.com/suzukishouten-study/rest-study/blob/2ad38483a86f1c451abdd8b24d042566329d7631/app/View/Layouts/default.ctp)
- [controller.js](https://github.com/suzukishouten-study/rest-study/blob/2ad38483a86f1c451abdd8b24d042566329d7631/app/webroot/js/routers/controller.js)
- [todo-detail-layout-view.js](https://github.com/suzukishouten-study/rest-study/blob/2ad38483a86f1c451abdd8b24d042566329d7631/app/webroot/js/views/todo-detail-layout-view.js)
- [todo-detail-item-view.js](https://github.com/suzukishouten-study/rest-study/blob/2ad38483a86f1c451abdd8b24d042566329d7631/app/webroot/js/views/todo-detail-item-view.js)



Chúng ta đã xong.


## :large_blue_circle: Chúc các bạn thành công

Hi vọng mọi thứ diễn ra suôn sẻ

Cuối cùng, chúng tôi tổng hợp lại những đặc điểm chính của Marionette mà mình đã sử dụng trong bài này, nếu bạn đã hiểu rồi cũng nên xem lại một lần nữa.

### Tóm tắt những đặc điểm của Marionette đã sử dụng trong bài viết


- Object
	- Marionette.Application
		- Định nghĩa các region và là nơi bắt đầu ứng dụng
	- Marionette.AppRouter
		- Làm nhiệm vụ Routing
	- Marionette.Controller
		- Định nghĩa các hàm sẽ chạy sau khi routing.
	- Marionette.Region
		- Nơi quản lý từng view
	- Marionette.LayoutView
		- Nó sẽ là view bao trọn các view khác trong region, region cũng có thể được định nghĩa ở đây.
                - Bạn có thể sử dụng template.
	- Marionette.CollectionView
		- ※ Chúng ta không sử dụng trong bài này, nó chứa những thứ nằm ngoài hàm template từ CompositeView.
                - Bạn có thể sẽ cần đến chúng nếu thực sự thích nó (Giới thiệu list box ) chỉ để xem cái gì bên trong ItemView.
	- Marionette.CompositeView
		- Sử dụng để hiển thị collection. Kèm theo các ItemView cho mỗi hiển thị ở collection.
                - Bạn có thể dử dụng template, đặt các button để quản lý collection ( button Thêm,... ) 
	- Marionette.ItemView
		- Sử dụng để hiển thị model.
                - Bạn có thể sử dụng template, đặt các button để quản lý collection ( button Refrest,... )  
- View ở các hàm riêng biệt
	- Biến `ui`
		- Các mô tả lựa chọn của HTML trong view được tập trung ở 1 nơi.
		- Có thể đọc từ view với formart `@ui.〜`
	- Biến regions
		- Định nghĩa region bạn muốn quản lý
	- Biến template
		- Định nghĩa bằng cách giữ ID của template, có có thể tự động render.
	- Biến options
		- Có thể trỏ đến object đã được thông qua trong quá trình khởi tạo view.

####  Cái mình không sử dụng

`triggers`、`Behaviors`、etc...

Rất đa dạng, bạn hãy tự kiểm tra nhé



Đó là tất cả những gì tôi muốn nói trong bài viết này.

## Tôi đang chờ đợi những bình luận và góp ý của các bạn

Cảm ơn các bạn đã đọc bài viết này.