اذهب إلى المحتوى

التوجيهات (Directives) في AngularJS


Mostafa Ata العايش

لَعِبت التّوجيهات المبنيّة داخل Angular مثل ng-bind وng-model وinput دور النّجوميّة في هذه السلسلة منذ الفصل الأوّل، المبادئ، حتّى أنّ فصل المجموعات كان يركّز كلّيًّا على استخدام توجيهٍ واحدٍ (قويٍّ إلى حدّ كبير) هو التّوجيه ng-repeat. قد تظنّ بعد ذلك أن قائمة التّوجيهات المبنيّة في داخل Angular تحوي حلولًا لجميع الحالات، ولكنّ هذا غير صحيحٍ بالطّبع. لقد تصوّر مصمّموا Angular بأنّها ستكون طريقةً للسّماح لغير المبرمجين ببناء صفحاتٍ تفاعليّة، إلّا أنّها تطوّرت لتصبح منصّةً تعطي مطوّري الويب القوّة لإنشاء امتداداتٍ وتخصيص HTML العاديّة، وهذا يعني إنشاء التّوجيهات الخاصّة بك، وهذا تمامًا ما سنقوم به في هذا الفصل.

angularjs-directives.thumb.png.2b246a9db

يكون إنشاء توجيهٍ مخصّص بشكله الأبسط مشابهًا كثيرًا لإنشاء مرشّح مخصّص، وهذا هو سبب الحديث عن المرشّحات قبل التّوجيهات في فصلٍ سابق. التّابع الخارجي الذي سنقوم بتمريره إلى التّابع directive هو غلافٌ لحقن التّبعيّة ويُدعى بتابع الصّناعة (factory function)، ويُعرف بالوصفة (recipe) ضمن توثيق Angular. يتمّ استدعاء هذا التّابع مرّةً على الأكثر، أو لا يتمّ استدعاؤه أبدًا إن لم يتمّ استخدام التّوجيه، كما تعلّمنا سابقًا في فصل الخدمات. في داخل تابع الصناعة يوجد تابعٌ آخر تقوم Angular فعليًّا باستدعائه وهو المكان الذي نقوم فيهذر عن بالعمل الحقيقيّ للتّوجيه، ويُسمّى بتابع الربط link.

التابع directive

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

angular.module('app', []);

ثمّ نقوم بتحميل الوحدة الجذر عن طريق تمرير اسم التّطبيق إلى التّوجيه ng-app.

<body ng-app="app">
  <!-- الأمثلة توضع هنا -->
</body>

وبذلك أصبحنا جاهزين لإنشاء توجيه مخصّص، ولنبدأ بتوجيهٍ بسيطٍ جدًّا اسمه hello.

angular.module('app')
  .directive('hello', function() {
    return function(scope, element) {
      element.text("hello");
    };
  });

للقيام بأيّ شيءٍ مفيدٍ سنحتاج إلى استخدام الوسيط الثّاني لتابع الرّبط، والّذي يكون غلافًا (wrapper) لعنصرٍ من المستند نجلبه من مكتبة jqLite الخاصّة بـAngular أو من مكتبة jQuery حسب الوسيط الذي قمت بتضمينه. في هذا المثال، قمنا باستبدال المحتوى السّابق للعنصر بالعبارة hello.

The message is <span hello></span>.

الناتج:

The message is hello.

أليس هذا رائعًا؟ لقد أنشأنا لتوّنا خاصّيّة HTML جديدة خاصّةً بنا.

الوسيط scope

في المثال السابق قمنا يدويًا (hardcoded) بكتابة النصّ التي نريد كتابته وهذا لا يفيدنا كثيرًا، لذا لنرى إن كان بإمكاننا استبدال النّصّ بقيمةٍ متغيّرة. ربّما كنت تتساءل بخصوص الوسيط الأوّل scope، لنقم بإضافة متحكّمٍ يقوم بإنشاء العنصر message في مجاله.

angular.module('app')
  .controller('MessageController', function($scope) {
    $scope.message = 'hello';
  });

والآن لنقم باستخدام هذا العنصر في التّوجيه، وسنُسمّيه message أيضًا.

angular.module('app')
  .directive('message', function() {
    return function(scope, element) {
      element.text(scope.message);
    };
  });

من الواضح أنّه يتوجّب علينا استخدام هذا التّوجيه في المكان داخل المستند الذي يكون تابعًا للمتحكّم MessageController.

<p ng-controller="MessageController">
  The message is <span message></span>.
</p>

الناتج:

The message is hello.

قد تتساءل، لماذا يتمّ في المتحكّم تعريف وسيط المجال مسبوقًا برمز الدولار scope$ بينما في تابع الرّبط يكون هذا الوسيط بدون علامة الدولار في بدايته scope، والسّبب هو أنّ أسماء الوسطاء في تابع الرّبط غير مهمّة (إلّا بالنّسبة لنا فقط) على عكس الوسطاء التي يتم حقنها.

الوسطاء

كما تعلّمنا سابقًا عند استخدام التّوجيهات المبنيّة داخل Angular، فالتّوجيهات تقبل تمرير الوسطاء، وهذه الوسطاء تُمرّر داخل الوسيط الثّالث لتابع الرّبط، وهو الوسيط attrs. مرّةً أخرى، وعلى عكس حقن التّبعيّات، يُمكننا تسمية الوُسطاء بأيّ اسمٍ نريده، ويُفضّل استخدام أسماءٍ مفهومةٍ لتبقى الشّيفرة مفهومة.

angular.module('app')
  .directive('welcomeMessage', function() {
    return function(scope, element, attrs) {
      element.text(attrs.welcomeMessage);
    };
  });
The message is <em welcome-message="bonjour"></em>.

الناتج:

The message is bonjour.

لاحظ أنّه عندما نستخدم أكثر من كلمةٍ واحدةٍ في كتابة اسم التّوجيه بأنّنا نستخدم نمط camel case حيث نكتب أوّل حرفٍ في الكلمة الثّانية بحرفٍ كبير، وهذا هو النّمط المتعارف عليه في JavaScript، ولكنّ النمط المستخدم في HTML هو فصل الكلمات باستخدام إشارة (-) وهو ما يُدعى hyphenated style، وخلاصة الكلام هي أنّك عندما تريد استخدام welcome-message لتضمين التّوجيه الخاص بك في القالب، سيكون عليك استخدام الاسم welcomeMessage عندما تقوم بتعريفها في JavaScript.

قد تكون لاحظت بأنّ بعض التّوجيهات المدمجة في Angular تقوم بمعالجة (evaluate) العبارات، إلّا أنّ هذا السلوك ليس هو السلوك الافتراضي.

The message is <em welcome-message="'bon' + 'jour'"></em>.

الناتج:

The message is 'bon' + 'jour'.

بما أنّنا نعمل داخل قالب Angular، سيكون بإمكاننا دومًا تمرير نتيجة عبارةٍ معالجَة (evaluated).

The message is <em welcome-message="{{'bon' + 'jour'}}"></em>.

الناتج:

The message is bonjour.

يُمكنك أيضًا إدخال سلسلةٍ نصّيّة معرّفة مسبقًا داخل السلسلة النّصّية العاديّة.

<p ng-init="greeting = 'bonjour'">
  The message is <span welcome-message="I say {{greeting}}, you say hello"></span>.
</p>

الناتج:

The message is I say bonjour, you say hello.

لتتمكّن من معالجة العبارة الممرّرة إلى التّوجيه كسلسلةٍ نظاميّة، ستحتاج إلى استخدام التّابع eval$.

التّابع scope.$eval

بالرغم من أنّ Angular لا تعتبر أنّ الوسيط المُمرّر إلى التّوجيه عبارةٌ تحتاج إلى معالجة، إلّا أنّه يمكننا ببساطةٍ أن نعالج هذه العبارة باستخدام التّابع eval$، ولأخذ العلم، فهذه الطّريقة لا تؤدّي إلى إعادة تحديث العرض (view) عند تغيُّر النّموذج، وهذا مفيدٌ فقط في حالات الاستخدام الأساسيّة.

angular.module('app')
  .directive('welcomeMessage', function() {
    return function(scope, element, attrs) {
      var result = scope.$eval(attrs.welcomeMessage);
      element.text(result);
    };
  });

لنقم بتجربة حالة استخدامٍ بسيطة نقوم فيها بوصل سلسلتين نصّيّتين.

The message is <em welcome-message="'bon' + 'jour'"></em>.

الناتج:

The message is bonjour.

يُمكننا أيضًا باستخدام scope.$eval أن نقوم بتمرير كائنات، مّما يتيح لنا طريقةً لتلقّي العديد من الوسطاء معًا، وسنلقي نظرةً في فقرةٍ لاحقةٍ من هذا الفصل علىتوجيهات العناصر، والتي تقبل أيضًا العديد من الوسطاء المُسمّاة عن طريقة إتاحة وجود العديد من الخصائص للعنصر، أمّا الآن فسنرى في المثال التالي معالجةً لكائنٍ من نوع options، حيث نقوم بتمرير العديد من الوسطاء إلى التّوجيه.

angular.module('app')
  .directive('welcomeMessage', function() {
    return function(scope, element, attrs) {
      var options = scope.$eval(attrs.welcomeMessage);
      var result = options.emoticon + ' ' + options.message + ' ' + options.emoticon;
      element.text(result);
    };
  });

وبهذه الطّريقة استطعنا إدخال وسيطين بفعّاليّة، message وemoticon، واستطعنا إنجاز الوصل بينهما داخل التّوجيه.

The message is <em welcome-message="{message: 'bonjour', emoticon: '\u263a'}"></em>.

الناتج:

The message is ☺ bonjour ☺.

صِرنا الآن نفهم كيفيّة استخدام توجيهاتٍ مخصّصةٍ مع المجالات والمتحكّمات. ولكن ماذا لو أردنا استخدام خدماتٍ أخرى داخل التّوجيه؟

حقن التبعية

إن أردنا الوصول إلى موارد مُدارة مثل خدمةٍ ما، أو مُرشّحٍ ما من داخل توجيهنا الخاصّ، فلن يكون علينا سوى التّصريح عن التّبعيّات كوسيطٍ في التّابع الخارجيّ. (راجع فصل حقن التّبعيّة وفصل الخدمات إن لم يكن هذا المفهوم مألوفًا لديك.)

على سبيل المثال، لنفترض بأنّنا نريد توجيهًا يُمكنه تطبيق حسمٍ على السّعر وأيضًا القيام بتنسيقٍ للعملة لقيمة المتغيّر price في المجال. سيحتاج هذا التّوجيه للوصول إلى الخدمة المخصّصة calculateDiscount المُعرّفة كما يلي.

angular.module('app')
  .value('discountRate', 0.8)
  .factory('calculateDiscount', function(discountRate) {
    return function(amount) {
      return amount * discountRate;
    };
  });

والآن سنستخدم تابع الوصفة (recipe) التّغليفيّ لحقن الخدمة calculateDiscount مع المُرشّح currency أيضًا.

angular.module('app')
  .directive('discount', function(calculateDiscount, currencyFilter) {
    return function(scope, element, attrs) {
      var price = scope.$eval(attrs.discount);
      var discountPrice = calculateDiscount(price);
      element.html(currencyFilter(discountPrice));
    };
  });

لاحظ بأنّنا قمنا بتمرير العنصر price كقيمةٍ لوسيط التّوجيه في القالب، ويُمكننا الحصول على هذه القيمة للوسيط عن طريق الوسيط attrs حيث نصل إلى الخاصّيّة (discount) وهي اسم التّوجيه الخاصّ بنا. في المثال التّالي، سنقوم بإسناد قيمة هذا العنصر في المجال (الّتي نحصل عليها عن طريق scope.$eval) إلى المتغيّر المحلّي المُسمّى price.

<table ng-init="price = 100">
  <tr>
    <td>Full price:</td>
    <td ng-bind="price | currency"></td>
  </tr>
  <tr>
    <td>Sale price:</td>
    <td discount="price"></td>
  </tr>
</table>

الناتج:

Full price:	$100.00
Sale price:	$80.00

قد ترغب في وضع مُرشّحٍ بدلًا من التّوجيه، فالتّحويل الذي قمنا به بسيطٌ للغاية، حيث لم نضِف إلى المستند أيّ سلوكٍ تفاعليّ. لننتقل الآن إلى مثالٍ أكثر تعقيدًا بحيث يحتاج إلى إضافة وسمٍ باستخدام القالب.

القوالب

كُلّ العمليّات التي قمنا بتنفيذها في الأمثلة السّابقة على الوسيط element كانت سهلةً إلى حدٍّ ما، ولكن ماذا لو أردنا إضافة قطعةٍ متغيّرة الحجم من محتوىً جديدٍ إلى المستند؟

لنقم بمحاولة حلّ هذه المشكلة بالتّدريج. في البداية، لنقُل بأننا نريد فقط تغليف سلسلةٍ نصّيّةٍ نُمرّرها كوسيطٍ للتّوجيه الخاصّ بنا باستخدام عنصر strong.

The message is <span strong-message="a strong hello!"></span>

يُمكننا دومًا استخدام عمليّة وصل السّلاسل (concatenation).

angular.module('app')
  .directive('strongMessage', function() {
    return function(scope, element, attrs) {
      element.html('<strong>' + attrs.strongMessage + '</strong>');
    };
  });

الناتج:

The message is a strong hello!

أو يُمكننا بناء المحتوى بطريقةٍ برمجيّة باستخدام jqLite أو jQuery.

angular.module('app')
  .directive('strongMessage', function() {
    return function(scope, element, attrs) {
      element.html('<strong>');
      element.find('strong').text(attrs.strongMessage);
    };
  });

الناتج:

The message is a strong hello!

عمليّة وصل السّلاسل والتّغييرات البرمجيّة على المستند طريقةٌ جيّدةٌ عند استخدامها للأمور الصّغيرة كعنصرٍ واحدٍ مثلًا، ولكن ماذا سنفعل عندما يكون المحتوى يتضمّن العديد من العناصر المتداخلة؟ أو عندما يكون لدينا قائمةٌ متغيّرة الحجم؟ سيجيبك عن هذا السّؤال أيّ خبيرٍ في تطوير النهاية الأماميّة (front-end) بأنّ مكتبة القوالب ستسهّل الأمر كثيرًا، ولحسن الحظّ، فـAngular مكتبة قوالب، وستجعل الأمر أكثر سهولة.

يُمكن لـAngular أن تقوم بإخراج (render) قوالب متداخلةٍ يقدّمها التّوجيه الخاصّ بنا، وذلك في الوقت نفسه الّذي تقوم فيه بإخراج قالب التّطبيق الخاصّ بنا.

لإضافة قالبٍ للتّوجيه، سنحتاج إلى تغيير أسلوبنا السّابق، فبدلًا من إعادة (return) تابع الرّبط فقط، سنقوم بإعادة كائنٍ يحوي تابع الربط ومعه قالب. هذا تغيير كبير، لذا فلنكرّر ذلك: سيقوم المُغلّف الوصفيّ (recipe) الآن بإعادة كائنٍ بدلًا من تابع، وسنستخدم العنصر link في هذا الكائن المُعاد للوصول إلى التّابع.

فيما يلي مثالٌ يستخدم قالبًا لإنشاء عددٍ غير محدّدٍ مسبقًا من عناصر li.

angular.module('app')
  .directive('wordList', function() {
    return {
      link: function(scope, element, attrs) {
        scope.words = attrs.wordList.split(" ");
      },
      template: "<li ng-repeat='word in words'>\
                   {{word}}\
                 </li>"
    };
  });

ستستلم Angular مهمّة تضمين القالب في المجال الصحيح، ثمّ إلحاق المخرجات بالعنصر الذي استخدم التّوجيه. بما أنّ مخرجات هذا التّوجيه ستكون عناصر في قائمة، لنقم بتضمين التّوجيه في العنصر ul.

<ul word-list="we love templates"></ul>

الناتج:

. we
. love
. templates

أظنّ بأن المثال بدأ يوضّح لك قوّة وأناقة التّوجيهات المخصّصة.

العنصر templateUrl

استخدمنا في المثال البسيط السّابق رمز الشرطة الخلفيّة (\) مرّتين لنتمكّن من كتابة نصّ القالب، وكما ترى، فالقوالب السّطريّة سُرعان ما تصبح مستهجنةً داخل شيفرة JavaScript، ولذلك سنستعين بالقوالب الخارجيّة التي تدعمها Angular، سواء كانت عناصر خاصّة مخفيّة في المستند، أو موارد ويب منفصلةً تمامًا في مكانٍ مستقل.

<li ng-repeat="word in words">
  {{word}}
</li>

لقد قمنا باستخراج شيفرة القالب إلى مورد ويب موجودٍ في الرابط النّسبيّ views/word-list.html، والآن سنقوم بإضافة العنصر templateUrl إلى كائن الإعدادات الذي يعيده التّوجيه الخاص بنا، وسنقوم بإسناد مسار القالب إليه، وذلك بدلًا من استخدام العنصر template وإسناد شيفرة القالب إليه مباشرةً.

angular.module('app')
  .directive('wordList', function() {
    return {
      link: function(scope, element, attrs) {
        scope.words = attrs.wordList.split(" ");
      },
      templateUrl: '/views/word-list.html'
    };
  });

أمّا الاستخدام فيبقى كما هو دون تغيير.

 

<ul word-list="external templates rock"></ul>

الناتج:

. external
. templates
. rock

القوالب الخارجيّة عمومًا أسهلُ بكثيرٍ للقراءة وللتّعديل من القوالب السّطريّة.

العنصر replace

لا بُدّ من استخدام التّوجيه في المثال السابق ضمن قائمة، ولكن ماذا لو لم يقم المستخدم باستخدام التّوجيه بشكلٍ صحيح؟ أحد الحلول هو استبدال العنصر الذي يتضمّن التّوجيه بدلًا من إلحاق العناصر به. إذًا لنقُم بإضافة عنصر ul لتغليف مخرجات قالبنا، سنحتاج أيضًا إلى عنصرٍ جديدٍ في كائن الإعدادات هو العنصر replace، وسنُسند إليه القيمة true. (القيمة الافتراضيّة له هي بالطّبع false.)

لنقم أوّلًا بإضافة العنصر ul إلى قالبنا.

<ul>
  <li ng-bind="word" ng-repeat="word in words"></li>
</ul>

التّغيير الوحيد الهامّ في التّوجيه هو استخدام العنصر الجديد replace.

angular.module('app')
  .directive('wordList', function() {
    return {
      link: function(scope, element, attrs) {
        scope.words = attrs.wordList.split(" ");
      },
      templateUrl: '/views/word-list-ul.html',
      replace: true
    };
  });

بما أنّ العنصر الذي يتضمّن التّوجيه سيتمّ استبداله مهما كان، يُمكننا تضمين التّوجيه في عنصرٍ غريبٍ وغير ملائمٍ مثل h1.

 

<h1 word-list="lists not headlines"></h1>

الناتج:

. lists
. not
. headlines

يبيّن المثال السّابق قوّة تأثير التّوجيهات على المستند، ولكنّني أُفضّل أن يكون التّصميم أكثر وضوحًا، ودلالته صحيحة، ولذلك فبدلًا من استبدال وسوم HTML، أرى أنّ الصّواب هو التّحقّق من صحّة العنصر الذي قام بتضمين التّوجيه، وإن لم يكن صحيحًا يتمّ إلقاء خطأ (throw an error).

العنصر controller

في بداية هذا الفصل، رأينا مثالًا يحوي متحكّمًا يقوم بتحضير عنصرٍ في المجال، ثمّ استخدمنا هذا العنصر لاحقًا داخل تابع الربط الخاصّ بالتّوجيه. والآن بعد أن تعرّفنا على طريقة استخدام القوالب، لنقُم بإعادة كتابة هذا المثال، واستبدال تابع الرّبط الذي يحدّد نصّ العنصر في المستند بقالب.

angular.module('app')
  .controller('MessageController', function($scope) {
    $scope.message = 'hello, from the external controller';
  })
  .directive('message', function() {
    return {
      template: "<strong>{{message}}</strong>"
    };
  });

بما أنّ المتحكّم والتّوجيه منفصلان، فكلاهما يحتاج إلى التّضمين بشكلٍ منفصلٍ في المثال التالي. يُمكننا أن نضع المتحكّم في نفس العنصر الذي يحوي التّوجيه أو في عنصرٍ مغلّف، فلا فرق.

The message is <span message ng-controller="MessageController"></span>.

الناتج:

The message is hello, from the external controller.

كما ترى في المثال السّابق، لقد قُمنا بإزالة تابع الرّبط، فوجوده أمرٌ اختياريّ، واقتصرنا على العنصر template، وذلك لأنّ استخدام تابع الرّبط يكون للقيام بالتّغييرات البرمجيّة على المستند بطريقةٍ أكثر أُلفةً لمطوّري النّهاية الأماميّة. أمّا الآن بعد أن انتقلنا إلى استخدام القوالب، فسنحتاج إليه للقيام بمهمّاتٍ نحتاج فيها إلى الوصول إلى العنصر الحاوي للتّوجيه أو إلى خصائص العنصر (مُتضمّنةً التّوجيه بذاته، كما رأينا سابقًا.)

إضافةً إلى ذلك، فإنّ تحضير بيانات النّموذج لا يحتاج إلى الوصول إلى العنصر أو إلى خصائصه، وهو من مهمّات المتحكّم، وهذا بالرّغم من إمكانيّة القيام بذلك داخل تابع الرّبط كما بيّنّا في فقرة القوالب. لذا، سيكون من الأفضل أن ننقل مسؤوليّة تحضير النّموذج إلى داخل المتحكّم، إلّا أنّ المشكلة الآن هي أنّ المتحكّم منفصلٌ ويحتاج إلى إعداداتٍ خاصّةٍ به، فهل يُمكننا نقل المتحكّم إلى داخل التّوجيه؟ نعم، يمكننا ذلك.

angular.module('app')
  .directive('message', function() {
    return {
      template: "<strong>{{message}}</strong>",
      controller: function($scope) {
        $scope.message = 'hello, from the internal controller';
      }
    };
  });

أليس هذا رائعًا؟ لم نحصل الآن على مكوِّنٍ مغلّفٍ جيّدًا وحسب، بل تخلّصنا الآن من تضمين المتحكّم واستخدام التّوجيه ng-controller في الوسم.

The message is <span message></span>.

الناتج:

The message is hello, from the internal controller.

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

الخيار restrict

تُوجد أربع طرقٍ لتضمين توجيهٍ ما، إلّا أنّنا لم نرَ حتّى الآن إلّا طريقة تضمين التّوجيهات كخصائص لعناصر HTML، وذلك لأنّه الأسلوب الأكثر شيوعًا وفائدةً للقيام بذلك، وهي أيضًا القيمة الافتراضيّة للخيار restrict الذي يُمكن أن يملك واحدةً أو أكثر من الخيارات التّالية:

  • 'A' يتمّ تضمينه كخاصّيّة (Attribute)
  • 'C' يتمّ تضمينه كفئة (Class)
  • 'E' يتمّ تضمينه كعنصر (Element)
  • 'M' يتمّ تضمينه كتعليق (Comment)

بالنّسبة للأساليب الثّلاثة الأخرى في التّضمين (الفئة، العنصر والتّعليق) فأسلوب التضمين كعنصرٍ هو الأكثر إثارةً للاهتمام من النّاحية العمليّة، فأسلوبا الفئة والتّعليق موجودان لدعم الحالات الهامشيّة مثل وجود تحقّق صارمٍ لصحّة نصوص HTML.

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

في المثال التّالي لن يكون لدينا هذا العدد الكبير من الوسوم إلّا أنّنا سنصمّم جدولًا مع كلّ الزخرفات الخاصة به.

<table class="table table-condensed">
  <thead>
    <tr>
      <th ng-bind="column" ng-repeat="column in columns"></th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="model in models">
      <td ng-bind="model[column]" ng-repeat="column in columns"></td>
    </tr>
  </tbody>
</table>

سيستخدم تابع الرّبط الآن وسيطين في تعريف التّوجيه الخاصّ بنا، وسنصل إلى هذا الوسيطين عن طريق attrs.models و attrs.columns.

angular.module('app')
  .directive('modelsTable', function() {
    return {
      restrict: 'E',
      templateUrl: '/views/models-table.html',
      link: function(scope, element, attrs) {
        scope.models = scope.$eval(attrs.models);
        scope.columns = attrs.columns.split(",");
      }
    };
  });

قد ترغب بإسناد القيمة true إلى العنصر replace في عنصر التّوجيه إن كانت البيئة التي تعمل عليها تُسبّب لك بعض المشاكل عند استخدام أسماء عناصر غير معياريّة، على الجانب الآخر، تركُ هذا العنصر المخصص في مستندك قد يساعدك أثناء تصحيح الأخطاء في الشيفرة.

سنقوم مرّةً أخرى باستخدام متحكّمٍ خارجيّ في هذا المثال.

angular.module('app')
  .controller('ItemsController', function($scope) {
    $scope.items = [
      {name: 'Item 1', color: 'green', price: 5.0},
      {name: 'Item 2', color: 'blue', price: 4.93}
    ];
  });

وأخيرًا، سنقوم بإضافة Bootstrap إلى صفحتنا لجعل مثالنا الأخير أكثر أناقة.

<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.js"></script>

الخطوة الأخيرة الآن هي تضمين عنصر التّوجيه الخاصّ بنا مع وسيطيه models وcolumns حيث سنضيفهما على شكل خصائص للوسم.

<p ng-controller="ItemsController">
  <models-table models="items" columns="name,color,price"></models-table>
</p>

رائع، لقد قمنا بإخفاء الشيفرات المزعجة الخاصّة بجدول HTML بإحكام.

خاتمة

لقد قدّم لك هذا الفصل عن التّوجيهات معرفةً ستعطيك قوّةً كبيرةً عند استخدامك لـAngular في تطوير تطبيقات طرف المستخدم، فكما رأينا في المثال الأخير، يُمكنك الآن تعريف توجيهٍ مخصّصٍ يستخدم قوّة قوالب Angular ويستخدم التّوجيهات المدمجة معها، مع قدرةٍ كبيرةٍ على التّقليل من الشّيفرات المزعجة ومن ثمّ زيادة الإنتاجيّة.

لا يزال علينا فهم أمورٍ أخرى بخصوص التّوجيهات، خصوصًا عندما تدخل مشكلة الحالة المشتركة إلى السّاحة. سيعرض الفصل القادم هذه السيناريوهات المتقدّمة، فإن كنت تشعر بأنك حصلت على المعرفة الكافية لخدمة أهدافك، فيُمكنك القفز مباشرةً إلى فصل HTTP. أما إن كنت ترغب في تعلُّم المزيد، فتابع مباشرةً إلى الجزء الثّاني من فصل التّوجيهات.

ترجمة وبتصرّف للفصل العاشر من كتاب: Angular Basics لصاحبه: Chris Smith.


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

أفضل التعليقات

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



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...