اذهب إلى المحتوى

إنشاء تطبيق قائمة مهام باستعمال إطار عمل Svelte


Ola Abbas

يمكننا الآن البدء في إنشاء تطبيقنا مثل تطبيق قائمة المهام بعد أن فهمنا الأمور الأساسية في إطار عمل Svelte في المقال السابق، إذ سنلقي في هذا المقال نظرةً على الوظائف المطلوبة لتطبيقنا أولًا ثم سننشئ المكوِّن Todos.svelte وسنضع شيفرة HTML وشيفرة التنسيق الثابتة في مكانها، وبالتالي سيصبح كل شيء جاهزًا لبدء تطوير ميزات تطبيق قائمة المهام التي سننتقل إليها في المقالات اللاحقة.

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

  • المتطلبات الأساسية: يوصَى على الأقل بأن تكون على دراية بأساسيات لغات HTML و CSS وجافاسكربت JavaScript، ومعرفة باستخدام سطر الأوامر أو الطرفية، وستحتاج طرفية مثبَّت عليها node وnpm لتصريف وبناء تطبيقك.
  • الهدف: معرفة كيفية إنشاء مكوِّن Svelte وتصييره في مكوِّن آخر وتمرير البيانات إليه باستخدام الخاصيات Props وحفظ حالته.

يمكن متابعة كتابة شيفرتك معنا، لذلك انسخ أولًا مستودع github -إذا لم تفعل ذلك مسبقًا- باستخدام الأمر التالي:

git clone https://github.com/opensas/mdn-svelte-tutorial.git

ثم يمكنك الوصول إلى حالة التطبيق الحالية من تشغيل الأمر التالي:

cd mdn-svelte-tutorial/02-starting-our-todo-app

أو يمكنك تنزيل محتوى المجلد مباشرةً كما يلي:

npx degit opensas/mdn-svelte-tutorial/02-starting-our-todo-app

تذكَّر تشغيل الأمر التالي لبدء تشغيل تطبيقك في وضع التطوير:

npm install && npm run dev

فإذا أردت متابعتنا فابدأ بكتابة الشيفرة باستخدام أداة REPL.

ميزات تطبيق قائمة المهام

سيبدو تطبيق قائمة المهام كما يلي بمجرد أن يصبح جاهزًا:

تطبيق قائمة المهام

سيتمكّن المستخدِم من تطبيق الأمور التالية باستخدام واجهة المستخدِم:

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

إنشاء المكون الأول

لننشئ المكوِّن Todos.svelte الذي سيحتوي على قائمة المهام.

أولًا، أنشئ مجلدًا جديدًا بالاسم src/components.

ملاحظة: يمكنك وضع مكوناتك في أيّ مكان ضمن المجلد src، ولكن المجلد components هو اصطلاح معروف يجب اتباعه، مما يسمح لك بالعثور على مكوناتك بسهولة.

ثانيًا، أنشئ ملفًا بالاسم src/components/Todos.svelte بحيث يحوي ما يلي:

<h1>Svelte To-Do list</h1>

ثالثًا، عدّل العنصر title في الملف public/index.html ليحتوي على النص "Svelte To-do list" كما يلي:

<title>Svelte To-Do list</title>

رابعًا، افتح الملف src/App.svelte واستبدل محتوياته بما يلي:

<script>
  import Todos from './components/Todos.svelte'
</script>

<Todos />

سيصدر إطار عمل Svelte في وضع التطوير تحذيرًا في طرفية المتصفح عند تحديد خاصية غير موجودة في المكوِّن مثل تحديد الخاصية name عند إنشاء نسخة من المكون App ضمن الملف src/main.js، إذ لا تُستخدَم هذه الخاصية ضمن المكوِّن App، كما يجب أن تعطيك الطرفية حاليًا رسالة مثل الرسالة "‎ was created with unknown prop 'name'‎"، لكن يمكنك حل هذه المشكلة من خلال إزالة الخاصية name من src/main.js ويجب أن يبدو الآن كما يلي:

import App from './App.svelte'

const app = new App({
  target: document.body
})

export default app

إذا تحققت من عنوان URL لخادم الاختبار، فسترى تصيير المكوِّن Todos.svelte كما يلي:

تصيير المكوِّن Todos.svelte

إضافة شيفرة HTML الثابتة

سنبدأ أولًا بتمثيل شيفرة HTML لتطبيقنا لتتمكّن من رؤية الشكل الذي سيبدو عليه، لذا انسخ والصق ما يلي في ملف المكوِّن Todos.svelte ليحل محل المحتوى الموجود مسبقًا:

<!-- Todos.svelte -->
<div class="todoapp stack-large">

  <!-- NewTodo -->
  <form>
    <h2 class="label-wrapper">
      <label for="todo-0" class="label__lg">
        What needs to be done?
      </label>
    </h2>
    <input type="text" id="todo-0" autocomplete="off"
      class="input input__lg" />
    <button type="submit" disabled="" class="btn btn__primary btn__lg">
      Add
    </button>
  </form>

  <!-- Filter -->
  <div class="filters btn-group stack-exception">
    <button class="btn toggle-btn" aria-pressed="true">
      <span class="visually-hidden">Show</span>
      <span>All</span>
      <span class="visually-hidden">tasks</span>
    </button>
    <button class="btn toggle-btn" aria-pressed="false">
      <span class="visually-hidden">Show</span>
      <span>Active</span>
      <span class="visually-hidden">tasks</span>
    </button>
    <button class="btn toggle-btn" aria-pressed="false">
      <span class="visually-hidden">Show</span>
      <span>Completed</span>
      <span class="visually-hidden">tasks</span>
    </button>
  </div>

  <!-- TodosStatus -->
  <h2 id="list-heading">2 out of 3 items completed</h2>

  <!-- Todos -->
  <ul role="list" class="todo-list stack-large" aria-labelledby="list-heading">

    <!-- todo-1 (editing mode) -->
    <li class="todo">
      <div class="stack-small">
        <form class="stack-small">
          <div class="form-group">
            <label for="todo-1" class="todo-label">
              New name for 'Create a Svelte starter app'
            </label>
            <input type="text" id="todo-1" autocomplete="off" class="todo-text" />
          </div>
          <div class="btn-group">
            <button class="btn todo-cancel" type="button">
              Cancel
              <span class="visually-hidden">renaming Create a Svelte starter app</span>
            </button>
            <button class="btn btn__primary todo-edit" type="submit">
              Save
              <span class="visually-hidden">new name for Create a Svelte starter app</span>
            </button>
          </div>
        </form>
      </div>
    </li>

    <!-- todo-2 -->
    <li class="todo">
      <div class="stack-small">
        <div class="c-cb">
          <input type="checkbox" id="todo-2" checked/>
          <label for="todo-2" class="todo-label">
            Create your first component
          </label>
        </div>
        <div class="btn-group">
          <button type="button" class="btn">
            Edit
            <span class="visually-hidden">Create your first component</span>
          </button>
          <button type="button" class="btn btn__danger">
            Delete
            <span class="visually-hidden">Create your first component</span>
          </button>
        </div>
      </div>
    </li>

    <!-- todo-3 -->
    <li class="todo">
      <div class="stack-small">
        <div class="c-cb">
          <input type="checkbox" id="todo-3" />
          <label for="todo-3" class="todo-label">
            Complete the rest of the tutorial
          </label>
        </div>
        <div class="btn-group">
          <button type="button" class="btn">
            Edit
            <span class="visually-hidden">Complete the rest of the tutorial</span>
          </button>
          <button type="button" class="btn btn__danger">
            Delete
            <span class="visually-hidden">Complete the rest of the tutorial</span>
          </button>
        </div>
      </div>
    </li>
  </ul>

  <hr />

  <!-- MoreActions -->
  <div class="btn-group">
    <button type="button" class="btn btn__primary">Check all</button>
    <button type="button" class="btn btn__primary">Remove completed</button>
  </div>

</div>

تحقّق من الخرج المُصيَّر مرةً أخرى، وسترى شيئًا يشبه ما يلي:

التحقّق من الخرج المُصيَّر

يُعَدّ تنسيق شيفرة HTML السابق ليس جيدًا كما أنه غير مفيد وظيفيًا، ولكن لنلقِ نظرةً على الشيفرة ونرى مدى ارتباطها بالميزات التي نرغب بها:

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

سنعمل في المقالات اللاحقة على تشغيل جميع هذه الميزات.

ميزات سهولة الوصول Accessibility لقائمة المهام

لاحظ وجود بعض السمات غير المعتادة مثل:

<button class="btn toggle-btn" aria-pressed="true">
  <span class="visually-hidden">Show</span>
  <span>All</span>
  <span class="visually-hidden">tasks</span>
</button>

تخبر السمة aria-pressed التقنيات المساعدة مثل قارئات الشاشة أنّ الزر يمكن أن يكون في إحدى الحالتين: pressed أو unpressed مثل القول بأن الزر في وضع التشغيل أو الإيقاف، ويعني ضبط القيمة true أنّ الزر مضغوط افتراضيًا.

ليس للصنف visually-hidden أيّ تأثير حتى الآن، لأننا لم نضمّن أيّ ملف CSS، وسيُخفَى أيّ عنصر موجود في هذا الصنف عن المستخدِمين المبصرين وسيظل متاحًا لمستخدِمي قارئات الشاشة بمجرد أن نضع التنسيق في مكانه، لأن هذه الكلمات لا يحتاجها المستخدِمون المبصرون، وإنما تُستخدَم لتقديم مزيد من المعلومات حول ما يفعله الزر لمستخدِمي قارئات الشاشة الذين ليس لديهم القدرة البصرية لمساعدتهم.

كما يمكنك العثور على عنصر <ul> التالي:

<ul role="list" className="todo-list stack-large" aria-labelledby="list-heading">

تساعد السمة role التقنيات المساعدة في توضيح نوع القيمة الدلالية للعنصر أو ما هو الغرض منه، إذ يُعامَل العنصر <ul> بوصفه قائمةً افتراضيًا، ولكن ستؤدي التنسيقات التي نريد إضافتها إلى تعطيل هذه الوظيفة، ولكن سيعيد هذا الدور معنى القائمة إلى العنصر <ul>.

تخبر السمة aria-labelledby التقنيات المساعدة بأننا نتعامل مع العنصر <h2> مع معرِّف id عنوان القائمة list-heading بوصفه التسمية التي تشرح الغرض من القائمة الموجودة تحتها، إذ يعطي هذا الارتباط القائمةَ سياقًا مفيدًا، مما يساعد مستخدِمي قارئات الشاشة على فهم الغرض منها بصورة أفضل.

دعم إطار عمل Svelte لسهولة الوصول

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

لا يُعَدّ عملية تطبيق مبادئ سهولة الوصول -التي تُختصَر إلى a11y- أمرًا سهلًا دائمًا، ولكن سيساعدك إطار Svelte من خلال تحذيرك إذا كتبت شيفرة HTML لا تراعي تلك المبادئ، فإذا أضفنا العنصر <img> مثلًا إلى المكوِّن todos.svelte بدون الخاصية alt المقابلة له كما يلي:

<h1>Svelte To-Do list</h1>

<img height="32" width="88" src="https://www.w3.org/WAI/wcag2A" />

فسيعطي المصرِّف التحذير التالي:

(!) Plugin svelte: A11y: <img> element should have an alt attribute
src/components/Todos.svelte
1: <h1>Svelte To-Do list</h1>
2:
3: <img height="32" width="88" src="https://www.w3.org/WAI/wcag2A">
   ^
created public/build/bundle.js in 220ms

[2020-07-15 04:07:43] waiting for changes

كما يمكن لمحرر الشيفرة عرض هذا التحذير حتى قبل استدعاء المصرِّف كما يلي:

عرض محرر الشيفرة التحذير حتى قبل استدعاء المصرِّف

يمكنك إخبار إطار عمل Svelte بتجاهل هذا التحذير للكتلة التالية من شيفرة HTML بتعليق يبدأ بعبارة svelte-ignore كما يلي:

<!-- svelte-ignore a11y-missing-attribute -->
<img height="32" width="88" src="https://www.w3.org/WAI/wcag2A">

ملاحظة: يمكنك باستخدام المحرّر VSCode إضافة تعليق التجاهل هذا تلقائيًا بالنقر على الرابط "Quick fix…‎" أو بالضغط على الاختصار Ctrl + .‎.

إذا أردت تعطيل هذا التحذير، فيمكنك إضافة المعالج onwarn إلى الملف rollup.config.js ضمن إعداد الإضافة Svelte كما يلي:

plugins: [
  svelte({
    dev: !production,
    css: css => {
      css.write('public/build/bundle.css');
    },
    // Warnings are normally passed straight to Rollup. You can
    // optionally handle them here, for example to squelch
    // warnings with a particular code
    onwarn: (warning, handler) => {
      // e.g. I don't care about screen readers -> please DON'T DO THIS!!!
      if (warning.code === 'a11y-missing-attribute') return;

      // let Rollup handle all other warnings normally
      handler(warning);
    }
  }),

  ...
]

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

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

قواعد الشمولية التي تحقق منها إطار عمل Svelte مأخوذة من الإضافة eslint-plugin-jsx-a11y، وهي إضافة من ESLint توفِّر فحوصات ساكنة للعديد من قواعد سهولة الوصول على عناصر JSX، كما يهدف إطار Svelte إلى تنفيذ كل من هذه القواعد في مصرِّفه، وقد نُقِل معظمها إلى Svelte فعليًا، بالإضافة إلى أنه يمكنك على GitHub معرفة فحوصات الشمولية التي لا تزال مفقودة، ويمكنك التحقق من معنى كل قاعدة من خلال النقر على رابطها الخاص.

تنسيق التطبيق

لنجعل قائمة المهام تبدو أفضل قليلًا، لذا استبدل محتويات الملف public/global.css بما يلي:

/* RESETS */
*,
*::before,
*::after {
  box-sizing: border-box;
}
*:focus {
  outline: 3px dashed #228bec;
  outline-offset: 0;
}
html {
  font: 62.5% / 1.15 sans-serif;
}
h1,
h2 {
  margin-bottom: 0;
}
ul {
  list-style: none;
  padding: 0;
}
button {
  border: none;
  margin: 0;
  padding: 0;
  width: auto;
  overflow: visible;
  background: transparent;
  color: inherit;
  font: inherit;
  line-height: normal;
  -webkit-font-smoothing: inherit;
  -moz-osx-font-smoothing: inherit;
  -webkit-appearance: none;
}
button::-moz-focus-inner {
  border: 0;
}
button,
input,
optgroup,
select,
textarea {
  font-family: inherit;
  font-size: 100%;
  line-height: 1.15;
  margin: 0;
}
button,
input {
  overflow: visible;
}
input[type="text"] {
  border-radius: 0;
}
body {
  width: 100%;
  max-width: 68rem;
  margin: 0 auto;
  font: 1.6rem/1.25 Arial, sans-serif;
  background-color: #f5f5f5;
  color: #4d4d4d;
}
@media screen and (min-width: 620px) {
  body {
    font-size: 1.9rem;
    line-height: 1.31579;
  }
}
/*END RESETS*/

/* GLOBAL STYLES */
.form-group > input[type="text"] {
  display: inline-block;
  margin-top: 0.4rem;
}
.btn {
  padding: 0.8rem 1rem 0.7rem;
  border: 0.2rem solid #4d4d4d;
  cursor: pointer;
  text-transform: capitalize;
}
.btn.toggle-btn {
  border-width: 1px;
  border-color: #d3d3d3;
}
.btn.toggle-btn[aria-pressed="true"] {
  text-decoration: underline;
  border-color: #4d4d4d;
}
.btn__danger {
  color: #fff;
  background-color: #ca3c3c;
  border-color: #bd2130;
}
.btn__filter {
  border-color: lightgrey;
}
.btn__primary {
  color: #fff;
  background-color: #000;
}
.btn__primary:disabled {
  color: darkgrey;
  background-color:#565656;
}
.btn-group {
  display: flex;
  justify-content: space-between;
}
.btn-group > * {
  flex: 1 1 49%;
}
.btn-group > * + * {
  margin-left: 0.8rem;
}
.label-wrapper {
  margin: 0;
  flex: 0 0 100%;
  text-align: center;
}
.visually-hidden {
  position: absolute !important;
  height: 1px;
  width: 1px;
  overflow: hidden;
  clip: rect(1px 1px 1px 1px);
  clip: rect(1px, 1px, 1px, 1px);
  white-space: nowrap;
}
[class*="stack"] > * {
  margin-top: 0;
  margin-bottom: 0;
}
.stack-small > * + * {
  margin-top: 1.25rem;
}
.stack-large > * + * {
  margin-top: 2.5rem;
}
@media screen and (min-width: 550px) {
  .stack-small > * + * {
    margin-top: 1.4rem;
  }
  .stack-large > * + * {
    margin-top: 2.8rem;
  }
}
.stack-exception {
  margin-top: 1.2rem;
}
/* END GLOBAL STYLES */

.todoapp {
  background: #fff;
  margin: 2rem 0 4rem 0;
  padding: 1rem;
  position: relative;
  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 2.5rem 5rem 0 rgba(0, 0, 0, 0.1);
}
@media screen and (min-width: 550px) {
  .todoapp {
    padding: 4rem;
  }
}
.todoapp > * {
  max-width: 50rem;
  margin-left: auto;
  margin-right: auto;
}
.todoapp > form {
  max-width: 100%;
}
.todoapp > h1 {
  display: block;
  max-width: 100%;
  text-align: center;
  margin: 0;
  margin-bottom: 1rem;
}
.label__lg {
  line-height: 1.01567;
  font-weight: 300;
  padding: 0.8rem;
  margin-bottom: 1rem;
  text-align: center;
}
.input__lg {
  padding: 2rem;
  border: 2px solid #000;
}
.input__lg:focus {
  border-color: #4d4d4d;
  box-shadow: inset 0 0 0 2px;
}
[class*="__lg"] {
  display: inline-block;
  width: 100%;
  font-size: 1.9rem;
}
[class*="__lg"]:not(:last-child) {
  margin-bottom: 1rem;
}
@media screen and (min-width: 620px) {
  [class*="__lg"] {
    font-size: 2.4rem;
  }
}
.filters {
  width: 100%;
  margin: unset auto;
}
/* Todo item styles */
.todo {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}
.todo > * {
  flex: 0 0 100%;
}
.todo-text {
  width: 100%;
  min-height: 4.4rem;
  padding: 0.4rem 0.8rem;
  border: 2px solid #565656;
}
.todo-text:focus {
  box-shadow: inset 0 0 0 2px;
}
/* CHECKBOX STYLES */
.c-cb {
  box-sizing: border-box;
  font-family: Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  font-weight: 400;
  font-size: 1.6rem;
  line-height: 1.25;
  display: block;
  position: relative;
  min-height: 44px;
  padding-left: 40px;
  clear: left;
}
.c-cb > label::before,
.c-cb > input[type="checkbox"] {
  box-sizing: border-box;
  top: -2px;
  left: -2px;
  width: 44px;
  height: 44px;
}
.c-cb > input[type="checkbox"] {
  -webkit-font-smoothing: antialiased;
  cursor: pointer;
  position: absolute;
  z-index: 1;
  margin: 0;
  opacity: 0;
}
.c-cb > label {
  font-size: inherit;
  font-family: inherit;
  line-height: inherit;
  display: inline-block;
  margin-bottom: 0;
  padding: 8px 15px 5px;
  cursor: pointer;
  touch-action: manipulation;
}
.c-cb > label::before {
  content: "";
  position: absolute;
  border: 2px solid currentColor;
  background: transparent;
}
.c-cb > input[type="checkbox"]:focus + label::before {
  border-width: 4px;
  outline: 3px dashed #228bec;
}
.c-cb > label::after {
  box-sizing: content-box;
  content: "";
  position: absolute;
  top: 11px;
  left: 9px;
  width: 18px;
  height: 7px;
  transform: rotate(-45deg);
  border: solid;
  border-width: 0 0 5px 5px;
  border-top-color: transparent;
  opacity: 0;
  background: transparent;
}
.c-cb > input[type="checkbox"]:checked + label::after {
  opacity: 1;
}

يبدو كل شيء الآن أفضل كما يلي:

نتيجة تنسيق قائمة المهام

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

cd mdn-svelte-tutorial/03-adding-dynamic-behavior

أو يمكنك تنزيل محتوى المجلد مباشرةً باستخدام الأمر التالي:

npx degit opensas/mdn-svelte-tutorial/03-adding-dynamic-behavior

تذكَّر تشغيل الأمر npm install && npm run dev لبدء تشغيل تطبيقك في وضع التطوير، فإذا أردت متابعتنا، فابدأ بكتابة الشيفرة باستخدام الأداة REPL.

الخلاصة

بدأ تطبيق قائمة المهام في التبلور مع تطبيق شيفرة HTML وشيفرة التنسيق CSS، وأصبح كل شيء جاهزًا لنتمكّن من التركيز على الميزات التي يجب تطبيقها.

ترجمة -وبتصرُّف- للمقال Starting our Svelte to-do list app.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...