0

Joomla Bài học 35: Lập trình Site Controller, Model và View hiển thị dữ liệu ngoài Frontend (Site MVC)

Trong các bài học trước (từ 31 đến 34), chúng ta đã hoàn thiện toàn bộ các thao tác quản trị phía sau hậu trường (Administrator CRUD). Tuy nhiên, một Custom Component chỉ thực sự hoàn thiện khi dữ liệu được đưa ra ngoài cho người dùng cuối xem và tương tác.

Trong Joomla Bài học 35, chúng ta sẽ chuyển trọng tâm sang không gian Site (Frontend) để xây dựng một luồng hiển thị hoàn chỉnh, cho phép khách truy cập website xem danh sách các bản ghi đã được xuất bản (state = 1).

1. Kiến trúc thư mục Frontend MVC (site/)

Phía người dùng cuối của một Component thường tập trung vào tính năng đọc dữ liệu (Read-only) và tối ưu hóa SEO. Cấu trúc thư mục chuẩn cho khu vực Frontend trong Component com_custom sẽ bao gồm:

Plaintext

com_custom/site/
├── src/
│   ├── Controller/
│   │   └── DisplayController.php      <-- Điều phối request chung cho Frontend
│   ├── Model/
│   │   └── ItemsModel.php              <-- Truy vấn lấy danh sách bản ghi công khai
│   └── View/
│       └── Items/
│           └── HtmlView.php            <-- Xử lý logic truyền dữ liệu ra giao diện danh sách
└── tmpl/
    └── items/
        └── default.php                 <-- Giao diện HTML hiển thị ngoài Frontend (Bootstrap 5)

2. Lập trình Site Controller (site/src/Controller/DisplayController.php)

Controller phía Frontend chịu trách nhiệm nhận các tham số từ URL (ví dụ view=items) và gọi View tương ứng.

Tạo file tại: src/components/com_custom/src/Controller/DisplayController.php

PHP

<?php
namespace MyCompany\Component\Custom\Site\Controller;

\defined('_JEXEC') or die;

use Joomla\CMS\MVC\Controller\BaseController;

class DisplayController extends BaseController
{
    protected $default_view = 'items';

    public function display($cachable = false, $urlparams = [])
    {
        return parent::display($cachable, $urlparams);
    }
}

3. Lập trình Site Model (site/src/Model/ItemsModel.php)

Khác với Model phía Admin (cho phép xem cả bản ghi chưa xuất bản hoặc đã ẩn), Model ở Frontend bắt buộc phải lọc ra các bản ghi có state = 1 (Đã xuất bản).

Tạo file tại: src/components/com_custom/src/Model/ItemsModel.php

PHP

<?php
namespace MyCompany\Component\Custom\Site\Model;

\defined('_JEXEC') or die;

use Joomla\CMS\MVC\Model\ListModel;

class ItemsModel extends ListModel
{
    protected function getListQuery()
    {
        $db    = $this->getDatabase();
        $query = $db->getQuery(true);

        // Chọn các cột dữ liệu cần hiển thị ra ngoài Frontend
        $query->select(
            $db->quoteName([
                'a.id',
                'a.title',
                'a.alias',
                'a.description',
                'a.created',
            ])
        )
        ->from($db->quoteName('#__custom_items', 'a'))
        // CHỈ LẤY CÁC BẢN GHI ĐÃ XUẤT BẢN (state = 1)
        ->where($db->quoteName('a.state') . ' = 1')
        ->order($db->quoteName('a.created') . ' DESC');

        return $query;
    }
}

4. Lập trình Site View Logic (site/src/View/Items/HtmlView.php)

View phía Frontend sẽ lấy dữ liệu từ Model (ItemsModel), chuẩn bị sẵn phân trang và truyền sang file Template để render.

Tạo file tại: src/components/com_custom/src/View/Items/HtmlView.php

PHP

<?php
namespace MyCompany\Component\Custom\Site\View\Items;

\defined('_JEXEC') or die;

use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Factory;

class HtmlView extends BaseHtmlView
{
    protected $items;
    protected $pagination;
    protected $params;

    public function display($tpl = null)
    {
        $app = Factory::getApplication();
        
        // Lấy dữ liệu từ Model
        $this->items      = $this->get('Items');
        $this->pagination = $this->get('Pagination');
        
        // Lấy tham số cấu hình từ Menu Item hiện tại
        $this->params     = $app->getParams('com_custom');

        // Kiểm tra lỗi nếu có
        if (count($errors = $this->get('Errors'))) {
            throw new \Exception(implode("\n", $errors), 500);
        }

        parent::display($tpl);
    }
}

5. Lập trình Giao diện Frontend (site/tmpl/items/default.php)

Thay vì dùng bảng dữ liệu thô (<table>) như trang quản trị, giao diện ngoài Frontend nên được thiết kế đẹp mắt bằng dạng lưới thẻ (Cards Grid) sử dụng Bootstrap 5.

Tạo file tại: src/components/com_custom/tmpl/items/default.php

PHP

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

use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;

/** @var \MyCompany\Component\Custom\Site\View\Items\HtmlView $this */
?>

<div class="custom-items-container py-4">
    <div class="row mb-4">
        <div class="col-12">
            <h1 class="fw-bold border-bottom pb-3 text-dark">Danh sách dữ liệu công khai</h1>
        </div>
    </div>

    <?php if (!empty($this->items)) : ?>
        <div class="row g-4">
            <?php foreach ($this->items as $item) : ?>
                <div class="col-12 col-md-6 col-lg-4">
                    <div class="card h-100 shadow-sm border-0 rounded-4 p-3 bg-white">
                        <div class="card-body">
                            <span class="badge bg-light text-primary mb-2">ID: <?php echo (int) $item->id; ?></span>
                            <h3 class="card-title h5 fw-bold text-dark mb-2">
                                <a href="<?php echo Route::_('index.php?option=com_custom&view=item&id=' . (int) $item->id); ?>" class="text-decoration-none text-dark">
                                    <?php echo $this->escape($item->title); ?>
                                </a>
                            </h3>
                            <p class="card-text text-secondary small mb-3">
                                <?php echo HTMLHelper::_('string.truncate', strip_tags($item->description), 120); ?>
                            </p>
                            <div class="text-muted small">
                                <i class="fas fa-calendar-alt me-1"></i> <?php echo HTMLHelper::_('date', $item->created, Text::_('DATE_FORMAT_LC3')); ?>
                            </div>
                        </div>
                    </div>
                </div>
            <?php endforeach; ?>
        </div>

        <!-- PHÂN TRANG FRONTEND -->
        <?php if ($this->pagination->get('pages.total') > 1) : ?>
            <div class="row mt-5">
                <div class="col-12 d-flex justify-content-center">
                    <?php echo $this->pagination->getPagesLinks(); ?>
                </div>
            </div>
        <?php endif; ?>

    <?php else : ?>
        <div class="alert alert-info border-0 shadow-sm rounded-4 text-center py-4">
            <i class="fas fa-info-circle me-1"></i> Hiện tại chưa có bản ghi dữ liệu nào được xuất bản.
        </div>
    <?php endif; ?>
</div>

6. Tổng kết bài học

  • Hiểu rõ cách cấu trúc không gian tên và tệp tin cho khu vực Frontend (Site MVC).

  • Thành thạo việc áp dụng bộ lọc state = 1 trong Model để đảm bảo an toàn dữ liệu công khai.

  • Xây dựng giao diện hiển thị danh sách dạng thẻ lưới (Cards Grid) chuyên nghiệp với Bootstrap 5 và hệ thống phân trang tích hợp sẵn của Joomla.

Trong Bài học 36, chúng ta sẽ tiếp tục khám phá kỹ thuật định tuyến nâng cao cho component: Xây dựng Custom Router (SEF URLs) cho Custom Component.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí