نسعى في هذا الدّرس إلى إنشاء قائمتين منسدلتين Dropdown lists ترتبط إحداهما بالأخرى. تحتوي الأولى مثلا على تصنيف والثانية على تصنيف فرعي من التصنيف الموجود في القائمة الأولى. يعني هذا أنْ تتغيّر التصنيفات الفرعيّة الموجودة في القائمة الثّانيّة عند تغيّر العنصُر المحدّد في القائمة الأولى.
تبدو النتيجة بنهاية الخطوات المشروحة في هذا الدرس على النحو التالي:
نستخدم Laravel في الجانب الخلفيّ Backend لتولي التعامل مع الطّلبات والتخاطب مع قاعدة البيانات للحصول على التصنيفات والتّصنيفات الفرعيّة منها. يتولّى سكربت jQuery العمل في الواجهة الأماميّة Frontend لتحديث محتويات القائمة الثانيّة عند تعديل محتوى الأولى.
في ما يلي نظرة عامّة على الخطوات التي سنتّبعها:
- إنشاء النماذج Models والتهجيرات Migrations.
- تهيئة معمل نماذج Model factory وبذر Seed جداول البيانات.
- إعداد المسارات Routes والمتحكّمات Controllers.
- إعداد العروض Views.
الطريقة التي نريد تنفيذها هي كالتالي: عند الدخول إلى المسار categories/
يتلقى ملف المسارات الطّلب ويحوّله إلى الدالّة categories
في المتحكّم HomeController
. تستقبل الداّلة الطلب وتطلب قائمة بالتّصنيفات من نموذج التّصنيف Category
؛ ثم ترسل التّصنيفات إلى العرض categories
الذي يعرضها في القائمة المنسدلة الأولى. نستخدم سكربت jQuery في العرض للإنصات لتغييرات القائمة المنسدلة العلويّة، وعند اختيّار أحد عناصرها يأخذ السكربت معرّف العنصر المحدَّد ثم يرسل به طلب Ajax إلى المسار api/category-dropdown/
الذي يجيب بلائحة التصنيفات الفرعيّة للتّصنيف المحدّد في القائمة الأولى. يستقبل السكربت اللائحة ويعرضها في القائمة الثانية.
تمكنك مراجعة المقالات التاليّة لتفصيلات أكثرعن إنشاء النماذج، استخدام معمل النماذج و العروض.
نبدأ بتثبيت Laravel بالأمر التالي:
composer create-project --prefer-dist laravel/laravel laradropdown "5.3.*"
انتظر اكتمال التثبيت ثم انتقل لمجلد المشروع laradropdown
لمتابعة بقيّة الخطوات. سنفترض في ما يلي أن لاتصال بقاعدة البيانات مضبوط.
إنشاء النماذج والتهجيرات
نبدأ بإنشاء نموذجيْن Category
و SubCategory
. الأوّل للتصنيفات والثاني للتصنيفات الفرعيّة. ننفّذ ما يلي في مجلّد المشروع:
php artisan make:model Category -m php artisan make:model SubCategory -m
استخدمنا خيار m-
لإنشاء التهجيرات مع إنشاء النماذج.
نفتح ملفّ التهجير الخاصّ بالتصنيف ونعدّله:
public function up() { Schema::create('categories', function (Blueprint $table) { $table->increments('id'); $table->string('name')->unique(); $table->timestamps(); }); } public function down() { Schema::dropIfExists('categories') }
يحوي الملفّ كما هو ظاهر أربعة حقول: معرّفا، اسما للتّصنيف وحقليْ الأختام الزمنية.
ننتقل لملفّ التهجير الخاصّ بالتصنيف الفرعي:
public function up() { Schema::create('sub_categories', function (Blueprint $table) { $table->increments('id'); $table->string('name')->unique(); $table->integer('category_id')->unsigned(); $table->timestamps(); }); Schema::table('sub_categories', function (Blueprint $table) { $table->foreign('category_id') ->references('id') ->on('categories') ->onDelete('cascade'); }); } public function down() { Schema::table('sub_categories', function(Blueprint $table) { $table->dropForeign('sub_categories_category_id_foreign'); }); Schema::dropIfExists('sub_categories'); }
يختلف ملفّ التهجير هذا قليلا عن الملفّ السّابق؛ إذ يحوي إلى جانب حقول المعرّف، الاسم والأختام الزمنيّة معرفَ التصنيف الذي يتفرّع منه. هذا المعرف هو مفتاح خارجي Foreign key يحيل إلى جدول التّصنيفات. نعرّف الحقول أولا ثم نحدّد الحقل category_id
على أنه مفتاح خارجي. ينبغي أن يكون الحقل مطابقا تماما من حيث النوع للحقل الذي يحيل إليه. في الدالة down
نبدأ بحذف القيد من على الحقل حتى يمكننا حذف الجدول.
أنهينا إعداد التهجيرات. ننتقل لإعداد النماذج.
نغيّر ملف النموذج Category
على النحو التالي:
class Category extends Model { protected $fillable = ['id', 'name']; public function sub_categories() { return $this->hasMany('SubCategory'); } }
نحدّد أولا الحقول التي يمكن إسنادها (خاصيّة fillable
) ثم نضيف الدالة sub_categories
التي تعرّف العلاقة بين التّصنيف Category
والتّصنيف الفرعي SubCategory
. هذه العلاقة هي من النوع hasMany
بمعنى أنه توجد بالتّصنيف تصنيفات فرعية تابعة له.
يفيدنا تعريف العلاقات بين النماذج في الاستعلامات ويجعلها أسهل بكثير كما سنرى.
نفس الشيء تقريبا بالنسبة للنموذج SubCategory
:
class SubCategory extends Model { protected $fillable = ['id', 'name', 'category_id']; public function category() { return $this->belongsTo('Category'); } }
نعرّف في النموذج SubCategory
العلاقة العكسيّة لتلك المعرّفة في النموذج Category
؛ وهي belongsTo
التي تعني أن هذا الصّنف يتبع للصّنف Category
.
النماذج والتهجيرات جاهزة؛ يمكننا الآن تنفيذ التهجيرات:
php artisan migrate
تهيئة معامل النماذج وبذر جداول البيانات
سنستفيد من الخبرى التي تحصّلنا عليها في درس استخدام معمل النماذج (Model factory) في Laravel لتوليد بيانات الاختبار لتهيئة معملَيْ نماذج نستخدمهما لبذر البيانات في الجدوليْن categories
و sub_categories
.
ننشئ ملفيْن في المجلّد /database/factories
؛ واحدا باسم CategoryFactory.php
والآخر SubCategoryFactory.php
. ثم نضيف المحتوى التالي إلى CategoryFactory.php
:
<?php $factory->define(App\Category::class, function (Faker\Generator $faker){ return [ 'name' => $faker->unique()->word ]; });
نفس المبدأ المستخدَم في الدرس المُشار إليه أعلاه. نعرّف النموذج الذي نريد توليد بيانات له ثم نستخدم مكتبة Faker
لملْء الحقل المحدَّد (name
). طلبنا توليد كلمات فريدة Unique حتى نوافق الشّرط المعرَّف في تهجير قاعدة البيانات الخاصّ بالجدول categories
.
الأمر مختلف قليلا مع الملف SubCategoryFactory.php
الذي نعدّله كالتالي:
<?php $factory->define(App\SubCategory::class, function (Faker\Generator $faker){ $categories = App\Category::get()->pluck('id')->all(); return [ 'name' => $faker->unique()->word, 'category_id' => $faker->randomElement($categories), ]; });
بما أن الحقل category_id
مفتاح خارجي على معرّف التّصنيف فلن تقبل قاعدة البيانات إضافة معرّف لتصنيف غير موجود في جدول التصنيفات. لتجاوز هذا القيد نبدأ بطلب معرّفات التصنيفات:
$categories = App\Category::get()->pluck('id')->all();
ثم عند توليد بيانات للحقل category_id
نطلب منه أن يختار واحدا عشوائيا من المعرّفات التي تحصّلنا عليها سابقا:
'category_id' => $faker->randomElement($categories),
هذا كلّ شيء بالنسبة لمعمل النماذج. ننتقل للبذر. ننشئ صنفا سنستخدمه لبذر النموذجيْن Category
و SubCategory
:
php artisan make:seeder SubCategoryTableSeeder
نفتح الملف ونعدّله كما يلي:
public function run() { App\SubCategory::truncate(); factory(App\Category::class, 10)->create(); factory(App\SubCategory::class, 50)->create(); }
نطلُب في الملفّ توليد 10 تصنيفات و50 تصنيفا فرعيا.
نعدّل ملف البذر DatabaseSeeder
كما يلي:
public function run() { Model::unguard(); $this->call(SubCategoryTableSeeder::class); Model::reguard(); }
لا ننسى استدعاء النموذج Model
في الملف DatabaseSeeder
:
use Illuminate\Database\Eloquent\Model;
نستدعي ملفّ البذر SubCategoryTableSeeder
الذي يستخدم معمليْ النماذج لتوليد البيانات. يمكننا الآن تنفيذ البذر:
php artisan db:seed
إعداد المسارات والمتحكمات
نريد أن نصل إلى صفحة القائمتين المنسدلتين عبر الرابط categories/
؛ لذا سنعرّف مسارا له في ملف مسارات الوِب routes/web.php
:
Route::get('/categories', 'HomeController@categories');
لا خصوصيةَ هنا؛ المسار والمتحكّم والدالة اللذان يعالجان الطّلب.
إن لم يكن المتحكّم HomeController
معرّفا لديك فاستخدم الأمر php artisan make:controller HomeController
وأضف إليه دالة باسم categories
:
public function categories() { $categories = Category::orderBy('name', 'asc')->get(); return view('layouts.categories', compact('categories')); }
لا تنس استيراد النموذج Category
:
use App\Category;
نستخدم النموذج Category
في الدالة للحصول على لائحة التصنيفات مرتبة تصاعديا حسب الاسم؛ ثم نرسل النتيجة إلى العرض categories
الموجود في المجلّد layouts
ضمن مجلّد العروض resources/views
. هذا العرض غير موجود لحدّ الساعة لذا يجب أن ننشئه. لكن قبل ذلك يجب أن ننهي إعداد المتحكّمات.
سنضع في العرض سكربت jQuery -كما أشرنا سابقا- للحصول على لائحة بالتصنيفات الفرعيّة للتّصنيف المحدّد في القائمة المنسدلة العلوية. يرسل السكربت طلب Ajax إلى المسار api/category-dropdown/
. ننشئ هذا المسار في ملفّ المسارات الخاصّ بواجهة التطبيقات البرمجيّة routes/api.php
على النحو التالي:
Route::get('/category-dropdown', 'ApiController@categoryDropDownData');
يمكن أن نستخدم نفس المتحكّم السابق (HomeController
) ونعرّف دالة فيه كما يمكن أن ننشئ متحكّما جديدا. اخترنا الحلّ الأخير حتى نفصل بين التحكّم في المسارات العادية والمسارات المعدّة لتكون واجهة برمجية Application programming interface, API.
نستخدم الأمر التالي لإنشاء متحكّم باسم ApiController
:
php artisan make:controller ApiController
ثم نعدّل عليه:
public function categoryDropDownData() { $category_id = Input::get('category_id'); $subcategories = App\Category::find($category_id)->sub_categories; return Response::json($subcategories); }
يتلقى المسار api/category-dropdown/
معرّف التّصنيف المحدّد في القائمة ضمن متغيّر category_id
. نستقبل المتغيّر المُرسَل من المستخدم بالدّالة get
من الصّنف Input
.
بما أننا عرّفنا علاقات بين التصنيف والتصنيف الفرعي في النموذجين المقابليْن فيمكننا بسهولة العثور على التّصنيفات الفرعيّة لتصنيف؛ كلّ ما علينا فعله هو استخدام الدّالة sub_categories
التي عرّفناها في الصّنف Category
.
تعثُر التعليمة التالية على جميع التّصنيفات الفرعيّة للتّصنيف ذي المعرّف category_id.
$subcategories = Category::find($category_id)->sub_categories;
في الأخير نرمّز التصنيفات الفرعيّة بصيغة Json ثم نرسلها في الإجابة.
لا تنس استدعاء الأصناف التي استخدمناها وإضافتها إلى بداية الملف:
use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Input; use App\Category;
المحصّلة:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Input; use App\Category; class ApiController extends Controller { public function categoryDropDownData() { $category_id = Input::get('category_id'); $subcategories = Category::find($category_id)->sub_categories; return Response::json($subcategories); } }
إعداد العروض
أكملنا الإعدادات من جهة النهاية الخلفيّة؛ تبقى فقط إعداد العرض لاستقبال البيانات وتقديمها للزائر.
ننشئ لهذا الغرض عرضا باسم categories.blade.php
في المجلّد layouts
. يمدّد هذا العرض عرضا رئيسا Master view اسمُه app.blade.php
، يوجد في نفس المجلّد. ينفّذ categories.blade.php
مقطعًا باسم content
معرّفًا في العرض الرّئيس ويوجد به محتوى الصفحة.
@extends('layouts.app') @section('content') <div class="container"> <div class="row"> <div class="col-md-10 col-md-offset-1"> <div class="panel panel-default"> <div class="panel-heading">Dashboard</div> <div class="panel-body"> <div class="form-group"> <label>Category:</label><br> <select class="form-control input-lg" name="category_id" id="category_id"> <option value="">Select Category</option> @foreach($categories as $category) <option value="{{ $category->id }}"> {{$category->name}}</option> @endforeach </select> </div> <div class="form-group"> <label>Subcategory:</label><br> <select class="form-control input-lg" name="subcategory_id" id="subcategory_id"> <option value="">First Select Category</option> </select> </div> </div> </div> </div> </div> </div> <script> $('#category_id').on('change', function(e){ var cat_id = e.target.value; //ajax $.get('/api/category-dropdown?category_id=' + cat_id, function(data){ //success data $('#subcategory_id').empty(); $('#subcategory_id').append('<option value=""> Please choose one</option>'); $.each(data, function(index, subcatObj){ $('#subcategory_id').append('<option value="' + subcatObj.id+'">' + subcatObj.name + '</option>'); }); }); }); </script> @endsection
يستقبل المقطع content
التصنيفات في المتغيّر categories
ثم يستخدم دالة foreach
التكراريّة لعرضها في القائمة المنسدلة:
@foreach($categories as $category) <option value="{{ $category->id }}"> {{$category->name}}</option> @endforeach
نضيف في المقطع script
سكربت jQuery الذي سيتولى مزامنة محتوى القائمتيْن.
يأخذ الجزء الأول من السكربت العنصر category_id
الذي يمثّل القائمة الأولى ويستخدم الحدث on change
لمراقبة التغيّرات عليه. نحتفظ بمعرّف التصنيف في المتغيّر cat_id
:
$('#category_id').on('change', function(e){ var cat_id = e.target.value;
ثم يأتي دور نداء Ajax الذي يرسل المتغيّر cat_id
إلى المسار api/category-dropdown/
ضمن المعطى category_id
:
$.get('/api/category-dropdown?category_id=' + cat_id, function(data)
يخزّن النداء النتيجة في المتغيّر data
. عند تلقّي الطلب ننفّذ ثلاثة أمور:
-
حذف محتوى القائمة المنسدلة الثّانية لمحو المحتوى الموجود فيها (المحتوى الأصلي أو المحتوى المرتبط بعنصُر محدد سابقا في القائمة الأولى):
$('#subcategory_id').empty();
ثم نضيف تعليمة جديدة تطلب من الزائر الاختيّار:
$('#subcategory_id').append(' Please choose one');
ثم في الأخير نستخدم دالة
each
التكرارية في jQuery للمرور على البيانات (التصنيفات الفرعيّة) الواحدة تلو الأخرى وإضافتها إلى القائمة المنسدلة الثانيّة:$.each(data, function(index, subcatObj){ $('#subcategory_id').append('' + subcatObj.subcategory_name + '</option'); });
بالنسبة للعرض الرّئيس app.blade.php
فقد استخدمنا هذا الملف.
لم يتبق سوى زيارة الرابط categories/
لمعاينة النتيجة.
ترجمة -وبتصرّف- للمقال Dependent Dropdown List with jquery in Laravel 5.1 لصاحبه Bill Keck.
تم التعديل في بواسطة محمد أحمد العيل
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.