استخدام AngularJS لبناء واجهة أمامية Frontend لتطبيق Laravel 5


محمد أحمد العيل

يهدف هذا الدرس إلى بناء تطبيق CRUD (إدراج بيانات في قاعدة بيانات، القراءة منها، تحديثها وحذفها؛ اختصار لـUpdate، Read، Create وDelete). التطبيق عبارة عن واجهة لإدارة لائحة من الموظفين: إضافة موظّف، تحرير بياناته أو حذفها. سنستخدم Laravel 5 للنهاية الخلفية Backend و AngularJS للنهاية الأمامية Frontend. سنستخدم أيضا Bootstrap لإضافة لمسة جمالية إلى التطبيق.

هذا الدرس جزء من سلسلة تعلم Laravel والتي تنتهج مبدأ "أفضل وسيلة للتعلم هي الممارسة"، حيث ستكون ممارستنا عبارة عن إنشاء تطبيق ويب للتسوق مع ميزة سلة المشتريات. يتكون فهرس السلسلة من التالي:

laravel5-angularjs-frontend.thumb.png.1e

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) الشكل التالي:

01_laravel_angular.thumb.png.79e4a8beb3e

  • يحوي المجلّد الفرعي 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/

ستحصُل، إن اتبعت الخطوات السابقة، على الواجهة التالية:

02_laravel_angular.thumb.png.08f8db0c1bb

اضغط على زر Add New Employee لإضافة موظّف. ستظهر نافذة منبثقة لإضافة بيانات الموظّف.

تصبح الواجهة بعد إضافة موظفين على النحو التالي:

03_laravel_angular.thumb.png.ce2e3e1cbe0

تمكّن الأزرار edit و delete الموجودة في كل سطر تحديثَ أو حذف بيانات موظّف.

الملف المرفق:  مجلد public.

ترجمة -وبتصرّف- لمقال Laravel 5 AngularJS Tutorial لصاحبه Rodrick Kazembe.



1 شخص أعجب بهذا


تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن