المحتوى عن 'angularjs'.



مزيد من الخيارات

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المُحتوى


التصنيفات

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

التصنيفات

  • PHP
    • Laravel
    • ووردبريس
  • جافاسكريبت
    • Node.js
    • jQuery
    • AngularJS
    • Cordova
  • HTML
    • HTML5
  • CSS
  • SQL
  • سي شارب #C
    • منصة Xamarin
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • Sass
    • إطار عمل Bootstrap
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • برمجة أندرويد
  • لغة Swift
  • لغة R
  • لغة TypeScript
  • ASP.NET
    • ASP.NET Core
  • سير العمل
    • Git
  • صناعة الألعاب
    • Unity3D
  • مقالات برمجة عامة

التصنيفات

  • تجربة المستخدم
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
    • كوريل درو
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
    • Blender
  • نصائح وإرشادات
  • مقالات تصميم عامة

التصنيفات

  • خواديم
    • الويب HTTP
    • قواعد البيانات
    • البريد الإلكتروني
    • DNS
    • Samba
  • الحوسبة السّحابية
    • Docker
  • إدارة الإعدادات والنّشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH
  • مقالات DevOps عامة

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • التسويق بالرسائل النصية القصيرة
  • استسراع النمو
  • المبيعات
  • تجارب ونصائح

التصنيفات

  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • مقالات عمل حر عامة

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجنتو
  • أندرويد
  • iOS
  • macOS
  • ويندوز

التصنيفات

  • شهادات سيسكو
    • CCNA
  • شهادات مايكروسوفت
  • شهادات Amazon Web Services
  • شهادات ريدهات
    • RHCSA
  • شهادات CompTIA
  • مقالات عامة

أسئلة وأجوبة

  • الأقسام
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة البرمجة
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات
    • أسئلة الشهادات المتخصصة

التصنيفات

  • ريادة الأعمال
  • العمل الحر
  • التسويق والمبيعات
  • البرمجة
  • التصميم
  • DevOps

تمّ العثور على 2 نتائج

  1. ما هو AngularJS؟AngularJS هو إطار عمل JavaScript لطرف العميل يتبع بنية Model-View-Controller/Model-View-View-Model، ويعتبر اليوم مُهمًا لبناء تطبيقات ويب وحيدة الصفحة (SAP) أو حتى المواقع العادية. يُعتبر Angular JS قفزة كبيرة نحو مستقبل HTML وما يجلبه الإصدار الخامس منها (مع التطورات على صعيد JavaScript بالطبع!)، ويبعث الحياة من جديد في تعاملنا مع الويب الحديث. هذا المقال جولة شاملة في Angular JS مستخلصة من تجاربي ونصائح وممارسات تعلمتها خلال استخدامي. المُصطلحاتليس عليك أن تبذل جهداً كبيراً لتعلم Angular، وأهم ما يجب معرفته هو معاني المُصطلحات وتبني نمط MVC، وMVC هو اختصار لـModel-View-Controller، أي نموذج-طريقة عرض-مُتحكِّم. فيما يلي بعض المصطلحات والواجهات البرمجية الأساسية التي تزوّدنا بها Angular. MVCربما سمعت بهذا الاختصار من قبل، وهو مستخدم في لغات برمجة عديدة كوسيلة لبناء هيكل التطبيقات أو البرامج. وهاك تلخيص سريع لمعناه: النموذج (Model): بنية البيانات التي تقوم عليها أجزاء التطبيقات، غالبًا ما تُمثل بصيغة JSON. يفضل أن تكون لديك معرفة مسبقة بـJSON قبل تعلم Angular، لأنها ضرورية للتواصل بين الخادوم وطريقة العرض (سنأتي على شرحها في النقطة التالية). على سبيل المثال، لنفترض أنه لدينا مجموعة من المستخدمين، يمكن تمثيل بيانات تعريفهم كما يلي:{ "users" : [{ "name": "أحمد", "id": "82047392" },{ "name": "سامر", "id": "65198013" }] }عادة ما تُجلب هذه المعلومات من خادوم بطلب XMLHttpRequest، ويقابله في jQuery الإجراء $.ajax، وفي Angular الكائن $http. وقد تكون هذه المعلومات مكتوبة ضمن النص البرمجي أثناء تفسير الصفحة (من قاعدة بيانات أو مخزن بيانات). بعد ذلك يكون بإمكانك تعديل هذه المعلومات وإعادة إرسالها. طريقة العرض (View): وهو أمر سهل التفسير، فهي ببساطة المُخرج النهائي أو صفحة HTML التي تعرض البيانات (النموذج) على المستخدم مثلاً. باستخدام إطار عمل MVC، تُجلب البيانات من النموذج وتُعرض المعلومات المناسبة في صفحة HTML.المُتحكِّم (Controller): وله من اسمه نصيب، فهو يتحكم بالأشياء، ولكن أية أشياء؟ البيانات. المُتحكمات هي الطريقة التي تصل من خلالها بين الخادوم وطريقة العرض، فتسمح بتحديث البيانات سريعًا من خلال التواصل مع كلا الخادوم والعميل.إعداد مشروع Angular JS (الأساسيات)يجب أولاً تهيئة الأساسيات التي يقوم عليها مشروعنا. يجب أن نبدأ بتطبيق (ng-app) الذي يُعرِّف التطبيق (وng هي بادئة تعني Angular وتسبق عادة كل مكونات Angular JS)، ثم متحكم (Controller) ليتواصل مع طريقة العرض، ثم ربط DOM ولا ننسى تضمين Angular بالطبع. إليك الأساسيات: نص HTML مع تصريحات ng-*:<div ng-app="myApp"> <div ng-controller="MainCtrl"> <!-- محتويات المتحكم --> </div> </div>وحدة Angular مع متحكم:var myApp = angular.module('myApp', []); myApp.controller('MainCtrl', ['$scope', function ($scope) { // أوامر المتحكم }]);قبل أن نستبق الأمور، نحتاج لإنشاء وحدة Angular (أو Angular module)، التي ستتضمن كل النص البرمجي المُتعلق بالمشروع. هناك أكثر من طريقة للتصريح عن الوحدات، إحداها سَلسَلة كل النص البرمجي معًا (لا أفضل هذه الطريقة): angular.module('myApp', []) .controller('MainCtrl', ['$scope', function ($scope) {...}]) .controller('NavCtrl', ['$scope', function ($scope) {...}]) .controller('UserCtrl', ['$scope', function ($scope) {...}]);ولكن الطريقة التي أفضلها، والتي أثبتت أنها الأفضل لكل مشاريع Angular التي صمّمتها هي تعريف الوحدة العامة بشكل منفصل. الطريقة التي تعتمد على تسلسل التصريحات قد تجعلك تنسى إغلاق بعض الأقواس وتجعل قراءة النص البرمجي وتصحيحه أكثر تعقيدًا. لذا أُفضّل هذا الأسلوب: var myApp = angular.module('myApp', []); myApp.controller('MainCtrl', ['$scope', function ($scope) {...}]); myApp.controller('NavCtrl', ['$scope', function ($scope) {...}]); myApp.controller('UserCtrl', ['$scope', function ($scope) {...}]);بهذه الطريقة أُقسّم النص البرمجي على عدة ملفات، وفي كل ملف أربط مكوّناً من المكونات مع فضاء الأسماء myApp فيصبح تلقائيًا جزءًا من تطبيقي. نعم، الأمر كما فهمت، أفضل أن أنشئ ملفًا مستقلًا لكل متحكم ومُرشِد (directive) ومعمل (factory) وأي شيء آخر (ستشكرني على هذا). فيما بعد يمكنك دمجها معًا وتقليصها لتصبح ملفًا واحدًا (مستخدمًا مُدير مهام مثل Grunt أو Gulp) فتدفعَه إلى DOM. المُتحكّمات (Controllers)أصبحت تعرف الآن مفهوم MVC وتعلمت طريقة إعداد مشروع جديد، فلنطّلع الآن على الكيفية التي يُطبِّق فيها Angular JS العمل بالمتحكّمات. بناء على المثال السابق، بإمكاننا الآن أن نخطو خطوة بسيطة نحو عرض بعض البيانات ضمن طريقة العرض مستخدمين مُتحكّمًا. يستخدم Angular تركيب " handlebars" لقولبة HTML. ببساطة يعني هذا أنه بإمكان المتحكمات أن تعرض البيانات في صفحة HTML بأن تستبدل كل عبارة فيها مكتوبة ضمن الأقواس المزدوجة هكذا: {{ data }} قيمة يُعيّنها المُتحكم. في الحالة المثالية يجب أن لا تحوي صفحة HTML نصًا حقيقيًا أو قيمًا مُدرجة مسبقًا، ويجب أن تترك هذه المهمة لمتحكمات Angular. فيما يلي مثال يُبيّن كيف يمكن عرض نص أو سلسلة حروف String بسيطة ضمن الصفحة: <div ng-app="myApp"> <div ng-controller="MainCtrl"> {{ text }} </div> </div>في ملف JavaScript: var myApp = angular.module('myApp', []); myApp.controller('MainCtrl', ['$scope', function ($scope) { $scope.text = 'مرحباً بمعجبي Angular!'; }]);والناتج النهائي: رابط المثال أهم مفهوم هنا هو مفهوم النطاق ($scope) والذي ستربطه بكل الوظائف ضمن متُحكّم مُعيّن. يُشير $scope إلى العُنصر أو المنطقة الحالية في DOM (فهو لا يُساوي this ضمن النص البرمجي) وبهذا يخلق نطاقًا يُحيط بكل البيانات والوظائف ضمن العناصر (DOM elements)، ويعزلها عن العناصر الأخرى، فيبدو وكأنه ينقل مجالات JavaScript العامة/الخاصة إلى DOM، وهذا شيء رائع. قد يبدو مفهوم النطاق مخيفًا للوهلة الأولى، لكنه طريقك الواصل بين الخادوم (أو حتى البيانات المحلية) من جهة وDOM من الجهة الأخرى. يعطيك هذا المثال فكرة عن الطريقة التي "تُدفع" بها البيانات إلى DOM. لنٌلقِ نظرة على بيانات حقيقية نفترض أننا جلبناها من خادوم لنعرض تفاصيل تسجيل دخول المستخدم، سنكتفي في هذه المرحلة باستخدام بيانات جاهزة، وسنتعلم كيفية جلبها من الخادوم على هيئة JSON لاحقًا. أولاً سنكتب شفرة JavaScript: var myApp = angular.module('myApp', []); myApp.controller('UserCtrl', ['$scope', function ($scope) { // لنجعل معلومات المستخدم ضمن عنصر فرعي $scope.user = {}; $scope.user.details = { "username": "Todd Motto", "id": "89101112" }; }]);ثم ننتقل إلى DOM لعرض هذه البيانات <div ng-app="myApp"> <div ng-controller="UserCtrl"> <p class="username">Welcome, {{ user.details.username }}</p> <p class="id">User ID: {{ user.details.id }}</p> </div> </div>النتيجة من المهمّ أن تتذكر أن المتحكمات تستخدم فقط للبيانات ولإنشاء وظائف تتواصل مع الخادوم وتجلب أو ترسل بيانات JSON. لا تستخدم المتحكمات لمعالجة DOM (كأن تنقل عنصرًا ضمن الصفحة أو تخفيه أو تظهره...)، فمعالجة DOM مهمة المُرشِدات (directives)، وهي ما سنشرحه لاحقًا، المهم أن تتذكر أن موضع jQuery وغيرها من المكتبات التي تتعامل مع DOM ليس ضمن المتحكّمات. تنويه: خلال اطلاعك على وثائق Angular الرسمية، ستلاحظ أن الأمثلة المقدمة تعتمد الأسلوب التالي لإنشاء المتحكمات: var myApp = angular.module('myApp', []); function MainCtrl ($scope) { //... };لا تفعل ذلك. هذا سيجعل كل الوظائف المُصرّحة تابعةً للنطاق العامّ (global scope) ولا يربطها بشكل جيد مع التطبيق. هذا يعني كذلك أن عمليات التقليص للنص البرمجي والتجارب ستكون أكثر صعوبة. لا تلوّث فضاء الأسماء العام، بل اجعل المتحكمات ضمن التطبيق. المُرشِدات (Directives)الُمرشد في أبسط صوره هو نص HTML مٌقولَب، يفضل أن يكون استخدامه متكررًا ضمن التطبيق. توفر المرشدات طريقة سهلة لإدخال أجزاء DOM ضمن التطبيق دون عناء. تعلم استخدام المرشدات ليس أمرًا سهلًا على الإطلاق، وإتقانها يتطلب جهدًا، ولكن الفقرات التالية ستضعك على الطريق الصحيح. إذن، ما فائدة المُرشدات؟ إنها مفيدة في عدة أمور، منها إنشاء عناصر DOM، مثل علامات التبويب (tabs) وقوائم التصفح - ففائدتها تعتمد على ما يفعله تطبيقك في الواجهة. لتسهيل الشرح، سأقول ببساطة: إن كنت استعملت ng-show وng-hide من قبل، فقد استعملت المرشدات (حتى وإن كان هذان لا يُدرجان أية عناصر DOM). على سبيل التمرين، سنُنشئ نوعًا خاصًّا من الأزرار ونُسميه customButton، يُدرج هذا العنصر بعض العناصر الفرعية التي لا نريد كتابتها في كل مرة. تتنوع طرق التصريح عن المرشدات في DOM، وهي مبينة في النص البرمجي التالي: <!-- 1: تصريح عن مُرشد كخاصّة (attribute) --> <a custom-button>انقرني</a> <!-- 2: كعنصر مخصص (custom elements) --> <custom-button>انقرني</custom-button> <!-- 3: كصنف (class) (للتوافق مع النسخ القديمة من IE) --> <a class="custom-button">انقرني</a> <!-- 4: كتعليق (comment) (ليس ملائماً لهذا التمرين) --> <!-- directive: custom-button --> أفضّل استخدام المرشدات كخواصّ (attributes)، أما العناصر المُخصصة (custom elements) فقادمة في النسخ المستقبلية من HTML باسم Web Components، يوفر Angular ما يشبهها، ولكنها قد تنطوي على بعض العيوب والعلل في المتصفحات القديمة. الآن نعرف كيف نصرح عن المرشدات ضمن الصفحة، سننتقل إلى إنشائها ضمن JavaScript. لاحظ أنني سأربطه مع فضاء الأسماء العام myApp؛ في صيغته الأبسط يُكتب المرشد كما يلي: myApp.directive('customButton', function () { return { link: function (scope, element, attrs) { // هنا اكتب التعليمات التي تعالج DOM أو تتعامل مع أحداثه } }; });عرّفنا المرشد باستخدام الدالة .directive()، ومررنا إليها اسم المرشد 'customButton'. عندما تكتب حرفًا كبيرًا بالإنجليزية في اسم المُرشد، فإنه ينبغي استخدام اسم المرشد ضمن DOM بصيغته التي يُفصل بها باستخدام الشرطة (-) بين الحروف الكبيرة (كما في المثال السابق: استخدمنا 'customElement' في JavaScript و"custom-button" في HTML). يُرجع المُرشد كائنًا (Object) له عدد من الخصائص. أهم ما يجب تعلّمه منها: restrict وreplace وtransclude وtemplate وtemplateUrl وأخيرًا link. لنُضف بعضها إلى شفرتنا البرمجية: myApp.directive('customButton', function () { return { restrict: 'A', replace: true, transclude: true, template: '<a href="" class="myawesomebutton" ng-transclude>' + '<i class="icon-ok-sign"></i>' + '</a>', link: function (scope, element, attrs) { // هنا اكتب التعليمات التي تعالج DOM أو تتعامل مع أحداثه } }; });النتيجة تأكد من فحص العنصر (من الأمر Inspect element في المُتصفح) لرؤية العناصر الجديدة التي أُدخلت في الصفحة. اعلم أن الرمز لم يظهر ضمن العنصر الجديد، ببساطة لأنني لم أُضمّن Font Awesome ضمن المشروع، ولكن يمكنك فهم كيف تعمل المرشدات. لنتعرف الآن ما تعنيه كل واحدة من خصائص المرشد السابقة الذكر: الخاصية restrict: تُقيّد هذه الخاصة كيفية استخدام المُرشد، كيف نريد أن نستخدمه؟ إن كنت تبني مشروعًا يتطلب دعم النسخ القديمة من IE، فعليك استخدامه كخاصّة (attribute) أو صنف (class). القيمة 'A' تعني حصر استخدام المرشد بالخواص (attributes) فقط. 'E' تعني Element و'C' صنف و'M' تعليق. القيمة الافتراضية هي 'EA' (أي عنصر وخاصة). الخاصية replace: تعني استبدال HTML العنصر المصرّح عن المُرشد ضمن الصفحة بالقالب (template) الذي يُحدد في الخاصة template (مشروحة أدناه). الخاصةtransclude: تسمح بنسخ المحتوى الأصلي للعنصر المُصرّح عن المُرشد في الصفحة ودمجه ضمن المرشد (عند التنفيذ، ستلاحظ أن العبارة "انقرني" انتقلت إلى المُرشد). الخاصية template: قالب (كذلك المستخدم في المثال) يُدخل إلى الصفحة. يفضّل استخدام القوالب الصغيرة فقط. تُعالج القوالب وتبنى من قبل Angular مما يسمح باستخدام صيغة handlebars ضمنها. الخاصة templateUrl: مشابهة للسابقة، ولكنها تُجلب من ملف أو من وسم <script> بدل كتابتها ضمن تعريف المُرشد. كل ما عليك هو تعيين مسار الملف الذي يحوي القالب. يكون هذا الخيار مناسبًا عندما تريد الاحتفاظ بالقوالب خارج النص البرمجي لملفات JavaScript: myApp.directive('customButton', function () { return { templateUrl: 'templates/customButton.html' // directive stuff... });وضمن الملف، نكتب: <!-- inside customButton.html --> <a href="" class="myawesomebutton" ng-transclude> <i class="icon-ok-sign"></i> </a>ملاحظة: لا يخضع اسم الملف إلى أية قاعدة، وليس من الضروري أن يوافق اسمَ المُرشد. عند استخدام الأسلوب السابق، سيحتفظ المتصفح بنسخة مُخبأة (cached) من ملف HTML، وهو أمر رائع، الخيار البديل هو استخدام قالب ضمن وسم <script> وهنا لا تُخبأ نسخة منه في المتصفح: <script type="text/ng-template" id="customButton.html"> <a href="" class="myawesomebutton" ng-transclude> <i class="icon-ok-sign"></i> </a> </script>هنا أخبرنا Angular بأن وسم <script> هذا هو قالب (ng-template) وأعطيناه المُعرف. سيبحث Angular عن القالب أو عن ملف html، فاستخدم ما تراه مناسبًا. شخصيًا، أفضّل إنشاء ملفات html لسهولة تنظيمها ولتحسين الأداء وإبقاء DOM نظيفًا، فقد يستخدم مشروعك مع الوقت عشرات المُرشدات، وترتيبها في ملفات مستقلة يجعل مراجعتها أسهل. الخدمات (Services)كثيرًا ما تثير الخدمات في Angular ارتباك المطورين، ومن خبرتي وأبحاثي، أعتقد أن الخدمات وُضعت كنمط وأسلوب للتصميم أكثر من اختلافها بالوظيفة التي تؤديها. بعد التنقيب في مصدر Angular، وجدت أنها تُعالج وتبنى باستخدام المُجمّع (compiler) ذاته، وكذلك فهي تقدم العديد من الوظائف المشابهة. أنصح باستخدام الخدمات لبناء الكائنات المُتفرِّدة (singletons)، واستخدام المعامل (Factories) لبناء وظائف أكثر تعقيدًا كالكائنات الحرفيّة (Object Literals). فيما يلي مثال لاستخدام خدمة توجد ناتج الضرب لعددين: myApp.service('Math', function () { this.multiply = function (x, y) { return x * y; }; });يمكنك بعد هذا استخدامها ضمن مُتحكم كما يلي: myApp.controller('MainCtrl', ['$scope', function ($scope) { var a = 12; var b = 24; // الناتج: 288 var result = Math.multiply(a, b); }]);نعم بالطبع إيجاد ناتج الضرب سهل ولا يحتاج خدمة، لكننا نستخدمه لإيصال الفكرة فحسب. عندما ننشئ خدمة (أو معملاً) نحتاج إلى إخبار Angular عن متطلبات هذه الخدمة، وهو ما يسمى "حقن المتطلبات Dependency Injection" - إن لم تُصرّح عن المتطلبات فلن يعمل المتحكم المعتمد على الخدمة، وسيحدث خطأ عند التجميع. ربما لاحظت الجزء function ($scope) ضمن التصريح عن المتحكم أعلاه، وهذا هو ببساطة حقن المتطلبات. ستُلاحظ أيضًا [$scope] قبل الجزء function ($scope)، وهو ما سأشرحه لاحقًا. فيما يلي طريقة استخدام حقن المتطلبات لإخبار Angular أنك تحتاج إلى الخدمة التي أنشأتها للتو: // مرر الخدمة Math myApp.controller('MainCtrl', ['$scope', 'Math', function ($scope, Math) { var a = 12; var b = 24; // يُعطي 288 var result = Math.multiply(a, b); }]);المعامل (Factories)إيضاح فكرة المعامل سهل إذا كنت قد استوعبت فكرة الخدمات، بإمكاننا إنشاء كائن حرفي (Object Literal) ضمن المعمل أو عبر طرق أكثر تعقيدًا: function ($http) { return { get: function(url) { return $http.get(url); }, post: function(url) { return $http.post(url); }, }; }]);هنا أنشأنا مُغلفات (wrappers) مخصصة لخدمة $http في Angular المسؤولة عن طلبات XHR. بعد حقن المتطلبات ضمن المتحكم يمكننا استخدام هذا المعمل بسهولة: myApp.controller('MainCtrl', ['$scope', 'Server', function ($scope, Server) { var jsonGet = 'http://myserver/getURL'; var jsonPost = 'http://myserver/postURL'; Server.get(jsonGet); Server.post(jsonPost); }]);إذا أرت طلب التحديثات من الخادوم، بإمكانك إنشاء دالة Server.poll أو إن كنت تستخدم مقبسًا (ٍSocket)، فربما سترغب في إنشاء دالة Server.socket وهكذا... المعامل تسمح لك بتنظيم نصك البرمجي ضمن وحدات يمكن إدراجها ضمن المتحكمات منعًا لتكرار النص البرمجي فيها والحاجة المتكررة لصيانته. المُرشّحاتتستخدم المرشحات مع مصفوفات (arrays) من البيانات وخارج الحلقات (loops). إن احتجت للمرور على عناصر من مصفوفة بيانات والحصول على بعض منها فقط، فأنت في المكان الصحيح، يمكنك أيضًا استخدام المرشحات لتصفية ما يكتبه المستخدم ضمن حقل إدخال <input> مثلًا. هناك عدة طرق لاستخدام المُرشحات: ضمن متحكم أو دالة مُعرفة. فيما يلي الطريقة الأخيرة: myApp.filter('reverse', function () { return function (input, uppercase) { var out = ''; for (var i = 0; i < input.length; i++) { out = input.charAt(i) + out; } if (uppercase) { out = out.toUpperCase(); } return out; } }); // Controller included to supply data myApp.controller('MainCtrl', ['$scope', function ($scope) { $scope.greeting = 'Todd Motto'; }]);وفي HTML: <div ng-app="myApp"> <div ng-controller="MainCtrl"> <p>No filter: {{ greeting }}</p> <p>Reverse: {{ greeting | reverse }}</p> </div> </div>رابط المثال وهنا نستخدم المُرشح ضمن حلقة ng-repeat: <ul> <li ng-repeat="number in myNumbers |filter:oddNumbers">{{ number }}</li> </ul>مثال عن مُرشح ضمن متحكم: myApp.controller('MainCtrl', ['$scope', function ($scope) { $scope.numbers = [10, 25, 35, 45, 60, 80, 100]; $scope.lowerBound = 42; // Does the Filters $scope.greaterThanNum = function (item) { return item > $scope.lowerBound; }; }]);واستخدامه حلقة ng-repeat: <li ng-repeat="number in numbers | filter:greaterThanNum"> {{ number }} </li>رابط المثال كان هذا القسم الأكبر مما تحتاج لمعرفته عن AngularJS وواجهاتها البرمجية، ومع أن ما تعلمناه كافٍ لبناء تطبيق Angular، ولكننا إلى الآن لم نسكتشف أغوراها بعد. ربط البيانات ثنائي الاتجاهعندما سمعت للمرة الأولى عن ربط البيانات ثنائي الاتجاه لم أفهم ما يعنيه. باختصار يمكن القول إنه حلقة متصلة من البيانات المُزامنة: حدّث النموذج (Model) لتُحدَّث طريقة العرض (View)، أو حدّث طريقة العرض ليُحدَّث النموذج (Model). هذا يعني أن البيانات تبقى محدثة دومًا دون عناء. إن ربطت نموذج ng-model مع حقل إدخال <input> وكتبت فيه، فهذا يُنشئ (أو يُحدِّث) نموذجاً في الوقت ذاته. فيما يلي نقوم بإنشاء حقل <input> ونربطه بنموذج نسميه myModel، يمكنني الآن استخدام صياغة handlebars لعكس هذا النموذج وما يطرأ عليه من تحديثات في طريقة العرض في الوقت ذاته: <div ng-app="myApp"> <div ng-controller="MainCtrl"> <input type="text" ng-model="myModel" placeholder="Start typing..." /> <p>My model data: {{ myModel }}</p> </div> </div> myApp.controller('MainCtrl', ['$scope', function ($scope) { // Capture the model data // and/or initialise it with an existing string $scope.myModel = ''; }]); النتيجة طلبات XHR/Ajax/$http وربط JSONنعرف الآن كيف نرسل بيانات بسيطة ضمن المجال ($scope)، ونعرف ما يكفي عن كيفية عمل النماذج وربط البيانات ثنائي الجانب، والآن حان الوقت لمحاكاة طلبات XHR حقيقية للخادوم. ليس هذا ضروريًا لمواقع الويب العادية، لكنه مناسب جدًا لجلب البيانات في تطبيقات الويب. عندما تطور تطبيقك على جهازك المحلي، فغالبًا ما ستستخدم لغة مثل Java أو ASP.NET أو PHP أو غيرها لتشغيل خادوم محلي. وسواء كنا نتصل بقاعدة بيانات محلية أم بخادوم بعيد كواجهة برمجية، فإننا نتبع نفس الخطوات بالضبط. هنا يأتي دور $http، صديقك المخلص من اليوم فصاعدًا. الدالة $http هي مُغلّف wrapper تقدمه Angular للوصول إلى البيانات من الخادوم، وهو سهل الاستخدام للغاية ولا يحتاج لأية خبرة. فيما يلي مثال عن طلب GET لجلب البيانات من الخادوم. الصياغة مشابهة جدًا لصياغة jQuery، وهذا يُسهل الانتقال من الأخيرة إلى Angular: myApp.controller('MainCtrl', ['$scope', '$http', function ($scope, $http) { $http({ method: 'GET', url: '//localhost:9000/someUrl' }); }]);يُعيد Angular إلينا أمرًا يُصطلح على تسميته الوعد (Promise) ، وهو بديل أسهل استخدامًا من الاستدعاءات الراجعة (callbacks). يمكن تركيب الوعود في سلسلة باستخدام النقطة، ويمكننا ربطها مع مستقبلات النجاح والفشل: myApp.controller('MainCtrl', ['$scope', function ($scope) { $http({ method: 'GET', url: '//localhost:9000/someUrl' }) .success(function (data, status, headers, config) { // successful data retrieval }) .error(function (data, status, headers, config) { // something went wrong :( }); }]);سهلة الاستخدام والقراءة. هنا نربط طريقة العرض والخادوم بربط نموذج أو تحديثه. لنفترض أنه لدينا خادومًا مُعدًّا ولنقم بدفع اسم المستخدم إلى طريقة العرض عن طريق طلب AJAX. علينا --لو كنا حريصين على المثالية-- أن نصمم بيانات JSON التي نريدها أولاً. دعونا الآن نُبسط الأمور، ولندع هذا الأمر ليتولاه من يفهم في أمور النهاية الخلفية (backend)، ولكن لنقل أننا تفترض أن نستقبل بيانات مثل هذه: { "user": { "name": "Todd Motto", "id": "80138731" } }هذا يعني أننا سنحصل على كائن Object من الخادوم (سنسميه data، وسترى أنه يُمرر إلى مستقبلات الوعد الذي أنشأناه). علينا الآن أن نربط هذا الكائن بالخاصة data.user، وضمنها لدينا name وid. يمكن ببساطة الوصول إلى هذه القيم باستخدام data.user.name للحصول على "Todd Motto" مثلاً. فيما يلي الشفرة البرمجية (اطلع على التعليقات المضمنة): myApp.controller('UserCtrl', ['$scope', '$http', function ($scope, $http) { // create a user Object $scope.user = {}; // Initiate a model as an empty string $scope.user.username = ''; // نريد أن نرسل طلباً ونحصل على اسم المستخدم $http({ method: 'GET', url: '//localhost:9000/someUrlForGettingUsername' }) .success(function (data, status, headers, config) { // عند نجاح الطلب، نُسنِد الاسم إلى النموذج الذي أنشأناه $scope.user.username = data.user.name; }) .error(function (data, status, headers, config) { // وقع خطأ ما! :( }); }]);الآن ضمن الصفحة يمكننا ببساطة كتابة: <div ng-controller="UserCtrl"> <p>{{ user.username }}</p> </div>هذا سيعرض اسم المستخدم. لننتقل الآن إلى خطوة أبعد بفهم ربط البيانات التصريحي (Declarative data-binding) حيث تصبح الأمور أكثر إثارة. ربط البيانات التصريحيتقوم فلسفة Angular على إنشاء نصوص HTML ديناميكية قادرة على القيام بوظائف بنفسها لم نكن نتوقع أنها ممكنة ضمن المتصفح. هذه هي المهمة التي تقوم بها Angualar على خير وجه. لنتخيل أننا أرسلنا طلب AJAX لجلب قائمة بعناوين البريد الإلكتروني وسطر الموضوع فيها مع تاريخ إرسالها، ولنفترض أننا نريد عرضها ضمن الصفحة. في هذا المكان بالضبط تذهلنا Angular بقدراتها. لننشئ أولاً متحكماً بالبريد الإلكتروني: yApp.controller('EmailsCtrl', ['$scope', function ($scope) { // نُنشئ كائناً يدعاً `emails` $scope.emails = {}; // لنفترض أننا حصلنا على هذه البيانات من الخادوم // هذه **مصفوفة** من **الكائنات** $scope.emails.messages = [{ "from": "Steve Jobs", "subject": "I think I'm holding my phone wrong :/", "sent": "2013-10-01T08:05:59Z" },{ "from": "Ellie Goulding", "subject": "I've got Starry Eyes, lulz", "sent": "2013-09-21T19:45:00Z" },{ "from": "Michael Stipe", "subject": "Everybody hurts, sometimes.", "sent": "2013-09-12T11:38:30Z" },{ "from": "Jeremy Clarkson", "subject": "Think I've found the best car... In the world", "sent": "2013-09-03T13:15:11Z" }]; }]);علينا الآن دفعها ضمن الصفحة. هنا نستخدم الربط التصريحي لنُعلن عما سيفعله تطبيقنا: إنشاء أول جزء من عناصر HTML الحيوية. سنستخدم مُرشد ng-repeat المبني ضمن Angular، والذي سوف يمر على البيانات ويعرض الناتج دون عناء الاستدعاءات الرجعية أو تغيير الحالة، بهذه السهولة: <ul> <li ng-repeat="message in emails.messages"> <p>From: {{ message.from }}</p> <p>Subject: {{ message.subject }}</p> <p>{{ message.sent | date:'MMM d, y h:mm:ss a' }}</p> </li> </ul>النتيجة قمت أيضًا باستخدام مُرشّح التاريخ لأبين لك كيف يمكن أن تعرض تواريخ UTC. اطلع على المُرشدات التي توفرها Angular لتتعرف على القدرات الكاملة للربط التصريحي. بهذا نكون قد عرفنا كيف نصل البيانات بين الخادوم وطريقة العرض. وظائف المجال (Scope functions)تعتبر وظائف المجال الخطوة التالية في بناء وظائف التطبيق واستكمالاً للربط التصريحي. فيما يلي وظيفة بسيطة تحذف إحدى الرسائل: myApp.controller('MainCtrl', ['$scope', function ($scope) { $scope.deleteEmail = function (index) { $scope.emails.messages.splice(index, 1) }; }]);تنويه: من المهم أن تفكر في حذف البيانات من النموذج، لا تحذف العناصر أو أي شيئ من الصفحة، دع Angular يتولى هذا بربطه ثنائي الجانب للبيانات، فقط فكر بذكاء واكتب شفرة برمجية تستجيب لبياناتك. ربط الوظائف مع طريقة العرض يمر عبر المُرشدات، هذه المرة نستخدم مُرشد ng-click: <a ng-click="deleteEmail($index)">Delete email</a>هذا يختلف تمامًا عن مستقبلات النقر التقليدية في JavaScript، لأسباب عديدة سنشرحها لاحقًا. لاحظ أنني أيضًا أمرر فهرس العنصر $index، إذ يعرف Angualr ما العنصر المُراد حذفه (كم يوفر هذا من العناء؟) النتيجة دوال DOM التصريحيةننتقل الآن لدوال DOM، وهي أيضًا مُرشدات تؤدي وظيفة ضمن الصفحة بدونها كنا سنكتب شفرات برمجية طويلة. أحد الأمثلة المناسبة لإيضاح الفكرة هنا هو إظهار أو إخفاء قسم التنقل ضمن الصفحة باستخدام ng-show وng-click، لنرَ بساطة هذا: <a href="" ng-click="toggle = !toggle">Toggle nav</a> <ul ng-show="toggle"> <li>Link 1</li> <li>Link 2</li> <li>Link 3</li> </ul>هنا ندخل عالم MVVM (اختصار Model-View-View-Model)، لاحظ أننا لم نستخدم متحكمًا، وسنشرح فكرة MVVM بعد قليل. الناتج (جرب الإظهار والإخفاء): النتيجة التعابير (Expressions)من أفضل ما يقدمه Angular، يقدم بديلاً عن الحاجة لكتابة الكثير من JavaScript والنصوص المكررة. هل قمت يومًا ما بكتابة شيء كهذا؟ elem.onclick = function (data) { if (data.length === 0) { otherElem.innerHTML = 'No data'; } else { otherElem.innerHTML = 'My data'; } };ربما يكون هذا استدعاءً راجعًا عن طلب GET، ونحتاج لنغير محتوى في الصفحة بناء على البيانات، يقدم Angular هذا بدون الحاجة لكتابة حرف JavaScript! <p>{{ data.length > 0 && 'My data' || 'No data' }}</p>سيقوم هذا بتحديث الصفحة تلقائيًا دون استدعاءات عند وصول البيانات أو ما شابه. إن لم تتوفر البيانات، سيظهر هذا واضحًا، وإن وجدت فسيظهر كذلك. هناك حالات كثيرة جدًا يُمكن فيها لـAngular أن يتولاها عبر ربطه ثنائي الجانب للبيانات الذي يعمل بشكل رائع. النتيجة طرق العرض الديناميكية والتوجيه (Routing)هي ما تقوم عليه فلسفة تطبيقات الويب (والمواقع) أحادية الصفحة: لديك قسم الترويسة وقسم التذييل وشريط جانبي ثم المحتوى الذي يُحدث تلقائيًا بناءً على الرابط الحالي. يجعل Angular إعداد هذا الأمر في منتهى السهولة. تحقن طرق العرض الديناميكة دوالا مُعينة بناء على الرابط، عبر استخدام $routeProvider. فيما يلي إعداد بسيط: myApp.config(['$routeProvider', function ($routeProvider) { /** * $routeProvider */ $routeProvider .when('/', { templateUrl: 'views/main.html' }) .otherwise({ redirectTo: '/' }); }]);لاحظ أنه عندما (when) يكون الرابط / فقط (الصفحة الرئيسية)، ستعرض الصفحة main.html. من المفيد تسمية طريقة العرض الأساسية main.html وليس index.html لأنه سيكون لدينا مسبقاً الصفحة index.html وهي الصفحة التي تحوي طرق العرض الديناميكية وبقية الأجزاء. يمكن ببساطة إضافة المزيد من طرق العرض: myApp.config(['$routeProvider', function ($routeProvider) { /** * $routeProvider */ $routeProvider .when('/', { templateUrl: 'views/main.html' }) .when('/emails', { templateUrl: 'views/emails.html' }) .otherwise({ redirectTo: '/' }); }]);بإمكاننا الآن تحميل الصفحة emails.html ببساطة لتقوم بعرض قائمة الرسائل الإلكترونية. الخلاصة أننا استطعنا أن نبني تطبيقًا معقدًا للغاية بجهد ضئيل جدًا. توفر الخدمة $routerProvider المزيد من الخيارات، لكن ما تعلمناه عنها كافٍ في البداية. هناك أيضًا أمور أخرى مثل مُعترِضات $http التي تبدأ أحداثًا خلال مسير طلب AJAX، فتتيح لنا عرض مقدار التقدم على سبيل المثال أثناء جلب البيانات. البيانات الثابتة العامةفي تطبيق Gmail للويب، تُكتب بيانات كثيرة بصيغة JSON ضمن الصفحة (انقر باليمين واختر عرض المصدر في صفحة Gmail). إن قمنا بخطوة مشابهة، أي كتابة البيانات ضمن الصفحة، فهذا سيجعل وقت عرضها أقل وسيبدو التطبيق أسرع. عندما أطور تطبيقات مؤسستنا، تُدرج وسوم Java ضمن الصفحة وعندما تُعرض، تُرسل البيانات من الخادوم (لا خبرة لدي في Java لذا سأكتب فيما يلي تصريحات وهمية، يمكنك استخدام أي لغة على الخادوم إن أحببت). النصوص التالية توضح كيف يمكنك كتابة JSON ضمن الصفحة ثم تمريرها إلى المتحكم لربطها مباشرة: <!-- ضمن index.html (في نهاية الصفحة بالطبع) --> <script> window.globalData = {}; globalData.emails = <javaTagHereToGenerateMessages>; </script>سيُنشئ وسم Java الذي اختلقته البينات بينما سيعالج Angular الرسائل فورًا. كل ما عليك هو إعطاؤه البيانات عبر المتحكم: myApp.controller('EmailsCtrl', ['$scope', function ($scope) { $scope.emails = {}; // Assign the initial data! $scope.emails.messages = globalData.emails; }]);تقليص الملفات (Minification)سنتحدث قليلاً عن تقليص حجم الشفرات البرمجية التي كتبناها. ربما تكون قد جربت تقليص شفراتك البرمجية التي كتبتها لـAngular وصادفت خطأ. ليس هناك أمور خاصة يتطلبها تقليص حجم هذه الملفات، باستثناء الحاجة لإدراج أسماء المتطلبات ضمن مصفوفة قبل الدالة المُصرّح عنها، لنوضح أكثر: myApp.controller('MainCtrl', ['$scope', 'Dependency', 'Service', 'Factory', function ($scope, Dependency, Service, Factory) { // code }]);بعد التقليص: myApp.controller('MainCtrl', ['$scope', 'Dependency', 'Service', 'Factory', function (a,b,c,d) { // a = $scope // b = Dependency // c = Service // d = Factory // $scope alias usage a.someFunction = function () {...}; }]);عليك أن تحافظ على ترتيب المتطلبات المحقونة في المصفوفة ['_one', '_two'] وضمن مُعاملات الدالة function(_one, _two)، وإلا فإنك ستسبب لنفسك ولفريق العمل معك مشاكل كثيرة! الاختلافات بين MVC وMVVMسننهي مقالتنا العملاقة الآن بشرح سريع يشمل الفروق بين MVC وMVVM: MVC: التواصل يعتمد على المتحكم (Controller)، لذلك نقول Model-View-ControllerMVVM: يشمل ربط البيانات التصريحي الذي يتواصل، بالمفهوم التقني، مع نفسه؛ أي Model-View-View-Model. يتواصل النموذج مع طريقة العرض، وتتواصل هذه الأخيرة مع النموذج ثانية. يسمح هذا للبيانات بأن تبقى محدّثة على الجانبين دون الحاجة لفعل أي شيء. لا داعي هنا للمتحكم. مثال على ذلك: إنشاء حلقة ng-repeat دون أن نعتمد على بيانات يُرسلها متحكم:<li ng-repeat="number in [1,2,3,4,5,6,7,8,9]"> {{ number }} </li>يبدو هذا مناسبًا للتجارب السريعة، ولكنني أنصح دومًا باستخدام متحكم للحفاظ على تنظيم النص البرمجي. مثال مكونات الويب في HTML5قد تبدو هذه الفكرة مُكررّة، ولكنني سأعيدها هنا لنتحدث عن مكونات الويب. يسمح Angular بإنشاء عناصر (elements) مخصصة مثل: <myCustomElement></myCustomElement> في الحقيقة هذا أشبه ما يكون بمستقبل HTML5 التي تقدم فكرة جديدة تُسمى مكونّات الويب (Web Components)، والتي تتركب من عناصر مخصصة ضمن HTML مترافقة مع نص JavaScript ديناميكي، وهذا أمر مثيرٌ للغاية - والأكثر إثارة أنه أمر ممكن اليوم باستخدام Angular. فريق Angular بعيد النظر - شكرًا لكم! تعليقات المجال (Scope)أعتقد أن تعليقات المجال إضافة جميلة لسياق العمل، فبدل الحاجة لكتابة التعليقات ضمن HTML بالطريقة التالية: <!-- header --> <header> Stuff. </header> <!-- /header -->أصبحنا نتحدث عن طرق العرض والمجالات بدل الصفحة، البيانات ضمن مجال ما لا تُشارك مع مجالات أخرى إلا إن قمت بفعل ذلك عن عمدٍ. لنستغل هذا في تحسين كتابة النص البرمجي بتقسيمه إلى مناطق تسبقها تعليقات: !-- scope: MainCtrl --> <div class="content" ng-controller="MainCtrl"> </div> <!-- /scope: MainCtrl -->تصحيح العلل في Angularتتوفر إضافة جميلة للغاية لمتصفح Chrome ينصح بها فريق Angular، وتسمى Batarang. برمجة سعيدة! ترجمة -وبتصرف- للمقال: Ultimate guide to learning AngularJS in one day لصاحبه Todd Motto (الذي -بطبيعة الحال- يعود عليه ضمير المُتكلم في هذا المقال).
  2. يهدف هذا الدرس إلى بناء تطبيق 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.