diff --git a/3 proba/js/lines-modal.js b/3 proba/js/lines-modal.js index 2291b51..b337365 100644 --- a/3 proba/js/lines-modal.js +++ b/3 proba/js/lines-modal.js @@ -1,65 +1,453 @@ -// Модуль для работы с модальным окном +/* eslint-disable linebreak-style */ +// модуль с логикой -// импорт компонента Modal из bootstrap -import { Modal } from "bootstrap"; -import { cntrls, imagePlaceholder } from "./lines-ui"; +// eslint-disable-next-line import/no-self-import +import { + hideUpdateModal, + showUpdateModal, +} from "./lines-modal"; +import { + createLine, + deleteLine, + getAllGengeTypes, + getAllAuthorTypes, + getAllLines, + getLine, + updateLine, +} from "./lines-rest-api"; +import { + cntrls, + createTableRow, + imagePlaceholder, + createGenresOption, + createAuthorsOption, + createTableRowOnIndex, +} from "./lines-ui"; -// поиск модального окна на странице -const modal = document.getElementById("items-update"); -// если он найден, то создается экземпляр компонента Modal -// для программного управления модальным окном -const myModal = modal ? new Modal(modal, {}) : null; - -// поиск тега с заголовком модального кона для его смены -const modalTitle = document.getElementById("items-update-title"); - -// обнуление значений модального окна, т. к. -// используется одно окно для всех операций -function resetValues() { - cntrls.lineId.value = ""; - cntrls.itemsType.value = ""; - cntrls.author.value = ""; - cntrls.name.value = ""; - cntrls.desc.value = ""; - cntrls.count.value = 0; - cntrls.date.value = ""; - cntrls.image.value = ""; - cntrls.imagePreview.src = imagePlaceholder; +async function drawGenreSelect() { + // вызов метода REST API для получения списка типов товаров + const data = await getAllGengeTypes(); + const data2 = await getAllAuthorTypes(); + // очистка содержимого select + // удаляется все, что находится между тегами + // но не атрибуты + cntrls.genresType.innerHTML = ""; + cntrls.authorsType.innerHTML = ""; + // пустое значение + cntrls.genresType.appendChild(createGenresOption("Выберите значение", "", true)); + cntrls.authorsType.appendChild(createGenresOption("Выберите значение", "", true)); + // цикл по результату ответа от сервера + // используется лямбда-выражение + // (item) => {} аналогично function(item) {} + data.forEach((genre) => { + cntrls.genresType.appendChild(createGenresOption(genre.name, genre.id)); + }); + data2.forEach((author) => { + cntrls.authorsType.appendChild(createAuthorsOption(author.name, author.id)); + }); } -// функция для показа модального окна -// перед показом происходит заполнение формы для редактирования -// если объект item не пуст -export function showUpdateModal(item) { - modalTitle.innerHTML = item === null ? "Добавить" : "Изменить"; - console.info(item); +async function drawLinesTable() { + console.info("Try to load data"); + if (!cntrls.table) { + return; + } + // вызов метода REST API для получения всех записей + const data = await getAllLines(); + // очистка содержимого table + // удаляется все, что находится между тегами
+ // но не атрибуты + cntrls.table.innerHTML = ""; + // цикл по результату ответа от сервера + // используется лямбда-выражение + // (item, index) => {} аналогично function(item, index) {} + data.forEach((item, index) => { + cntrls.table.appendChild( + createTableRow( + item, + index, + // функции передаются в качестве параметра + // это очень удобно, так как аргументы функций доступны только + // в данном месте кода и не передаются в сервисные модули + () => showUpdateModal(item), + () => removeLine(item.id), + ), + ); + }); +} - if (item) { - cntrls.lineId.value = item.id; - cntrls.itemsType.value = item.itemsId; - cntrls.author.value = item.authorsId; - cntrls.name.value = item.name; - cntrls.desc.value = item.desc; - cntrls.count.value = item.count; - cntrls.date.value = item.date; - // заполнение превью - // Если пользователь выбрал изображение, то оно загружается - // в тэг image с id image - preview - // иначе устанавливается заглушка, адрес которой указан в imagePlaceholder - cntrls.imagePreview.src = item.image ? item.image : imagePlaceholder; - } else { - resetValues(); +async function drawLinesTableOnIndex() { + console.info("Try to load data On Index"); + if (!cntrls.container) { + return; + } + // вызов метода REST API для получения всех записей + const data = await getAllLines(); + // очистка содержимого table + // удаляется все, что находится между тегами
+ // но не атрибуты + cntrls.container.innerHTML = ""; + // цикл по результату ответа от сервера + // используется лямбда-выражение + // (item, index) => {} аналогично function(item, index) {} + data.forEach((item, index) => { + cntrls.container.appendChild( + createTableRowOnIndex( + item, + index, + // функции передаются в качестве параметра + // это очень удобно, так как аргументы функций доступны только + // в данном месте кода и не передаются в сервисные модули + () => showUpdateModal(item), + () => location.assign(`page3.html?id=${item.id}`), + () => removeLine(item.id), + ), + ); + }); +} + +async function addLine(nameBook, authorsType, genresType, year, description, count, date, image) { + console.info("Try to add item"); + // вызов метода REST API для добавления записи + // eslint-disable-next-line max-len + const data = await createLine(nameBook, authorsType, genresType, year, description, count, date, image); + console.info("Added"); + console.info(data); + // загрузка и заполнение table + drawLinesTable(); +} + +// eslint-disable-next-line max-len +async function editLine(id, nameBook, authorsType, genresType, year, description, count, date, image) { + console.info("Try to update item"); + console.log(id, nameBook, authorsType, genresType, description, count, date, image); + // вызов метода REST API для обновления записи + // eslint-disable-next-line max-len + const data = await updateLine(id, nameBook, authorsType, genresType, year, description, count, date, image); + console.info("Updated"); + console.info(data); + // загрузка и заполнение table + drawLinesTable(); +} + +async function removeLine(id) { + if (!confirm("Вы точно хотите удалить этот товар?")) { + console.info("Canceled"); + return; + } + console.info("Try to remove item"); + // вызов метода REST API для удаления записи + const data = await deleteLine(id); + console.info(data); + // загрузка и заполнение table + drawLinesTable(); +} + +// функция для получения содержимого файла в виде base64 строки +// https://ru.wikipedia.org/wiki/Base64 +async function readFile(file) { + const reader = new FileReader(); + + // создание Promise-объекта для использования функции + // с помощью await (асинхронно) без коллбэков (callback) + // https://learn.javascript.ru/promise + return new Promise((resolve, reject) => { + // 2. "Возвращаем" содержимое когда файл прочитан + // через вызов resolve + // Если не использовать Promise, то всю работу по взаимодействию + // с REST API пришлось бы делать в обработчике (callback) функции + // onloadend + reader.onloadend = () => { + const fileContent = reader.result; + // Здесь могла бы быть работа с REST API + // Чтение заканчивает выполняться здесь + resolve(fileContent); + }; + // 3. Возвращаем ошибку + reader.onerror = () => { + // Или здесь в случае ошибки + reject(new Error("oops, something went wrong with the file reader.")); + }; + // Шаг 1. Сначала читаем файл + // Чтение начинает выполняться здесь + reader.readAsDataURL(file); + }); +} + +// функция для обновления блока с превью выбранного изображения +async function updateImagePreview() { + // получение выбранного файла + // возможен выбор нескольких файлов, поэтому необходимо получить только первый + const file = cntrls.image.files[0]; + // чтение содержимого файла в виде base64 строки + const fileContent = await readFile(file); + console.info("base64 ", fileContent); + // обновление атрибута src для тега img с id image-preview + cntrls.imagePreview.src = fileContent; +} + +// Функция для обработки создания и редактирования элементов таблицы через модальное окно +// Если хотите делать через страницу, то удалите эту функцию +export function linesForm() { + console.info("linesForm"); + + // загрузка и заполнение select со списком товаров + drawGenreSelect(); + // drawAuthorSelect(); + // загрузка и заполнение table + drawLinesTable(); + + // Вызов функции обновления превью изображения при возникновении + // события oncahnge в тэге input с id image + cntrls.image.addEventListener("change", () => updateImagePreview()); + + // обработчик события нажатия на кнопку для показа модального окна + cntrls.button.addEventListener("click", () => showUpdateModal(null)); + + // обработчик события отправки формы + // возникает при нажатии на кнопку (button) с типом submit + // кнопка должна находится внутри тега form + cntrls.form.addEventListener("submit", async (event) => { + console.info("Form onSubmit"); + // отключение стандартного поведения формы при отправке + // при отправке страница обновляется и JS перестает работать + event.preventDefault(); + event.stopPropagation(); + // если форма не прошла валидацию, то ничего делать не нужно + if (!cntrls.form.checkValidity()) { + return; + } + + let imageBase64 = ""; + // Получение выбранного пользователем изображения в виде base64 строки + // Если пользователь ничего не выбрал, то не нужно сохранять в БД + // дефолтное изображение + if (cntrls.imagePreview.src !== imagePlaceholder) { + // Загрузка содержимого атрибута src тэга img с id image-preview + // Здесь выполняется HTTP запрос с типом GET + const result = await fetch(cntrls.imagePreview.src); + // Получение из HTTP-ответа бинарного содержимого + const blob = await result.blob(); + // Получение base64 строки для файла + // Здесь выполняется Promise из функции readFile + // Promise позволяет писать линейный код для работы с асинхронными методами + // без использования обработчиков (callback) с помощью await + imageBase64 = await readFile(blob); + } + + // получение id строки для редактирования + // это значение содержится в скрытом input + const currentId = cntrls.lineId.value; + // если значение id не задано, + // то необходимо выполнить добавление записи + // иначе обновление записи + if (!currentId) { + await addLine( + cntrls.nameBook.value, + cntrls.authorsType.value, + cntrls.genresType.value, + cntrls.year.value, + cntrls.description.value, + cntrls.count.value, + cntrls.date.value, + imageBase64, + ); + } else { + await editLine( + currentId, + cntrls.nameBook.value, + cntrls.authorsType.value, + cntrls.genresType.value, + cntrls.year.value, + cntrls.description.value, + cntrls.count.value, + cntrls.date.value, + imageBase64, + ); + } + + // после выполнения добавления/обновления модальное окно скрывается + hideUpdateModal(); + }); +} + +export async function linesFormOnIndex() { + console.info("linesFormOnIndex"); + + // await drawGenreSelect(); + await drawLinesTableOnIndex(); + + // обработчик события отправки формы + // возникает при нажатии на кнопку (button) с типом submit + // кнопка должна находится внутри тега form + cntrls.form.addEventListener("submit", async (event) => { + console.info("Form onSubmit"); + // отключение стандартного поведения формы при отправке + // при отправке страница обновляется и JS перестает работать + event.preventDefault(); + event.stopPropagation(); + // если форма не прошла валидацию, то ничего делать не нужно + if (!cntrls.form.checkValidity()) { + return; + } + + let imageBase64 = ""; + // Получение выбранного пользователем изображения в виде base64 строки + // Если пользователь ничего не выбрал, то не нужно сохранять в БД + // дефолтное изображение + if (cntrls.imagePreview.src !== imagePlaceholder) { + // Загрузка содержимого атрибута src тэга img с id image-preview + // Здесь выполняется HTTP запрос с типом GET + const result = await fetch(cntrls.imagePreview.src); + // Получение из HTTP-ответа бинарного содержимого + const blob = await result.blob(); + // Получение base64 строки для файла + // Здесь выполняется Promise из функции readFile + // Promise позволяет писать линейный код для работы с асинхронными методами + // без использования обработчиков (callback) с помощью await + imageBase64 = await readFile(blob); + } + + // получение id строки для редактирования + // это значение содержится в скрытом input + const currentId = cntrls.lineId.value; + // если значение id не задано, + // то необходимо выполнить добавление записи + // иначе обновление записи + if (!currentId) { + await addLine( + cntrls.nameBook.value, + cntrls.authorsType.value, + cntrls.genresType.value, + cntrls.year.value, + cntrls.description.value, + cntrls.count.value, + cntrls.date.value, + imageBase64, + ); + } else { + await editLine( + currentId, + cntrls.nameBook.value, + cntrls.authorsType.value, + cntrls.genresType.value, + cntrls.year.value, + cntrls.description.value, + cntrls.count.value, + cntrls.date.value, + imageBase64, + ); + } + + // после выполнения добавления/обновления модальное окно скрывается + hideUpdateModal(); + }); +} +// Функция для обработки создания и редактирования элементов таблицы через страницу page-admin.html +// Если хотите делать через модальное окно, то удалите эту функцию +export async function linesPageForm() { + console.info("linesPageForm"); + + // загрузка и заполнение select со списком товаров + // drawGenreSelect(); + + // func1 = (id) => {} аналогично function func1(id) {} + const goBack = () => location.assign("/page3.html"); + + // Вызов функции обновления превью изображения при возникновении + // события onchange в тэге input с id image + cntrls.image.addEventListener("change", () => updateImagePreview()); + + // получение параметров GET-запроса из URL + // параметры перечислены после символа ? (?id=1&color=black&...) + const urlParams = new URLSearchParams(location.search); + + // получение значения конкретного параметра (id) + // указан только при редактировании + const currentId = urlParams.get("id"); + // если id задан + if (currentId) { + try { + // вызов метода REST API для получения записи по первичному ключу(id) + const line = await getLine(currentId); + // заполнение формы для редактирования + cntrls.nameBook.value = line.nameBook; + cntrls.authorsType.value = line.authorsType; + cntrls.genresType.value = line.genresType; + cntrls.year.value = line.year; + cntrls.description.value = line.description; + cntrls.count.value = line.count; + cntrls.date.value = line.date; + // заполнение превью + // Если пользователь выбрал изображение, то оно загружается + // в тэг image с id image - preview + // иначе устанавливается заглушка, адрес которой указан в imagePlaceholder + cntrls.imagePreview.src = line.image ? line.image : imagePlaceholder; + } catch { + // в случае ошибки происходит возврат к index + goBack(); + } } - myModal.show(); -} - -// функция для скрытия модального окна -export function hideUpdateModal() { - resetValues(); - - // удаление класса was-validated для скрытия результатов валидации - cntrls.form.classList.remove("was-validated"); - - myModal.hide(); + // обработчик события отправки формы + // возникает при нажатии на кнопку (button) с типом submit + // кнопка должна находится внутри тега form + cntrls.form.addEventListener("submit", async (event) => { + console.info("Form onSubmit"); + // отключение стандартного поведения формы при отправке + // при отправке страница обновляется и JS перестает работать + event.preventDefault(); + event.stopPropagation(); + // если форма не прошла валидацию, то ничего делать не нужно + if (!cntrls.form.checkValidity()) { + return; + } + + let imageBase64 = ""; + // Получение выбранного пользователем изображения в виде base64 строки + // Если пользователь ничего не выбрал, то не нужно сохранять в БД + // дефолтное изображение + if (cntrls.imagePreview.src !== imagePlaceholder) { + // Загрузка содержимого атрибута src тэга img с id image-preview + // Здесь выполняется HTTP запрос с типом GET + const result = await fetch(cntrls.imagePreview.src); + // Получение из HTTP-ответа бинарного содержимого + const blob = await result.blob(); + // Получение base64 строки для файла + // Здесь выполняется Promise из функции readFile + // Promise позволяет писать линейный код для работы с асинхронными методами + // без использования обработчиков (callback) с помощью await + imageBase64 = await readFile(blob); + } + + // если значение параметра запроса не задано, + // то необходимо выполнить добавление записи + // иначе обновление записи + if (!currentId) { + await addLine( + cntrls.nameBook.value, + cntrls.authorsType.value, + cntrls.genresType.value, + cntrls.year.value, + cntrls.description.value, + cntrls.count.value, + cntrls.date.value, + imageBase64, + ); + } else { + await editLine( + currentId, + cntrls.nameBook.value, + cntrls.authorsType.value, + cntrls.genresType.value, + cntrls.year.value, + cntrls.description.value, + cntrls.count.value, + cntrls.date.value, + imageBase64, + ); + } + // возврат к странице index + goBack(); + }); } diff --git a/3 proba/js/lines-rest-api.js b/3 proba/js/lines-rest-api.js index daaac3b..3a382a6 100644 --- a/3 proba/js/lines-rest-api.js +++ b/3 proba/js/lines-rest-api.js @@ -1,3 +1,4 @@ +/* eslint-disable linebreak-style */ // модуль для работы с REST API сервера // адрес сервера diff --git a/3 proba/js/lines-ui.js b/3 proba/js/lines-ui.js index d89e46b..8e63ed7 100644 --- a/3 proba/js/lines-ui.js +++ b/3 proba/js/lines-ui.js @@ -1,5 +1,7 @@ +/* eslint-disable linebreak-style */ // модуль для работы с элементами управления +// eslint-disable-next-line linebreak-style // объект для удобного получения элементов // при обращении к атрибуту объекта вызывается // нужная функция для поиска элемента diff --git a/3 proba/js/lines.js b/3 proba/js/lines.js index 622216f..ac7e063 100644 --- a/3 proba/js/lines.js +++ b/3 proba/js/lines.js @@ -1,5 +1,9 @@ +/* eslint-disable linebreak-style */ +/* eslint-disable import/named */ +/* eslint-disable linebreak-style */ // модуль с логикой +// eslint-disable-next-line import/named import { hideUpdateModal, showUpdateModal } from "./lines-modal"; import { createLine, diff --git a/3 proba/отчет 3.docx b/3 proba/отчет 3.docx new file mode 100644 index 0000000..48b5c49 Binary files /dev/null and b/3 proba/отчет 3.docx differ