وضع التقويمات مع وضع إمكانية الوصول والتدويل في الاعتبار

وضع التقويمات مع وضع إمكانية الوصول والتدويل في الاعتبار

يُظهر إجراء بحث سريع هنا على CSS-Tricks عدد الطرق المختلفة المتاحة للتعامل مع التقويمات. يظهر البعض كيف يمكن لشبكة CSS إنشاء التخطيط بكفاءة. بعض المحاولات جلب البيانات الفعلية في هذا المزيج. بعض تعتمد على إطار عمل للمساعدة في إدارة الدولة.

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

يخشى العديد من المطورين أن يكون Date() موضوع والتشبث بالمكتبات القديمة مثل moment.js. ولكن في حين أن هناك العديد من "المشاكل" عندما يتعلق الأمر بالتواريخ والتنسيق ، فإن JavaScript لديها الكثير من واجهات برمجة التطبيقات الرائعة والأشياء للمساعدة!

شبكة تقويم يناير 2023.
وضع التقويمات مع وضع إمكانية الوصول والتدويل في الاعتبار

لا أريد إعادة إنشاء العجلة هنا ، لكنني سأوضح لك كيف يمكننا الحصول على تقويم جيد مع Vanilla JavaScript. سننظر في إمكانية الوصول، باستخدام الترميز الدلالي والصديق لقارئ الشاشة <time> -العلامات - وكذلك تدويل و التنسيق، وذلك باستخدام Intl.Locale, Intl.DateTimeFormat و Intl.NumberFormat-واجهات برمجة التطبيقات.

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

أولاً ، التسمية

ماذا يجب أن نسمي مكون التقويم لدينا؟ في لغتي الأم ، سيسمى "عنصر kalender" ، لذلك دعونا نستخدم ذلك ونختصره إلى "Kal-El" - المعروف أيضًا باسم اسم سوبرمان على كوكب كريبتون.

دعنا ننشئ وظيفة لتستمر الأمور:

function kalEl(settings = {}) { ... }

هذه الطريقة سوف تجعل شهر واحد. في وقت لاحق سوف نسمي هذه الطريقة من [...Array(12).keys()] لتقديم سنة كاملة.

البيانات الأولية والتدويل

من الأشياء الشائعة التي يقوم بها التقويم العادي عبر الإنترنت تحديد التاريخ الحالي. لذلك دعونا ننشئ مرجعًا لذلك:

const today = new Date();

بعد ذلك ، سننشئ "كائن التكوين" الذي سنقوم بدمجه مع الخيار الاختياري settings موضوع الطريقة الأساسية:

const config = Object.assign( { locale: (document.documentElement.getAttribute('lang') || 'en-US'), today: { day: today.getDate(), month: today.getMonth(), year: today.getFullYear() } }, settings
);

نتحقق مما إذا كان عنصر الجذر (<html>) يحتوي على lang-نسبه مع محلي معلومات؛ خلاف ذلك ، سوف نعود إلى استخدام en-US. هذه هي الخطوة الأولى نحو تدويل التقويم.

نحتاج أيضًا إلى تحديد الشهر الذي سيتم عرضه في البداية عند تقديم التقويم. لهذا السبب قمنا بتوسيع نطاق config كائن مع الأساسي date. بهذه الطريقة ، إذا لم يتم تقديم تاريخ في ملف settings كائن ، سنستخدم today المرجع بدلاً من ذلك:

const date = config.date ? new Date(config.date) : today;

نحتاج إلى مزيد من المعلومات لتنسيق التقويم بشكل صحيح بناءً على اللغة. على سبيل المثال ، قد لا نعرف ما إذا كان اليوم الأول من الأسبوع هو الأحد أو الاثنين ، بناءً على الإعدادات المحلية. إذا كانت لدينا المعلومات ، فهذا رائع! ولكن إذا لم يكن الأمر كذلك ، فسنقوم بتحديثه باستخدام ملف Intl.Locale API. يحتوي API على ملف weekInfo موضوع التي ترجع firstDay خاصية تمنحنا بالضبط ما نبحث عنه دون أي متاعب. يمكننا أيضًا الحصول على أيام الأسبوع المخصصة لـ weekend:

if (!config.info) config.info = new Intl.Locale(config.locale).weekInfo || { firstDay: 7, weekend: [6, 7] };

مرة أخرى ، نقوم بإنشاء احتياطات. "اليوم الأول" من الأسبوع لـ en-US هو يوم الأحد ، لذلك يتم تعيين القيمة الافتراضية على 7. هذا أمر محير بعض الشيء ، مثل getDay طريقة في JavaScript بإرجاع الأيام كـ [0-6]، حيث 0 هو الأحد ... لا تسألني لماذا. عطلات نهاية الأسبوع هي السبت والأحد ، وبالتالي [6, 7].

قبل أن يكون لدينا Intl.Locale API و weekInfo كان من الصعب جدًا إنشاء تقويم دولي بدون العديد من العناصر والمصفوفات التي تحتوي على معلومات حول كل منطقة أو منطقة. في الوقت الحاضر ، الأمر سهل للغاية. إذا مررنا en-GB، تعيد الطريقة:

// en-GB
{ firstDay: 1, weekend: [6, 7], minimalDays: 4
}

في بلد مثل بروناي (ms-BN) ، نهاية الأسبوع هي الجمعة والأحد:

// ms-BN
{ firstDay: 7, weekend: [5, 7], minimalDays: 1
}

قد تتساءل ما هذا minimalDays الملكية. هذا هو الأيام القليلة المطلوبة في الأسبوع الأول من الشهر تحسب على أنها أسبوع كامل. في بعض المناطق ، قد يكون يومًا واحدًا فقط. بالنسبة للآخرين ، قد تكون سبعة أيام كاملة.

بعد ذلك ، سننشئ ملف render أسلوبنا ضمن kalEl-طريقة:

const render = (date, locale) => { ... }

ما زلنا بحاجة إلى مزيد من البيانات للعمل معها قبل تقديم أي شيء:

const month = date.getMonth();
const year = date.getFullYear();
const numOfDays = new Date(year, month + 1, 0).getDate();
const renderToday = (year === config.today.year) && (month === config.today.month);

آخر واحد هو أ Boolean أن يتحقق ما إذا كان today موجود في الشهر الذي نحن بصدد تقديمه.

الترميز الدلالي

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

مُجمّع التقويم

أولاً ، لدينا الغلاف غير الدلالي: <kal-el>. هذا جيد لأنه لا يوجد دلالي <calendar> علامة أو أي شيء من هذا القبيل. إذا لم ننشئ عنصرًا مخصصًا ، <article> قد يكون العنصر الأنسب لأن التقويم يمكن أن يقف على صفحته الخاصة.

أسماء الأشهر

<time> سيكون عنصرًا كبيرًا بالنسبة لنا لأنه يساعد في ترجمة التواريخ إلى تنسيق يمكن لقراء الشاشة ومحركات البحث تحليله بشكل أكثر دقة واتساق. على سبيل المثال ، إليك كيف يمكننا نقل "يناير 2023" في الترميز الخاص بنا:

<time datetime="2023-01">January <i>2023</i></time>

أسماء الأيام

قد يكون الصف الموجود أعلى تواريخ التقويم الذي يحتوي على أسماء أيام الأسبوع معقدًا. إنه مثالي إذا كان بإمكاننا كتابة الأسماء الكاملة لكل يوم - على سبيل المثال الأحد ، الاثنين ، الثلاثاء ، وما إلى ذلك - ولكن يمكن أن يستغرق ذلك مساحة كبيرة. لذا ، فلنختصر الأسماء الآن داخل <ol> حيث يكون كل يوم <li>:

<ol> <li><abbr title="Sunday">Sun</abbr></li> <li><abbr title="Monday">Mon</abbr></li> <!-- etc. -->
</ol>

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

<ol> <li> <abbr title="S">Sunday</abbr> </li>
</ol>

... نحصل على الأسماء الكاملة بشكل افتراضي. يمكننا بعد ذلك "إخفاء" الاسم الكامل عند نفاد المساحة وعرض ملف title السمة بدلاً من ذلك:

@media all and (max-width: 800px) { li abbr::after { content: attr(title); }
}

لكننا لا نذهب بهذه الطريقة لأن Intl.DateTimeFormat يمكن أن تساعد API هنا أيضًا. سنصل إلى ذلك في القسم التالي عندما نغطي العرض.

أرقام اليوم

يحصل كل تاريخ في شبكة التقويم على رقم. كل رقم هو عنصر قائمة (<li>) في قائمة مرتبة (<ol>) والمضمنة <time> العلامة يلتف الرقم الفعلي.

<li> <time datetime="2023-01-01">1</time>
</li>

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

أرقام الأسبوع

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

سنستخدم ملف data-weeknumber كخطاف تصميم وقم بتضمينها في الترميز لكل تاريخ يمثل تاريخ الأسبوع الأول.

<li data-day="7" data-weeknumber="1" data-weekend=""> <time datetime="2023-01-08">8</time>
</li>

أداء

دعنا نحصل على التقويم على الصفحة! نحن نعلم ذلك بالفعل <kal-el> هو اسم العنصر المخصص لدينا. أول شيء نحتاج إلى تهيئته هو ضبط firstDay على ذلك ، لذلك يعرف التقويم ما إذا كان الأحد أو أي يوم آخر هو اليوم الأول من الأسبوع.

<kal-el data-firstday="${ config.info.firstDay }">

سنكون باستخدام قالب الحرف لتقديم الترميز. لتنسيق التواريخ لجمهور دولي ، سنستخدم ملف Intl.DateTimeFormat API ، مرة أخرى باستخدام ملف locale حددنا في وقت سابق.

الشهر والسنة

عندما نتصل ب month، يمكننا تحديد ما إذا كنا نريد استخدام ملف long الاسم (مثل فبراير) أو short الاسم (مثل فبراير). دعنا نستخدم long الاسم لأنه العنوان أعلى التقويم:

<time datetime="${year}-${(pad(month))}"> ${new Intl.DateTimeFormat( locale, { month:'long'}).format(date)} <i>${year}</i>
</time>

أسماء أيام الأسبوع

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

Intl.DateTimeFormat([locale], { weekday: 'long' })
Intl.DateTimeFormat([locale], { weekday: 'short' })

دعنا نصنع طريقة مساعدة صغيرة تجعل من الأسهل قليلاً استدعاء كل منها:

const weekdays = (firstDay, locale) => { const date = new Date(0); const arr = [...Array(7).keys()].map(i => { date.setDate(5 + i) return { long: new Intl.DateTimeFormat([locale], { weekday: 'long'}).format(date), short: new Intl.DateTimeFormat([locale], { weekday: 'short'}).format(date) } }) for (let i = 0; i < 8 - firstDay; i++) arr.splice(0, 0, arr.pop()); return arr;
}

إليك كيفية استدعاء ذلك في النموذج:

<ol> ${weekdays(config.info.firstDay,locale).map(name => ` <li> <abbr title="${name.long}">${name.short}</abbr> </li>`).join('') }
</ol>

أرقام اليوم

وأخيرًا ، الأيام ، ملفوفة في <ol> جزء:

${[...Array(numOfDays).keys()].map(i => { const cur = new Date(year, month, i + 1); let day = cur.getDay(); if (day === 0) day = 7; const today = renderToday && (config.today.day === i + 1) ? ' data-today':''; return ` <li data-day="${day}"${today}${i === 0 || day === config.info.firstDay ? ` data-weeknumber="${new Intl.NumberFormat(locale).format(getWeek(cur))}"`:''}${config.info.weekend.includes(day) ? ` data-weekend`:''}> <time datetime="${year}-${(pad(month))}-${pad(i)}" tabindex="0"> ${new Intl.NumberFormat(locale).format(i + 1)} </time> </li>`
}).join('')}

دعنا نقسم ذلك:

  1. نقوم بإنشاء مصفوفة "وهمية" ، بناءً على متغير "عدد الأيام" ، والذي سنستخدمه للتكرار.
  2. نقوم بإنشاء ملف day متغير لليوم الحالي في التكرار.
  3. نصلح التناقض بين Intl.Locale API و getDay().
  4. إذا كان day مساوي ل today، نضيف أ data-* السمة.
  5. أخيرًا ، نعيد <li> عنصر كسلسلة مع بيانات مدمجة.
  6. tabindex="0" يجعل العنصر قابلاً للتركيز ، عند استخدام التنقل بلوحة المفاتيح ، بعد أي قيم جدولة موجبة (ملاحظة: يجب عليك ذلك أبدا تضيف إيجابي قيم tabindex)

إلى "لوحة" الأرقام في ال datetime السمة ، نستخدم طريقة مساعدة صغيرة:

const pad = (val) => (val + 1).toString().padStart(2, '0');

رقم الأسبوع

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

function getWeek(cur) { const date = new Date(cur.getTime()); date.setHours(0, 0, 0, 0); date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7); const week = new Date(date.getFullYear(), 0, 4); return 1 + Math.round(((date.getTime() - week.getTime()) / 86400000 - 3 + (week.getDay() + 6) % 7) / 7);
}

أنا لم أكتب هذا getWeek-طريقة. إنها نسخة نظيفة من هذا البرنامج النصي.

وهذا كل شيء! شكرا ل Intl.Locale, Intl.DateTimeFormat و Intl.NumberFormat واجهات برمجة التطبيقات ، يمكننا الآن ببساطة تغيير ملف lang-سمة <html> عنصر لتغيير سياق التقويم بناءً على المنطقة الحالية:

شبكة تقويم يناير 2023.
de-DE
شبكة تقويم يناير 2023.
fa-IR
شبكة تقويم يناير 2023.
zh-Hans-CN-u-nu-hanidec

تصميم التقويم

قد تتذكر كيف أن كل الأيام هي يوم واحد فقط <ol> مع عناصر القائمة. لتصميمها في تقويم قابل للقراءة ، نتعمق في عالم CSS Grid الرائع. في الواقع ، يمكننا إعادة توظيف نفس الشبكة من نموذج تقويم بداية هنا على CSS-Tricks، ولكن تم تحديث سميدج بامتداد :is() الزائفة العلائقية لتحسين الشفرة.

لاحظ أنني أقوم بتعريف متغيرات CSS القابلة للتكوين على طول الطريق (وأبدأها بـ ---kalel- لتجنب النزاعات).

kal-el :is(ol, ul) { display: grid; font-size: var(--kalel-fz, small); grid-row-gap: var(--kalel-row-gap, .33em); grid-template-columns: var(--kalel-gtc, repeat(7, 1fr)); list-style: none; margin: unset; padding: unset; position: relative;
}
شبكة تقويم من سبعة أعمدة مع عرض خطوط الشبكة.
وضع التقويمات مع وضع إمكانية الوصول والتدويل في الاعتبار

دعنا نرسم حدودًا حول أرقام التاريخ للمساعدة في فصلها بصريًا:

kal-el :is(ol, ul) li { border-color: var(--kalel-li-bdc, hsl(0, 0%, 80%)); border-style: var(--kalel-li-bds, solid); border-width: var(--kalel-li-bdw, 0 0 1px 0); grid-column: var(--kalel-li-gc, initial); text-align: var(--kalel-li-tal, end); }

تعمل الشبكة المكونة من سبعة أعمدة بشكل جيد عندما يكون اليوم الأول من الشهر أيضا اليوم الأول من الأسبوع للإعدادات المحلية المحددة). لكن هذا هو الاستثناء وليس القاعدة. في معظم الأوقات ، سنحتاج إلى تحويل اليوم الأول من الشهر إلى يوم آخر من أيام الأسبوع.

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

تذكر كل ما هو إضافي data-* الصفات التي حددناها عند كتابة الترميز الخاص بنا؟ يمكننا ربطها لتحديث عمود الشبكة (--kalel-li-gc) يتم وضع رقم التاريخ الأول من الشهر في:

[data-firstday="1"] [data-day="3"]:first-child { --kalel-li-gc: 1 / 4;
}

في هذه الحالة ، نمتد من عمود الشبكة الأول إلى عمود الشبكة الرابع - والذي سيقوم تلقائيًا "بدفع" العنصر التالي (اليوم 2) إلى عمود الشبكة الخامس ، وهكذا دواليك.

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

[data-today] { --kalel-day-bdrs: 50%; --kalel-day-bg: hsl(0, 86%, 40%); --kalel-day-hover-bgc: hsl(0, 86%, 70%); --kalel-day-c: #fff;
}

تعجبني فكرة تصميم أرقام التواريخ لعطلات نهاية الأسبوع بشكل مختلف عن أيام الأسبوع. سأستخدم لونًا ضارب إلى الحمرة لتصميم هؤلاء. لاحظ أنه يمكننا الوصول إلى :not() فئة زائفة لتحديدها مع ترك التاريخ الحالي بمفرده:

[data-weekend]:not([data-today]) { --kalel-day-c: var(--kalel-weekend-c, hsl(0, 86%, 46%));
}

أوه ، ودعونا لا ننسى أرقام الأسبوع التي تسبق رقم التاريخ الأول من كل أسبوع. استخدمنا data-weeknumber السمة في الترميز لذلك ، ولكن لن يتم عرض الأرقام فعليًا ما لم نكشفها باستخدام CSS ، وهو ما يمكننا القيام به على ::before العنصر الزائف:

[data-weeknumber]::before { display: var(--kalel-weeknumber-d, inline-block); content: attr(data-weeknumber); position: absolute; inset-inline-start: 0; /* additional styles */
}

لقد انتهينا من الناحية الفنية في هذه المرحلة! يمكننا تقديم شبكة تقويم تعرض تواريخ الشهر الحالي ، مع استكمال الاعتبارات المتعلقة بترجمة البيانات حسب المنطقة المحلية ، والتأكد من أن التقويم يستخدم دلالات مناسبة. وكل ما استخدمناه كان Vanilla JavaScript و CSS!

لكن لنأخذ هذا خطوة أخرى أيضا...

تقديم سنة كاملة

ربما تحتاج لعرض سنة كاملة من التواريخ! لذلك ، بدلاً من عرض الشهر الحالي ، قد ترغب في عرض كافة شبكات الشهر للسنة الحالية.

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

بسيطة مثل استدعاء render- الطريقة 12 مرة ، وقم فقط بتغيير العدد الصحيح لـ month - i:

[...Array(12).keys()].map(i => render( new Date(date.getFullYear(), i, date.getDate()), config.locale, date.getMonth() )
).join('')

من المحتمل أن يكون إنشاء غلاف رئيسي جديد للسنة المقدمة فكرة جيدة. كل شبكة تقويم هي <kal-el> عنصر. دعنا نسمي الغلاف الأصلي الجديد <jor-el>، حيث Jor-El هو اسم والد Kal-El.

<jor-el id="app" data-year="true"> <kal-el data-firstday="7"> <!-- etc. --> </kal-el> <!-- other months -->
</jor-el>

يمكننا استخدام <jor-el> لإنشاء شبكة لشبكاتنا. حتى ميتا!

jor-el { background: var(--jorel-bg, none); display: var(--jorel-d, grid); gap: var(--jorel-gap, 2.5rem); grid-template-columns: var(--jorel-gtc, repeat(auto-fill, minmax(320px, 1fr))); padding: var(--jorel-p, 0);
}

العرض النهائي

المكافأة: تقويم النثار

قرأت كتابا ممتازا اسمه صنع وكسر الشبكة في ذلك اليوم وعثرت على هذا "ملصق رأس السنة الجديدة" الجميل:

عمل التقاويم مع إمكانية الوصول والتدويل في الاعتبار ذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.
المصدر صنع وكسر الشبكة (الإصدار الثاني) بواسطة تيموثي سمارة

اعتقدت أنه يمكننا القيام بشيء مماثل دون تغيير أي شيء في HTML أو JavaScript. لقد سمحت لي بتضمين الأسماء الكاملة لأشهر ، والأرقام بدلاً من أسماء الأيام ، لجعلها أكثر قابلية للقراءة. يتمتع!

الطابع الزمني:

اكثر من الخدع المغلق