مرحله ۹: افزودن Ajax

در این مرحله، Ajax را به ماژول mod_hello اضافه می‌کنیم و همچنین شرح می‌دهیم که:

- چگونه می‌توانید رشته‌های زبان (language strings) را به کد جاوااسکریپت انتقال دهید،

- و چگونه می‌توانید پیام‌های سیستمی را از طریق جاوااسکریپت نمایش دهید.

کد منبع در mod_hello مرحله ۹ موجود است

(https://github.com/joomla/manual-examples/tree/main/module-tutorial/step9_ajax)

کامپوننت com_ajax

جوملا یک کامپوننت به نام com_ajax فراهم کرده است که اجازه می‌دهد کد جاوااسکریپت در ماژول یک درخواست Ajax ارسال کند و پاسخ Ajax دریافت کند.

در کد جاوااسکریپت، یک درخواست Ajax به آدرس زیر ارسال می کنیم

 `index.php?option=com_ajax&module=hello&method=count&format=json`

پارامترهای URL به ترتیب زیر تفسیر می‌شوند:

- `option=com_ajax` یعنی جوملا این درخواست HTTP را به کامپوننت com_ajax هدایت می‌کند و پارامترهای بعدی توسط com_ajax بررسی می‌شوند،

- `module=hello` یعنی می‌خواهیم به ماژول mod_hello هدایت کنیم،

- `method=count` یعنی com_ajax متد `countAjax()` کلاس helper ماژول را فراخوانی می‌کند،

- `format=json` یعنی می‌خواهیم پاسخ به صورت فرمت JSON باشد.

نحوه عملکرد این فرایند توسط نمودار ترتیبی نشان داده می‌شود.

وقتی com_ajax نتیجه را دریافت می‌کند، از کلاس JsonResponse جوملا استفاده می‌کند تا نتیجه را به کد جاوااسکریپت بازگرداند.

تغییرات در mod_hello

برای نمایش قابلیت Ajax، یک دکمه در ماژول قرار می‌دهیم که وقتی روی آن کلیک شود، درخواست Ajax ارسال می‌شود تا تعداد کاربران وارد شده به سیستم دریافت شود. پاسخ Ajax می‌تواند این‌ها باشد:

- تعداد کاربران وارد شده به سیستم، اگر خود کاربر هم وارد شده باشد،

- خطای "دسترسی غیرمجاز" اگر کاربر وارد سیستم نشده باشد.

تغییرات کد ما در mod_hello عبارت‌اند از:

- در فایل قالب (tmpl) یک دکمه برای کلیک کاربر قرار دهیم،

- کد جاوااسکریپت برای ارسال درخواست Ajax و دریافت پاسخ ایجاد کنیم،

- تابع countAjax را در کلاس helper بنویسیم.

تغییرات فایل tmpl

فایل جدید mod_hello/tmpl/default.php به شکل زیر است:

 
<?php
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
$document = $this->app->getDocument();
$wa = $document->getWebAssetManager();
$wa->getRegistry()->addExtensionRegistryFile('mod_hello');
$wa->useScript('mod_hello.add-suffix');
// ارسال مقدار suffix به جاوااسکریپت
$document->addScriptOptions('vars', ['suffix' => '!']);
$h = $params->get('header', 'h4');
$greeting = "<{$h} class='mod_hello'>{$hello}</{$h}>";
Text::script('MOD_HELLO_AJAX_OK');
Text::script('JLIB_JS_AJAX_ERROR_OTHER');
?>
<?php echo $greeting; ?>
<div>
    <p><?php echo Text::_('MOD_HELLO_NUSERS'); ?><span class="mod_hello_nusers"></span></p>
    <button class="mod_hello_updateusers"><?php echo Text::_('MOD_HELLO_UPDATE_NUSERS'); ?></button>
</div>

در اینجا با استفاده از تابع `Text::script` رشته‌های زبان را به کد جاوااسکریپت ارسال می‌کنیم، مثلاً:

 
Text::script('MOD_HELLO_AJAX_OK');

این باعث می‌شود رشته زبان `MOD_HELLO_AJAX_OK` در جاوااسکریپت قابل دسترسی باشد و در جاوااسکریپت می‌توان آن را به صورت زیر خواند:

 
Joomla.Text._('MOD_HELLO_AJAX_OK')

این `Joomla.Text` در فایل `core.js` موجود است (`media/system/js/core.js`). رشته `JLIB_JS_AJAX_ERROR_OTHER` در فایل زبان `lib_joomla.ini` تعریف شده که همیشه توسط جوملا بارگذاری می‌شود.

تغییرات جاوااسکریپت

کد Ajax را در فایل `add-suffix.js` اضافه می‌کنیم. خطوط اضافه شده:

 
const countUsers = (event) => {
  const nusers = event.target.parentElement.querySelector('span.mod_hello_nusers');
  Joomla.request({
    url: 'index.php?option=com_ajax&module=hello&method=count&format=json',
    method: 'GET',
    onSuccess(data) {
      const response = JSON.parse(data);
      if (response.success) {
        nusers.innerText = response.data;
        const confirmation = Joomla.Text._('MOD_HELLO_AJAX_OK').replace('%s', response.data);
        Joomla.renderMessages({ 'info': [confirmation] });
      } else {
        const messages = { 'error': [response.message] };
        Joomla.renderMessages(messages);
      }
    },
    onError(xhr) {
      Joomla.renderMessages(Joomla.ajaxErrorsMessages(xhr));
      const response = JSON.parse(xhr.response);
      Joomla.renderMessages({ 'error': [response.message] }, undefined, true);
    }
  });
};

document.querySelectorAll('.mod_hello_updateusers').forEach(element => {
  element.addEventListener('click', countUsers);
});

توضیح: یک Listener روی دکمه "بروزرسانی کاربران" گذاشته‌ایم که با کلیک روی آن درخواست Ajax ارسال می‌شود.

- `Joomla.request` (در core.js) برای ارسال Ajax است.

- `Joomla.renderMessages` (در media/system/js/messages.js) برای نمایش پیام‌ها در بخش پیام سیستم کاربرد دارد.

به‌روزرسانی joomla.asset.json

از آنجا که کد ما اکنون به "messages" و "core" وابسته است، فایل `joomla.asset.json` را بایستی به شکل زیر به‌روزرسانی کنیم و همچنین شماره نسخه اسکریپت را تغییر دهیم تا کش مرورگر خالی شود:

 
{
  "$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
  "name": "mod_hello",
  "version": "1.0.0",
  "description": "Joomla Module Tutorial",
  "license": "GPL-2.0-or-later",
  "assets": [
    {
      "name": "mod_hello.add-suffix",
      "type": "script",
      "uri": "mod_hello/add-suffix.js",
      "dependencies": [
        "jquery", "core", "messages"
      ],
      "version": "1.1.0"
    } 
  ]
}

تغییرات فایل Helper

وقتی که به بخش مدیریت (administrator back-end) جوملا وارد می‌شوید، جوملا فهرستی از کاربران وارد شده را نمایش می‌دهد. با کمی بررسی می‌توانید بفهمید که این خروجی توسط یک ماژول مدیریت به نام `administrator/modules/mod_logged` ایجاد می‌شود و این ماژول در فایل Helper خود متدی به نام `getList` دارد که فهرست کاربران وارد شده را برمی‌گرداند.

یکی از گزینه‌ها این است که مستقیماً از این کد استفاده کنیم و متد `\Joomla\Module\Logged\Administrator\Helper\LoggedHelper::getList()` را فراخوانی کنیم، اما استفاده مجدد از کد جوملا به این صورت ریسک بالایی دارد چون ممکن است بین نسخه‌ها تغییر کند. به خصوص زمانی که ماژول از سبک قدیمی (جوملا ۳) به سبک جدید با تزریق وابستگی (dependency injection) به‌روزرسانی می‌شود، متد `getList` از یک تابع استاتیک به یک تابع نمونه‌ای تبدیل می‌شود.

به همین دلیل ساده‌تر است که روند را کپی کنیم و از یک کوئری مشابه در پایگاه داده استفاده کنیم. خطوط جدید در فایل Helper ما بدین صورت است:

mod_hello/src/Helper/HelloHelper.php

 
use Joomla\Database\DatabaseInterface;
use Joomla\CMS\Language\Text;
...
public function countAjax()
{
    $user = Factory::getApplication()->getIdentity();

    if ($user->id == 0) {
        // کاربر وارد نشده است
        throw new \Exception(Text::_('JERROR_ALERTNOAUTHOR'));
    }

    $db    = Factory::getContainer()->get(DatabaseInterface::class);
    $query = $db->getQuery(true)
        ->select('COUNT(*)')
        ->from('#__session AS s')
        ->where('s.guest = 0');

    $db->setQuery($query);

    return (string) $db->loadResult();
}

دقت کنید که کامپوننت `com_ajax` از `$module->getHelper(…)` روی کلاس ماژول `Extension` برای پیدا کردن کلاس Helper استفاده می‌کند (مانند آنچه در مرحله ۸ توضیح داده شده). بنابراین باید `HelperFactory` را در فایل `services/provider.php` تنظیم کنید، همانطور که در مرحله تزریق وابستگی انجام دادیم.

تغییرات فایل مانیفست

mod_hello/mod_hello.xml

 
<?xml version="1.0" encoding="utf-8"?>
<extension type="module" client="site" method="upgrade">
    <name>MOD_HELLO_NAME</name>
    <version>1.0.9</version>
    <author>me</author>
    <creationDate>today</creationDate>
    <description>MOD_HELLO_DESCRIPTION</description>
    <namespace path="src">My\Module\Hello</namespace>
    <files>
        <folder module="mod_hello">services</folder>
        <folder>src</folder>
        <folder>tmpl</folder>
        <folder>language</folder>
    </files>
    <scriptfile>script.php</scriptfile>
    <media destination="mod_hello" folder="media">
        <filename>joomla.asset.json</filename>
        <folder>js</folder>
    </media>
    <config>
        <fields name="params">
            <fieldset name="basic">
                <field
                    name="header"
                    type="list"
                    label="MOD_HELLO_HEADER_LEVEL"
                    >
                    <option value="h3">MOD_HELLO_HEADER_LEVEL_3</option>
                    <option value="h4">MOD_HELLO_HEADER_LEVEL_4</option>
                    <option value="h5">MOD_HELLO_HEADER_LEVEL_5</option>
                    <option value="h6">MOD_HELLO_HEADER_LEVEL_6</option>
                </field>
            </fieldset>
        </fields>
    </config>
</extension>