در این آموزش، ما یک برنامه CRUD در قالب یک برنامه یادداشت برداری می سازیم. همچنین نحوه استفاده از حافظه محلی برای ذخیره یادداشت ها در مرورگر خود را بررسی خواهیم کرد.

CRUD چیست؟

ممکن است از قبل بپرسید: “CRUD به چه معناست؟!” یک برنامه CRUD رایج ترین شکل هر برنامه نرم افزاری است. CRUD مخفف ايجاد كردن، خواندن، به روز رسانی و حذف و برای هر نرم افزاری که قادر است به کاربران اجازه ایجاد و مشاهده داده ها را در رابط کاربری خود و همچنین ایجاد تغییرات و حذف داده های موجود را بدهد، اعمال می شود.

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

1. ایجاد نشانه گذاری

برای نشانه گذاری خود، ما دو عنصر اصلی خواهیم داشت: new-note ظرفی که شامل قسمت ورودی و متن برای وارد کردن محتوای یادداشت‌های ما است و notes-wrapper ظرفی که حاوی یادداشت های ایجاد شده است. ما همچنین یک form-error div برای نمایش پیغام های خطا و دکمه ای که آن را فراخوانی می کند addNote() تابعی که بعدا تعریف خواهیم کرد.

1
<main>
2
  <div class="new-note">
3
    <input type="text" name="title" id="title" placeholder="Title">
4
    <textarea name="content" id="content" placeholder="Start writing"></textarea>
5
    <div id="form-error"></div>
6
  </div>
7
  <div class="button-container">
8
    <button class="add-btn" onclick="addNote()">Add note</button>
9
  </div>
10
  <div id="notes-wrapper"></div>
11
</main>

2. یک ظاهر طراحی شده به طرح

این CSS برای طرح اولیه ما است:

1
.new-note {
2
  margin-bottom: 32px;
3
  background-color: white;
4
  border: 1px solid #dadada;
5
  border-radius: 6px;
6
  padding: 16px;
7
  position: relative;
8
}
9
10
.new-note input {
11
  width: 100%;
12
  background: transparent;
13
  border: none;
14
  outline: none;
15
  font-size: 1.5rem;
16
  font-weight: 700;
17
  padding: 0 0 12px;
18
}
19
20
.new-note textarea {
21
  display: block;
22
  width: 100%;
23
  min-height: 100px;
24
  border: none;
25
  resize: none;
26
  background: transparent;
27
  outline: none;
28
  padding: 0;
29
}
30
31
#form-error {
32
  color: #b33030;
33
  position: absolute;
34
  bottom: -32px;
35
  left: 0;
36
}
37
38
.button-container {
39
  display: flex;
40
  justify-content: center;
41
  margin-bottom: 32px;
42
}
43
44
.add-btn {
45
  border: 1px solid rgb(95, 95, 95);
46
  background-color: transparent;
47
  transition: background-color 250ms, color 250ms;
48
  border-radius: 16px;
49
  cursor: pointer;
50
  padding: 8px 12px;
51
}
52
53
.add-btn:hover {
54
  background-color: rgb(95, 95, 95);
55
  color: white;
56
}
57
58
#notes-wrapper {
59
  display: flex;
60
  flex-direction: column;
61
  gap: 32px;
62
}
63
64
.note {
65
  position: relative;
66
  overflow: hidden;
67
  background: white;
68
  border: 1px solid #dadada;
69
  border-radius: 6px;
70
  padding: 16px;
71
}
72
73
.note-title {
74
  font-size: 1.5em;
75
  font-weight: 700;
76
  margin-bottom: 12px;
77
  width: 100%;
78
  display: inline-block;
79
  overflow-wrap: break-word;
80
  white-space: pre-wrap;
81
}
82
83
.note-title:last-child {
84
  margin-bottom: 0;
85
}
86
87
.note-text {
88
  margin-bottom: 16px;
89
  background-color: white;
90
  width: 100%;
91
  overflow-wrap: break-word;
92
  white-space: pre-wrap;
93
  background-color: white;
94
  overflow: hidden;
95
  width: 100%;
96
}
97
98
.note-date {
99
  font-size: 0.75em;
100
  text-align: right;
101
  border-top: 1px solid #dadada;
102
  padding-top: 16px;
103
  width: 100%;
104
  margin-top: auto;
105
}
106
107
.note-controls {
108
  position: absolute;
109
  right: 0;
110
  top: 0;
111
  background-color: white;
112
  font-size: 0.75rem;
113
  column-gap: 8px;
114
  padding: 8px;
115
  display: flex;
116
  opacity: 0;
117
  transition: opacity 350ms;
118
}
119
120
.note:hover .note-controls,
121
.note-controls:focus-within {
122
  opacity: 1;
123
}
124
125
.note-controls button {
126
  padding: 0;
127
  border: none;
128
  background-color: transparent;
129
  cursor: pointer;
130
  padding: 0.5rem;
131
}
132
133
.note-controls button:hover {
134
  filter: brightness(0.85)
135
}
136
137
.note-delete {
138
  color: #bb0000;
139
}
140
141
.note-edit {
142
  color: #00bb00;
143
}
144
145
.note-save {
146
  color: #0000bb;
147
}
148
149
.note-save[disabled="true"] {
150
  color: #dfdfdf;
151
  pointer-events: none;
152
  user-select: none;
153
  cursor: not-allowed;
154
}

در این نسخه ی نمایشی، یک ظاهر طراحی شده را از روی خود حذف کرده ایم input و textarea زمینه هایی برای جلوگیری از تداخل با یک ظاهر طراحی شده. با این حال، در صورت تمایل، می توانید یک ظاهر طراحی شده را برای اهداف دسترسی ترک کنید.

در حال حاضر چیدمان ما به این صورت است:

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

3. ایجاد و خواندن داده ها

اکنون می توانیم روی منطق برنامه خود کار کنیم!

عناصر جهانی

ابتدا، بیایید تمام عناصر جهانی را که به آن نیاز داریم، بدست آوریم.

1
const notesWrapper = document.getElementById("notes-wrapper");
2
const title = document.getElementById("title");
3
const content = document.getElementById("content");
4
const error = document.getElementById("form-error");

سپس یک متغیر جهانی برای ذخیره داده های یادداشت هایمان تعریف می کنیم.

ایجاد و ذخیره داده ها

به منظور ایجاد داده، یک تعریف می کنیم addNote() عملکردی که با کلیک روی دکمه فراخوانی می شود. این همان چیزی است که داده ها را از فیلد ورودی گرفته و آنها را در یک عنصر یادداشت جدید در قالب قابل خواندن قرار می دهد.

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

1
const addNote = () => {
2
  if (title.value.trim().length == 0 && content.value.trim().length == 0) {
3
    error.innerText = "Note cannot be empty";
4
    return;
5
  }
6
};

در مرحله بعد، یک شی ایجاد می کنیم که تاریخ یک یادداشت جدید را در خود داشته باشد. در این آموزش، یک مقدار id (uid) منحصر به فرد برای هر یادداشت با استفاده از date.getTime() روش. این دقیقاً میلی‌ثانیه‌ای را که یادداشت ایجاد شده است برمی‌گرداند و برای اطمینان از اینکه هیچ دو نت شناسه یکسانی ندارند استفاده می‌شود.

همچنین مقدار تاریخ ایجاد یادداشت را با استفاده از آن درج می کنیم date.toLocaleDateString() روش. عملکرد به روز شده ما به این صورت است:

1
const noteObj = {
2
    uid: new Date().getTime().toString(),
3
    title: title.value,
4
    text: content.value,
5
    date: new Date().toLocaleDateString()
6
};

اکنون که شی note را داریم، می‌توانیم آن داده‌ها را در آرایه notesData و همچنین در مرورگر ذخیره کنیم محل ذخیره سازی.

1
notesData.push(noteObj);
2
localStorage.setItem("notes", JSON.stringify(notesData));

localStorage فقط از داده ها در قالب رشته پشتیبانی می کند، بنابراین ما از آن استفاده می کنیم JSON.stringify() روش تبدیل آرایه ما به فرمت مناسب.

نمایش داده ها

بیایید یک تابع تعریف کنیم createNote() برای رسیدگی به الحاق یادداشت جدید به notesWrapper ظرف عنصر یادداشت عنوان، محتوا و تاریخ را بر اساس ورودی کاربر نمایش می دهد. همچنین یک دکمه ویرایش، ذخیره و حذف برای انجام عملکردهای مربوطه بر اساس شناسه منحصر به فرد یادداشت خواهد داشت.

1
const createNote = (uid, title, text, date) => {
2
  const note = document.createElement("div");
3
  note.className = "note";
4
  note.id = uid;
5
  note.innerHTML = `
6
    <div class="note-title">${title}</div>
7
    <div class="note-controls">
8
      <button class="note-edit" onclick="editNote(${uid})">
9
        Edit
10
      </button>
11
      <button class="note-save" style="display:none" onclick="saveNote(${uid})">
12
        Save
13
      </button>
14
      <button class="note-delete" onclick="deleteNote(${uid})">
15
        Delete
16
      </button>
17
    </div>
18
    <div class="note-text">${text}</div>
19
    <div class="note-date">${date}</div>
20
  `;
21
22
  notesWrapper.insertBefore(note, notesWrapper.firstChild);
23
};

در این تابع از .insertBefore() روشی برای اطمینان از اینکه جدیدترین یادداشت در بالای محفظه notesWrapper قرار دارد.

عنوان و محتوا را برای یادداشت بعدی بازنشانی کنید

در نهایت، ما می توانیم خود را به روز کنیم addNote() عملکرد ایجاد یک یادداشت جدید و همچنین تنظیم مجدد عنوان، محتوا و عناصر خطا هنگام کلیک بر روی دکمه.

1
const addNote = () => {
2
  if (title.value.trim().length == 0 && content.value.trim().length == 0) {
3
    error.innerText = "Note cannot be empty";
4
    return;
5
  }
6
7
  const noteObj = {
8
    uid: new Date().getTime().toString(),
9
    title: title.value,
10
    text: content.value,
11
    date: new Date().toLocaleDateString()
12
  };
13
  
14
  createNote(noteObj.uid, noteObj.title, noteObj.text, noteObj.date);
15
16
  error.innerText = "";
17
  content.value = "";
18
  title.value = "";
19
};

داده های موجود را بررسی کنید

از آنجایی که ما از localStorage استفاده می‌کنیم، همچنین می‌توانیم شرطی را برای بررسی اینکه آیا داده‌های موجود برای یادداشت‌های ما در localStorage وجود دارد یا نه و پس از بارگیری صفحه در صفحه نمایش داده شود. این JSON.parse() روش برای تبدیل داده های رشته ای ما به فرمت اصلی خود استفاده می شود.

1
window.addEventListener("load", () => {
2
  notesData = localStorage.getItem("notes")
3
    ? JSON.parse(localStorage.getItem("notes"))
4
    : [];
5
    
6
  notesData.forEach((note) => {
7
    createNote(note.uid, note.title, note.text, note.date);
8
  });
9
});

4. به روز رسانی داده ها

در این مرحله، ما بخش‌های «C» و «R» CRUD را مدیریت کرده‌ایم، و توانستیم با موفقیت یادداشت‌هایی را براساس ورودی کاربر ایجاد کنیم و یادداشت‌های موجود را نمایش دهیم. حالا بیایید با تعریف تابعی که به ما امکان ویرایش و ذخیره (به روز رسانی) یادداشت موجود را می دهد، توجه خود را به U معطوف کنیم.

ویرایش..

وقتی تعریف کردیم createNote() تابع، ما دو عنصر دکمه را برای ویرایش و ذخیره یک یادداشت بر اساس یک شناسه منحصر به فرد اضافه کردیم، بنابراین اکنون می‌توانیم editNote() و saveNote() کارکرد. هنگامی که دکمه ویرایش کلیک می شود، دکمه ویرایش را مخفی می کنیم و دکمه ذخیره را نمایش می دهیم:

1
const editNote = (uid) => {
2
  const note = document.getElementById(uid);
3
4
  const noteTitle = note.querySelector(".note-title");
5
  const noteText = note.querySelector(".note-text");
6
  const noteSave = note.querySelector(".note-save");
7
  const noteEdit = note.querySelector(".note-edit");
8
9
  noteTitle.contentEditable = "true";
10
  noteText.contentEditable = "true";
11
  noteEdit.style.display = "none";
12
  noteSave.style.display = "block";
13
  noteText.focus();
14
};

در این تابع از uid برای یافتن عنصر یادداشت در DOM. سپس عنوان و عناصر متن را در داخل یادداشت هدف هدف قرار داده و از آن استفاده می کنیم contentEditable روشی که به ما امکان می دهد در محتوا تغییراتی ایجاد کنیم. contentEditable یک ویژگی مرورگر داخلی است که به کاربر اجازه می دهد محتوای هر عنصر را در صورت تنظیم روی true تغییر دهد.

ما می‌توانیم CSS خود را به‌روزرسانی کنیم تا در هنگام ویرایش، استایلی را روی عنصر یادداشت اضافه کنیم.

1
.note > *[contenteditable="true"] {
2
  color: #5f5f5f;
3
  width: 100%;
4
  outline: none;
5
}

..و ذخیره مجدد

برای ما saveNote() تابع، ما باید مقدار notesData و داده های localStorage خود را به روز کنیم. ما می توانیم این کار را با استفاده از .forEach() روشی برای یافتن یادداشتی که دارای uid مربوطه است و محتوا را به روز می کند. سپس آرایه به روز شده خود را به localStorage فشار می دهیم تا مقدار قدیمی را جایگزین کند.

1
  notesData.forEach((note) => {
2
    if (note.uid == uid) {
3
      note.title = noteTitle.innerText;
4
      note.text = noteText.innerText;
5
    }
6
  });
7
8
  localStorage.setItem("notes", JSON.stringify(notesData));

سپس از همان منطق استفاده خواهیم کرد editNote() تابع، فقط این بار ویژگی های contentEditable را روی false قرار می دهیم و دکمه ذخیره را در حین نمایش دکمه ویرایش پنهان می کنیم. ما همچنین از همان شرایطی که در خود استفاده کردیم استفاده خواهیم کرد addNote() عملکردی برای اطمینان از اینکه کاربران نمی توانند یک یادداشت خالی ذخیره کنند.

1
const saveNote = (uid) => {
2
  const note = document.getElementById(uid);
3
4
  const noteTitle = note.querySelector(".note-title");
5
  const noteText = note.querySelector(".note-text");
6
  const noteSave = note.querySelector(".note-save");
7
  const noteEdit = note.querySelector(".note-edit");
8
9
  if (
10
    noteTitle.innerText.trim().length == 0 &&
11
    noteText.value.trim().length == 0
12
  ) {
13
    error.innerHTML = "Note cannot be empty";
14
    return;
15
  }
16
17
  notesData.forEach((note) => {
18
    if (note.uid == uid) {
19
      note.title = noteTitle.innerText;
20
      note.text = noteText.innerText;
21
    }
22
  });
23
24
  localStorage.setItem("notes", JSON.stringify(notesData));
25
26
  noteTitle.contentEditable = "false";
27
  noteText.contentEditable = "false";
28
  noteEdit.style.display = "block";
29
  noteSave.style.display = "none";
30
  error.innerText = "";
31
};

5. حذف داده ها

و در آخر، بخش “D” از CRUD.

برای اجرای نهایی برنامه ما، حذف داده ها را مدیریت می کنیم. برای این تابع، عنصر note را از DOM حذف می کنیم و همچنین شی یادداشت را از خود حذف می کنیم notesData آرایه. می‌توانیم با استفاده از عبارت، شیء را از آرایه حذف کنیم .filter() روش.

1
const deleteNote = (uid) => {
2
  let confirmDelete = confirm("Are you sure you want to delete this note?");
3
  if (!confirmDelete) {
4
    return;
5
  }
6
7
  const note = document.getElementById(uid);
8
  note.parentNode.removeChild(note);
9
  
10
  notesData = notesData.filter((note) => {
11
    return note.uid != uid;
12
  });
13
  localStorage.setItem("notes", JSON.stringify(notesData));
14
};

یکی از مزایای ساخت یک برنامه وب این است که می توانیم از ویژگی های موجود مرورگر استفاده کنیم. این تابع از تایید() روشی برای نمایش یک مدال داخلی که می تواند ورودی کاربر را تأیید کند، بدون نیاز به ساخت یک مدال سفارشی و تشخیص پاسخ خودمان.

نتیجه

همینه که هست! آنچه که ما کدگذاری کردیم به اندازه کافی پیچیده بود که چالش برانگیز باشد، در عین حال به اندازه کافی ساده برای درک، و نمایش کامل CRUD بود. ما عملیات اساسی یک برنامه نرم افزاری را با استفاده از جاوا اسکریپت وانیلی پیاده سازی کرده ایم (به خوبی انجام شده است).