إطار Laravel

مكونات Blade المتقدمة

15 دقيقة الدرس 33 من 45

مقدمة إلى مكونات Blade المتقدمة

توفر مكونات Blade طريقة قوية لإنشاء عناصر واجهة مستخدم قابلة لإعادة الاستخدام في تطبيقات Laravel الخاصة بك. بينما قمنا بتغطية المكونات الأساسية سابقاً، يستكشف هذا الدرس التقنيات المتقدمة بما في ذلك المكونات المجهولة، وفئات المكونات مع الدوال، والمكونات الديناميكية، والفتحات، وسمات المكونات التي ستساعدك على بناء واجهات مستخدم متطورة وسهلة الصيانة.

لماذا نستخدم المكونات المتقدمة؟

  • إنشاء عناصر واجهة مستخدم قابلة لإعادة الاستخدام والتكوين بدرجة عالية
  • تغليف المنطق المعقد ضمن فئات المكونات
  • تمرير محتوى ديناميكي عبر الفتحات
  • بناء أنظمة التصميم ومكتبات المكونات
  • تحسين قابلية الصيانة والقراءة للكود
  • مشاركة المكونات عبر مشاريع متعددة

مراجعة أنواع المكونات

يقدم Laravel نوعين من مكونات Blade:

  • المكونات القائمة على الفئة: مكونات مع فئة PHP تتعامل مع المنطق والبيانات
  • المكونات المجهولة: مكونات بسيطة بدون فئة، فقط قالب Blade

إنشاء المكونات

# مكون قائم على الفئة
php artisan make:component Alert

# ينشئ:
# app/View/Components/Alert.php
# resources/views/components/alert.blade.php

# مكون مجهول
php artisan make:component forms.input --view

# ينشئ:
# resources/views/components/forms/input.blade.php

فئات المكونات المتقدمة

يمكن أن تحتوي فئات المكونات على منطق معقد ودوال وخصائص محسوبة:

<?php
// app/View/Components/UserCard.php
namespace App\View\Components;

use App\Models\User;
use Illuminate\View\Component;

class UserCard extends Component
{
    public $user;
    public $showEmail;
    public $theme;

    public function __construct(User $user, $showEmail = false, $theme = 'light')
    {
        $this->user = $user;
        $this->showEmail = $showEmail;
        $this->theme = $theme;
    }

    // خاصية محسوبة - متاحة في العرض
    public function fullName()
    {
        return $this->user->first_name . ' ' . $this->user->last_name;
    }

    // التحقق من وجود صورة رمزية
    public function hasAvatar()
    {
        return !empty($this->user->avatar);
    }

    // الحصول على رابط الصورة الرمزية مع احتياطي
    public function avatarUrl()
    {
        return $this->hasAvatar()
            ? asset('storage/' . $this->user->avatar)
            : 'https://ui-avatars.com/api/?name=' . urlencode($this->fullName());
    }

    // الحصول على فئات السمة
    public function themeClasses()
    {
        return $this->theme === 'dark'
            ? 'bg-gray-800 text-white'
            : 'bg-white text-gray-900';
    }

    public function render()
    {
        return view('components.user-card');
    }
}
<!-- resources/views/components/user-card.blade.php -->
<div class="user-card {{ $themeClasses() }}">
    <img src="{{ $avatarUrl() }}" alt="{{ $fullName() }}">

    <div>
        <h3>{{ $fullName() }}</h3>
        <p>@{{ $user->username }}</p>

        @if($showEmail)
            <p>{{ $user->email }}</p>
        @endif
    </div>

    {{ $slot }}
</div>
<!-- استخدام المكون -->
<x-user-card :user="$user" show-email theme="dark">
    <button>متابعة</button>
</x-user-card>

الوصول للدوال: يمكن استدعاء الدوال في فئات المكونات من عرض Blade باستخدام الأقواس: {{ $fullName() }}. يتم الوصول إلى الخصائص العامة بدون أقواس: {{ $user }}

المكونات المجهولة

المكونات المجهولة مثالية للمكونات البسيطة والعرضية بدون منطق معقد:

<!-- resources/views/components/badge.blade.php -->
@props([
    'type' => 'default',
    'size' => 'md',
])

@php
$classes = match($type) {
    'success' => 'bg-green-100 text-green-800',
    'danger' => 'bg-red-100 text-red-800',
    'warning' => 'bg-yellow-100 text-yellow-800',
    'info' => 'bg-blue-100 text-blue-800',
    default => 'bg-gray-100 text-gray-800',
};

$sizeClasses = match($size) {
    'sm' => 'text-xs px-2 py-1',
    'lg' => 'text-base px-4 py-2',
    default => 'text-sm px-3 py-1',
};
@endphp

<span {{ $attributes->merge(['class' => "badge $classes $sizeClasses rounded-full"]) }}>
    {{ $slot }}
</span>
<!-- الاستخدام -->
<x-badge type="success">نشط</x-badge>
<x-badge type="danger" size="lg">حرج</x-badge>
<x-badge type="warning" class="ml-2">قيد الانتظار</x-badge>

سمات المكونات

يمنحك متغير $attributes تحكماً دقيقاً في سمات HTML:

<!-- resources/views/components/button.blade.php -->
@props([
    'type' => 'button',
    'variant' => 'primary',
    'size' => 'md',
    'disabled' => false,
])

@php
$variantClasses = match($variant) {
    'primary' => 'bg-blue-600 hover:bg-blue-700 text-white',
    'secondary' => 'bg-gray-600 hover:bg-gray-700 text-white',
    'danger' => 'bg-red-600 hover:bg-red-700 text-white',
    'outline' => 'border border-gray-300 hover:bg-gray-50 text-gray-700',
};

$sizeClasses = match($size) {
    'sm' => 'px-3 py-1 text-sm',
    'lg' => 'px-6 py-3 text-lg',
    default => 'px-4 py-2 text-base',
};

$baseClasses = 'rounded font-medium focus:outline-none focus:ring-2 transition';
@endphp

<button
    type="{{ $type }}"
    {{ $attributes->class([$baseClasses, $variantClasses, $sizeClasses])
        ->merge(['disabled' => $disabled]) }}>
    {{ $slot }}
</button>
<!-- أمثلة الاستخدام -->
<x-button>زر افتراضي</x-button>
<x-button variant="danger" size="lg">حذف</x-button>
<x-button variant="outline" class="mt-4" id="submit-btn">إرسال</x-button>
<x-button type="submit" :disabled="$processing">حفظ</x-button>

دوال السمات:

  • $attributes->merge() - دمج السمات الافتراضية مع السمات الممررة
  • $attributes->class() - دمج فئات CSS بشكل شرطي
  • $attributes->filter() - تصفية سمات محددة
  • $attributes->only() - الحصول على سمات محددة فقط
  • $attributes->except() - الحصول على كل السمات باستثناء المحددة

الفتحات المسماة

تتيح لك الفتحات المسماة تمرير أقسام محتوى متعددة إلى مكون:

<!-- resources/views/components/card.blade.php -->
<div {{ $attributes->merge(['class' => 'card border rounded-lg shadow-sm']) }}>
    @if(isset($header))
        <div class="card-header border-b px-6 py-4 bg-gray-50">
            {{ $header }}
        </div>
    @endif

    <div class="card-body p-6">
        {{ $slot }}
    </div>

    @if(isset($footer))
        <div class="card-footer border-t px-6 py-4 bg-gray-50">
            {{ $footer }}
        </div>
    @endif
</div>
<!-- استخدام الفتحات المسماة -->
<x-card>
    <x-slot:header>
        <h2 class="text-xl font-bold">ملف المستخدم</h2>
    </x-slot:header>

    <!-- محتوى الفتحة الافتراضية -->
    <p>هذا هو المحتوى الرئيسي للبطاقة.</p>
    <p>معلومات المستخدم تذهب هنا.</p>

    <x-slot:footer>
        <button>تحرير الملف</button>
        <button>حذف الحساب</button>
    </x-slot:footer>
</x-card>

الفتحات النطاقية

تتيح الفتحات النطاقية للمكونات الأصلية تمرير البيانات إلى محتوى الفتحة:

<!-- resources/views/components/dropdown.blade.php -->
@props(['items'])

<div class="dropdown" x-data="{ open: false }">
    <button @click="open = !open">
        {{ $trigger }}
    </button>

    <div x-show="open" @click.away="open = false" class="dropdown-menu">
        @foreach($items as $item)
            {{ $slot(['item' => $item]) }}
        @endforeach
    </div>
</div>
<!-- استخدام الفتحات النطاقية -->
<x-dropdown :items="$users">
    <x-slot:trigger>
        اختر مستخدماً
    </x-slot:trigger>

    <!-- فتحة نطاقية مع الوصول إلى $item -->
    @scope($item)
        <a href="/users/{{ $item->id }}">
            {{ $item->name }}
        </a>
    @endscope
</x-dropdown>

ملاحظة: الفتحات النطاقية ميزة متقدمة. لمعظم حالات الاستخدام، الفتحات العادية أو تمرير البيانات كخصائص أبسط وأسهل في الصيانة.

المكونات الديناميكية

عرض المكونات ديناميكياً بناءً على قيم وقت التشغيل:

<!-- مكون ديناميكي بناءً على متغير -->
@php
$componentType = $user->is_premium ? 'premium-badge' : 'regular-badge';
@endphp

<x-dynamic-component :component="$componentType" :user="$user" />

<!-- مكون ديناميكي في حلقة -->
@foreach($notifications as $notification)
    <x-dynamic-component
        :component="'notifications.' . $notification->type"
        :notification="$notification"
    />
@endforeach
<?php
// وحدة التحكم أو الخدمة
class NotificationRenderer
{
    public function render($notification)
    {
        $componentMap = [
            'message' => 'notifications.message',
            'friend_request' => 'notifications.friend-request',
            'order_update' => 'notifications.order-update',
        ];

        $component = $componentMap[$notification->type] ?? 'notifications.default';

        return view()->make('components.' . $component, [
            'notification' => $notification
        ]);
    }
}

أكوام المكونات

تسمح لك الأكوام بدفع المحتوى من المكونات الفرعية إلى التخطيطات الأصلية:

<!-- resources/views/layouts/app.blade.php -->
<html>
<head>
    <!-- كومة الأنماط -->
    @stack('styles')
</head>
<body>
    {{ $slot }}

    <!-- كومة السكريبتات -->
    @stack('scripts')
</body>
</html>
<!-- resources/views/components/chart.blade.php -->
<div class="chart" id="chart-{{ $id }}"></div>

@once
    @push('styles')
        <link rel="stylesheet" href="/css/charts.css">
    @endpush

    @push('scripts')
        <script src="/js/chart.js"></script>
    @endpush
@endonce

@push('scripts')
<script>
    initChart('{{ $id }}', @json($data));
</script>
@endpush

توجيه @once: استخدم @once للتأكد من دفع الكود إلى الكومة مرة واحدة فقط، حتى لو تم عرض المكون عدة مرات على الصفحة.

بناء مكتبة مكونات النماذج

أنشئ نظام مكونات نماذج متكامل:

<!-- resources/views/components/forms/input.blade.php -->
@props([
    'name',
    'label' => null,
    'type' => 'text',
    'value' => '',
    'placeholder' => '',
    'required' => false,
    'disabled' => false,
])

<div class="form-group mb-4">
    @if($label)
        <label for="{{ $name }}" class="block text-sm font-medium text-gray-700 mb-2">
            {{ $label }}
            @if($required)
                <span class="text-red-500">*</span>
            @endif
        </label>
    @endif

    <input
        type="{{ $type }}"
        name="{{ $name }}"
        id="{{ $name }}"
        value="{{ old($name, $value) }}"
        placeholder="{{ $placeholder }}"
        {{ $required ? 'required' : '' }}
        {{ $disabled ? 'disabled' : '' }}
        {{ $attributes->class([
            'w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500',
            'border-red-500' => $errors->has($name),
            'border-gray-300' => !$errors->has($name),
        ]) }}
    >

    @error($name)
        <p class="mt-1 text-sm text-red-600">{{ $message }}</p>
    @enderror
</div>
<!-- resources/views/components/forms/select.blade.php -->
@props([
    'name',
    'label' => null,
    'options' => [],
    'selected' => null,
    'placeholder' => 'اختر خياراً',
    'required' => false,
])

<div class="form-group mb-4">
    @if($label)
        <label for="{{ $name }}" class="block text-sm font-medium text-gray-700 mb-2">
            {{ $label }}
            @if($required) <span class="text-red-500">*</span> @endif
        </label>
    @endif

    <select
        name="{{ $name }}"
        id="{{ $name }}"
        {{ $required ? 'required' : '' }}
        {{ $attributes->class([
            'w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500',
            'border-red-500' => $errors->has($name),
            'border-gray-300' => !$errors->has($name),
        ]) }}
    >
        <option value="">{{ $placeholder }}</option>
        @foreach($options as $value => $text)
            <option value="{{ $value }}" {{ old($name, $selected) == $value ? 'selected' : '' }}>
                {{ $text }}
            </option>
        @endforeach
    </select>

    @error($name)
        <p class="mt-1 text-sm text-red-600">{{ $message }}</p>
    @enderror
</div>
<!-- استخدام مكونات النماذج -->
<form method="POST" action="/users">
    @csrf

    <x-forms.input
        name="name"
        label="الاسم الكامل"
        placeholder="أدخل اسمك الكامل"
        required
    />

    <x-forms.input
        name="email"
        type="email"
        label="عنوان البريد الإلكتروني"
        placeholder="your@email.com"
        required
    />

    <x-forms.select
        name="country"
        label="الدولة"
        :options="$countries"
        :selected="$user->country ?? null"
        required
    />

    <x-button type="submit" variant="primary">إنشاء مستخدم</x-button>
</form>

تمرين تطبيقي 1: نظام مكونات التنبيه

أنشئ مكون تنبيه شامل بالميزات التالية:

  • دعم الأنواع: نجاح، خطأ، تحذير، معلومات
  • أيقونة اختيارية بناءً على النوع
  • خيار قابل للرفض مع Alpine.js
  • فتحات اختيارية للعنوان والوصف
  • فتحة أزرار الإجراءات
  • مهلة تلقائية للرفض (خاصية اختيارية)

مثال الاستخدام:

<x-alert type="success" dismissible :timeout="5000">
    <x-slot:title>نجاح!</x-slot:title>
    تم حفظ التغييرات.
</x-alert>

تمرين تطبيقي 2: مكون جدول البيانات

ابنِ مكون جدول بيانات قابل لإعادة الاستخدام:

  • قبول إعدادات الأعمدة (الاسم، التسمية، قابل للفرز)
  • قبول بيانات الصفوف
  • دعم الأعمدة القابلة للفرز مع الأيقونات
  • عناصر التحكم في الترقيم
  • رسالة الحالة الفارغة
  • فتحة عمود الإجراءات للأزرار المخصصة
  • مرشح بحث اختياري

إضافة: اجعله يعمل مع Livewire للفرز والتصفية الديناميكية.

تمرين تطبيقي 3: مكون النافذة المنبثقة

أنشئ مكون نافذة منبثقة مرن:

  • استخدم Alpine.js لوظيفة الفتح/الإغلاق
  • دعم الأحجام: sm، md، lg، xl، full
  • فتحات مسماة: trigger، title، body، footer
  • زر إغلاق في الرأس
  • النقر على الخلفية للإغلاق (اختياري)
  • مفتاح ESC للإغلاق
  • مصيدة التركيز عند الفتح
  • قفل تمرير الجسم عند فتح النافذة المنبثقة

تحدي: اجعله يعمل مع كل من Alpine.js ومحفزات Livewire wire:click.

الخلاصة

في هذا الدرس، أتقنت تقنيات مكونات Blade المتقدمة. المفاهيم الرئيسية المغطاة:

  • بناء مكونات قائمة على الفئة مع منطق ودوال معقدة
  • إنشاء مكونات مجهولة خفيفة الوزن لحالات الاستخدام البسيطة
  • إدارة سمات المكونات مع دوال الدمج والفئة والتصفية
  • استخدام الفتحات المسماة لتمرير أقسام محتوى متعددة
  • تنفيذ الفتحات النطاقية لمشاركة البيانات
  • عرض المكونات الديناميكية بناءً على شروط وقت التشغيل
  • الاستفادة من أكوام المكونات لإدارة الأصول
  • بناء مكتبات مكونات شاملة (نماذج، تنبيهات، بطاقات)
  • أفضل الممارسات للمكونات القابلة لإعادة الاستخدام وسهلة الصيانة

تمكّنك تقنيات المكونات المتقدمة هذه من بناء مكونات واجهة مستخدم متطورة على مستوى نظام التصميم قابلة لإعادة الاستخدام عبر تطبيقك بالكامل. في الدرس التالي، سنستكشف تحسين قاعدة البيانات وتقنيات الأداء.