ساخت یک notification system عالی در svelte js

در این مدتی که با فریمورک svelte کار می کرده ام موقعیت هایی پیش اومده که باید به کاربر notification نشون میدادم (با استفاده از المان های html). راه های مختلفی رو امتحان کردم و در نهایت راهی رو که برای پیاده سازی یک notification system ترجیح میدم پیدا کردم.

در این پست می خوام روشی رو که هربار استفاده می کنم رو با شما هم به اشتراک بزارم.

راه اندازی پروژه

قبل از اینکه یه راست بریم سراغ کد، اول باید یه پروژه svelte رو راه بندازیم. برای اینکار من ترجیح میدم از template رسمی خود svelte استفاده کنم. پس اول بریم و با استفاده از این template یه repo ایجاد کنیم. لینک repo رو در انتهای پست بهتون میدم (مورد توجه کسانی که صبرشون به آخر رسیده). پس اگه کد ها رو اشتباه جایگذاری کردید نگران نباشید.

اول از همه در گیتهاب به svletejs/template مراجعه می کنیم و گزینه Use this template رو میزنیم. سپس می تونیم یه repo ایجاد کنیم

بعد از ساخت repo. باید روی کامپیوتر خودمون پروژه رو clone کنیم:

1git clone https://github.com/Amohammadi2/Svelte-Notification-System-Tutorial.git

و بعد از وارد شدن به فولدر repo دستور زیر رو جهت نصب وابستگی (dependency) های پروژه وارد می کنیم:

1npm install

بعد از اینکه وابستگی های مورد نیاز نصب شدند. app رو تست می کنیم تا ببینیم همه چی OK هست یا نه.

1npm run dev

با زدن دستور بالا اول کل پروژه compile میشه و بعد سرور توسعه rollup راه می افته. اگه تا به این مرحله همه چیز طبق انتظار پیش رفته باشه، می تونید مرورگرتون رو باز کنید و به آدرس localhost:5000 برید.

بعد با این صفحه مواجه میشید:

Hello world خیلی به آدم انرژی میده
Hello world خیلی به آدم انرژی میده

نقشه راه

قبل از اینکه بریم سراغ نوشتن کد ها. بزارید با ویژگی های notification systemـی که می خواهیم بسازیم آشناتون کنم:

  • قابلیت افزودن سه نوع notification مختلف _ success – warning – alert _ البته شما محدود به این سه تا نیستید، شما می تونید هرچقدر که عشقتون کشید به انواع notification هاتون اضافه کنید
  • قابلیت حذف notification توسط کاربر
  • یک timer برای بستن اتوماتیک notification ها به همراه یک animation برای نشون دادن زمان بسته شدن notification به کاربر
  • انیمیشن هایی برای اضافه/پاک شدن notification ها.

برای پیاده سازی هم به دو component نیاز خواهیم داشت:

  • NotificationBox
  • Notification

اولی جا و مکان Notification ها رو مشخص می کنه، دومی هم خود notification ها هستند که شامل یه متن پیام یه رنگ پس زمینه و دکمه بستن و همچنین یک تایمر هستند.

همچنین به یک ماژول مستقل javascript برای برای افزودن Notification نیاز خواهیم داشت که با استفاده از utility های اون ماژول از هر جای کد که خواستیم به سادگی یک Notification به نمایش بزاریم.

برای اینکه یه دید اجمالی از پروژه دستتون بیاد به gif زیر نگاهی بندازید:

XD
XD

خوب دیگه حالا که یه دل سیر portfolio من رو دیدید. می تونیم بریم سراغ پیاده سازی.

پاک سازی!

اولین کاری که باید بکنید اینه که وارد فایل App.svelte در پوشه src بشید و هرچی کد هست رو پاک کنید. شما رو نمی دونم ولی انجام دادن این کار با کد developer های دیگه به من احساس دوباره متولد شدن میده.

حالا دیگه باید پنجره مرورگر شما تبدیل به یک صفحه نقاشی سفید و دست نخورده شده باشه. صفحه ای که باید هنرتون رو در اون به نمایش بزارید!

طراحی notification ها و ساخت component ها

در پوشه src پوشه دیگه ای به نام components بسازید. باز داخل components پوشه دیگه ای به نام notifications بسازید. اینجا همونجایی هست که قراره component هامون رو قرار بدیم.

در نهایت ساختار پوشه های شما باید اینطور باشه

1└───src
1          └───components
1                     └───notifications

حالا در پوشه notifications دو فایل به نام های زیر بسازید:

  • Notification.svelte
  • NotificationBox.svelte

ساخت NotificationBox

اول از همه میریم سراغ NotificationBox.svelte که جا و مکان Notification ها رو در صفحه مشخص می کنه و با وجود اون می تونیم Notification ها رو هر جای صفحه جا بدیم.

همونطور که می بینید زیاد چیز خاصی نیست. z-index هم فقط محض احتیاط به کار رفته که .notif-box به هیچ قیمتی زیر المان های دیگه نره. در component های svelte، کد های css رو بین تگ های style می نویسید و هر جای دیگه از کد (طبق قرار داد بالای style ها) می تونید کد های html تون رو بنویسید. کد های javascript هم بین تگ های script نوشته میشن که به صورت قراردادی در ابتدای فایل قرار میگیرند.

تمام کد های css ای که در NotificationBox.svelte می نویسید فقط مربوط به اون قسمت میشن و قسمت های دیگه رو تحت تاثیر قرار نمیدن. این نامگذاری رو آسون می کنه و باعث میشه که دیگه اشکال های مربوط به selector های همنام و ترکیبات عجیب و غریبشون پیش نیاد.

ساخت Notification

حالا فعلا کارمون با NotificationBox تمام شده. فایل Notification.svelte رو باز کنید. کد های زیر رو در اون بنویسید:

در این component ما دو property میگیریم به نام msg و type. از type برای تعیین نوع notification (مثلا success, alert, warning) و اعمال style های css بر اساس اون استفاده می کنیم. msg هم که به نمایش گذاشته میشه.

نکته: not-progress همون progress-barـی هست که روند پیشرفت timer رو نشون میده. بعدا با یک key frame animation اون رو هم راه می اندازیم.

حالا بیاید کمی css به این فایل اضافه کنیم. میان تگ های style این کد ها رو وارد کنید.

اول از همه عرض notification رو 100% عرض المان والد (parent) در نظر میگیریم و با قرار دادن box-sizing در خط 3 مانع تاثیر padding روی اندازه اصلی میشیم. position اش رو هم به خاطر اینکه بعدا قراره یک progress-bar با position: absolute بهش اضافه کنیم، relative در نظر میگیریم. بقیه (box-shadow) فقط برای خوشگلی notification مونه.

حالا وقتشه که حالت های مختلف notification رو هم با رنگ های پس زمینه مختلف بهش اضافه کنیم:

حالا اگه فرضا مقدار property که اسمش type هست “success” باشه، رنگ پس زمینه سبز برای notification در نظر گرفته خواهد شد (و همچنین درمورد بقیه حالت های alert و warning)

اگه یادتون باشه، notification ما یک دکمه برای بستن داشت با کلاسی به نام dismiss. کمی هم به اون style بدیم بد نیست:

در کد های بالا تنها نکته ای که می خوام بهش اشاره کنم display: inline-block هستش که برای این استفاده میشه که المان های inline مانند block ها قابلیت افزودن margin هم داشته باشند. (نمی دونم چرا وقتی فقط از inline استفاده می کنم دیگه margin ها هیچ اثری ندارند).

الان می تونیم به progress-bar مون یک animation هم اضافه کنیم که روند پیشرفت timer رو مشخص کنه:

توضیحات: .not-progress عرض 100% داره (به واسطه right: 0 و left: 0) ولی در واقع المان after:: چیزیه که قراره با animation عرضش رو به مرور زمان افزایش بدیم.

حالا تقریبا کار های مربوط به طراحی و افزودن html و css تموم شده. برای اینکه یه تست بکنیم. فایل App.svelte رو باز کنید و کد های زیر رو داخلش بنویسید:

از هر نوع notification که دارید تست کنید
از هر نوع notification که دارید تست کنید

تا به الان اگر کد ها رو به درستی وارد کرده باشید همچین نتیجه ای رو خواهید دید:

و واقعا چه زیباست
و واقعا چه زیباست

جاوا اسکریپت وارد میشود!

حالا وقتشه که با استفاده از javascript سیستمی برای افزودن، مدیریت و بستن notification ها اضافه بسازیم.

  • برای مدیریت notification ها به کلاسی به نام NotificationAPI احتیاج داریم که متد هایی static برای افزودن و حذف notification ها داره.
  • برای ذخیره notification ها و دسترسی به اونها از جا های مختلف پروژه نیاز به یک writable store داریم که کار های مربوط به state management رو انجام بده.

ساخت یک store جهت ذخیره Notification ها

در پوشه src پروژه خود پوشه ای به نام stores بسازید و در اون فایلی به نام notifications.js بسازید. داخل فایل این کد ها رو وارد کنید:

حالا ما یه سری notification به صورت hard-code شده داریم. نکته ای که درباره notification هامون وجود داره اینه که من دارم از یک Symbol برای ساخت یک unique id استفاده می کنم. بعدا با استفاده از Symbol موجود در pk می توان تشخیص داد کدوم notification باید حذف بشه.

اگه قبلا با دیتابیس های SQL کار کرده باشید. pk دقیقا کار یک primary key رو انجام میده. اگه درمورد Symbol ها چیزی نمی دونید می تونید به این صفحه مراجعه کنید.

‌گرفتن لیست notification ها به صورت dynamic از store

برای اینکه از لیست notification هامون component های Notification بسازیم. باید از each-block استفاده کنیم (البته این مخصوص svelte js هستش و مثل template ها هستش). کد زیر رو در App.svelte در همونجایی که NotificationBox رو قرار دادید وارد کنید.

در همین 5 خط کد نکات زیادی وجود داره که شاید براتون سوال باشه. اولا syntax کلی `each` در svelte اینطوریه:

1234{#each list as item (optional_key)}
    <!-- now a variable called item is accessible in this scope -->
    <DoStuff /> ...
{/each}
  • در بالا list همون آرایه ای هست که می خواهیم item هایی رو ازش استخراج کنیم.
  • قسمت optinal_key برای ساختن keyed-each-block ها استفاده میشه.البته این key در حالت عادی وجودش اختیاری هست اما در این مورد ما بهش نیاز داریم، چرا؟ چون قراره روی لیست notification هامون عملیات افزودن و حذف کردن رو انجام بدیم. در ادامه بیشتر راجع بهش توضیح میدم، چون درکش اول برای من هم سخت بود. توجه داشته باشید که مقدار key ها باید unique باشه و نباید در لیست تکرار بشن. (من از Symbol ها استفاده می کنم پس این اطمینان هست که هیچ کدوم تکراری نباشن)
  • در کد اصلی، از اونجایی که اسم key های object هایی که در notification_list استفاده شده اند دقیقا با property های Notification هم اسم اند، میشه با استفاده از spread operator (همون … سه نقطه) مقادیر رو به property های متناظر انتساب داد.
  • برخلاف جاوا اسکریپت معمولی (به قول بعضی ها وانیلی)، در svelte علامت $ یک کاراکتر رزرو شده است و معنای معمول رو نداره. از علامت $ قبل از اسم store ها استفاده می کنند تا هم مقدارشون گرفته بشه و هم subscribe بشه (که بعدا در صورت تغییر مقدار، تغییرات منعکس بشه

اجازه بدید قبل از اینکه ادامه بدیم یکم حاشیه برم. اگه با keyed each block ها آشنا هستید که این پرانتز رو رد کنید وگرنه پیشنهاد می کنم بخونید

پرانتز باز (

معرفی به keyed each-block

اگه قرار باشه که روی list تون عملیاتی انجام بدید که باعث تغییر index آیتم ها میشه (مثل حذف یا مرتب سازی (sort)) باید از یک key استفاده کنید. svelte میاد این key رو با هر آیتم در نظر میگیره و با استفاده از اون تشخیص میده کدوم آیتم ها حذف شده اند یا جای کدوم آیتم ها تغییر کرده.

اگه key نباشه چه اتفاقی می افته؟

در حالت عادی svelte JS در each block میاد به ازای هر آیتم در لیست component هایی که بهش میگیم رو می سازه. اگر key وجود نداشته باشه و ما مثلا آیتمی رو حذف کنیم، component هایی که به ازای اون آیتم قبلا ایجاد شده اند پاک نمیشن بلکه فقط مقدار property هاشون عوض میشه.

مثلا در این مورد ما اگر key وجود نداشته باشه، ما هم 5 notification داشته باشیم و دومی رو حذف کنیم. component مربوط به دومی حذف نمیشه بلکه property های msg و type و pk نوتیفیکیشن سوم رو میگیره و همچنین notification سوم هم مقدار property های notification چهارم رو میگیره و به همین ترتیب تا آخر میره و در نهایت آخرین Notification حذف میشه.

بزارید با یک عکس دقیق تر توضیح بدم:

این نوع مکانیزم در شرایطی که ما مقادیر وابسته به property ها داریم مشکل ایجاد می کنه. مثلا فرض کنید که ما یک componentـی داریم که propertyـی داره به نام number. ما مقدار number رو دو برابر می کنیم و در متغیری به نام double ذخیره می کنیم. حالا اگه ترتیب لیست عوض بشه، مقدار number تغییر پیدا می کنه اما مقدار double ثابت می مونه.

البته میشه با روش هایی این رو حل کرد اما چرا راه سخت رو بریم وقتی راه آسون تری هست؟ یه key به svelte بدی همه این مشکلات رو حریفه و می تونه تصمیم بگیره چیکار کنه تا این مشکلات پیش نیاد.

) پرانتز بسته

ساخت کلاس NotificationAPI

حالا وقتشه که یک کلاس ایجاد کنیم که با استفاده ازش Notification اضافه و حذف کنیم. برای این کار در فولدر src فولدری به نام utils بسازید و داخلش فایلی به نام notification_api.js اضافه کنید. کد ها رو در فایلی که ایجاد کرده اید بنویسید:

  • کار متد _notify اضافه کردن notification هاست. در ادامه قراره یه سری توابع wrapper براش ایجاد کنیم. قرار نیست از این متد استفاده مستقیم بشه بلکه باید از wrapper های این متد مثل (success، warning، alert) استفاده بشه
  • کار متد delete هم معلومه :-/

اگه توجه کنید، در هر دو متد داریم متد update رو که در notifications_list قرار داره رو فراخوانی می کنیم. این متد مقدار ذخیره شده در notifications_list رو در یک callback function بهمون میده. حالا ما باید مقدار جدید رو در این callback برگردونیم.

در متد delete داریم از filter برای حذف کردن notification ای که pk اش برابر با pk دریافت شده از طریق پارامتر تابع است استفاده می کنیم. _(فقط یکی از notification ها pk اش می تونه برابر با pk دریافت شده باشه پس بقیه از filter رد میشن)

حالا وقتشه که wrapper ها رو هم در ادامه کلاس اضافه کنیم تا دیگه کارمون با NotificationApi تموم بشه.

پیاده سازی مکانیزم بسته شدن notification ها

به طور کلی در دو حالت زیر باید notification ها بسته بشن:

  • 3 ثانیه از زمان اضافه شدن notification گذشته باشه.
  • کاربر خودش روی دکمه dismiss بزنه

حالا ما تمام چیز های مورد نیازمون رو به صورت یکپارچه در NotificationAPI داریم، فقط کافیه با component مون integrate اش کنیم. برای این کار فایل Notification.svelte رو باز کنید و کد هاشو تغییر بدید:

چند تا دکمه برای افزودن notification

این قسمت جنبه تست داره می تونید کلا ردش کنید چون اصلا مهم نیست. اگه هم تصمیم گرفتید ادامه بدید هم که دمتون گرم واقعا.

هرجور عشقتون کشید بهشون style بدید. جنبه مهم قضیه اضافه کردن notification ها از طریق NotificationAPI هست.

به هر حال. تا الان نتیجه کارمون اینه:

اما آخه چرا اینقدر خشک و خالی! باید کمی animation بهش بدیم.

اضافه کردن animation به notification ها

این رو به راحتی به کمک svelte حلش می کنیم. فقط کافیه کمی کد App.svelte رو تغییر بدید:

  • علت این که Notification رو بین تگ div گذاشتیم اینه که اولا transition و animate رو نمیشه مستقیما روی Notification اعمال کرد و نیاز به واسطه است. راحت ترین کار همینه که یه تگ div بزاری دور Notification. اگه هم بخواهیم در داخل خود Notification.svelte استفاده کنیم، نمیشه چون animate فقط روی المان هایی که مستقیما در یک keyed-each-block قرار دارن قابل استفاده است.
  • مقابل transition اسم transition رو داریم (fly). duration مدت اش رو مشخص می کنه و پارامتر x هم مشخص می کنه که زمان شروع و پایان transition ما notification از جایی که باید باشه 200 پیکسل افقی فاصله داره (از سمت راست فاصله داره). این transition دقیقا موقع اضافه شدن و حذف شدن notification اجرا میشه (اینکه در نتیجه چه شکلی هستش رو در ادامه می بینید)
  • از animate برای این استفاده میشه که بعد از اینکه یکی از notification ها حذف شد بقیه notification ها با animation ای روان به سمت جا های جدیدشون هدایت بشن. اگه دلتون می خواد می تونید برداریدش و امتحان کنید تا دستتون بیاد چیکار می کنه.

کار نهایی:

همونطور که قول داده بودم، این هم لینک repository پروژه


خوب دیگه به آخر پست رسیدیم، امیدوارم مفید واقع شده باشه. اگه خوشتون اومد لایک کنید اگه سوالی داشتید کامنت بزارید، در اسرع وقت جواب میدم.

ممکنه از این نوشته ها هم خوشتون بیاد: