viewlogic 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1488,6 +1488,7 @@ var FormHandler = class {
1488
1488
  this.router = router;
1489
1489
  this.config = {
1490
1490
  debug: options.debug || false,
1491
+ requestTimeout: options.requestTimeout || 3e4,
1491
1492
  ...options
1492
1493
  };
1493
1494
  this.log("debug", "FormHandler initialized");
@@ -1500,6 +1501,48 @@ var FormHandler = class {
1500
1501
  this.router.errorHandler.log(level, "FormHandler", ...args);
1501
1502
  }
1502
1503
  }
1504
+ /**
1505
+ * 중복 요청 체크
1506
+ */
1507
+ isDuplicateRequest(form) {
1508
+ if (form._isSubmitting) {
1509
+ this.log("debug", "Duplicate request blocked");
1510
+ return true;
1511
+ }
1512
+ return false;
1513
+ }
1514
+ /**
1515
+ * 폼 제출 시작
1516
+ */
1517
+ startFormSubmission(form) {
1518
+ form._isSubmitting = true;
1519
+ form._abortController = new AbortController();
1520
+ form._timeoutId = setTimeout(() => {
1521
+ if (form._isSubmitting) {
1522
+ this.abortFormSubmission(form);
1523
+ }
1524
+ }, this.config.requestTimeout);
1525
+ }
1526
+ /**
1527
+ * 폼 제출 완료
1528
+ */
1529
+ finishFormSubmission(form) {
1530
+ form._isSubmitting = false;
1531
+ if (form._timeoutId) {
1532
+ clearTimeout(form._timeoutId);
1533
+ delete form._timeoutId;
1534
+ }
1535
+ delete form._abortController;
1536
+ }
1537
+ /**
1538
+ * 폼 제출 중단
1539
+ */
1540
+ abortFormSubmission(form) {
1541
+ if (form._abortController) {
1542
+ form._abortController.abort();
1543
+ }
1544
+ this.finishFormSubmission(form);
1545
+ }
1503
1546
  /**
1504
1547
  * 자동 폼 바인딩
1505
1548
  */
@@ -1525,28 +1568,40 @@ var FormHandler = class {
1525
1568
  const errorHandler = form.getAttribute("data-error-handler");
1526
1569
  const loadingHandler = form.getAttribute("data-loading-handler");
1527
1570
  const redirectTo = form.getAttribute("data-redirect");
1571
+ action = this.processActionParams(action, component);
1572
+ if (!this.validateForm(form, component)) {
1573
+ return;
1574
+ }
1575
+ if (this.isDuplicateRequest(form)) {
1576
+ return;
1577
+ }
1578
+ this.startFormSubmission(form);
1579
+ const formData = new FormData(form);
1580
+ const data = Object.fromEntries(formData.entries());
1528
1581
  try {
1529
1582
  if (loadingHandler && component[loadingHandler]) {
1530
1583
  component[loadingHandler](true, form);
1531
1584
  }
1532
- action = this.processActionParams(action, component);
1533
- if (!this.validateForm(form, component)) {
1534
- return;
1535
- }
1536
- const formData = new FormData(form);
1537
- const data = Object.fromEntries(formData.entries());
1538
1585
  this.log("debug", `Form submitting to: ${action}`, data);
1539
- const response = await this.submitFormData(action, method, data, form, component);
1586
+ const response = await this.submitFormData(action, method, data, form, component, form._abortController.signal);
1540
1587
  if (successHandler && component[successHandler]) {
1541
1588
  component[successHandler](response, form);
1542
1589
  }
1590
+ this.finishFormSubmission(form);
1543
1591
  if (redirectTo) {
1544
- setTimeout(() => {
1545
- component.navigateTo(redirectTo);
1546
- }, 1e3);
1592
+ requestAnimationFrame(() => {
1593
+ setTimeout(() => {
1594
+ component.navigateTo(redirectTo);
1595
+ }, 1e3);
1596
+ });
1547
1597
  }
1548
1598
  } catch (error) {
1549
- this.log("warn", `Form submission error:`, error);
1599
+ if (error.name === "AbortError") {
1600
+ this.log("debug", "Form submission aborted");
1601
+ return;
1602
+ }
1603
+ this.log("warn", "Form submission error:", error);
1604
+ this.finishFormSubmission(form);
1550
1605
  if (errorHandler && component[errorHandler]) {
1551
1606
  component[errorHandler](error, form);
1552
1607
  } else {
@@ -1567,11 +1622,13 @@ var FormHandler = class {
1567
1622
  /**
1568
1623
  * 폼 데이터 서브밋 (ApiHandler 활용)
1569
1624
  */
1570
- async submitFormData(action, method, data, form, component) {
1625
+ async submitFormData(action, method, data, form, component, signal = null) {
1571
1626
  const hasFile = Array.from(form.elements).some((el) => el.type === "file" && el.files.length > 0);
1572
1627
  const options = {
1573
1628
  method: method.toUpperCase(),
1574
- headers: {}
1629
+ headers: {},
1630
+ signal
1631
+ // AbortController 신호 추가
1575
1632
  };
1576
1633
  if (hasFile) {
1577
1634
  options.data = new FormData(form);
@@ -1624,20 +1681,44 @@ var FormHandler = class {
1624
1681
  this.log("warn", `Validation function '${validationFunction}' not found`);
1625
1682
  return true;
1626
1683
  }
1684
+ /**
1685
+ * 모든 폼 요청 취소
1686
+ */
1687
+ cancelAllRequests() {
1688
+ const forms = document.querySelectorAll("form");
1689
+ forms.forEach((form) => {
1690
+ if (form._isSubmitting) {
1691
+ this.abortFormSubmission(form);
1692
+ }
1693
+ });
1694
+ }
1627
1695
  /**
1628
1696
  * 정리 (메모리 누수 방지)
1629
1697
  */
1630
1698
  destroy() {
1699
+ this.cancelAllRequests();
1631
1700
  const forms = document.querySelectorAll("form.auto-form, form[action]");
1632
1701
  forms.forEach((form) => {
1633
1702
  if (form._boundSubmitHandler) {
1634
1703
  form.removeEventListener("submit", form._boundSubmitHandler);
1635
1704
  delete form._boundSubmitHandler;
1636
1705
  }
1706
+ this.cleanupFormState(form);
1637
1707
  });
1638
1708
  this.log("debug", "FormHandler destroyed");
1639
1709
  this.router = null;
1640
1710
  }
1711
+ /**
1712
+ * 폼 상태 정리
1713
+ */
1714
+ cleanupFormState(form) {
1715
+ delete form._isSubmitting;
1716
+ delete form._abortController;
1717
+ if (form._timeoutId) {
1718
+ clearTimeout(form._timeoutId);
1719
+ delete form._timeoutId;
1720
+ }
1721
+ }
1641
1722
  };
1642
1723
 
1643
1724
  // src/core/ApiHandler.js