Lập trình tạo một MVC Component đơn giản cho Joomla. Phần 2. Hiển thị danh sách các phần tử

Trong bài viết này

Đặt vấn đề 🔝

Phần này sẽ trình bày việc xây dựng chức năng xem danh sách sinh viên. Chức năng xem danh sách sinh viên sẽ là chức năng mặc định. Tức là khi người dùng (người quản trị) lần đầu truy cập vào component (khi nhấn vào menu "Quản lý sinh viên" hoặc khi nhập URL http://localhost/administrator/index.php?option=com_students), ta sẽ trả về cho họ trang chứa danh sách sinh viên như hình dưới đây

Ta sẽ hiển thị dòng tiêu đề "Danh sách sinh viên", sau đó là danh sách các sinh viên có trong CSDL.

Entry point 🔝

Hãy trở lại với nội dung của file khởi động (entry-point) \com_students\students.php:
$controller = JControllerLegacy::getInstance('Students');
$task = JFactory::getApplication()->input->getCmd('task');
$controller->execute($task);
Trước hết, tên gọi JControllerLegacy chỉ là một alias của lớp BaseController, được định nghĩa trong file "\libraries\src\MVC\Controllers\BaseController.php". Khi đọc tài liệu https://docs.joomla.org/Model-View-Controller ta có thể hiểu được rằng phương thức JControllerLegacy::getInstance() sẽ xác định giá trị của tham số 'task' trong truy vấn HTTP. Nếu tồn tại tham số 'task' có dạng 'controllername.methodname' thì nó sẽ tìm kiếm (trong thư mục \com_students\controllers) và nạp lớp tương ứng với 'controllername'; đồng thời, nó sẽ sửa giá trị của tham số 'task' bằng cách bỏ đi phần thứ nhất và chỉ giữ lại phần thứ hai, tức là giá trị mới sẽ có dạng 'methodname'; khi đó, câu lệnh $controller->execute($task) sẽ dẫn đến việc gọi phương thức 'methodname' của lớp controller đã được khởi tạo trước đó. Còn nếu không có tham số 'task' như vậy thì JControllerLegacy::getInstance()sẽ nạp lớp mặc định trong file \com_students\controller.php.

Controller mặc định 🔝

Khi người dùng (người quản trị) lần đầu truy cập controller, tham số 'task' sẽ không tồn tại, vì thế, như phân tích ở trên, controller mặc định sẽ được gọi. Hiện thời, controller mặc định của chúng ta chỉ đơn thuần là dòng khai báo thừa kế từ lớp JControllerLegacy (cũng tức là lớp BaseController)
class StudentsController extends JControllerLegacy
{
}
Câu hỏi đặt ra: sau khi đối tượng $controller (trong entry point) được khởi tạo là đối tượng của lớp controller mặc định thì việc gọi phương thức $controller->($task) sẽ dẫn tới điều gì? Vì 'task' không tồn tại nên 'hành động' mặc định sẽ được thực hiện, và 'hành động' mặc định đó là phương thức 'display()'. Điều này được thể hiện ở dòng 234 trong mã nguồn "BaseController.php":
$command  = $input->get('task', 'display');
Và ta hãy xem mã nguồn của phương thức display() này (bắt đầu từ dòng 614 của file trên):
public function display($cachable = false, $urlparams = array())
{
    $viewName = $this->input->get('view', $this->default_view);
    $viewLayout = $this->input->get('layout', 'default', 'string');
    $view = $this->getView($viewName, $viewType, '', array('base_path' => $this->basePath, 'layout' => $viewLayout));

    // Get/Create the model
    if ($model = $this->getModel($viewName))
    {
        // Push the model into the view (as default)
        $view->setModel($model, true);

    }

    // Display the view
    $view->display();

    return $this;
}
Phương thức display() sẽ xác định 2 giá trị là $viewName và $viewLayout tương ứng với hai tham số 'view' và 'layout' trong truy vấn HTTP. Nếu không tồn tại tham số 'view' thì $viewName sẽ nhận giá trị là $default_view, mà giá trị mặc định của $default_view lại được xác định bằng tên của controller (dòng số 427 trong file "BaseController.php")
$this->default_view = $this->getName();
Trong tình huống hiện thời thì tên của controller chính là 'Students', do đó $viewName sẽ có giá trị mặc định là 'Students'. Còn giá trị mặc định của $viewLayout thì dễ thấy là 'default'. Bên cạnh việc xác định view và layout thì phương thức display() cũng sẽ khởi tạo model để gắn vào view, tên của model trùng với tên của view (đều là $viewName).
Như vậy, điều chúng ta cần làm là xây dựng một lớp view có tên là 'Students' (tức là tên đầy đủ của lớp sẽ là StudentsViewStudents), cùng với một layout có tên là 'default.php'. Để phục vụ cho hoạt động của view này, ta cũng sẽ cần xây dựng model cùng tên 'Students' (tức là tên đầy của của lớp sẽ là StudentsModelStudents).

Xây dựng view và layout mặc định 🔝

Trong thư mục com_students\views, ta tạo thư mục 'students' (tên của view). Một view sẽ bao gồm file 'view.html.php' chứa định nghĩa lớp view, một thư mục tên là 'tmpl', trong đó chứa các file *.php, mỗi file ứng với một layout của view; layout mặc định có tên là 'default.php'. Tất nhiên, như đã nói ở phần 1, trong mỗi thư mục cần chứa một file 'index.html' để ngăn chặn việc duyệt thư mục.

Nội dung của file view.html.php

<?php
defined('_JEXEC') or die;
class StudentsViewStudents extends JViewLegacy
{
    protected $items;
    public function display($tpl = null)
    {
        $this->items = $this->get('Items');
        JToolbarHelper::title("Danh sách sinh viên");
        return parent::display($tpl);
    }
}
Ở đây, ta định nghĩa một lớp view có tên là StudentsViewStudent thừa kế lớp JViewLegacy (là alias của lớp HtmlView, được định nghĩa trong file \libraries\src\MVC\View\HtmlView.php). Phương thức display(...) của lớp này, như đã phân tích ở phần phía trên, sẽ được gọi trong phương thức display() của một controller nào đó nhằm mục đích hiển thị thông tin cho người dùng. Nhiệm vụ hiển thị thông tin cho người dùng được chia thành 2 phần.
Phần thứ nhất được thể hiện trực tiếp trong mã của phương thức display(...) của lớp view mà chúng ta xây dựng. Trong trường hợp cụ thể trên đây thì chỉ gồm việc hiển thị thanh tiêu đề "Danh sách sinh viên" (về sau ta có thể bổ sung thêm vài thứ khác).
Phần thứ hai được thể hiện trong mã của tập tin layout. Việc lựa chọn layout được thực hiện bởi controller (như phân tích ở phần trên), còn việc nạp mã của layout được thực hiện như sau: trong phương thức display() của chúng ta có gọi đến phương thức display() của lớp cha (HtmlView), phương thức HtmlView::display() lại gọi đến phương thức HtmlView::loadTemplate(), và trong mã của phương thức HtmlView::loadTemplate(), ở dòng 701 của file HtmlView.php, có lệnh include để nạp mã của tập tin layout:
include $this->_template;
trong đó $_template chứa đường dẫn đầy đủ tới tập tin layout.
Phần mã trong file layout sẽ cần sử dụng đến dữ liệu (danh sách sinh viên) để có thể hiển thị nó. Dữ liệu này được chuẩn bị sẵn, trước khi gọi phương thức display() của lớp cha. Cụ thể, ở đây ta gọi phương thức $this->get('Items') để lấy được danh sách sinh viên (cơ chế này sẽ được giải thích ở phần model dưới đây), đưa kết quả vào thuộc tính $items. Đó là một mảng mà mỗi phần tử là thông tin về một sinh viên. Do tập tin layout được chèn vào bằng lệnh include nên nó sẽ hoạt động trong ngữ cảnh của lớp view, và do đó nó có thể truy cập $this->items để lấy dữ liệu. Khi đó, mã của tập tin layout sẽ như sau.

Nội dung của tập tin layout default.php

<?php
defined('_JEXEC') or die;
?>

<table class="table table-striped">
    <thead>
        <tr>
            <th>Họ và tên</th>
            <th>Năm sinh</th>
            <th>Điểm trung bình</th>
        </tr>
    </thead>
    <tbody>
        <?php foreach ($this->items as $i=>$item)
        {?>
            <tr class="row<?php echo $i%2;?>">
                <td><?php echo $item->name;?></td>
                <td><?php echo $item->year;?></td>
                <td><?php echo $item->avg;?></td>
            </tr>
        <?php } ?>
    </tbody>
</table>
Nội dung đoạn mã trên là khá rõ ràng: hiển thị ra một bảng có một dòng tiêu đề và các dòng nội dung; mỗi dòng nội dung là thông tin về một sinh viên (hãy xem hình ở đầu bài viết). Vấn đề còn lại chưa rõ ràng là vì sao chỉ với mỗi lời gọi $this->items = $this->get('Items') mà ta lại có được danh sách sinh viên để hiển thị ra?

Xây dựng model 🔝

Trong phần trình bày về controller mặc định ở trên, ta đã biết rằng phương thức display() của controller sẽ nạp một model cùng tên với view rồi gắn vào cho view.
Trong view, nếu gọi phương thức get('Something') thì nó sẽ được chuyển hóa thành lời gọi phương thức getSomething() của model tương ứng. Trong trường hợp của ta, lời gọi $view->get('Items') thực chất được chuyển hóa thành lời gọi $model->getItems().
Như vậy, để view Students có thể hoạt động được như trên thì ta cần tạo ra một model Students mà trong đó phương thức getItems() phải trả về một danh sách (mảng) sinh viên. Trong số các lớp model của Joomla (https://docs.joomla.org/Model-View-Controller) thì JModelList (tức là ListModel) là lớp phù hợp cho nhiệm vụ của chúng ta, cụ thể, nó có sẵn phương thức getItems() mà ta cần. Tất nhiên, ta hiểu rằng Joomla không phải thần thánh để có thể đoán được là ta muốn lấy dữ liệu gì, từ đâu. Đo đó, ta nhất định phải cung cấp thông tin gì đó rồi thì mới có thể lấy được thứ ta muốn. Qua phân tích mã của phương thức getItems() trong file \libraries\src\MVC\Model\ListModel.php ta thấy rằng nó sẽ thực hiện truy vấn CSDL bằng câu truy vấn được xác định bởi phương thức getListQuery(). Như vậy, ta cần xây dựng một model dựa trên lớp JModelList và cần override phương thức getListQuery() để trả về câu truy vấn cần thiết cho việc lấy danh sách sinh viên từ CSDL.
Trong thư mục \com_students\models, tạo file 'students.php' với nội dung như sau
<?php
defined('_JEXEC') or die;
class StudentsModelStudents extends JModelList
{
    protected function getListQuery()
    {
        $db = $this->getDbo();
        $query = $db->getQuery(true); //Create new query
        $query->select('*')
            ->from($db->quoteName('#__students'));
        return $query;
    }
}
Dễ thấy, câu truy vấn ta xây dựng có ý nghĩa là lấy toàn bộ nội dung của bảng '#__students' trong CSDL. Đơn giản chỉ có thế. Và đến đây, ta cũng đã hoàn tất nhiệm vụ hiển thị danh sách sinh viên.

Comments

Popular posts from this blog

Cài đặt Xdebug cho VSCode trên Windows

Lập trình tạo một MVC Component đơn giản cho Joomla. Phần 4. Chỉnh sửa một phần tử

Lập trình tạo một MVC Component đơn giản cho Joomla. Phần 1. Khởi tạo component