LoginSignup
5
5

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-10-08

: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回】マニュアル 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 TriggersBehaviors 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 Không
Marionette.LayoutView Không
Marionette.CollectionView Không Không
Marionette.CompositeView Không
Marionette.ItemView Không

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
Marionette.Controller
Backbone.View Marionette.Region
Marionette.LayoutView
Marionette.CollectionView
Marionette.CompositeView
Marionette.ItemView

: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

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

Tải về từ trang chủ của Marionette Marionette.js – The Backbone Framework

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

  • :white_check_mark: Mình sẽ tải nó vào /var/www/study/rest-study/app/webroot/js/lib
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

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

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.

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

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
- main.js
- app.js
- router.js
- 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.ApplicationLayoutView 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

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

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

  • Để 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

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.

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

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.

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.

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ệnreset` 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 listRegiontodo-layout-view.jsbê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,childViewContainerui.

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 uiTodo-composite-view.js.
Mình sẽ xóa phần vẽ template ItemView hiện tại.

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
- app.js
- controller.js
- todo-layout-view.js
- todo-composite-view.js
- 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

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.

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.
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 địnhthis.options.modelId, bạn lấy ID của model được xác định trongcontroller.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 collectionmodel, 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

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.jstạ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
- controller.js
- todo-detail-layout-view.js
- 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

triggersBehaviors、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.

5
5
0

Register as a new user and use Qiita more conveniently

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