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

الإعداد الأولي لقائمة المنتجات
لنبدأ بمصفوفة تحتوي على عدة منتجات، وكل عنصر فيها يمثل كائناً يتضمن اسم المنتج وسعره وسعة الذاكرة:
const products = [
{ name: 'Macbook Air', price: '180000', ram: 16 },
{ name: 'Samsung Galaxy M21', price: '13999', ram: 4 },
{ name: 'Redmi Note 9', price: '11999', ram: 4 },
{ name: 'OnePlus 8T 5G', price: '45999', ram: 12 }
];
سنستخدم قائمتين منسدلتين:
- الأولى لتحديد معيار الفرز مثل
priceأوram. - الثانية لتحديد اتجاه الفرز مثل
ascأوdesc.
<div class="filters">
<div>
Sort By:
<select class="sort-by">
<option value="">Select one</option>
<option value="price">Price</option>
<option value="ram">Ram</option>
</select>
</div>
<div>
Sort Order:
<select class="sort-order">
<option value="">Select one</option>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
</div>
</div>
<div class="products"></div>
كيفية عرض المنتجات داخل الواجهة
قبل تنفيذ الفرز، نحتاج أولاً إلى عرض البيانات على الشاشة. أفضل طريقة هنا هي إنشاء دالة مثل displayProducts() تتولى بناء عناصر HTML اعتماداً على المصفوفة المرسلة إليها.
const container = document.querySelector('.products');
const displayProducts = (products) => {
let result = '';
products.forEach(({ name, price, ram }) => {
result += `
<div class="product">
<div><strong>Name:</strong> <span>${name}</span></div>
<div><strong>Price:</strong> <span>${price}</span></div>
<div><strong>Ram:</strong> <span>${ram} GB</span></div>
</div>
`;
});
container.innerHTML = result;
};
displayProducts(products);
تعتمد هذه الدالة على عدة أفكار مهمة:
- الوصول إلى العنصر الحاوي باستخدام
document.querySelector(). - المرور على عناصر المصفوفة عبر
forEach(). - استخدام
template literalsلتوليد واجهة مرنة وسهلة القراءة. - استخدام
destructuringلاستخراجnameوpriceوramمباشرة من كل عنصر.
بهذه الخطوة، تظهر المنتجات مبدئياً للمستخدم قبل تطبيق أي عملية فرز.

إضافة منطق الفرز عند تغيير معيار الترتيب
بعد تجهيز العرض، ننتقل إلى ربط القوائم المنسدلة بالأحداث. أولاً نحصل على مراجع العناصر المطلوبة:
const sortByDropdown = document.querySelector('.sort-by');
const sortOrderDropdown = document.querySelector('.sort-order');
const container = document.querySelector('.products');
ثم نضيف مستمع حدث change إلى قائمة معيار الفرز:
sortByDropdown.addEventListener('change', () => {
const sortByValue = sortByDropdown.value;
const sortOrderValue = sortOrderDropdown.value;
const sorted = sortOrderValue === 'desc'
? descendingSort(sortByValue)
: ascendingSort(sortByValue);
displayProducts(sorted);
});
ما الذي يحدث هنا تحديداً؟
- قراءة القيمة المختارة من القائمة الأولى، مثل
priceأوram. - قراءة اتجاه الفرز من القائمة الثانية، مثل
ascأوdesc. - اختيار الدالة المناسبة للفرز بناءً على الاتجاه.
- إرسال النتيجة إلى
displayProducts()لإعادة تحديث الواجهة فوراً.
تنفيذ دوال الفرز التصاعدي والتنازلي
الآن نحتاج إلى كتابة الدوال التي تنفذ الفرز فعلياً. سنستخدم الدالة sort() الخاصة بالمصفوفات، مع دالة مقارنة comparator تحدد ترتيب العناصر.
دالة الفرز التصاعدي
const ascendingSort = (sortByValue) => {
return products.sort((a, b) => {
if (a[sortByValue] < b[sortByValue]) return -1;
if (a[sortByValue] > b[sortByValue]) return 1;
return 0;
});
};
دالة الفرز التنازلي
const descendingSort = (sortByValue) => {
return products.sort((a, b) => {
if (a[sortByValue] < b[sortByValue]) return 1;
if (a[sortByValue] > b[sortByValue]) return -1;
return 0;
});
};
السبب في استخدام الصيغة a[sortByValue] بدلاً من a.price أو a.ram هو أن قيمة الحقل ليست ثابتة، بل تأتي بشكل ديناميكي من اختيار المستخدم.
على سبيل المثال، إذا كان لدينا الكائن التالي:
const product = {
name: 'Macbook Air',
price: '180000',
ram: 16
};
فيمكن الوصول إلى خصائصه بطريقتين:
product.nameproduct['name']
وعندما تكون الخاصية متغيرة، تصبح الصيغة الثانية هي الأنسب.
كيف تعمل دالة المقارنة داخل sort()
آلية الفرز التصاعدي
في الفرز التصاعدي، تعمل الدالة وفق القواعد التالية:
- إذا كانت قيمة العنصر الأول أصغر من الثاني، نعيد قيمة سالبة مثل
-1. - إذا كانت قيمة العنصر الأول أكبر من الثاني، نعيد قيمة موجبة مثل
1. - إذا تساوت القيمتان، نعيد
0.
هذا النمط يجعل المصفوفة ترتب تلقائياً من الأصغر إلى الأكبر.
آلية الفرز التنازلي
في الفرز التنازلي نعكس المنطق:
- إذا كانت قيمة العنصر الأول أصغر من الثاني، نعيد قيمة موجبة.
- إذا كانت قيمة العنصر الأول أكبر من الثاني، نعيد قيمة سالبة.
- إذا كانت القيمتان متساويتين، نعيد
0.
والنتيجة هي ترتيب العناصر من الأكبر إلى الأصغر.
تفعيل الفرز عند تغيير اتجاه الترتيب
لا يكفي أن نستجيب لتغيير معيار الفرز فقط، بل يجب أيضاً تحديث النتائج عند تغيير اتجاه الفرز. لذلك نضيف مستمع حدث آخر إلى القائمة الثانية:
sortOrderDropdown.addEventListener('change', () => {
const event = new Event('change');
const sortByValue = sortByDropdown.value;
if (sortByValue) {
sortByDropdown.dispatchEvent(event);
}
});
هذا الأسلوب يعيد تشغيل منطق الفرز نفسه عبر إطلاق حدث change على قائمة معيار الفرز، بشرط أن يكون المستخدم قد اختار معياراً بالفعل. وجود الشرط if (sortByValue) مهم حتى لا تُنفذ عملية فرز غير مكتملة أو غير مفهومة للمستخدم.

تبسيط الفرز باستخدام مكتبة Lodash
إذا كنت تفضل تقليل الكود اليدوي، فيمكنك الاستفادة من مكتبة Lodash التي توفر الدالة _.orderBy() لإنجاز المهمة بطريقة أكثر اختصاراً ووضوحاً.
sortByDropdown.addEventListener('change', () => {
const sortByValue = sortByDropdown.value;
const sortOrderValue = sortOrderDropdown.value;
const sorted = _.orderBy(products, [sortByValue], sortOrderValue);
displayProducts(sorted);
});
عند استخدام _.orderBy() لن تحتاج إلى الدالتين ascendingSort() وdescendingSort(). وتعمل هذه الدالة وفق المعاملات التالية:
- المعامل الأول: المصفوفة المراد ترتيبها، وهي هنا
products. - المعامل الثاني: الحقل أو الحقول المستخدمة في الفرز، مثل
[sortByValue]. - المعامل الثالث: اتجاه الفرز، مثل
ascأوdesc.
هذه الطريقة تمنحك كوداً أنظف وأسهل في الصيانة، خصوصاً عندما تتوسع متطلبات المشروع لاحقاً.
أفضل ممارسات مهمة عند بناء ميزة الفرز
حتى لو كانت الفكرة بسيطة، فإن تنفيذها بشكل احترافي يتطلب مراعاة بعض النقاط:
- احرص على أن تكون القيم الرقمية مخزنة بصيغة رقمية كلما أمكن، وليس كسلاسل نصية
string، لتجنب نتائج غير دقيقة في المقارنة. - افصل بين منطق البيانات وواجهة العرض، بحيث تبقى دالة
displayProducts()مسؤولة فقط عن الإظهار. - تأكد من وجود قيم افتراضية واضحة داخل القوائم المنسدلة مثل
Select one. - اختبر سلوك التطبيق عند عدم اختيار المستخدم لأي معيار فرز.
- إذا كانت البيانات كبيرة، ففكر في تنفيذ الفرز على مستوى الخادم أو باستخدام تقنيات أكثر كفاءة.
متى تستخدم الفرز اليدوي ومتى تعتمد على Lodash؟
| الحالة | الخيار الأنسب | السبب |
|---|---|---|
| مشروع صغير أو تعليمي | الفرز اليدوي باستخدام sort() |
لفهم المنطق الداخلي والتحكم الكامل |
| مشروع متوسط أو كبير | Lodash مع _.orderBy() |
لتقليل الكود وتبسيط الصيانة |
| وجود معايير فرز متعددة | Lodash |
لأنه يتعامل بسهولة مع أكثر من حقل |
الخلاصة التقنية
إضافة ميزة الفرز إلى التطبيق ليست مجرد تحسين بصري، بل عنصر أساسي في تجربة المستخدم وتنظيم البيانات. باستخدام JavaScript يمكنك تنفيذ ذلك عبر sort() ودوال المقارنة المخصصة، بينما تمنحك Lodash حلاً أسرع وأكثر أناقة عبر _.orderBy(). من الناحية التقنية، أنصح بالبدء بالفهم اليدوي للفرز أولاً، ثم الانتقال إلى المكتبات المساعدة عند الحاجة إلى التوسع أو تبسيط الشيفرة.