Hello our valued visitor, We present you the best web solutions and high quality graphic designs with a lot of features. just login to your account and enjoy ...

<none>

Hello our valued visitor, We present you the best web solutions and high quality graphic designs with a lot of features. just login to your account and enjoy ...

CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
3 + 2 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.

أخبار تكنلوجيا

رقم الخبر عنوان الخبر التفاصيل
44,787 علماء ألمان يبحثون سر العلاقة بين كورونا والإصابة بالسكري الإصابة بفيروس كورونا المستجد لا تؤثر على الجهاز التنفسي فقط، بل قد يصل تأثير ذلك إلى الجسم بأكمله حسبما أظهرت عدة دراسات، كان آخرها لجامعة أولم الألمانية، والتي أظهرت كيفية تأثير الفيروس على عملية التمثيل الغذائي.
44,733 التحكم في مقاس عناصر صفحة HTML وعملية تمرير الصفحة (scrolling) عبر جافاسكربت

تضم جافاسكربت العديد من الخاصيات التي تسمح بقراءة معلومات طول عنصر ما (height)، وعرضه (width) وجوانب أخرى متعلقة بهندسته (geometry). هي معلومات نحتاجها عادة متى ما أردنا تحريك أو وضع عناصر ما على صفحة ويب باستعمال لغة جافاسكربت.

العنصر النموذجي

نستعين فيما يلي بالعنصر النموذجي التالي من أجل عرض الخاصيات:

<div id="example"> ...Text... </div> <style> #example { width: 300px; height: 200px; border: 25px solid #E8C48F; padding: 20px; overflow: auto; } </style>

See the Pen JS-p2-09-Element size and scrolling-ex1 by Hsoub (@Hsoub) on CodePen.

يملك هذا العنصر إطارًا (border)، وحاشية (padding) وإحداثيات تمرير (scrolling)؛ أي مجموعة كاملة من الخاصيات. في حين أنه لا يملك هوامش (margins) لأنها لا تُعدّ جزءًا منه وليست لها خاصيات. ويبدو العنصر على الشكل التالي:

يمكنك الاطلاع على الشيفرة ومعاينتها على هذا الرابط.

ملاحظة: شريط التمرير

تُظهر الصورة السابقة أكثر الحالات تعقيدًا، أي حين يكون للعنصر شريط تمرير،حيث تُخصِص بعض المتصفحات ( وليس كلّها) مساحةً لشريط التمرير من المساحة المخصصة للمحتوى، والمؤشَّر عليها في الصورة بعبارة 'عرض المحتوى' (content width). وبذلك يكون عرض المحتوى هنا 300 بكسل في حالة عدم وجود شريط تمرير. ولكن شَغلَ شريط التمرير لمساحةٍ عرضها 16 بكسل (يتغيّر هذا العرض من متصفح لآخر ومن جهاز لآخر) يُصغِّر من عرض المحتوى ليصبح 300-16=284 بكسل. وعليه فإنه من الأهمية بمكان أخذ ذلك في الحسبان.

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

ملاحظة: يمكن لمساحة الحاشية السفلى padding-bottom أن تحتوي نصا

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

الخاصيات الهندسية لعنصر ما

فيما يلي صورة شاملة تُظهِر الخاصيات الهندسية لعنصر ما.

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

الخاصيات offsetParent وoffsetLeft وoffsetTop

نادرًا ما نحتاج إلى هذه الخاصيات ولكنها تصف العنصر من الخارج ، لذا سنبدأ العرض بها:

تمثل الخاصية offsetParent السلف الأقرب (ancestor) للعنصر ويستعملها المتصفح لحساب الإحداثيات أثناء ترجمة الشيفرات إلى صفحات ويب، ويكون السلف الأقرب إمّا:

  1. متموضع باستعمال لغة CSS: يكون الموضع (position) إمّا مطلقا (absolute)، أو نسبي (relative)، أو ثابت (fixed) أو لاصق (sticky) أو،
  2. أحد العناصر: <td> ،<th> ،<table>، أو،
  3. العنصر <body>.

وتعطي، في المثال التالي، الخاصيتان offsetLeft وoffsetTop الإحداثيات x وy إنطلاقًا من الركن الأيسر العلوي للسلف الأقرب offsetParent. ويُعدّ العنصر <main> السلف الأقرب offsetParent للعنصر <div> المحصور داخله، وتحسب الخاصيتان offsetLeft وoffsetTop إنطلاقًا من الركن العلوي الأيسر للعنصر <main>:

<main style="position: relative" id="main"> <article> <div id="example" style="position: absolute; left: 180px; top: 180px">...</div> </article> </main> <script> alert(example.offsetParent.id); // main alert(example.offsetLeft); // 180 (*) alert(example.offsetTop); // 180 </script>

See the Pen JS-p2-09-Element size and scrolling-ex2 by Hsoub (@Hsoub) on CodePen.

(*): لاحظ أن القيمة هنا عبارة عن رقم (180) وليس سلسلة نصية “180px”.

هناك عدة حالات تحمل فيها الخاصية offsetParent القيمة null:

  1. إذا كان العنصر غير ظاهر (display:none أو غير ظاهرٍ في الصفحة)
  2. إذا كان العنصر هو <body> أو <html>،
  3. إذا كان موضع العنصر يحمل القيمة position:fixed.
offsetWidth وoffsetHeight

نباشر الآن في عرض خاصيات العنصر نفسه. تُعدّ الخاصيتان offsetHeight وoffsetWidth أبسط الخاصيات، حيث تعطيان الطول والعرض الخارجي الكلي للعنصر، بما في ذلك الأطر.

وفيما يتعلّق بالعنصر النموذجي لدينا:

  • offsetWidth = 390: العرض الكلي ويمكن حسابه بجمع العرض الداخلي المحدَّد في شيفرة CSS (أي 300 بكسل)، والحاشيتين (220 بكسل) و الإطارين (225 بكسل).
  • offsetHeight = 290: الطول الخارجي.

ملاحظة: تأخذ الخاصيات الهندسية للعناصر غير الظاهرة على الصفحة إحدى القيمتان 0 أو null

لا تُحسب الخاصيات الهندسية إلا إذا تعلّق الأمر بعناصر ظاهرة على الصفحة. إذا كان العنصر أو أيًا من أسلافه يملك الخاصية display:none أو لم يكن ظاهرًا في الصفحة، تكون قيم كل الخاصيات الهندسية الخاصة به معدومة ( أو null، إذا تحدثنا عن الخاصية offsetParent).

فعلى سبيل المثال، تحمل الخاصية offsetParent القيمة null، والخاصيتان offsetWidth وoffsetHeight القيمة 0 إذا أنشأنا عنصرًا ما ولم نُضفه إلى الصفحة أو إذا كانت الخاصية display الخاصة به أو بأسلافه محدّدة بقيمة none. ويمكن التحقّق ممّا إذا كان العنصر ظاهرًا أو مخفيًا كالآتي:

function isHidden(elem) { return !elem.offsetWidth && !elem.offsetHeight; }

لاحظ هنا أن الدالّة isHidden تعيد القيمة true أيضًا إذا كان العنصر ظاهرًا على الصفحة ولكن مقاساته منعدمة، كالعنصر <div> مثلا إذا كان فارغًا.

clientTop وclientLeft

داخل العنصر، توجد الأطر، ولقياسها لدينا الخاصيتان clientTop وclientLeft، حيث تكون قيم كلُّ من الخاصيتين في المثال السابق كالآتي:

  • clientLeft = 25، عرض الإطار الأيسر.
  • clientTop = 25، عرض الإطار العلوي.

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

ما الفرق بينهما؟ يَظهر الفرق عندما يكون اتجاه الصفحة من اليمين إلى اليسار (إذا كانت لغة المتصفح هي العربية أو العبرية)، حيث يَظهر شريط التمرير في هذه الحالة على الجهة اليسرى بدل اليمنى، وبذلك تضم الخاصية clientLeft عرض شريط التمرير أيضا. وهنا لا تكون قيمة الخاصية clientLeft هي 25، بل 25 زائد عرض شريط التمرير (25 + 16 = 41) . وفيما يلي مثال باللغة العبرية:

clientWidth وclientHeight

تعطي هاتان الخاصيتان مقاسات المساحة المحصورة بين إطاري العنصر، وتضمّان عرض المحتوى بما فيه الحاشيتين، ولكن دون عرض شريط التمرير.

فلنتحدث أولا عن الخاصية clientHeight المذكورة في الصورة أعلاه. لا وجود في الصورة لشريط تمرير أفقي، فقيمتها إذًا تساوي بالتدقيق مجموع ماهو محصور بين الإطارين، أي الطول المحدَّد في شيفرة CSS (أي 200 بكسل) زائد الحاشيتين السفلية والعلوية (2*20 بكسل) وهو ما يعطي القيمة 240 بكسل. أما بالنسبة للخاصية clientWidth، فلا يساوي عرض المحتوى 300 بكسل، بل 240 بكسل، ذلك لأن شريط التمرير يشغَل مساحةً عرضها 16 بكسل. فالنتيجة تكون 284 بكسل زائد عرض الحاشيتين اليمنى واليسرى. المجموع إذا 324 بكسل. وفي حالة عدم وجود الحاشيتين تساوي قيمة الخاصيتين clientWidth وclientHeight بالتدقيق عرض وطول مساحة المحتوى المحصورة بين الأطر وشريط التمرير إن وجد.

يمكننا إذا استعمال الخاصيتين clientWidth وclientHeight للحصول على مقاسات مساحة المحتوى إن لم يكن للعنصر حواشي.

scrollWidth وscrollHeight

تشبه هاتان الخاصيتان الخاصيتين clientWidth وclientHeight، ولكنهما تضمان أيضًا الأجزاء غير الظاهرة.

لدينا في الصورة أعلاه:

  • scrollHeight = 723، وتمثل الطول الداخلي الكلي لمساحة المحتوى بما فيه الأجزاء غير الظاهرة.
  • scrollWidth = 324، وتمثل العرض الداخلي الكلي، وبما أنه لا وجود لشريط تمرير أفقي، فهي تساوي clientWidth.

ويمكننا استخدام هاتين الخاصيتين لتوسيع العنصر إلى طوله أو عرضه الكلي كالآتي:

// توسيع محتوى العنصر إلى طوله الكلي element.style.height = `${element.scrollHeight}px`;

حيث يختفي هنا شريط التمرير، ويظهر محتوى العنصر كاملا.

scrollLeft وscrollTop

تمثل الخاصيتان scrollLeft وscrollTop عرض وطول الجزء غير الظاهر من العنصر (الذي جرى المُرور عليه). لاحظ في الصورة الموالية الخاصيتين scrollHeight وscrollTop لمقطع يضم شريط تمرير عمودي.

بعبارة أخرى، تمثل الخاصية scrollTop مقدار تمرير العنصر إنطلاقًا من أعلى الصفحة.

ملاحظة: يمكن التعديل على الخاصيتين scrollLeft وscrollTop

تُعدّ أغلب الخاصيات التي استعرضناها هنا قابلة للقراءة فقط (read-only)، ولكن الخاصيتين scrollLeft وscrollTop يمكن تغييرهما ليحرِّك المتصفح العنصر. إذا كان لدينا عنصرًا ما، وليكن elem، فإن التعليمة elem.scrollTop += 10 تُحرِّكه بمقدار 10 بكسل نحو الأسفل. ويؤدي إسناد القيمة 0 أو أيّ قيمةٍ كبيرةٍ جدًا (مثلا 1e9) للخاصية scrollTop إلى تحريك العنصر إلى أقصى أعلى أو أسفل الصفحة.

لا تأخذ طول وعرض العنصر من شيفرة CSS

تحدثنا أعلاه عن الخاصيات الهندسية لعناصر DOM التي يمكن استخدامها للحصول على الطول والعرض ولحساب المسافات. وسبق لك وأن تعرفت في فصل الأنماط التنسيقية والأصناف، أنه يمكن قراءة الطول والعرض المحدَّدّين في شيفرة CSS لعنصر ما باستعمال الدالّة getComputedStyle، فلماذا لا نقرأ عرض عنصرٍ ما باستعمال الدالّة getComputedStyle، كما في المثال التالي؟

let elem = document.body; alert( getComputedStyle(elem).width ); // إظهار عرض العنصر المحدَّدّ في شيفرة CSS

See the Pen JS-p2-09-Element size and scrolling-ex3 by Hsoub (@Hsoub) on CodePen.

لماذا عليك استعمال الخاصيات الهندسية بدلًا من هذه الدالّة؟ هناك سببان لذلك:

  • الأول، هو أن الطول والعرض المحدَّدين في شيفرة CSS يتعلّقان بخاصية أخرى وهي box-sizing، التي تحدِّد الطول والعرض في شيفرة CSS. ويمكن لأيّ تغيير في الخاصية box-sizing أن يوقِف عمل شيفرة جافاسكربت.
  • والثاني، هو أن الطول والعرض المحدَّدين في شيفرة CSS، قد يحملان القيمة "auto" كما هو الحال بالنسبة للعناصر السطرية (inline element).

مثال:

<span id="elem">Hello!</span> <script> alert( getComputedStyle(elem).width ); // auto </script>

See the Pen JS-p2-09-Element size and scrolling-ex4 by Hsoub (@Hsoub) on CodePen.

يُعدّ إسناد القيمة "auto" للخاصية width أمرًا عاديًا جدًا في لغة CSS، ولكن جافاسكربت يتطلّب مقاسا دقيقا بالبكسل حتى يستخدمه في العمليات الحسابية. لذلك لن يفيدك العمل بالعرض المحدَّد في شيفرة CSS في هذه الحالة.

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

ولكن الأمر يختلف عند استخدام التعليمة getComputedStyle(elem).width، حيث تُعيد بعض المتصفحات (مثل المتصفح Chrome) العرض الداخلي الحقيقي بعد طرح عرض شريط التمرير منه، وتُعيد بعض المتصفحات الأخرى (مثل المتصفح Firefox) العرض المحدَّد في شيفرة CSS (حيث يُهمِل هذا المتصفح شريط التمرير). ويُعدّ هذا الاختلاف من متصفح لآخر هو السبب الذي يحمِلنا على عدم استعمال الدالّة getComputedStyle بل الاعتماد على الخاصيات الهندسية بدلا منها.

وإذا كان المتصفح المثبَّت على حاسوبك يُخصِص مساحةً لشريط التمرير (وهو ما تُوفره معظم متصفحات نظام التشغيل Windows) ، يمكنك معاينة السكربت من خلال هذا الرابط

هنا، يساوي عرض العنصر المحدَّد في شيفرة CSS الذي يضم النص 300 بكسل.

تُخصِص المتصفحات Chrome وFirefox وEdge على نظام التشغيل Windows المكتبي مساحةً لشريط التمرير. ولكن المتصفح Firefox يُظهر القيمة 300 بكسل، في حين يُظهر المتصفح Chrome والمتصفح Edge قيمةً أقل، لأن المتصفح Firefox يُظهر العرض المحدَّد في شيفرة CSS، أمّا بقية المتصفحات فتُعيد العرض الحقيقي للعنصر (بعد اقتطاع عرض شريط التمرير). لاحظ أن الفرق يظهر فقط عند قراءة القيمة باستعمال الدالّة getComputedStyle(...).width في شيفرة جافاسكربت، حيث يكون الشكل الذي يَظهر على الشاشة صحيحًا.

الخلاصة

تملك العناصر الخاصيات الهندسية التالية:

  • offsetParent: تمثل السلف الأقرب للعنصر من حيث التموضع أو إحدى القيم التالية: td أو th أوtable أوbody.
  • offsetLeft وoffsetTop: تمثل إحداثيات العنصر إنطلاقًا من الحافة العلوية اليسرى للعنصر السلف offsetParent الخاص به.
  • offsetWidth وoffsetHeight: تمثل العرض والطول الخارجي لعنصر ما بما فيه الأطر.
  • clientLeft وclientTop: تمثلان المسافتين بين الركن العلوي الأيسر الخارجي والركن العلوي الأيسر الداخلي (المحتوى + الحاشية). بالنسبة لأنظمة التشغيل التي تعمل باللّغات التي تُكتَب من اليسار إلى اليمين، تساوي هاتان الخاصيتان عرض الإطارين العلوي والأيسر، أما بالنسبة لأنظمة التشغيل التي تعمل باللّغات التي تُكتَب من اليمين إلى اليسار، يكون شريط التمرير من الجهة اليسرى، وبذلك تضم الخاصية clientLeft عرض شريط التمرير أيضًا.
  • clientWidth وclientHeight: تمثلان عرض وطول المحتوى بما فيه الحواشي دون احتساب عرض شريط التمرير.
  • scrollWidth وscrollHeight: تمثلان عرض وطول المحتوى، تماما مثل ما هو الحال بالنسبة للخاصيتين clientWidth وclientHeight، ولكن مع احتساب الأجزاء غير الظاهرة من العنصر.
  • scrollLeft وscrollTop: تمثل عرض وطول الجزء العلوي للعنصر الذي جرى المُرور عليه إنطلاقًا من الركن العلوي الأيسر للعنصر.

وتُعد كلّ هذه الخاصيات قابلة للقراءة فقط (read-only) عدا الخاصيتين scrollLeft وscrollTop اللّتين يؤدي تغيير قيمتهما إلى تحريك المتصفح للعنصر.

تمارين مقاس الجزء الذي لم نمر عليه بعد

درجة الأهمية: 5 تمثل الخاصية elem.scrollTop مقاس الجزء الذي جرى المُرور عليه إنطلاقًا من الأعلى. كيف يمكننا إذًا الحصول على مقاس الجزء الذي لم نمر عليه بعد (ولنسمه scrollBottom)؟

اكتب الشيفرة التي تسمح بالحصول على مقاس الجزء (من عنصر ما) الذي لم نمر عليه بعد، وليكن هذا العنصر elem.

ملاحظة: عليك التحقّق من الشيفرة التي تكتبها، حيث ينبغي أن تعطي القيمة 0 إذا لم يكن هناك شريط تمرير، أو إذا كان العنصر متموضعًا أساسا في الأسفل.

الحل

الحل هو كالآتي:

let scrollBottom = elem.scrollHeight - elem.scrollTop - elem.clientHeight;

أو بعبارة أخرى، (الطول الكلي) ناقص (الجزء العلوي الذي جرى المُرور عليه) ناقص (الجزء الظاهر). هذا ما يسمح بالحصول على الجزء الذي لم نمر عليه بعد.

ما هو عرض شريط التمرير؟

_درجة الأهمية: _3

اكتب الشيفرة التي تُعيد عرض شريط تمرير عادي. يكون هذا العرض بالنسبة لنظام التشغيل Windows بين 12 و20 بكسل. وفي حالة ما لم يُخصِص المتصفح مساحةَ لشريط التمرير (يحدث أن يكون الشريط نصف شفاف ويتموضع فوق النص)، يكون عرضه منعدما.

ملاحظة: ينبغي أن تعمل الشيفرة على أيّ صفحة HTML كانت وأن لا تكون مرتبطة بمحتوى هذه الصفحة.

الحل

للحصول على عرض شريط التمرير، يمكن إنشاء عنصرٍ يملك شريط تمرير لكن دون أطر ولا حواشي. وبذلك يكون الفرق بين العرض الكلي للعنصر offsetWidth وعرض المساحة الداخلية للمحتوى clientWidth هو عرض شريط التمرير.

// إنشاء حاوية تملك شريط تمرير let div = document.createElement('div'); div.style.overflowY = 'scroll'; div.style.width = '50px'; div.style.height = '50px'; // إضافتها للصفحة، وإلا فسوف نحصل على مقاسات منعدمة (0 بكسل) document.body.append(div); let scrollWidth = div.offsetWidth - div.clientWidth; div.remove(); alert(scrollWidth); وضع الكرة في مركز المساحة الخضراء

درجة الأهمية: 5

تكون الصفحة مبدئيا كالآتي:

ما هي إحداثيات مركز المساحة الخضراء؟

احسبها واتبِّع ما يلي لوضع الكرة في مركز المساحة الخضراء:

  • حرِّك العنصر باستعمال لغة جافاسكربت، وليس لغة CSS.
  • ينبغي أن تعمل الشيفرة مهما كان مقاس الكرة ومهما كان مقاس المساحة الخضراء وليس فقط مع المقاسات المعطاة.

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

يمكنك الاطلاع على شيفرة التمرين من هنا

الحل

موضع الكرة هو position:absolute، وهو ما يعني أن إحداثياتها بالنسبة للأعلى ولليسار تُحسب بناءً على أقرب عنصر متموضع، أي العنصر ‎#field (لأنه يملك الموضع position:relative). يكون مبدأ الإحداثيات (0،0) متطابقا مع الركن العلوي الأيسر الداخلي للمساحة الخضراء.

تُمثِل قيم الخاصيتين clientWidthوclientHeight العرض والطول الداخليين للمساحة الخضراء، وبالتالي تكون إحداثيات المركز كالآتي: (clientWidth/2, clientHeight/2). ولكننا حين نسند هذه القيم للخاصيتين ball.style.left وball.style.top نجعل الحافة العلوية اليسرى للكرة، وليس الكرة بأكملها، تتحرك نحو المركز، فتظهر على هذا الشكل:

ball.style.left = Math.round(field.clientWidth / 2) + 'px'; ball.style.top = Math.round(field.clientHeight / 2) + 'px';

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

ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px'; ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px';

تحذير لا تعمل الشيفرة بالشكل المطلوب إذا لم يكن للكرة طول أو عرض.

<img src="ball.png" id="ball">

إذا لم يجد المتصفح طول وعرض صورةٍ ما ضمن سمات الوسوم أو في شيفرة CSS، يعتدّها منعدمة إلى غاية التحميل الكلي للصورة ، وبالتالي تكون قيمة ball.offsetWidth منعدمة إلى غاية تحميل الصورة بشكل كامل. وهذا ما ينتج عنه إحداثيات خاطئة عند استعمال الشيفرة أعلاه.

بعد عملية التحميل الأولى للصورة، يضع المتصفح الصورة في الذاكرة المؤقتة (cache)، وعند إعادة التحميل، يحصل المتصفح على المقاسات في الحين. لكن المشكل هو أن قيمة الخاصية ball.offsetWidth تكون منعدمة عند عملية التحميل الأولى. يمكن تفادي حصول ذلك بإسناد قيمة للسمتين width وheight الخاصتين بالصورة <img>:

<img src="ball.png" width="40" height="40" id="ball">

أو إضافة المقاسات في شيفرة CSS كالآتي:

#ball { width: 40px; height: 40px; }

يمكنك الاطلاع على الحل من خلال هذا الرابط

الفرق بين العرض المحدَّد في شيفرة CSS وclientWidth

درجة الأهمية 5
ما هو الفرق بين getComputedStyle(elem).width و elem.clientWidth؟ أعط ثلاث فوارق على الأقل، ويستحسن أكثر.

الحل

الفوارق هي كالآتي:

  1. تُعدّ قيمة الخاصية clientWidth قيمة عددية، بينما تكون قيمة getComputedStyle(elem).width عبارة عن سلسلة نصية تحمل في نهايتها الحرفينpx.
  2. يمكن أن تُعيد الدالّة getComputedStyle قيمًا غير عددية كالقيمة "auto" مثلا بالنسبة للعناصر السطرية.
  3. تمثل الخاصية clientWidth مقاس المساحة الداخلية للمحتوى بالإضافة إلى الحواشي، بينما لا يضم العرض المحدَّد في شيفرة CSS (مع خاصية box-sizing عادية) الحواشي (يتمثل في مقاس المساحة الداخلية للمحتوى دون الحاشيتين).
  4. إذا كان هناك شريط تمرير وخَصص المتصفح مساحةً لهذا الشريط، قد تطرح بعض المتصفحات هذه المسافة من العرض المحدَّد في شيفرة CSS (لأنه لم يَعُد متاحا ليضم محتوى ما)، ولكن بعض المتصفحات الأخرى لا تسلك السلوك نفسه. أما الخاصية clientWidth فهي دائما تمثل نفس القيمة، حيث يُطرَح عرض شريط التمرير إذا خُصصت له مساحةً ما.

ترجمة -وبتصرف- للفصل Element size and scrolling من كتاب Browser: Document, Events, Interfaces

44,674 كل ما تريد معرفته عن تطبيق اللياقة البدنية Google Fit

أصبحت تطبيقات اللياقة البدنية والأجهزة القابلة للارتداء من أبرز التقنيات التي تجد شعبية كبيرة لدى المستخدمين خلال الفترة الأخيرة، حيث تأتي بالعديد من الميزات التي تساعد على تتبع اللياقة البدنية والصحية الجسدية، ومن أشهر هذه التطبيقات تطبيق (Google Fit) الذي يركز على البساطة، مثل: المساعدة في ضبط أهدافك بناءً على نشاطك البدني بطريقة سهلة للغاية.

ما هو تطبيق Google Fit وكيف يعمل؟

هناك الكثير من تطبيقات اللياقة البدنية في السوق الآن، حيث تأتي بعضها بميزات كثيرة للغاية تتطلب دليل خاص لمعرفتها، بينما تمتلك بعضها تصميمًا بسيطا غير معقد، ومن ثم نجد أن تطبيق (Google Fit) يقع ضمن الفئة الأخيرة، فاعتمادًا على كيفية استخدامه قد تجد أن التطبيق من أفضل التطبيقات، أو أنه مجرد تطبيق بسيط يساعدك في تتبع صحتك ولياقتك البدنية.

أما عن طريقة الاستخدام، فبعد إدخال البيانات الأساسية عن نفسك في التطبيق، التي تشمل: الجنس وتاريخ الميلاد والوزن والطول، سيظهر لك مقياسي النشاط الرئيسيين للتطبيق وهما: (دقائق الحركة) Move Minutes، و (نقاط القلب) Heart Points، بعد ذلك مباشرة ستظهر لك الشاشة الرئيسية للتطبيق.

حيث تجد ثلاثة أقسام رئيسية في تطبيق (Google Fit)، هي:

الصفحة الرئيسية Home: هنا يمكنك إلقاء نظرة عامة على أنشطة اليوم الحالي – تتوفر بيانات أسبوعية وشهرية – والمقاييس الصحية، منها: دقائق الحركة، ونقاط القلب، والخطوات التي قمت بها، والسعرات الحرارية المحروقة والمسافة المقطوعة وبيانات معدل ضربات القلب والوزن والتدريبات الأخيرة والنوم ومقاطع فيديو تعليمية من يوتيوب.

المجلة Journal: يمكن الإشارة إلى هذا القسم على أنه جدول للأعمال يعرض لك قائمة بسيطة وقابلة للتمرير لجميع أنشطتك المسجلة، سواء كانت قطع مسافة قصيرة أو القيام بنشاط سباحة طويلة.

الملف الشخصي Profile: في هذا القسم يمكنك ضبط ميزتي (دقائق الحركة) و (نقاط القلب) بالإضافة إلى المعلومات الشخصية.

يحتوي تطبيق (Google Fit) أيضًا على ميزة (الوضع الداكن) Dark Mode، مما يساعدك على توفير طاقة البطارية، حيث يُطبق الوضع على مستوى التطبيق من خلال ظهور خلفية رمادية داكنة بلمسات خضراء وزرقاء في كل صفحة تنتقل إليها في التطبيق.

ما هي ميزتا دقائق الحركة ونقاط القلب في التطبيق وكيف تستخدمهما؟

يتعامل تطبيق (Google Fit) مع الأشياء بشكل مختلف عن تطبيقات اللياقة الأخرى، حيث لا يزال بإمكانك التحقق من المقاييس الشائعة، مثل: معدل ضربات القلب وعدد الخطوات، لكن في الوقت نفسه يجعل التطبيق هذه المقاييس لها معني.

حيث عملت غوغل من أجل ذلك مع جمعية القلب الأمريكية لتضمين ميزتين في التطبيق بناءً على توصيات نشاط جمعية القلب، لتعليم المستخدمين طرق قيمة في كيفية الحفاظ على صحتهم، وهي:

دقائق الحركة Move Minutes: تهدف غوغل من تضمين هذه الميزة في التطبيق تسهيل فهم نتائج التمرين من خلال كسب دقائق حركة إضافية مقابل كل نشاط بدني تمارسه، مثل: المشي والجري والسباحة واليوغا.

نقاط القلب Heart Points: من خلال هذا الميزة يمكنك ربح نقاط إضافية عند أداء أنشطة بدنية بوتيرة أعلى، حيث تربح نقطة قلب واحدة في الدقيقة عند ممارسة تمرينات رياضية متوسطة الشدة، مثل: المشي السريع، بينما تربح نقاط مضاعفة إذا كنت تشارك في أنشطة أكثر كثافة، مثل: الجري لمسافات طويلة.

ما هي التطبيقات المتوافقة؟

مع أن تطبيق (Google Fit) هو في حد ذاته تطبيق للياقة البدنية، إلا أنه يمكنك اقرأنه بأي تطبيق لياقة بدنية آخر، لماذا؟

على سبيل المثال: إذا كنت تستخدم جهاز قابل للارتداء لتتبع التدريبات الرياضية الخاصة بك من خلال تطبيق آخر مثل: Strava ، لكنك في الوقت نفسه تجد أن المقاييس التي توفرها ميزتي (دقائق الحركة) و (نقاط القلب) مفيدة، فيمكنك توصيل حسابك في تطبيق (Google Fit) بتطبيق (Strava) لنقل نقل بيانات اللياقة الخاصة بك تلقائيًا إلى (Google Fit).

وهنالك الكثير من التطبيقات الشهيرة المتوافقة مع التطبيق، يمكنك التحقق منها في متجر غوغل بلاي عبر هذا الرابط، بالإضافة إلى ذلك يمكنك جدولة التمارين المتكررة، مثل: الجري أو المشي لمسافات طويلة أو اليوغا باستخدام التطبيق من خلال تطبيق (تقويم غوغل) Google Calendar، حيث تعتبر طريقة رائعة لدمجها في جدولك الزمني إذا كنت تستخدم (تقويم جوجل) بشكل أساسي لمتابعة مهامك اليومية.

ما هي الأجهزة المتوافقة؟

هناك الكثير من أجهزة تتبع اللياقة البدنية والصحة تتوافق مع تطبيق (Google Fit)، ومعظمها تعمل بنظام التشغيل (Wear OS)، مثل: الساعات الذكية، ولكن تذكر أن البيانات التي يسجلها التطبيق ستعتمد على الساعة التي تستخدمها.

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

هل تحتاج إلى التطبيق إذا كنت تمتلك بالفعل جهازًا متوافقًا معه؟

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

ما هي أبرز سلبيات تطبيق Google Fit؟

تكمن قوة تطبيق (Google Fit) في بساطته، حيث يعتبر في الأساس وسيلة لتتبع النشاط البدني والصحة بشكل مبسط للغاية، ولكن أكبر الجوانب المفقودة في التطبيق هو دعمه لمواقع التواصل الاجتماعي والمجتمعات حيث يمكنك الوصول إلى الأشخاص ذوي التفكير المماثل لمساعدتك وتحفيزك لمواصلة ممارسة الأنشطة الرياضية.

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

44,673 كيفية نقل المرفقات من بريد Outlook إلى تطبيق Teams بسهولة

شهد تطبيق مايكروسوفت (Teams) لمكالمات الفيديو نجاحًا كبيرًا خلال عام 2020؛ بسبب جائحة فيروس كورونا (COVID-19) المستمرة حتى الآن التي أجبرت الناس على العمل والدراسة من المنزل، ونظرًا إلى ذلك عملت مايكروسوفت على تطوير التطبيق ودعمه بالعديد من الميزات القوية باستمرار.

وقد أطلقت مايكروسوفت يوم الجمعة الماضي تحديثًا رئيسيًا لتطبيق (Teams) يتضمن مجموعة من الميزات الجديدة التي ستتاح للمستخدمين خلال هذا الشهر، ومن الميزات الجديدة ميزة السحب والإفلات الجديدة لمشاركة الملفات بين Teams و Outlook. لقد استغرق ظهور الميزة ما يقرب من خمس سنوات ولكنها أخيرًا أصبحت متاحة.

إليك كيفية نقل الملفات من Outlook إلى تطبيق Teams بسهولة:

• انتقل إلى (Outlook).
• افتح رسالة البريد الإلكتروني التي تحتوي على المرفقات التي تريد نقلها.
• انقر فوق المرفق لتمييز الملف، ثم اسحبه إلى المحادثة Teams التي تريد مشاركته فيها.

أصبح الأمر سهلًا للغاية، حيث إنه قد لا يبدو تغييرًا كبيرًا بالنسبة لأولئك الذين لا يستخدمون Outlook أو Teams، ولكنه كان مصدر إزعاج كبير للمستخدمين. لأنه قبل وجود هذه الميزة الجديدة، كان عليك حفظ مرفق البريد الإلكتروني في جهازك أولًا، ثم بعد ذلك تحميله إلى تطبيق (Teams) وهذا أمر مزعج مع المرفقات الكبيرة الحجم.

تعد ميزة السحب والإفلات الجديدة لمشاركة الملفات بين Teams و Outlook مجرد واحدة من العديد من الميزات الجديدة التي جاءت بالتحديث الأخير، منها: ميزة (الإرسال التلقائي دون اتصال) Offline autosend، حيث تحدد هذه الميزة الوقت الذي يتم فيه إرسال رسائل Teams أو تحريرها عندما يكون المستخدم غير متصل، ويتم إرسال هذه الرسائل تلقائيًا بمجرد عودة الجهاز إلى الاتصال بالإنترنت بالترتيب نفسه الذي كُتبت به.

بالإضافة إلى ذلك؛ أصبح من الممكن تضمين ما يصل إلى 10000 مستخدم في فريق على مستوى المؤسسة لتعزيز التعاون والاتصالات عبر المؤسسة بأكملها، وأصبح يوجد مركز جديد للموافقات يتيح طلب الموافقات وعرضها وإدارتها في مكان واحد يسهل الوصول إليه.

الجدير بالذكر أن معدل استخدام تطبيق (Teams) ارتفع بشكل كبير خلال 2020، حيث وصل أكثر من 115 مليون مستخدم نشيط يوميًا بحلول أكتوبر 2020، كما زاد عدد المؤسسات التي تستخدمه من 50,000 في عام 2017 إلى 500,000 في عام 2020.

بالإضافة إلى ذلك؛ يستخدم التطبيق الآن أكثر من 100 مليون طالب، ويُظهر هذا الرقم زيادة قدرها 30 مليون طالب منذ بداية العام الدراسي 2020 في سبتمبر من العام الماضي، وذلك وفقًا لما قاله (Eran Megiddo) نائب رئيس مايكروسوفت لمنتجات ويندوز والتعليم.

لم تشارك مايكروسوفت علنًا عدد الطلاب الذين يستخدمون Teams حديثًا، ومع ذلك، فقد ذكرت أن “أكثر من 200 مليون طالب وأعضاء هيئة تدريس وقادة ومعلمين يستخدمون منتجات مايكروسوفت للتعليم بنشاط، ومنها: تطبيق Teams عندما أعلنت عن العديد من أجهزة الحواسيب الشخصية التي تعمل بنظام ويندوز 10 للتعليم.

44,639 الأصناف المتداخلة (Nested Classes) في جافا

تُعدّ الأصناف (classes) اللَبِنة الأساسية عالية المستوى (high-level) بالبرنامج، حيث تُستخدَم لتمثيل الأفكار والكيانات المُعقدة ضِمْن البرنامج وما يَرتبِط بها من بيانات (data) وسلوكيات (behavior). يَدفَع ذلك البعض إلى وَضْع الأصناف بمكانة خاصة، فيُحاولون تَجنُّب كتابة الأصناف الصغيرة جدًا والموجودة فقط لتجميع مجموعة من البيانات معًا، ويَرَونَها تافهة مع أنها قد تَكُون مفيدة أحيانًا أو حتى ضرورية في بعض الأحيان الآخرى. لحسن الحظ، تَرفَع الجافا هذا الحرج؛ حيث تَسمَح بكتابة تعريف صَنْف (class definition) ضِمْن تعريف صَنْف آخر، وبذلك لَمْ تَعُدْ تلك الأصناف الصغيرة جدًا موجودة بمُفردها، وإنما أَصبحَت جُزءًا من أصناف أكبر ذات هيبة. إلى جانب ذلك، هنالك العديد من الأسباب الآخرى التي قد تَدفَعك لتَضْمِين تعريف صنف (class definition) داخل صنف آخر.

الصَنْف المُتداخِل (nested class) هو ببساطة أيّ صنف يَقَع تعريفه (definition) داخل تعريف صنف آخر. وفي الواقع، يُمكِن حتى كتابة تعريف صَنْف داخل تابع (method) والذي بدوره يَقَع ضِمْن صنف. الأصناف المُتداخِلة إِما أن تَكُون مُسمَاة (named) أو مجهولة الاسم (anonymous). سنعود لاحقًا إلى الأصناف مجهولة الاسم (anonymous classes) أما بالنسبة للأصناف المُتداخِلة المُسمَاة (named nested class)، فيُمكِنها أن تَكُون ساكنة (static) أو غَيْر ساكنة (non-static) وذلك كأي شيء مُعرَّف ضِمْن صَنْف. بالمثل من الأصناف، يُمكِن للواجهات (interfaces) أيضًا أن تَقَع ضِمْن تعريفات الأصناف (class definitions) وقد تَكُون ساكنة (static) أو غَيْر ساكنة (non-static)، كما قد تَحتوِي تعريفات الواجهات (interface definitions) على أصناف مُتداخِلة ساكنة (static nested classes) أو واجهات آخرى، ولكننا لن نَتَعرَّض لذلك.

الأصناف المتداخلة (nested) الساكنة

تُعرَّف الأصناف المُتداخِلة (nested class) الساكنة كأي تعريف لصَنْف عادي باستثناء أمرين: أولهما وقوع التعريف ضِمْن صنف آخر، والآخر اِستخدَام المُبدِّل static ضِمْن التّصْريح. يُعدّ أي صنف مُتداخِل ساكن جزءًا من البنية الساكنة (static) للصَنْف الحاضن له والذي قد يَستخدِمه لإنشاء كائنات بالطريقة العادية. في المقابل، يُمكِن اِستخدَامه أيضًا خارج صَنْفه الحاضن، ولكن ينبغي أن يُشير اسمه في تلك الحالة إلى كَوْنه عضوًا بصَنْفه الحاضن، أي ينبغي أن يُستخدَم الاسم الكامل للصنف والذي يَتَكوَّن من اسم صَنْفه الحاضن متبوعًا بنقطة ثم باسمه. كأيّ مُكوِّن ساكن آخر ضِمْن صَنْف، تُعدّ الأصناف المُتداخِلة (nested class) الساكنة جزءًا من الصَنْف ذاته بنفس الطريقة التي يُعدّ بها أي مُتْغيِّر عضو (member variable) ساكن جزءًا من الصَنْف ذاته.

لنَفْترِض مثلًا وجود صَنْف، اسمه WireFrameModel يُمثِل مجموعة من الخطوط بفضاء ثلاثي الأبعاد. يَحتوِي ذلك الصنف على صَنْف مُتداخِل ساكن Line يُمثِل خطًا واحدًا. يُمكِننا الآن كتابة تعريف الصنف WireFrameModel والصنف المُتداخِل Line كالتالي:

public class WireFrameModel { . . . // أعضاء آخرى ضمن الصنف static public class Line { // ‫يمثل خطًا من النقطة (x1,y1,z1) إلى النقطة (x2,y2,z2) بفضاء // ثلاثي الأبعاد double x1, y1, z1; double x2, y2, z2; } // ‫نهاية الصنف Line . . . // أعضاء آخرى ضمن الصنف } // ‫نهاية الصنف WireFrameModel

لاحِظ أن الاسم الكامل للصَنْف المُتداخِل هو WireFrameModel.Line ويُمكِنك اِستخدَامه للتّصْريح عن مُتْغيِّر مثلًا. تستطيع تَحْديدًا إنشاء كائن من الصَنْف Line باِستخدَام البَانِي new Line()‎ من داخل الصَنْف WireFrameModel، بينما قد تَستخدِم التعبير new WireFrameModel.Line()‎ لإنشائه من خارج الصَنْف.

يستطيع أي صَنْف مُتداخِل (nested) ساكن الوصول إلى أي عُضو ساكن (static members) مُعرَّف بالصنف الحاضن له حتى وإن كان عضوًا خاصًا (private). بالمثل، يستطيع الصَنْف الحاضن لصَنْف مُتداخِل (nested) ساكن الوصول إلى أعضاء ذلك الصنف المُتداخِل (nested) حتى وإن كانت خاصة (private). يُمثِل ذلك دافعًا قويًا لاِستخدَام الأصناف المُتداخِلة (nested)؛ نظرًا لأنها تَسمَح لصَنْف معين بالوصول إلى الأعضاء الخاصة (private) المُعرَّفة بصَنْف آخر دون الحاجة إلى إِتاحة تلك الأعضاء بصورة أعم لجميع الأصناف الآخرى. وأخيرًا، يُمكِن لأي صَنْف مُتداخِل (nested) أن يَكُون خاصًا (private) وعندها يُمكِن اِستخدَامه من داخل الصَنْف الحاضن له فقط.

على الرغم من أن تعريف الصَنْف Line يَقَع ضِمْن الصَنْف WireFrameModel، ستُخزَّن النسخة المُصرَّفة (compiled) من الصَنْف Line بملف مُنفصل، اسمه هو WireFrameModel$Line.class، أي أنه عند تصريف تعريف الصَنْف (class definition) -بالأعلى-، سيُنشِئ المُصرِّف ملفًا منفصلًا لكل صَنْف.

الأصناف الداخلية (inner)

يُطلَق على الأصناف المُتداخِلة (nested) غَيْر الساكنة (non-static) اسم "الأصناف الداخلية (inner classes)". من الناحية العملية، هي لا تَختلِف كثيرًا عن الأصناف المُتداخِلة الساكنة (static)، مع أنها تُعدّ جزءًا من كائنات الأصَنْاف الحاضنة لها لا الأصناف ذاتها.

لا تُعدّ الأعضاء غَيْر الساكنة (non-static) المُعرَّفة بأي صنف جزءًا فعليًا من الصنف ذاته على الرغم من أن شيفرتها مُتضمَّنة بتعريف ذلك الصنف (class definition)، وهو ما يَنطبِق على الأصناف الداخلية (inner classes). تُحدِّد تلك الأعضاء ما ستَحتوِيه كائنات ذلك الصنف عند إنشائها، وهو ما يَنطبِق أيضًا على الأصناف الداخلية على الأقل منطقيًا أي كما لو كان كل كائن من الصنف الحاضن يَتَضمَّن نسخة خاصة من الصَنْف المُتداخِل (nested) -لا تَأخُذ ذلك بالمعنى الحرفي-. بإمكان تلك النُسخة الوصول لجميع توابع النُسخ (instance methods) ومُتْغيَّرات النُسخ (instance variables) المُعرَّفة بالكائن حتى وإن كانت خاصة (private). مثلًا، إذا كان لدينا كائنين من صَنْف يَتَضمَّن صنفًا داخليًا (inner class)، فإن نُسختي الصَنْف الداخلي بهذين الكائنين مختلفتان؛ لأنهما يُشيران إلى مُتْغيَّرات نُسخ وتوابع نُسخ تَقَع ضِمْن كائنات مختلفة. كيف تُقرِّر ما إذا كان ينبغي لصنف مُتداخِل (nested) معين أن يَكُون ساكنًا أو غَيْر ساكن؟ الأمر بسيط للغاية: إذا اِستخدَم الصنف المُتداخِل مُتْغيِّر نسخة (instance variable) أو تابع نسخة (instance method) من الصَنْف الحاضن، فلابُدّ أن يَكُون غَيْر ساكن (non-static)، أما إن لم يَستخدِم أيًا منها، فيُمكِنه أن يَكُون ساكنًا (static).

يُستخدَم الصنف الداخلي (inner class) في غالبية الأحوال ضِمْن الصَنْف الحاضن له فقط. وفي تلك الحالة، لا يَختلِف اِستخدَامه كثيرًا عن اِستخدَام أيّ صنف آخر؛ فيُمكِنك أن تُصرِّح عن مُتْغيِّر أو أن تُنشِئ كائنًًا باِستخدَام الاسم البسيط للصَنْف الداخلي، ولكن فقط ضِمْن الأجزاء غَيْر الساكنة (non-static) من الصَنْف.

أما إذا أردت اِستخدَامه من خارج صَنْفه الحاضن له، فلابُدّ من الإشارة إليه باِستخدَام اسمه الكامل والذي يُكْتَب بالصياغة .، حيث أن هو مُتْغيِّر يُشير إلى كائن مُتضمِّن لصَنْف داخلي (inner class). لاحِظ أنه لكي تَتَمكَّن من إنشاء كائن ينتمي لصَنْف داخلي، لابُدّ أولًا أن تُنشِئ كائنًا من صَنْفه الحاضن.

لنَفْترِض أننا نريد كتابة صنف يُمثِل مباراة بوكر، بحيث يَتَضمَّن صنفًا داخليًا يُمثِل لاعبي المباراة. يُمكِننا كتابة تعريف الصنف PokerGame كالتالي:

public class PokerGame { // يمثل مباراة بوكر class Player { // يمثل أحد لاعبي المباراة . . . } // ‫نهاية الصنف Player private Deck deck; // مجموعة ورق اللعب private int pot; // قيمة الرهان . . . } // ‫نهاية الصنف PokerGame

إذا كان game مُتْغيِّرًا من النوع PokerGame، فإنه سيَتَضمَّن نسخة خاصة به من الصَنْف Player على نحو منطقي. كأي صَنْف عادي آخر، تستطيع اِستخدَام التعبير new Player()‎ لإنشاء كائن جديد من الصَنْف Player بشَّرْط أن يَقَع ذلك داخل تابع نسخة (instance method) بكائن الصَنْف PokerGame. في المقابل، تستطيع اِستخدَام تعبير مثل game.new Player()‎ لإنشاء كائن من الصَنْف Player من خارج الصَنْف PokerGame، ولكنه في الواقع أمر نادر الحدوث. يستطيع أي كائن من الصَنْف Player الوصول إلى مُتْغيِّرات النُسخ deck و pot المُعرَّفة بكائن الصَنْف PokerGame. من الجهة الأخرى، يَمتلك أي كائن من الصَنْف PokerGame نسخة خاصة به من كُلًا من deck و pot و Players. فمثلًا، يَستخدِم لاعبو مباراة بوكر معينة المُتْغيِّرين deck و pot الخاصين بتلك المباراة تَحْديدًا، بينما يَستخدِم لاعبو مباراة بوكر آخرى المُتْغيِّرين deck و pot الخاصين بتلك المباراة الآخرى. ذلك بالتحديد هو تأثير كَوْن الصَنْف Player غير ساكن، وهو في الواقع الطريقة الطبيعية التي ينبغي للاعبين التصرُّف على أساسها. يُمثِل كل كائن من الصَنْف Player لاعبًا ضِمْن مباراة بوكر مُحدَّدة، أما إذا كان الصَنْف Player صَنْْفًا مُستقلًا أو صنفًا مُتداخِلًا (nested) ساكنًا، فسيُمثِل عندها الفكرة العامة للاعب البوكر بصورة مُستقلة عن أي مباراة بوكر مُحدَّدة.

الأصناف الداخلية مجهولة الاسم (anonymous)

قد تُعرِّف صنفًا داخليًا (inner class)، ثم تَجِد نفسك تَستخدِمه بسطر وحيد بالبرنامج، فهل ينبغي حقًا في تلك الحالة تعريف مثل ذلك الصنف؟ ربما، ولكن قد تُفضِّل أيضًا أن تَستخدِم صنفًا داخليًا مجهول الاسم (anonymous) في مثل هذه الحالات. يُكْتَب أي صَنْف داخلي مجهول الاسم بالصيغة التالية:

new <superclass-or-interface> ( <parameter-list> ) { <methods-and-variables> }

يُعرِّف الباني -بالأعلى- صَنْفًا جديدًا بدون أن يُعطيه أي اسم، ويُنشِئ كائنًا يَنتمِي إلى ذلك الَصَنْف عند تَشْغِيل البرنامج. في العموم، يُنشِئ هذا الشكل من العَامِل new -والذي تستطيع اِستخدَامه أينما استطعت اِستخدَام العامل new العادي- كائنًا جديدًا ينتمي لصَنْف مجهول الاسم يُشبه الصَنْف أو الواجهة ، والتي تَعمَل كأساس لصنف ذلك الكائن مع إضافة المُتْغيرِّات والتوابع المُعرَّفة، أي أنه يُنشِئ كائنًا فريدًا. الأهم من ذلك هو أنه يُنشئِه فقط بذلك الجزء من البرنامج حيث تحتاج إليه. لا يُشترَط أن يَكُون ذلك الأساس صنفًا، فيُمكِن أن يَكُون واجهة (interface) أيضًا، ولكن في تلك الحالة -أي اِستخدَام واجهة كأساس-، يَكُون الصَنْف مجهول الاسم مُلزَمًا بتّنْفيذ (implement) الواجهة، أي بتعريف (define) جميع التوابع المُصرَّح (declare) عنها ضِمْن تلك الواجهة كما أن قائمة المُعامِلات لابُدّ أن تَكُون فارغة. في المقابل، إذا اِستخدَمت صنفًا كأساس، فبإمكانك تمرير مُعامِلات (parameters) بحيث يَستقبِلها أحد البواني (constructor) المُعرَّفة بالصَنْف الأعلى .

إذا كان لدينا الواجهة Drawable المُعرَّفة كالتالي:

public interface Drawable { public void draw(GraphicsContext g); }

لنَفْترِض الآن أننا نحتاج إلى كائن من النوع Drawable لكي يَرسِم مربعًا أحمر اللون طوله يُساوِي ١٠٠ بكسل. قد تختار أن تُعرِّف صنفًا جديدًا يُنفِّذ الواجهة Drawable، ثم تُنشِئ كائنًا منه. في المقابل، قد تَستخدِم صنفًا مجهول الاسم (anonymous class) لإنشاء الكائن بتَعْليمَة واحدة فقط، كالتالي:

Drawable redSquare = new Drawable() { public void draw(GraphicsContext g) { g.setFill(Color.RED); g.fillRect(10,10,100,100); } };

يُشير redSquare -بالأعلى- إلى كائن يُنفِّذ الواجهة Drawable، ويَرسِم مربعًا أحمر اللون عند استدعاء تابعه draw()‎. لاحِظ أن الفاصلة المنقوطة بنهاية التَعْليمَة ليست جزءًا من تعريف الصنف (class definition)، وإنما هي تلك التي تُوجد بنهاية أي تَعْليمَة تّصْريح (declaration).

يَشيع تمرير الأصناف مجهولة الاسم (anonymous class) كمُعامِلات فعليّة (actual parameters) للتوابع. فمثلًا، يَستقبِل التابع التالي كائنًا من النوع Drawable، ثم يَرسمه بسياقين رُسوميين (graphics contexts) مختلفين:

void drawTwice( GraphicsContext g1, GraphicsContext g2, Drawable figure ) { figure.draw(g1); figure.draw(g2); }

عندما تَستدعِي ذلك التابع، يُمكِنك أن تُمرِّر صنفًا داخليًا (inner class) مجهول الاسم (anonymous) كقيمة للمُعامِل (parameter) الثالث، كالتالي:

drawTwice( firstG, secondG, new Drawable() { void draw(GraphicsContext g) { g.fillOval(10,10,100,100); } } );

يُنشِئ المُصرِّف ملف صنف ‎.class منفصل لكل صنف مُتداخِل (nested) مجهول الاسم ضِمْن الصَنْف. إذا كان اسم الصنف الأساسي هو MainClass، فإن أسماء ملفات الأصناف المتداخلة مجهولة الاسم تكون كالتالي: MainClass$1.class و MainClass$2.class و MainClass$3.class وهكذا.

تُمثِل الواجهة Drawable المُعرَّفة بالأعلى واجهة نوع دالة (functional interface)، وبالتالي نستطيع في هذه الحالة اِستخدَام تعبيرات لامدا (lambda expressions) بدلًا من الأصناف مجهولة الاسم. يُمكِن إعادة كتابة المثال الأخير كالتالي:

drawTwice( firstG, secondG, g -> g.fillOval(10,10,100,100) );

كما يُمكِن إعادة تعريف المُتْغيِّر redSquare كالتالي:

Drawable redSquare = g -> { g.setFill(Color.RED); g.fillRect(10,10,100,100); };

لا يُنشِئ المُصرِّف أي ملفات جديدة لتعبيرات لامدا (lambda expressions) وهو ما يُحسَب لها، ولكن لاحِظ أنها تُستخدَم فقط مع واجهات نوع الدالة (functional interfaces). في المقابل، تستطيع اِستخدَام الأصناف مجهولة الاسم (anonymous classes) مع أي واجهة أو صنف. قبل الإصدار ٨ من الجافا، كانت الأصناف مجهولة الاسم تُستخدَم لمعالجة الأحداث (events) ببرامج واجهات المُستخدِم الرُسومية (GUI)، ولكن مع الإصدار ٨ ومنصة جافا إف إكس (JavaFX)، أصبحت تعبيرات لامدا مُستخدَمة بكثرة ضِمْن هذا السياق بدلًا من الأصناف مجهولة الاسم.

الأصناف المحلية (local) وتعبيرات لامدا

تستطيع أن تُعرِّف صنفًا ضِمْن تعريف برنامج فرعي (subroutine definition). تُعرَف تلك النوعية من الأصناف باسم "الأصناف المحلية (local classes)". يَقْتصِر اِستخدَام أي صنف محلي على البرنامج الفرعي (subroutine) المُعرَّف بداخله، ولكن يُمكِن لكائنات ذلك الصنف أن تُستخدَم خارج البرنامج الفرعي، فمثلًا، قد يُعاد كائن ينتمي لصنف محلي من برنامج فرعي أو قد يُمرَّر كمُعامِل (parameter) لبرنامج فرعي آخر. هذا ممكن لأننا نستطيع عمومًا إِسْناد كائن ينتمي لصنف معين B إلى مُتْغيِّر من النوع A طالما كان B صنفًا فرعيًا من A في حالة كان A عبارة عن صنف، أما إذا كان واجهة فطالما كان B يُنفِّذ A. لنَفْترِض مثلًا أن لدينا برنامج فرعي (subroutine) يَستقبِل مُعامِلًا من النوع Drawable -الواجهة المُعرَّفة بالأعلى-، فنستطيع ببساطة أن نُمرِّر إليه أي كائن طالما كان يُنفِّذ (implements) تلك الواجهة، وعليه، قد ينتمي الكائن لصنف محلي (local class) يُنفِّذ تلك الواجهة.

بمثال سابق بهذا القسم، مَرَّرنا كائنًا من النوع Drawable إلى التابع drawTwice()‎، والذي يَستقبِل مُعاملًا (parameter) من النوع Drawable. في ذلك المثال، كان الصنف المُمرَّر صنفًا داخليًا مجهول الاسم (anonymous inner class). لاحِظ أن الأصناف المحلية عادة ما تَكُون مجهولة الاسم (anonymous) والعكس، لكنه مع ذلك ليس أمرًا ضروريًا. فمثلًا، قد تَستخدِم صنفًا مجهول الاسم لتعريف القيمة المبدئية لمُتْغيِّر عام (global variable). في تلك الحالة، لا يَقع الصَنْف ضِمْن أي برنامج فرعي (subroutine)، وبالتالي لا يُعدّ محليًا (local).

يُمكِن لأي صَنْف محلي (local class) أن يُشير إلى أي مُتْغيِّر محلي (local variables) ضِمْن برنامجه الفرعي أو إلى أي من مُعامِلاته (parameters) المُمرَّرة، ولكن وفقًا لعدة شروط: لابُدّ أن يُصرَّح عن ذلك المُتْغيِّر المحلي (local variable) أو المُعامِل (parameter) باستخدام المُبدِّل final أو أن يَكُون على الأقل "نهائيًا على نحو فعال". يُعدّ المُعامِل (parameter) "نهائيًا على نحو فعال" إذا لم تَتغيّر قيمته داخل البرنامج الفرعي (subroutine) بما في ذلك أصنافه المحلية (local class) التي أشارت إلى ذلك المُعامِل (parameter) بينما يُعدّ المُتْغيِّر المحلي (local variable) "نهائيًا على نحو فعال" إذا لم تَتغيّر قيمته أبدًا بعد التهيئة المبدئية (initialize). في المقابل، تستطيع الأصناف المحلية الإشارة إلى أي مُتْغيِّر عام (global variables) ودون أي شروط.

تَنطبِق نفس شروط اِستخدَام المُتْغيِّرات المحلية (local variables) على تعبيرات لامدا (lambda expressions)، والتي هي كثيرة الشبه بالأصناف مجهولة الاسم (anonymous classes). تَستعرِض الشيفرة التالية برنامجًا فرعيًا يَستخدِم كُلًا من واجهة برمجة التطبيقات stream والواجهة Runnable -ناقشناها بالقسم ٤.٥-؛ لطباعة الأعداد من ١ إلى ١٠ بترتيب غير مُحدَّد يَعتمِد على مجرى مُتوازي (parallel stream):

static void print1to10() { ArrayList<Runnable> printers = new ArrayList<>(); for (int i = 1; i <= 10; i++) { int x = i; printers.add( () -> System.out.println(x) ); } printers.parallelStream().forEach( r -> r.run() ); }

لمّا كان المُتْغيِّر المحلي x "نهائيًا على نحو فعال"، تَمكَّنا من اِستخدَامه ضِمْن تعبير لامدا (lambda expression). في المقابل، لا يُمكِن اِستخدَام المُتْغيِّر i ضِمْن تعبير لامدا؛ لأنه غَيْر نهائي حيث تَتغيّر قيمته عند تّنْفيذ التعبير i++‎.

ترجمة -بتصرّف- للقسم Section 8: Nested Classes من فصل Chapter 5: Programming in the Large II: Objects and Classes من كتاب Introduction to Programming Using Java.

44,638 كيفية تنسيق عناصر صفحة HTML عبر جافاسكربت

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

  1. إنشاء صنف في ملف CSS وإضافته للعنصر على الشكل التالي: <div class="..."‎>
  2. كتابة خاصيات السمة style مباشرة بين تسلسلات التهريب الخاصة بالعنصر على الشكل التالي: <div style="..."‎>

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

فعلى سبيل المثال، يمكن استعمال الخاصية style إذا كان عليك حساب إحداثيات عنصر ما ديناميكيًا وتحديد قيمها باستعمال جافاسكربت كالآتي:

let top = /* عمليات حسابية معقّدة */; let left = /* عمليات حسابية معقّدة */; elem.style.left = left; // e.g '123px', تُحسب أثناء التنفيذ elem.style.top = top; // e.g '456px'

وفي حالات أخرى مثل تلوين النص بالأحمر، أو إضافة صورة للخلفية، يُستحسن وصف التنسيق باستعمال CSS ثم إضافة الصنف للعنصر (يمكن عمل ذلك باستعمال جافاسكربت)، حيث يمنحك ذلك أكثر مرونة وسهولة في البرمجة.

اسم الصنف (className) وقائمة الأصناف (classList)

يُعدّ تعديل الصنف أكثر عمليةٍ نُصادفها في السكربتات. وكانت جافاسكربت فيما مضى تتّسمُ بالمحدودية حين يتعلّق الأمر بالكلمة المحجوزة "class"، حيث لم تكن تسمح بأن تحمل خاصيةٌ من خواص الكائن (object) اسم "class" كالآتي : elem.class. حينها جاء التفكير في استحداث خاصية مشابهة تسمى "className" تُطبّق على الأصناف. حيث يمثِّل elem.className اسم السمة "class" كما هو مبين في المثال التالي:

<body class="main page"> <script> alert(document.body.className); // main page </script> </body>

See the Pen JS-p2-08-Styles and classes-ex1 by Hsoub (@Hsoub) on CodePen.

في حالة ما إذا أسندنا قيمةً معينةً للخاصية elem.className، تعوِّض هذه القيمة مُجمل سلسلة الأصناف. هذا ما نحتاج إليه أحيانا، ولكننا نحتاج في غالبية الحالات إلى إضافة أو حذف صنفٍ واحدٍ فقط. ولهذا وُجدت خاصية أخرى؛ إنها خاصية elem.classList. تُعدّ هذه الخاصية كائنًا خاصًا بحد ذاته له دوالّه الخاصة (methods) لإضافة صنف ما (add) أو حذفه (remove) أو إما إضافته إن لم يكن موجودًا أو حذفه إن وُجد (toggle).كما هو مبين في المثال التالي:

<body class="main page"> <script> // إضافة صنف document.body.classList.add('article'); alert(document.body.className); // main page article </script> </body>

See the Pen JS-p2-08-Styles and classes-ex2 by Hsoub (@Hsoub) on CodePen.

وبذلك يمكننا إجراء عمليات على مجمل سلسلة الأصناف دفعة واحدة باستعمال className أو على الأصناف، كلٌّ على حدى، باستعمال classList. اختيارنا لهذا أو ذاك مرتبط بما نحتاج القيام به.

الدوالّ الخاصة بـالخاصية classList هي: *elem.classList.add/remove("class")‎: إضافة الصنف المذكور كوسيط للدلّة/حذف الصنف المذكور كوسيط للدلّة. *elem.classList.toggle("classe")‎: إضافة الصنف المذكور كوسيط للدلّة إن لم يكن موجودًا أو حذفه إن وُجد. *elem.classList.contains("class")‎: البحث عن الصنف المذكور كوسيط للدلّة، والنتيجة تكون صحيح أو خطأ (true/false).

وتقبل الخاصية classList الإدماج داخل حلقة التكرار for....of لإظهار قائمة الأصناف كما في المثال التالي:

<body class="main page"> <script> for (let name of document.body.classList) { alert(name); // main, and then page } </script> </body>

See the Pen JS-p2-08-Styles and classes-ex3 by Hsoub (@Hsoub) on CodePen.

تنسيق العنصر باستعمال الخاصية style

تُعدّ الخاصية elem.style كائنًا يحمل محتوى السمة style. ويؤدي إسناد القيمة "100px" للخاصية elem.style.width على الشكل التالي: elem.style.width="100px" إلى النتيجة نفسها لو كانت السمة style تحمل السلسلة النصية "width:100px".

إذا كان اسم السمة يتكون من عدة كلمات، يُشكَّل اسم الخاصية بجعل الحرف الأول من كل كلمة حرفا كبيرًا ماعدا الكلمة الأولى كما في المثال التالي:

background-color => elem.style.backgroundColor z-index => elem.style.zIndex border-left-width => elem.style.borderLeftWidth

ملاحظة: الخاصيات التي تبدأ ببادئة

تتبع الخاصيات التي تبدأ ببادئة تُحدّد المتصفح نفس القاعدة، كالخاصيتين -moz-border-radius و-webkit-border-radius، حيث تُترجَم الشرطة إلى حرفٍ كبيرٍ كالآتي:

button.style.MozBorderRadius = '5px'; button.style.WebkitBorderRadius = '5px'; تغيير قيمة خاصية التنسيق style

يحدث أن تَرغب في إسناد قيمةٍ للخاصية style ثم حذفها لاحقا. يمكننا على سبيل المثال إسناد القيمة "none" للخاصية elem.style.display كالأتي elem.style.display = "none" ثم حذفها وكأننا لم نحدّد لها قيمةً من قبل. هنا، ينبغي إسناد سلسلة نصية فارغة للخاصية elem.style.display كالآتي elem.style.display = ""‎ بدلا من حذفها (delete).

//عند تنفيذ هذا السكربت يختفي العنصر <body> ثمّ يُعاود الظهور document.body.style.display = "none"; // يختفي setTimeout(() => document.body.style.display = "", 1000); // يُعاود الظهور

إذا أسندنا سلسلة نصية فارغة للخاصية style.display، يُطبِّق المتصفح أصناف CSS والأنماط التنسيقية المتضمَّنة بداخلها بطريقة عاديةٍ جدًا وكأن الخاصية style.display غير موجودة تماما.

ملاحظة: التعديل على خاصيات الخاصية style جملةً واحدةً باستعمال الخاصية style.cssStyle

تُستعمل عادة الخاصية *.style للتعديل على قيم خاصيات التنسيق، كلٌ على حدى، ولا يمكننا التعديل عليها دفعة واحدة كالآتي: div.style="color:red; width:100px"‎، لأن div.style هو كائنٌ لا يمكن التعديل عليه (ِread-only) بهذه الطريقة.

يمكن تغيير التنسيق كاملا دفعة واحدة بإسناد سلسلة نصية (تحمل وصف التنسيق) للخاصية style.cssStyle كما في المثال التالي:

<div id="div">Button</div> <script> // يمكننا استعمال رايات تنسيقية خاصة مثل الراية “important” div.style.cssText=`color: red !important; background-color: yellow; width: 100px; text-align: center; `; alert(div.style.cssText); </script>

See the Pen JS-p2-08-Styles and classes-ex4 by Hsoub (@Hsoub) on CodePen.

غير أنه من النادر استعمال هذه الخاصية كونها تحذف الأنماط التنسيقية السابقة وتستبدلها بالقيم الجديدة، أي أنها قد تحذف أشياء مازلنا بحاجتها. فيما يمكن أن تُستخدم لتنسيق العناصر الجديدة، فلن يؤدي إسناد القيم بهذه الطريقة إلى أيّ عملية حذف (بما أن العناصر الجديدة لا تملك تنسيقات بعد). ويمكننا عمل ذلك أيضا باستعمال الدالّة div.setAttribute('style', 'color: red...')‎.

الوحدات

لا تنس إضافة الوحدات للقيم في شيفرة CSS، فلا يصِحُّ إسناد القيمة "10" للخاصية elem.style.top بل القيمة 10px هي الأصح، وإلا فلن يعمل السكربت بالشكل المطلوب.

<body> <script> // لا يعمل document.body.style.margin = 20; alert(document.body.style.margin); // '' (سلسلة نصية فارغة، إهمال عملية الإسناد) // يعمل بعد إضافة الوحدة document.body.style.margin = '20px'; alert(document.body.style.margin); // 20px alert(document.body.style.marginTop); // 20px alert(document.body.style.marginLeft); // 20px </script> </body>

See the Pen JS-p2-08-Styles and classes-ex5 by Hsoub (@Hsoub) on CodePen.

لاحظ في السطرين الأخيرين أن المتصفح يفكّك الخاصية style.margin إلى خاصيتين وهما: style.marginLeft وstyle.marginTop.

الأنماط المحسوبة باستعمال الدالة getComputedStyle

يُعدّ التعديل على الأنماط عمليةً سهلةً ولكن كيف تُقرأ الأنماط؟ نريد مثلا معرفة مقاس، هوامش ولون عنصرٍ ما، كيف نتحصل عليها؟

تعمل الخاصية style على تعديل قيمة السمة style فقط دون الوصول إلى الأنماط الموصوفة في الأوراق التنسيقية المتتالية CSS. وبالتالي لا يمكننا قراءة أيّ قيمٍ من أصناف CSS باستعمال الخاصية elem.style. فعلى سبيل المثال لا يمكن أن تصل الخاصية style في هذا المثال إلى الهامش.

<head> <style> body { color: red; margin: 5px } </style> </head> <body> The red text <script> alert(document.body.style.color); // فارغة alert(document.body.style.marginTop); // فارغة </script> </body>

See the Pen JS-p2-08-Styles and classes-ex6 by Hsoub (@Hsoub) on CodePen.

ماذا لو أردنا على سبيل المثال إضافة 20px للهامش؟ سيكون علينا أولا الوصول إلى القيمة الحالية له حتى يتسنى لنا تعديلها. وهنا لدينا طريقة أخرى للحصول على ذلك وتكون باستعمال الدالة getComputedStyle وبنيتها كالآتي:

getComputedStyle(element, [pseudo])

حيث يمثّل العنصر element العنصر الذي سنحسب قيمه ويمثل pseudo العنصر الزائف، مثلا before::. إذا كانت قيمة pseudo عبارة عن سلسلة نصية فارغة أو غير موجودة أصلا فهذا يعني أننا نقصد العنصر نفسه. وتكون مخرجات الدالّة (output) عبارة عن كائن يحوي أنماط تنسيقية مثله مثل elem.style ولكن يأخذ في الحسبان هذه المرة كلّ الأصناف الموجودة في ملف CSS. وفيما يلي مثال على ذلك:

<head> <style> body { color: red; margin: 5px } </style> </head> <body> <script> let computedStyle = getComputedStyle(document.body); // يمكننا الآن قراءة اللون والهامش alert( computedStyle.marginTop ); // 5px alert( computedStyle.color ); // rgb(255, 0, 0) </script> </body>

See the Pen JS-p2-08-Styles and classes-ex7 by Hsoub (@Hsoub) on CodePen.

ملاحظة: القيم المحسوبة والقيم النهائية (المُحدَّدة)

هناك مفهومان في لغة CSS هما:

  1. قيمة تنسيقية محسوبة وهي القيمة المتحصّل عليها بعد تطبيق مجمل القواعد التنسيقية وقواعد الوراثة المُتضمَّنة في ملف CSS. قد تكون على شكل height:1em أو font-size:125% .
  2. قيمة تنسيقية نهائية (مُحدَّدة) وهي القيمة التي يقع عليها الاختيار في آخر المطاف وتُطبَّق على العنصر. القيمتان 1em و125% هما قيمتان نسبيتان. يأخذ المتصفح كافة القيم المحسوبة ويجعل كافة الوحدات مطلقة كما في المثال التالي: height:20px ،font-size:16px. ويمكن للقيم النهائية الخاصة بالخاصيات الهندسية أن تكون عشرية مثل: width:50.5px.

لقد اُستحدثت الدالّة getComputedStyle أساسا للحصول على قيم محسوبة ولكن تبيّن فيما بعد أن القيم النهائية (المُحدَّدة) أحسن، فتغيرت المعايير، وأصبحت الدالّة getComputedStyle تُخرِج القيم النهائية للخاصية والتي تكون غالبا بالبكسل px بالنسبة للخاصيات الهندسية.

ملاحظة: تتطلب الدالة getComputedStyle ذكر الاسم الكامل للخاصية

علينا البحث على الدوام عن الخاصية التي نحتاج إليها بدقة مثل: padingLeft، أو marginTop أو borderTopWidth وإلا فلن نتمكن من ضمان صحة النتيجة المتحصّل عليها. فمع وُجود، على سبيل المثال، الخاصيتين padingLeft/padingTop، على ماذا سوف نحصل عند تنفيذ الدالّة getComputedStyle(elem, pading)‎؟

لن نحصل على شيئ؟ أو ربما سنحصل على قيمة "مستوحاة" من قيم معرّفة مسبقا للحاشية (pading)؟ في الحقيقة لا يوجد أيّ معايير تتحدث عن هذا الموضوع.

وهناك بعض التناقضات الأخرى، حيث تُظهِر بعض المتصفحات (Chrome مثلا) في مثال الموالي القيمة 10px، ولا تُظهِرها متصفحات أخرى (كالمتصفح Firefox).

مثال:

<style> body { margin: 10px; } </style> <script> let style = getComputedStyle(document.body); alert(style.margin); // نحصل على سلسلة فارغة عند استعمال المتصفح Firefox </script>

See the Pen JS-p2-08-Styles and classes-ex8 by Hsoub (@Hsoub) on CodePen.

ملاحظة: الأنماط التي تُطبَّق على الروابط ‎:visited تكون مخفية

يمكن تلوين الروابط التي سبق وأن زيرت باستخدام الصنف الزائف ‎:visited في ملف CSS. لكن الدالّة getComputedStyle لا يمكنها الوصول إلى هذا اللون، لأن ذلك يمكِّن أيّ صفحة كانت من إنشاء الرابط على الصفحة والإطلاع على الأنماط وبالتالي معرفة ما إذا كان المستخدم قد زار الرابط من قبل.

لا يمكن للغة جافاسكربت الإطلاع على الأنماط المعرّفة باستخدام الصنف الزائف ‎:visited، كما تمنع لغة CSS تطبيق تنسيقاتِ تغيير الشكل والأبعاد (geometry-changing styles) ضمن الصنف الزائف ‎:visited وذلك لغلق الطريق أمام أيّ صفحةٍ مشبوهةٍ تسعى لمعرفة ما إذا زار المستخدم الرابط أم لا، وبالتالي التعدي على خصوصيته.

الخلاصة

هناك خاصيتان تُستخدمان للعمل على الأصناف وهما:

  • className: وهي سلسلة نصية تُستخدم للعمل على كافة الأصناف دفعةً واحدةً.
  • classList: هي عبارة عن كائن له دوالّه الخاصة (add/delete/toggle/contains) وتستخدم للعمل على الأصناف، كلُ على حدى.

ولتغيير التنسيق لدينا:

  • الخاصية style؛ وهي عبارة عن كائن تُشكَّل خواصه بجعل الحرف الأول من كل كلمة حرفا كبيرًا ما عدا الكلمة الأولى. تُعدّ قراءته والتعديل عليه تماما كالتعديل على خاصيات السمة style، كلٌ على حدى. وللاطلاع على كيفية إضافة الراية important وغيرها، يمكنك زيارة موقع MDN حيث تجد قائمة من الدوالّ التي تُستخدم لذلك.
  • الخاصية style.cssText: هي الخاصية التي تقابِل السمة "style" في مجملها، أي السلسلة النصية التي تحمل كافة الأنماط التنسيقية دفعةً واحدةً.

ولقراءة الأنماط التنسيقية النهائية (التي تأخذ في الحسبان كافة الأصناف بعد تطبيق تنسيقات CSS وحساب القيم النهائية) وُجدَت الدالة getComputedStyle(elem, [pseudo])‎ والتي تخرِج/تعيد كائنا يحمل التنسيقات وهو قابلٌ للقراءة فقط.

تمرين إنشاء إشعار

درجة الأهمية: 5

اكتب شيفرة الدالّة showNotification(options)‎ التي تُنشِئ إشعارًا كالآتي:

بالمحتوى الذي يُمرَّر لها كوسيط، حيث يختفي الإشعار بعد ثانية ونصف من إظهاره. وتُوفّر الخيارات التالية: // أظهِر عنصرًا يحمل النص "Hello" بالقرب من الركن العلوي الأيمن للنافذة showNotification({ top: 10, // عشرة بكسل بدءًا من أعلى النافذة والذي فاصلته 0 right: 10, // عشرة بكسل بدءًا من الحافة اليمنى للنافذة والتي ترتيبتها 0 html: "Hello!", // شيفرة HTML الخاصة بالإشعار className: "welcome" // صنف إضافي للحاوية ‘div’ (اختياري) });

استعمل تنسيقات CSS لتحديد موضع إظهار العنصر حسب الإحداثيات المعطاة بافتراض أن الصفحة تحتوي مسبقا على الأنماط التنسيقية الضرورية.

الحل

يمكنك الإطلاع على الحل من هنا

ترجمة -وبتصرف- للفصل Styles and classes من كتاب Browser: Document, Events, Interfaces

44,637 الواجهات (Interfaces) في جافا

تَسمَح بعض اللغات البرمجية كائنية التوجه (object-oriented programming)، مثل C++‎، للصَنْف بأن يَتمدَّد (extend) من أكثر من مُجرّد صَنْف أعلى (superclass) واحد، وهو ما يُعرَف باسم الوراثة المُتعدّدة (multiple inheritance). بالرسم التالي مثلًا، يَتمدَّد الصنف E من صنفين أعليين (superclasses) مباشرةً، هما الصنفين A و B، بينما يَتمدَّد الصنف F من ثلاثة أصناف أعلين (superclasses) مباشرةً:

أراد مُصمِّمي الجافا أن يجعلوا اللغة بسيطة على نحو معقول، ولمّا وجدوا أن مزايا الوراثة المُتعدّدة (multiple inheritance) لا تَستحِقّ ما يُقابِلها من تعقيد مُتزايد، فإنهم لم يُدعِّموها باللغة. ومع هذا، تُوفِّر الجافا ما يُعرَف باسم الواجهات (interfaces) والتي يُمكِن اِستخدَامها لتحقيق الكثير من أهداف الوراثة المُتعدّدة. لقد تَعرَّضنا -بالقسم ٤.٥- لواجهات نوع الدالة (functional interfaces) وعلاقتها بتعبيرات لامدا (lambda expressions)، ورأينا أنها تُخصِّص تابعًا (method) وحيدًا. في المقابل، يُمكِن للواجهات (interfaces) أن تَكُون أكثر تعقيدًا بمراحل كما أن لها استخدامات آخرى كثيرة.

من غَيْر المُحتمَل أن تحتاج إلى كتابة واجهات (interfaces) خاصة بك حاليًا؛ فهي ضرورية فقط للبرامج المُعقَّدة نسبيًا، ولكن هنالك عدة واجهات (interfaces) مُستخدَمة بحزم جافا القياسية (Java's standard packages) بطرائق مُهِمّة وتحتاج إلى تَعلُّم طريقة اِستخدَامها.

تعريف الواجهات (interfaces) وتنفيذها (implementation)

لقد تَعرَّضنا لمصطلح "الواجهة (interface)" ضِمْن أكثر من سياق، سواء فيما يَتَعلَّق بالصناديق السوداء (black boxes) في العموم أو فيما يَتَعلَّق بالبرامج الفرعية (subroutines) على وجه الخصوص. تَتكوَّن واجهة أي برنامج فرعي (subroutine interface) من اسمه، ونوعه المُعاد (return type)، وعدد مُعامِلاته (parameters) وأنواعها. تُمثِل تلك المعلومات كل ما أنت بحاجة إلى مَعرِفته لكي تَتَمكَّن من استدعاء البرنامج الفرعي. بالإضافة إلى ذلك، يَمتلك أي برنامج فرعي جزءًا تّنْفيذيًا (implementation)، هو كتلة الشيفرة المُعرِّفة له (defines) والتي تُنفَّذ عند استدعاءه.

بلغة الجافا، كلمة interface هي كلمة محجوزة تَحمِل معنًى تقنيًا إضافيًا. وفقًا لهذا المعنى، تَتكوَّن الواجهة من مجموعة من واجهات توابع النُسخ (instance method interfaces) بدون أجزائها التّنفيذية (implementations). يستطيع أي صنف أن يُنفِّذ (implement) واجهة معينة بتوفير الأجزاء التّنْفيذية (implementation) لجميع التوابع المُخصَّصة ضِمْن تلك الواجهة. اُنظر المثال التالي لواجهة (interface) بسيطة جدًا بلغة الجافا:

public interface Strokeable { public void stroke(GraphicsContext g); }

تبدو الشيفرة بالأعلى مشابهة لتعريف صنف (class definition) باستثناء حَذْف الجزء التّنْفيذي (implementation) للتابع stroke()‎. إذا أراد صنف معين أن يُنفِّذ تلك الواجهة Strokeable، فلابُدّ له من أن يُوفِّر جزءًا تّنْفيذيًا للتابع stroke()‎ كما قد يَتَضمَّن أي توابع أو متغيرات آخرى. اُنظر الشيفرة التالية على سبيل المثال:

public class Line implements Strokeable { public void stroke(GraphicsContext g) { . . . // ارسم خطًا } . . . // توابع ومتغيرات وبواني آخرى }

لكي يُنفِّذ صنف واجهةً (interface) معينةً، ينبغي عليه أن يَفعَل أكثر من مُجرّد توفير الأجزاء التّنْفيذية (implementation) لجميع التوابع ضِمْن تلك الواجهة، فعليه تحديدًا أن يُعلن صراحةً عن تّنفيذه (implements) لتلك الواجهة باستخدام الكلمة المحجوزة implements كالمثال بالأعلى. لابُدّ لأي صنف حقيقي (concrete class) يَرغَب بتّنْفيذ الواجهة Strokeable من أن يُعرِّف تابع نسخة اسمه stroke()‎، لذا سيَتضمَّن أي كائن (object) مُنشَئ من هذا الصنف التابع stroke()‎. يُعدّ الكائن مُنفِّذًا (implement) لواجهة معينة إذا كان ينتمي لصنف يُنفِّذ (implements) تلك الواجهة، فمثلًا، يُنفِّذ أي كائن من النوع Line الواجهة Strokeable.

في حين يستطيع الصنف أن يَتمدَّد (extend) من صنف واحد فقط، فإنه في المقابل يستطيع أن يُنفِّذ (implements) أي عدد من الواجهات (interfaces). وفي الواقع، يُمكِن للصنف أن يَتمدَّد (extend) من صنف آخر، وأن يُنفِّذ واجهة واحدة أو أكثر بنفس ذات الوقت، لذلك نستطيع كتابة التالي مثلًا:

class FilledCircle extends Circle implements Strokeable, Fillable { . . . }

على الرغم من أن الواجهات (interfaces) ليست أصنافًا (classes)، فإنها تُشبهها إلى حد كبير. في الواقع، أي واجهة (interface) هي أَشْبه ما تَكُون بصنف مُجرّد (abstract class) لا يُستخدَم لإنشاء كائنات، وإنما كقاعدة لإنشاء أصناف فرعية (subclasses). تُعدّ البرامج الفرعية (subroutines) ضِمْن أي واجهة توابعًا مجردةً (abstract methods)، والتي لابُدّ لأيّ صنف حقيقي (concrete class) يَرغَب بتّنْفيذ تلك الواجهة من أن يُنفِّذها (implement). تستطيع الموازنة بين الواجهة Strokeable والصَنْف المُجرّد (abstract class) التالي:

public abstract class AbstractStrokeable { public abstract void stroke(GraphicsContext g); }

يَكمُن الفرق بينهما في أن الصنف الذي يَتمدَّد (extend) من الصنف AbstractStrokeable لا يُمكِنه أن يَتَمدَّد من أي صنف آخر. أما الصنف الذي يُنفِّذ الواجهة Strokeable يستطيع أن يَتَمدَّد من أي صنف آخر كما يستطيع أن يُنفِّذ (implement) أي واجهات (interfaces) آخرى. بالإضافة إلى ذلك، يُمكِن لأي صنف مُجرّد (abstract class) أن يَتَضمَّن توابعًا غير مُجرّدة (non-abstract) وآخرى مُجرّدة (abstract). في المقابل، تستطيع أي واجهة (interface) أن تَتَضمَّن توابعًا مُجرّدة فقط، لذا فهي أَشْبه ما تَكُون بصنف مُجرّد نقي (pure).

ينبغي أن تُصرِّح عن التوابع ضِمْن أي واجهة (interface) على أساس كَوْنها -أي التوابع- عامة public ومُجردّة abstract. ولمّا كان هذا هو الخيار الوحيد المُتاح، فإن تَخْصِيص هذين المُبدِّلين (modifiers) ضِمْن التّصْريح (declaration) ليس ضروريًا.

إلى جانب التّصريح (method declarations) عن التوابع، يُمكِن لأي واجهة (interface) أن تُصرِّح عن وجود مُتْغيِّرات (variable declarations)، وينبغي عندها أن تُصرِّح عنها على أساس كَوْنها عامة public، وساكنة static، ونهائية final، ولذا فإنها تَصيِر عامة وساكنة ونهائية بأي صنف يُنفِّذ (implements) تلك الواجهة. ولمّا كان هذا هو الخيار الوحيد المُتاح للتّصْريح عنها، فإن تَخْصِيص تلك المُبدِّلات (modifiers) ضِمْن التّصْريح (declaration) ليس ضروريًا. اُنظر المثال التالي:

public interface ConversionFactors { int INCHES_PER_FOOT = 12; int FEET_PER_YARD = 3; int YARDS_PER_MILE = 1760; }

هذه هي الطريقة المناسبة لتعريف (define) ثوابت مُسماة (named constants) يُمكِن اِستخدَامها بعدة أصناف. يُمكِن لأي صنف يُنفِّذ (implements) الواجهة ConversionFactors أن يَستخدِم الثوابت المُعرَّفة بتلك الواجهة (interface) كما لو كانت مُعرَّفة بالصنف.

لاحِظ أن أي مُتْغيِّر مُعرَّف ضِمْن واجهة (interface) هو بالنهاية ثابت (constant) وليس مُتْغيِّرًا على الإطلاق. وفي العموم، لا يُمكِن لأي واجهة (interface) أن تُضيف مُتْغيِّرات نُسخ (instance variables) إلى الأصناف التي تُنفِّذها (implement).

يُمكِن لأي واجهة (interface) أن تَتَمدَّد (extend) من واجهة واحدة أو أكثر. على سبيل المثال، إذا كان لدينا الواجهة Strokeable المُعطاة بالأعلى، بالإضافة إلى الواجهة Fillable والتي تُعرِّف التابع fill(g)‎، نستطيع عندها تعريف الواجهة التالية:

public interface Drawable extends Strokeable, Fillable { // المزيد من التوابع أو الثوابت }

ينبغي لأي صَنْف حقيقي (concrete class) يُنفِّذ الواجهة Drawable من أن يُوفِّر الأجزاء التّنْفيذية (implementations) لكُلًا من التابع stroke()‎ من الواجهة Strokeable، والتابع draw()‎ من الواجهة Fillable، بالإضافة إلى أي توابع مُجرّدة (abstract methods) آخرى قد تُخصِّصها الواجهة Drawable مباشرة. عادة ما تُعرَّف (define) الواجهة (interface) ضِمْن ملف ‎.java الخاص بها، والذي لابُدّ أن يَكُون له نفس اسم الواجهة. فمثلًا، تُعرَّف الواجهة Strokeable بملف اسمه Strokeable.java. وبالمثل من الأصناف (classes)، يُمكِن للواجهة (interface) أن تقع ضِمْن حزمة (package)، كما يُمكِنها أن تَستورِد (import) أشياءً من حزم آخرى.

التوابع الافتراضية (default methods)

بداية من الإصدار الثامن من الجافا، تستطيع الواجهات (interfaces) أن تَتَضمَّن ما يعرف باسم "التوابع الافتراضية (default methods)"، والتي تَملُك جزءًا تّنْفيذيًا (implementation) بعكس التوابع المُجرّدة (abstract methods) المُعتادة. تُورَث التوابع الافتراضية من الواجهات (interfaces) إلى أصنافها المُنفِّذة (implement) بنفس الطريقة التي تُورَث بها التوابع العادية من الأصناف إلى أصنافها الفرعية. لذا عندما يُنفِّذ (implement) صنف معين واجهةً تَحتوِي على توابع افتراضية، فإنه لا يَكُون مضطرًا لأن يُوفِّر جزءًا تّنْفيذيًا (implementation) لأي تابع افتراضي (default method) ضِمْن الواجهة، مع أن بإمكانه القيام بذلك إذا كان لديه تّنْفيذًا (implementation) مُختلفًا. تَدفَع التوابع الافتراضية (default methods) لغة الجافا خطوة للأمام بطريق دَعْم الوراثة المُتعدّدة (multiple inheritance)، ولكنها مع ذلك ليست وراثة مُتعدّدة بحق؛ لأن الواجهات لا تستطيع تعريف مُتْغيِّرات نُسخ (instance variables). تستطيع التوابع الافتراضية (default methods) استدعاء التوابع المجردة (abstract methods) المُعرَّفة بنفس الواجهة، لكنها لا تستطيع الإشارة إلى أي مُتْغيِّر نسخة (instance variable).

ملحوظة: تستطيع واجهات نوع الدالة (functional interfaces) أيضًا أن تَحتوِي على توابع افتراضية (default methods) بالإضافة إلى التابع المُجرّد (abstract method) الوحيد الذي بإمكانها تَخْصِيصه.

ينبغي أن تُصرِّح عن التوابع الافتراضية (default methods) على أساس كَوْنها عامة public، ولمّا كان ذلك هو الخيار الوحيد المُتاح، فإن تَخْصِيص المبدل public ضِمْن التّصْريح (declaration) ليس ضروريًا. في المقابل، لابُدّ من كتابة المُبدِّل default بشكل صريح أثناء التّصْريح عن أي تابع افتراضي (default method). اُنظر المثال التالي:

public interface Readable { // تمثل مصدر إدخال public char readChar(); // اقرأ المحرف التالي المُدْخَل default public String readLine() { //اقرأ حتى نهاية السطر StringBuilder line = new StringBuilder(); char ch = readChar(); while (ch != '\n') { line.append(ch); ch = readChar(); } return line.toString(); } }

لابُدّ لأي صنف حقيقي (concrete class) يُنفِّذ الواجهة (interface) -بالأعلى- من أن يُوفِّر تّنْفيذًا (implementation) للتابع readChar()‎. في المقابل، سيَرِث ذلك الصنف تعريف readLine()‎ من الواجهة، ولكنه قد يُوفِّر تعريفًا (definition) جديدًا إذا كان ذلك ضروريًا. عندما يَتَضمَّن صنف معين تّنفيذًا (implementation) لتابع افتراضي (default method)، فإن ذلك التّنْفيذ الجديد يُعيد تعريف (overrides) التابع الافتراضي الموجود بالواجهة (interface).

بالمثال السابق، يَستدعِي التابع الافتراضي readLine()‎ التابع المُجرّد readChar()‎، والذي يَتوفَّر تعريفه (definition) فقط من خلال الأصناف المُنفِّذة للواجهة، ولهذا تُعدّ الإشارة إلى readChar()‎ مُتعدِّدة الأشكال (polymorphic). كُتب التّنْفيذ الافتراضي للتابع readLine()‎ بحيث يَكُون ملائمًا لأي صنف يُنفِّذ الواجهة Readable. اُنظر الشيفرة التالية والتي تَتَضمَّن صنفًا يُنفِّذ الواجهة Readable كما يَحتوِي على البرنامج main()‎ لاختبار الصَنْف:

public class Stars implements Readable { public char readChar() { if (Math.random() > 0.02) return '*'; else return '\n'; } public static void main(String[] args) { Stars stars = new Stars(); for (int i = 0 ; i < 10; i++ ) { // اِستدعي التابع الافتراضي String line = stars.readLine(); System.out.println( line ); } } }

تُوفِّر التوابع الافتراضية (default methods) إمكانية شبيهة لما يُعرَف باسم "المخلوط (mixin)" المُدعَّم ببعض اللغات البرمجية الآخرى، والتي تَعنِي المقدرة على خَلْط وظائف مصادر آخرى إلى داخل الصنف. لمّا كان بإمكان أي صنف أن يُنفِّذ أي عدد من الواجهات (interfaces)، فإنه يستطيع خلط وظائف عدة مصادر آخرى مختلفة.

الواجهات كأنواع

كما هو الحال مع الأصناف المُجرّدة (abstract classes)، لا يُمكِنك إنشاء كائن فعليّ من واجهة (interface)، ولكن تستطيع التّصْريح (declare) عن مُتْغيِّر نوعه عبارة عن واجهة. لنَفْترِض مثلًا أن لدينا الواجهة Strokeable المُعرَّفة بالأعلى، ويُنفِّذها كُلًا من الصنفين Line و Circle، يُمكِنك عندها كتابة التالي:

// صرح عن متغير من النوع‫ Strokeable والذي يمكنه الإشارة إلى أي // كائن ينفذ تلك الواجهة Strokeable figure; figure = new Line(); // ‫يشير إلى كائن من الصنف Line figure.stroke(g); // ‫اِستدعي التابع stroke() من الصنف Line figure = new Circle(); // ‫يشير الآن إلى كائن من الصنف Circle figure.stroke(g); // ‫اِستدعي التابع stroke() من الصنف Circle

يُمكِن لأي مُتْغيِّر من النوع Strokeable أن يُشير إلى أي كائن طالما كان صَنْفه يُنفِّذ الواجهة Strokeable. لمّا كان figure مُتْغيِّرًا من النوع Strokeable، ولأن أي كائن من النوع Strokeable يَحتوِي على التابع stroke()‎، فحتمًا سيَحتوِي الكائن الذي يُشير إليه المُتْغيِّر figure على التابع stroke()‎، ولهذا فإن التَعْليمَة figure.stroke(g)‎ صالحة تمامًا.

تُستخدَم الأنواع (types) في العموم إما للتّصْريح (declare) عن مُتْغيِّر، أو لتَخْصِيص نوع معامل برنامج فرعي (routine)، أو لتَخْصِيص النوع المُعاد (return type) من دالة (function). النوع في العموم إما أن يَكُون صنفًا أو واجهة (interface) أو أحد الأنواع البسيطة (primitive) الثمانية المَبنية مُسْبَقًا (built-in). ليس هنالك من أيّ احتمال آخر، ربما باستثناء بعض الحالات الخاصة كأنواع التعداد (enum) والتي هي بمثابة نوع خاص من الأصناف. من بين كل تلك الأنواع، الأصناف هي الوحيدة التي يُمكِن اِستخدَامها لإنشاء كائنات (objects).

يُمكِنك أيضًا اِستخدَام الواجهات (interface) لتَخْصِيص النوع الأساسي (base type) لمصفوفة. فمثلًا، تستطيع أن تُصرِّح عن مُتْغيِّر أو أن تُنشِئ مصفوفة باستخدام نوع المصفوفة Strokeable[]‎، وفي تلك الحالة، يُمكِن لعناصر تلك المصفوفة الإشارة إلى أي كائن طالما كان يُنفِّذ الواجهة Strokeable. اُنظر الشيفرة التالية:

Strokeable[] listOfFigures; listOfFigures = new Strokeable[10]; listOfFigures[0] = new Line(); listOfFigures[1] = new Circle(); listOfFigures[2] = new Line(); . . .

تَملُك جميع عناصر تلك المصفوفة التابع stroke()‎، مما يَعنِي إمكانية كتابة تعبيرات مثل listOfFigures.stroke(g)‎.

ترجمة -بتصرّف- للقسم Section 7: Interfaces من فصل Chapter 5: Programming in the Large II: Objects and Classes من كتاب Introduction to Programming Using Java.

44,636 التعديل على صفحة HTML عبر جافاسكربت

يُعدّ التعديل على نموذج تمثيل المستند ككائن (DOM) مفتاح الحصول على صفحات "حيّة" (ديناميكية). سوف تتعرف فيما يلي على كيفية إنشاء عناصر جديدة على الصفحات في لمح البصر (بطريقة آنية دون الحاجة إلى إعادة تحميل الصفحات).

مثال: إظهار رسالة

فلنعرض ذلك على شكل مثالٍ تطبيقيٍ، حيث نُضيف رسالةً للصفحة تكون أكثر تنسيقًا من رسالة alert كالآتي:

<style> .alert { padding: 15px; border: 1px solid #d6e9c6; border-radius: 4px; color: #3c763d; background-color: #dff0d8; } </style> <div class="alert"> <strong>Hi there!</strong> You've read an important message. </div>

See the Pen JS-p2-07-Modifying the document-ex1 by Hsoub (@Hsoub) on CodePen.

استعملنا في هذا المثال لغة HTML. فلنُنشئ نفس الحاوية div باستخدام جافاسكربت (على افتراض أن شيفرة HTML/CSS تتضمّن الأنماط التنسيقية).

إنشاء عنصر ما

توجد طريقتان لإنشاء عناصر (DOM):

  • الدالّة (document.createElement(tag: تُستخدم لإنشاء عقدة عناصرية جديدة باستعمال الوسم الذي يُمرَّر لها كالآتي:
let div = document.createElement('div');
  • الدالّة (document.createTextNode(text: تُستخدم لإنشاء عقدة نصية جديدة باستعمال النص الذي يُمرَّر لها كالآتي:
let textNode = document.createTextNode('Here I am');

غالبا ما نحتاج إلى إنشاء عقدٍ عناصريةٍ، مثل الحاوية div، من أجل إظهار الرسالة.

إنشاء الرسالة

يتطلّب إنشاء حاوية الرسالة div ثلاث مراحل:

// 1. إنشاء العنصر <div> let div = document.createElement('div'); // 2. إسناد القيمة "alert" لصنف الحاوية div.className = "alert"; // 3. وضع المحتوى داخل الحاوية div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

صحيحٌ أنّنا أنشأنا العنصر ولكنه لحد الآن غير ظاهرٍ في الصفحة لأنّنا أنشأناه داخل المتغيّر div ولم نُضفه بعد للصفحة.

دوالّ إضافة العناصر للصفحة

من أجل إظهار الحاوية div، نحتاج إلى إضافتها في موضع ما من الصفحة، داخل العنصر <body> مثلا والمشار إليه بالكائن document.body. توجد دالّة خاصة تسمّى append تساعد على ذلك كما هو موضّح في المثال الآتي (انظر السطر الموسوم بـ (*)):

<style> .alert { padding: 15px; border: 1px solid #d6e9c6; border-radius: 4px; color: #3c763d; background-color: #dff0d8; } </style> <script> let div = document.createElement('div'); div.className = "alert"; div.innerHTML = "<strong>Hi there!</strong> You've read an important message."; document.body.append(div);//(*) </script>

See the Pen JS-p2-07-Modifying the document-ex2 by Hsoub (@Hsoub) on CodePen.

نادينا، في هذا المثال، الدالّة append وطبقناها على الكائن document.body. كان يمكن أن نُطبقها على أيّ عنصرٍ كان من أجل إضافة عنصرٍ آخر له، حيث يمكننا على سبيل المثال إضافة عنصر ما للعنصر <div> باستعمال الدالّة div.append(anotherElement)‎. وهذه قائمة دوالّ غيرها تُستعمل لإضافة عنصرٍ ما لعنصرٍ آخرٍ في موضعٍ معيّنٍ من الصفحة:

  • node.append(...nodes or strings)‎: إضافة عقد أو سلاسل نصية في نهاية العقدة node.
  • node.prepend(...nodes or strings)‎: إضافة عقد أو سلاسل نصية في بداية العقدة node.
  • node.before(...nodes or strings)‎: إضافة عقد أو سلاسل نصية قبل العقدة node.
  • node.after(...nodes or strings)‎: إضافة عقد أو سلاسل نصية بعد العقدة node.
  • node.replaceWith(...nodes or strings)‎: وضع عقد أو سلاسل نصية مكان العقدة node.

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

<ol id="ol"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> ol.before('before'); // إضافة السلسلة "before" قبل العنصر <ol> ol.after('after'); // إضافة السلسلة "after" بعد العنصر <ol> let liFirst = document.createElement('li'); liFirst.innerHTML = 'prepend'; ol.prepend(liFirst); // إضافة العنصر liFirst في بداية القائمة <ol> let liLast = document.createElement('li'); liLast.innerHTML = 'append'; ol.append(liLast); // إضافة العنصر liLast في نهاية القائمة <ol> </script>

See the Pen JS-p2-07-Modifying the document-ex3 by Hsoub (@Hsoub) on CodePen.

وفيما يلي صورةٌ توضيحيةٌ لما تقوم به هذه الدوالّ:

وبالتالي تصبح القائمة في شكلها النهائي كما يلي:

before <ol id="ol"> <li>prepend</li> <li>0</li> <li>1</li> <li>2</li> <li>append</li> </ol> after

وكما سبق أن ذكرنا، يُمكننا إضافة العديد من العقد والسلاسل النصية بمناداة الدالّة مرّةً واحدةً فقط. في المثال التالي، أُضيفت كلٌّ من السلسلة النصية والعنصر دفعةً واحدةً:

<div id="div"></div> <script> div.before('<p>Hello</p>', document.createElement('hr')); </script>

See the Pen JS-p2-07-Modifying the document-ex4 by Hsoub (@Hsoub) on CodePen.

لاحظ هنا أن النص أُضيف بمثابة "نصٍ" وليس بمثابة شيفرة HTML، حيث تظهر التسلسلات التهريبية مثل < و>. وبالتالي تصبح شيفرة HTML النهائية كالآتي:

<p>Hello</p> <hr> <div id="div"></div>

وبعبارة أخرى، تُضاف السلاسل النصية بطريقة آمنة كأنها أُضيفت باستعمال الخاصية textContent.

تُستخدم هذه الدوالّ إذا فقط لإضافة عقد DOM أو مقاطع نصية. ولكن ماذا لو أردنا إضافة سلسلةٍ نصيةٍ تحوي شيفرة HTML بما فيها الوسوم وغيرها، مثل ما هو معمول به مع الخاصية innerHTML؟

الدوالّ insertAdjacentHTML/Text/Element

والجوابٍ على السؤال السابق، هو استخدام دالّةٍ أخرى متعدّدة الاستعمالات، إنها الدالّة elem.insertAdjacentHTML(where, html)‎. الوسيط الأوّل where للدالّة عبارة عن كلمة رمز (code word) تُحدِّد في أيّ موضع بالضبط من العنصر سيُضاف الوسيط الثاني، وتأخذ هذه الكلمة الرمزواحدةً من القيم التالية:

  • "beforebegin": إضافة شيفرة HTML مباشرةً قبل العنصرelem.
  • "afterbegin": إضافة شيفرة HTML في بداية العنصرelem.
  • "beforeend": إضافة شيفرة HTML في نهاية العنصرelem.
  • "afterend": إضافة شيفرة HTML مباشرةً بعد العنصرelem.

ويكون الوسيط الثاني html للدالّة عبارة عن سلسلة نصية تُضاف على شكل شيفرة HTML. لاحظ المثال التالي:

<div id="div"></div> <script> div.insertAdjacentHTML('beforebegin', '<p>Hello</p>'); div.insertAdjacentHTML('afterend', '<p>Bye</p>'); </script>

See the Pen JS-p2-07-Modifying the document-ex5 by Hsoub (@Hsoub) on CodePen.

الذي يولّد الشيفرة التالية:

<p>Hello</p> <div id="div"></div> <p>Bye</p>

كانت هذه هي الطريقة التي تُمكّننا من إضافة شيفرة HTML، أيًّا كانت، للصفحة. وفيما يلي صورة توضيحية لطرائق الإضافة في مختلف المواضع:

ويمكننا بسهولة ملاحظة أوجه الشبه بين هذه الصورة والصورة التي قبلها، حيث أن مواضع الإضافة هي نفسها لكن هذه الدالّة تُضيف شيفرة HTML في حين تُضيف السابقة عقدًا أو سلاسل نصيةً. ولهذه الدالّة دالّتين "شقيقتين":

  • elem.insertAdjacentText(where, text)‎: لها نفس البنية ولكن السلسلة النصية تُضاف هنا كنص وليس كشيفرة HTML.
  • elem.insertAdjacentElement(where, elem)‎: لها أيضًا نفس البنية ولكنها تُستعمل لإضافة عنصرٍ ما.

وُجدت هاتين الدالّتين فقط لضرورة توحيد البنية، ولكن تُستخدم فقط الدالّة elem.insertAdjacentHTML على أرض الواقع، طالما وُجدت الدوالّ append/prepend/before/after التي تُستخدم لإضافة كلٍّ من العقد والنصوص بالإضافة إلى أنها أقصر من حيث عدد الأحرف.
وفيما يلي طريقةٌ بديلةٌ لإظهار رسالةٍ ما:

<style> .alert { padding: 15px; border: 1px solid #d6e9c6; border-radius: 4px; color: #3c763d; background-color: #dff0d8; } </style> <script> document.body.insertAdjacentHTML("afterbegin", `<div class="alert"> <strong>Hi there!</strong> You've read an important message. </div>`); </script>

See the Pen JS-p2-07-Modifying the document-ex6 by Hsoub (@Hsoub) on CodePen.

إزالة/حذف عقدة ما

اُستحدثت الدالّة node.remove()‎ لإزالة/حذف عقدةٍ ما. فلنجعل الرسالة التي أظهرناها في المثال السابق تختفي بعد ثانية واحدة من إظهارها:

<style> .alert { padding: 15px; border: 1px solid #d6e9c6; border-radius: 4px; color: #3c763d; background-color: #dff0d8; } </style> <script> let div = document.createElement('div'); div.className = "alert"; div.innerHTML = "<strong>Hi there!</strong> You've read an important message."; document.body.append(div); setTimeout(() => div.remove(), 1000); </script>

See the Pen JS-p2-07-Modifying the document-ex7 by Hsoub (@Hsoub) on CodePen.

لاحظ هنا أنه لا داعيَ لتحويل عنصرٍ ما من الموضع الذي كان فيه إذا أردت وضعه في مكان آخر، هذا لأنّ كافة دوالّ الإضافة تُحوِّل آليا العنصر من مكانه السابق. فلنُحوّل من خلال المثال الآتي موضعي العنصرين التاليين:

<div id="first">First</div> <div id="second">Second</div> <script> // لا داعيَ لمناداة الدالّة remove second.after(first); //تُضيف العنصر second بعد العنصر first </script>

See the Pen JS-p2-07-Modifying the document-ex8 by Hsoub (@Hsoub) on CodePen.

نسخ العُقد باستعمال الدالة cloneNode

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

تُنشِئ الدالّة elem.cloneNode(true)‎ عند مناداتها نسخةً كاملةً للعنصر، بما فيه من سمات وتوابع (العناصر الوليدة). أما إذا نادينا الدالّة elem.cloneNode(false)‎- مع تغيير الوسيط من true إلى false- تُنشِئ الدالّة العنصر وحده دون العناصر التابعة له (العناصر الوليدة).

وهذا مثال عن نسخ الرسالة:

<style> .alert { padding: 15px; border: 1px solid #d6e9c6; border-radius: 4px; color: #3c763d; background-color: #dff0d8; } </style> <div class="alert" id="div"> <strong>Hi there!</strong> You've read an important message. </div> <script> let div2 = div.cloneNode(true); // نسخ الرسالة div2.querySelector('strong').innerHTML = 'Bye there!'; // تغيير محتوى الرسالة المنسوخة div.after(div2); // إظهار النسخة بعد الحاوية الأولى </script>

See the Pen JS-p2-07-Modifying the document-ex9 by Hsoub (@Hsoub) on CodePen.

العقدة الخاصة DocumentFragment

تُعدّ DocumentFragment عقدة DOM خاصة تُستعمل كمُغلِّف (wrapper) لتمرير قوائم تحوي عددًا من العقد. ويمكننا إضافة عقدٍ لها باستعمال الدالّة append، ولكن عند إضافتها هي (أي العقدة الخاصة DocumentFragment) في موضعٍ ما، يكون محتواها هو ما يُضاف في هذا الموضع بدلا عنها.

تُنشِئ الدالّة getListContent في هذا المثال مقطعا يتضمّن عناصر <li> تُضاف لاحقا إلى العنصر <ul>:

<ul id="ul"></ul> <script> function getListContent() { let fragment = new DocumentFragment(); for(let i=1; i<=3; i++) { let li = document.createElement('li'); li.append(i); fragment.append(li); } return fragment; } ul.append(getListContent()); // (*) </script>

See the Pen JS-p2-07-Modifying the document-ex10 by Hsoub (@Hsoub) on CodePen.

لاحظ هنا في السطر الأخير الموسوم بالرمز (*) أنّنا أضفنا العقدة الخاصة DocumentFragment باستعمال الدالّة append (التي تضيف عنصرًا في نهاية عنصرٍ آخر) لكنها تخلّلت الشيفرة فحصلنا على البنية التالية:

<ul> <li>1</li> <li>2</li> <li>3</li> </ul>

ونادرًا ما تُستخدم عقدة DOM الخاصية DocumentFragment، ذلك لأنه لا يوجد سببٌ يحملنا على إضافة عنصرٍ أو عناصرَ إلى عقدة من نوعٍ خاصٍ في حين يمكننا وضعه/وضعها في مصفوفة من العقد كالآتي:

<ul id="ul"></ul> <script> function getListContent() { let result = []; for(let i=1; i<=3; i++) { let li = document.createElement('li'); li.append(i); result.push(li); } return result; } ul.append(...getListContent()); </script>

See the Pen JS-p2-07-Modifying the document-ex11 by Hsoub (@Hsoub) on CodePen.

أتينا على ذكر العقدة الخاصة DocumentFragment لأنها تندرج ضمن بعض المفاهيم، كالعنصر template (أي القالب) مثلا، التي سنتطرّق إليها لاحقا.

دوال الإضافة والحذف على الطريقة القديمة

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

  • append
  • prepend
  • before
  • after
  • remove
  • replaceWith

السبب الوحيد الذي جعلنا نذكر لك هنا هذه الدوالّ هو أنك يمكن أن تصادفها في الكثير من السكربتات القديمة.

الدالة appendChild‎

تضيف الدالّة parentElem.appendChild(node)‎ العقدة node كآخر عنصرٍ وليدٍ للعنصر parentElem. سنضيف في المثال التالي عنصرًا جديدًا <li> في نهاية العنصر الوالد <ol>:

<ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> let newLi = document.createElement('li'); newLi.innerHTML = 'Hello, world!'; list.appendChild(newLi); </script>

See the Pen JS-p2-07-Modifying the document-ex12 by Hsoub (@Hsoub) on CodePen.

الدالة insertBefore

وتضيف الدالّة parentElem.insertBefore(node, nextSibling)‎ العقدة node قبل العقدة nextSibling ضمن العقدة parentElem.

تُضيف الشيفرة التالية عنصرًا جديدًا للقائمة <ol> مباشرة قبل العنصر<li> الثاني في القائمة:

<ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> let newLi = document.createElement('li'); newLi.innerHTML = 'Hello, world!'; list.insertBefore(newLi, list.children[1]); </script>

See the Pen JS-p2-07-Modifying the document-ex13 by Hsoub (@Hsoub) on CodePen.

يمكننا إضافة العقدة newLi في المرتبة الأولى كالآتي:

list.insertBefore(newLi, list.firstChild); الدالة replaceWith

تَستبدلُ هذه الدالّة العقدة node بالعقدة oldChild التي تُعدّ وليدة العقدة parentElem.

الدالة removeChild

تحذِف هذه الدالّة العقدة node من العقدة parentElem (حيث نفترض هنا أن العقدة node وليدة العقدة parentElem).

يُحذَف العنصر الأول <li> من القائمة <ol> في المثال الموالي:

<ol id="list"> <li>0</li> <li>1</li> <li>2</li> </ol> <script> let li = list.firstElementChild; list.removeChild(li); </script>

See the Pen JS-p2-07-Modifying the document-ex14 by Hsoub (@Hsoub) on CodePen.

تكون مخرجات (output) هذه الدوالّ عبارة عن عُقد (العقد التي حُذفت/التي أُضيفت)، حيث تُوّلِد الدالّة parentElement.appendChild(node)‎ عقدةً ولكن غالبًا ما لا تُستعمل القيمة التي توّلدت من مناداة هذه الدالّة، نحتاج فقط إلى تطبيق الدالّة/تنفيذها.

نبذة عن الدالة "document.write"

هناك أيضا الدالّة document.write، وهي دالّة قديمة جدًا تُستخدم لإضافة جزءٍ ما لصفحة الويب، وبنيتها كالآتي:

<p>Somewhere in the page...</p> <script> document.write('<b>Hello from JS</b>'); </script> <p>The end</p>

See the Pen JS-p2-07-Modifying the document-ex15 by Hsoub (@Hsoub) on CodePen.

تؤدي مناداة الدالّة document.write(html)‎ إلى كتابة الشيفرة html في الصفحة 'آنذاك وفي هذا الموضع بالضبط'. وبذلك تُولَّد السلسلة النصية html بطريقة ديناميكية (آنية) مرنة. يمكن إذًا استعمال لغة جافاسكربت لإنشاء وكتابة صفحة ويب كاملة.

اِستُحدثت هذه الدالّة في وقت لم تكن فيه كائنات DOM موجودة ولم تكن هناك معايير أيضًا….إلخ، أي منذ زمنٍ بعيدٍ جدًا، ومازالت 'حيّة' ما دام البعض يستعملها لحد الآن في سكربتاته، ولكن نادرًا ما نصادفها في السكربتات العصرية الدارجة حاليا، نظرًا لمحدوديتها في الجوانب التالية:

تُنفَّذ مناداة الدالّة document.write فقط أثناء تحميل الصفحة وفي حال نُوديَت بعد ذلك، يُحذف محتوى الصفحة كما في المثال التالي:

<p>After one second the contents of this page will be replaced...</p> <script> // مناداة الدالّة document.write بعد مرور ثانية واحدة // أي بعد تحميل الصفحة، فتَحذِف هذه الدالّة المحتوى الأصلي للصفحة setTimeout(() => document.write('<b>...By this.</b>'), 1000); </script>

See the Pen JS-p2-07-Modifying the document-ex16 by Hsoub (@Hsoub) on CodePen.

لا ينبغي إذًا استعمال هذه الدالّة بعد تحميل الصفحة، على عكس غيرها من دوالّ DOM التي تحدثنا عنها سابقا.

تحدثنا عن الجانب السلبي لهذه الدالّة، لكنها تملك جانبا إيجابيًا أيضًا؛ فمن وجهة نظر تقنية، وفي حال ما نوديت الدالّة document.write أثناء تحليل/قراءة المتصفّح لشيفرة HTML تدريجيا، وكتبت الدالّة بعض الأسطر، يَعتبر المتصفّح أن هذه الأسطر كانت موجودةً منذ البداية في شيفرة HTML، يُسرِّع ذلك كثيرًا من عملية القراءة حيث لا تشمل هذه العملية أيّ تعديلات على DOM، فتكتب الدالّة مباشرةً على الصفحة قبل أن يُنشَأ نموذج تمثيل المستند ككائن.

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

الخلاصة
  • الدوال التي تُستخدم في إنشاء عقدٍ جديدةٍ هي:
    • document.createElement(tag)‎: تُنشِئ عنصرًا بناءً على الوسم tag.
    • document.createTextNode(value)‎: تُنشِئ عقدةً نصيةً (نادرًا ما تُستخدم).
    • elem.cloneNode(deep)‎: تنسخ العنصر، مع كل توابعه (أي العناصر الوليدة) إذا كانت قيمة المعامل deep تُساوي true.
  • الدوالّ التي تستخدم للإضافة والحذف هي:
    • node.append(...nodes or strings)‎: تُضيف عقدة أو سلسلة نصية في نهاية العقدة node. -node.prepend(...nodes or strings)‎: تُضيف عقدة أو سلسلة نصية في بداية العقدة node. -node.before(...nodes or strings)‎: تُضيف عقدة أو سلسلة نصية مباشرة قبل العقدة node. -node.after(...nodes or strings)‎: تُضيف عقدة أو سلسلة نصية مباشرة بعد العقدة node. -node.replaceWith(...nodes or strings)‎: تستبدل بالعقدة node ما مُرِّر إليها من عقدة/عقد أو محتوى نصي. -node.remove()‎: تحذف العقدة node.

وهي تُضيف السلاسل النصية كنص وليس كشيفرة HTML.

  • الدوالّ القديمة هي:
    • rent.appendChild(node)‎
    • parent.insertBefore(node, nextSibling)‎
    • parent.removeChild(node)‎
    • parent.replaceChild(newElem, node)‎

وتكون مُخرجات (output) هذه الدوالّ عبارة عن عُقد.

  • تضيف الدالّة elem.insertAdjacentHTML(where, html)‎ شيفرة HTML، التي تُمرّر لها عبر الوسيط html، في المواضع التالية بناءً على قيمة الوسيط where:
  • "beforebegin": تُضاف الشيفرة html مباشرة قبل العنصر elem.
  • "afterbegin": تُضاف الشيفرة html في بداية العنصر elem.
  • "beforeend": تُضاف الشيفرة html في نهاية العنصر elem.
  • "afterend": تُضاف الشيفرة html مباشرة بعد العنصر elem.
  • هناك أيضًا دوالٌّ مماثلةٌ لها وهي elem.insertAdjacentText وelem.insertAdjacentElement ولكن نادرًا ما تٌستخدم.
  • إذا أردت إضافة شيفرة HTML إلى الصفحة قبل نهاية عملية تحميلها اِستعمل الدالّة document.write(html)‎.
    تؤدي مناداة هذه الدالّة بعد انتهاء عملية تحميل الصفحة إلى مسح محتوى الصفحة، ونُصادفها خاصة في السكربتات القديمة.
تمارين createTextNode/innerHTML/textContent

درجة الأهمية: 5 لدينا عنصر DOM فارغ يسمّى elem وسلسلة نصية text. حدِّد، من بين التعليمات الثلاث التالية، تلك التي لها نفس الوظيفة.

  1. elem.append(document.createTextNode(text))‎
  2. elem.innerHTML = text
  3. elem.textContent = text
الحل

التعليمتان 1 و3 لهما نفس الوظيفة، حيث تضيف كلتا التعليمتين السلسلة النصية text للعنصر elem كنص وليس كشيفرة HTML. وفيما يلي مثال على ذلك:

<div id="elem1"></div> <div id="elem2"></div> <div id="elem3"></div> <script> let text = '<b>text</b>'; elem1.append(document.createTextNode(text)); elem2.innerHTML = text; elem3.textContent = text; </script> مسح عنصر ما

درجة الأهمية: 5

أنشئ الدالّة clear(elem)‎ التي تحذف محتوى العنصر elem بالكامل.

<ol id="elem"> <li>Hello</li> <li>World</li> </ol> <script> function clear(elem) { /*اكتب الشيفرة هنا */ } clear(elem); // clears the list </script> الحل

فلنبدأ بالحل الذي لا ينبغي أن تقترحه، لأنه ببساطة حلٌ خاطئ.

function clear(elem) { for (let i=0; i < elem.childNodes.length; i++) { elem.childNodes[i].remove(); } }

طريقة الحل هذه لا تنفع، لأن مناداة الدالّة remove()‎ تؤدي إلى تغيير مواضع العُقد ضِمن المجموعة elem.childNodes، فبعد حذف العقدة الأولى مثلا (التي كانت في الموضع 0 من المجموعة) تُسحب المجموعة كاملةً للخلف لتصبح العقدة ذات الموضع 1 مكان العقدة ذات الموضع 0، لكن المؤشر i في الدورة الثانية من الحلقة يأخذ القيمة 1 وبالتالي تُحذف العقدة ذات الموضع 1 وتبقى العقدة ذات الموضع 0 موجودة، وهكذا دواليك. لن تُحذف إذا بعض العناصر من المجموعة. ويحدث نفس الشيء عند استعمال الحلقة for...of.

الحل الصحيح يكون مثلا كالآتي:

function clear(elem) { while (elem.firstChild) { elem.firstChild.remove(); } }

أو بطريقة أبسط:

function clear(elem) { elem.innerHTML = ''; } لماذا لا تختفي السلسلة 'aaa'؟

درجة الأهمية: 1

عند مناداة الدالّة table.remove()‎، في المثال التالي، يُحذف الجدول من الصفحة ولكن السلسلة 'aaa' تبقى ظاهرة. فلماذا يحدث ذلك؟

<table id="table"> aaa <tr> <td>Test</td> </tr> </table> <script> alert(table); // الجدول موجود table.remove(); // لماذا مازالت السلسلة 'aaa' ؟ ظاهرة </script>

الحل

يعود السبب في حدوث ذلك إلى خطأ في شيفرة HTML لهذا التمرين.

اِعلم أن من مهام المتصفّح تصحيح الأخطاء آليًا. وبما أن المواصفة التقنية تنصّ على أن الجداول لا تضمّ أيّ سلاسل نصية، بل فقط الوسوم الخاصة بالجداول، يُضيف المتصفح السلسلة "aaa" قبل الجدول table مباشرة. وعندما يُحذف الجدول، تبقى السلسلة "aaa" ظاهرة.

للإجابة على سؤال هذا التمرين، يمكنك الإستعانة بعرض نموذج تمثيل المستند ككائن DOM باستعمال المتصفح، وستظهر لك السلسلة "aaa" قبل الجدول table.

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

إنشاء قائمة

درجة الأهمية: 4 اكتب شيفرة تُنشِئ واجهةً تسمح للمستخدم بإدخال محتوى ما على مراحل وتجمع هذا المحتوى في شكل قائمة. ولكل عنصر من القائمة عليك:

  • مطالبة المستخدِم بإدخال المحتوى باستعمال الدالّة prompt.
  • إنشاء العنصر<li> بناءً على المحتوى الذي أدخله المستخدِم.
  • مواصلة العملية إلى غاية فراغ المستخدِم من إدخال المعطيات (بالضغط على المفتاح esc على لوحة المفاتيح، أو الضغط على زر الالغاء الظاهر على النافذة).

يجب أن تُنشَئ العناصر ديناميكيًا وإذا أدخل المستخدِم وسوم HTML، تُعالَج على أنها نصوص، لا شيفرة HTML.

الحل

ملاحظة: لاحظ استعمال الخاصية textContent لإسناد محتوى للعنصر <li>.

يمكنك الإطلاع على الحل من هنا

إنشاء شجرة اعتمادًا على كائن

درجة الأهمية: 5

اكتب الدالّة createTree()‎ التي تُنشئ قائمةً متفرعةً من العناصر ul/li بناءً على كائن متفرع:

let data = { "Fish": { "trout": {}, "salmon": {} }, "Tree": { "Huge": { "sequoia": {}, "oak": {} }, "Flowering": { "apple tree": {}, "magnolia": {} } } };

وتكون بنيتها كالآتي:

let container = document.getElementById('container'); createTree(container, data); // تُنشِئ الشجرة بداخل الحاوية ‘container’

على أن تكون النتيجة المتحصل عليها كالآتي:

اِختر واحدةً من بين الطريقتين التاليتين لحل التمرين:

  • أنشئ الشجرة في شكل شيفرة HTML ثم أسندها للخاصية Container.innerHTML أو
  • أنشئ عُقد الشجرة وأضفها باستعمال دوالّ DOM.

ويُستحسن أن تَحُل التمرين بالطريقين.

ملاحظة: لا ينبغي أن تَضمّ الشجرة في أوراقها عناصر إضافية فارغة <ul></ul>.

الحل

ملاحظة: أسهل طريقة لتصفح الكائن (المرور بعناصره الواحد تلوى الآخر) تكون باستعمال التعاود (recursion).

  1. حل التمرين باستعمال الخاصية innerHTML
  2. حل التمرين باستعمال دوالّ DOM
إظهار العناصر الوليدة لشجرة ما

درجة الأهمية: 5

لدينا هذه الشجرة وهي مبنية على شكل عناصر ul/li متفرعة. اكتب شيفرة تُضيف من خلالها لكل عنصر <li> عدد عناصره الوليدة مع إهمال الأوراق (العُقد التي ليس لها عناصر وليدة)، والنتيجة تكون على الشكل التالي:

الحل

لإضافة نصٍ لكلّ عنصر من العناصر <li> يمكن التعديل على العُقدة النصية data.

يمكنك الإطلاع على الحل من هنا

إنشاء يومية

الأهمية:4

اكتب الدالّة createCalendar(elem, year, month)‎ التي تُنشئ يومية الشهر month من السنة year، وتسندها للعنصر elem. تكون اليومية على شكل جدول تُمثِّل فيه العناصر <tr> الأسابيع والعناصر <td> الأيام، وتكون خانات السطر الأول عبارة عن عناصر <th> تحوي أسماء أيام الأسبوع مُرتّبةً من الإثنين إلى الأحد. فعلى سبيل المثال، تؤدي مناداة الدالّة createCalendar(cal, 2012, 9)‎ إلى إنشاء اليومية التالية وإسنادها للعنصر cal.

ملاحظة: يكفي في هذه المرحلة إظهار اليومية، لا نحتاج أن تكون تفاعلية.

يمكنك الإطلاع على شيفرة التمرين من هنا

الحل

نُنشئ الجدول على شكل سلسلة نصية "<table>....</table>" ثم نسندها إلى الخاصية innerHTML، باتباع خطوات الخوارزمية التالية:

  1. إنشاء خانات السطر الأول من الجدول (table header) باستعمال عناصر<th> تحوي أسماء أيام الأسبوع.
  2. إنشاء الكائنdate باستعمال الدالّة البانية d=newDate(year, month-1)‎ والذي يُمثِّل أول يوم في الشهر month (مع الأخذ في الحسبان أن ترتيب الأشهر في جافاسكربت يبدأ من 0 وليس من 1).
  3. ملء الخانات التي تسبق خانة اليوم الأول من الشهر (أي اليوم d.getDay()‎)، والتي تكون فارغة في اليومية، بإضافة عناصر <td></td> فارغة للجدول.
  4. الانتقال تصاعديا عبر الأيام باستعمال الدالّة d.setDate(d.getDate()+1)‎ وإضافة الخانات <td> لليومية مادامت قيمة d.getMonth()‎ تشير إلى الشهر نفسه (لم نصل بعد للشهر الموالي). وإذا كان اليوم يوم أحدٍ نضيف سطرًا جديدًا <tr></tr>.
  5. إذا وصلنا إلى نهاية الشهر، ومازالت في السطر خانات فارغة، نملؤها بعناصر <td></td> فارغة لنجعل الجدول مستطيلا.

يمكنك الإطلاع على الحل من هنا

إنشاء ساعة ملونة باستعمال الدالّة setInterval

الأهمية: 4

أنشئ ساعة ملونة تشبه الساعة في الشكل التالي:

استعمل HTML/CSS للتنسيق، وجافاسكربت فقط لتحديث القيم الزمنية للعناصر.

الحل

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

<div id="clock"> <span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span> </div>

تحدِّث الدالّة update()‎ الوقت، وتنادي الدالّة setInterval الدالّة update()‎ كل ثانية لتحيين الوقت.

function update() { let clock = document.getElementById('clock'); let date = new Date(); // (*) let hours = date.getHours(); if (hours < 10) hours = '0' + hours; clock.children[0].innerHTML = hours; let minutes = date.getMinutes(); if (minutes < 10) minutes = '0' + minutes; clock.children[1].innerHTML = minutes; let seconds = date.getSeconds(); if (seconds < 10) seconds = '0' + seconds; clock.children[2].innerHTML = seconds; }

نتحقّق في السطر الموسوم بالرمز (*) من الوقت آنيا لأن القيم التي تَنتُج من مناداة الدالّة setInterval غير موثوقة، حيث يمكن أن يكون فيها بعض التأخير. فيما يلي الدوالّ التي تُستعمل لإدارة الساعة:

let timerId; function clockStart() { // run the clock timerId = setInterval(update, 1000); update(); // (*) } function clockStop() { clearInterval(timerId); timerId = null; }

لاحظ أن الدالّة update()‎، بالإضافة لكونها مُبرمَجةً لتُنفَّذ دوريًا كلّ ثانية داخل الدالّة clockStart()‎، فهي تُنفَّذُ في السطر الموسوم بالرمز (*) في الحين ولا تنتظر حتى تنادي الدالّة setInterval الدالّة update()‎، وذلك لتفادي بقاء الساعة فارغةً طول مدّة تنفيذ الدالّةsetInterval (أي مدّة ثانية واحدة) عند مناداتها أول مرة.

يمكنك الإطلاع على الحل من هنا

إضافة شيفرة HTML إلى قائمة

_الأهمية: 5 _

اكتب الشيفرة التي تُضيف المقطع <li>2</li><li>3</li> بين العنصرين <li> فيما يلي:

<ul id="ul"> <li id="one">1</li> <li id="two">4</li> </ul> الحل

تُعدّ الدالّة insertAdjacentHTML أحسن اختيار حين يتعلق الأمر بإضافة شيفرة HTML في موضع ما من الصفحة.

one.insertAdjacentHTML('afterend', '<li>2</li><li>3</li>'); فرز جدول ما

الأهمية: 5

لدينا الجدول التالي (قد يحوي عددًا أكبر من السطور):

<table> <thead> <tr> <th>Name</th><th>Surname</th><th>Age</th> </tr> </thead> <tbody> <tr> <td>John</td><td>Smith</td><td>10</td> </tr> <tr> <td>Pete</td><td>Brown</td><td>15</td> </tr> <tr> <td>Ann</td><td>Lee</td><td>5</td> </tr> <tr> <td>...</td><td>...</td><td>...</td> </tr> </tbody> </table>

اكتب الشيفرة التي تعيدُ ترتيبه ترتيبًا أبجديًا حسب محتوى العمود name.

الحل

الحل عبارة عن بضعة سطور فقط، ولكننا نحتاج إلى شرح الحيل التي اُستعمِلت فيه. ويكون باتباع خطوات الخوارزمية التالية:

  1. استخراج سطور الجدول من العنصر tbody.
  2. ترتيبها حسب محتوى العمود الأول name ترتيبًا أبجديًا.
  3. إضافة العُقد حسب الترتيب الجديد باستعمال append(sortedRows)‎.

لستَ مضطرًا لحذف العناصر<tr>، عليك فقط إعادة إضافتها حيث تنتقل آليًا من مواضعها الأصلية إلى المواضع الجديدة بعد ترتيبها.

ملاحظة: العنصر tbody من الجدول مذكورٌ صراحةً في شيفرة HTML ولكن حتى وإن لم يُذكر صراحةً، فهو دائمًا موجود في بنية DOM.

يمكنك الإطلاع على الحل من هنا

ترجمة -وبتصرف- للفصل Modifying the document من كتاب Browser: Document, Events, Interfaces

44,635 أهمية صفحة إعدادات القراءة في ووردبريس وكيفية ضبطها

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

إعدادات القراءة

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

ملاحظة: هناك إضافات تعمل على التحكم بطريقة عرض المقالات، وواجهات الموقع، يمكنها التأثير على طريقة عمل هذه الميزة.

  • آخر مقالاتك: حدد هذا الخيار إذا كنت ترغب بإظهار مقالاتك الأخيرة على واجهة مدونتك، وتذكر أن عدد المقالات المعروضة يمكن التحكم به عبر ضبط الحد الأقصى لعدد المقالات بالصفحة.

  • صفحة ثابتة (اختر من التالي): حدد هذا الخيار لعرض صفحة ساكنة محددة كواجهة لموقعك، ويمكنك من هنا اختيار الصفحة التي ستعرض مقالات موقعك أيضًا؛ لكن تذكر أن إعدادات صفحة الواجهة، وصفحة المقالات لا يمكن أن تحتوي على نفس القيمة.

  • الصفحة الرئيسية: اختر صفحة محددة من بين خيارات القائمة المنسدلة لتحديد الصفحة التي ستظهر كواجهة لموقعك. إن لم تحدد إحدى هذه الصفحات، فسيعرض ووردبريس تلقائيًا مقالاتك على كل من صفحة الواجهة، وصفحة المقالات التي أعددتها سابقًا. إذا رغبت بتصميم صفحة واجهة لقالبك، فلا تسمها home.php، وإلا واجهتك مشاكل عند عرض قسم المقالات بموقعك، ولتفادي هذا اختر أي اسم آخر عدا home.php.

  • صفحة المقالات: اختر اسم الصفحة التي ستحتوي مقالاتك من بين خيارات القائمة المنسدلة. إذا لم تحدد خيارًا ما؛ فلن يمكن عرض مقالاتك إلا عبر خيارات التنقل الأخرى، مثل: التصنيفات، والوسوم، وصفحات الأرشيف، وعبر التقويم. حتى لو كانت الصفحة المحددة محمية بكلمة مرور، لن يُطلب من زوارك إدخالها عند استعراض صفحة المقالات، كما سيتجاهل ووردبريس أي قالب مخصص لهذه الصفحة، وسيعتمد على ملف index.php، أو home.php، إذا وجد طريقة للتحكم بطريقة عرض المقالات.

توجّه إلى دليل إنشاء واجهة موقعك لمزيد من التفاصيل عن هذه النقطة.

عدد الموضوعات المعروضة في الصفحة
  • [س] مقالة: أدخل عدد المقالات التي ترغب بعرضها لكل صفحة على موقعك.
الحد الأقصى لأحدث خلاصات التغذية
  • [س] مقالة: أدخل عدد المقالات التي سيراها المستخدمين عند تحميلهم لتغذية خلاصات موقعك.
ما سيظهر من كل مقال في الخلاصة

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

  • النص بالكامل: انقر على هذا الخيار لعرض كامل المحتوى من كل مقال.

  • ملخص: حدد هذا الخيار لعرض ملخص للمقال، ويمكن لهذا الخيار تقليل استهلاك البيانات على نحو ملحوظ.

ترميز الصفحات، والملقمات الإخبارية (ملغي منذ إصدار 3.5)

أدخل ترميز المحارف لتحديد خيارات اللغة التي ستستخدمها أنت، وبقية المحررين، ومن سيُعلِق على مقالاتك. الإعداد الافتراضي، والآمن أيضًا، هو "UTF-8" باعتبار دعمه لعدد كبير من اللغات. إذا رغبت في استخدام ترميز آخر (لأسباب أخرى، مثل: استيراد محتوى يستخدم ترميز مختلف)، حدد ذلك الترميز هنا، وانتبه عند التعامل مع هذه الإعدادات؛ إذ يمكنها تغيير طريقة عرض المعلومات على موقعك. ولمزيد من المعلومات المُفصلة عن ترميز المحارف راجع مقال ويكيبيديا عن تشفير الرموز.

الظهور لمحركات البحث (مستحدث منذ إصدار 3.5)

ملاحظة: حُذفت صفحة إعدادات الخصوصية من ووردبريس منذ إصدار 3.5.0

حدد خيار منع محركات البحث من أرشفة هذا الموقع، بطلب عدم فهرسته من محركات البحث. عند تحديد هذا الخيار ستحدث العملية التالية:

  • سيتسبب هذا الخيار بتوليد السطر التالي:
<meta name='robots' content='noindex,nofollow' />

وذلك في قسم <head> </head> الخاص بالكود المصدري لموقعك، مما يتسبب بتجاهل عناكب محركات البحث لموقعك.

  • يتسبب هذا الخيار بإرسال استجابة من ملف robots.txt تتضمن:
User-agent: * Disallow: /

ملاحظة: ستعمل النقاط سابقة الذكر، فقط إذا كان ووردبريس مثبتًا على مجلد الجذر الخاص بخادمك، وفي ظل عدم وجود ملف robots.txt.

  • سيُوقف هذا الخيار إرسال النبضات إلى خدمة ping-o-matic، وبقية خدمات تحديث المحتوى المُدرجة بصفحة إعدادات الكتابة في قسم الإعدادات بلوحة التحكم، ويحدث ذلك عبر حذف تلك الخدمات من القائمة، من قِبل دالّة privacy_ping_filter()‎. أُضيف هذا المُرشّح إلى المُرشحات الافتراضية من قِبل الدالة التالية:
add_filter('option_ping_sites','privacy_ping_filter')
  • عندما تحاول دالة generic_pingfunction الحصول على قيمة خيار ping_sites، سيمنعها ذلك المُرشح من الاستجابة، بتوفير أي معلومة.
  • ستُخفي هذه العملية خيار خدمات تحديث المحتوى بالكامل من صفحة إعدادات الكتابة، في قسم الإعدادات، بلوحة التحكم، مع عرض رسالة "لا يُعلم ووردبريس أي من خدمات تحديث المحتوى بسبب إعدادات خصوصية موقعك."

يسمح هذا الخيار بتصفح موقعك من قِبل الزوار الاعتيادين.

ملاحظة: لا يوجد من بين تلك الخيارات ما يمنع الوصول إلى موقعك، كما ستعتمد هذه العملية على احترام محركات البحث لطلب تجاهل الفهرسة.

حفظ التغييرات

انقر على زر حفظ التغييرات للتأكد من حفظ جميع التعديلات التي أجريتها على الإعدادات، في قاعدة بيانات موقعك؛ وسيظهر صندوق تأكيد أعلى الصفحة يخبرك بحفظ تلك التعديلات.

ترجمة -وبتصرف- للمقال Settings Reading Screen من موقع WordPress.org

44,634 لماذا يجب منح ووردبريس مجلدها الخاص؟

يريد الكثير من الأشخاص تشغيل ووردبريس على جذر موقعهم، ليصبح عنوان مدونتهم، (http://example.com) على سبيل المثال، لكنهم لا يرغبون بأن يزدحم مسار خادمهم الرئيسي بالكثير من ملفات ووردبريس البرمجية. لحسن الحظ يسمح ووردبريس بتثبيت النظام داخل مجلدات فرعية والوصول إلى الموقع من نفس العنوان الرئيسي.

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

ملاحظة إلى مطوري القوالب، والإضافات: لن يؤثر هذا على توافق برمجياتك مع ووردبريس، إذ ستظل القوالب، والإضافات موجودةً في مجلد wp-content كما هو معتاد.

نقل النظام المثبت على الجذر إلى مسار مخصص

لنفترض أنك ثبتَّ ووردبريس على جذر عنوان example.com، يمكنك اختيار إحدى الطريقتين لنقل نظام ووردبريس إلى مجلد فرعي:

  1. بدون تغيير عنوان الموقع، أي سيظل عنوان مدونتك example.com
  2. عبر تغيير عنوان الموقع، ستُعيد هذه الطريقة توجيه عنوان الموقع إلى example.com/، اسم المجلد الفرعي.
الطريقة الأولى: بدون تغيير عنوان الموقع
  1. بعد تثبيت ووردبريس على مجلد الجذر، بخادم موقعك، انقل جميع الملفات من مجلد الجذر إلى مجلد فرعي.

  2. أنشئ ملف htaccess.، على مجلد الجذر، وضع المحتوى، أدناه، داخل ذلك الملف (تذكر تغيير العنوان example.com، واسم المجلد الفرعي my_subdir):

<IfModule mod_rewrite.c> RewriteEngine on RewriteCond %{HTTP_HOST} ^(www.)?example.com$ RewriteCond %{REQUEST_URI} !^/my_subdir/ RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ /my_subdir/$1 RewriteCond %{HTTP_HOST} ^(www.)?example.com$ RewriteRule ^(/)?$ my_subdir/index.php [L] </IfModule>

هذا كل ما عليك القيام به.

الطريقة الثانية: عبر تغيير عنوان الموقع عملية النقل

ملاحظة: إذ ثبتَّ ووردبريس مسبقًا على مجلد فرعي، فبعض الخطوات التالية قد أُنجزت تلقائيًا بالفعل.

  1. أنشئ مجلدًا جديدًا لتخزين ملفات ووردبريس الرئيسية (سنستخدم wordpress/ كاسم المجلد الفرعي في مثالنا هذا). في حالة استخدام سطر أوامر لينكس، استخدم أمر mkdir من على مجلد الجذر، لإنشاء مجلد جديد؛ كما قد ترغب بمنح Apache صلاحيات الوصول اللازمة للمجلد الفرعي الذي أنشأته عبر أوامر chown apache.

  2. انتقل إلى صفحة الإعدادات العامة، داخل قسم الإعدادات، في لوحة التحكم بووردبريس.

  3. عدّل قيمة حقل عنوان ووردبريس (URL)، إلى عنوان المجلد الفرعي الذي يحتوي على ملفات ووردبريس الرئيسية (أي http://example.com/wordpress في مثالنا).

  4. غيّر رابط عنوان الموقع (URL)، إلى عنوان مجلد الجذر (أي http://example.com في مثالنا)

  5. انقر على زر حفظ التغييرات، ولا تقلق بشأن رسائل الخطأ التي ستظهر، واستمر باتباع الخطوات التالية.

  6. انقل ملفات ووردبريس الرئيسية من مجلد الجذر، إلى المجلد الفرعي.

  7. انسخ (لا تنقل) ملفي index.php، وhtaccess.، من المجلد الذي يحتوي على ملفات ووردبريس، إلى مجلد الجذر الخاص بموقعك. عادةً ما يكون ملف htaccess. سابق الذكر مخفيًا، لذا يجدر بك إعداد برنامج FTP الذي تستخدمه ليعرض الملفات المخفية في خادمك. إذا لم تكن تستخدم ميزة تجميل الروابط، فقد لا تملك ملف htaccess.، وإذا كنت تستضيف ووردبريس على خادم ويندوز، وتستخدم ميزة تجميل الروابط، فستجد ملف web.config عوضًا عن htaccess. في مجلد ووردبريس الخاص بك. فيما يتعلق بملف index.php فإن الإرشادات تبقى كما هي، انسخ، ولا تنقل ملف index.php إلى مجلد الجذر؛ من ناحية أخرى، فإن التعامل مع ملف web.config يختلف عن htaccess.، فهنا يجب عليك نقل الملف (وليس نسخه) إلى مجلد الجذر.

  8. افتح ملف index.php الموجود في مجلد الجذر باستخدام محرر نصّي.

  9. غيّر القيم التالية قبل حفظ الملف. غيّر السطر الذي يحتوي على ما يلي:

(require( dirname( __FILE__ ) . '/wp-blog-header.php' ))

إلى التالي، مستخدمًا اسم مجلد ووردبريس، الذي يحتوي على ملفات ووردبريس الرئيسية:

(require( dirname( __FILE__ ) . '/wordpress/wp-blog-header.php' ))
  1. سجّل الدخول إلى العنوان الجديد، والذي قد يكون (http://example.com/wordpress/wp-admin/).

  2. إذا أعددت ميزة تجميل روابط ووردبريس، انتقل إلى صفحة إعدادات الروابط لتحديث هيكل الروابط الجديد. سيُحدِّث ووردبريس تلقائيًا ملف htaccess.، طالما امتلك صلاحيات الوصول اللازمة. إذا لم يستطع ووردبريس الكتابة إلى ملف htaccess. الخاص بك، فسيعرض لك صلاحيات الوصول الجديدة التي يطلبها، وسيكون عليك نسخها يدويًا إلى ملف htaccess.، والموجود في نفس المجلد الذي يحتوي على ملف index.php الرئيسي.

تعديل ملف htaccess.

قد يرغب بعض المستخدمين في تثبيت إصدارات ووردبريس مختلفة، على مجلدات فرعية، مع مراعاة استخدام موقعهم لآخر الإصدارات بشكل افتراضي؛ في تلك الحالة ثبِّت ووردبريس في مجلد فرعي، وأضف التعليمات التالية إلى ملف htaccess.، الموجود بمجلد الجذر، مع تعديل الأجزاء المتعلقة باسم الموقع، والمجلد الفرعي حسب الحاجة:

RewriteEngine On RewriteCond %{HTTP_HOST} ^(www.)?example.com$ RewriteRule ^(/)?$ my_subdir[L]

الآن سيتوجّه المستخدمين تلقائيًا إلى المجلد الفرعي الذي حددته، عندما يرغبون بالذهاب إلى عنوان موقعك الرئيسي (example.com).

ملاحظة: أتت التعليمات البرمجية السابقة من دليل شركة Site 5 بعنوان: كيف تعيد توجيه عنوان موقعك إلى مجلد فرعي باستخدام ملف htaccess.

نقل مجلدات ووردبريس محددة

تشرح الروابط التالية كيفية تغيير مجلدات محددة، ضمن نظام ووردبريس:

وبهذا نكون قد ذكرنا كيفية القيام بمنح ووردبريس لمجلد خاص به، من خلال نقل النظام المثبت على الجذر إلى مسار مخصص، وذلك بطريقتين؛ إلى جانب كيفية نقل مجلدات الووردبريس المحددة وتغييرها.

ترجمة -وبتصرف- للمقال Giving WordPress Its Own Directory من موقع WordPress.org

الصفحات

أنت هنا