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

المرشحات (filters) في AngularJS


Mostafa Ata العايش

تَستخدم المُرشّحات في Angular نمط خطّ الأنابيب (pipeline) بشكلٍ مشابه للـUnix shell، فقد استعارت رمز الخطّ العمودي المشابه لرمز الأنبوب من Unix لتستخدمه لتمرير عبارات التّهيئة أو خواص المجال للمُرشّح وأيضًا لربط المُرشّحات معًا.

filters-angularjs.thumb.png.c5c1d4f293d7

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

<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>

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

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

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

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

مفهوم المُرشّحات سهلُ التّعلّم، لذا سيكون هذا الفصل مرورًا سريعًا على المُرشّحات المبنيّة داخل Angular، ثُمّ سنستعرض كيف يمكننا إنشاء المُرشّحات الخاصّة بنا.

lowercase و uppercase

لنبدأ جولتنا مع المُرشّحات المبنيّة في Angular ببعض التّحويلات على السّلاسل النّصية، والمُرشّح lowercase بدايةٌ ممتازة. نبدأ بكتابة أيّ عبارة (في مثالنا استخدمنا عبارة "AngularJS")، ثم نضع رمز الأنبوب (|)، وبعده نكتب اسم المُرشّح (lowercase).

<p>{{"AngularJS" | lowercase}}</p>

الناتج:

angularjs

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

<p>{{"AngularJS" | lowercase | uppercase }}</p>

الناتج:

ANGULARJS

بالطّبع ليس هناك فائدةٌ من وضع lowercase قبل uppercase، وسنرى في نهاية هذا الفصل مثالًا أفضل على سَلسَلة المُرشّحات، ولكن على الأقل رأينا كيف نقوم بذلك الآن.

number

المُرشّح التّالي سهلٌ أيضًا، المُرشّح number يُقدّم المساعدة الأساسيّة لتنسيق الأعداد وتقريبها.

<p>{{1000.12345 | number}}</p>

الناتج:

1,000.123

بالنّسبة لإعداداتي المحلّيّة، en-us، المُخرجات في المثال هي 1,000.123، يقوم المُرشّح بتقريب العدد المُمرّر إليه إلى 3 خاناتٍ بعد الفاصلة، ومن الواضح أنّ هذا لن يناسب جميع الاستخدامات، ولحسن الحظ، يمكننا تمرير وسطاء إلى المرشّحات التي تقبل القيام بذلك، والمُرشّح number يقبل ذلك. الوسيط الأول الذي نقوم بتمريره هو القيمة التي نريد تطبيق المُرشّح عليها، أما الوسيط الإضافي الثّاني فيجب أن يتبع اسم المُرشّح ويكون مفصولًا عنه بنقطتين (:).

الوسيط الإضافيّ الثّاني للمُرشّح number هو fractionSize، وهو يسمح لنا بالتّحكّم بعدد المنازل التي يتم عرضها بعد الفاصلة، وبهذا يمكننا رؤية القيمة كاملةً.

<p>{{1000.12345 | number:5}}</p>

الناتج:

1,000.12345

ماذا يحدث لو قمنا بتمرير قيمةٍ سالبةٍ إلى الوسيط fractionSize؟ استبدل 5 بـ 3- ثمّ جرّب 4-، هل يُمكنك تخمين السّبب؟ لا مشكلة، حتى أنا لا يمكنني ذلك.

currency

المُرشّح التّالي مفيدٌ بعض الشّيء في التّطبيقات العمليّة: يُقدّم المُرشّح currency الدّعم الأساسيّ لتنسيق العملة وتقريب قيمة العدد.

<p>{{1999.995 | currency}}</p>

الناتج:

$2,000.00

وهو يقبل وسيطين إضافيين، أوّلهما هو رمز العملة، استبدل currency بـ'€':currency في المثال السّابق لتجريبه.

الوسيط الثّاني هو fractionSize. انتبه عند استخدامك للمُرشّح currency عند تقريب المبَالِغ الماليّة، فليس هناك طريقةٌ واحدةٌ فقط هي الصّحيحة عندما ننظر للأمر على أنّه مسألةٌ ماليٌّةٌ وليست تقنيًّة، فهذا المُرشّح يستخدم تقريب النصف للأعلى وقد لا يكون هذا جيّدًا دومًا، كما رأينا في المثال السّابق. لا يوجد حاليًّا طريقةٌ موثّقةٌ لتجاوز طريقة المُرشّح في التّقريب، ما رأيك بأن تحاول التّعديل على المثال السّابق لتجعل المُرشّح يعرض القيمة الصّحيحة.

date

سيبيّن المُرشّح date لك إلى أيّ مدىً تذهب بك إعدادات المُرشّحات.

لا تسمح Angular بتهيئة كائنٍ من نوع Date داخل عبارة، لذا سنستخدم متحكّمًا لتحضير النّموذج الخاصّ بالمثال.

angular.module('app')
  .controller('DateController', function($scope) {
    $scope.now = Date.now();
  });

يبدأ المثال التّالي بعنصرٍ في المجال اسمه now، بدلًا من كتابة عبارة.

<p ng-controller="DateController">
{{now | date}}
</p>

الناتج:

Oct 28, 2015

إن قمت بإزالة date | من المثال السّابق سترى القيمة الأصليّة للعنصر now، وهي عدد الميللي ثانية منذ شهر كانون الثّاني عام 1970 حسب التوقيت العالميّ UTC. والآن قم بتجربة بعض التّنسيقات المُعرّفة سابقًا، والتي يدعمها هذا المُرشّح، قم بتجربة 'date:'medium | ثمّ 'date:'shortTime | وبعدها 'date:'yyyy-MM-dd HH:mm:ss Z |، وجرّب بعض التّعديلات من عندك بعد ذلك. يُسمح بكتابة السّلاسل المحرفية داخل نمط التّنسيق، مثلًا "date:"MMMM 'of the year' yyyy |، وبدلًا من استخدام محارف الهروب مثل % لوضع رموز التّنسيق، سيكون عليك وضع السّلاسل المحرفية بين علامتَي تنصّيّصٍ مفردة (') وأن تكون عبارة التّنسيق الكاملة بين علامتَي تنصّيّصٍ مزدوجة ("). كما أنّ الفراغات في عبارة التّنسيق تدخل في تنسيق العبارة.

orderBy

يُمكن للمُرشّحات القيام بما هو أكثر من تنسيق المخرجات، حيث يُمكن استخدمها لتحويل المجموعات، كما سنرى في المثال التّالي.

لنفترض أنّ خدمةً في النهاية الخلفية (backend) أَرجعت سجلًّا مرتًبًا بطريقةٍ غير مرغوبةٍ لأسباب تتعلّق بالعرض. (لتبسيط المثال، ستكون البيانات مكتوبةً يدويًّا داخل الخدمة items التي تراها أدناه، وذلك بدلًا من التّعامل مع نهايةٍ خلفيّة بعيدة حقيقيّة.)

angular.module('app')
  .value('items', [
    {name: 'Item 3', position: 3, color: 'red', price: 2.75},
    {name: 'Item 1', position: 1, color: 'red', price: 0.92},
    {name: 'Item 4', position: 4, color: 'blue', price: 3.09},
    {name: 'Item 2', position: 2, color: 'red', price: 1.95}
  ]);

سنقوم بعد ذلك بحقن المصفوفة items في داخل متحكّم.

angular.module('app')
  .controller('ItemsController', function($scope, items) {
    $scope.items = items;
  });

بناءً على ترتيب العناصر الحالي في المصفوفة، كيف يمكننا ترتيب العناصر حسب قيمة العنصر position؟

إن كنتَ قد قفزتَ من مقعدك الآن وصرخت قائلًا "Underscore" (أو "lodash")، فتستحقّ منّي وسام الوفاء لـJavaScript، ولكنّها ليست الإجابة التي نبحث عنها، فـAngular تملك مُرشّحًا للقيام بذلك، إنّه المُرشّح orderBy.

<ol ng-controller="ItemsController">
  <li ng-repeat="item in items | orderBy:'position'">
     {{item.name}}
  </li>
</ol>

الناتج:

1. Item 1
2. Item 2
3. Item 3
4. Item 4

هذا رائع، ولكن ماذا لو أردنا أن يكون التّرتيب بالجهة المعاكسة؟ في هذا المثال البسيط يمكننا القيام بذلك بطريقتين، الأولى هي إضافة الرمز - قبل اسم العنصر، أي أن نكتب 'orderBy:'-position بدلًا من 'orderBy:'position. (يمكنك أيضًا إضافة الرمز + لاختيار التّرتيب التّصاعدي، إلا أنّه لا يؤثّر على مثالنا السّابق.) أمّا الطّريقة الثّانية فهي إعطاء قيمةٍ للوسيط البولياني reverse بعد اسم العنصر المطلوب التّرتيبُ حسب قيمته. في مثالنا، قم بكتابة orderBy:'position':true بدلًا من 'orderBy:'position.

فائدةٌ إضافيّة: عدّل سجلّ البيانات في المتحكّم بحيث تجعل عنصرين في المصفوفة لهما القيمة نفسها بالنّسبة للعنصر position، ألا يزال بإمكانك استخدام المُرشّحorderBy لترتيب العناصر بشكلٍ صحيح؟ ماهي الخاصّيّة التي استخدمتها؟

انتبه للتحديثات

بفضل طريقة Angular في الربط ثنائيّ الاتّجاه، سيكون من السّهل جدًّا أن تجعل النّماذج في قائمة أو جدول العرض قابلةً للتّعديل، كلّ ما تحتاجه هو إضافة خانة إدخالٍ داخل الحلقة مع إسناد اسم عنصر المجموعة إلى التّوجيه ng-model. ولكن انتبه، فبما أنّ Angular جاهزةٌ دومًا وتنتظر إعادة توليد العرض عند أيّ تغيير في الحالة، فقد يتمّ إعادة ترتيب قائمتك قبل أن تنتهي من تعديل العنصر حتّى.

<table class="table table-condensed" ng-controller="ItemsController">
  <tr ng-repeat="item in items | orderBy:'name'">
    <td ng-bind="item.name"></td>
    <td><input type="text" ng-model="item.name"></td>
  </tr>
</table>

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

limitTo

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

<ol ng-controller="ItemsController">
  <li ng-repeat="item in items | orderBy:'-price' | limitTo:2">
     {{item.price | currency}}
  </li>
</ol>

الناتج:

1. $3.09
2. $2.75

لا تُقدّم Angular طريقةً لتمرير وسيط offset إلى المُرشّح limitTo حاليًّا. ولتقديم حلٍّ كاملٍ للتصحيف (pagination) سيتضمّن ذلك استخدام التّابع slice داخل المتحكّم.

filter

اسم هذا المُرشّح ظريفٌ نوعًا ما، فهو مرشّحٌ اسمه filter، ربّما لم يكن من المناسب تسمية المُرشّحات بهذا الاسم العام (ربّما يكون الاسم مزخرِفات أو مساعِدات أفضل)، إلّا أنّ هذا المرشّح يقوم فعلًا بالتّرشيح. فهو يقوم بإعادة مصفوفةٍ تحوي فقط على العناصر التي تطابق الشرط المُسند. لنرى ما هي العناصر التي ستطابق وضعنا لشرطٍ يقول بأنّ القيمة العدديّة تساوي 3.

<table class="table table-condensed" ng-controller="ItemsController">
  <thead>
    <tr><th>name</th><th>color</th><th>price</th></tr>
  <thead>
  <tbody>
    <tr ng-repeat="item in items | filter:3">
      <td ng-bind="item.name"></td>
      <td ng-bind="item.color"></td>
      <td ng-bind="item.price | currency"></td>
    </tr>
  <tbody>
</table>

لقد حصلنا على تطابقات في كلا العنصرين name وprice. ماذا سيحدث لو استبدلنا الشرط 3 بالسّلسلة النّصية '3' أو بسلسلة نصّيّة خالية؟ أو لو أضفنا فراغًا في البداية ('3 ')؟

لتجنُّب مطابقة أيّ عنصرٍ، يمكنك تمرير كائنٍ باعتباره شرط التّطابق.

<table class="table table-condensed" ng-controller="ItemsController">
  <thead>
    <tr><th>name</th><th>color</th><th>price</th></tr>
  <thead>
  <tbody>
    <tr ng-repeat="item in items | filter:{price: 2, color: 'red'}">
      <td ng-bind="item.name"></td>
      <td ng-bind="item.color"></td>
      <td ng-bind="item.price | currency"></td>
    </tr>
  <tbody>
</table>

في المثال السّابق، لم يتمّ مطابقة 2 مع item 2 لأننا حدّدنا اسم عنصر التّطابق بأنّه price.

أكثر ما يجعل المُرشّح filter رائعًا، هو السهولة الفائقة في دمجه مع الربط ثنائيّ الاتّجاه في Angular، لإنتاج صندوق ترشيحٍ قويّ.

Filter: <input type="text" ng-model="q">
<table class="table table-condensed" ng-controller="ItemsController">
  <thead>
    <tr><th>name</th><th>color</th><th>price</th></tr>
  <thead>
  <tbody>
    <tr ng-repeat="item in items | filter:q">
      <td ng-bind="item.name"></td>
      <td ng-bind="item.color"></td>
      <td ng-bind="item.price | currency"></td>
    </tr>
  <tbody>
</table>

إن كان هناك مثالٌ واحدٌ يبيّن بأفضل طريقةٍ مرونة وقوّة امتدادات HTML التي توفّرها Angular، فقد يكون هذا هو.

استخدام المرشحات في JavaScript

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

<p ng-controller="ItemsController">
  <!-- Invalid code! -->
  {{items | filter:{color:'red'}.length}} red items
</p>

الناتج:

[{"name":"Item 3","position":3,"color":"red","price":2.75},{"name":"Item 1","position":1,"color":"red","price":0.92},{"name":"Item 4","position":4,"color":"blue","price":3.09},{"name":"Item 2","position":2,"color":"red","price":1.95}] red items

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

يكون اسم التّابع الذي يقوم بالترشيح من الشكل filtername>Filter>. مثلًا لاستخدام المُرشّح date نقوم بحقن dateFilter، لاستخدام المُرشّح currency نقوم بحقن currencyFilter، ولاستخدام المُرشّح filter نقوم بحقن filterFilter. (أعرف ذلك، أعرف ذلك)

angular.module('app')
  .controller('FilteredItemsController', function($scope, items, filterFilter) {
    $scope.redItemsCount = filterFilter(items, {color: 'red'}).length;
  });
<p ng-controller="FilteredItemsController">
  {{redItemsCount}} red items
</p>

الناتج:

3 red items

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

مرشح مخصص

المُرشّح المخصّص هو أفضل مكانٍ لتضع فيه الشيفرات التي تقوم بتحويل بيانات النّموذج للعرض، وذلك لأنّ المُرشّح نموذجيًّا مكوِّنٌ ثابت الحالة (stateless) ويُمكن استخدامه في أيّ مكانٍ داخل القوالب دون خوفٍ من أيّ تأثيراتٍ جانبيّة، كما أنّ إنشاء مُرشّح مخصّص ليس صعبًا، فبشكلٍ مشابهٍ لبقيّة المكوّنات في Angular، نقوم بتسجيل وصفةٍ للمُرشّح داخل الوحدة، باستخدام التّابع filter.

angular.module('app')
  .filter('smiley', function() {
    return function(text) {
      return '\u263A ' + text + ' \u263A';
    };
  });

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

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

<strong ng-bind="'hello' | smiley"></strong>

الناتج:

☺ hello ☺

إليك هذا التّحدّي: هل يمكنك إعادة تشكيل المُرشّح بحيث يصبح بإمكاننا تمرير رمز الوجه الضّاحك كوسيط؟

حقن التبعية

بالرغم من كون المُرشّحات ثابتة الحالة وبسيطةً نوعًا ما، إلّا أنّها يمكن أن تملك تبعيّاتٍ تحتاج إلى أن تُحقن.

يسمح تابع الوصفة الإنشائيّة الخارجيّ بالتّصريح عن التّبعيّات تمامًا كما نقوم بذلك في الوحدات. فلنلقِ نظرةً على الكيفيّة التي نقوم بها بحقن التّبعيّات في داخل مُرشّح مخصّص للمحلّيّات (localization). سنقوم بتسمية المُرشّح بالاسم localize، ولكن قبل ذلك نحتاج إلى شيءٍ ما لنقوم بالحقن. يمكننا إنشاء جدول لمحتوى المتغيّر locales، حيث يتمّ تعريفها كخدمةٍ قابلةٍ للحقن باستخدام التّابع value.

angular.module('app')
  .value('locales', {
    'de': {greeting: 'guten Tag'},
    'en-us': {greeting: 'howdy'},
    'fr': {greeting: 'bonjour'}
  });

يمكننا حقن هذه البيانات في داخل مُرشّحنا البسيط المخصّص بالتّصريح عن وسيطٍ اسمه locales، ولنتمكّن من الحصول على الموضع الحاليّ للمستخدم، يُمكننا استخدام الخدمة locale$ المبنيّة داخل Angular .

angular.module('app')
  .filter('localize', function(locales, $locale) {
    return function(key) {
      var locale = locales[$locale.id] || locales['en-us'];
      return locale[key];
    };
  });

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

<p>
  The app says <strong ng-bind="'greeting' | localize"></strong>.
</p>

الناتج:

The app says howdy.

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

خاتمة

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

ترجمة وبتصرّف للفصل التاسع من كتاب: 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.


×
×
  • أضف...