يهدف هذا الدرس إلى بناء تطبيق CRUD (إدراج بيانات في قاعدة بيانات، القراءة منها، تحديثها وحذفها؛ اختصار لـUpdate، Read، Create وDelete). التطبيق عبارة عن واجهة لإدارة لائحة من الموظفين: إضافة موظّف، تحرير بياناته أو حذفها. سنستخدم Laravel 5 للنهاية الخلفية Backend و AngularJS للنهاية الأمامية Frontend. سنستخدم أيضا Bootstrap لإضافة لمسة جمالية إلى التطبيق.
هذا الدرس جزء من سلسلة تعلم Laravel والتي تنتهج مبدأ "أفضل وسيلة للتعلم هي الممارسة"، حيث ستكون ممارستنا عبارة عن إنشاء تطبيق ويب للتسوق مع ميزة سلة المشتريات. يتكون فهرس السلسلة من التالي:
- مدخل إلى Laravel 5.
- تثبيت Laravel وإعداده على كلّ من Windows وUbuntu.
- أساسيات بناء تطبيق باستخدام Laravel.
- إنشاء روابط محسنة لمحركات البحث (SEO) في إطار عمل Laravel.
- نظام Blade للقوالب.
- تهجير قواعد البيانات في Laravel.
- استخدام Eloquent ORM لإدخال البيانات في قاعدة البيانات، تحديثها أو حذفها.
- إنشاء سلة مشتريات في Laravel.
- الاستيثاق في Laravel.
- إنشاء واجهة لبرمجة التطبيقات API في Laravel.
- إنشاء مدوّنة باستخدام Laravel.
- استخدام AngularJS واجهةً أمامية Front end لتطبيق Laravel. (هذا الدرس)
- الدوّال المساعدة المخصّصة في Laravel.
- استخدام مكتبة Faker في تطبيق Laravel لتوليد بيانات وهمية قصدَ الاختبار.
AngularJS هو إطار عمل جافاسكريبت قويّ وفعّال، يتبنى مبدأ النموذج-العرض-المتحكم MVC ويعمل لدى العميل (المتصفّح). للاطلاع على المزيد عن AngularJS تابع سلسلة دروس مدخل إلى تعلم AngularJS .
يفترض هذا الدرس معرفة أساسيات Laravel 5، تثبيت كلّ من PHP، MySQL، ِApache وComposer.
يغطي الدرس المواضيع التالية:
- إنشاء نهاية خلفية في Laravel 5، ستكون عبارة عن واجهة برمجية API.
- بنية تطبيق AngularJS.
واجهة برمجية باستخدام Laravel 5
ننشئ في هذه الفقرة النهاية الخلفية للتطبيق الذي نعمل عليه. سيتولى Laravel هذه المهمة. نبدأ بإنشاء تطبيق Laravel ثم نعدّه لتوفير الواجهة البرمجية.
الخطوة الأولى: إنشاء تطبيق Laravel
ننفذ الأمر التالي في أصل المستند Document root لإنشاء مشروع Laravel باسم angulara
:
composer create-project laravel/laravel angulara
راجع درس تثبيت Laravel 5 وإعداده على Windows وUbuntu للمزيد حول تثبيت Laravel وإعداده. سنفترض في بقية هذا الدرس تثبيت Laravel على المنصة المفضلة لديك وإنشاء مضيف افتراضي باسم angulara.dev
.
الخطوة التالية هي إعداد تهجير قاعدة البيانات.
الخطوة الثانية: إعداد قاعدة البيانات
ستحتاج لقاعدة بيانات للعمل مع التطبيق. استخدم برنامج إدارة قاعدة البيانات المفضل لديك أو نفذ الأمر التالي في سطر أوامر MySQL لإنشاء قاعدة بيانات للمشروع:
CREATE DATABASE `angulara`;
ننتقل الآن لإعداد Laravel لكي يعمل مع قاعدة البيانات. افتح ملف env.
في مجلد تطبيق Laravel وعدل القيم التالية بما يناسب:
DB_HOST=localhost // اسم قاعدة البيانات DB_DATABASE=angulara // اسم مستخدم في MySQL لديه صلاحيات الكتابة والقراءة في القاعدة أعلاه DB_USERNAME=root // كلمة سر المستخدم DB_PASSWORD=melody
احفظ التعديلات.
نستغل أداة Artisan لإنشاء ملف تهجير خاص بجدول يحوي بيانات الموظفين employees
.
نفذ الأمر التالي لإنشاء ملف التهجير:
php artisan make:migration create_employees_table
ستحصُل على الرسالة التالية التي تعرض اسم الملف المنشأ:
Created Migration: 2016_01_05_203637_create_employees_table
ينشئ الأمر ملفا في المجلد database/migrations
، نفتحه للتعديل عليه. عدّل المحتوى كالتالي:
<?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateEmployeesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('employees', function (Blueprint $table) { $table->increments('id'); $table->string('name')->unique(); $table->string('email')->unique(); $table->string('contact_number'); $table->string('position'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('employees'); } }
احفظ التعديلات ثم نفذ أمر ِArtisan التالي:
php artisan migrate
ينفذ الأمر التهجيرات وينشئ جدول بيانات الموظفين. تظهر الرسائل التالية بعد تنفيذ الأمر:
Migration table created successfully. Migrated: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_100000_create_password_resets_table Migrated: 2016_01_05_203637_create_employees_table
تحقق من قاعدة بيانات MySQL، ستجد أن جدولا جديدا أُدرِج في القاعدة.
الخطوة الثالثة: إنشاء واجهة برمجية
ننشئ متحكما Controller للتطبيق، سنسميه Employees
:
php artisan make:controller Employees
ثم يأتي دور النموذج Model:
php artisan make:model Employee
سنضيف مصفوفة في النموذج لتفعيل الإسناد الشامل؛ افتح ملف النموذج وعدّله على النحو التالي:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Employee extends Model { protected $fillable = array('id', 'name', 'email','contact_number','position'); }
احفظ التعديلات.
ننتقل للتعديل على المتحكم. افتح ملف المتحكم Employees
وعدله ليصبح كالتالي
<?php namespace App\Http\Controllers; use App\Employee; use Illuminate\Http\Request; use App\Http\Requests; use App\Http\Controllers\Controller; class Employees extends Controller { /** * Display a listing of the resource. * * @return Response */ public function index($id = null) { if ($id == null) { return Employee::orderBy('id', 'asc')->get(); } else { return $this->show($id); } } /** * Store a newly created resource in storage. * * @param Request $request * @return Response */ public function store(Request $request) { $employee = new Employee; $employee->name = $request->input('name'); $employee->email = $request->input('email'); $employee->contact_number = $request->input('contact_number'); $employee->position = $request->input('position'); $employee->save(); return 'Employee record successfully created with id ' . $employee->id; } /** * Display the specified resource. * * @param int $id * @return Response */ public function show($id) { return Employee::find($id); } /** * Update the specified resource in storage. * * @param Request $request * @param int $id * @return Response */ public function update(Request $request, $id) { $employee = Employee::find($id); $employee->name = $request->input('name'); $employee->email = $request->input('email'); $employee->contact_number = $request->input('contact_number'); $employee->position = $request->input('position'); $employee->save(); return "Sucess updating user #" . $employee->id; } /** * Remove the specified resource from storage. * * @param int $id * @return Response */ public function destroy($id = null) { if ($id == null) { } else { $employee = Employee::find($id); $employee->delete(); return "Employee record successfully deleted #" . $id; } } }
يعرِّف المتحكم الدوالّ index
لسرد قائمة بالموظفين، store
لإضافة موظف جديد، show
لعرض موظف بناء على معرّفه، update
لتحديث بيانات موظّف وdestroy
لحذف موظّف.
تبقى لنا إعداد المسارات Routes في Laravel. افتح ملف المسارات routes.php
وعدله كالتالي:
<?php /* |-------------------------------------------------------------------------- | Application Routes |-------------------------------------------------------------------------- | | Here is where you can register all of the routes for an application. | It's a breeze. Simply tell Laravel the URIs it should respond to | and give it the controller to call when that URI is requested. | */ Route::get('/', function () { return view('index'); }); Route::get('/api/v1/employees/{id?}', 'Employees@index'); Route::post('/api/v1/employees', 'Employees@store'); Route::post('/api/v1/employees/{id}', 'Employees@update'); Route::delete('/api/v1/employees/{id}', 'Employees@destroy');
لاحظ استخدام إجراءات HTTP وفقا للمبدأ المشروح في درس إنشاء واجهة برمجية API في Laravel 5. يختلف التعامل مع المسار api/v1/employees
حسب إجراء HTTP المستخدم: get
يُستخدم للحصول على معلومات موظف أو قائمة بالموظفين، post
لإضافة موظف جديد، put
لتحديث بيانات موظّف وdelete
لحذف موظّف.
أكملنا الآن جانب النهاية الخلفية بإنشاء واجهة برمجية. ننتقل للواجهة الأمامية التي ستستخدم الواجهة البرمجية للحصول على بيانات الموظفين وتنسيقها قبل عرضها في المتصفح.
بنية تطبيق AngularJS
سيأخذ مجلد public
(الواجهة الأمامية لتطبيق Laravel) الشكل التالي:
- يحوي المجلّد الفرعي
app
ملفات جافاسكريبت التابعة لـAngularJS. - توجد متحكمات AngularJS في المجلّد
app/controllers
. - توجد ملفات نواة AngularJS في المجلّد
app/lib
. توجد أيضا إمكانية استخدام شبكة توصيل محتوى Content delivery network, CDN. - توجد ملفات CSS في المجلد
css
. - ملفات جافاسكريبت غير التابعة لـAngularJS توجد في المجلد
js
.
توجد ملفات الواجهة الأمامية في الملف المرفق، سنشرح ما يحتاج لشرح منها.
الملف app.js
سنستخدم الملف public/app/app.js
لتعريف التطبيق الخاص بنا:
var app = angular.module('employeeRecords', []) .constant('API_URL', 'http://angulara.dev/api/v1/');
ننشئ وحدة Module في AngularJS ونسند الكائن إلى المتغير app
. سيُستخدم هذا المتغير في جميع ملفات AngularJS؛ ثم نعرّف ثابتا Constant لرابط الواجهة البرمجية API_URL
.
ملحوظة: إن كنت اخترت اسما مختلفا للمضيف فضعه مكان angulara.dev
.
المتحكم Employees.js
سيكون هذا الملف مسؤولا عن التفاعل مع عروض تطبيق AngularJS:
app.controller('employeesController', function($scope, $http, API_URL) { // الحصول على لائحة الموظفين $http.get(API_URL + "employees") .success(function(response) { $scope.employees = response; }); // عرض نافذة منبثقة $scope.toggle = function(modalstate, id) { $scope.modalstate = modalstate; switch (modalstate) { case 'add': $scope.form_title = "Add New Employee"; break; case 'edit': $scope.form_title = "Employee Detail"; $scope.id = id; $http.get(API_URL + 'employees/' + id) .success(function(response) { console.log(response); $scope.employee = response; }); break; default: break; } console.log(id); $('#myModal').modal('show'); } //save new record / update existing record $scope.save = function(modalstate, id) { var url = API_URL + "employees"; var request_method = 'POST'; //append employee id to the URL if the form is in edit mode if (modalstate === 'edit'){ url += "/" + id; request_method = 'PUT'; } $http({ method: request_method, url: url, data: $.param($scope.employee), headers: {'Content-Type': 'application/x-www-form-urlencoded'} }).success(function(response) { console.log(response); location.reload(); }).error(function(response) { console.log(response); alert('This is embarassing. An error has occured. Please check the log for details'); }); } //delete record $scope.confirmDelete = function(id) { var isConfirmDelete = confirm('Are you sure you want this record?'); if (isConfirmDelete) { $http({ method: 'DELETE', url: API_URL + 'employees/' + id }). success(function(data) { console.log(data); location.reload(); }). error(function(data) { console.log(data); alert('Unable to delete'); }); } else { return false; } } });
نعرِّف متحكما باسم employeesController
في المتغيّر app
الذي أنشأناه في الملف app.js
. حدّدنا الاعتماديات بـ $http
، scope$
وAPI_URL
.
نستدعي خدمة http$
في AngularJS لإرسال طلب إلى الواجهة البرمجية، مع تمرير المعطى API_URL + "employees"
:
$http.get(API_URL + "employees").success(function(response) {$scope.employees = response;});
عند نجاح الطلب نُسنِد الإجابة إلى المتغير scope.employees$
الذي سيصبح متاحا في العرض View لإظهار محتواه.
تعرض الدالة:
$scope.toggle = function(modalstate, id) {…}
نافذة منبثقة تحوي استمارة لإضافة موظّف جديد أو تعديل بيانات موظّف موجود حسب الحالة.
نستخدم الدالة:
$scope.save = function(modalstate, id){…}
في حالتيْ الإضافة والتعديل. بالنسبة لإضافة موظّف نستخدم إجراء POST
؛ وفي حالة التعديل نستخدم الإجراء PUT
.
تحذف الدالة:
$scope.confirmDelete = function(id){…}
بيانات موظّف من جدول الموظفين.
عرض البيانات المتحصل عليها من الواجهة البرمجية بـAngularJS
سننشئ عرضا لإظهار البيانات التي نتحصل عليها من الواجهة البرمجية؛ يستخدم كل من angularJS وBlade الحاضنات المزدوجة {{}}
لعرض البيانات، لذا لن نحفظ عرض AngularJS بلاحقة blade.php
حتى لا يعدّ نظام Blade العرض موجها له.
محتوى العرض هو التالي، نحفظ الملف index.php
في ملف العروض resources/views
حتى يُحمّل عند طلبه من المسار /
(الصفحة الرئيسية للموقع):
<!DOCTYPE html> <html lang="en-US" ng-app="employeeRecords"> <head> <title>Laravel 5 AngularJS CRUD Example</title> <!-- Load Bootstrap CSS --> <link href="<?= asset('css/bootstrap.min.css') ?>" rel="stylesheet"> </head> <body> <h2>Employees Database</h2> <div ng-controller="employeesController"> <!-- Table-to-load-the-data Part --> <table class="table"> <thead> <tr> <th>ID</th> <th>Name</th> <th>Email</th> <th>Contact No</th> <th>Position</th> <th><button id="btn-add" class="btn btn-primary btn-xs" ng-click="toggle('add', 0)">Add New Employee</button></th> </tr> </thead> <tbody> <tr ng-repeat="employee in employees"> <td>{{ employee.id }}</td> <td>{{ employee.name }}</td> <td>{{ employee.email }}</td> <td>{{ employee.contact_number }}</td> <td>{{ employee.position }}</td> <td> <button class="btn btn-default btn-xs btn-detail" ng-click="toggle('edit', employee.id)">Edit</button> <button class="btn btn-danger btn-xs btn-delete" ng-click="confirmDelete(employee.id)">Delete</button> </td> </tr> </tbody> </table> <!-- End of Table-to-load-the-data Part --> <!-- Modal (Pop up when detail button clicked) --> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="myModalLabel">{{form_title}}</h4> </div> <div class="modal-body"> <form name="frmEmployees" class="form-horizontal" novalidate=""> <div class="form-group error"> <label for="inputEmail3" class="col-sm-3 control-label">Name</label> <div class="col-sm-9"> <input type="text" class="form-control has-error" id="name" name="name" placeholder="Fullname" value="{{name}}" ng-model="employee.name" ng-required="true"> <span class="help-inline" ng-show="frmEmployees.name.$invalid && frmEmployees.name.$touched">Name field is required</span> </div> </div> <div class="form-group"> <label for="inputEmail3" class="col-sm-3 control-label">Email</label> <div class="col-sm-9"> <input type="email" class="form-control" id="email" name="email" placeholder="Email Address" value="{{email}}" ng-model="employee.email" ng-required="true"> <span class="help-inline" ng-show="frmEmployees.email.$invalid && frmEmployees.email.$touched">Valid Email field is required</span> </div> </div> <div class="form-group"> <label for="inputEmail3" class="col-sm-3 control-label">Contact Number</label> <div class="col-sm-9"> <input type="text" class="form-control" id="contact_number" name="contact_number" placeholder="Contact Number" value="{{contact_number}}" ng-model="employee.contact_number" ng-required="true"> <span class="help-inline" ng-show="frmEmployees.contact_number.$invalid && frmEmployees.contact_number.$touched">Contact number field is required</span> </div> </div> <div class="form-group"> <label for="inputEmail3" class="col-sm-3 control-label">Position</label> <div class="col-sm-9"> <input type="text" class="form-control" id="position" name="position" placeholder="Position" value="{{position}}" ng-model="employee.position" ng-required="true"> <span class="help-inline" ng-show="frmEmployees.position.$invalid && frmEmployees.position.$touched">Position field is required</span> </div> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" id="btn-save" ng-click="save(modalstate, id)" ng-disabled="frmEmployees.$invalid">Save changes</button> </div> </div> </div> </div> </div> <!-- Load Javascript Libraries (AngularJS, JQuery, Bootstrap) --> <script src="<?= asset('app/lib/angular/angular.min.js') ?>"></script> <script src="<?= asset('js/jquery.min.js') ?>"></script> <script src="<?= asset('js/bootstrap.min.js') ?>"></script> <!-- AngularJS Application Scripts --> <script src="<?= asset('app/app.js') ?>"></script> <script src="<?= asset('app/controllers/employees.js') ?>"></script> </body> </html>
نربط تطبيق AngularJS بوسم html
حتى يكون له كامل التحكّم في العناصر الموجودة في الصفحة:
<html lang="en-US" ng-app="employeeRecords">
نفس الشيء بالنسبة للمتحكم employeesController
الذي نربطه بعنصر div
حتى نجعل جميع دوال المتحكم متاحة في هذا العنصر:
<div ng-controller="employeesController">
نستخدم تعليمة ng-repeat
للمرو على جميع عناصر المجموعة employees
:
<tr ng-repeat="employee in employees">
تعمل ng-repeat
بطريقة مشابهة لعمل الحلقة التكرارية foreach
.
التحقق من بيانات استمارات AngularJS
استخدمنا في استمارات الموظفين طرقا للتحقق من المُدخلات. اعثر على الشفرات التالية في ملف العرض index.php
ولاحظ استخدامها:
<form name="frmEmployees" class="form-horizontal" novalidate="">
تعرّف هذه الشفرة استمارة باسم frmEmployees
وتضيف إليها خاصيّة novalidate
للطلب من HTML 5 الامتناع عن تدقيق الاستمارة، سنتولى الأمر.
<input type… ng-model="employee.name" ng-required="true">
تستخدم تعليمة ng-model
لربط حقول الاستمارة ببيانات النموذج Employee
. ربطنا الحقل أعلاه بخاصية name
في النموذج. بهذه الطريقة يكون محتوى الحقل متاحا للنموذج، والعكس أيضا. تدل التعليمة ng-required
أن الحقل مطلوب. إن لم تذكر قيمة لهذا الحقل فلن يمكن إرسال الاستمارة.
<span class="help-inline" ng-show="frmEmployees.name.$invalid && frmEmployees.name.$touched">
لا يظهر هذا العنصُر إلا إذا أخفق التحقق من حقل name
قبله. تظهر رسالة تفيد أن قيمة الحقل مطلوبة.
ng-disabled="frmEmployees.$invalid"
تعطّل هذه التعليمة إمكانية إرسال الاستمارة عند إخفاق التحقق من أحد الحقول المطلوبة.
حان الآن وقت تجربة ما عملناه في الخطوات السابقة، افتح الموقع (بالنسبة لي اخترتُ إنشاء مضيف افتراضي angulara.dev
):
http://angulara.dev/
ستحصُل، إن اتبعت الخطوات السابقة، على الواجهة التالية:
اضغط على زر Add New Employee
لإضافة موظّف. ستظهر نافذة منبثقة لإضافة بيانات الموظّف.
تصبح الواجهة بعد إضافة موظفين على النحو التالي:
تمكّن الأزرار edit
و delete
الموجودة في كل سطر تحديثَ أو حذف بيانات موظّف.
الملف المرفق: مجلد public.
ترجمة -وبتصرّف- لمقال Laravel 5 AngularJS Tutorial لصاحبه Rodrick Kazembe.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.