|
579 | 579 |
|
580 | 580 | // исключение недопустимых символов (вообще) |
581 | 581 | target.value = target.value.replace( |
582 | | - /[=+,!?%#$^&:{}()<>|"'*/\\]/g, |
| 582 | + /[=+,`~!?%#$^&:{}()<>|"'*/\\]/g, |
583 | 583 | '' |
584 | 584 | ); |
585 | 585 |
|
|
1102 | 1102 | // остановка мониторинга (если более не требуется) |
1103 | 1103 | // observer.disconnect(); |
1104 | 1104 |
|
1105 | | - // ** динамическое добавление строки контактов в add-модальном окне (по нажатию "Добавить контакт" кнопки, объёмная логика.. ряд "внутренних" функций) |
| 1105 | + // ** динамическое добавление строки контактов в add-модальном окне (по нажатию "Добавить контакт" кнопки) |
1106 | 1106 | const addModalContactsArr = []; |
1107 | 1107 |
|
1108 | 1108 | function createAddModalContactsElement() { |
|
1326 | 1326 | } |
1327 | 1327 | }); |
1328 | 1328 |
|
1329 | | - // замена/обновление содержимого/контента кнопки, так и через TAB/Enter (согласно значений li/вариантов) |
| 1329 | + // замена/обновление содержимого/контента кнопки, так.. и через TAB/Enter (согласно значений li/вариантов) |
1330 | 1330 | addModalContactList.addEventListener('click', (event) => { |
1331 | 1331 | if (event.target.tagName === 'LI') { |
1332 | | - getContactDropSelection(event.target); // отработка выбора |
| 1332 | + getContactDropSelection( |
| 1333 | + event.target, |
| 1334 | + addModalContactDropBtn, |
| 1335 | + addModalContactList, |
| 1336 | + addModalContactHiddenInput, |
| 1337 | + addModalContactInput |
| 1338 | + ); // отработка выбора |
1333 | 1339 | } |
1334 | 1340 | }); |
1335 | 1341 |
|
1336 | 1342 | addModalContactList.addEventListener('keydown', (event) => { |
1337 | 1343 | if (event.target.tagName === 'LI' && event.key === 'Enter') { |
1338 | 1344 | event.preventDefault(); // исключение непредвиденных событий/поведения |
1339 | | - getContactDropSelection(event.target); // отработка выбора |
| 1345 | + getContactDropSelection( |
| 1346 | + event.target, |
| 1347 | + addModalContactDropBtn, |
| 1348 | + addModalContactList, |
| 1349 | + addModalContactHiddenInput, |
| 1350 | + addModalContactInput |
| 1351 | + ); // отработка выбора |
1340 | 1352 | } |
1341 | 1353 | }); |
1342 | 1354 |
|
1343 | | - function getContactDropSelection(target) { |
1344 | | - const selectedItemValue = target.getAttribute('data-value'); |
1345 | | - const previousItemValue = addModalContactHiddenInput.value; // фиксация скрытого значения |
1346 | | - |
1347 | | - // обработка смены/типа контакта (если/с одного "вдруг" на другой решили) |
1348 | | - if ( |
1349 | | - !changeContactRowType( |
1350 | | - addModalContactInput, |
1351 | | - selectedItemValue, |
1352 | | - previousItemValue |
1353 | | - ) |
1354 | | - ) { |
1355 | | - return; // исключение корректировки, если/в confirm отмена |
1356 | | - } |
1357 | | - |
1358 | | - addModalContactDropBtn.textContent = target.textContent; |
1359 | | - addModalContactHiddenInput.value = selectedItemValue; // обновление данных в "скрытом" input (для последующей отправки на сервер) |
1360 | | - |
1361 | | - target.style.display = 'none'; // скрытие li/варианта в выпадающем списке (т.е. после выбора, отображения в drop-btn) |
1362 | | - |
1363 | | - // отображение/снова li/варианта (т.е. до этого скрытого) |
1364 | | - if (previousItemValue) { |
1365 | | - const previousItem = addModalContactList.querySelector( |
1366 | | - `[data-value="${previousItemValue}"]` |
1367 | | - ); |
1368 | | - if (previousItem) { |
1369 | | - previousItem.style.display = ''; // сброс display: none; |
1370 | | - } |
1371 | | - } |
1372 | | - |
1373 | | - updateRowInputType(addModalContactInput, selectedItemValue); // обновление атрибута/значения "type" у/для инпута (кому возможно) |
1374 | | - updateDropItemPaddings(addModalContactList); // обновление/изменение отступов для li/вариантов выпадающего списка (для первого и последнего элементов) |
1375 | | - closeBtnDropdown(); // закрытие выпадающего списка |
1376 | | - addModalContactInput.focus(); // перевод фокуса на соседний инпут (после выбора в выпадающем списке) |
1377 | | - } |
1378 | | - |
1379 | | - function updateDropItemPaddings(dropList) { |
1380 | | - Array.from(dropList.children).forEach((item) => { |
1381 | | - item.classList.remove('first-visible', 'last-visible'); // изначальная очистка от дополнительных классов |
1382 | | - }); |
1383 | | - |
1384 | | - // фиксация li/вариантов находящихся "сейчас" в выпадающем списке |
1385 | | - const visibleDropItems = Array.from(dropList.children).filter( |
1386 | | - (item) => item.style.display !== 'none' |
1387 | | - ); |
1388 | | - |
1389 | | - // добавление отступов (для первого/последнего элементов списка) |
1390 | | - if (visibleDropItems.length > 0) { |
1391 | | - const firstVisibleItem = visibleDropItems[0]; |
1392 | | - const lastVisibleItem = visibleDropItems[visibleDropItems.length - 1]; |
1393 | | - firstVisibleItem.classList.add('first-visible'); |
1394 | | - lastVisibleItem.classList.add('last-visible'); |
1395 | | - } |
1396 | | - } |
1397 | | - |
1398 | | - function closeBtnDropdown() { |
1399 | | - const openDropdownBtn = document.querySelector('.drop-open'); // фиксация "открывающей" drop-кнопки |
1400 | | - |
1401 | | - if (openDropdownBtn) { |
1402 | | - const dropdownList = openDropdownBtn.nextElementSibling; // фиксация выпадающего списка |
1403 | | - openDropdownBtn.classList.remove('arrow-rotate', 'drop-open'); // возврат направления стрелки, удаление "открывающего" класса |
1404 | | - dropdownList.classList.add('d-none'); // скрытие выпадающего списка |
1405 | | - openDropdownBtn.blur(); // снятие фокуса с кнопки (после выбора) |
1406 | | - } |
1407 | | - } |
1408 | | - |
1409 | 1355 | // автоматическое закрытие/скрытие развёрнутого выпадающего drop-списка (при работе НЕ с ним) |
1410 | 1356 | document.addEventListener('click', (event) => { |
1411 | 1357 | const openDropdownBtn = document.querySelector('.drop-open'); |
|
1463 | 1409 | createAddModalContactsElement(); |
1464 | 1410 | }); |
1465 | 1411 |
|
1466 | | - // ** организация замены выбора/типа row-контакта, после начала/внесения данных в инпут (вывод уточняющего сообщения) |
| 1412 | + // ** обработка выбора/типа модального row-контакта из li/вариантов выпадающего drop-списка (замена/обновление содержимого/контента drop-кнопки, ряд других действий) |
| 1413 | + function getContactDropSelection( |
| 1414 | + target, |
| 1415 | + dropBtn, |
| 1416 | + contactList, |
| 1417 | + hiddenInput, |
| 1418 | + contactInput |
| 1419 | + ) { |
| 1420 | + const selectedItemValue = target.getAttribute('data-value'); |
| 1421 | + const previousItemValue = hiddenInput.value; // фиксация скрытого значения |
| 1422 | + |
| 1423 | + // обработка смены/типа контакта (если/с одного "вдруг" на другой решили) |
| 1424 | + if ( |
| 1425 | + !changeContactRowType(contactInput, selectedItemValue, previousItemValue) |
| 1426 | + ) { |
| 1427 | + return; // исключение корректировки, если/в confirm отмена |
| 1428 | + } |
| 1429 | + |
| 1430 | + dropBtn.textContent = target.textContent; |
| 1431 | + hiddenInput.value = selectedItemValue; // обновление данных в "скрытом" input (для последующей отправки на сервер) |
| 1432 | + |
| 1433 | + target.style.display = 'none'; // скрытие li/варианта в выпадающем списке (т.е. после выбора, отображения в drop-btn) |
| 1434 | + |
| 1435 | + // отображение/снова li/варианта (т.е. до этого скрытого) |
| 1436 | + if (previousItemValue) { |
| 1437 | + const previousItem = contactList.querySelector( |
| 1438 | + `[data-value="${previousItemValue}"]` |
| 1439 | + ); |
| 1440 | + if (previousItem) { |
| 1441 | + previousItem.style.display = ''; // сброс display: none; |
| 1442 | + } |
| 1443 | + } |
| 1444 | + |
| 1445 | + updateRowInputType(contactInput, selectedItemValue); // обновление атрибута/значения "type" у/для инпута (кому возможно) |
| 1446 | + updateDropItemPaddings(contactList); // обновление/изменение отступов для li/вариантов выпадающего списка (для первого и последнего элементов) |
| 1447 | + closeBtnDropdown(); // закрытие выпадающего списка |
| 1448 | + contactInput.focus(); // перевод фокуса на соседний инпут (после выбора в выпадающем списке) |
| 1449 | + } |
| 1450 | + |
| 1451 | + // ** обновление атрибута/значения "type" у/для модального row-инпута (кому возможно, после выбора) |
| 1452 | + function updateRowInputType(input, contactType) { |
| 1453 | + // объект для сопоставления (кому заменять, на какое значение) |
| 1454 | + const typeMapping = { |
| 1455 | + phone: 'tel', |
| 1456 | + 'extra-phone': 'tel', |
| 1457 | + email: 'email', |
| 1458 | + }; |
| 1459 | + |
| 1460 | + // корректировка атрибута/значения "type" (или будет text) |
| 1461 | + const newType = typeMapping[contactType] || 'text'; |
| 1462 | + input.setAttribute('type', newType); |
| 1463 | + } |
| 1464 | + |
| 1465 | + // ** обновление/изменение отступов для модальных li/вариантов выпадающего row-списка (для первого и последнего элементов) |
| 1466 | + function updateDropItemPaddings(dropList) { |
| 1467 | + Array.from(dropList.children).forEach((item) => { |
| 1468 | + item.classList.remove('first-visible', 'last-visible'); // изначальная очистка от дополнительных классов |
| 1469 | + }); |
| 1470 | + |
| 1471 | + // фиксация li/вариантов находящихся "сейчас" в выпадающем списке |
| 1472 | + const visibleDropItems = Array.from(dropList.children).filter( |
| 1473 | + (item) => item.style.display !== 'none' |
| 1474 | + ); |
| 1475 | + |
| 1476 | + // добавление отступов (для первого/последнего элементов списка) |
| 1477 | + if (visibleDropItems.length > 0) { |
| 1478 | + const firstVisibleItem = visibleDropItems[0]; |
| 1479 | + const lastVisibleItem = visibleDropItems[visibleDropItems.length - 1]; |
| 1480 | + firstVisibleItem.classList.add('first-visible'); |
| 1481 | + lastVisibleItem.classList.add('last-visible'); |
| 1482 | + } |
| 1483 | + } |
| 1484 | + |
| 1485 | + // ** организация закрытия/скрытия выпадающего row-списка в модальном окне (снятие фокуса) |
| 1486 | + function closeBtnDropdown() { |
| 1487 | + const openDropdownBtn = document.querySelector('.drop-open'); // фиксация "открывающей" drop-кнопки |
| 1488 | + |
| 1489 | + if (openDropdownBtn) { |
| 1490 | + const dropdownList = openDropdownBtn.nextElementSibling; // фиксация выпадающего списка |
| 1491 | + openDropdownBtn.classList.remove('arrow-rotate', 'drop-open'); // возврат направления стрелки, удаление "открывающего" класса |
| 1492 | + dropdownList.classList.add('d-none'); // скрытие выпадающего списка |
| 1493 | + openDropdownBtn.blur(); // снятие фокуса с кнопки (после выбора) |
| 1494 | + } |
| 1495 | + } |
| 1496 | + |
| 1497 | + // ** организация замены выбора/типа модального row-контакта, после начала/внесения данных в инпут (вывод уточняющего сообщения) |
1467 | 1498 | function changeContactRowType(input, newType, previousType) { |
1468 | 1499 | const inputCurrentValue = input.value.trim(); |
1469 | 1500 |
|
|
1512 | 1543 | return true; // факт завершения |
1513 | 1544 | } |
1514 | 1545 |
|
1515 | | - // ** обновление атрибута/значения "type" у/для row-инпута (кому возможно, после выбора) |
1516 | | - function updateRowInputType(input, contactType) { |
1517 | | - // объект для сопоставления (кому заменять, на какое значение) |
1518 | | - const typeMapping = { |
1519 | | - phone: 'tel', |
1520 | | - 'extra-phone': 'tel', |
1521 | | - email: 'email', |
1522 | | - }; |
| 1546 | + // ** удаление строки row-контактов в модальном окне (через "X" кнопку, с/без уточняющего сообщения) |
| 1547 | + function deleteModalContactsElement(event) { |
| 1548 | + const clickedContactsXBtn = event.currentTarget; // получение ИМЕННО кнопки, а не/может внутренней иконки (согласно "размазанного" события) |
1523 | 1549 |
|
1524 | | - // корректировка атрибута/значения "type" (или будет text) |
1525 | | - const newType = typeMapping[contactType] || 'text'; |
1526 | | - input.setAttribute('type', newType); |
| 1550 | + if (clickedContactsXBtn) { |
| 1551 | + tippy.hideAll(); // предварительное скрытие всех/вдруг "активных" tooltips (перед удалением искомой строки) |
| 1552 | + |
| 1553 | + // фиксация родительского элемента |
| 1554 | + const modalContactsElement = clickedContactsXBtn.closest( |
| 1555 | + '.modal__add-body-add-contact-element' |
| 1556 | + ); |
| 1557 | + |
| 1558 | + if (modalContactsElement) { |
| 1559 | + // удаление/исключение привязанного tooltip (к конкретной строке/кнопке) |
| 1560 | + if (clickedContactsXBtn._tippy) { |
| 1561 | + clickedContactsXBtn._tippy.destroy(); |
| 1562 | + } |
| 1563 | + |
| 1564 | + // определение заполненности инпута в данной строке контакта |
| 1565 | + const currentInput = modalContactsElement.querySelector( |
| 1566 | + '.modal__add-body-add-contact-input' |
| 1567 | + ); |
| 1568 | + const isCurrentInputFilled = |
| 1569 | + currentInput && currentInput.value.trim() !== ''; |
| 1570 | + let confirmed = true; // изначально подтверждение/confirm не требуется |
| 1571 | + |
| 1572 | + if (isCurrentInputFilled) { |
| 1573 | + confirmed = confirm('Вы действительно хотите удалить этот контакт?'); // если/есть данные в инпуте, то тогда confirm/подтверждение при удалении |
| 1574 | + } |
| 1575 | + |
| 1576 | + if (confirmed) { |
| 1577 | + modalContactsElement.remove(); // удаление строки контактов |
| 1578 | + |
| 1579 | + // удаление строки контактов и из массива |
| 1580 | + const contactIndex = |
| 1581 | + addModalContactsArr.indexOf(modalContactsElement); |
| 1582 | + if (contactIndex > -1) addModalContactsArr.splice(contactIndex, 1); |
| 1583 | + |
| 1584 | + // проверка на количество элементов в массиве, меньше 10.. возврат возможности прожатия кнопки "Добавить контакт" |
| 1585 | + if (addModalContactsArr.length < 10) { |
| 1586 | + addModalBodyAddBtn.disabled = false; |
| 1587 | + } |
| 1588 | + |
| 1589 | + // проверка на количество строк контактов (нет, скрытие обвёртки/родителя и удаление дополнительных отступов) |
| 1590 | + if ( |
| 1591 | + document.querySelectorAll('.modal__add-body-add-contact-element') |
| 1592 | + .length === 0 |
| 1593 | + ) { |
| 1594 | + const addBodySelectWrap = document.querySelector( |
| 1595 | + '.modal__add-body-add-contacts-row-wrap' |
| 1596 | + ); |
| 1597 | + addBodySelectWrap.classList.add('d-none'); |
| 1598 | + addModalBodyAddBtn.classList.remove('modal-btn-margin'); |
| 1599 | + } |
| 1600 | + } |
| 1601 | + } |
| 1602 | + } |
1527 | 1603 | } |
1528 | 1604 |
|
1529 | 1605 | // ** организация проверки на "пустые" row-контакты, перед закрытием модального окна (вывод сообщения) |
|
1589 | 1665 |
|
1590 | 1666 | addModalWrap.addEventListener('hidden.bs.modal', removeInvalidRowContacts); // удаление/очистка от невалидных row-контактов (при закрытие add-модального окна) |
1591 | 1667 |
|
1592 | | - // ** удаление строки контактов в add-модальном окне (через "X" кнопку, с/без уточняющего сообщения) |
1593 | | - function deleteModalContactsElement(event) { |
1594 | | - const clickedContactsXBtn = event.currentTarget; // получение ИМЕННО кнопки, а не/может внутренней иконки (согласно "размазанного" события) |
1595 | | - |
1596 | | - if (clickedContactsXBtn) { |
1597 | | - tippy.hideAll(); // предварительное скрытие всех/вдруг "активных" tooltips (перед удалением искомой строки) |
1598 | | - |
1599 | | - // фиксация родительского элемента |
1600 | | - const modalContactsElement = clickedContactsXBtn.closest( |
1601 | | - '.modal__add-body-add-contact-element' |
1602 | | - ); |
1603 | | - |
1604 | | - if (modalContactsElement) { |
1605 | | - // удаление/исключение привязанного tooltip (к конкретной строке/кнопке) |
1606 | | - if (clickedContactsXBtn._tippy) { |
1607 | | - clickedContactsXBtn._tippy.destroy(); |
1608 | | - } |
1609 | | - |
1610 | | - // определение заполненности инпута в данной строке контакта |
1611 | | - const currentInput = modalContactsElement.querySelector( |
1612 | | - '.modal__add-body-add-contact-input' |
1613 | | - ); |
1614 | | - const isCurrentInputFilled = |
1615 | | - currentInput && currentInput.value.trim() !== ''; |
1616 | | - let confirmed = true; // изначально подтверждение/confirm не требуется |
1617 | | - |
1618 | | - if (isCurrentInputFilled) { |
1619 | | - confirmed = confirm('Вы действительно хотите удалить этот контакт?'); // если/есть данные в инпуте, то тогда confirm/подтверждение при удалении |
1620 | | - } |
1621 | | - |
1622 | | - if (confirmed) { |
1623 | | - modalContactsElement.remove(); // удаление строки контактов |
1624 | | - |
1625 | | - // удаление строки контактов и из массива |
1626 | | - const contactIndex = |
1627 | | - addModalContactsArr.indexOf(modalContactsElement); |
1628 | | - if (contactIndex > -1) addModalContactsArr.splice(contactIndex, 1); |
1629 | | - |
1630 | | - // проверка на количество элементов в массиве, меньше 10.. возврат возможности прожатия кнопки "Добавить контакт" |
1631 | | - if (addModalContactsArr.length < 10) { |
1632 | | - addModalBodyAddBtn.disabled = false; |
1633 | | - } |
1634 | | - |
1635 | | - // проверка на количество строк контактов (нет, скрытие обвёртки/родителя и удаление дополнительных отступов) |
1636 | | - if ( |
1637 | | - document.querySelectorAll('.modal__add-body-add-contact-element') |
1638 | | - .length === 0 |
1639 | | - ) { |
1640 | | - const addBodySelectWrap = document.querySelector( |
1641 | | - '.modal__add-body-add-contacts-row-wrap' |
1642 | | - ); |
1643 | | - addBodySelectWrap.classList.add('d-none'); |
1644 | | - addModalBodyAddBtn.classList.remove('modal-btn-margin'); |
1645 | | - } |
1646 | | - } |
1647 | | - } |
1648 | | - } |
1649 | | - } |
1650 | | - |
1651 | 1668 | // ** дополнительная/местная организация логики для tooltips (т.е. помимо customTippy.js) |
1652 | 1669 | function initTippy(selector, content, side) { |
1653 | 1670 | // определение входящего элемента (селектор или DOM-элемент, поиск/корректировка) |
|
0 commit comments