Проверка сроков выполнения
В этой статье мы рассмотрим сложный пример, связанный с проверкой текущего времени и даты по отношению к сроку, хранящемуся в IndexedDB. Основная сложность здесь заключается в сравнении сохранённых данных о сроке (месяц, час, день и т. д.) с текущим временем и датой, взятыми из объекта Date.
Основным примером приложения, на которое мы будем ссылаться в этой статье, является Уведомления о задачах, простое приложение списка задач, которое хранит заголовки задач и сроки выполнения с помощью IndexedDB, а затем предоставляет пользователям уведомления, когда сроки истекают, с помощью API Notification и Vibration. Вы можете скачать приложение Уведомления о задачах с GitHub и поработать с исходным кодом или посмотреть приложение в работе.
Основная проблема
В приложении списка задач мы хотели сначала записать информацию о времени и дате в формате, который одновременно является машиночитаемым и понятным для человека при отображении, а затем проверить, соответствует ли каждое время и дата текущему моменту. В основном, мы хотим проверить, какое сейчас время и дата, а затем проверить каждый сохранённый элемент, чтобы увидеть, совпадают ли какие-либо из их сроков со временем и датой текущего момента. Если да, то мы хотим сообщить об этом пользователю с помощью уведомления.
Это было бы легко, если бы мы просто сравнивали два Date объекта, но, конечно, люди не хотят вводить информацию о сроках в том же формате, что и JavaScript. Человекочитаемые даты сильно отличаются, имея множество различных представлений.
Запись информации о дате
Чтобы обеспечить приемлемый пользовательский опыт на мобильных устройствах и уменьшить неоднозначность, я решил создать форму HTML с:
- Поле ввода текста для ввода заголовка для вашего списка задач. Это наименее неизбежная часть ввода пользователем.
- Поля ввода числа для часов и минут срока выполнения. В браузерах, поддерживающих
type="number", вы получаете удобный числовой выбор с помощью стрелок вверх и вниз. На мобильных платформах, как правило, используется цифровая клавиатура для ввода данных, что удобно. В других случаях вы просто получаете стандартное текстовое поле, что тоже нормально. -
<select>элементы для ввода дня, месяца и года срока выполнения. Поскольку эти значения являются наиболее неоднозначными для ввода пользователем (7, воскресенье, вс? 04, 4, апрель, апр? 2013, '13, 13?), я решил, что лучшим решением будет дать им возможность выбора, что также экономит раздражающий ввод данных на мобильных устройствах. Дни записываются как цифровые дни месяца, месяцы — как полные названия месяцев, а годы — как полные четырёхзначные числа.
При нажатии кнопки отправки формы мы запускаем функцию addData(), которая начинается так:
function addData(e) {
e.preventDefault();
if (!title.value || !hours.value || !minutes.value || !day.value || !month.value || !year.value) {
note.appendChild(document.createElement("li")).textContent = "Data not submitted — form incomplete.";
return;
}
В этом фрагменте мы проверяем, заполнены ли все поля формы. Если нет, мы выводим сообщение в область уведомлений разработчика (см. левый нижний угол пользовательского интерфейса приложения), чтобы сообщить пользователю о происходящем, и выходим из функции. Этот шаг предназначен в основном для браузеров, которые не поддерживают проверку форм HTML (я использовал атрибут required в своём HTML для принудительной проверки в тех, которые её поддерживают).
else {
const newItem = [
{
taskTitle: title.value,
hours : hours.value,
minutes : minutes.value,
day : day.value,
month : month.value,
year : year.value,
notified : "no"
}
];
// open a read/write db transaction, ready for adding the data
const transaction = db.transaction(["toDoList"], "readwrite");
// report on the success of opening the transaction
transaction.oncomplete = (event) => {
note.appendChild(document.createElement("li")).textContent =
"Transaction opened for task addition.";
};
transaction.onerror = (event) => {
note.appendChild(document.createElement("li")).textContent =
"Transaction not opened due to error. Duplicate items not allowed.";
};
// create an object store on the transaction
const objectStore = transaction.objectStore("toDoList");
// add our newItem object to the object store
const request = objectStore.add(newItem[0]);
В этом разделе мы создаём объект, называемый newItem, который хранит данные в формате, необходимом для вставки в базу данных. Следующие несколько строк открывают транзакцию базы данных и предоставляют сообщения, чтобы уведомить пользователя, если это удалось или нет. Затем создаётся objectStore, в который добавляется новый элемент. Свойство notified объекта данных указывает, что срок выполнения элемента списка задач ещё не наступил и не был уведомлён — подробнее об этом позже!
Примечание: Переменная db хранит ссылку на экземпляр базы данных IndexedDB; затем мы можем использовать различные свойства этой переменной для управления данными.
request.onsuccess = (event) => {
note.appendChild(document.createElement("li")).textContent = "New item added to database.";
title.value = "";
hours.value = null;
minutes.value = null;
day.value = "01";
month.value = "January";
year.value = 2020;
};
}
В этом следующем разделе создаётся сообщение в журнал, указывающее на успешное добавление нового элемента, и сбрасывается форма, чтобы она была готова для ввода следующей задачи.
// update the display of data to show the newly added item, by running displayData() again. displayData(); };
В заключение мы запускаем функцию displayData(), которая обновляет отображение данных в приложении, чтобы показать новую задачу, которая только что была введена.
Проверка, наступил ли срок выполнения
На этом этапе наши данные находятся в базе данных; теперь мы хотим проверить, наступил ли срок выполнения для каких-либо из них. Это выполняется нашей функцией checkDeadlines():
function checkDeadlines() {
const now = new Date();
Сначала мы получаем текущую дату и время, создав пустой объект Date. Легко, да? Это только что станет немного сложнее.
const minuteCheck = now.getMinutes(); const hourCheck = now.getHours(); const dayCheck = now.getDate(); const monthCheck = now.getMonth(); const yearCheck = now.getFullYear();
Объект Date имеет ряд методов для извлечения различных частей даты и времени внутри него. Здесь мы получаем текущие минуты (дают простое числовое значение), часы (дают простое числовое значение), день месяца (getDate() необходим для этого, так как getDay() возвращает день недели, 1-7), месяц (возвращает число от 0 до 11, см. ниже) и год (getFullYear() необходим; getYear() устарел и возвращает странное значение, которое мало кому пригодится!).
const objectStore = db.transaction(['toDoList'], "readwrite").objectStore('toDoList');
objectStore.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
let monthNumber;
if (cursor) {
Далее мы создаём ещё одну транзакцию IndexedDB objectStore, и используем метод openCursor() для открытия курсора, который, по сути, является способом в IndexedDB итерироваться по всем элементам в хранилище. Затем мы итерируемся по всем элементам в курсоре до тех пор, пока в курсоре есть действительный элемент.
switch (cursor.value.month) {
case "January":
monthNumber = 0;
break;
case "February":
monthNumber = 1;
break;
// other lines removed from listing for brevity
case "December":
monthNumber = 11;
break;
default:
alert("Incorrect month entered in database.");
}
Первое, что мы делаем, это конвертируем названия месяцев, которые мы сохранили в базе данных, в число месяца, которое поймёт JavaScript. Как мы видели раньше, объект JavaScript Date создаёт значения месяцев в виде числа от 0 до 11.
if (
Number(cursor.value.hours) === hourCheck &&
Number(cursor.value.minutes) === minuteCheck &&
Number(cursor.value.day) === dayCheck &&
monthNumber === monthCheck &&
cursor.value.year === yearCheck &&
notified === "no"
) {
// If the numbers all do match, run the createNotification()
// function to create a system notification
createNotification(cursor.value.taskTitle);
}
С текущими частями времени и даты, которые мы хотим проверить по отношению к сохранённым значениям в IndexedDB, всё собрано, настало время выполнить проверки. Мы хотим, чтобы все значения совпадали, прежде чем показывать пользователю какое-либо уведомление, чтобы сказать им, что их срок выполнения истек.
Оператор + в данном случае преобразует числа с ведущими нулями в их эквиваленты без ведущих нулей, например, 09 -> 9. Это необходимо, потому что числа JavaScript Date никогда не имеют ведущих нулей, но наши данные могут их иметь.
Проверка notified === "no" предназначена для того, чтобы вы получали только одно уведомление на каждый элемент задачи. Когда для каждого объекта задачи генерируется уведомление, его свойство notification устанавливается в "yes", поэтому эта проверка не пройдёт на следующей итерации, посредством следующего кода внутри функции createNotification() (см. Использование IndexedDB для объяснения):
// now we need to update the value of notified to "yes" in this particular data object, so the
// notification won't be set off on it again
// first open up a transaction as usual
const objectStore = db.transaction(['toDoList'], "readwrite").objectStore('toDoList');
// get the to-do list object that has this title as its title
const request = objectStore.get(title);
request.onsuccess = () => {
// grab the data object returned as the result
const data = request.result;
// update the notified value in the object to "yes"
data.notified = "yes";
// create another request that inserts the item back into the database
const requestUpdate = objectStore.put(data);
// when this new request succeeds, run the displayData() function again to update the display
requestUpdate.onsuccess = () => {
displayData();
}
Если все проверки пройдут, мы затем запускаем функцию createNotification() для предоставления уведомления пользователю.
cursor.continue();
}
}
}
Последняя строка функции перемещает курсор, что заставляет механизм проверки сроков выполнения выше быть запущенным для следующей задачи, хранящейся в IndexedDB.
Продолжайте проверку!
Конечно, нет смысла запускать функцию проверки сроков выполнения выше только один раз! Мы хотим постоянно проверять все сроки, чтобы увидеть, не наступил ли какой-либо из них. Для этого мы используем setInterval() для запуска checkDeadlines() один раз в секунду:
setInterval(checkDeadlines, 1000);
© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Checking_when_a_deadline_is_due