البحث في الموقع
المحتوى عن 'jquery'.
-
توفّر jQuery أدوات قويّة لإيجاد العنصر أو العناصر التي تريدها في الصّفحة، ثمّ العمل بهذه العناصر للوصول إلى النّتيجة المرغوبة. تسهّل jQuery بأدواتها هذه عمليّات كانت لتكون أكثر تعقيدًا لو أردنا تنفيذها من خلال وظائف DOM الأصليّة. سنطّلع في هذا الجزء على بعض (لا كلّ) وظائف الانتقال عبر الصّفحة وتّعديل العناصر في jQuery. وقبل أن نبدأ، علينا فهم بعض المصطلحات الضّروريّة. لنفترض أنّ لدينا نصّ HTML التّالي: <ul> <li> <span> <i>Foo</i> </span> </li> <li>Bar</li> </ul> نقول عن عنصر القائمة الأوّل (<li>) أنّه ابن (child) القائمة غير المرتّبة (<ul>). نقول عن القائمة غير المرتّبة (<ul>) أنّها والد (parent) عنصري القائمة الاثنين. نقول عن العنصر <span> أنّه خَلَفُ (descendant) القائمة غير المرتّبة. نقول عن القائمة غير المرتّبة أنّها سَلَفٌ (ancestor) لكلّ ما داخلها. نقول عن عنصري القائمة أنّهما شقيقان (siblings). الانتقال عبر الصّفحة (Traversal) تسمح jQuery لنا بالانتقال عبر عناصر HTML الّتي تكوّن صفحتنا. إذ نُنشئ أوّلًا تحديدًا مبدئيًّا ثمّ ننتقل عبر DOM انطلاقًا منه. وخلال مسيرنا عبر DOM، فإنّنا نُغيّر من تحديدنا الأوّل فنضيف إليه أو نحذف منه بعض العناصر، أو نستبدل به تحديدًا آخر بالكامل في بعض الأحيان. تصفية التّحديدات بإمكانك تصفية تحديد موجودٍ بحيث يتضمّن فقط العناصر الّتي تطابق معاييرَ مُحدّدة. بإمكانك مثلًا إجراء التّصفية بإحدى الطّرق التّالية: var listItems = $( 'li' ); // صفِّ التّحديد ليحوي فقط العناصر ذات الصّنف 'special' var special = listItems.filter( '.special' ); // صفّ التّحديد ليحوي فقط العناصر من غير الصّنف 'special' var notSpecial = listItems.not( '.special' ); // صفّ التّحديد ليحوي فقط العناصر الّتي تتضمّن span var hasSpans = listItems.has( 'span' ); من المهمّ أن تعرف أن الوظيفة .not() ليست عكس .is()، لأنّ .is() تُعيد قيمة منطقيّة (true أو false)، بينما تُعيد .not() كائن jQuery جديدًا. إيجاد العناصر انطلاقًا من تحديد يمكن الاستفادة من تحديد أوّليّ كأساس لإنشاء تحديدات إضافيّة؛ فإذا كان لديك تحديدٌ يحوي عنصر قائمة مُفردًا مثلًا، وأردت التّعامل مع "أشقّائه" أو مع القائمة غير المُرتّبة الّتي تحويه، فبإمكانك إنشاء تحديد جديد انطلاقًا من التّحديد الموجود بسهولة: // اختر أوّل عنصر قائمة في الصّفحة var listItem = $( 'li' ).first(); // أيضًا: .last() // اختر أشقّاء عنصر القائمة var siblings = listItem.siblings(); // اختر الشّقيق التّالي لعنصر القائمة var nextSibling = listItem.next(); // أيضًا: .prev() // اختر والد عنصر القائمة var list = listItem.parent(); // اختر عناصر القائمة الّتي تنحدر مباشرةً من القائمة var listItems = list.children(); // اختر كلّ عناصر القائمة ضمن القائمة، بما في ذلك العناصر الفرعيّة var allListItems = list.find( 'li' ); // اختر كل أسلاف عنصر القائمة ذوي الصّنف "module" var modules = listItem.parents( '.module' ); // اختر أقرب سلفٍ لعنصر القائمة له الصّنف "module" var module = listItem.closest( '.module' ); بإمكانك كذلك الإضافة على التّحديد الحاليّ باستخدام الوظيفة .add()، الّتي تقبل مُحدِّدًا أو مصفوفة عناصر أو نص HTML أو كائن jQuery. var list = $( '#my-unordered-list' ); // افعل شيئًا ما بالقائمة ثم ... var listAndListItems = list.add( '#my-unordered-list li' ); العودة إلى التّحديد الأصليّ تحتفظ jQuery بإشارة إلى تحديد الأصليّ عندما تستخدمه للانتقال إلى تحديدات أخرى انطلاقًا منه، في حال أردت العودة إلى التّحديد الأصليّ. افترض مثلًا أنّك حدّدت قائمة غير مرتّبه، ثمّ أردت التّعديل على عناصر القائمة، ثمّ العودة مجدّدًا للعمل على القائمة غير المرتّبة، عندها بإمكانك استخدام الوظيفة .end() للرّجوع إلى التّحديد الأصليّ: $( '#my-unordered-list' ) .find('li') // نحن الآن نعمل على عناصر القائمة .addClass('special') .end() // عدنا الآن للعمل على القائمة ذاتها .addClass('super-special'); تُسهِّل الوظيفة .end() إجراء تعديلات كثيرة في جملة واحدة، إلّا أنّ هذا الأسلوب لا يُلقي بالًا لوضوح النّصّ البرمجيّ، فهو أشبه بأن تحكي قصّة دون أن تلتقط أنفاسك. لهذا السّبب لا أنصحك بالإكثار من استعماله، فهو يؤدّي في معظم الحالات إلى جعل قراءة النّصّ البرمجيّ وصيانته وتنقيحه أكثر صعوبة. فيما يلي حلّ أفضل للمشكلة ذاتها: var list = $( '#my-unordered-list' ); var listItems = list.find('li'); listItems.addClass( 'special' ); list.addClass( 'super-special' ); توفّر jQuery أيضًا الوظيفة .addBack() إن أردت إضافة تحديدك الأصليّ إلى التّحديد الحاليّ. مثال: $( 'li.special' ) .siblings() // نحن نعمل الآن على أشقّاء التّحديد السّابقة .removeClass( 'important' ) .addBack() // الآن نعمل على عناصر القائمة الأصليّة وأشقائها **معًا** .addClass( 'urgent' ); هل اختلط عليك الأمر؟ الوظيفة .addBack() تشبه الوظيفة .end() في عيوبها، فكلاهما (وإن كان لهما استخدامها) يزيدان تعقيد النّصّ البرمجيّة. الحلّ الأفضل هو استخدام الوظيفة .add() لدمج التّحديدين الأصليين معًا: var specialListItems = $( 'li.special' ); var otherListItems = specialListItems.siblings(); otherListItems.removeClass( 'important' ); specialListItems.add( otherListItems ).addClass( 'urgent' ); هناك وظائف عديدة لم نتطرّق إليها هنا، يمكنك الاطّلاع عليها في وثائق الانتقال عبر الصّفحة. التّعامل مع العناصر (Manipulation) تسمح وظائف التّعامل مع العناصر في jQuery بتغيير DOM الصّفحة بصياغة أكثر بساطة من تلك الّتي توفّرها وظائف DOM الخام. تُعيد وظائف التّعامل مع العناصر في jQuery كائن jQuery الّتي استدعيت للعمل عليه، وهذا يعني إمكانيّة ربطها في سلسلة أو دمجها مع وظائف jQuery أخرى كالّتي ناقشناها في الفقرات السّابقة. تعديل العناصر كثيرةٌ هي طرق تعديل العناصر في jQuery. سنطّلع فيما يلي على طرق إنجاز المهام الأكثر شيوعًا. إضافة أو حذف الأصناف (classes) يمكن الاستفادة من أصناف الكائنات في HTML بأن نستهدفها في CSS بغرض تنسيقها، كما يُستفاد منها في إنشاء تحديدات jQuery. فمثلًا يمكن لعنصر في الصّفحة أن يقع تحت الصّنف hidden، والّذي يُستخدم في CSS لجعل خاصّة display موافقة للقيمة none للعناصر من هذا الصّنف، ثمّ يمكن حذف هذا الصّنف أو إضافته لتغيير حالة ظهور العناصر الموافقة في jQuery: $( 'li' ).addClass( 'hidden' ); $( 'li' ).eq( 1 ).removeClass( 'hidden' ); جرّب المثال في ساحة التّجربة (تأكد من ضغط زر Run with JS في هذا المثال وكلّ الأمثلة التالية) إن تطلّبت حالتك إضافة صنفٍ أو حذفه مرارًا، فبإمكانك استخدام الوظيفة .toggleClass() الّتي تُبدّل حالة الصّنف على العنصر، فتضيفه إن لم يكن موجودًا أو تحذفه إن وُجد: $( 'li' ).eq( 1 ).toggleClass( 'hidden' ); جرّب المثال في ساحة التّجربة تغيير المظهر ملاحظة: يُفضّل دومًا استخدام الأصناف واستهدافها بقواعد CSS لتغيير طريقة عرض العناصر، والاقتصار على استخدام jQuery في إضافة هذه الأصناف أو حذفها كما ورد للتوّ. في هذه الفقرة سنتعرّف كيف نُغيّر مظاهر العناصر مُباشرةً في jQuery، ولكننا نُفضِّل دومًا الأسلوب الأوّل إن كان يُحقِّق النّتائج ذاتها. عندما تعجز عن تحقيق هدفك بإضافة الأصناف أو حذفها، فإنّ jQuery تقدّم الوظيفة .css() الّتي تسمح بتعيين مظهر العناصر مباشرةً، ولعلّ هذا يكون ضروريًّا عادةً عندما تحتاج إلى إسناد قيم عدديّة لا يمكن حسابها إلّا أثناء عمل التّطبيق، كمعلومات توضّع العناصر في الصّفحة. لا يُفضَّل استخدام الوظيفة .css() لإجراء تنسيقات بسيطة مثل display: none، بل يُفضَّل في معظم الحالات إنجاز الغاية ذاتها باستخدام الأصناف وCSS. افترض مثلًا أنّنا نريد تعيين مظهر العنصر بالاعتماد على عرض والده، وربّما يصعب أو يستحيل معرفة عرض الوالد مُسبقًا عند اعتماد تخطيط مرنٍ للصّفحة. في هذه الحالة قد نلجأ إلى الوظيفة .css() لتنسيق العنصر: var list = $( '#my-unordered-list' ); var width = Math.floor( list.width() * 0.1 ); list.find('li').each(function( index, elem ) { var padding = width * index; $( elem ).css( 'padding-right', padding + 'px' ); }); جرّب المثال في ساحة التّجربة إن احتجت إلى تعيين أكثر من خاصّة في وقت واحدٍ، مرّر كائنًا إلى الوظيفة .css() بدلًا من اسم الخاصّة وقيمتها. لاحظ أنّ عليك إحاطة أيّة خاصّة تحوي الرّمز "-" بعلامتي اقتباس: $( 'li' ).eq( 1 ).css({ 'font-size': '20px', 'padding-right': '20px' }); جرّب المثال في ساحة التّجربة تغيير قيم النّماذج (forms) تقدّم jQuery الوظيفة .val() لتعديل قيمة العناصر في النّماج مثل input وselect. بإمكانك تمرير سلسلة نصّيّة لتعيين محتوى حقول input النّصّيّة: $( 'input[type="text"]' ).val( 'new value' ); جرّب المثال في ساحة التّجربة بالنّسبة للعناصر من نوع select، بإمكانك تعيين الخيار المُختار باستخدام .val() أيضًا: $( 'select' ).val( '2' ); جرّب المثال في ساحة التّجربة أمّا لحقول input من نوع checkbox، فعليك تعيين الخاصّة checked على العنصر بالوظيفة .prop(). $( 'input[type="checkbox"]' ).prop( 'checked', 'checked' ); جرّب المثال في ساحة التّجربة ملاحظة: أُضيفت الوظيفة .prop() في الإصدارة 1.6 من jQuery؛ وقبل ذلك كانت تُستخدم الوظيفة .attr() للغرض ذاته، وهي ما تزال تعمل في الإصدارات الحديثة من jQuery، ولكنّها في حالة checked تكتفي باستدعاء .prop(). إن كنت تستخدم إصدارةً أحدث من 1.6، فأنصحك باستخدام .prop() دومًا لتعيين الخاصّة checked وخصائص عناصر DOM الأخرى. اطّلع على الوثائق لتفاصيل أكثر. تغيير الصّفات (attributes) الأخرى بإمكانك استخدام وظيفة .attr() لتغيير صفات العناصر، فيمكنك مثلًا تغيير عنوان رابط (الخاصّة title لعنصر <a>): $( 'a' ).attr( 'href', 'new title'); عند تعيين قيمة لصفة، بإمكانك تمرير دالّة في موضع المُعامل الثّاني للوظيفة، ومثلها مثل كلّ دوالّ الكتابة السّابقة، تتلقّى هذه الدّالّة مُعاملين اثنين: دليل العنصر الّذي تعمل عليه، والقيمة الحاليّة للصّفة. يجب أن تُعيد هذه الدّالة القيمة الجديدة للصّفة: $( 'a' ).attr( 'href', function(index, value) { return value + '?special=true'; }); جرّب المثال في ساحة التّجربة بإمكانك حذف الصّفات أيضًا، وذلك باستخدام .removeAttr(). الحصول على معلومات من العناصر ناقشنا في الجزء السّابق (أساسيّات jQuery) فكرة وظائف القراءة والكتابة. كلّ الوظائف الّتي يمكن استخدامها لتغيير العناصر، يمكن أيضًا استخدامها لقراءة معلومات من تلك العناصر. فيمكن مثلًا استخدام الوظيفة .val() الّتي وصفناها أعلاه كوظيفة قراءة وكتابة معًا: var input = $( 'input[type="text"]' ); input.val( 'new value' ); input.val(); // returns 'new value' وكذلك الأمر بالنّسبة للوظيفة .css()، إذ يمكن استخدامها لقراءة قيمة خصائص CSS مُفردة بإمرار اسم الخاصّة فقط دون قيمة: var listItemColor = $( 'li' ).css( 'color' ); عندما تُستخدم وظائف التّعامل مع العناصر للقراءة، فإنّها تعمل فقط مع العنصر الأول في التّحديد، باستثناء الوظيفة .text() الّتي تقرأ المحتوى النّصيّ لكلّ العناصر المُحدّدة إن لم يُمرّر معامل إليها. إضافة العناصر إلى الصّفحة سواءٌ حدّدت عنصرًا أو أنشأت واحدًا جديدًا، فبإمكانك إضافة هذا العنصر إلى الصّفحة يمكن فعل ذلك بطريقتين: باستدعاء وظيفة تتبع للعنصر (أو العناصر) المطلوب إضافته، أو باستدعاء وظيفة تتبع لعنصر مرتبط بذلك الّذي تريد إضافته. افترض مثلًا أنّك تريد نقل عنصر في قائمة من رأسها إلى ذيلها، هناك عدّة طرق لفعل ذلك. بإمكانك مثلًا إضافة العنصر إلى القائمة باستدعاء الوظيفة .appendTo() على عنصر القائمة ذاته: var listItem = $( '#my-unordered-list li' ).first(); listItem.appendTo( '#my-unordered-list' ); جرّب المثال في ساحة التّجربة وبإمكانك أيضًا إضافة العنصر باستدعاء .append() على القائمة: var listItem = $( '#my-unordered-list li' ).first(); $( '#my-unordered-list' ).append( listItem ); أو إضافته باستدعاء .insertAfter() على العنصر المُراد نقله مُمرّرًا العنصر الأخير في القائمة إلى الوظيفة: var listItems = $( '#my-unordered-list li' ); listItems.first().insertAfter( listItems.last() ); جرّب المثال في ساحة التّجربة أو إضافته باستدعاء .after() على العنصر الأخير في القائمة مُمرًّرا العنصر الأولى في القائمة إلى الوظيفة: var listItems = $( '#my-unordered-list li' ); listItems.last().after( listItems.first() ); جرّب المثال في ساحة التّجربة هناك طرق آخرى كثير لإضافة العناصر، فبإمكانك إضافتها حول عناصر أخرى أو داخلها أو خارجها بحسب حاجتك. تعتمد أكثر الطّرق كفاءة في إضافة عنصر إلى الصّفحة على العناصر الّتي تتوفّر بين يديك بالفعل. فقد ترغب في إضافة العنصر إلى القائمة غير المُرتّبة في المثال السّابق إن كنت قد حدّدت القائمة غير المرتّبة من قبل لغرض آخر؛ أو إن كنت قد حدّدت عناصر القائجة جميعها، فقد يكون إضافة العنصر الأول بعد العنصر الأخير أمرًا أسهل. عندما تختار الطّريقة المناسبة لإضافة العنصر، فلا تكتفِ بالنّظر في سهولة الطّريقة، بل فكّر في إمكانيّة صيانتها لاحقًا. تجنّب الطّرق الّتي تعتمد على افتراض بنية مُحدِّدة بدقَّة لصفحتك، فقد تقرّر تغيير هذه البنية فيما بعد. نسخ العناصر بإمكانك إنشاء نسخة من عنصر أو مجموعة عناصر باستخدام الوظيفة .clone() في jQuery، وستُنشئ النُّسخة في الذّاكرة دون أن تُدرج في الصّفحة، فعليك فعل ذلك بنفسك إن أردته. بإمكانك تعديل العناصر المنسوخة قبل إضافتها: var clones = $( 'li' ).clone(); clones.html(function( index, oldHtml ) { return oldHtml + '!!!'; }); $( '#my-unordered-list' ).append( clones ); جرّب المثال في ساحة التّجربة ملاحظة*: لن تمنعك jQuery من نسخ عنصر ذي مُعرِّف (ID)، ولكن عليك التأكّد من حذف المُعرِّف أو تغييره في العنصر المنسوخ بتعديل الصّفة id قبل إدراجه في المستند، إذ لا ينبغي أ يوجد عنصران بمُعرِّف واحدٍ في الصّفحة. حذف العناصر هناك ثلاث طرق لحذف العناصر من الصّفحة: .remove() و.detach() و.replaceWith()، ولكلّ منها غرضٌ مُختلف. يجب استخدام .remove() عند الحاجة لحذف العناصر بصورة دائمة، فهي ستحذف مع العنصر كلّ مُتوليّات الأحداث المُرتبطة به (event handlers). تُعيد الوظيفة .remove() إشارةً إلى العناصر المُحذوفة، ولكن عند إضافة هذه العناصر مرّة ثانيةً، فلن تكون أيّة أحداث مُرتبطةً بها. $( '#my-unordered-list li' ).click(function() { alert( $( this ).text() ); }); var removedListItem = $( '#my-unordered-list li' ).first().remove(); removedListItem.appendTo( '#my-unordered-list' ); removedListItem.trigger( 'click' ); // لا رسالة تنبيه! جرّب المثال في ساحة التّجربة تُفيد الوظيفة .detach() في حذف العناصر مؤقّتًا من الصّفحة، فمثلًا إن رغب في إجراء تعديلات كبيرة على بنية الصّفحة باستخدام jQuery، فقد يكون حذف العناصر مؤقّتًا من الصّفحة ثمّ إضافتها ثانيةً أفضل أداءً بمراحل. ستحتفظ العناصر المحذوفة بهذه الوظيفة بمُتولّيات الأحداث المُرتبطة بها، ويمكن بعد ذلك إضافتها إلى الصّفحة مُجدّدًا باستخدام .appendTo() أو غيرها من وظائف الإضافة. $( '#my-unordered-list li' ).click(function() { alert( $( this ).text() ); }); var detachedListItem = $( '#my-unordered-list li' ).first().detach(); // افعل شيئًا ما مُعقّدًا بعنصر القائمة detachedListItem.appendTo( '#my-unordered-list' ); detachedListItem.trigger( 'click' ); // alert! أخيرًا لدينا الوظيفة .replaceWith() الّتي تُحلِّ عنصرًا أو نصّ HTML محلّ عنصرٍ أو عناصر أخرى. تُعاد العناصر الّتي أُزيلت من الوظيفة، ولكنّ كلّ متولّيات الأحداث المرتبطة بها تُحذف، تمامًا كالوظيفة .remove(). $( '#my-unordered-list li' ).click(function() { alert( $( this ).text() ); }); var replacedListItem = $( '#my-unordered-list li' ).first() .replaceWith( '<li>new!</li>' ); replacedListItem.appendTo( '#my-unordered-list' ); replacedListItem.trigger( 'click' ); // لا رسالة تنبيه! جرّب المثال في ساحة التّجربة خاتمة تعلّمنا في هذا الجزء الطّرق المختلفة للانتقال بين العناصر في الصّفحة، وكيفيّة نقلها وتغييرها وإضافة عناصر جديدة. سنتعلّم في الجزء القادم كيف نُنصِت لتفاعل المستخدم مع صفحتنا. ترجمة (بشيء من التصرف) للجزء الثالث من سلسلة jQuery Fundamentals لمؤلّفتها Rebecca Murphey.
-
يوفر بوتستراب بيئة عمل جيدة لبناء واجهات صفحات الانترنت بشكل احترافي وسهل الاستخدام من قبل زوار الموقع، بالإضافة إلى التصاميم الجذابة المريحة للعين في التصفح والانتقال ضمن الموقع. سنقوم اليوم ببناء قائمة شجرية قابلة للتوسع أو الطي collapse tree grid ضمن القائمة الجانبية للموقع، والتي تعد أحد الأعمدة الأساسية عند بناء المواقع القائمة في ترتيب صفحاتها على التصنيف ضمن بنية شجرية. يمكن الاستخدام في بناء القائمة الشجرية إضافة بنيت من قبل Pomazan Max ونشرها على GitHub وتخضع هذه الإضافة لشروط اتفاقية MIT من خلال استخدام ملفي jquery.treegrid.js و jquery.treegrid.css سنقوم الآن بتخصيص بناء هذه الشجرة وإكمالها. أولًا من المهم إضافة ملفات css التي نستخدمها لبناء الشجرة وهي الملفات الخاصة بنا Main ملف الخاص بالقائمة الشجرية jquery.treegrid.css وملفات البوتستراب <link rel="stylesheet" href="css/bootstrap.min.css"> <link rel="stylesheet" href="css/bootstrap-theme.min.css"> <link rel="stylesheet" href="css/main.css"> <link rel="stylesheet" href="css/jquery.treegrid.css"> نقوم ببناء panel-group لنضمن داخله العقد الثلاث. المعرف الخاص id والذي يملك القيمة accordion هو أحد معرفات bootstrap ويفيد من أجل التبديل بين العقد المفتوحة أو المطويّة <div class="panel-group" id="accordion"> </div> نضيف عنوان للقائمة الجانبية <h3>Try Tree Gird Sidebar</h3> لبناء العقدة الأولى node1 نضيف التعليمات حيث أضفنا node1 كعنوان للقائمة التي تحته node1-1... ولذلك ضمناه تحت اسم الصف panel-heading أما بالنسبة للقائمة التي تحته فقد تم بناؤها كقائمة حيث يضاف إلى كل عنص من القائمة صف list-group-item وبنفس الطريقة قمنا بإضافة قائمة فرعية ضمن عنصر القائمة node1-3 من أجل تغيير شكل العنوان لكل عقدة رئيسية node1، node2، node3 من خلال إضافة أحد الصفوف panel-info والتي تعطي اللون الأزرق panel-danger وتعطي اللون الأحمر و panel-success وتعطي اللون الأخضر <div class="panel panel-info"> <div class="panel-heading"> <div class="panel-title"> <a data-toggle="collapse" data-parent="#accordion" href="#node1">Node1</a> </div> </div> <div id="node1" class="panel-collapse collapse in"> <div class="panel-body"> <div class="list-group-item">Node1-1</div> <div class="list-group-item">Node1-2</div> <div class="list-group-item"> <div class="panel-title"> <a data-toggle="collapse" href="#node1-3"> Node1-3 <span class="glyphicon glyphicon-chevron-down"></span> </a> </div> <div id="node1-3" class="panel-collapse collapse"> <ul class="list-group"> <li class="list-group-item">node1-3-1</li> <li class="list-group-item">node1-3-2</li> <li class="list-group-item">node1-3-3</li> </ul> </div> </div> </div> </div> </div> وبنفس الطريقة السابقة نبني العقد node2 و node3 في حال أردت أي تغيير للقائمة تستطيع القيام بذلك: لإضافة عقدة جديدة وعنوان جديد يتم من خلال إضافة panel جديد ضمن panel-group لإضافة عنصر تابع لـ panel يتم إضافته كـ list-group-item يمكن إضافة لكل من العقد الداخلية رابط للتتمكن من الولوج إلى صفحة مختلفة من خلال وضع اسم العقدة ضمن رمز الرابط <a>node1-1</a> وفي نهاية ملف html نضيف ملفات الجافات سكربت التي نستخدمها في البوتستراب والخاص ببناء القائمة الشجرية jquery.treegrid.js <script src="js/vendor/jquery-1.11.2.min.js"></script> <script src="js/vendor/bootstrap.min.js"></script> <script src="js/vendor/jquery.treegrid.js"></script> <script src="js/main.js"></script> ضمن ملف الجافا سكربت Main.js نبني التابع الذي يقوم بفتح وإغلاق كل عقدة عند النقر عليها وذلك من خلال إضافة الصف active عند فتح العقدة إليها function openNode(evt, nodeName) { // Declare all variables var i, tablinks; // Get all elements with class="tablinks" and remove the class "active" tablinks = document.getElementsByClassName("tablinks"); for (i = 0; i < tablinks.length; i++) { tablinks[i].className = tablinks[i].className.replace(" active", ""); } // Show the current tab, and add an "active" class to the link that opened the tab document.getElementById(nodeName).style.display = "block"; evt.currentTarget.className += " active"; } ونضيف التابع الذي يقوم ببداية تحميل الصفحة إغلاق جميع العقد التي كانت مفتوحة أي إعادة كل المتغيرات إلى الحالة الافتراضية $(document).ready(function () { $('#accordion').find('.collapse').collapse('hide'); var $myGroup = $('#accordion'); $myGroup.on('show.bs.collapse', '.collapse', function () { $myGroup.not($(this).parents()).find('.collapse.in').collapse('hide'); }); } ); وبذلك نكون قد أنشأنا قائمة شجرية قابلة للطي والتوسع ديناميكية تمكّن المستخدم من الاطلاع على محتويات الموقع والانتقال بين صفحاته بسهولة
-
أصبحت تطبيقات الويب ذات الصفحة الوحيدة Single Page Apps رائجةً في هذه الفترة في تطوير الويب، فأمسى كل شخصٍ يريد أن ينُشِئ تطبيق ويب ذا صفحةٍ وحيدة. سأريك في هذا الدرس طريقةٍ سهلة لإنشاء تطبيقات الويب ذات الصفحة الوحيدة باستخدام jQuery ودون استخدام أيّة إطارات عمل مثل React أو Angular أو Vue …إلخ. لمحة لأننا نريد إنشاء تطبيق ويب ذا صفحةٍ وحيدة، فسنستخدم sammy.js للتوجيه (routing)، مكتبة Sammy هي مكتبة jQuery بمساحة تخزينية لا تتجاوز 5.2 كيلوبايت. سيبدو سكربت التوجيه المكتوب لإضافة Sammy كما يلي: var app = $.sammy(function() { this.get('#/', function() { //your function }); this.get('#about/', function() { //your function }); this.get('#contact/', function() { //your function }); }); علينا أولًا تهيئة التطبيق باستخدام $.sammy وتخزين نسخة من الكائن في المتغير app. يمكننا تعريف تعليمة «توجيه» (route) في sammy بالطريقة الآتية: this.get('path/',function(){ // ... }); هنالك دالة ستُستدعى لكل عملية توجيه، والتي يمكن أن نكتب بداخلها البنية المنطقية لها، ونربط البيانات اللازمة إلى كل «صفحة»، لأن كل عملية توجيه ستؤدي إلى إظهار «صفحة» مختلفة. بعد تعريف كل تعليمات التوجيه، فسنتمكن من تشغيل تطبيق الويب ذي الصفحة الوحيدة باستدعاء الدالة run() التابعة للكائن app كالتالي app.run(). تطبيق التدوين الذي سننشئه لتوضيح المفهوم الذي سنشرحه، فسنحاول إنشاء مثال واقعي. لنفترض أننا نريد إنشاء مدونة بسيطة، التي يوجد فيها صفحة رئيسية (أي صفحة Home) وصفحة معلومات (About). سنُنشِئ في صفحة index قائمة بالتدوينات، والضغط على أي واحدة منها سيأخذنا إلى صفحتها. يمكنك أن تجرب هذا التطبيق عمليًا هنا. سنستخدم صيغة JSON لقائمة التدوينات، وسنستخدم إضافة Sammy لعرضها. بنية الملفات - index.html <!-- main layout --> - app.js <!-- stores all our routing logic --> - css --- style.css - js --- jquery-1.11.3.min.js --- sammy.min.js --- sammy.template.js - data --- articles.json - templates <!-- the templates pages that will be injected into the main layout --> --- article.template --- article-detail.template --- about.template شيفرة HTML سنستخدم قالب HTML boilerplate لإنشاء شيفرة HTML: <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="js/jquery-1.11.3.min.js" type="text/javascript"></script> <script src="js/sammy.min.js" type="text/javascript"></script> <script src="js/sammy.template.js" type="text/javascript"></script> <link rel="stylesheet" href="css/style.css" /> <script src="app.js"></script> </head> <body> <div class="header-container"> <header class="wrapper clearfix"> <nav> <ul> <li><a href="#/">Home</a></li> <li><a href="#/about/">About</a></li> <!-- defining nav url according to route--> </ul> </nav> </header> </div> <div class="main-container"> <div class="main wrapper clearfix"> <div id="app"> <!-- template will be injected here --> </div> </div> </div> </body> </html> تعريف التوجيهات //app.js (function($) { var app = $.sammy('#app', function() { this.use('Template'); this.around(function(callback) { var context = this; this.load('data/articles.json') .then(function(items) { context.items = items; }) .then(callback); }); this.get('#/', function(context) { context.app.swap(''); $.each(this.items, function(i, item) { context.render('templates/article.template', {id: i, item: item}) .appendTo(context.$element()); }); }); this.get('#/about/', function(context) { var str=location.href.toLowerCase(); context.app.swap(''); context.render('templates/about.template', {}) .appendTo(context.$element()); }); this.get('#/article/:id', function(context) { this.item = this.items[this.params['id']]; if (!this.item) { return this.notFound(); } this.partial('templates/article-detail.template'); }); this.before('.*', function() { var hash = document.location.hash; $("nav").find("a").removeClass("current"); $("nav").find("a[href='"+hash+"']").addClass("current"); }); }); $(function() { app.run('#/about/'); }); })(jQuery); هيئنا في البداية التطبيق داخل العنصر #app، حيث سنُضيف القوالب المختلفة بناءً على مسار التوجيه. سنستخدم أيضًا محرك قوالب باسم sammy template engine. this.use('Template'); سنحصل على بيانات المدونة من ملف articles.json باستخدام الدالة load() التابعة لمكتبة jQuery (يمكنك أيضًا استخدام $.get أو $.post)، وتخزين الناتج في المتغير context. حان الآن الوقت لتعريف تعليمة التوجيه لصفحة index باستخدام #/: this.get('#/', function(context) { context.app.swap(''); $.each(this.items, function(i, item) { context.render('templates/article.template', {id: i, item: item}) .appendTo(context.$element()); }); }); لدينا بيانات مخزنة في المتغير context، سنمرّ الآن عبر تلك البيانات عبر حلقة تكرار باستعمال الدالة $.each ثم عرضها في article.template. سنستخدم أيضًا الدالة context.app.swap() لكي نُفرِّغ العنصر الذي سنضع فيه المحتويات (أي #app) مما فيه قبل عرض القالب. <article> <section> <a href="#/article/<%= id %>"><h2><%= item.title %></h2></a> </section> </article> سنستخدم هنا مُحرِّك القوالب (templating engine) لكي نعرض القيم القابلة للتغيير باستخدام الصيغة <%= yourdata %>، يمكنك أيضًا إضافة رابط لصفحة تفاصيل كل مقالة باستخدام الصيغة #/article/<%= id %>. الضغط على رابط التدوينة سيأخذنا إلى قالب article-detail.template، حيث نستطيع إظهار صورة التدوينة وملخص عنها …إلخ. وخلف الكواليس، سنُعرِّف تعليمة توجيه إلى تفاصيل التدوينة في ملف app.js، وسنحصل على مُعرِّف التدوينة كمعامل (parameter) ثم نُمرِّر البيانات إلى article-detail.template. this.get('#/article/:id', function(context) { this.item = this.items[this.params['id']]; if (!this.item) { return this.notFound(); } this.partial('templates/article-detail.template'); }); وبشكلٍ شبيه، يمكننا إنشاء صفحة ثابتة باسم «about» ونعرضها عبر قالب about.template. this.get('#/about/', function(context) { var str=location.href.toLowerCase(); context.app.swap(''); context.render('templates/about.template', {}) .appendTo(context.$element()); }); لقد نسينا أهم خطوة، ألا وهي تهيئة التطبيق باستخدام app.run() حيث يمكنك تحديد مكان التوجيه الافتراضي لتطبيقك. إذا أردتَ أن تفتح صفحة about أولًا فيمكنك أن تكتب: $(function() { app.run('#/about/'); }); إذا أردتَ إجراء بعض العمليات قبل استدعاء كل تعليمة توجيه، فيمكنك استخدام الدالة before(). سنُجري في الأسطر الآتية تعديلاتٍ على قائمة التنقل (nav) اعتمادًا على مسار التوجيه الحالي: this.before('.*', function() { var hash = document.location.hash; $("nav").find("a").removeClass("current"); $("nav").find("a[href='"+hash+"']").addClass("current"); }); الخلاصة هذا درسٌ بسيطٌ جدًا لكي تأخذ فكرة عن كيفية عمل تطبيق ذي صفحةٍ وحيدةٍ باستخدام jQuery. يمكنك الآن المضي قدمًا وإنشاء تطبيقات ذات صفحة وحيدة. إحدى الأشياء التي عليك أن تأخذها بعين الاعتبار هي أنَّك ستحتاج إلى خادوم ويب لتشغيل التطبيق، ويمكنك أيضًا تجربة متصفح Firefox، لأنَّ متصفح Chrome يحجب طلبيات Ajax لأيّة بروتوكولات ما عدا http:// أو https://، لذا إذا كنتَ ستُشغِّل هذا السكربت على جهازك المحلي فلن يعمل لأنَّ مسارات الملفات المحلية تبدأ بالسابقة file://. ترجمة –وبتصرّف– للمقال Single Page Apps with jQuery Routing لصاحبه Arkaprava Majumder
- 1 تعليق
-
- single page apps
- spa
-
(و 1 أكثر)
موسوم في:
-
نسعى في هذا الدّرس إلى إنشاء قائمتين منسدلتين Dropdown lists ترتبط إحداهما بالأخرى. تحتوي الأولى مثلا على تصنيف والثانية على تصنيف فرعي من التصنيف الموجود في القائمة الأولى. يعني هذا أنْ تتغيّر التصنيفات الفرعيّة الموجودة في القائمة الثّانيّة عند تغيّر العنصُر المحدّد في القائمة الأولى. تبدو النتيجة بنهاية الخطوات المشروحة في هذا الدرس على النحو التالي: نستخدم Laravel في الجانب الخلفيّ Backend لتولي التعامل مع الطّلبات والتخاطب مع قاعدة البيانات للحصول على التصنيفات والتّصنيفات الفرعيّة منها. يتولّى سكربت jQuery العمل في الواجهة الأماميّة Frontend لتحديث محتويات القائمة الثانيّة عند تعديل محتوى الأولى. في ما يلي نظرة عامّة على الخطوات التي سنتّبعها: إنشاء النماذج Models والتهجيرات Migrations. تهيئة معمل نماذج Model factory وبذر Seed جداول البيانات. إعداد المسارات Routes والمتحكّمات Controllers. إعداد العروض Views. الطريقة التي نريد تنفيذها هي كالتالي: عند الدخول إلى المسار categories/ يتلقى ملف المسارات الطّلب ويحوّله إلى الدالّة categories في المتحكّم HomeController. تستقبل الداّلة الطلب وتطلب قائمة بالتّصنيفات من نموذج التّصنيف Category؛ ثم ترسل التّصنيفات إلى العرض categories الذي يعرضها في القائمة المنسدلة الأولى. نستخدم سكربت jQuery في العرض للإنصات لتغييرات القائمة المنسدلة العلويّة، وعند اختيّار أحد عناصرها يأخذ السكربت معرّف العنصر المحدَّد ثم يرسل به طلب Ajax إلى المسار api/category-dropdown/ الذي يجيب بلائحة التصنيفات الفرعيّة للتّصنيف المحدّد في القائمة الأولى. يستقبل السكربت اللائحة ويعرضها في القائمة الثانية. تمكنك مراجعة المقالات التاليّة لتفصيلات أكثرعن إنشاء النماذج، استخدام معمل النماذج و العروض. نبدأ بتثبيت Laravel بالأمر التالي: composer create-project --prefer-dist laravel/laravel laradropdown "5.3.*" انتظر اكتمال التثبيت ثم انتقل لمجلد المشروع laradropdown لمتابعة بقيّة الخطوات. سنفترض في ما يلي أن لاتصال بقاعدة البيانات مضبوط. إنشاء النماذج والتهجيرات نبدأ بإنشاء نموذجيْن Category و SubCategory. الأوّل للتصنيفات والثاني للتصنيفات الفرعيّة. ننفّذ ما يلي في مجلّد المشروع: php artisan make:model Category -m php artisan make:model SubCategory -m استخدمنا خيار m- لإنشاء التهجيرات مع إنشاء النماذج. نفتح ملفّ التهجير الخاصّ بالتصنيف ونعدّله: public function up() { Schema::create('categories', function (Blueprint $table) { $table->increments('id'); $table->string('name')->unique(); $table->timestamps(); }); } public function down() { Schema::dropIfExists('categories') } يحوي الملفّ كما هو ظاهر أربعة حقول: معرّفا، اسما للتّصنيف وحقليْ الأختام الزمنية. ننتقل لملفّ التهجير الخاصّ بالتصنيف الفرعي: public function up() { Schema::create('sub_categories', function (Blueprint $table) { $table->increments('id'); $table->string('name')->unique(); $table->integer('category_id')->unsigned(); $table->timestamps(); }); Schema::table('sub_categories', function (Blueprint $table) { $table->foreign('category_id') ->references('id') ->on('categories') ->onDelete('cascade'); }); } public function down() { Schema::table('sub_categories', function(Blueprint $table) { $table->dropForeign('sub_categories_category_id_foreign'); }); Schema::dropIfExists('sub_categories'); } يختلف ملفّ التهجير هذا قليلا عن الملفّ السّابق؛ إذ يحوي إلى جانب حقول المعرّف، الاسم والأختام الزمنيّة معرفَ التصنيف الذي يتفرّع منه. هذا المعرف هو مفتاح خارجي Foreign key يحيل إلى جدول التّصنيفات. نعرّف الحقول أولا ثم نحدّد الحقل category_id على أنه مفتاح خارجي. ينبغي أن يكون الحقل مطابقا تماما من حيث النوع للحقل الذي يحيل إليه. في الدالة down نبدأ بحذف القيد من على الحقل حتى يمكننا حذف الجدول. أنهينا إعداد التهجيرات. ننتقل لإعداد النماذج. نغيّر ملف النموذج Category على النحو التالي: class Category extends Model { protected $fillable = ['id', 'name']; public function sub_categories() { return $this->hasMany('SubCategory'); } } نحدّد أولا الحقول التي يمكن إسنادها (خاصيّة fillable) ثم نضيف الدالة sub_categories التي تعرّف العلاقة بين التّصنيف Category والتّصنيف الفرعي SubCategory. هذه العلاقة هي من النوع hasMany بمعنى أنه توجد بالتّصنيف تصنيفات فرعية تابعة له. يفيدنا تعريف العلاقات بين النماذج في الاستعلامات ويجعلها أسهل بكثير كما سنرى. نفس الشيء تقريبا بالنسبة للنموذج SubCategory: class SubCategory extends Model { protected $fillable = ['id', 'name', 'category_id']; public function category() { return $this->belongsTo('Category'); } } نعرّف في النموذج SubCategory العلاقة العكسيّة لتلك المعرّفة في النموذج Category؛ وهي belongsTo التي تعني أن هذا الصّنف يتبع للصّنف Category. النماذج والتهجيرات جاهزة؛ يمكننا الآن تنفيذ التهجيرات: php artisan migrate تهيئة معامل النماذج وبذر جداول البيانات سنستفيد من الخبرى التي تحصّلنا عليها في درس استخدام معمل النماذج (Model factory) في Laravel لتوليد بيانات الاختبار لتهيئة معملَيْ نماذج نستخدمهما لبذر البيانات في الجدوليْن categories و sub_categories. ننشئ ملفيْن في المجلّد /database/factories؛ واحدا باسم CategoryFactory.php والآخر SubCategoryFactory.php. ثم نضيف المحتوى التالي إلى CategoryFactory.php : <?php $factory->define(App\Category::class, function (Faker\Generator $faker){ return [ 'name' => $faker->unique()->word ]; }); نفس المبدأ المستخدَم في الدرس المُشار إليه أعلاه. نعرّف النموذج الذي نريد توليد بيانات له ثم نستخدم مكتبة Faker لملْء الحقل المحدَّد (name). طلبنا توليد كلمات فريدة Unique حتى نوافق الشّرط المعرَّف في تهجير قاعدة البيانات الخاصّ بالجدول categories. الأمر مختلف قليلا مع الملف SubCategoryFactory.php الذي نعدّله كالتالي: <?php $factory->define(App\SubCategory::class, function (Faker\Generator $faker){ $categories = App\Category::get()->pluck('id')->all(); return [ 'name' => $faker->unique()->word, 'category_id' => $faker->randomElement($categories), ]; }); بما أن الحقل category_id مفتاح خارجي على معرّف التّصنيف فلن تقبل قاعدة البيانات إضافة معرّف لتصنيف غير موجود في جدول التصنيفات. لتجاوز هذا القيد نبدأ بطلب معرّفات التصنيفات: $categories = App\Category::get()->pluck('id')->all(); ثم عند توليد بيانات للحقل category_id نطلب منه أن يختار واحدا عشوائيا من المعرّفات التي تحصّلنا عليها سابقا: 'category_id' => $faker->randomElement($categories), هذا كلّ شيء بالنسبة لمعمل النماذج. ننتقل للبذر. ننشئ صنفا سنستخدمه لبذر النموذجيْن Category و SubCategory: php artisan make:seeder SubCategoryTableSeeder نفتح الملف ونعدّله كما يلي: public function run() { App\SubCategory::truncate(); factory(App\Category::class, 10)->create(); factory(App\SubCategory::class, 50)->create(); } نطلُب في الملفّ توليد 10 تصنيفات و50 تصنيفا فرعيا. نعدّل ملف البذر DatabaseSeeder كما يلي: public function run() { Model::unguard(); $this->call(SubCategoryTableSeeder::class); Model::reguard(); } لا ننسى استدعاء النموذج Model في الملف DatabaseSeeder: use Illuminate\Database\Eloquent\Model; نستدعي ملفّ البذر SubCategoryTableSeeder الذي يستخدم معمليْ النماذج لتوليد البيانات. يمكننا الآن تنفيذ البذر: php artisan db:seed إعداد المسارات والمتحكمات نريد أن نصل إلى صفحة القائمتين المنسدلتين عبر الرابط categories/؛ لذا سنعرّف مسارا له في ملف مسارات الوِب routes/web.php: Route::get('/categories', 'HomeController@categories'); لا خصوصيةَ هنا؛ المسار والمتحكّم والدالة اللذان يعالجان الطّلب. إن لم يكن المتحكّم HomeController معرّفا لديك فاستخدم الأمر php artisan make:controller HomeController وأضف إليه دالة باسم categories: public function categories() { $categories = Category::orderBy('name', 'asc')->get(); return view('layouts.categories', compact('categories')); } لا تنس استيراد النموذج Category: use App\Category; نستخدم النموذج Category في الدالة للحصول على لائحة التصنيفات مرتبة تصاعديا حسب الاسم؛ ثم نرسل النتيجة إلى العرض categories الموجود في المجلّد layouts ضمن مجلّد العروض resources/views. هذا العرض غير موجود لحدّ الساعة لذا يجب أن ننشئه. لكن قبل ذلك يجب أن ننهي إعداد المتحكّمات. سنضع في العرض سكربت jQuery -كما أشرنا سابقا- للحصول على لائحة بالتصنيفات الفرعيّة للتّصنيف المحدّد في القائمة المنسدلة العلوية. يرسل السكربت طلب Ajax إلى المسار api/category-dropdown/. ننشئ هذا المسار في ملفّ المسارات الخاصّ بواجهة التطبيقات البرمجيّة routes/api.php على النحو التالي: Route::get('/category-dropdown', 'ApiController@categoryDropDownData'); يمكن أن نستخدم نفس المتحكّم السابق (HomeController) ونعرّف دالة فيه كما يمكن أن ننشئ متحكّما جديدا. اخترنا الحلّ الأخير حتى نفصل بين التحكّم في المسارات العادية والمسارات المعدّة لتكون واجهة برمجية Application programming interface, API. نستخدم الأمر التالي لإنشاء متحكّم باسم ApiController: php artisan make:controller ApiController ثم نعدّل عليه: public function categoryDropDownData() { $category_id = Input::get('category_id'); $subcategories = App\Category::find($category_id)->sub_categories; return Response::json($subcategories); } يتلقى المسار api/category-dropdown/ معرّف التّصنيف المحدّد في القائمة ضمن متغيّر category_id. نستقبل المتغيّر المُرسَل من المستخدم بالدّالة get من الصّنف Input. بما أننا عرّفنا علاقات بين التصنيف والتصنيف الفرعي في النموذجين المقابليْن فيمكننا بسهولة العثور على التّصنيفات الفرعيّة لتصنيف؛ كلّ ما علينا فعله هو استخدام الدّالة sub_categories التي عرّفناها في الصّنف Category. تعثُر التعليمة التالية على جميع التّصنيفات الفرعيّة للتّصنيف ذي المعرّف category_id. $subcategories = Category::find($category_id)->sub_categories; في الأخير نرمّز التصنيفات الفرعيّة بصيغة Json ثم نرسلها في الإجابة. لا تنس استدعاء الأصناف التي استخدمناها وإضافتها إلى بداية الملف: use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Input; use App\Category; المحصّلة: <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Input; use App\Category; class ApiController extends Controller { public function categoryDropDownData() { $category_id = Input::get('category_id'); $subcategories = Category::find($category_id)->sub_categories; return Response::json($subcategories); } } إعداد العروض أكملنا الإعدادات من جهة النهاية الخلفيّة؛ تبقى فقط إعداد العرض لاستقبال البيانات وتقديمها للزائر. ننشئ لهذا الغرض عرضا باسم categories.blade.php في المجلّد layouts. يمدّد هذا العرض عرضا رئيسا Master view اسمُه app.blade.php، يوجد في نفس المجلّد. ينفّذ categories.blade.php مقطعًا باسم content معرّفًا في العرض الرّئيس ويوجد به محتوى الصفحة. @extends('layouts.app') @section('content') <div class="container"> <div class="row"> <div class="col-md-10 col-md-offset-1"> <div class="panel panel-default"> <div class="panel-heading">Dashboard</div> <div class="panel-body"> <div class="form-group"> <label>Category:</label><br> <select class="form-control input-lg" name="category_id" id="category_id"> <option value="">Select Category</option> @foreach($categories as $category) <option value="{{ $category->id }}"> {{$category->name}}</option> @endforeach </select> </div> <div class="form-group"> <label>Subcategory:</label><br> <select class="form-control input-lg" name="subcategory_id" id="subcategory_id"> <option value="">First Select Category</option> </select> </div> </div> </div> </div> </div> </div> <script> $('#category_id').on('change', function(e){ var cat_id = e.target.value; //ajax $.get('/api/category-dropdown?category_id=' + cat_id, function(data){ //success data $('#subcategory_id').empty(); $('#subcategory_id').append('<option value=""> Please choose one</option>'); $.each(data, function(index, subcatObj){ $('#subcategory_id').append('<option value="' + subcatObj.id+'">' + subcatObj.name + '</option>'); }); }); }); </script> @endsection يستقبل المقطع content التصنيفات في المتغيّر categories ثم يستخدم دالة foreach التكراريّة لعرضها في القائمة المنسدلة: @foreach($categories as $category) <option value="{{ $category->id }}"> {{$category->name}}</option> @endforeach نضيف في المقطع script سكربت jQuery الذي سيتولى مزامنة محتوى القائمتيْن. يأخذ الجزء الأول من السكربت العنصر category_id الذي يمثّل القائمة الأولى ويستخدم الحدث on change لمراقبة التغيّرات عليه. نحتفظ بمعرّف التصنيف في المتغيّر cat_id: $('#category_id').on('change', function(e){ var cat_id = e.target.value; ثم يأتي دور نداء Ajax الذي يرسل المتغيّر cat_id إلى المسار api/category-dropdown/ ضمن المعطى category_id: $.get('/api/category-dropdown?category_id=' + cat_id, function(data) يخزّن النداء النتيجة في المتغيّر data. عند تلقّي الطلب ننفّذ ثلاثة أمور: حذف محتوى القائمة المنسدلة الثّانية لمحو المحتوى الموجود فيها (المحتوى الأصلي أو المحتوى المرتبط بعنصُر محدد سابقا في القائمة الأولى): $('#subcategory_id').empty(); ثم نضيف تعليمة جديدة تطلب من الزائر الاختيّار: $('#subcategory_id').append(' Please choose one'); ثم في الأخير نستخدم دالة each التكرارية في jQuery للمرور على البيانات (التصنيفات الفرعيّة) الواحدة تلو الأخرى وإضافتها إلى القائمة المنسدلة الثانيّة: $.each(data, function(index, subcatObj){ $('#subcategory_id').append('' + subcatObj.subcategory_name + '</option'); }); بالنسبة للعرض الرّئيس app.blade.php فقد استخدمنا هذا الملف. لم يتبق سوى زيارة الرابط categories/ لمعاينة النتيجة. ترجمة -وبتصرّف- للمقال Dependent Dropdown List with jquery in Laravel 5.1 لصاحبه Bill Keck.
-
تمنح تقنيّة Ajax مواقع الويب إمكانيّةَ تحميل محتوى إلى عناصر مختلفة ضمن الصّفحة دون الحاجة لإنعاش Refresh الصّفحة. قد لا تبدو - للوهلة الأولى - ميزةً مؤثّرة، إلّا أنّها تمكّن من فعل الكثير من الأمور. تستخدم الكثير من الإجراءات مثل التّصويت، إبداء الإعجاب، التّعليقات على Disqus والتّغريدات على تويتر؛ وإجراءات أخرى كثيرة Ajax للحصول على تجربة مستخدم User experience لا مثيل لها. ذاع صيت Ajax سنة 2005 عندما استخدمته Google في ميزة اقتراحات البحث، الّتي تُظهر اقتراحات أثناء الكتابة على محرّك بحث Google. لم تكن هذه الميزة لترى النّور لولا وجود تقنيّة Ajax. سنعرض في هذا المقال لكيفيّة استخدام Ajax في ووردبريس. سيكون الأمر أكثر تعقيدًا قليلًا ممّا تعوّدت عليه، إلّا أنّ الأمور ستجري على ما يُرام إن كان لديك فهم جيّد CSS، HTML وPHP؛ إضافةً لفهم لأساسيّات Javascript. كيف يعمل Ajaxليس Ajax في الواقع سوى Javascript. تأتي كلمة Ajax اختصارًا لAsynchronous Javascript And XML (أي: Javascript وXML لا تزامنيّان). تهدف تقنيّة Ajax إلى أن تكون جسرًا بين موقع الويب والخادوم، وهو ما يعني أنّك ستسخدمها بالتّزامن مع Javascript، CSS، HTML وربّما PHP أيضًا؛ الأمر الّذي قد يعقّد الأمور قليلًا. يعدّ الكائن XMLHttpRequest الّذي يتبادل البيانات مع الخادوم الأساس الّذي تقوم عليه تقنيّة Ajax. يشبه عمل Ajax عمومًا إرسال استمارة في صفحة ويب. في ما يلي الخطوات الأساسيّة: تحدّد البيانات الّتي تريد إرسالهاتضبُط نداء Ajaxتستخدم كائن XMLHttpRequest لإرسال البيانات إلى الخادومتحصُل على إجابة من الخادوم. يُمكن استخدام ردّ الخادوم ضمن شفرة Javascript.يكثُر استخدام Ajax عبر دوالّ التّغليف Wrapper functions في jQuery، لذا سنركّز على هذه الطّريقة في هذا المقال. أوّل نداء Ajaxسنحتاج قبل البدء في كتابة شفرة Ajax إلى قالب يُمكن التّعديل عليه، قالب فرعيّ أو إضافة. أنصح باستخدام إضافة. إنشاء إضافة سهل: أنشئ مجلَّدًا فرعيًّا ضمن wp-content/plugins (اخترتُ اسم ajax-text). أنشئ ملفّ PHP ضمن المجلَّد وأعطِه نفس اسم المجلَّد (ajax-text.php) وألصِق الشفرة التّاليّة في الملفّ: <?php /** * Plugin Name: Ajax Test * Plugin URI: <a href="http://danielpataki.com">http://danielpataki.com</a> * Description: This is a plugin that allows us to test Ajax functionality in WordPress * Version: 1.0.0 * Author: Daniel Pataki * Author URI: <a href="http://danielpataki.com">http://danielpataki.com</a> * License: GPL2 */ ستظهر الإضافة، بعد حفظ الملفّ، في ركن الإضافات في لوحة التّحكّم بووردبريس. فعّلها. للمزيد حول تطوير إضافات ووردبريس راجع دليل مدخل إلى برمجة إضافات ووردبريس. 1- صفّ Enqueueing ملفّ Javascriptسنحتاج لماستخدام ملفّ JavaScript في الإضافة، لذا سنصُفّ الملفّ. نستخدم الشّفرة التّاليّة لهذا الغرض: add_action( 'wp_enqueue_scripts', 'ajax_test_enqueue_scripts' ); function ajax_test_enqueue_scripts() { wp_enqueue_script( 'test', plugins_url( '/test.js', __FILE__ ), array('jquery'), '1.0', true ); }تُضيف الشّفرة أعلاه ملفّ test.js إلى الموقع. أخبرنا ووردبريس أنّ jQuery اعتماديّة يجب تحميلها في التّذييل Footer. الخطوة التّاليّة هي إنشاء نداء Ajax بسيط داخل ملفّ Javascript. 2- إنشاء نداء Ajaxيحوي نداء Ajax على العديد من المعطيات، سنتعرّف على الأساسيّة منها. jQuery(document).ready( function($) { $.ajax({ url: "http://yourwebsite.com", success: function( data ) { alert('يبلُغ عدد عناصر div في الصّفحة الرّئيسيّة لديك:' + $(data).find('div').length); } }) })كلّ ما فعلناه هو استخدام دالّة ()ajax.$ وتحديد بعض الخيّارات. يحدّد المُعطى url لنداء Ajax أين يجب أن يُرسل الطّلب الّذي تستقبل الدّالةُ الموجودة في المعطى success الإجابةَ عليه. يجب أن يُحيل المُعطى url إلى رابط الصّفحة الرّئيسيّة لموقعك. ستظهر لك، إذا نفّذت الخطوات بطريقة صحيحة، نافذةٌ منبثقة بعدد عناصر div الموجودة في الصّفحة الرئيسيّة فورَ إنعاش الصّفحة (ﻷخذ التّغييرات على الإضافة في الحسبان). لا يبدو المظهر أنيقًا؛ إلّا أنّ ما فعلناه في الواقع هو تحميل صفحة مختلفة تمامًا دون إعادة تحميل تلك الّتي نوجد فيها. مثال كامل لاستخدام Ajax في ووردبريسيوفّر ووردبريس دعمًا أكبر للمساعدة في استخدام نداءات Ajax ويقدّم آليّة معياريّة لتأديّتها. سنُبدِل عنوان url بآخر أكثر ديناميكيّة وننشئ طريقةً أفضل للتّخاطب مع الخادوم. سنعرض في المثال التّالي كيفيّةَ إضافة ميزة “أعجبني هذا المقال” إلى موقعنا. أنشأتُ إضافةً جديدة لهذا الغرض باسم “post-love”، يمكن الملفّ المضغوط للإضافة في آخر الدّرس. ملحوظة: يجب تعطيل وحذف الإضافة السّابقة ajax-text حتى يمكن للإضافة الجديدة post-love العمل بطريقة صحيحة. 1- تخزين وعرض البياناتسنستخدم حقلًا مُخصَّصًا Custom field لحفظ عدد الإعجابات الّتي تلقّاها المنشور. سنسمّي هذا الحقل post_love وسنعرض قيمته تحت كلّ مقال في صفحة منفردة Single page. في ما يلي كيفيّة ذلك (ملفّ content-filter.php): add_filter( 'the_content', 'post_love_display', 99 ); function post_love_display( $content ) { $love_text = ''; if ( is_single() ) { $love = get_post_meta( get_the_ID(), 'post_love', true ); $love = ( empty( $love ) ) ? 0 : $love; $love_text = '<p class="love-received"><a class="love-button" href="#" data-id="' . get_the_ID() . '">أبدِ(ي) إعجابك</a><span id="love-count">' . $love . '</span></p>'; } return $content . $love_text; } يمكننا عبر استخدام content-filter.php عرض أيّ شيء نُريده أسفل محتوى المقال مباشرةً. نستخدم دالّة ()is_singl للتّأكّد من أنّ الإعجابات تظهر على الصّفحات المنفردة. نحصُل على عدد الإعجابات من الحقل المخصَّص post_love ونتأكّد من أنّ العدد يساوي صفرًا إن لم توجد إعجابات حتى اللّحظة. استخدمنا HTML لعرض رابط لإبداء الإعجاب وعدد الإعجابات. نحصُل على معرّف المقال عبر الدّالّة ()get_the_ID ونمرّره لدالّة get_post_meta الّتي نحصُل من خلالها على قيمة الحقل المخصَّص post_love؛ ثمّ في اﻷخير نُرجِع المحتوى متبوعًا بالنّص الّذي أنشأناه للتّو. نستخدم CSS لإضافة بعض التّحسينات على طريقة العرض عبر صفّ ملفّ CSSكان تفهم جيّدًا على النّحو التّاليّ: add_action( 'wp_enqueue_scripts', 'post_love_assets' ); function post_love_assets() { if( is_single() ) { wp_enqueue_style( 'love', plugins_url( '/love.css', __FILE__ ) ); }لاحظ استخدام الشّرط ()is_single قبل تحميل ملفّ CSS، يعود السّبب في ذلك إلى أنّه لا معنى لتحميل النّمط في صفحات غير منفردة، حيثُ إنّه لا يُستخدَم إلّا فيها؛ الأمر الّذي يُساعد في التّقليل من تأثير الإضافة على زمن تحميل الموقع. أنشئ ملفّ love.css في مجلَّد الإضافة وضمّن فيه النّمط التّالي: .entry-content .love-button { background: #f14864; color: #fff; padding:11px 22px; display:inline-block; border:0px; text-decoration: none; box-shadow: 0px 6px #d2234c; position:relative; } .entry-content .love-button:hover{ top:3px; box-shadow: 0px 3px #d2234c; } .entry-content .love-button:active{ top:6px; box-shadow: none; } #love-count { background: #eee; box-shadow: 0px 6px #ddd; color: #666; padding:11px 22px; display:inline-block; }تظهر النّتيجة بعد إكمال الخطوات السّابقة على هيئة زرّ بتأثير ثلاثيّ الأبعاد يوجد أمامه عدّاد. 2- نداء Ajax الخطوة التّاليّة هي تجميع نداء Ajax. يستدعي ذلك كتابة السكربت وصَفَّه. سنناقش، قبل البدْء في كتابة السّكربت، معطَى url الموجود في نداء Ajax. في المثال السّابق (ajax-text) كتبنا عنوان الموقع مباشرةً ضمن الشّفرة البرمجيّة، وهي طريقة غير صحيحة أثناء إنشاء الإضافات؛ فكلّ موقع لديه عنوانه الخاصّ. ملحوظة: سنحذف الشّفرة التّاليّة ونعيد كتابتها من جديد مع إضافة نداء Ajax: add_action( 'wp_enqueue_scripts', 'post_love_assets' ); function post_love_assets() { if( is_single() ) { wp_enqueue_style( 'love', plugins_url( '/love.css', __FILE__ ) ); }يوفّر ووردبريس ملفًّا موّحدًا للاستخدام (wp-admin/admin-ajax.php) ووسائلَ للحصول على مسار الملفّ كامِلًا. يستدعي الملفّ بعض الحيّل أثناء صفّه، نعرض لها في الشّفرة التّاليّة: add_action( 'wp_enqueue_scripts', 'ajax_test_enqueue_scripts' ); function ajax_test_enqueue_scripts() { if( is_single() ) { wp_enqueue_style( 'love', plugins_url( '/love.css', __FILE__ ) ); } wp_enqueue_script( 'love', plugins_url( '/love.js', __FILE__ ), array('jquery'), '1.0', true ); wp_localize_script( 'love', 'postlove', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) )); }تُسجّل الشّفرة أعلاه ملفّ Javascript بالطّريقة المُعتادة، ثمّ تمرّر سلسلة محارف String إلى السّكربت عبر الدّالّة ()wp_localize_script (تُستخدَم هذه الدّالّة عادةً لترجمة سلاسل المحارف الموجودة في Javascript). يُمكنك إضافة سلاسل محارف إلى المصفوفة Array بحسب حاجتك. يمكننا الآن استخدام المتغيّر postlove.ajax_url في ملفّ love.js للحصول على مسار admin-ajax.php، على النّحو التّاليّ: jQuery( document ).on( 'click', '.love-button', function() { var post_id = jQuery(this).data('id'); jQuery.ajax({ url : postlove.ajax_url, type : 'post', data : { action : 'post_love_add_love', post_id : post_id }, success : function( response ) { alert(response) } }); })ليست هذه النّسخةَ النّهائيّة، لكنّنا وضعنا أساس التّخاطب مع الخادوم. ننفّذ دالّة في كلّ مرة يُضغَط فيها على الزّرّ أبد(ي) إعجابك، ثمّ نأخذ معرّف المقال ونخزّنه في المتغيّر post_id. يُنشئ سكربت love.js نداء Ajax يأخذ في معطى url قيمةَ المتغيّر postlove.ajax_url. بالنّسبة لمعطى type فقد أعطيناه القيمة post أيّ أنّ التّخاطب مع الخادوم سيكون عبر طلبات Post وهي ما يعني أنّنا سنستخدِم المتغيّر POST_$، من جانب الخادوم، للحصول على البيانات. توجد في قسم data كلّ المعطيات الّتي تودّ إرسالها. نحتاج لإرسال معرّف المقال (post_id) لنعرِف المقال الّذي أبدى الزّائر إعجابه به. يطلُب ووردبريس تحديد إجراء Action عند استخدام الملفّ admin-ajax.php. حدّدنا الإجراء post_love_add_love. أخيرًا نعرض النّتيجة في نافذة منبثقة. إذا جربّت التّعديلات الجديدة وضغط على زرّ إبداء الإعجاب فستظهر رسالة منبثقة تحوي عدد مرّات إبداء الإعجاب (0). لم نعرّف خطّافًا Hook للإجراء post_love_add_love في الشّفرة أعلاه وهو ما يؤدّي بadmin-ajax إلى إرجاع القيمة 0. سنعرّف في الفقرة المواليّة الخطّاف النّاقص. 3- المعالجة من جانب الخادوموصلنا في هذه المرحلة إلى إرسال بيانات إلى الخادوم دون أن نخبره مالّذي يجب عليه فعله بها. يجب، عند الضّغط على الزّرّ، زيّادة عدد الإعجابات ب1 وإرجاع القيمة الجديدة. سنحتاج لإنشاء خطّافَين لهذا الغرض. add_action( 'wp_ajax_nopriv_post_love_add_love', 'post_love_add_love' ); add_action( 'wp_ajax_post_love_add_love', 'post_love_add_love' ); function post_love_add_love() { }يُنفَّذ الخطّاف الأوّل بالنّسبة للزّوّار الضّيوف، والثّاني لمُسجَّلي الدّخول. بالمناسبة، هذه طريقة جيّدة للتّحكّم في الوصول. الاتّفاق هو كالتّالي: هل تتذكّر كيف عرّفنا معطى الإجراء في نداء Ajax؟ نحتاج إلى إلحاق هذا المعطى ب_wp_ajax و/أو _wp_ajax_nopriv. بالنّسبة للدّالّة يُمكن استخدام أيّ تسميّة، استخدمنا نفس سلسلة المحارف الموجودة في اسم الإجراء للحفاظ على التّجانس. نُضيف الآن وظيفة الزّيّادة بالتّدرّج Incrementing add_action( 'wp_ajax_nopriv_post_love_add_love', 'post_love_add_love' ); add_action( 'wp_ajax_post_love_add_love', 'post_love_add_love' ); function post_love_add_love() { $love = get_post_meta( $_POST['post_id'], 'post_love', true ); $love++; update_post_meta( $_POST['post_id'], 'post_love', $love ); echo $love; die(); }نعثُر على القيمة الحاليّة، نزيدها بواحد ثمّ نخزّن القيمة الجديدة ونعرضها. يجب تنفيذ ()die في نهاية الدّالّة وإلّا فإنّ ملفّ admin-ajax.php سينفّذ (0)die الخاصّة به ممّا ينتج عنه عرض 0 إضافيّ في الإجابة. تظهرعند الضّغط على زر إبداء الإعجاب الآن نافذة منبثقة بها عددُ الإعجابات، وعند إنعاش الصّفحة يظهر العدد إلى جانب الزّرّ المذكور. لم يتبقّ لنا سوى جعل العدد يتغيّر دون الحاجة لإنعاش الصّفحة. تتكفّل شفرة Javascript التّاليّة بهذه المهمّة: jQuery( document ).on( 'click', '.love-button', function() { var post_id = jQuery(this).data('id'); jQuery.ajax({ url : postlove.ajax_url, type : 'post', data : { action : 'post_love_add_love', post_id : post_id }, success : function( response ) { jQuery('#love-count').html( response ); } }); })يؤدّي الضّغط على الزّرّ الآن إلى زيّادة عدد الإعجابات دون الحاجة لإنعاش الصّفحة. 4- لمسات أخيرةربّما لاحظت أنّنا أضفنا في زرّ الإعجاب رابطًا إلى #: <a class="love-button" href="#" data-id="' . get_the_ID() . '">أبدِ(ي) إعجابك</a>يعود السّبب في ذلك إلى أخذ الحالة الّتي يكون Javascript فيها غير مفعَّل لدى الزّائر. في هذه الحالة يؤدّي الضّغط على الزّرّ إلى إعادة تنزيل الصّفحة. إلّا أنّ استخدام # وحده ضمن وسم Tag الرّابط يجعل هذا الأخير غير صالح Invalid. فلنعالج هذا الأمر. ملحوظة: يُستخدَم ماسك المكان Placeholder # في خاصيّة href ضمن وسم <a> للدّلالة على أنّ الرّابط يُحيل إلى عنصر من الصّفحة يُعطَى معرّفه بعد ماسك المكان (مثلًا:"a href="#id5). استخدام # لوحده يعني أنّ الرّابط يُحيل إلى معرّف غير موجود في الصّفحة. في المحصّلة يُحيل الرّابط إلى الصّفحة لكنّه يبقى غير صالح دلاليًّا Semantic. سنعدّل على الشّفرة بحيث يُحيل الرّابط إلى نداء Ajax، مثلًا: http://yourwebsite.com/wp-admin/admin-ajax.php?action=post_love_add_love&post_id=23 بالنّسبة للزّوّار الّذين يفعّلون Ajax. add_filter( 'the_content', 'post_love_display', 99 ); function post_love_display( $content ) { $love_text = ''; if ( is_single() ) { $love = get_post_meta( get_the_ID(), 'post_love', true ); $love = ( empty( $love ) ) ? 0 : $love; $love_text = '<p class="love-received"><a class="love-button" href="' . admin_url( 'admin-ajax.php?action=post_love_add_love&post_id=' . get_the_ID() ) . '" data-id="' . get_the_ID() . '">give love</a><span id="love-count">' . $love . '</span></p>'; } return $content . $love_text; }إذا كان Javascript مفعَّلًا في المتصفّح فيجب تعطيل إمكانيّة تتبّع رابط URL. لذا نُضيف return false إلى الدّالّة في حدث click كما يلي: jQuery( document ).on( 'click', '.love-button', function() { var post_id = jQuery(this).data('id'); jQuery.ajax({ url : postlove.ajax_url, type : 'post', data : { action : 'post_love_add_love', post_id : post_id }, success : function( response ) { jQuery('#love-count').html( response ); } }); // نُرجع القيمة false هنا return false; })الخطوة الأخيرة هيّ التّفريق بين عمليّات Ajax والعمليّات الأخرى ضمن دالّة ()post_love_add_love. عند استخدام Ajax نعرض القيمة الجديدة، ثمّ يتوقّف السكربت. إن لم يُستخدَم Ajax (يعني هذا أنّ الزّائر سيُوجَّه إلى admin-ajax) فكلّ ما نفعله هو إعادة توجيه المتصفّح إلى المقال بعد تنفيذ السّكربت. في ما يلي النّسخة النّهائيّة لدالّة ()post_love_add_love: function post_love_add_love() { $love = get_post_meta( $_REQUEST['post_id'], 'post_love', true ); $love++; update_post_meta( $_REQUEST['post_id'], 'post_love', $love ); if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { echo $love; die(); } else { wp_redirect( get_permalink( $_REQUEST['post_id'] ) ); exit(); } }أبدلنا المتغيّر POST_$ بREQUEST_$ إذ أنّ الزوّارّ بAjax مفعَّل سيستخدمون طلب POST في ما يستخدم بقيّة الزّوّار طلب GET. يمكّن متغيّر REQUEST_$ من الحصول على البيانات في الحالتيْن. جوانب إضافية تجب مراعاتها عند استخدام AjaxAjax ليس صعبًا، إلّا أنّه يوجد الكثير من الأمور السّهلة الّتي تجب عليك معرفتها. في ما يلي أهمّ جانبيْن ينبغي عليك الاهتمام بهما. 1- الأمانيُمكن أن تؤدّي قلّة الحذر أثناء استخدام Ajax إلى مشاكل أمنيّة عديدة. لا تتحقّق الإضافة التّي أنشأناها أعلاه من الضّغطات المتعدّدة الّتي يُمكن أن تؤدّي - فضلًا عن تشويه النّتائج - إلى زيّادة الحمل على الخادوم، خصوصًا إن واصل الكثيرون الضّغط على الزّرّ في نفس الوقت. علاوةً على ذلك فإنّ تغيير العدّاد لا يحتاج للضّغط على الزّر؛ مجرّد الدّخول إلى الرّابط (مثلًا http://yourwebsite.com/wp-admin/admin-ajax.php?action=post_love_add_love&post_id=23) يفي بالمهمّة. خذ في الحسبان أنّ بعض الإضافات تسمح بحذف المقالات من الواجهة اﻷماميّة دون الحاجة للدّخول إلى لوحة التّحكّم؛ ماذا لو وُجِد رابط تؤدّي زيّارته إلى نفس التّأثير؟ ليست وضعيّة مثاليّة. استخدام الأرقام الخاصّة Nonces هو إحدى وسائل تأمين الاستمارات والرّوابط ضدّ المحاولات الخبيثة. 2- تجربة المستخدميعود انتشار استخدام Ajax إلى أنّ المستخدم يحصُل على تفاعليّة أكبر مع التّقليل من وقت التّحميل. يعني هذا أيضًا أنّه يجب عليك العمل جاهدًا للتّأكّد من أنّ استخدامك لAjax يرفع حقًا من نوعيّة تجربة المستخدم: يجب أن يعرف الزّائر دائمًا مالّذي يحدُث ولماذا. لو كانت إضافة post-love موجَّهةً للنّشر ﻷضفتُ أيقونة لحالة التّحميل تعطّل زرّ الإعجاب عند الضّغط عليه، ثمّ بعد نجاح العمليّة يعود الزّرّ لحالته الطّبيعيّة. تُمكن إضافة هذه الميزة عن طريق معطى beforeSend ضمن دالّة ()ajax.$. يُتيح beforeSend إمكانيّة تنفيذ بعض التّعليمات قبل إرسال البيانات إلى الخادوم. إضافة إشارة مرئيّة أثناء تغيّر عدد الإعجابات فكرة جيّدة؛ إن لم يكن المستخدم يعرف مالّذي ينتظره عند الّضغط على الزّر فربّما لا يُلاحظ تغيّر عدد الإعجابات. في المقابل، لا تبالِغ باستخدام التّأثيرات المرئيّة أثناء التّعامل مع نداءات Ajax، كما حصل للكثيرين مع انتشار هذه التّقنيّة فجعلوا كلّ شيء يتحرّك وينشط في كلّ اتّجاه. لا تضف تأثيرات تلفت النّظر إلّا إذا كان لذلك معنى، ولا تجعل المستخدم ينتظر حتّى نهاية التّحريكة Animation؛ وإلّا فإنّ جودة تجربة المستخدم ستنقُص ولن تزيد. خاتمةتعدّ تقنيّة Ajax من الأدوات القويّة المتاحة للمطوّرين من أجل إضافة التّفاعليّة والتّقليل من الضّغط على الخادوم. لن يُمكن إحصاء الميزات الممكن تقديمها باستخدام Ajax: عرض التّعليقات دون الحاجة لإعادة تنزيل الصّفحة، التّمرير اللّامتناهيّ Infinite scrolling للمقالات، التّحميل الخامل Lazy-loading للصوّر، وغيرها الكثير. سيجعل Ajax تطبيقك أفضل بكثير شرطَ الانتباه إلى تفاصيل تجربة المستخدم وعدم استخدامه لغير ضرورة. رابط تنزيل إضافة `post-love` في صورتها النّهائيّة. post-love.zip ترجمة بتصرّف لمقال Using AJAX With PHP on Your WordPress Site Without a Plugin لكاتبه Daniel Pataki.
-
رغم أنّ تحويلات CSS3 ثلاثية الأبعاد صار لها فترةً لا بأس بها من الزمن، إلّا أنّني اكتشفت عدم امتلاك الخبرة الكافية للعمل معها بعد. أستخدم Windows 8 منذ فترة من الوقت، ومن أوّل الأمور التي لفتت نظري كانت الانتقالات transitions والتحريكات animations المبنية ضمن لوح البداية Start Dashboard، لذلك خطرت ببالي فكرة رائعة، وهي لماذا لا تكون خبرتي الأولى مع تحويلات CSS3 ثلاثية البعد ببناء تطبيق يماثل في سلوكه تلك التحريكات والتأثيرات في Windows 8؟ وهذا ما حدث في هذا الدرس. سأستخدم خصائص CSS بدون أي بادئة prefix وذلك بغرض الاختصار، لكنك ستجد الخصائص كاملة ضمن النص المصدري للمشروع على Github. ستعمل مقاطع الشيفرة التي ستجدها هنا على متصفحات تدعم خصائص CSS المستخدمة. الرماز The Markupبنية هذا التطبيق بسيطة: يتكون لوح البداية من قائمة من القطع tiles بثلاثة قياسات وهي الصغير، الكبير، والكبير جدًا. لكل قطعة من هذه القطع صفحة page مرتبطة معها. والصفحة عبارة عن تغطية overlay تظهر عند النقر على إحدى القطع في لوح البداية. سنعتبر الصفحة أنّها تُحاكي تطبيق سطح مكتب في Windows 8، فتكون القطعة كما هو واضح اختصارًا له (للصفحة). عند النقر على قطعة ستُفتح الصفحة الموافقة لها. يوجد نوعان من الانتقالات للصفحة بعد فتحها يدعمهما اللوح في Windows 8. يفتح أحدهما الصفحة بتأثير دوران ثلاثي الأبعاد اعتبارًًا من يمين الشاشة، أمّا الآخر فتنزلق فيه الصفحة من وإلى اليسار. سنعرّف صنف CSS لكل نوع من نوعيّ الصفحة، حيث سيكون الصنف s-page للصفحات التي تنزلق من وإلى اليسار، أمّا الصنف r-page فسيكون للصفحات التي تدور من اليمين. ينبغي علينا من أجل كل قطعة تعيين نوع الصفحة التي ستفتحها القطعة (بالاعتماد على التأثير الذي نريده من أجل هذه الصفحة). سنعرّف نوع الصفحة لكلّ قطعة باستخدام سمة attribute مخصّصة سنسميها data-type-page، ستهتم هذه الـسمة بتطبيق أسماء أصناف CSS الصحيحة التي ستفعّل التحريكات المناسبة فيما بعد. يجب أن يكون لكل صفحة اسم. سيختلف اسم الصفحة لتطبيق معيّن عن اسم الصفحة لتطبيق آخر، فمثلًا الـقطعة المسمّاة Skype ستفتح صفحة اسمها skype-app وهكذا دواليك. لقد استخدمت اسميّ صفحة فقط في هذا المثال، وقد كرّرتهما من أجل جميع الـقطع الباقية بغرض التبسيط، كما استخدمت الاسم custom-page لآخر قطعة وذلك على سبيل المثال. ربما تحتاج أن تضيف صفحة مختلفة لكل قطعة، وهذا يعني اسم صفحة مختلف لكلٍّ منها. إليك الرماز الخاص بكامل اللوح (الـقطع والصفحات): <div class="demo-wrapper"> <div class="s-page random-restored-page"> <div class="page-content"> <h2 class="page-title">Some minimized App</h2> <div class="close-button s-close-button">x</div> </div> </div> <div class="s-page custom-page"> <div class="page-content"> <h2 class="page-title">Thank You!</h2> <div class="close-button s-close-button">x</div> </div> </div> <div class="r-page random-r-page"> <div class="page-content"> <h2 class="page-title">App Screen</h2> <p>Chew iPad power cord chew iPad power cord attack feet chase mice leave dead animals as gifts and stick butt in face chew iPad power cord. Chase mice. Run in circles use lap as chair why must they do that. Intrigued by the shower destroy couch leave hair everywhere sleep on keyboard chew iPad power cord. Use lap as chair. Missing until dinner time stand in front of the computer screen, intently sniff hand. Find something else more interesting. Destroy couch play time so inspect anything brought into the house hate dog burrow under covers. Sleep on keyboard destroy couch so hate dog so hide when guests come over. Chase mice destroy couch lick butt throwup on your pillow use lap as chair yet intrigued by the shower but climb leg. Stare at ceiling make muffins or hunt anything that moves claw drapes. Intently sniff hand intrigued by the shower. Why must they do that. Cat snacks leave dead animals as gifts or inspect anything brought into the house sweet beast so stare at ceiling give attitude. Flop over claw drapes but sun bathe lick butt, and chase mice. Rub face on everything lick butt leave hair everywhere lick butt, missing until dinner time for use lap as chair lick butt. Make muffins leave dead animals as gifts play time. Chew foot intrigued by the shower stare at ceiling inspect anything brought into the house yet hopped up on goofballs. Hunt anything that moves intently sniff hand for hunt anything that moves play time. Chew foot climb leg throwup on your pillow so lick butt yet make muffins hate dog. Intrigued by the shower. Intently sniff hand shake treat bag. Cat snacks burrow under covers make muffins but all of a sudden go crazy find something else more interesting. Flop over chase mice. Give attitude. Inspect anything brought into the house. Stick butt in face sun bathe so find something else more interesting and intrigued by the shower. Rub face on everything use lap as chair. Under the bed claw drapes chase mice but leave hair everywhere yet make muffins yet claw drapes. Use lap as chair. Find something else more interesting stretch for under the bed. Nap all day intrigued by the shower, hate dog sweet beast intently sniff hand so hate dog nap all day. Swat at dog hide when guests come over and mark territory chase mice for cat snacks. Use lap as chair. Lick butt throwup on your pillow need to chase tail. Mark territory. Stick butt in face shake treat bag yet hunt anything that moves, yet hopped up on goofballs yet stare at ceiling under the bed. Give attitude chase imaginary bugs stretch so hunt anything that moves so hide when guests come over but intrigued by the shower find something else more interesting. Make muffins behind the couch for chew foot. Sweet beast flop over but throwup on your pillow. Intently sniff hand use lap as chair and missing until dinner time and chase imaginary bugs. </p> </div> <div class="close-button r-close-button">x</div> </div> <div class="dashboard clearfix"> <ul class="tiles"> <div class="col1 clearfix"> <li class="tile tile-big tile-1 slideTextUp" data-page-type="r-page" data-page-name="random-r-page"> <div><p>This tile's content slides up</p></div> <div><p>View all tasks</p></div> </li> <li class="tile tile-small tile tile-2 slideTextRight" data-page-type="s-page" data-page-name ="random-restored-page"> <div><p class="icon-arrow-right"></p></div> <div><p>Tile's content slides right. Page opens from left</p></div> </li> <li class="tile tile-small last tile-3" data-page-type="r-page" data-page-name="random-r-page"> <p class="icon-calendar-alt-fill"></p> </li> <li class="tile tile-big tile-4" data-page-type="r-page" data-page-name="random-r-page"> <figure> <img src="images/blue.jpg" /> <figcaption class="tile-caption caption-left">Slide-out Caption from left</figcaption> </figure> </li> </div> <div class="col2 clearfix"> <li class="tile tile-big tile-5" data-page-type="r-page" data-page-name="random-r-page"> <div><p><span class="icon-cloudy"></span>Weather</p></div> </li> <li class="tile tile-big tile-6 slideTextLeft" data-page-type="r-page" data-page-name="random-r-page"> <div><p><span class="icon-skype"></span>Skype</p></div> <div><p>Make a Call</p></div> </li> <li class="tile tile-small tile-7 rotate3d rotate3dX" data-page-type="r-page" data-page-name="random-r-page"> <div class="faces"> <div class="front"><span class="icon-picassa"></span></div> <div class="back"><p>Launch Picassa</p></div> </div> </li> <li class="tile tile-small last tile-8 rotate3d rotate3dY" data-page-type="r-page" data-page-name="random-r-page"> <div class="faces"> <div class="front"><span class="icon-instagram"></span></div> <div class="back"><p>Launch Instagram</p></div> </div> </li> </div> <div class="col3 clearfix"> <li class="tile tile-2xbig tile-9" data-page-type="custom-page" data-page-name="random-r-page"> <figure> <img src="images/summer.jpg" /> <figcaption class="tile-caption caption-bottom">Fixed Caption: Some Subtitle or Tile Description Goes Here with some kinda link or anything </figure> </li> <li class="tile tile-big tile-10" data-page-type="s-page" data-page-name="custom-page"> <div><p>Windows-8-like Animations with CSS3 & jQuery © Sara Soueidan. Licensed under MIT.</p></div> </li> </div> </ul> </div> </div>حصلت على خط الأيقونة الذي استخدمته من Icomoon. الذي سيحدث الآن: ستحصل JavaScript على اسم ونوع الصفحة المراد فتحها عند نقر القطعة، وبعد ذلك، ووفقًا لنوع الصفحة ستعمل JavaScript على تطبيق أسماء أصناف CSS المناسبة على الصفحة (والتي سنحصل على اسمها أيضًا من السمة data-page-name) لفتحها بنمط تحريك معيّن لكل صنف CSS يتم تطبيقه. تنسيقات CSSأرجو ملاحظة أنّني أستخدم أنماط تنسيق تراعي الأجهزة المحمولة أولًا mobile-first، والتي سنجعلها لاحقًا ذات استجابية responsive عالية ضمن قسم استعلامات الوسائط media queries في CSS. سنتناول في البداية التنسيقات الخاصة بالحاوية (عنصر div) والتي ستحوي كامل المثال التوضيحي. سنعرّف تنسيقات عامّة ونتأكّد من ضبط الخاصية perspective بحيث يؤدي ذلك إلى تفعيل الفضاء ثلاثي البعد 3D، وإلّا سيبدو كامل المثال بشكل منبسط أي ثنائي البعد 2D. .demo-wrapper { padding: 2em .5em; width: 100%; height:100%; perspective: 3300px; position: relative; }لنبدأ الآن بتنسيقات وتحريكات لوح البداية. ستُنفَّذ أوّل تحريكة سنطبّقها على لوح البداية عند تحميل الصفحة ضمن متصفّح الويب. سيكون لوح البداية مخفيًّا في يمين الشاشة بادئ الأمر، ثمّ يظهر تدريجيًّا مع الانتقال إلى موقعه الأساسي عند تحميل الصفحة. .dashboard { margin: 0 auto; width: 100%; padding: 1em; transform: translateX(200px); opacity:0; animation: start 1s ease-out forwards; } @keyframes start{ 0%{ transform: translateX(200px); opacity:0; } 50%{ opacity:1; } 100%{ transform: translateX(0); opacity:1; } }يتضاءل لوح البداية ويختفي عند نقر قطعة منه. حيث ينتقل اللوح على طول محور z، ويتضاءل حجمه، كما تنخفض قيمة الخاصية opacity له تدريجيًا حتى تصبح في النهاية تساوي الصفر مما يعطي شعورًا بأنّ اللوح يختفي بالتدريج. أمّا عندما يُغلق المستخدم الصفحة فإنّ لوح البداية يعود للظهور مرّة أخرى. بالنسبة للأعمدة الثلاثة في لوح البداية فإنّها تظهر تدريجيًّا واحدًا تلو الآخر مع تأخير زمني طفيف بينها. عندما تُغلق الصفحة، سيُضاف اسم صنف CSS لكل عمود (بواسطة JavaScript) وكل من هذه الأصناف ستُفعّل تحريكة بتأخير زمني مُحدّد. فيما يلي أصناف CSS والتحريكات المطبّقة على لوح البداية عند نقر القطع tiles وإغلاق الصفحات. .fadeOutback{ animation: fadeOutBack 0.3s ease-out 1 normal forwards; } .fadeInForward-1, .fadeInForward-2, .fadeInForward-3 { opacity:0; transform: translateZ(-5em) scale(0.75); animation: fadeInForward .5s cubic-bezier(.03,.93,.43,.77) .4s normal forwards; } .fadeInForward-2{ animation-delay: .55s; } .fadeInForward-3{ animation-delay: .7s; } @keyframes fadeOutBack{ 0% {transform: translateX(-2em) scale(1); opacity:1;} 70% {transform: translateZ(-5em) scale(0.6); opacity:0.5;} 95% {transform: translateZ(-5em) scale(0.6); opacity:0.5;} 100% {transform: translateZ(-5em) scale(0); opacity:0;} } @keyframes fadeInForward{ 0% {transform: translateZ(-5em) scale(0); opacity:0;} 100% {transform: translateZ(0) scale(1); opacity:1;} }وبالنسبة لتنسيقات الصفحات: .r-page { width: 100%; height: 100%; text-align: center; font-size: 2em; font-weight: 300; position: absolute; right: 0; top: 0; left:0; bottom:0; opacity: 0; color: white; z-index: 10; padding:10px; transform-origin: 100% 0%; transform: rotateY(-90deg) translateZ(5em) } .s-page { color: white; z-index: 10; text-align: center; font-size: 2em; font-weight: 300; } .page-content{ overflow-y:auto; max-height:100%; font-size:.6em; padding:.6em; text-align:left; } .s-page, .r-page{ background-color: white; color:black; } .page-title { margin: .25em 0; font-weight: 100; font-size: 3em; text-align:center; } .close-button { font-size: 1.5em; width: 1em; height: 1em; position: absolute; top: .75em; right: .75em; cursor: pointer; line-height: .8em; text-align: center }لقد ضبطت الموقع الأصلي لكل صفحة من النوع r-page في الفضاء ثلاثي البعد بتدويرها حول محور التراتيب (محور y) بعد ذلك نقل الصفحة بمقدار 5em إلى يسار الشاشة باستخدام الخاصية translateZ (النقل على محور z). ينبغي ألّا ننسى أنّه عند تحويل (نقل – تدوير) عنصر في الفضاء ثلاثي البعد فمن الضروري تحويل نظام الإحداثيات الخاص به بنفس الصورة. الذي نريده الآن هو نقل الصفحة بمقدار 5em إلى يسار الشاشة، ولكن لاحظ أنّنا بدلًا من استخدام translateX استخدمنا translateZ، ويعود سبب ذلك إلى أنّه بعد التحويل الأوّل (الدوران حول محور y) يدور نظام الإحداثيات أيضًا، وهكذا يُشير محور z في هذه الحالة إلى اليسار وليس إلى الأعلى، أمّا محور الفواصل (محور x) فسيشير باتجاه المستخدم. يكون لجميع الصفحات باستثناء النوع s-page نفس موقع البداية في الفضاء ثلاثي البعد. بالنسبة للصفحات من النوع s-page فإنّها تكون بموقع يبعد بما يُعادل -150% يسار الشاشة (واضح أنّها لن تكون مرئية بهذه الحالة)، بحيث أنّها تنزلق لتعود إلى الشاشة عند تفعيل التحريكة الخاصة بها. عند نقر قطعة مرتبطة بصفحة ما، فسيُضاف صنف CSS الموافق بواسطة JavaScript إلى الصفحة، بالنتيجة ستحصل الصفحة على اسم صنف CSS يُعرّف التأثير ثلاثي البعد الواجب تطبيقه عليها. فيما يلي أسماء أصناف CSS التي تُفعّل عملية فتح وإغلاق الصفحات، بالإضافة إلى التحريكات المعرّفة من أجل كل صنف. .openpage{ animation: rotatePageInFromRight 1s cubic-bezier(.66,.04,.36,1.03) 1 normal forwards; } .slidePageLeft{ transform: rotateY(0) translateZ(0); opacity: 1; animation:slidePageLeft .8s ease-out 1 normal forwards; } .slidePageInFromLeft{ animation: slidePageInFromLeft .8s cubic-bezier(.01,1,.22,.99) 1 0.25s normal forwards; } .slidePageBackLeft{ opacity: 1; left: 0; animation: slidePageBackLeft .8s ease-out 1 normal forwards; }لاحظ أنّني أستخدم الخاصية animation بالشكل المختصر. تعود القيمة الأخيرة forward ضمن الخاصية animation إلى الخاصيّة الفرعية animation-fill-mode، وهذه القيمة ضرورية لها، وإلّا ستعود الصفحة إلى حالتها الابتدائية (المغلقة) فور انتهاء التحريكة التي ستُظهر الصفحة. إذًا لكي نُبقي الصفحة مفتوحة، ولكي نكون قادرين على إنشاء تحريكات متلاحقة، يجب على العنصر أن يبقى مُحتفظًا بحالته النهائية المعرّفة ضمن تحركية ما، ومن هذه "الحالة النهائية" يبدأ عمل التحريكة التالية، وهكذا. فيما يلي التحريكات لأصناف CSS المطبّقة على الصفحات. @keyframes rotatePageInFromRight{ 0% {transform:rotateY(-90deg) translateZ(5em);opacity:0} 30% {opacity:1} 100% {transform: rotateY(0deg) translateZ(0) ; opacity:1} } @keyframes slidePageLeft{ 0% {left:0; transform: rotateY(0deg) translateZ(0) ; opacity:1} 70% {opacity:1;} 100% {opacity:0; left:-150%; transform: rotateY(0deg)} } @keyframes slidePageInFromLeft{ 0% {opacity:0; } 30% {opacity:1} 100% {opacity:1; left:0;} } @keyframes slidePageBackLeft{ 0% {opacity:1; left:0; transform: scale(0.95);} 10% {transform: scale(0.9);} 70% {opacity:1;} 100% {opacity:0; left:-150%;} }أخيرًا وليس آخرًا، سنُنسّق قطع لوح البداية ونُعرّف الانتقالات والتحريكات المطبّقة عليها عند تحريك مؤشّر الفأرة فوقها. لاحظ أنّ التنسيقات العامّة تُعرّف حجم القطع. .tile{ float: left; margin: 0 auto 1%; color: white; font-size: 1.3em; text-align: center; height: 8em; font-weight: 300; overflow: hidden; cursor: pointer; background-color: #fff; color: #333; position:relative; transition: background-color 0.2s ease-out } .tile-2xbig{ height:16.15em; width:100%; } .tile-big { width: 100% } .tile-small { width: 49%; margin-right: 2% } .tile-small.last { margin-right: 0 }سيحتوي زوج من القطع على صورة مع عنوان لها، هاتان القطعتان ستحصلان على الصنف fig-tile وذلك لتمييز نوعهما في شيفرة JavaScript التي سنراها لاحقًا. سنحصل على الألوان المستخدمة من أجل النص والخلفية لصفحة ما من الألوان المستخدمة في العنوان، لذلك لا تنسى أن تُعرّفهم. بالنسبة للعنوان إمّا أن يكون ثابتًا أو أن يُعطي شعورًا بالانزلاق عندما يتحرك فوقه مؤشّر الفأرة: .tile-caption{ position:absolute; z-index:1; background-color: #455962; color:#fff; font-size:1em; padding:1em; text-align: left; } .caption-bottom{ left:0; bottom:0; right:0; height:40%; } .caption-left{ left:-100%; top:0; bottom:0; width:40%; transition: left .3s linear; } .tile:hover .caption-left{ left:0; }بالنسبة للقطع النظامية regular tiles التي لا تملك أي نوع من التحريك، فستغيّر لون خلفيّتها ولون النص عند تحريك الفأرة فوقها. لكي نتأكّد من أنّ النص مُوسّط عموديًا vertically centered في كل قطعة، سنضع في كل قطعة عنصر div يحوي بدوره عنصر فقرة (العنصر <p>) يحوي النص. سنستخدم القيمة table-cell للخاصية display وذلك لتوسيط النص عموديًا ضمن الفقرة: .tile div{ position:absolute; top:0; left:0; right:0; bottom:0; width:100%; height:100%; text-align:center; display:table; padding:0 1em; transition: all .3s ease; } .tile div p{ display:table-cell; vertical-align:middle; }سنترك الحديث عن أنماط التنسيق العامّة للقطع بهدف الاختصار، ولكن ينبغي علينا التأكّد من أنّنا سنعيّن لون النص والخلفية لجميع القطع، حتى تلك التي ستُغطّى بصورة، لأنّ هذه الألوان التي سنحصل عليها باستخدام JavaScript ستُستخدَم لضبط الألوان في الصفحة المرتبطة بها كما أشرنا قبل قليل. دعونا الآن نتحدّث عن التحريكات والانتقالات التي تحدث على قطعة ما. ستحتوي القطع المزوّدة بنص منزلق على عنصري div، سيبدو كل عنصر div كوجه أو كتلة منفصلة داخل القطعة. سيكون تموضعًا عنصريًا div مطلقًا positioned absolutely وسيتحرّكان عند تحرّك الفأرة فوقهما وذلك وفقًا لاتجاه الانزلاق المطلوب. فلكي ينزلق نص القطعة إلى الأعلى عندما يحوم مؤشّر الفأرة فوقه سنطبّق الصنف slideTextUp: .slideTextUp div:nth-child(2){ top:100%; } .slideTextUp:hover div{ transform: translateY(-100%); } .tile-1 p{ font-size:1.3em; }وبشكل مماثل ولكي ينزلق نص القطعة إلى اليسار وإلى اليمين سنطبّق الصنفين slideTextLeft و slideTextRight على الترتيب. .slideTextRight div:first-child{ left:-100%; } .slideTextRight:hover div{ transform: translateX(100%); } .slideTextLeft div:nth-child(2){ left:100%; } .slideTextLeft:hover div{ transform: translateX(-100%); }أمّا بالنسبة لزوج القطع التي ستنقلب، فسيكون لهما تأثير مختلف عندما يحوم مؤشّر الفأرة فوقهما، فهما يدوران ليُظهران الوجه الخلفي. يُعتبر هذا التأثير نوعًا بسيطًا جدًا من تأثير "انقلاب البطاقة" card flip. لن نخوض في تفاصيل هذا التأثير. لإنجاز هذا التأثير، سنطبّق الصنف rotate3d إلى القطعة التي نريد أن تنقلب. بالنسبة للقطعة التي نريد أن تنقلب عموديًّا، سنطبّق الصنف rotate3dy، أمّا بالنسبة للانقلاب الأفقي سنطبّق الصنف rotate3dx (مع الانتباه إلى وجوب وجود الصنف rotate3d في كلتا الحالتين). انظر إلى تنسيقات CSS التالية: .rotate3d{ perspective: 800px; overflow: visible; } .faces{ transform-style: preserve-3d; transition: transform 1s; } .faces div { display: block; position: absolute; top:0; left:0; right:0; bottom:0; width: 100%; height: 100%; backface-visibility: hidden; }لاحظ بأنّه عند تحريك مؤشّر الفأرة فوقهما بالتتالي، فسيظهر وجه أحدهما في حين ستظهر خلفية الآخر بنفس الوقت. .rotate3dY .back{ transform: rotateY( 180deg ); } .rotate3dX .back{ transform: rotateX( 180deg ); }وعندما يحوم مؤشّر الفأرة فوق القطعة فإنّ أي عنصر div يخضع للصنف faces. سيُدوّر ليُظهر وجهه الخلفي: .rotate3dY:hover .faces:hover{ transform: rotateY( 180deg ); } .rotate3dX:hover .faces:hover{ transform: rotateX( 180deg ); } لتنسيق القطع في الدوران ثلاثي البعد، ينبغي الانتباه إلى ضبط لوني الخلفية والنص للوجه الأمامي (ذو الصنف front.) بحيث يمكن الحصول على هذين اللونين واستخدمهما لاحقًا في الصفحة المتربطة بالقطعة عند فتحها. لنعرّف الآن تنسيقات تفاعلية للوح Dashboard. ستكون أعمدة اللوح ذات عرض كامل على الشاشات الصغيرة في البداية (تذكّر أنّنا نراعي متطلبات تصميم الأجهزة المحمولة أولًا mobile-first)، في حين أنّ هذه الأعمدة ستكون بجانب بعضها البعض في الشاشات الكبيرة. .col1, .col2, .col3 { width: 99%; margin: 1em auto } @media screen and (min-width: 43.75em) { .col1, .col2, .col3 { float: left; margin-right: 1%; width: 49% } .page-title{ font-size:2.5em; } .page-content{ font-size:1em; } .close-button{ font-size:2em; } } @media screen and (min-width: 64em) { .col1, .col2, .col3 { float: left; margin-right: .5%; width: 31% } .col3 { margin-right: 0 } .col1 { margin-left: 2em } .page-title{ font-size:3.5em; } } جافا سكريبت JavaScriptستُعالَج جميع أحداث النقر عن طريق JavaScript. سنستخدم مكتبة jQuery لهذه الغاية، وسنضبط معالج حدث event handler النقر لكل قطعة على لوح البداية، فعندما ينقر المستخدم على قطعة ما، يعمل معالج حدث النقر الموافق على الحصول على اسم ونوع الصفحة المرتبطة بهذه القطعة وذلك من السمتين data-page-name و data-page-type على الترتيب، حيث سنستخدم هذه المعلومات لفتح الصفحة المطلوبة. أمّا عند إغلاق الصفحة عند النقر زر الإغلاق لها، فسيعمل معالج حدث النقر لهذا الزر على تطبيق أسماء الأصناف المناسبة لإغلاق الصفحة. بالإضافة لذلك، ولكي نُكسِب كل صفحة لون خلفية ولون نص مماثل لتلك التي للقطعة المرتبطة بها، فإنّنا سنطوف بدايةً على جميع القطع، ونحصل على ألوانها، ثمّ نُطبّق هذه الألوان على الصفحات المرتبطة معها. في حال كان لقطعة ما الصنف rotate3d، فإنّنا سنبحث عن لون الخلفية لوجه face القطعة، ومن ثمّ نطبّق هذا اللون على الصفحة المرتبطة معها. function(){ $('.tile').each(function(){ var $this= $(this), page = $this.data('page-name'), bgcolor = $this.css('background-color'), textColor = $this.css('color'); if($this.hasClass('rotate3d')) { frontface = $this.find('.front'); bgcolor = frontface.css('background-color'); textColor = frontface.css('color'); } if($this.hasClass('fig-tile')) { caption = $this.find('figcaption'); bgcolor = caption.css('background-color'); textColor = caption.css('color'); } $this.on('click',function(){ $('.'+page).css({'background-color': bgcolor, 'color': textColor}) .find('.close-button').css({'background-color': textColor, 'color': bgcolor}); }); }); function showDashBoard(){ for(var i = 1; i <= 3; i++) { $('.col'+i).each(function(){ $(this).addClass('fadeInForward-'+i).removeClass('fadeOutback'); }); } } function fadeDashBoard(){ for(var i = 1; i <= 3; i++) { $('.col'+i).addClass('fadeOutback').removeClass('fadeInForward-'+i); } } $('.tile').each(function(){ var $this= $(this), pageType = $this.data('page-type'), page = $this.data('page-name'); $this.on('click',function(){ if(pageType === "s-page"){ fadeDashBoard(); $('.'+page).addClass('slidePageInFromLeft').removeClass('slidePageBackLeft'); } else{ $('.'+page).addClass('openpage'); fadeDashBoard(); } }); }); $('.r-close-button').click(function(){ $(this).parent().addClass('slidePageLeft') .one('webkitAnimationEnd oanimationend msAnimationEnd animationend', function(e) { $(this).removeClass('slidePageLeft').removeClass('openpage'); }); showDashBoard(); }); $('.s-close-button').click(function(){ $(this).parent().removeClass('slidePageInFromLeft').addClass('slidePageBackLeft'); showDashBoard(); }); })();وبهذا نكون قد وصلنا إلى نهاية الدرس. أرجو أن يكون ممتعًا ومفيدًا. بإمكانك استعراض مثال حيّ لهذا الدّرس من هنا. أما الشيفرة المصدرية فهي مُتوفّرة في هذا المُستودع. ترجمة -وبتصرّف- للمقال How to Create Windows-8-like animations with CSS3 and JQuery لصاحبته Sara Soueidan.
-
إنّ إتقان تخطيط صفحات الويب بدون إتقان لغة CSS سيكون كمن يحاول السباحة على أرضٍ جافة، ولكن على عكس السباحة التي عندما تتقنها تبقى معك طول الحياة فإنّه لا يوجد مرحلة يمكنك عندها التوقف عن التعلم وتقول أنّك أتقنت CSS فهذه اللغة تتطور بسرعة يومًا بعد يوم. كما أنّ تعلم وإتقان هذه اللغة سيكون أكثر تحديًا بسبب وجود إختلافات في كيفية تطبيق ودعم هذه اللغة من قبل المتصفحات (حتى بين الإصدارات المختلفة للمتصفح نفسه). ولمدة تناهز العشر سنوات كان مطوّرو الويب يتصارعون ويعانون من الدعم المتشتت وغير المتناسق لخصائص CSS3 في كل إصدار جديد للمتصفحات. ولكن لنتفق على شيء ما وهو أنّ إتقان CSS شيء لا بد منه لأي مطور ويب جيد. وفي هذا المقال سوف نأخذكم في جولة لنتعرف على مبادئ CSS في تخطيط الصفحات وسوف نبدأ من التقنيات التي ظهرت في CSS2 وانتهاءً بآخر ما ظهر في CSS3. ملاحظة: سوف نستخدم HTML5 وSass في هذا المقال. ويمكنك الحصول على الشيفرات البرمجية كاملة من هنا. إحدى حالات الاستخدامإنّ من أفضل الطرق لتعلم أي تقنية هو أن يكون هناك حالة استخدام محددة تحاول دعمها أو أنّك تبحث عن حل لمشكلة ما. وحتى نهاية هذا المقال سنركز على حالة استخدام بمجموعة من المتطلبات. ستكون حالة الاستخدام التي سنعمل عليها عبارة عن تخطيط لتطبيق ويب (Web App) مع بعض السلوكيات المتغيرة/الديناميكية (dynamic)، بحيث سيكون هناك عناصر ثابتة في الصفحة مثل الترويسة (header) والتذييل (footer) وقائمة رئيسية (navigation) وقائمة فرعية (sub-navigation) وقسم محتوى قابل للتمرير(scrollable content). ستكون المتطلبات الخاصة بتخطيط الصفحة كما يلي: التخطيط الأساسي:الترويسة، التذييل، قائمة رئيسية وقائمة فرعية وهذه العناصر ستبقى ثابتة عند التمرير (scroll).سوف تشغل القائمة الرئيسية والقائمة الفرعية أي مساحة عمودية فارغة.سوف يشغل قسم المحتوى المساحة المتبقية في الصفحة وسوف يحتوي على منطقة قابلة للتمرير.السلوكيات المتغيرة/الديناميكية:سوف تحتوي القائمة الرئيسية على أيقونات فقط بشكل افتراضي، ولكن يمكن لها أن تتمدد لتحتوي على بعض النصوص (وأيضًا تتقلص/تنطوي لتظهر الأيقونات فقط مرة أخرى). اختلافات في تخطيط الصفحة:ستحتوي بعض الصفحات على قائمة فرعية بجانب القائمة الرئيسية والبعض الآخر لا. استخدام تقنيات CSS2 هذا هو تخطيط HTML5 الذي سوف نستخدمه: <body class="layout-classic"> <header id="header"></header> <nav id="nav"></nav> <aside id="subnav"></aside> <main id="main"></main> <footer id="footer"></footer> </body> 1. الموضعة الثابتة (position: fixed)في CSS2 يمكنك الحصول على عناصر ثابتة في الصفحة (مثل الترويسة، التذييل...الخ) عن طريق توظيف نموذج تخطيط يَستخدم الموضعة الثابتة (يستخدم position: fixed). سوف نستخدم أيضًا الخاصية z-index لنتأكد بأنّ العناصر الثابتة سوف تظهر فوق جميع العناصر الأخرى في الصفحة، بحيث تقوم هذه الخاصية بتحديد مكان العنصر إمّا أعلى أو أسفل عنصر آخر، فالعناصر التي تحتوي على قيمة z-index كبيرة سوف تظهر فوق العناصر التي تحتوي على قيمة z-index أقل. هناك شيء مهم يجب عليك تذكره وهو أنّ خاصية z-index لن تعمل إلا بوجود خاصية position، أي أنّك إذا استخدمت خاصية z-index على أحد العناصر ولكنك لم تستخدم خاصية position على نفس العنصر فإنّ خاصية z-index لن تعمل. وبالنسبة لنا، فسوف نستخدم القيمة 20 (وهي أعلى من القيمة الافتراضية) حتى نُبقي العناصر الثابتة فوق العناصر الأخرى في الصفحة. وسوف نستخدم خاصية width ونعطيها القيمة 100% وهذا سيسمح للعناصر بالتمدد أفقيًا بالقدر الذي تستطيعه. #header, #footer { position: fixed; width: 100%; z-index: 20; } #header { top: 0; height: 5em; } #footer { bottom: 0; height: 3em; }هذا بالنسبة للترويسة والتذييل، ولكن ماذا بالنسبة للقائمة الرئيسية (nav#) والفرعية (subnav#)؟ 2. تقنية التوسع/التمدد في CSSبالنسبة للقائمة الرئيسية (nav#) والفرعية (subnav#)، سوف نستخدم تقنية تسمى بتقنية التمدد (CSS Expantion) بحيث تُستخدم هذه التقنية على العناصر التي تحتوي على الخاصية position: fixed (بحيث يبقى العنصر ثابت في الصفحة) أو الخاصية position: absolute (بحيث يتم موضعة العنصر بناءً على أقرب عنصر حاوي يحتوي على الخاصية position بقيمة غير القيمة static). يمكن الحصول على تمدد رأسي/عمودي (vertical) باستخدام الخاصيتين top و bottom وإعطائها قيم محددة بحيث يتمدد العنصر عموديًا ليستخدم المساحة العمودية المتبقية وفقًا لتلك القيم، أي أنّ ما نقوم به هو ربط الجزء العلوي للعنصر بمسافة محددة من الجزء العلوي للصفحة وكذلك ربط الجزء السفلي للعنصر بمسافة محددة من الجزء السفلي للصفحة مما سيؤدي إلى تمدد العنصر ليشغل المساحة العمودية بين هاتين النقطتين (العلوية والسفلية). ونفس الأمر ينطبق على التمدد الأفقي بحيث نستخدم الخاصيتين left و right ونعطيها قيم محددة بحيث يتمدد العنصر أفقيًا ليشغل المساحة الأفقية المتبقية وفقًا لتلك القيم. وبالنسبة لنا في هذه الحالة فإننا نريد أن نستخدم التمدد العمودي: #nav, #subnav { position: fixed; top: 6em; /* ترك مسافة فوق الترويسة */ bottom: 4em; /* ترك مسافة تحت التذييلة */ z-index: 20; } #nav { left: 0; width: 5em; } #subnav { left: 6em; /* leave 1em margin to right of nav */ width: 13em; }3. الموضعة الإفتراضية/الساكنة (static)سوف نستخدم الموضعة الساكنة لموضعة منطقة المحتوى القابلة للتمرير، بحيث تظهر العناصر وتتموضع بالترتيب كما تظهر بالتدفق الطبيعي للمستند (كما تظهر في ملف HTML)، وبما أنّ جميع العناصر الأخرى في الصفحة متموضعة بشكل ثابت فإنّ هذا العنصر سيكون هو العنصر الوحيد الذي يظهر وفقًا للتدفق الطبيعي للمستند. ونتيجة لذلك فكل ما نحتاجه لموضعة العنصر بشكل مناسب هو استخدام الخاصية margin حتى لا يحصل تداخل بينه وبين العناصر الأخرى الثابتة (الترويسة، التذييل والقائمتين الرئيسية والفرعية): #main { margin: 6em 0 4em 20em; }وبهذا نكون قد أتممنا متطلبات التخطيط الأساسي باستخدام CSS2 وبقي علينا أن نهتم بأمر المتطلبات الإضافية الخاصة بالسلوكيات المتغيرة/الديناميكية. 4. السلوكيات المتغيرة/الديناميكية باستخدام تقنيات CSS2ذكرنا سابقًا بأنّ القائمة الرئيسية سوف تحتوي على أيقونات فقط بشكل افتراضي، ولكن يمكن لها أن تتمدد لتحتوي على بعض النصوص (وأيضًا تتقلص/تنطوي لتظهر الأيقوانات فقط مرة أخرى). لنبدأ أولًا بإعطاء القائمة الرئيسية عندما تكون متمددة عرضًا (width) بقيمة 5em زيادة على عرضها الرئيسي (أي يصبح عرضها عندما تتمدد 10em)، وسوف نقوم بذلك عن طريق إنشاء class باسم "expanded" بحيث يمكننا ديناميكيًا (باستخدام الجافاسكربت) إضافته أو إزالته من القائمة الرئيسية: #nan { left: 0; width: 5em; &.expanded { /* Sass notation */ width: 10em; } } يمكنك بالأسفل رؤية كود الجافاسكربت (jQuery في حالتنا هذه) الذي سوف نستخدمه لإضافة أو إزالة الـclass الذي يحمل الاسم "expanded" عندما يقوم المستخدم بالنقر على أيقونة القائمة: $('.layout-classic #nav').on('click', 'li.nav-toggle', function() { $('#nav').toggleClass('expanded'); });يمكن الآن للقائمة الرئيسية أن تتمدد أو تتقلص بكل سهولة. ولكن هناك مشكلة صغيرة وهو أنه عندما تتمدد القائمة الرئيسية فإنها سوف تتداخل مع القائمة الفرعية وهو ما لا نريده بكل تأكيد، ولذلك سوف نحتاج إلى تعديل الأمور قليلًا. يمكنك الآن رؤية واحدة من المشاكل/القيود في CSS2، فسوف نحتاج الآن إلى كتابة العديد من الأكواد الخاصة بقيم العناصر المتموضعة بشكل ثابت، ونتيجة لذلك فسوف نحتاج إلى تعريف classes باسم "expanded" إضافية حتى نسمح للعناصر الأخرى بأن تتموضع لاستيعاب القائمة الرئيسية عندما تتمدد، وبذلك سوف نحتاج إلى كتابة المزيد والمزيد من الأكواد الإضافية. #subnav { left: 6em; width: 13em; &.expanded { left: 11em; /* تحريكها لليمين */ } } #main { margin: 6em 0 4em 20; z-index: 10; &.expanded { margin-left: 25em; /* تحريكها لليمين */ } }سوف نحتاج أيضًا إلى إضافة أكواد جافاسكربت إضافية لإضافة أو إزالة الـclass "expanded" لتلك العناصر عندما يقوم المستخدم بالنقر على القائمة الرئيسية. $('.layout-classic #nav').on('click', 'li.nav-toggle', function() { $('#nav, #subnav, #main').toggleClass('expanded'); });سيكون كل شيء أفضل الآن. 5. اختلافات تخطيط الصفحة باستخدام تقنيات CSS2كنا قد ذكرنا مسبقًا بأنّ بعض الصفحات لن تحتوي على قائمة فرعية. ولنكون أكثر دقة فإننا نريد للقائمة الفرعية أن تختفي عندما يضغط المستخدم على أيقونة "المستخدمين" (users) الموجودة في القائمة الرئيسية. سوف نبدأ أولًا بإنشاء class بالاسم "hidden" وفيه الخاصية display: none: hidden { display: none; }كما أننا سنستخدم الجافاسكربت لإخفاء القائمة الفرعية وذلك عن طريق تطبيق الفئة "hidden" على هذه القائمة عندما يقوم المستخدم بالنقر على أيقونة "المستخدمين" (users): $('#nav.fa-user').on('click', function() { $('#subnav').toggleClass('hidden'); });وبهذا يمكن للعناصر أن تختفي عند النقر على أيقونة "المستخدمين" ولكن المساحة التي كانت تحتلها سوف تبقى غير مستخدمة بدلًا من أن تقوم العناصر الأخرى باستخدام تلك المساحة. وحتى نحصل على السلوك المطلوب عندما نقوم بإخفاء القائمة الفرعية فإننا سوف نستخدم المحدد المجاور(adjacent sibling selector) وهو يأتي على شكل إشارة الجمع +. 6. المحدد المجاوريُستخدم المحدد المجاور لتحديد عنصرين واختيار العنصر الثاني الذي يأتي مباشرة بعد العنصر الأول. فعلى سبيل المثال، سيقوم الكود التالي باختيار العنصر الذي يحمل ID بقيمة main والذي يأتي مباشرة بعد العنصر الذي يحمل ID بقيمة subnav: #subnav + #main { margin-left: 20em; }استخدمنا الكود في الأعلى لإعطاء العنصر main# الخاصية margin-left: 20em فقط إذا كان هذا العنصر يأتي مباشرة بعد العنصر subnav#. ولكن عندما يتمدد العنصر nav# (بحيث يتم إضافة الفئة expanded إلى العنصر main# بناءً على الكود الذي كتبناه سابقًا) فإننا نريد للخاصية margin-left أن تحتوي على القيمة 25em. #subnav + #main.expanded { margin-left: 25em; }وأمّا إذا كانت القائمة الفرعية subnav# مخفية فإننا نريد للخاصية margin-left الخاصة بالعنصر main# أن تحتوي على القيمة 6em: #subnav.hidden + #main { margin-left: 6em; }ملاحظة: واحدة من مساوئ استخدام المحدد المجاور هو أن الـDOM سوف يحتوي دائمًا على العنصر subnav# حتى لو كان غير ظاهر في الصفحة. وأخيرًا، إذا كان العنصر subnav# مخفيًا وnav# متمددًا فإننا نريد الخاصية margin-left للعنصر main# بأن تكون بالقيمة 11em: #subnav.hidden + #main.expanded { margin-left: 11em; }خاتمةكل شيء إلى الآن يظهر في مكانه الصحيح من دون الحاجة إلى استخدام أكواد جافاسكربت كثيرة، ولكن يمكنك ملاحظة أن الكود يمكن أن يكون كبيرًا ومعقدًا إذا ما أردنا إضافة المزيد من العناصر إلى الصفحة. لاحظ أيضًا أنّه باستخدام CSS2 سيكون هناك الكثير من الأكواد لموضعة كل شيء في مكانه المناسب. لذلك سوف نقوم في الدرس القادم باستخدام بعض تقنيات CSS3 الجديدة وإعادة تخطيط الصفحة باستخدام تلك التقنيات. ترجمة -وبتصرّف- للمقال CSS Layout Tutorial: From Class+ic Approaches to the Latest Techniques لصاحبه Laureano Martin Arcanio.
-
تُعتبر لوحة تسجيل الدخول في ووردبريس من أقل المواضيع التي يتم التحدث فيها أو التلاعب بتصاميمها على عكس التصاميم الخاصة بالقوالب، ولكنه من الجيد لك أن تعرف كيف تُنشئ واحدة حتى يبدو موقعك أو موقع عميلك متميّزًا وله رونقه الخاص. تابع معنا هذا الدرس لتعرف كيفية إنشاء لوحة تسجيل دخول خاصة بدون استعمال الإضافات. لماذا يجب عليك تعلم ذلك؟كل المواقع التي تعمل على منصة ووردبريس تملك نفس تصميم لوحة الدخول وهو التصميم الرئيسي الخاص بالووردبريس، ولكن بعض العملاء يريدون أن يتميزوا بكل صغيرة وكبيرة في موقعهم ومن ضمنها لوحة الدخول. سأريك الآن كيف تقوم بذلك فالأمر سهل وغير معقد ويمكنك أن تكتب أكواد CSS وjQuery خاصة بك ولن تحتاج إلى أي إضافات. لم لا تستخدم إضافة جاهزة وحسب؟هناك إضافة جيدة اسمها "BM Custom Login" ولكن حدثت عليها الكثير من التعديلات والاختلافات منذ إصدارها الأول، وأصبحت أحس أن الإصدار الحالي ليس بتلك الجودة. وشيء آخر، وهو أنه يجب عليك أن تقلل من الإضافات في موقعك على قدر الإمكان إن أردت لموقعك أن يكون سريعًا. على كل حال وكما ذكرنا سابقًا فإننا لن نستخدم أي إضافات في هذا الدرس. هيكلة القالب الجميل في هذه الطريقة هو أنك تستطيع أن تحفظ كل الملفات المهمة داخل القالب نفسه على عكس الإضافات التي تُبقي ملفاتها بداخل مجلد الإضافة نفسه، وبذلك نُبقي كل شيء منظم ومن السهل الرجوع إليه في أي وقت. تحديث/تعديل ملف functions.phpfunction custom_login() { $files = '<link rel="stylesheet" href="'.get_bloginfo('template_directory').'/css/login.css" /> <script src="http://use.typekit.com/pgf3epu.js"></script> <script>try{Typekit.load();}catch(e){}</script> <script src="'.get_bloginfo('template_directory').'/js/jquery.min.js"></script> <script src="'.get_bloginfo('template_directory').'/js/login.js"></script>'; echo $files; } addaction('loginhead', 'custom_login');أول خطوة ستكون كتابة دالة داخل ملف functions.php وتخزين كل الملفات الضرورية داخل مُتغيّر ثمَّ عمل echo له. يمكننا مناداة الملفات الموجودة داخل القالب باستعمال ('get_bloginfo('template_directory وربط الملف مباشرة. لقد قمت أيضًا بإضافة ملف jQuery مُصغّر (minified) وtypekit أيضًا. function customloginurl() { echo bloginfo('url'); } addfilter('loginheaderurl', 'customloginurl'); function customlogintitle() { echo get_option('blogname'); } addfilter('loginheadertitle', 'customlogintitle');قمت أيضًا بإضافة دالّتين؛ واحدة لتغيير رابط الشّعار حتى يظهر الشّعار الخاص بالموقع بدلًا من شعار موقع Wordpress.org، والدالة الثانية استخدمتها لتغيير عنوان لوحة تسجيل الدخول. هذا كان ما يخص أكواد PHP فلن نحتاج إلى أي أكواد PHP إضافية بعد الآن. تغيير تنسيقات CSSهنا يبدأ التحدي. سوف تحتاج الآن إلى الإطلاع على عناصر DOM لمعرفة العناصر التي تستطيع تعديلها وسوف تحتاج أيضًا إلى إزاحة تنسيقات CSS الموجودة في الصفحة واستبدالها بتنسيقات أخرى، وحتى تفعل ذلك سوف تحتاج إلى استخدام Developer Tools كتلك الموجودة في متصفح Google Chrome أو يمكنك استخدام إضافة Firebug المشهورة. ما أحبّ إضافته في بداية الملف هو المحدد العام * لتحديد واستهداف كافة العناصر ومن ثم أضيف لها خاصية transition وخاصية webkit-font-smoothing: antialiasing- حتى نحصل على خط أفضل في متصفحات webkit. ما أفضله أيضًا هو التعديل على محدد الفئة الزائفة (pseudo-class) المسمى focus: للتخلص من الحدود الخارجية (outlines). * { -webkit-transition: all 0.3s ease; -moz-transition: all 0.3s ease; -ms-transition: all 0.3s ease; transition: all 0.3s ease; -webkit-font-smoothing: antialiased; } :focus { outline: 0!important; }يمكننا باستعمال Developer Tools معرفة العناصر الموجودة ومعرفة تنسيقات CSS المستخدمة لنتمكن من تغييرها. فعلى سبيل المثال، يمكننا تغيير الشعار الموجود أعلى لوحة تسجيل الدخول إلى شعار الموقع نفسه: body.login h1 a { background: url('../images/logo.png') center center no-repeat transparent; background-size: 188px 189px; width: 188px; height: 189px; margin: 0 auto 30px; opacity: 0.7; padding: 0; } body.login h1 a:hover {opacity: 1;}كما ترى في الأعلى فقد قمت بتحديد العنصر body.login ثم h1 الموجود بداخله انتهاءً بالوسم a، وقمت بعد ذلك باستخدام الخاصية background لوضع صورة الشعار الموجودة في مجلد images واستعمال بعض تنسيقات CSS بسيطة. لاحظ أيضًا أنني استعملت خاصية background-size كما هو في Wordpress 3.4 وخاصية opacity لتقليل شفافية الشّعار وإعادته إلى شفافيته كاملة عند وضع مؤشر الفأرة فوقه (hover). نريد أيضًا إخفاء عنصر backtoblog# لأننا لن نحتاجه فالشّعار سوف يفي بالغرض ليأخدنا إلى الصفحة الرئيسية للمدونة. يمكنني استعمال الكثير من تنسيقات CSS لتغيير جميع العناصر حتى تتوافق مع التصميم، وإذا أردت استبدال تنسيقات مكان أخرى فسوف أستعمل ids الموجودة في الصفحة وimportant! إن اضطررت لذلك. form#loginform p.forgetmenot label { position: relative; background-image: url('../images/checkbox.png'); background-position: 0 0; background-repeat: no-repeat; padding: 2px 0 0 24px; height: 18px; display: inline-block; -webkit-transition: none; -moz-transition: none; -ms-transition: none; transition: none; } form#loginform p.forgetmenot label input[type="checkbox"] { position: absolute; left: 0; opacity: 0; width: 20px; height: 20px; display: block; cursor: pointer; }سنقوم الآن بتغيير التصميم الخاص بمربع الاختيار (checkbox) وذلك باستعمال background-image على الـتسمية (label) وإضافة بعض padding إلى اليسار وسوف نقوم بإخفاء مربع الاختيار نفسه. ولسوء الحظ، فإنه سيكون من الصعب تغيير الصورة عند النقر عليها وذلك لأن مربع الاختيار موجود داخل وسم <label>، وبالتالي سوف نضطر إلى استعمال jQuery. إضافة أكواد jQueryيمكننا استعمال jQuery لإضافة بعض التنسيقات أو attributes أو حتى تغيير أجزاء بعض العناصر، كما أنني أريد أن أضيف placeholders إلى حقول الإدخال وكذلك جعل مربع الاختيار يعمل مع بعض الصور الخاصة وكل ذلك سوف يتم داخل ملف login.js الموجود في مجلد "js" الخاص بالقالب. $('#loginform input[type="text"]').attr('placeholder', 'Username'); $('#loginform input[type="password"]').attr('placeholder', 'Password'); $('#loginform label[for="user_login"]').contents().filter(function() { return this.nodeType === 3; }).remove(); $('#loginform label[for="user_pass"]').contents().filter(function() { return this.nodeType === 3; }).remove();يمكننا باستعمال jQuery إضافة placeholder إلى حقول الإدخال، ولكن ذلك لن يكون سهلًا بسبب وجود الحقول داخل وسوم <label> مما سيجعل عملية إزالة النص الخاص بالـتسمية أمرًا ليس باليسير. سوف نقوم باستعمال ()contents. و ()filter. لإزالة النصوص الخاصة بالـتسمية ليبقى لدينا placeholders فقط. $('input[type="checkbox"]').click(function() { $(this+':checked').parent('label').css("background-position","0px -20px"); $(this).not(':checked').parent('label').css("background-position","0px 0px"); });وكما قلنا سابقًا، فوجود مربع الاختيار داخل وسم <label> سيجعل عملية تطويع مربع الاختيار ليعمل كما نريد أمرًا صعبًا. فالطريقة التي من المفترض أن يعمل بها هو أنّه عندما يتم الضغط على التسمية (label) فإنّ مربع الاختيار سوف يتم اختياره (يصبح checked) وبالتالي تتغير الصورة التي أضفناها لتدل على أنه تم النقر على المربع، ولذلك قمنا باستعمال jQuery لنتفقد فيما إذا كان مربع الاختيار في حالة checked أو لا، فإذا كان في حالة checked فإن المحدد checked: سوف يعمل على تغيير موضعة الصورة (باستعمال background-position) واذا لم يكن كذلك فسوف يعود كل شيء إلى طبيعته. خاتمة كما رأيت، فقد قمنا بإنشاء لوحة تسجيل دخول بكل سهولة وذلك فقط باستعمال Wordpress functions ،CSS وjQuery ومن دون الحاجة إلى أي إضافات. يمكنك الإطلاع على النتيجة النهائية من هنا. ترجمة -وبتصرف- للمقال: Create a Custom WordPress Login Without Plugins لصاحبه: Iggy.
-
تعد شبكة الخلايا المنزلِقة Slidable grid وسيلة رائعة وجذابة لعرض أجزاء عدة من المعلومات في نفس المساحة؛ فتنزلق كل خلية من الشبكة عند النقر أو الحومان Hover وتعرِض محتوى إضافيا. سنتطرق خلال هذا الدرس إلى عملية إنشاء شبكة خلايا منزلقة ابتداءً من وسوم HTML الضرورية، التنسيق وجعل الشبكة متجاوبة Responsive؛ مع إضافة خطوط أيقونات الويب. سنعرِض أيضا لكيفيّة استخدام jQuery لإضافة بعض التأثيرات على شبكة الخلايا. هكذا ستبدو شبكة الخلايا المنزلِقة بعد اكتمال الدّرس. إنشاء الشبكة في HTMLفي ما يلي وسوم HTML المستعملة لإنشاء الشبكة. سنشرح عملها بالمختصر. نحتاج لعنصر تغليف Wrapping يحيط بالشبكة، ثم أجزاء مستقلة بتموضع نسبي Relative positioning داخل عنصر التغليف. تمثل هذه الأجزاء الخلايا المرئية المكونة للشبكة. توجد داخل كل خليّة مساحتان بتموضع مطلق Absolute تملأ كل واحدة منهما طول وعرض الخلية تماما. سنضيف تأثيرات إلى كل من المساحتيْن بحيث تكون كل مساحة إما خارج مجال الرؤية في الصفحة أو مرئية. <div id="services" class="cf"> <section class="service"> <div class="service-icon"><span class="icon-web"></span><br/>التصميم للويب</div> <div class="service-description"><p>تقديم لطيف حول التصميم للويب وما نقدمه من خِدْمات في هذا المجال</p></div> </section> <section class="service"> <div class="service-icon"><span class="icon-graphic"></span><br/>التصميم الغرافيكي</div> <div class="service-description"><p> ... </p></div> </section> <section class="service"> <div class="service-icon"><span class="icon-logo"></span><br/>تصميم الشعارات</div> <div class="service-description"><p> ... </p></div> </section> <section class="service"> <div class="service-icon"><span class="icon-dev"></span><br/>التطوير للويب</div> <div class="service-description"><p> ... </p></div> </section> <section class="service"> <div class="service-icon"><span class="icon-3d"></span><br/>التصميم ثلاثي الأبعاد</div> <div class="service-description"><p> ... </p></div> </section> <section class="service"> <div class="service-icon"><span class="icon-illustration"></span><br/>إيضاحات</div> <div class="service-description"><p> ... </p></div> </section> </div> <!-- END #services -->يوجد div بمعرّف services وصنف Class اسمُه cf؛ توجد داخله ستة عناصر section وبداخل كل section عنصرا div، على النحو الذي ذكرناه سابقا. يوجد بداخل الأول من عنصري div عنصر span لنضع داخله أيقونة وعنوان. في عنصر div الآخر يمكن إدراج المحتوى الإضافي (وصف مثلا). حرصنا على جعل السّكربت يظهر في حالة عدم وجود Javascript لدى الزائر، فيظهر لديه المحتوى دون التأثيرات. ننتقل الآن إلى تنسيق المحتوى عن طريق CSS وتهيئته لعمل تأثيرات عليه بواسطة jQuery. تنسيق الشبكة عن طريق CSSيتكوّن ملف CSS من ثلاثة أجزاء: الأساسي للعمل مع jQuery، شفرة خط الأيقونة، والأخير تنسيقات لمظهر أجمل. في ما يلي الجزء الأول. #services .service { width: 33%; float: left; padding: 0.5em; min-height: 200px; overflow: hidden; position: relative; border: 1px solid #eee; } @media screen and (max-width: 600px) { #services .service { width: 50%; } } @media screen and (max-width: 320px) { #services .service { width: 100%; } } #services .service .service-icon, #services .service .service-description { position: absolute; width: 100%; height: 100%; top: 0; left: 0; background: #fff; padding: 50px 0; color: #222; } #services .service .service-description { left: 100%; background: #249EC2; color: white; padding: 50px; } #services .service .service-description:hover { cursor: pointer; }نشرح عمل الأسطر السابقة. نستهدف أولا الخلايا (service.) لترتيبها داخل الشبكة بإعطائها عرضا مائعا Fluid وحدا أدنى للارتفاع، وجعلها تطفو إلى اليسار (لهذا السبب سنجعل - في ما بعد - عنصر التغليف يمنع المحتوى من الانسياب باستخدام خاصية clear. راجع درس أساسيات الطوفان Float في أوراق الأنماط المُتتالية CSS بهذا الخصوص). ثم - وهذا مهم جدا - نعطي القيمة hidden لخاصية overflow (لو لم نفعل ذلك لعُرِض المحتوى الإضافي طول الوقت) مع تحديد التموضع النسبي position: relative. نستخدم بعدها استعلامات الوسائط Media queries لأخذ عدة شاشات في الحسبان وبالتالي جعل الشبكة متجاوبة. تعني هذه الشيفرة أن تصميم الشبكة سيتكون من ثلاثة أعمدة بالنسبة لسطح المكتب (عرض الشاشة أكبر من 600px)، ثم يتحول إلى عموديْن إذا نقُص عرض الشاشة وفي الشاشات الصغيرة يصبح عمودا واحدا. نستهدف الآن - بعد الانتهاء من تجهيز الخلايا - العناصِر الداخلية، service-icon. وservice-description.؛ ونعطيها تموضعا مطلقا (لهذا السبب أعطينا حدا أدنى للارتفاع في النمط السابق) ثم نضعها في يسار الجزء العلوي. بالنسبة لموضع الوصف service-description. فسنغيره بعد قليل. نجعل العنصريْن الداخليّيْن يملآن كامل العنصر الأب. البقية للتأثير المرئي. أخيرا نستهدف عنصُر الوصف لوحده فنعطي القيمة %100 لخاصية left مما يجعله بالكامل يندفع إلى اليمين خارج مجال الرؤية نظرا لضبط خاصية overflow على hidden ضمن service.. سنستهدغ قيمة left هذه في jQuery، وهو ما يجعل من المهم تعريفها الآن. خط الأيقونةننتقل الآن إلى الخطوة التالية وهي تعريف أسماء الأصناف المُستخدمة ضمن HTML لتُعرَض الأيقونات الصحيحة؛ نستخدم font-face@ للحصول على خطوط الأيقونات. نبحث أولا عن وسيلة لإيجاد خط أيقونات مناسب لاحتياجاتنا. توجد عدة خيّارات اخترنا من بينها موقع Fontastic. تختار على الموقع الأيقونات التي ترغب في استخدامها، يعتمد الاختيار طبعا على نوعية المشروع الذي تعمل عليه. يمكنك تغيير بعض المعلومات مثل أسماء أصناف الأيقونات وأسماء الخطوط على النحو المبيَّن أدناه. اخترنا نفس الأسماء الموجودة في سكربت HTML من أجل تطابق دون مشاكل. يعطيك الموقع مجلدا لتنزيله مع شفرة للاستخدام. ضَع مجلد الخطوط في مجلد CSS (أو أي مجلد آخر يناسبك) ثم خذ الشفرة وضعها في ملف CSS. إليك ما تحتاجه: @font-face { font-family: "slidable-grid"; src:url("fonts/slidable-grid.eot"); src:url("fonts/slidable-grid.eot?#iefix") format("embedded-opentype"), url("fonts/slidable-grid.woff") format("woff"), url("fonts/slidable-grid.ttf") format("truetype"), url("fonts/slidable-grid.svg#slidable-grid") format("svg"); font-weight: normal; font-style: normal; } [class^="icon-"]:before, [class*=" icon-"]:before { font-family: "slidable-grid" !important; font-style: normal !important; font-weight: normal !important; font-variant: normal !important; text-transform: none !important; speak: none; font-size: 4em; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-web:before { content: "a"; } .icon-graphic:before { content: "b"; } .icon-logo:before { content: "c"; } .icon-dev:before { content: "d"; } .icon-3d:before { content: "e"; } .icon-illustration:before { content: "f"; }إن أعدت تحميل المشروع فستظهر الأيقونات. لم يتبقَّ لنا سوى إكمال التنسيق. تنسيقات نهائيةنستكمل ما بقي من تنسيقات. في ما يلي الشفرة التي تضع الشبكة في الوسط وتعطيها العرض الأقصى. نضيف تأثيرا للحوم على الأيقونات: @import url(reset.css); * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } .cf:before, .cf:after { content: " "; /* 1 */ display: table; /* 2 */ } .cf:after { clear: both; } .cf { *zoom: 1; } body { font-family: 'Exo 2', sans-serif; /* Google Font <a href="http://google.com/fonts">http://google.com/fonts</a> */ text-align: center; color: #999; background: #444; -webkit-font-smoothing: antialiased; } #services { max-width: 850px; margin: 0 auto; } #services .service .service-icon:hover { cursor: pointer; color: #249EC2; } #services .service .service-icon span { display: block; -webkit-transition: all 0.1s linear; -moz-transition: all 0.1s linear; transition: all 0.1s linear; } #services .service .service-icon:hover span { position: relative; bottom: 5px; }إضافة jQueryنهدف من خلال استخدام jQuery إلى إعادة استخدام نفس الشفرة البرمجية للشبكة كاملة. ننتظر النقر على إحدى خلايا الشبكة (صنف service) وعند حدوثه نحرك مكان الأيقونة ونزيحها خارج الإطار المرئي ثم نجلب الوصف لعرضه. نضيف أيضا صنفا جديدا (open) لمساعدتنا في معرفة العنصر المرئي. اجلب jQuery إلى الصفحة ثم أضف الشفرة التالية في ملف أو في وسم <script>: $(document).ready(function() { $('.service').click(function() { var $this = $(this); if ($this.hasClass("open")) { $this.find('.service-icon').animate({left: "0"}); $this.find('.service-description').animate({left: "100%"}); $this.removeClass("open"); } else { $this.find('.service-icon').animate({left: "-100%"}); $this.find('.service-description').animate({left: "0"}); $this.addClass("open"); } }); });بعد أن تجهز الصفحة وعند النقر على كتلة service. نختبر هل لدى هذه الكتلة صنف open أم لا. في البداية لا يوجد قسم صنف لدى العناصر service مما يعني أن الاختبار سيتجاوز مباشرة إلى تنفيذ الأوامر الموجودة في else، فنبحث عن عنصر div الذي توجد به الأيقونة (service-icon.) ونحركه إلى اليسار ب%100-؛ ثم نعثر على الوصف ونحركه إلى اليسار عند 0 مما يجعله مرئيا. في الأخير نضيف صنف open إلى service. يعني هذا أنه سيكون لدى service بعد النقر عليه وإظهار المحتوى الإضافي صنفٌ باسم open على النحو الذي يظهر في الصورة التالية. يمكننا بالعودة إلى تعليمة if في الشفرة السابقة أن نرى أنه بالنقر على كتلة service. لديها صنف باسم open فإن الأيقونة تعود إلى الوضعية 0 والوصف ينزاح ب%100. نزيل بعدها الصنف open لتمكن إعادة استخدامه والنقر عليه أكثر من مرة. خاتمة وأفكارلدينا الآن نظام بشبكة لدى كل خلية منها وجهان. يمكن التعديل على الفكرة ومواءمتها حسب طبيعة الاستخدام. يوجد في الصفحة التجريبية مثال آخر يُبدَل فيه وجه الخلية عند الحوم فوقها وليس عند النقر. يمكن الحصول على هذا التأثير عبر إبدال دالة ()click. ب()hover. في شفرة jQuery. يعود الخيار لك. يمكنك أيضا تغيير اتجاه التحريك بإبدال left بالقيمة المناسبة. سرعة الحركة يمكن التعديل عليها هي الأخرى. ابتعد عن الإكثار من استخدام شبكة الخلايا المنزلقة بدون داع، ولا تستعملها إلا إذا كانت تضيف إلى تجربة المستخدم. يمكن تنزيل نتيجة هذا الدرس demo.zip. ترجمة بتصرف لمقال How to create a slidable grid with jQuery لصاحبه Harry Atkins.
-
تعد مكتبة jQuery واحدة من أكثر المكتبات استخداما للإضافة على الصفحات؛ إذ أنها تجعل من التعامل مع نموذج كائن المستند Document object model, DOM أمرا في منتهى اليسر. لا شك أن السهولة في التعامل سبب أساسي في شعبية jQuery، إذ يبدو بالإمكان فعل أي شيء نريده عن طريق هذه المكتبة. توجد، من بين الخيارات المتاحة أمامنا، مقاطع Snippets تجنح إلى الظهور المرة تلو الأخرى. سنعرِض في هذا المقال إلى عشرة مقاطِع سيستخدمها الجميع، من المبتدئ إلى المتمكن، مرارا وتكرارا. زر العودة إلى الأعلى// عد إلى الأعلى $('a.top').click(function(){ $(document.body).animate({scrollTop : 0}, 800); return false; }); // Anchor tag أنشئ وسما للمربط <a class="top" href="#">عد إلى الأعلى</a>يتضح أننا لا نحتاج إلى إضافة Plugin لـ jQuery من أجل الحصول على تحريك سهل إلى الأعلى؛ يكفي استخدام دالتي animate و scrollTop. يمكن تغيير المكان الذي يحُط فيه شريط التمرير عبر تغيير قيمة scrollTop. في المثال أعلاه استخدمنا القيمة 0 لأننا نريد العودة إلى أعلى الصفحة، لكن لو أردنا زَيحانًا offset بقيمة 100px فيمكن إدراج هذه القيمة. يتلخص ما فعلناه في تحريك جسم المستند Document body طوال 800 ملي ثانيّة حتى يصل إلى القمّة. التحقق من تحميل الصور$('img').load(function() { console.log('image load successful'); });تحتاج أحيانا إلى التأكد من تحميل كل الصور قبل إكمال السكربت؛ تؤدي الأسطر الثلاثة أعلاه هذه المهمة بكل يُسر. يمكن أيضا التحقق من تحميل صورة معينة عبر إبدال وسم tag بمعرِّف ID أو صنف Class. تصحيح الصور المعطوبة تلقائيا$('img').error(function(){ $(this).attr('src', 'img/broken.png'); });تحصل أحيانا أعطاب في روابط الصور على الموقع يكون معها إبدال الروابط يدويا أمرا صعبا. يعمل المقطع أعلاه على إبدال الصور المعطوبة تلقائيا مما ينقِذ من الكثير من المشاكل. تبديل الصنف عند الحومان Hover$('.btn').hover(function(){ $(this).addClass('hover'); }, function(){ $(this).removeClass('hover'); } );نرغب عادة في تغيير مظهر العناصر القابلة للنقر في صفحة الويب عندما يحوم حولها المؤشر وهو بالضبط ما تفعله الأسطر في المقطع أعلاه؛ فتضيف صنفا للعنصر عند الحوم حوله ثم تزيل الصنف عندما يكُفّ المستخدِم. كل ما عليك فعله هو إضافة التنسيق المرغوب ضمن ملف CSS. تعطيل حقول الإدخال$('input[type="submit"]').attr("disabled", true);قد تريد تعطيل زر الإرسال أو حقل إدخال إلى أن يؤدي المستخدم إجراء معينا (التأشير على صندوق “قرأتُ الشروط”، مثلا). يضيف المقطع خاصية disabled (مُعطَّل) إلى الحقل مما يتيح لك تفعيله عندما تريد. كل ما عليك فعله لتفعيل الحقل هو تنفيذ الدالة removeAttr مع تمرير المُعطى disabled على النحو التالي: $('input[type="submit"]').removeAttr("disabled");إيقاف تحميل الروابط$('a.no-link').click(function(e){ e.preventDefault(); });قد نود أن تؤدي الروابط أعمالا أخرى غير الانتقال إلى صفحة أو حتى إعادة تحميلها، وهو ما تعمل عليه الأسطر أعلاه عبر تعطيل الإجراء الافتراضي Default action. قد يكون السبب - على سبيل المثال - تنشيطَ سكربت آخر. التبديل بين تأثيريْ التلاشي Fade والانزلاق Slide// تلاش $(".btn").click(function() { $(".element").fadeToggle("slow"); }); // تبديل $(".btn").click(function() { $(".element").slideToggle("slow"); });تأثيرا التّلاشي والانزلاق من أكثر التّأثيرات في jQuery استخداما. عندما نريد فقط عرض عنصر عند النقر فإن دالتي fadeIn وslideDown ملائمتان تماما. لكن إن أردنا أن يظهر العنصر بعد النقرة الأولى ثم يختفي بعد الثانية فهذا المقطع يؤدي المهمة بنجاح. تأثير الطّيّ// أغلق كل اللوحات $('#accordion').find(‘.content').hide(); // تأثير الطّيّ $('#accordion').find('.accordion-header').click(function(){ var next = $(this).next(); next.slideToggle('fast'); $('.content').not(next).slideUp('fast'); return false; });كل ما تحتاجه إلى جانب هذا المقطع هو شفرة HTML المناسبة للتأثير. أولا نغلق كل اللوحات ثم عند حدث النقر click ينزلق المحتوى المربوط بالترويسة accordion-header بالتتابع. هذه طريقة سهلة للحصول على تأثير طي بسرعة. تحديد ارتفاع عنصر div اعتمادا على آخر$('.div').css('min-height', $('.main-div').height());تمكِّن هذه الطريقة من تحديد نفس الارتفاع لعنصريْ div بغض النظر عن محتوى كل منهما. في السطر أعلاه عيّنا ارتفاع عناصر div بحيث يكون لديها على الأقل ارتفاع العنصر main-div. لائحة بألوان مختلفة حسب العناصِر الزوجية والفردية$('li:odd').css('background', '#E8E8E8');يمنح هذا السطر خلفية باللون المحدد للعناصر الفردية من اللائحة؛ مما يمكنك من الحصول على لائحة مخطَّطة - مثل هيئة الحمار الوحشي - عبر إضافة لون خلفية افتراضي في ملف CSS تأخذه العناصر الزوجية من اللائحة. لا يقتصر استخدام هذه الطريقة على اللوائح بل يتعداها إلى الجداول وعناصر div وغيرها. ترجمة بتصرّف لمقال 10 jQuery snippets every designer should know لصاحبته Sara Vieira.
-
AJAXAJAX هي اختصار للعبارة "asynchronous JavaScript and XML"، وهي وسيلة لجلب البيانات من الخادوم دون الحاجة لإعادة تحميل الصّفحة، وهي تقوم على استخدام كائن مُتاح في المتصفّح اسمه XMLHttpRequest (أو XHR اختصارًا) لإرسال الطّلب إلى الخادوم ثمّ التّعامل مع البيانات الّتي يُجيب بها الخادوم. تُوفّر jQuery الوظيفة $.ajax (ووظائف أخرى مرافقة مُختصرة) لتسهيل العمل مع طلبات XHR في جميع المتصفّحات. $.ajaxبإمكاننا استخدام الوظيفة $.ajax() المُرفقة مع jQuery بعدّة أساليب: إحداها أن نُمرّر إليها كائنًا يحوي الإعدادات فقط، أو أن نُمرّر الرّابط مع أو بدون كائن الإعدادات. لنُلقِ نظرة على الأسلوب الأول: // أنشئ دالّة الاستدعاء الرّاجع الّتي ستُنفّذ عندما ينجح طلب AJAX var updatePage = function( resp ) { $( '#target').html( resp.people[0].name ); }; // وعندما يفشل var printError = function( req, status, err ) { console.log( 'something went wrong', status, err ); }; // أنشئ كائن الإعدادات الذي يصف الطّلب var ajaxOptions = { url: '/data/people.json', dataType: 'json', success: updatePage, error: printError }; // أرسل الطّلب $.ajax(ajaxOptions);بإمكانك طبعًا أن تُمرّر كائنًا حرفيًّا مباشرةً إلى الوظيفة$.ajax() وأن تستخدم دالّة مجهولة محلّ success وerror، هذا الأسلوب كتابته أسهل، وصيانته في المستقبل أسهل: $.ajax({ url: '/data/people.json', dataType: 'json', success: function( resp ) { $( '#target').html( resp.people[0].name ); }, error: function( req, status, err ) { console.log( 'something went wrong', status, err ); } });كما قلنا، بإمكانك استخدام الوظيفة$.ajax() بأسلوب ثانٍ، وذلك بتمرير الرّابط أوّلًا ثمّ كائن الإعدادات ثانيًا (ليس إلزاميًّا). يُفيدك هذا في حال رغبت في استخدام الإعدادات المبدئيّة للوظيفة أو في حال رغبت في استخدام كائن الإعدادات نفسه لأكثر من رابط: $.ajax( '/data/people.json', { type: 'GET', dataType: 'json', success: function( resp ) { console.log( resp.people ); }, error: function( req, status, err ) { console.log( 'something went wrong', status, err ); } });في المثال السّابق، لا تشترط الوظيفة سوى الرّابط، ولكنّ إضافة كائن الإعدادات تسمح لنا بإخبار jQuery بنوع البيانات الّتي نُرسلها، وأي فعل HTTP نستخدمه (POST، GET، إلخ...)، وما نوع البيانات الّتي نتوقّع استقبالها من الخادوم، وما الّذي يجب فعله إن نجح الطّلب أو فشل... اطّلع على وثائق الوظيفة$.ajax() لقراءة كامل الخيارات الّتي يمكن إضافتها إلى كائن الإعدادات. A في AJAX تعني "لامتزامن"تجري طلبات AJAX بصورة لا متزامنة، وهذا يعني أنّ الوظيفة$.ajax تنتهي قبل انتهاء الطّلب، وقبل أن تُستدعى دّالة success، أيّ أنّ جملة return تُنفّذ قبل أن يصل جواب الطّلب. فالدّالة getSomeData في المثال التّالي ستُعيد قيمة data قبل أن تُعرّف، مما يؤدّي إلى وقوع خطأ: تحذير: نصّ برمجيّ غير سليم var getSomeData = function() { var data; $.ajax({ url: '/data/people.json', dataType: 'json', success: function(resp) { data = resp.people; } }); return data; } $( '#target' ).html( getSomeData().people[0].name );X في AJAX تعني JSON!وضع المصطلح AJAX عام 2005 ليصف طريقة لجلب البيانات من الخادوم دون الحاجة لإعادة تحميل كامل الصّفحة. في ذلك الوقت، كانت الصّيغة الأكثر شيوعًا للبيانات الّتي تُرسلها الخوادم هي XML، أمّا اليوم، فإنّ JSON هي الصّيغة الّتي تعتمدها أكثر التّطبيقات الحديثة. صيغة JSON في أساسها هي سلسلة نصّيّة (string) تُمثّل البيانات، وتبدو مُشابهة كثيرًا لكائن JavaScript عاديّ، ولكنّها لا تستطيع تمثيل كلّ أنواع البيانات الّتي يستطيع كائن JavaScript تمثيلها. فمثلًا: لا يمكن لـJSON تمثيل كائنات التّاريخ (Date) ولا الدّوال (functions). فيما يلي مثال عن نصّ JSON، لاحظ كيف تُحاط كلّ أسماء الخصائص بعلامتي اقتباس مُضاعفتين: { "people" : [ { "name" : "Ben", "url" : "http://benalman.com/", "bio" : "I create groovy websites, useful jQuery plugins, and play a mean funk bass. I'm also Director of Pluginization at @bocoup." }, { "name" : "Rebecca", "url" : "http://rmurphey.com", "bio" : "Senior JS dev at Bocoup" }, { "name" : "Jory", "url" : "http://joryburson.com", "bio" : "super-enthusiastic about open web education @bocoup. lover of media, art, and fake mustaches." } ] }تذكّر أنّ JSON هو تمثيل نصّيّ لكائن، ما يعني أنّه يجب تفسير السّلسلة النّصيّة لتحويلها إلى كائن JavaScript عاديّ قبل التّعامل معها. عندما تعمل مع جواب ورد من الخادوم بصيغة JSON، فإنّ jQuery تتولّى هذه المهمّة عنك. ولكن من المهمّ التمييز بين الكائنات الفعليّة، وطريقة تمثيلها في JSON. ملاحظة: إن أردت إنشاء سلسلة JSON نصّيّة من كائن JavaScript أو تفسير سلسلة JSON نصّيّة لتحويلها إلى كائن JavaScript دون الاستعانة بـjQuery، فإنّ المُتصفّحات الحديثة تُقدّم الوظيفتين JSON.stringify() وJSON.parse()، ويمكن إضافة هذه الخصائص إلى المُتصفّحات القديمة باستخدام المكتبة json2.js. توفّر jQuery أيضًا وظيفة jQuery.parseJSON()، الّتي توافق الوظيفة JSON.parse() في المتصفّحات، إلّا أنّها لا توفّر وظيفة تُقابل JSON.stringify(). وظائف مُختصرةإن كان كلّ ما نريده إرسال طلب بسيط، دون الاهتمام بالتّعامل مع الأخطاء الّتي قد تقع، فإنّ jQuery تُوفّر وظائف مُختصرة تسمح لنا بفعل ذلك. تستقبل كل وظيفة مُختصرة رابطًا وكائن إعدادات غير إلزاميّ، ودالّة تُستدعى عند نجاح الطّلب فقط: $.get( '/data/people.html', function( html ){ $( '#target' ).html( html ); }); $.post( '/data/save', { name: 'Rebecca' }, function( resp ) { console.log( resp ); });إرسال البيانات والعمل مع النّماذجبإمكاننا إرسال بيانات مع طلبنا بتعيين قيمة للخاصة data في كائن الإعدادات، أو تمرير كائن كمُعامل ثانٍ للوظائف المُختصرة. ستُضاف هذه البيانات إلى الرّابط في طلبات GET بصورة "جملة استعلام" (query string)، أمّا في طلبات POST فإنّها ستُرسل كبيانات نموذج. توفّر jQuery وظيفة مُفيدة .serialize() الّتي تستقبل مُدخلات نموذج وتُحوّلها إلى صيغة "جملة استعلام" (مثل field1name=field1value&field2name=field2value...): $( 'form' ).submit(function( event ) { event.preventDefault(); var form = $( this ); $.ajax({ type: 'POST', url: '/data/save', data: form.serialize(), dataType: 'json', success: function( resp ) { console.log( resp ); } }); });jqXHRتُعيد$.ajax() والوظائف المُختصرة المرافقة لها، كائن jqXHR (اختصارًا لـjQuery XML HTTP Request) والّذي يتضمّن وظائف مُفيدةً كثيرة. بإمكاننا إرسال طلب باستخدام $.ajax() ثمّ حفظ كائن jqXHR في مُتغيّر: var req = $.ajax({ url: '/data/people.json', dataType: 'json' });بإمكاننا استخدام هذا العنصر لربط الاستدعاءات الرّاجعة بالطّلب، حتّى بعد أن يكتمل الطّلب. بإمكاننا مثلًا استخدام الوظيفة .then() (ثُمَّ) لإرفاق استدعاءي نجاح الطّلب وفشله، إذ تقبل .then() دالّة أو اثنتين، تستدعى الأولى عند نجاح الطّلب، والثّانية إن فشل: var success = function( resp ) { $( '#target' ).append( '<p>people: ' + resp.people.length + '</p>' ); console.log( resp.people ); }; var err = function( req, status, err ) { $( '#target' ).append( '<p>something went wrong</p>' ); }; req.then( success, err ); req.then(function() { $( '#target' ).append( '<p>it worked</p>' ); });بإمكاننا استدعاء .then() على كائن الطّلب قدر ما نشاء، وستُنفّذ الاستدعاءات الرّاجعة بالتّرتيب ذاته الّتي أرفقت وفقه. إن لم نُرد إرفاق استدعاءي النّجاح والفشل معًا، فبإمكاننا استخدام الوظيفتين .done() و.fail() على كائن الطّلب: req.done( success ); req.fail( err );لو أردنا إرفاق استدعاء راجعٍ يُنفَّذ دومًا، بغض النّظر عن نجاح الطّلب أو فشله، فيمكننا استخدام الوظيفة .always() على كائن الطّلب: req.always(function() { $( '#target' ) .append( '<p>one way or another, it is done now</p>' ); });JSONPيستغرب كثيرٌ من المبتدئين في JavaScript فشل طلبات XHR الّتي يرسلونها إلى نطاق آخر على الإنترنت، فمثلاً: يحاول بعض المُطوّرين جلب بيانات من واجهة برمجيّة من طرف ثالث (third-party API)، ليفاجؤوا بفشل الطّلب باستمرار. السّبب وراء ذلك أنّ المُتصفّحات لا تسمح بإرسال طلبات XHR إلى نطاقات إنترنت أخرى لأسباب أمنيّة، ولكنّ بعض الواجهات البرمجيّة تُعيد البيانات بصيغة JSONP (اختصارًا لـJSON with Padding)، الّتي تسمح للمُطوّرين بجلب البيانات متجاوزين حظر المُتصفّح. الحقيقة أن JSONP ليس طلب AJAX فعليًّا، فهو لا يستخدم طلب XHR الّذي يوفّره المُتصفّح، بل يعمل بإدراج وسم <script> في صفحة الويب، الّذي يحوي بدوره البيانات المطلوبة، مُحاطة بدالّة تُعيد هذه البيانات عند استدعائها. ليس هذه التّفاصيل مهمّة الآن، لأنّ jQuery تسمح لك بطلب JSONP كما لو كان XHR باستخدام الوظيفة $.ajax() بتعيين نوع البيانات dataType إلى 'jsonp' في كائن الإعدادات. $.ajax({ url: '/data/search.jsonp', data: { q: 'a' }, dataType: 'jsonp', success: function( resp ) { $( '#target' ).html( 'Results: ' + resp.results.length ); } });ملاحظة: عادةً ما توفّر الواجهات البرمجيّة خيارًا لتعيين اسم الدّالّة الّتي تُحيط بالبيانات والّتي ستُستدعى في عنوان الرّابط. عادةً ما يكون هذا اسم مُعامل الرّابط callback، وهذا ما تتوقّعه jQuery مبدئيًّا، إلّا أن بإمكانك تغييره بتعيين قيمة للخاصة jsonp في كائن الإعدادات الّذي تُمرّره لـ $.ajax(). بإمكانك أيضًا استخدام الوظيفة المُختصرة $.getJSON() لإرسال طلب JSONP، حيث تستطيع jQuery تمييزه من خلال وجود callback=? أو ما يشبهها في الرّابط: $.getJSON( '/data/search.jsonp?q=a&callback=?', function( resp ) { $( '#target' ).html( 'Results: ' + resp.results.length ); } );مشاركة الموارد عبر الأصول (cross-origin resource sharing أو CORS اختصارًا) هي خيارٌ آخر للسّماح بالطّلبات العابرة للأصول. ولكنّها غير مدعومة في المتصفّحات القديمة، كما أنّها تحتاج تهيئة خاصّة على الخادوم وتعديل ترويسات الطّلبات في XHR لتعمل. الكائنات المُؤجّلة (Deferreds)ليست كائنات jqXHR الّتي تعرّفنا عليها إلا "نكهة" خاصّة ممّا يُعرف "بالكائنات المؤجّلة". تسمح jQuery لك بإنشاء كائنات مؤجّلة بنفسك، والّتي يمكن الاستفادة منها في تسهيل التّعامل مع الأوامر اللامتزامنة، فهي توفّر طريقة للاستجابة لعمليّة تجري بصورة غير متزامنة بعد نجاحها أو فشلها، وتجنّبك الحاجة لكتابة استدعاءات راجعة مُتداخلة فيما بينها. $.Deferredبإمكانك إنشاء كائنٍ مؤجّل باستخدام $.Deferred(). في المثال التّالي نُنفّذ دالة داخل setTimeout، ثمّ "نفي" (resolve) بوعدنا بإعادة القيمة الّتي تُرجعها الدّالة هذه. نُعيد الوعد (promise)، وهو كائن يمكن ربط الاستدعاءات الرّاجعة به، ولكنّه لا يؤثّر في نتيجة الكائن المؤجّل بحدّ ذاته. بإمكاننا "الإخلاف" (reject) بالوعد إذا وقع خطأ ما أثناء عمل الدّالّة: function doSomethingLater( fn, time ) { var dfd = $.Deferred(); setTimeout(function() { dfd.resolve( fn() ); }, time || 0); return dfd.promise(); } var promise = doSomethingLater(function() { console.log( 'This function will be called in 100ms' ); }, 100);.then() و.done() و.fail() و.always()يمكننا ربط دوالّ تتولّى حالات الخطأ والنّجاح بالوعود، تمامًا كما في كائنات jqXHR: function doSomethingLater( fn, time ) { var dfd = $.Deferred(); setTimeout(function() { dfd.resolve( fn() ); }, time || 0); return dfd.promise(); } var success = function( resp ) { $( '#target' ).html( 'it worked' ); }; var err = function( req, status, err ) { $( '#target' ).html( 'it failed' ); }; var dfd = doSomethingLater(function() { /* ... */ }, 100); dfd.then( success, err );.pipe()بإمكاننا استخدام الوظيفة .pipe() للوعود للاستجابة إلى القيمة الّتي تُوفى وذلك بتعديلها ثمّ إعادة كائن مؤجّل جديد. تعمل الوظيفة .then() بدءًا من الإصدارة 1.8 من jQuery كما تعمل الوظيفة .pipe(). function doSomethingLater( fn, time ) { var dfd = $.Deferred(); setTimeout(function() { dfd.resolve( fn() ); }, time || 0); return dfd.promise(); } var dfd = doSomethingLater(function() { return 1; }, 100); dfd .pipe(function(resp) { return resp + ' ' + resp; }) .done(function(upperCaseResp) { $( '#target' ).html( upperCaseResp ); });التّعامل مع العمليّات الّتي قد تكون لامتزامنةأحيانًا تكون لدينا وظيفة قد تعمل بصورة متزامنة أو لا متزامنة وفق ظروف مُعيّنة، فمثلًا: دالّة تقوم بعمليّة لا متزامنة أوّل مرّة تُستدعى فيها، ثمّ تُخزّن القيمة الّتي أنتجتها العمليّة لتُعيدها مباشرةً عند استدعاءها مُستقبلًا. في هذه الحالة يمكننا الاستفادة من $.when() للاستجابة لكلا الحالتين: function maybeAsync( num ) { var dfd = $.Deferred(); // أعِد وعدًا مؤجّلًا عندما num === 1 if ( num === 1 ) { setTimeout(function() { dfd.resolve( num ); }, 100); return dfd.promise(); } // أنهِ مباشرة فيما سوى ذلك، مُعيدًا num return num; } // هذا سيُجرى بصورة غير متزامنة ويعِد بإعادة 1 $.when( maybeAsync( 1 ) ).then(function( resp ) { $( '#target' ).append( '<p>' + resp + '</p>' ); }); // هذا سُيعيد 0 مُباشرةً $.when( maybeAsync( 0 ) ).then(function( resp ) { $( '#target' ).append( '<p>' + resp + '</p>' ); });بإمكانك أيضًا تمرير أكثر من معامل إلى $.when()، الأمر الّذي يسمح لك بدمج عمليّات متزامنة ولا متزامنة معًا ثمّ الحصول على نتائج تنفيذها كلّها كُمعاملات للاستدعاء الرّاجع: function maybeAsync( num ) { var dfd = $.Deferred(); // أعد وعدًا مؤجّلًا عندما num === 1 if ( num === 1 ) { setTimeout(function() { dfd.resolve( num ); }, 100); return dfd.promise(); } // أنهِ مباشرةً فيما سوى ذلك، مُعيدًا num return num; } $.when( maybeAsync( 0 ), maybeAsync( 1 ) ) .then(function( resp1, resp2 ) { var target = $( '#target' ); target.append( '<p>' + resp1 + '</p>' ); target.append( '<p>' + resp2 + '</p>' ); });عندما يكون إحدى مُعاملات $.when() كائن jqXHR، فإنّنا نحصل على مصفوفة من المُعاملات تُمرّر إلى استدعائنا الرّاجع: function maybeAsync( num ) { var dfd = $.Deferred(); // أعد وعدًا مؤجّلًا عندما num === 1 if ( num === 1 ) { setTimeout(function() { dfd.resolve( num ); }, 100); return dfd.promise(); } // أنهِ مباشرةً فيما سوى ذلك، مُعيدًا num return num; } $.when( maybeAsync( 0 ), $.get( '/data/people.json' ) ) .then(function( resp1, resp2 ) { console.log( "Both operations are done", resp1, resp2 ); });مصادر إضافيةتوثيق AJAXكائن jqXHRالكائنات المؤجّلة في jQueryترجمة (بشيء من التصرف) للجزء السادس من سلسلة jQuery Fundamentals لمؤلّفتها Rebecca Murphey.
-
- javascript
- http
-
(و 6 أكثر)
موسوم في:
-
code { font-size: 1rem !important; }تجعل jQuery إضافة التأثيرات الحركيّة على الصّفحة أمرًا سهلًا للغاية، ويمكن لهذه التأثيرات أن تعتمد الإعدادات المبدئيّة أو إعدادات يُعيّنها المُطوّر. بإمكانك أيضًا إنشاء حركاتٍ مُخصّصة من خصائص CSS عشوائيّة. اطّلع على وثائق التأثيرات لتفاصيل أكثر عن تأثيرات jQuery. ملاحظة مهمّة عن الحركات: يكون إنجاز الحركات باستخدام CSS بدل JavaScript أكثر كفاءةً في المُتصفّحات الحديثة، وخصوصًا في الأجهزة المحمولة. تفاصيل إنجاز هذه الحركات خارجةٌ عن نطاق السّلسلة، ولكن إن كنت تستهدف المُتصفّحات والأجهزة المحمولة الّتي تدعم حركات CSS، فقد ترغب بتعيين الإعداد jQuery.fx.off إلى القيمة true على الأجهزة ذات المواصفات الضّعيفة؛ فهذا من شأنه إبطال الحركات والوصول بالعنصر المطلوب تحريكه إلى حالته النّهائية مباشرةً دون تطبيق الحركة. التأثيرات المُرفقة مع jQueryتُرفَق الحركات المُستخدم بكثرة مع jQuery كوظائف يمكنك استدعاؤها على أي كائن jQuery: .show(): أظهر العناصر المُحدّدة..hide(): أخفِ العناصر المُحدّدة..fadeIn(): حرّك ظلاليّة العناصر (opacity) المُحدّدة إلى 100%..fadeOut(): حرّك ظلاليّة العناصر المُحدّدة إلى 0%..slideDown(): أظهر العناصر المُحدّدة بحركة سحب شاقوليّة..slideUp(): أخفِ العناصر المُحدّدة بحركة سحب شاقوليّة..slideToggle(): أخفِ العناصر المُحدّدة أو أظهرها بحركة سحبٍ شاقوليّة، اعتمادًا على كون العناصر المُحدّدة مخفيّة أو ظاهرة.يسهل تطبيق إحدى هذه التأثيرات على التّحديد بعد إنشائه: $( '.hidden' ).show();جرّب المثال في ساحة التّجربة (تأكد من ضغط الزرّ Run with JS في هذ المثال وكلّ الأمثلة التّالية) بإمكانك أيضًا تحديدُ مدّة للتأثيرات السّابقة، وهناك طريقتان لتحديدها، الأولى: تعيين الوقت بالميللي ثانيّة: $( '.hidden' ).show( 300 );جرّب المثال في ساحة التّجربة والثّانية استخدام إحدى السُرعات المُعرّفة مُسبقًا: $( '.hidden' ).show( 'slow' );جرّب المثال في ساحة التّجربة عُرِّفت هذه السُرعات في الكائن jQuert.fx.speeds؛ ممّا يعني أنّ بإمكانك تعديله لتغيير القيم المبدئيّة، أو إضافة سُرعات جديدة إليه: // أعد تعيين سرعةٍ مُعرّفة jQuery.fx.speeds.fast = 50; // عرّف سرعة جديدة jQuery.fx.speeds.turtle = 3000; // بما أنّنا غيّر قيمة السّرعة `fast`، فإنّ هذه الحركة ستستغرق 50 ميللي ثانية $( '.hidden' ).hide( 'fast' ); // بإمكاننا استخدام السّرعات الّتي عرفناها بأنفسنا تمامًا كتلك المُعرّفة مسبقًا $( '.other-hidden' ).show( 'turtle' );كثيرًا ما يرغب المُطوّر بفعل شيءٍ ما بعد انتهاء الحركة مباشرةً، فإن حاول فعله قبل انتهاء الحركة، فقد يسبّب تشوّه الحركة وتقطّعها، أو قد يحذف سهوًا عناصر تتحرّك في لحظة حركتها. بإمكانك تمرير استدعاء راجع (callback) إلى وظائف الحركة إن رغبت بتنفيذ أمرٍ ما بعد انتهاء التأثير، وتُشير this داخل هذا الاستدعاء إلى عنصر DOM الخام الّذي طُبقّت عليه الحركة، ومثلها ومثل دوالّ تولّي الأحداث، يمكن إحاطة this بالوظيفة $() لاستخدامها ككائن jQuery: $( 'p.old' ).fadeOut( 300, function() { $( this ).remove(); });جرّب المثال في ساحة التّجربة إن لم يحوِ التّحديد أيّة عناصر، فلن تُستدعى الدّالة. إن احتجت إلى استدعاء الدّالة بصرف النّظر عن وجود العناصر أو غيابها في التّحديد، بإمكانك إنشاء دالّة تتعامل مع الحالتين: var oldElements = $( 'p.old' ); var thingToAnimate = $( '#nonexistent' ); // هذه الدّالة ستكون الاستدعاء الرّاجع للوظيفة `show` في حال وجود عناصر نريد إظهارها، فإن لم توجد أيّة عناصر، فإنّنا نستدعيها مباشرةً بأنفسنا. var removeOldElements = function() { oldElements.remove(); }; if ( thingToAnimate.length ) { // ستُستدعى وظيفتنا بعد انتهاء الحركة thingToAnimate.show( 'slow', removeOldElements ); } else { removeOldElements(); }جرّب المثال في ساحة التّجربة تأثيرات مُخصّصة باستخدام .animate()إن لم تُلبِّ الحركات المُرفقة مع jQuery حاجتك، فبإمكانك استخدام الوظيفة .animate() لإنشاء حركات مخصّصة قائمة على خصائص CSS مُتعدّدة (إحدى الاستثناءات: الخاصّ' color الّتي لا يمكن تحريكها، ولكن تتوفّر إضافة تسمح بذلك). تقبل الوظيفة .animate() ثلاثة مُعاملات على الأكثر: كائن يُحدّد الخصائص الّتي يُراد تحريكهامدّة الحركة، مُقدّرة بالميللي ثانيةدالّة تُستدعى عند انتهاء الحركةيمكن أن تُعيّن قيمة الحركة بكتابة القيمة النّهائيّة المُراد التّحريك إليها، أو كتابة المقدار الّذي يجب تحريكه (الفرق بين موضعي الحركة): $( '.funtimes' ).animate({ left: '+=50', // زد بمقدار 50 opacity: 0.25, fontSize: '12px' }, 300, function() { // تنفّذ عند انتهاء الحركة } );جرّب المثال في ساحة التّجربة ملاحظة: إن أردت تحريك خاصّة CSS يحوي اسمها على الإشارة "-"، فعليك تحويل الاسم إلى صيغة camelCase أوّلًا إن لم تشأ إحاطة اسم الخاصّة بعلامات اقتباس، فمثلًا الخاصّة font-size تُصبح fontSize. إدارة الحركاتتُوفّر jQuery وظيفتين مُهمّتين لإدارة الحركات: .stop(): تُوقف الحركات الجارية على العناصر المُحدّدة..delay(): تُؤخِّر بدء الحركة القادمة بالمقدار الذي يُمرّر إليها (بالميللي ثانية).تُوفّر jQuery أيضًا وظائف لإدارة تعاقب الحركات وتنظيمها في "طوابير"، وإنشاء طوابير مُخصّة، وإضافة دوالّ مُخصّصة إلى هذه الطّوابير. مناقشة هذه الوظائف موضوع أكبر من هذه السّلسلة، ولكن قد ترغب بالاطّلاع عليها في وثائق jQuery. ترجمة (بشيء من التصرف) للجزء الخامس من سلسلة jQuery Fundamentals لمؤلّفتها Rebecca Murphey.
- 1 تعليق
-
- web development
- animations
- (و 5 أكثر)
-
تُسهّل jQuery الاستجابة لتفاعل المُستخدم مع صفحات الويب. معنى هذا أنّ بإمكانك تنفيذ أمرٍ ما عندما ينقر المُستخدم على جزءٍ مُعيّن من الصّفحة، أو عندما يُحرّك مؤشّر الفأرة فوق عنصر في نموذج مثلاً. في المثال التّالي، لدينا أمر يُنفَّذ عندما ينقر المُستخدم فوق أيّ عنصر قائمة في الصّفحة: $( 'li' ).click(function( event ) { console.log( 'clicked', $( this ).text() ); });جرّب المثال في ساحة التّجربة (تأكد من ضغط الزرّ Run with JS في هذ المثال وكلّ الأمثلة التّالية) يُحدّد النّصّ السّابق كلّ عناصر القائمة ويُسند إليها دالّة تتولّى حدث النّقر على كلّ عنصر، باستخدام وظيفة .click() في jQuery. توفّر jQuery وظائف مُختصرةً عديدةً لربط الأحداث، وكلّ من هذه الوظائف يوافق حدث DOM أصليًّا: td {direction:ltr; }اسم الحدث الأصليّ الوظيفة المُختصرةclick.click()keydown.keydown()keypress.keypress()keyup.keyup()mouseover.mouseover()mouseout.mouseout()mouseenter.mouseenter()mouseleave.mouseleave()scroll.scroll()focus.focus()blur.blur()resize.resize()تستخدم هذه الوظائف المُختصرة الوظيفة .on() ما وراء الكواليس، وهي وظيفة يمكنك استخدامها بنفسك لمرونةٍ أكبر. وعند استخدامك لها، فإنّك تُمرّر اسم الحدث الأصليّ كمُعامل أوّل للوظيفة، ثمّ دالّة تتولى الحدث كمعامل ثانٍ: $( 'li' ).on( 'click', function( event ) { console.log( 'clicked', $( this ).text() ); });جرّب المثال في ساحة التّجربة ما إن "تربط" مُتولّي الحدث بعنصرٍ من العناصر، فبإمكانك إثارة هذا الحدث بـjQuery أيضًا: $( 'li' ).trigger( 'click' );وإن كان للحدث الّذي تُريد إثارته وظيفةٌ مختصرة (كما ورد في الجدول السّابق)، فبإمكانك إثارة الحدث باستدعاء الوظيفة المختصرة ذاتها: $( 'li' ).click();ملاحظة: عندما تُثير حدثًا باستخدام .trigger()، فإنّك تستدعي مُتولّيات الأحداث الّتي أُنشئت في JavaScript فقط ولا تستدعي السّلوك الافتراضيّ للحدث. فمثلًا إن أثرت حدث النّقر على رابط (عنصر <a>) فلن ينتقل المتصفّح إلى الرّابط المُسند إليه في صفة href (مع أنّ بإمكانك كتابة أوامر تُنفّذ هذه الغاية). بعد أن تربط حدثًا بُعنصر، بإمكانك فكّ هذا الارتباط باستخدام الوظيفة .off() الّتي تُزيل أية مُتولّيات ارتبطت بهذا الحدث: $( 'li' ).off( 'click' );حصر الأحداث ضمن فضاء أسماءمن المزايا الّتي تُتيحها .on() إمكانيّة حصر الأحداث ضمن "فضاء أسماء". قد تتساءل عن الحاجة لذلك. افترض مثلًا أنّك تريد ربط بعض الأحداث بعنصر ما، ثمّ إزالة بعض المُتولّيات، يمكنك أن تفعل ما فعلناه في الفقرة الماضية: تحذير: أسلوب برمجيّ غير مُفضّل $( 'li' ).on( 'click', function() { console.log( 'a list item was clicked' ); }); $( 'li' ).on( 'click', function() { registerClick(); doSomethingElse(); }); $( 'li' ).off( 'click' );إلّا أنّ هذا الأسلوب سيُزيل كلّ مُتولّيات النقر على كلّ عناصر القوائم، وليس هذا ما نريد. إذا ربطت متولّيًا للأحداث محصورًا ضمن فضاء أسماء، فبإمكانك استهدافه بدقّة: $( 'li' ).on( 'click.logging', function() { console.log( 'a list item was clicked' ); }); $( 'li' ).on( 'click.analytics', function() { registerClick(); doSomethingElse(); }); $( 'li' ).off( 'click.logging' );هذا الأسلوب لا يؤثّر على الأحداث المرتبطة بالنّقر والمُتعلّقة بأغراض إحصاءات الاستخدام في الصّفحة، بينما يزيل أحداث النّقر المُتعلّقة بالسّجلّات. بإمكاننا استخدام ميزة حصر الأحداث لإثارة أحداثٍ مُحدّدة: $( 'li' ).trigger( 'click.logging' );ربط أحداث مُتعدّدة في وقت واحدميزة أخرى تُوفّرها .on()، وهي إمكانيّة ربط أحداث مُتعدّدة في وقتٍ واحد. افترض مثلًا أنّك تريد تنفيذ أمرٍ مُعيّن عندما يُمرّر المستخدم الصّفحة أو يغيّر قياس النّافذة. فهذه الوظيفة تُتيح لك تمرير الحدثين معًا مفصولين بمسافة في سلسلة نصيّة، يتبعهما الدّالّة الّتي تريد أن تتولّى الحدثين: $( 'input[type="text"]' ).on('focus blur', function() { console.log( 'The user focused or blurred the input' ); }); $( window ).on( 'resize.foo scroll.bar', function() { console.log( 'The window has been resized or scrolled!' ); });جرّب المثال في ساحة التّجربة تمرير دوالّ مُسمّاة كُمتولّيات الأحداثفي كلّ أمثلتنا السّابقة، كنّا نُمرّر دوالّ مجهولة كمُتولّيات للأحداث، ولكن يمكننا إنشاء دالّة قبل إمرارها وحفظها في مُتغيّر ثمّ تمرير هذا المُتغيّر كمُتولّي الحدث. هذا يُفيد في حال أردت استخدام الدّالة نفسها لتتولّى أحداثًا مُختلفة أو أحداثًا من عناصر مُختلفة: var handleClick = function() { console.log( 'something was clicked' ); }; $( 'li' ).on( 'click', handleClick ); $( 'h1' ).on( 'click', handleClick );كائن الحدثفي كلّ مرّة يُثار فيها حدثٌ ما، تستقبل الدّالّة المُتولّية للحدث مُعاملًا واحدًا، وهو كائن الحدث الّذي يتبع معايير مُتّفقًا عليها بين كلّ المُتصفّحات. ولهذا الكائن خصائص مُفيدة كثيرة، منها: $( document ).on( 'click', function( event ) { console.log( event.type ); // نوع الحدث، مثل: "click" console.log( event.which ); // الزرّ أو المفتاح الّذي ضغط console.log( event.target ); // العنصر الّذي انطلق منه الحدث console.log( event.pageX ); // موقع مؤشّر الفأرة على المحور X console.log( event.pageY ); // موقع مؤشّر الفأرة على المحور Y });جرّب المثال في ساحة التّجربة داخل مُتولّي الأحداثعندما تُحدِّد الدّالة المُتولّية لحدث ما، فإنّه يُتاح لهذه الدّالّة وصول إلى عنصر DOM الخام الّذي أطلق الحدث كسياق الدّالّة this، فإن أردت استخدام jQuery للتّعامل مع الحدث، فأحطه بـ$(): $( 'input' ).on( 'keydown', function( event ) { // this: العنصر الخام الّذي أطلق الحدث // event: كائن الحدث // غير لون الخلفية إلى أحمر إذا ضغط زر مسح الحرف، أو إلى أخضر فيما سوى ذلك $( this ).css( 'background', event.which === 8 ? 'red' : 'green' ); });جرّب المثال في ساحة التّجربة منع السّلوك المبدئيّقد ترغب أحيانًا في منع السّلوك المبدئيّ لحدثٍ ما، كأن ترغب في تولّي النّقر فوق رابط باستخدام AJAX، بدلًا من بدء إعادة تحميل كاملةٍ للصّفحة (وهو السّلوك المبدئيّ). يصل العديد من المُطوّرين إلى هذه الغاية بإعادة false من مُتولّي الحدث، ولكنّ لهذا تأثيرًا جانبيًّا آخر: فهو يمنع تفشّي الحدث (propagation) أيضًا (سنشرحه بعد قليل)، ممّا قد يعطي نتائج غير مرغوبة. الطّريقة السّليمة لمنع السّلوك المبدئيّ لحدث تكون باستدعاء .preventDefault() على كائن الحدث: $( 'input' ).on( 'keydown', function( event ) { // this: العنصر الخام الّذي أطلق الحدث // event: كائن الحدث // غير لون الخلفية إلى أحمر إذا ضغط زر مسح الحرف، أو إلى أخضر فيما سوى ذلك $( this ).css( 'background', event.which === 8 ? 'red' : 'green' ); });جرّب المثال في ساحة التّجربة صعود الأحداث (Event bubbling)تمعّن النّص البرمجيّ التّالي: $( 'a' ).on( 'click', function( event ) { // امنع الحدث المبدئيّ. event.preventDefault(); // سجّل ما حدث. console.log( 'I was just clicked!' ); });جرّب المثال في ساحة التّجربة يربط هذا النّص مُتولّيًا للنقر على كلّ العناصر في الصّفحة (وهو أمر يجب ألّا تفعله نهائيًّا في المواقع الحقيقيّة)، بالإضافة إلى عنصري النّافذة والمُستند. ولكن ما الّذي يحدث عندما تنقر على عنصر <a> مُدرج داخل عناصر أخرى؟ الحقيقة أنّ الحدث سيُثار على العنصر <a> وعلى كلّ العناصر الّتي تُحيط به صعودًا حتّى الوصول إلى العنصرين document وwindow. يُسمّى هذا السّلوك "صعود الأحداث"، فالحدث يُثار على العنصر الّذي نقر عليه المُستخدم، ثمّ ينتقل صاعدًا إلى كلّ العناصر الّتي تحويه وصولًا إلى أعلى DOM، إلّا إن استدعيت الوظيفة .stopPropagation() على كائن الحدث. بإمكانك فهم ذلك بسهولة أكبر في هذا المثال: <a href="#foo"><span>I am a Link!</span></a> <a href="#bar"><b><i>I am another Link!</i></b></a>$( 'a' ).on( 'click', function( event ) { event.preventDefault(); console.log( $( this ).attr( 'href' ) ); });عندما تنقر على "I am a Link!"، فإنك لا تنقر فعليًّا على العنصر <a>، بل على العنصر <span> داخله، ولكن الحدث "يصعد" نحو العنصر <a> ويُثير حدث النّقر المُرتبط به. تفويض الأحداث (Event delegation)يسمح لنا مفهوم "صعود الأحداث" بإنجاز فكرة "تفويض الأحداث"، أي ربط الأحداث بعناصر في مستوى أعلى، ثمّ اكتشاف أيّ عنصر فرعيّ ضمنها أثار الحدث. بإمكاننا مثلًا ربط حدث بقائمة غير مُرتّبة، ثمّ تحديد أيّ العناصر أثار الحدث: $( '#my-unordered-list' ).on( 'click', function( event ) { console.log( event.target ); // يُسجّل العنصر الّذي أثار الحدث });جرّب المثال في ساحة التّجربة بالطّبع ستتعقّد الأمور إذا احتوت عناصر القائمة على عناصر فرعيّة ضمنها، ولهذا تُقدّم jQuery وظيفةً مُساعدة تسمح لنا بتحديد أي العناصر نهتمّ بها، مع الاحتفاظ بالحدث مُرتبطًا بالعنصر ذي المُستوى الأعلى: $( '#my-unordered-list' ).on( 'click', 'li', function( event ) { console.log( this ); // يُسجّل عنصر القائمة الّذي أثار الحدث });جرّب المثال في ساحة التّجربة لتفويض الأحداث فائدتان اثنتان: أولاهما أنّه يسمح بربط عددٍ أقلّ من مُتولّيات الأحداث مقارنة بالعدد الّذي نحتاجه لو قرّرنا ربط الأحداث بالعناصر المُنفردة، وهذا يُحسّن الأداء في الصّفحة بصورة كبيرة. وثاني الفائدتين أنّه يسمح لنا بربط الأحداث بالآباء (كالقائمة غير المرتّبة في مثالنا)، مع اطمئنانا إلى أنّ الأحدث ستُثار حتّى وإن تغيّرت مُحتويات العنصر الأب. هذا النّصّ مثلًا، يُضيف عنصر قائمةٍ جديدًا بعد تفويض الحدث إلى العنصر الأب، والنّقر فوق هذا العنصر سيُثير الحدث كما ينبغي، دون الحاجة لربط أيّة أحداث جديدة: $( '#my-unordered-list' ).on( 'click', 'li', function( event ) { console.log( this ); // يُسجّل عنصر القائمة الّذي نُقر عليه }); $( '<li>a new list item!</li>' ).appendTo( '#my-unordered-list' );جرّب المثال في ساحة التّجربة خاتمةتعلّمنا في هذا الجزء الوسائل المُختلفة للإنصات إلى تفاعل المُستخدم مع صفحتنا، بما في ذلك كيفيّة الاستفادة من التفويض لتحسين كفاءة ربط الأحداث بالعناصر. سنتعرّف الجزء القادم كيف نُحرّك العناصر باستخدام وظائف التأثيرات الحركيّة في jQuery. ترجمة (بشيء من التصرف) للجزء الرابع من سلسلة jQuery Fundamentals لمؤلّفتها Rebecca Murphey.
-
- تطوير الويب
- javascript
-
(و 4 أكثر)
موسوم في:
-
تُسهِّل مكتبة jQuery التّعامل مع محتويات صفحة HTML بعد أن يعرضها المتصفّح، وتوفّر أدوات تُساعدك في متابعة تفاعل المستخدم مع الصّفحة، وتحريك العناصر فيها، والتّواصل مع الخواديم دون إعادة تحميل الصّفحة. سنشرح هذه الأدوات بعد قليل. لنبدأ أوّلًا بالاطّلاع على أساسيّات jQuery، وكيف يمكن استخدام وظيفتها الأساسيّة: الوصول إلى عناصر مُحدَّدة في الصفحة وفعل شيءٍ ما بها. ملاحظة: يفترض هذا الدّليل أنّك على علم بأساسيّات HTML ومُحدِّدات CSS. إن لم تكن تألف كيف يمكن استخدام مُحدّدات CSS للوصول إلى عناصر مُحدّدة في الصّفحة، فعليك أوّلًا تعلّم ذلك قبل الشروع في متابعة هذا الدّليل. ما هذا الرمز: $؟توفّر مكتبة jQuery الدّالّة jQuery، التي تتيح لك تحديد العناصر بمُحدّدات CSS. var listItems = jQuery( 'li' );إن قرأت من قبل برامج تستخدم jQuery، فلا بدّ أنّك اعتدت على هذا: var listItems = $( 'li' );كما ناقشنا في الجزء السّابق (أساسيّات JavaScript)، فكلّ الأسماء تكاد تكون سّليمة في JavaScript ما لم تبدأ برقم أو تحوي إشارة "-". ولذا فالاسم $ في المثال الأخير ليس إلّا اسمًا مُختصرًا للدّالّة jQuery، ولو اطّلعت على مصدر jQuery، لقرأت هذا قرب نهايته: // Expose jQuery to the global object window.jQuery = window.$ = jQuery;عندما تستدعي الدّالّة $() وتمرّر لها مُحدّدًا، فإنّك تحصل على كائن jQuery جديدٍ. الدّوالّ في JavaScript هي الأخرى كائنات، وهذا يعني أنّ للدّالّة $ (وjQuery بالطّبع) خصائص ووظائف أيضًا. مثلاً: يمكنك استخدام الخاصّة $.support لمعرفة ما الميزات الّتي يدعمها المتصفّح الحاليّ، كما يمكنك استخدام الوظيفة $.ajax لإرسال طلب AJAX. ملاحظة: من الآن فصاعدًا سنستخدم $ بدلًا من jQuery في هذه السّلسلة سعيًا للاختصار. لاحظ أنّه إن احتوت صفحتك أكثر من مكتبة واحدة، فقد يُستخدم الاسم $ من مكتبة أخرى، ممّا يمنع عمل jQuery، فإن واجهتك مشكلة كهذه، جرّب استخدام jQuery.noConflict قبل تحميل المكتبات الأخرى. $(document).ready()قبل استخدام jQuery لفعل أيّ شيء في الصّفحة، علينا التأكّد من كون الصّفحة قد بلغت حالةً تسمح بتعديل محتوياتها. يمكن تنفيذ ذلك في jQuery بإحاطة برنامجنا ضمن دالّة ثمّ إمرار هذه الدّالة إلى $(document).ready(). كما ترى في المثال التّالي، يمكن للدّالّة الّتي نمرّرها أن تكون مجهولة (بلا اسم): $(document).ready(function() { console.log( 'ready!' ); });هذا سيؤدّي إلى استدعاء الدّالّة الّتي مرّرناها إلى .ready() بعد أن يصبح المُستند (الصفحة) جاهزًا. ما الذي يحدث هنا؟ استخدمنا $(document) لإنشاء كائن jQuery من document في الصّفحة، ثمّ استدعينا الدّالّة .ready() على هذا الكائن، مُمرِّرين إليها الدّالّة الّتي نريد تنفيذها. بما أنّك ستجد نفسك تُعيد كتابة هذا النّصّ مرارًا، فإنّ jQuery تقدّم لك طريقةً مُختصرةً لإنجازه، إذ تقوم الدّالّة $() بمهمّة مُختلفة عند إمرار دالّة إليها بدلًا من مُحدِّد CSS، وعندها تتصرّف وكأنّها اسم بديلٌ للوظيفة $(document).ready(): $(function() { console.log( 'ready!' ); });ملاحظة: من الآن فصاعدًا، سنفترض أنّ النّصوص الّتي ترد في هذه السّلسلة مُحاطة بالعبارة $(document).ready(function() { ... });، وسنترك هذه العبارة بغرض الإيجاز. الوصول إلى العناصرأبسط ما يمكن إنجازه بـjQuery تحديد بعض العناصر ثمّ فعل شيء ما بها. إن كنت تفهم مُحدّدات CSS، فستجد أنّ الوصول إلى بعض العناصر سهل ومباشر: ليس عليك إلا إمرار المُحدِّد المناسب إلى $(). $( '#header' ); // حدّد العنصر الّذي مُعرِّفه 'header' $( 'li' ); // حدّد كل عناصر القوائم في الصّفحة $( 'ul li' ); // حدّد كل عناصر القوائم الموجودة في قوائم غير مُرتّبة $( '.person' ); // حدّد كل العناصر ذات الصّنف 'person'من المهمّ أن تفهم أنّ أيّ تحديد تُجري لن يتضمّن إلّا العناصر الموافقة للمُحدّد والتي كانت موجودة في اللّحظة الّتي أجريت فيها التّحديد، بمعنى أنّك إذا كتبت var anchors = $( 'a' ); ثمّ أضفت عنصر <a> إلى الصّفحة لاحقًا، فلن تحوي anchors العنصر الجديد. طرق أخرى لإنشاء كائن jQueryبالإضافة إلى إمرار مُحدّد بسيط إلى الدّالة $()، يمكن إنشاء كائنات jQuery بطرق أخرى: // أنشئ كائن jQuery من عنصر DOM $( document.body.children[0] ); // أنشئ كائن jQuery من قائمة بعناصر DOM $( [ window, document ] ); // أجرِ التّحديد بسياق عنصر DOM var firstBodyChild = document.body.children[0]; $( 'li', firstBodyChild ); // أجرِ التّحديد ضمن تحديد سابق var paragraph = $( 'p' ); $( 'a', paragraph );هل يحتوي التّحديد الّذي أجريته على أيّة عناصر؟ترغب أحيانًا في تنفيذ بعض الأوامر عندما يُطابق تحديدك عنصرًا أو أكثر فقط، ولكنّ الدّالّة $() تُعيدُ دومًا كائن jQuery، وهذا الكائن دائمًا صائب (truthy)، ولذا عليك فحص محتوى التّحديد لمعرفة إن كان يحوي أيّة عناصر. تحذير: نصّ برمجيّ غير سليم if ( $( '#nonexistent' ) ) { // خطأ! الأوامر هنا ستُنفَّذ دومًا! } if ( $( '#nonexistent' ).length > 0 ) { // صحيح! لن تُنفّذ الأوامر هنا إلّا إن احتوت الصّفحة على كائن مُعرّفه 'nonexistent' }بإمكاننا اختصار هذا الفحص أكثر بالاعتماد على كون 0 قيمة خاطئة (falsy): if ( $( '#nonexistent' ).length ) { // لن تُنفّذ الأوامر هنا إلّا إن وجد عنصر مُطابق }الوصول إلى عناصر مُفردة من تحديدإن كنت تحتاج التّعامل مع عنصر DOM خام من تحديد، فعليك الوصول إلى هذا العنصر من كائن jQuery. لنفترض مثلًا أنّك تريد الوصول إلى الخاصّة value لكائن <input> مباشرةً، عليك إذن التّعامل مع عنصر DOM الخام: var listItems = $( 'li' ); var rawListItem = listItems[0]; // أو listItems.get( 0 ) var html = rawListItem.innerHTML;لاحظ أنّه ليس بإمكانك استدعاء وظائف jQuery على عناصر DOM الخام، فلن يعمل المثال التّالي: تحذير: نصّ برمجيّ غير سليم var listItems = $( 'li' ); var rawListItem = listItems[0]; var html = rawListItem.html(); // Object #<HTMLInputElement> has no method 'html'إن أردت العمل مع عنصر مُفرد في تحديد وأردت استخدام وظائف jQuery معه، فعليك إنشاء كائن jQuery جديد انطلاقًا منه باستخدام الدّالّة .eq()، وإمرار دليل العنصر الّذي تريده: var listItems = $( 'li' ); var secondListItem = listItems.eq( 1 ); secondListItem.remove();جرب المثال في ساحة التّجربة (تأكد من ضغط زر Run with JS في هذا المثال وكل الأمثلة التالية) إنشاء كائنات جديدةللدّالّة $ دورٌ ثالث أخير: إنشاء عناصر جديدة. إن مرّرت قصاصة HTML إلى $()، فستُنشئ كائنًا جديدًا في الذّاكرة، بمعنى أنّ الكائن يُنشأ ولكن لا يُضاف إلى الصّفحة إلى أن تفعل ذلك بنفسك. $( '<p>' ); // يُنشئ عنصر <p> بلا محتوى $( '<p>Hello!</p>' ); // يُنشى عنصر <p> فيه نصّ $( '<p class="greet">Hello!</p>' ); // يُنشى عنصر <p> فيه نصّ وله صنفبإمكانك أيضًا إنشا عنصر جديد بإمرار كائنٍ يحوي معلومات تصف كيفيّة إنشاء العنصر: $( '<p>', { html: 'Hello!', 'class': 'greet' });لاحظ أنّه يجب أن نُحيط class بعلامتي اقتباس، لأنّ class كلمة محجوزة في JavaScript، وعدم إحاطتها بالعلامتين سيسبّب وقوع أخطاء في بعض المتصفّحات. راجع وثائق jQuery لتفاصيل الخصائص المختلفة الّتي يمكنك تضمينها في هذا الكائن. سنتعرّف كيف نُضيف العناصر في الصّفحة في الجزء القادم من السّلسلة، الّذي يشرح الانتقال عبر الصّفحة وتعديل محتوياتها. التّعامل مع التحديداتبعد إنشائك كائن jQuery يحوي تحديدًا، فإنّك غالبًا ما تريد فعل شيء ما به، وقبل ذلك عليك أن تتعرّف على أسلوب jQuery في التّعامل مع الكائنات. فحص تحديدبإمكاننا معرفة إن كان تحديد ما يُطابق معايير مُعيّنة باستخدام الوظيفة .is(). أكثر الطّرق شيوعًا لاستخدام هذه الوظيفة تزويدها بمُحدِّد كمُعاملٍ مفرد لها، وعندها تُعيد true أو false حسب مُطابقة التّحديد للمُحدِّد: $( 'li' ).eq( 0 ).is( '.special' ); // false $( 'li' ).eq( 1 ).is( '.special' ); // trueبإمكانك تمرير كائن jQuery أيضًا إلى الوظيفة .is()، أو حتّى كائن DOM خام، أو حتّى دالّة لإجراء اختبار أكثر تعقيدًا إن لزم. راجع الوثائق لمزيد من التّفاصيل. وظائف القراءة والكتابة والسّرد الضّمنيّبعد عمل التّحديد، تتوفّر وظائف عديدة يمكنك استدعاؤها. تقع هذه الوظائف عمومًا في إحدى مجموعتين: وظائف القراءة (getters) ووظائف الكتابة (setters). فالأولى تعطينا معلومات عن التّحديد، والثّانية تُغيّر التّحديد بشكل من الأشكال. وفي معظم الحالات يقتصر عمل وظائف القراءة على العنصر الأول في التّحديد (.text() إحدى استثناءات هذه القاعدة)؛ أمّا وظائف الكتابة فتشمل بعملها كلّ العناصر في التّحديد، مستخدمةً ما يُعرف بالسّرد الضّمنيّ (implicit iteration). معنى السّرد الضّمنيّ أنّ jQuery ستمرّ تلقائيًّا على كلّ العناصر في التّحديد عندما تستدعي وظيفة كتابة على هذا التّحديد، أيّ أنّه ليس عليك استدعاء وظيفة على كلّ عنصر في التّحديد بمفرده عندما تريد فعل شيء على كل العناصر في تحديد واحدٍ، بل اكتفِ باستدعاء هذه الوظيفة على التّحديد نفسه، وستفهم jQuery أنّ عليها تنفيذه على كلّ العناصر في التّحديد. لنفترض أنّنا نريد تغيير نصّ HTML في كل عناصر القوائم في الصّفحة، ولفعل ذلك علينا استخدام الوظيفة .html() الّتي تقوم بتغيير نصّ HTML في كلّ عناصر القوائم المُحدّدة. $( 'li' ).html( 'New HTML' );جرب المثال في ساحة التّجربة بإمكانك أيضًا إمرار دالّة إلى وظائف الكتابة في jQuery، وستُستخدم القيمة المُعادة منها باعتبارها القيمة الجديدة، وتستقبل هذه الدّالة مُعاملين اثنين: دليل العنصر (index) في التّحديد، والقيمة القديمة للشّيء الذي تحاول تغييره، وهذا مُفيد في حال احتجت معلومات عن حالة العنصر الحاليّة لتعيين حالته الجديدة بشكل صحيح. $( 'li' ).html(function( index, oldHtml ) { return oldHtml + '!!!' });جرب المثال في ساحة التّجربة السّرد الصّريح (Explicit Iteration)في بعض الأحيان، لن تلبّي وظائف jQuery الأصليّة المهمّة الّتي تريد إنجازها بدقّة، وسيكون عليك حينها المرور على العناصر في التّحديد بشكل صريح، وهذا ما تتيحه الوظيفة .each(). في المثال التّالي نستخدمها لإضافة وسم <b> في بداية عنصر القائمة، يحوي دليل العنصر: $( 'li' ).each(function( index, elem ) { // this: عنصر DOM الخام الحالي // index: دليل العنصر الحالي في التّحديد // elem: عنصر DOM الخام الحالي (مثل this) $( elem ).prepend( '<b>' + index + ': </b>' ); });جرب المثال ساحة التّجربة ملاحظة: ستلاحظ أنّ عنصر DOM الخام مُتاح ضمن الدّالّة الّتي نُمرّرها إلى .each() بطريقتين: الأولى عبر this والثّانية عبر elem. وكما ناقشنا في الجزء السّابق (أساسيّات JavaScript)، فإنّ this كلمة خاصّة في JavaScript تُشير إلى الكائن الّذي يُمثّل سياق الدّالّة الحاليّ. وفي jQuery فإنّ this تُشير في معظم الحالات إلى عنصر DOM الخام الّذي تعمل عليه الدّالّة الحاليّة. لذا فإنّها تُشير في حالة .each() إلى العنصر الحاليّ في مجموعة العناصر الّتي نسردها. السَّلسَلة (Chaining)من أكثر الأمور فائدةً في jQuery إمكانيّة "سَلسَلة" الوظائف معًا. هذا يعني أنّ بإمكاننا استدعاء سلسِلة من الوظائف على تحديدٍ ما دون الحاجة لإعادة التّحديد أو حفظه في متغيّر. بإمكاننا حتّى إنشاء تحديدات جديدة بناء على التّحديد السّابق، دون كسر السّلسلة: $( 'li' ) .click(function() { $( this ).addClass( 'clicked' ); }) .find( 'span' ) .attr( 'title', 'Hover over me' );الأمر ممكن لأنّ كل دالّة كتابة (setter) في jQuery تُعيد التّحديد الذي اُستدعيت لتعمل عليه. وهذا أمر عظيم الفائدة، حتّى أنّ مكتبات كثيرة اعتمدته تأثّرًا بـjQuery. ولكن يجب الحذر عند استخدامه. فالسّلاسل الطّويلة تجعل النّصّ البرمجيّة صعب القراءة والتّعديل والتنقيح لا قاعدة واضحة تفرض طولًا مناسبًا للسّلسلة، ولكن حتّى السلاسل القصيرة قد تحتاج إلى إعادة الصّياغة تسهيلًا لقراءتها. var listItems = $( 'li' ); var spans = listItems.find( 'span' ); listItems .click(function() { $( this ).addClass( 'clicked' ); }); spans.attr( 'title', 'Hover over me' );خاتمةلدينا الآن معلومات ممتازة عن تفاصيل عمل jQuery؛ وسنستعرض في الجزء القادم كيف يمكننا تطبيق هذه المعلومات لإنجاز أشياء حقيقيّة بها! مصادر إضافيةوثائق الواجهة البرمجيّة لـjQueryوثائق المُحدّداتترجمة (بشيء من التصرف) للجزء الثاني من سلسلة jQuery Fundamentals لمؤلّفتها Rebecca Murphey.
-
- 2
-
- تطوير الويب
- web development
-
(و 3 أكثر)
موسوم في:
-
بُنيت jQuery على لغة جافاسكريبت، وهي لغةٌ غنيّة وقويّة في حدّ ذاتها. يُغطّي هذا الدّرس أساسيّات لغة جافا سكريبت، وبعض الأخطاء الشّائعة الّتي يرتكبها المبتدئون بها. يُفيد هذا الدّرس القادمين الجدد إلى عالم البرمجة، ويفيد أيضًا المبرمجين بلغات أخرى الّذين لم يسبق لهم الاطّلاع على الجوانب المميّزة لـJavaScript. فيما يلي برنامج JavaScript بسيط يُضيف رسالةً إلى صفحة ويب: // أنشئ دالّة لإلقاء التّحية على شخص // وأسندها إلى المُتغيّر `greet` var greet = function( person, message ) { var greeting = 'Hello, ' + person + '!'; log( greeting + ' ' + message ); }; // استخدم الدالّة لتحيّة Jory، بإمرار اسمها ورسالة التّحيّة greet( 'Jory', 'Welcome to JavaScript' ); // استخدم الدالّة لتحيّة Rebecca، بإمرار اسمها ورسالة مختلفة greet( 'Rebecca', 'Thanks for joining us' );مُلاحظة: في المثال السابق، استخدمنا الدّالّة log. وهي دالّة مُساعِدة متوفّرة في الأمثلة في هذه السّلسلة فقط، وليست متوفّرة تلقائيًّا في JavaScript، يمكن استخدام log في محرّر النّصوص البرمجيّة في هذه السّلسلة، ولكن ستحتاج إلى استخدام console.log محلّها في النّصوص البرمجيّة خارج السّلسلة، وعندها ستُطبع نتائج النّصّ إلى طرفيّة المتصفّح الّذي تستعمله. // create a function that will greet a person, // and assign the function to the `greet` variable var greet = function( person, message ) { var greeting = 'Hello, ' + person + '!'; log( greeting + ' ' + message ); }; // use the function to greet Jory, passing in her // name and the message we want to use greet( 'Jory', 'Welcome to JavaScript' ); // use the function to greet Rebecca, passing in her // name and a different message greet( 'Rebecca', 'Thanks for joining us' );النّتيجة النّتيجة مطالعةلم نخض في أعماق لغة JavaScript بعدُ. شبكة مُطوّري موزيلّا (MDN) مصدر ممتاز (بالإنكليزيّة) لتعلّم JavaScript بتفاصيلها، وخصوصًا دليل JavaScript على الشّبكة. أكثر المواضيع أهمّيّة لك الآن: نظرة عامّة على JavaScriptالقيم والمتغيّرات والمكوّنات الحرفيّةالدّوالّعبارات الدّوالّ المُستدعاة فورًاالمصفوفاتمصادر إضافيةشبكة مُطوّري موزيلّا: JavaScriptChrome Developer Tools OverviewFixing these jQuery: A Guide to DebuggingChrome Developer Tools Cheat SheetChrome Dev Tools: 12 Tricks to Develop Quicker (فيديو)ترجمة (بشيء من التصرف) للجزء الأول من سلسلة jQuery Fundamentals لمؤلّفتها Rebecca Murphey.
-
- 2
-
- javascript
- jquery
- (و 5 أكثر)