|
907 | 907 | const modalTitle = type === 'add' ? 'Новый клиент' : 'Изменить данные'; |
908 | 908 | const modalCancelBtn = type === 'add' ? 'Отмена' : 'Удалить клиента'; |
909 | 909 |
|
| 910 | + const modalContactsArr = []; // организация массива контактов (будущих контактов) |
| 911 | + |
910 | 912 | const modalWrap = document.createElement('div'); |
911 | 913 | const modalDialog = document.createElement('div'); |
912 | 914 | const modalContent = document.createElement('div'); |
|
1145 | 1147 | modalDialog.append(modalContent); |
1146 | 1148 | modalWrap.append(modalDialog); |
1147 | 1149 |
|
1148 | | - // ! |
1149 | | - // запуск логики добавления/создания строки контактов |
| 1150 | + // вызов/инициализация tooltips для "X" модальной кнопки (для кнопки закрытия модального окна, с задержкой) |
| 1151 | + setTimeout(() => { |
| 1152 | + initTippy(modalHeaderXBtn, 'закрыть', 'left'); |
| 1153 | + }, 0); |
| 1154 | + |
| 1155 | + // запуск логики добавления/создания "внутренней" строки контактов (передача объекта/context(a) с необходимыми элементами/сущностями) |
1150 | 1156 | modalBodyAddBtn.addEventListener('click', () => { |
1151 | | - createModalContactsElement(); |
| 1157 | + createModalContactsElement({ |
| 1158 | + modalWrap, |
| 1159 | + modalBodyAddBtn, |
| 1160 | + modalContactsArr, |
| 1161 | + }); |
1152 | 1162 | }); |
1153 | 1163 |
|
| 1164 | + // запуск проверки на "пустые" контакты (перед закрытием модального окна) |
| 1165 | + modalWrap.addEventListener('hide.bs.modal', checkEmptyRowContacts); |
| 1166 | + |
| 1167 | + // удаление/очистка от невалидных row-контактов (при закрытие модального окна, передача объекта/context(a)) |
| 1168 | + modalWrap.addEventListener('hidden.bs.modal', () => |
| 1169 | + removeInvalidRowContacts({ |
| 1170 | + modalBodyAddBtn, |
| 1171 | + modalContactsArr, |
| 1172 | + }) |
| 1173 | + ); |
| 1174 | + |
1154 | 1175 | return modalWrap; // возврат модального окна (т.е. здесь/без добавления в DOM.. позже, при клике) |
1155 | 1176 | } |
1156 | 1177 |
|
|
1175 | 1196 | } |
1176 | 1197 |
|
1177 | 1198 | // ** организация "динамического" добавления строки контакта/row-contact (по нажатию "Добавить контакт" кнопки, в модальных/универсальных окнах) |
1178 | | - const modalContactsArr = []; |
| 1199 | + function createModalContactsElement(context = {}) { |
| 1200 | + const { modalWrap, modalBodyAddBtn, modalContactsArr } = context; // получение необходимых элементов/сущностей (через деструктуризациию входящего/передаваемого объекта) |
1179 | 1201 |
|
1180 | | - function createModalContactsElement() { |
1181 | 1202 | if (modalContactsArr.length >= 10) return; // проверка количества контактов (не более 10) |
1182 | 1203 |
|
1183 | 1204 | const modalContactElement = document.createElement('div'); |
|
1354 | 1375 | dynamicContactValidation: true, |
1355 | 1376 | }); |
1356 | 1377 |
|
1357 | | - modalContactsArr.push(modalContactElement); // добавление контакта во внешний/глобальный массив |
| 1378 | + modalContactsArr.push(modalContactElement); // добавление контакта во "внешний" массив |
1358 | 1379 |
|
1359 | 1380 | // исключение ещё/прожатия кнопки "Добавить контакт", если контактов/уже 10 (вывод сообщения) |
1360 | 1381 | if (modalContactsArr.length >= 10) { |
|
1397 | 1418 | // замена/обновление содержимого/контента кнопки, так.. и через TAB/Enter (согласно значений li/вариантов) |
1398 | 1419 | modalContactList.addEventListener('click', (event) => { |
1399 | 1420 | if (event.target.tagName === 'LI') { |
1400 | | - getContactDropSelection( |
1401 | | - event.target, |
| 1421 | + getContactDropSelection(event.target, { |
1402 | 1422 | modalContactDropBtn, |
1403 | 1423 | modalContactList, |
1404 | 1424 | modalContactHiddenInput, |
1405 | | - modalContactInput |
1406 | | - ); // отработка выбора |
| 1425 | + modalContactInput, |
| 1426 | + }); // отработка выбора (передача context(a)) |
1407 | 1427 | } |
1408 | 1428 | }); |
1409 | 1429 |
|
1410 | 1430 | modalContactList.addEventListener('keydown', (event) => { |
1411 | 1431 | if (event.target.tagName === 'LI' && event.key === 'Enter') { |
1412 | 1432 | event.preventDefault(); // исключение непредвиденных событий/поведения |
1413 | | - getContactDropSelection( |
1414 | | - event.target, |
| 1433 | + getContactDropSelection(event.target, { |
1415 | 1434 | modalContactDropBtn, |
1416 | 1435 | modalContactList, |
1417 | 1436 | modalContactHiddenInput, |
1418 | | - modalContactInput |
1419 | | - ); // отработка выбора |
| 1437 | + modalContactInput, |
| 1438 | + }); // отработка выбора (передача context(a)) |
1420 | 1439 | } |
1421 | 1440 | }); |
1422 | 1441 |
|
|
1434 | 1453 | } |
1435 | 1454 | }); |
1436 | 1455 |
|
1437 | | - addModalWrap.addEventListener('focusout', (event) => { |
| 1456 | + modalWrap.addEventListener('focusout', (event) => { |
1438 | 1457 | const openDropdownBtn = document.querySelector('.drop-open'); |
1439 | 1458 |
|
1440 | 1459 | // закрытие/скрытие выпадающего списка (если "фокус" перешёл на другой элемент, в другое место) |
|
1460 | 1479 | // организация удаления строки контактов |
1461 | 1480 | modalContactXBtn.addEventListener('click', (event) => { |
1462 | 1481 | event.stopPropagation(); // исключение непредвиденных событий/поведения |
1463 | | - deleteModalContactsElement(event); // удаление строки контактов (посредствам "X" кнопки) |
| 1482 | + deleteModalContactsElement(event, { |
| 1483 | + modalBodyAddBtn, |
| 1484 | + modalContactsArr, |
| 1485 | + }); // удаление строки контактов (посредствам "X" кнопки, передача объекта/context(a)) |
1464 | 1486 | }); |
1465 | 1487 |
|
1466 | 1488 | // организация удаления строки контактов и через TAB/Enter |
1467 | 1489 | modalContactXBtn.addEventListener('keydown', (event) => { |
1468 | 1490 | if (event.key === 'Enter') { |
1469 | 1491 | event.stopPropagation(); // исключение непредвиденных событий/поведения |
1470 | | - deleteModalContactsElement(event); // удаление строки контактов (посредствам "X" кнопки) |
| 1492 | + deleteModalContactsElement(event, { |
| 1493 | + modalBodyAddBtn, |
| 1494 | + modalContactsArr, |
| 1495 | + }); // удаление строки контактов (посредствам "X" кнопки, передача объекта/context(a)) |
1471 | 1496 | } |
1472 | 1497 | }); |
1473 | 1498 | } |
1474 | 1499 |
|
1475 | 1500 | // ** обработка выбора/типа строки контакта, согласно вариантов выпадающего drop-btn списка (в модальных/универсальных окнах, ряд сопутствующих действий) |
1476 | | - function getContactDropSelection( |
1477 | | - target, |
1478 | | - dropBtn, |
1479 | | - contactList, |
1480 | | - hiddenInput, |
1481 | | - contactInput |
1482 | | - ) { |
| 1501 | + function getContactDropSelection(target, context = {}) { |
1483 | 1502 | const selectedItemValue = target.getAttribute('data-value'); |
1484 | | - const previousItemValue = hiddenInput.value; // фиксация скрытого значения |
| 1503 | + const { |
| 1504 | + modalContactDropBtn, |
| 1505 | + modalContactList, |
| 1506 | + modalContactHiddenInput, |
| 1507 | + modalContactInput, |
| 1508 | + } = context; // получение необходимых элементов/сущностей (через деструктуризациию входящего/передаваемого объекта) |
| 1509 | + |
| 1510 | + const previousItemValue = modalContactHiddenInput.value; // фиксация скрытого значения |
1485 | 1511 |
|
1486 | 1512 | // обработка смены/типа контакта (если/с одного "вдруг" на другой решили) |
1487 | 1513 | if ( |
1488 | | - !changeContactRowType(contactInput, selectedItemValue, previousItemValue) |
| 1514 | + !changeContactRowType( |
| 1515 | + modalContactInput, |
| 1516 | + selectedItemValue, |
| 1517 | + previousItemValue |
| 1518 | + ) |
1489 | 1519 | ) { |
1490 | 1520 | return; // исключение корректировки, если/в confirm отмена |
1491 | 1521 | } |
1492 | 1522 |
|
1493 | | - dropBtn.textContent = target.textContent; |
1494 | | - hiddenInput.value = selectedItemValue; // обновление данных в "скрытом" input (для последующей отправки на сервер) |
| 1523 | + modalContactDropBtn.textContent = target.textContent; |
| 1524 | + modalContactHiddenInput.value = selectedItemValue; // обновление данных в "скрытом" input (для последующей отправки на сервер) |
1495 | 1525 |
|
1496 | 1526 | target.style.display = 'none'; // скрытие li/варианта в выпадающем списке (т.е. после выбора, отображения в drop-btn) |
1497 | 1527 |
|
1498 | 1528 | // отображение/снова li/варианта (т.е. до этого скрытого) |
1499 | 1529 | if (previousItemValue) { |
1500 | | - const previousItem = contactList.querySelector( |
| 1530 | + const previousItem = modalContactList.querySelector( |
1501 | 1531 | `[data-value="${previousItemValue}"]` |
1502 | 1532 | ); |
1503 | 1533 | if (previousItem) { |
1504 | 1534 | previousItem.style.display = ''; // сброс display: none; |
1505 | 1535 | } |
1506 | 1536 | } |
1507 | 1537 |
|
1508 | | - updateRowInputType(contactInput, selectedItemValue); // обновление атрибута/значения "type" у/для инпута (кому возможно) |
1509 | | - updateDropItemPaddings(contactList); // обновление/изменение отступов для li/вариантов выпадающего списка (для первого и последнего элементов) |
| 1538 | + updateRowInputType(modalContactInput, selectedItemValue); // обновление атрибута/значения "type" у/для инпута (кому возможно) |
| 1539 | + updateDropItemPaddings(modalContactList); // обновление/изменение отступов для li/вариантов выпадающего списка (для первого и последнего элементов) |
1510 | 1540 | closeBtnDropdown(); // закрытие выпадающего списка |
1511 | | - contactInput.focus(); // перевод фокуса на соседний инпут (после выбора в выпадающем списке) |
| 1541 | + modalContactInput.focus(); // перевод фокуса на соседний инпут (после выбора в выпадающем списке) |
1512 | 1542 | } |
1513 | 1543 |
|
1514 | 1544 | // ** обновление атрибута/значения "type" у/для модального row-contact инпута (кому возможно, после выбора) |
1515 | | - function updateRowInputType(input, contactType) { |
| 1545 | + function updateRowInputType(modalContactInput, contactType) { |
1516 | 1546 | // объект для сопоставления (кому заменять, на какое значение) |
1517 | 1547 | const typeMapping = { |
1518 | 1548 | phone: 'tel', |
|
1522 | 1552 |
|
1523 | 1553 | // корректировка атрибута/значения "type" (или будет text) |
1524 | 1554 | const newType = typeMapping[contactType] || 'text'; |
1525 | | - input.setAttribute('type', newType); |
| 1555 | + modalContactInput.setAttribute('type', newType); |
1526 | 1556 | } |
1527 | 1557 |
|
1528 | | - // ! корректировка |
1529 | 1558 | // ** обновление/изменение отступов у/для модального row-contact списка (для первого и последнего li/элементов) |
1530 | | - function updateDropItemPaddings(dropList) { |
1531 | | - Array.from(dropList.children).forEach((item) => { |
| 1559 | + function updateDropItemPaddings(modalContactList) { |
| 1560 | + Array.from(modalContactList.children).forEach((item) => { |
1532 | 1561 | item.classList.remove('first-visible', 'last-visible'); // изначальная очистка от дополнительных классов |
1533 | 1562 | }); |
1534 | 1563 |
|
1535 | 1564 | // фиксация li/вариантов находящихся "сейчас" в выпадающем списке |
1536 | | - const visibleDropItems = Array.from(dropList.children).filter( |
| 1565 | + const visibleDropItems = Array.from(modalContactList.children).filter( |
1537 | 1566 | (item) => item.style.display !== 'none' |
1538 | 1567 | ); |
1539 | 1568 |
|
|
1546 | 1575 | } |
1547 | 1576 | } |
1548 | 1577 |
|
1549 | | - // ! корректировка |
1550 | 1578 | // ** организация закрытия/скрытия выпадающего row-contact списка в модальном окне (снятие фокуса) |
1551 | 1579 | function closeBtnDropdown() { |
1552 | 1580 | const openDropdownBtn = document.querySelector('.drop-open'); // фиксация "открывающей" drop-кнопки |
|
1559 | 1587 | } |
1560 | 1588 | } |
1561 | 1589 |
|
1562 | | - // ! корректировка |
1563 | 1590 | // ** организация замены выбора/типа модального row-контакта, после начала/внесения данных в инпут (вывод уточняющего сообщения) |
1564 | | - function changeContactRowType(input, newType, previousType) { |
1565 | | - const inputCurrentValue = input.value.trim(); |
| 1591 | + function changeContactRowType(modalContactInput, newType, previousType) { |
| 1592 | + const inputCurrentValue = modalContactInput.value.trim(); |
1566 | 1593 |
|
1567 | 1594 | // определение типов контактов, которые схожи/одного формата (замена без изменений) |
1568 | 1595 | const phoneTypes = ['phone', 'extra-phone']; |
|
1588 | 1615 | return false; // отмена изменений |
1589 | 1616 | } |
1590 | 1617 |
|
1591 | | - input.value = ''; // очистка инпута |
| 1618 | + modalContactInput.value = ''; // очистка инпута |
1592 | 1619 | } |
1593 | 1620 |
|
1594 | 1621 | // очистка от уведомлений на некорректный ввод/валидации (от предыдущих типов, кроме схожих/телефонов) |
1595 | 1622 | if ( |
1596 | 1623 | !phoneTypes.includes(newType) || |
1597 | 1624 | (phoneTypes.includes(newType) && !phoneTypes.includes(previousType)) |
1598 | 1625 | ) { |
1599 | | - const feedback = input |
| 1626 | + const feedback = modalContactInput |
1600 | 1627 | .closest('.modal-contact-element') |
1601 | 1628 | .querySelector('.invalid-feedback'); |
| 1629 | + |
1602 | 1630 | if (feedback) { |
1603 | 1631 | feedback.textContent = ''; // удаление сообщений |
1604 | 1632 | } |
1605 | | - input.classList.remove('is-invalid'); // исключение классов ошибки |
| 1633 | + modalContactInput.classList.remove('is-invalid'); // исключение классов ошибки |
1606 | 1634 | } |
1607 | 1635 |
|
1608 | | - updateRowInputType(input, newType); // обновление атрибута/значения "type" у/дя инпута (нового выбора) |
| 1636 | + updateRowInputType(modalContactInput, newType); // обновление атрибута/значения "type" у/дя инпута (нового выбора) |
1609 | 1637 | return true; // факт завершения |
1610 | 1638 | } |
1611 | 1639 |
|
1612 | | - // ! корректировка |
1613 | 1640 | // ** удаление строки row-контакта в модальном окне (через "X" кнопку, с/без уточняющего сообщения) |
1614 | | - function deleteModalContactsElement(event) { |
| 1641 | + function deleteModalContactsElement(event, context = {}) { |
1615 | 1642 | const clickedContactsXBtn = event.currentTarget; // получение ИМЕННО кнопки, а не/может внутренней иконки (согласно "размазанного" события) |
| 1643 | + const { modalBodyAddBtn, modalContactsArr } = context; // получение необходимых элементов/сущностей (через деструктуризациию входящего/передаваемого объекта) |
1616 | 1644 |
|
1617 | 1645 | if (clickedContactsXBtn) { |
1618 | 1646 | tippy.hideAll(); // предварительное скрытие всех/вдруг "активных" tooltips (перед удалением искомой строки) |
|
1658 | 1686 | .length === 0 |
1659 | 1687 | ) { |
1660 | 1688 | const modalContactsRowWrap = document.querySelector( |
1661 | | - '.modal__add-body-add-contacts-row-wrap' |
| 1689 | + '.modal__body-contacts-row-wrap' |
1662 | 1690 | ); |
1663 | 1691 | modalContactsRowWrap.classList.add('d-none'); |
1664 | 1692 | modalBodyAddBtn.classList.remove('modal-contact-btn-margin'); |
|
1670 | 1698 | } |
1671 | 1699 | } |
1672 | 1700 |
|
1673 | | - // ! корректировка |
1674 | 1701 | // ** организация проверки на "пустые" row-контакты, перед закрытием соответствующего модального окна (вывод сообщения) |
1675 | 1702 | function checkEmptyRowContacts(event) { |
1676 | 1703 | const allContactRows = document.querySelectorAll('.modal-contact-element'); |
|
1696 | 1723 | } |
1697 | 1724 | } |
1698 | 1725 |
|
1699 | | - addModalWrap.addEventListener('hide.bs.modal', checkEmptyRowContacts); // запуск проверки на "пустые" контакты (перед закрытием add-модального окна) |
1700 | | - |
1701 | | - // ! корректировка |
1702 | 1726 | // ** удаление/очистка от невалидных row-контактов (при закрытии модального окна) |
1703 | | - function removeInvalidRowContacts() { |
| 1727 | + function removeInvalidRowContacts(context) { |
| 1728 | + const { modalBodyAddBtn, modalContactsArr } = context; // получение необходимых элементов/сущностей (через деструктуризациию входящего/передаваемого объекта) |
1704 | 1729 | const invalidContactRows = document.querySelectorAll( |
1705 | 1730 | '.modal-contact-element .is-invalid' |
1706 | 1731 | ); |
|
1733 | 1758 | } |
1734 | 1759 | } |
1735 | 1760 |
|
1736 | | - addModalWrap.addEventListener('hidden.bs.modal', removeInvalidRowContacts); // удаление/очистка от невалидных row-контактов (при закрытие add-модального окна) |
1737 | | - |
1738 | 1761 | // ** дополнительная/местная организация логики для tooltips (т.е. помимо customTippy.js) |
1739 | 1762 | function initTippy(selector, content, side) { |
1740 | 1763 | // определение входящего элемента (селектор или DOM-элемент, поиск/корректировка) |
|
0 commit comments