ساخت تقویم با قابلیت دسترسی و بین المللی سازی در ذهن

ساخت تقویم با قابلیت دسترسی و بین المللی سازی در ذهن

گره منبع: 2009236

انجام یک جستجوی سریع در اینجا در CSS-Tricks نشان می دهد که چند راه مختلف برای نزدیک شدن به تقویم ها وجود دارد. برخی نشان می دهند که چگونه CSS Grid می تواند طرح بندی را به طور موثر ایجاد کند. برخی تلاش می کنند داده های واقعی را به ترکیب بیاورید. برخی از بر یک چارچوب تکیه کنید برای کمک به مدیریت دولتی

هنگام ساختن یک جزء تقویم ملاحظات زیادی وجود دارد - بسیار بیشتر از آنچه در مقالاتی که من پیوند دادم پوشش داده شده است. اگر به آن فکر کنید، تقویم‌ها مملو از نکات ظریف هستند، از رسیدگی به مناطق زمانی و قالب‌های تاریخ گرفته تا محلی‌سازی و حتی اطمینان از اینکه تاریخ‌ها از یک ماه به ماه دیگر می‌روند... و این قبل از این است که بسته به مکان تقویم، به ملاحظات دسترسی و طرح‌بندی اضافی بپردازیم. نمایش داده می شود و چه چیز دیگری.

بسیاری از توسعه دهندگان از این می ترسند Date() هدف و به کتابخانه های قدیمی تر مانند moment.js. اما در حالی که در مورد تاریخ‌ها و قالب‌بندی «گوچا»های زیادی وجود دارد، جاوا اسکریپت APIهای جالب و چیزهای زیادی برای کمک به شما دارد!

شبکه تقویم ژانویه 2023.

من نمی‌خواهم چرخ را در اینجا دوباره ایجاد کنم، اما به شما نشان می‌دهم که چگونه می‌توانیم یک تقویم خوب با جاوا اسکریپت وانیلی داشته باشیم. ما بررسی خواهیم کرد دسترسی، با استفاده از نشانه گذاری معنایی و صفحه خوان مناسب <time> برچسب ها - و همچنین بین المللی و قالب بندی، با استفاده از Intl.Locale, Intl.DateTimeFormat و Intl.NumberFormat-API ها

به عبارت دیگر، ما یک تقویم می‌سازیم... فقط بدون وابستگی‌های اضافی که معمولاً در آموزش‌هایی مانند این استفاده می‌شوند، و با برخی از تفاوت‌های ظریف که معمولاً نمی‌بینید. و، در این فرآیند، امیدوارم شما قدردانی جدیدی از چیزهای جدیدتری که جاوا اسکریپت می‌تواند انجام دهد به دست آورید، در حالی که ایده‌ای از انواع چیزهایی که هنگام کنار هم قرار دادن چیزی شبیه به این به ذهن من می‌رسد، به دست آورید.

اول نام بردن

جزء تقویم خود را چه بنامیم؟ در زبان مادری من، "عنصر 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 هدف که a را برمی گرداند firstDay دارایی که دقیقاً همان چیزی را که به دنبال آن هستیم بدون هیچ زحمتی به ما می دهد. ما همچنین می توانیم دریافت کنیم که کدام روزهای هفته به آن اختصاص داده شده است weekend:

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

دوباره، ما عقب‌نشینی ایجاد می‌کنیم. "اولین روز" هفته برای en-US یکشنبه است، بنابراین مقدار پیش فرض آن است 7. این کمی گیج کننده است، زیرا getDay روش در جاوا اسکریپت روزها را به صورت برمی گرداند [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> ممکن است مناسب‌ترین عنصر باشد، زیرا تقویم می‌تواند در صفحه خودش باشد.

نام ماه ها

La <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. اگرچه خیلی معمول نیست، اما نمایش عدد برای یک هفته معین در تقویم برای زمینه های اضافی می تواند خوب باشد. من دوست دارم الان آن را داشته باشم، حتی اگر من از آن استفاده نکنم. اما ما به طور کامل از آن در این آموزش استفاده خواهیم کرد.

ما از a استفاده خواهیم کرد 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-value)

به اعداد را "پد" کنید در 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 API ها، اکنون می توانیم به سادگی آن ها را تغییر دهیم lang-ویژگی از <html> عنصر برای تغییر زمینه تقویم بر اساس منطقه فعلی:

شبکه تقویم ژانویه 2023.
de-DE
شبکه تقویم ژانویه 2023.
fa-IR
شبکه تقویم ژانویه 2023.
zh-Hans-CN-u-nu-hanidec

سبک دادن به تقویم

شاید به یاد بیاورید که چگونه همه روزها فقط یک روز هستند <ol> با آیتم های لیست برای شکل دادن به آنها به یک تقویم خوانا، به دنیای شگفت انگیز CSS Grid می پردازیم. در واقع، ما می‌توانیم از همان شبکه استفاده کنیم یک الگوی تقویم شروع درست در اینجا در CSS-Tricks، اما یک smidge را به روز کرد :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%));
}

اوه، و اعداد هفته را فراموش نکنیم که قبل از اولین شماره تاریخ هر هفته قرار دارند. ما از a استفاده کردیم 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 */
}

ما از نظر فنی در این مرحله تمام شده است! ما می‌توانیم یک شبکه تقویم ارائه کنیم که تاریخ‌های ماه جاری را نشان می‌دهد، با ملاحظاتی برای بومی‌سازی داده‌ها بر اساس محلی، و اطمینان از اینکه تقویم از معنایی مناسب استفاده می‌کند. و تنها چیزی که استفاده کردیم جاوا اسکریپت وانیلی و 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 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);
}

نسخه ی نمایشی نهایی

پاداش: تقویم شیرینی

یک کتاب عالی خواندم به نام ساخت و شکستن شبکه روز دیگر و به طور تصادفی به این "پوستر سال نو" زیبا برخورد کردم:

منبع: ساخت و شکستن شبکه (نسخه دوم) توسط تیموتی سامارا

من فکر کردم که می‌توانیم کاری مشابه را بدون تغییر چیزی در HTML یا جاوا اسکریپت انجام دهیم. من این اختیار را قائل شده‌ام که برای ماه‌ها نام‌های کامل و به‌جای نام روز، اعداد را درج کنم تا آن را خواناتر کنم. لذت ببرید!

تمبر زمان:

بیشتر از ترفندهای CSS