viewlogic 1.0.4 → 1.1.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.
@@ -1123,6 +1123,7 @@ var QueryManager = class {
1123
1123
  };
1124
1124
  this.router = router;
1125
1125
  this.currentQueryParams = {};
1126
+ this.currentRouteParams = {};
1126
1127
  this.log("info", "QueryManager initialized with config:", this.config);
1127
1128
  }
1128
1129
  /**
@@ -1326,8 +1327,9 @@ var QueryManager = class {
1326
1327
  /**
1327
1328
  * 특정 쿼리 파라미터 가져오기
1328
1329
  */
1329
- getQueryParam(key) {
1330
- return this.currentQueryParams ? this.currentQueryParams[key] : void 0;
1330
+ getQueryParam(key, defaultValue = void 0) {
1331
+ const value = this.currentQueryParams ? this.currentQueryParams[key] : void 0;
1332
+ return value !== void 0 ? value : defaultValue;
1331
1333
  }
1332
1334
  /**
1333
1335
  * 쿼리 파라미터 설정
@@ -1391,6 +1393,42 @@ var QueryManager = class {
1391
1393
  setCurrentQueryParams(params) {
1392
1394
  this.currentQueryParams = params || {};
1393
1395
  }
1396
+ /**
1397
+ * 현재 라우팅 파라미터 설정 (navigateTo에서 호출)
1398
+ */
1399
+ setCurrentRouteParams(params) {
1400
+ this.currentRouteParams = params || {};
1401
+ this.log("debug", "Route params set:", this.currentRouteParams);
1402
+ }
1403
+ /**
1404
+ * 통합된 파라미터 반환 (라우팅 파라미터 + 쿼리 파라미터)
1405
+ */
1406
+ getAllParams() {
1407
+ return {
1408
+ ...this.currentRouteParams,
1409
+ ...this.currentQueryParams
1410
+ };
1411
+ }
1412
+ /**
1413
+ * 통합된 파라미터에서 특정 키 값 반환
1414
+ */
1415
+ getParam(key, defaultValue = void 0) {
1416
+ const value = this.currentQueryParams[key] !== void 0 ? this.currentQueryParams[key] : this.currentRouteParams[key];
1417
+ return value !== void 0 ? value : defaultValue;
1418
+ }
1419
+ /**
1420
+ * 라우팅 파라미터만 반환
1421
+ */
1422
+ getRouteParams() {
1423
+ return { ...this.currentRouteParams };
1424
+ }
1425
+ /**
1426
+ * 라우팅 파라미터에서 특정 키 값 반환
1427
+ */
1428
+ getRouteParam(key, defaultValue = void 0) {
1429
+ const value = this.currentRouteParams[key];
1430
+ return value !== void 0 ? value : defaultValue;
1431
+ }
1394
1432
  /**
1395
1433
  * URL 업데이트 (라우터의 updateURL 메소드 호출)
1396
1434
  */
@@ -1416,6 +1454,7 @@ var QueryManager = class {
1416
1454
  */
1417
1455
  destroy() {
1418
1456
  this.currentQueryParams = {};
1457
+ this.currentRouteParams = {};
1419
1458
  this.router = null;
1420
1459
  this.log("debug", "QueryManager destroyed");
1421
1460
  }
@@ -1558,20 +1597,26 @@ ${template}`;
1558
1597
  template = this.mergeLayoutWithTemplate(routeName, layout, template);
1559
1598
  }
1560
1599
  }
1600
+ let loadedComponents = {};
1601
+ if (this.config.useComponents && router.componentLoader) {
1602
+ try {
1603
+ loadedComponents = await router.componentLoader.loadAllComponents();
1604
+ this.log("debug", `Components loaded successfully for route: ${routeName}`);
1605
+ } catch (error) {
1606
+ this.log("warn", `Component loading failed for route '${routeName}', continuing without components:`, error.message);
1607
+ loadedComponents = {};
1608
+ }
1609
+ }
1561
1610
  const component = {
1562
1611
  ...script,
1563
1612
  name: script.name || this.toPascalCase(routeName),
1564
1613
  template,
1565
- components: this.config.useComponents && router.componentLoader ? await router.componentLoader.loadAllComponents() : {},
1614
+ components: loadedComponents,
1566
1615
  data() {
1567
1616
  const originalData = script.data ? script.data() : {};
1568
1617
  const commonData = {
1569
1618
  ...originalData,
1570
1619
  currentRoute: routeName,
1571
- pageTitle: script.pageTitle || router.routeLoader.generatePageTitle(routeName),
1572
- showHeader: script.showHeader !== false,
1573
- headerTitle: script.headerTitle || router.routeLoader.generatePageTitle(routeName),
1574
- headerSubtitle: script.headerSubtitle,
1575
1620
  $query: router.queryManager?.getQueryParams() || {},
1576
1621
  $lang: router.i18nManager?.getCurrentLanguage() || router.config.i18nDefaultLanguage,
1577
1622
  $dataLoading: false
@@ -1580,8 +1625,9 @@ ${template}`;
1580
1625
  },
1581
1626
  computed: {
1582
1627
  ...script.computed || {},
1628
+ // 하위 호환성을 위해 params는 유지하되 getAllParams 사용
1583
1629
  params() {
1584
- return router.queryManager?.getQueryParams() || {};
1630
+ return router.queryManager?.getAllParams() || {};
1585
1631
  }
1586
1632
  },
1587
1633
  async mounted() {
@@ -1589,21 +1635,25 @@ ${template}`;
1589
1635
  await script.mounted.call(this);
1590
1636
  }
1591
1637
  if (script.dataURL) {
1592
- await this.$fetchData();
1638
+ if (typeof script.dataURL === "string") {
1639
+ await this.$fetchData();
1640
+ } else if (typeof script.dataURL === "object") {
1641
+ await this.$fetchMultipleData();
1642
+ }
1593
1643
  }
1644
+ await this.$nextTick();
1645
+ this.$bindAutoForms();
1594
1646
  },
1595
1647
  methods: {
1596
1648
  ...script.methods,
1597
1649
  // 라우팅 관련
1598
1650
  navigateTo: (route, params) => router.navigateTo(route, params),
1599
1651
  getCurrentRoute: () => router.getCurrentRoute(),
1600
- getQueryParams: () => router.queryManager?.getQueryParams() || {},
1601
- getQueryParam: (key) => router.queryManager?.getQueryParam(key),
1602
- setQueryParams: (params, replace) => router.queryManager?.setQueryParams(params, replace),
1603
- removeQueryParams: (keys) => router.queryManager?.removeQueryParams(keys),
1652
+ // 통합된 파라미터 관리 (라우팅 + 쿼리 파라미터)
1653
+ getParams: () => router.queryManager?.getAllParams() || {},
1654
+ getParam: (key, defaultValue) => router.queryManager?.getParam(key, defaultValue),
1604
1655
  // i18n 관련
1605
1656
  $t: (key, params) => router.i18nManager?.t(key, params) || key,
1606
- $i18n: () => router.i18nManager || null,
1607
1657
  // 인증 관련
1608
1658
  $isAuthenticated: () => router.authManager?.isUserAuthenticated() || false,
1609
1659
  $logout: () => router.authManager ? router.navigateTo(router.authManager.handleLogout()) : null,
@@ -1614,21 +1664,245 @@ ${template}`;
1614
1664
  $removeToken: (storage) => router.authManager?.removeAccessToken(storage) || null,
1615
1665
  $getAuthCookie: () => router.authManager?.getAuthCookie() || null,
1616
1666
  $getCookie: (name) => router.authManager?.getCookieValue(name) || null,
1617
- // 데이터 fetch
1618
- async $fetchData() {
1667
+ // 데이터 fetch (단일 API 또는 특정 API)
1668
+ async $fetchData(apiName) {
1619
1669
  if (!script.dataURL) return;
1620
1670
  this.$dataLoading = true;
1621
1671
  try {
1622
- const data = await router.routeLoader.fetchComponentData(script.dataURL);
1623
- if (router.errorHandler) router.errorHandler.debug("RouteLoader", `Data fetched for ${routeName}:`, data);
1624
- Object.assign(this, data);
1625
- this.$emit("data-loaded", data);
1672
+ if (typeof script.dataURL === "string") {
1673
+ const data = await router.routeLoader.fetchComponentData(script.dataURL);
1674
+ if (router.errorHandler) router.errorHandler.debug("RouteLoader", `Data fetched for ${routeName}:`, data);
1675
+ Object.assign(this, data);
1676
+ this.$emit("data-loaded", data);
1677
+ } else if (typeof script.dataURL === "object" && apiName) {
1678
+ const url = script.dataURL[apiName];
1679
+ if (url) {
1680
+ const data = await router.routeLoader.fetchComponentData(url);
1681
+ if (router.errorHandler) router.errorHandler.debug("RouteLoader", `Data fetched for ${routeName}.${apiName}:`, data);
1682
+ this[apiName] = data;
1683
+ this.$emit("data-loaded", { [apiName]: data });
1684
+ }
1685
+ } else {
1686
+ await this.$fetchMultipleData();
1687
+ }
1626
1688
  } catch (error) {
1627
1689
  if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Failed to fetch data for ${routeName}:`, error);
1628
1690
  this.$emit("data-error", error);
1629
1691
  } finally {
1630
1692
  this.$dataLoading = false;
1631
1693
  }
1694
+ },
1695
+ // 다중 API 데이터 fetch
1696
+ async $fetchMultipleData() {
1697
+ if (!script.dataURL || typeof script.dataURL !== "object") return;
1698
+ const dataURLs = script.dataURL;
1699
+ this.$dataLoading = true;
1700
+ try {
1701
+ const promises = Object.entries(dataURLs).map(async ([key, url]) => {
1702
+ try {
1703
+ const data = await router.routeLoader.fetchComponentData(url);
1704
+ return { key, data, success: true };
1705
+ } catch (error) {
1706
+ if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Failed to fetch ${key} for ${routeName}:`, error);
1707
+ return { key, error, success: false };
1708
+ }
1709
+ });
1710
+ const results = await Promise.all(promises);
1711
+ const successfulResults = {};
1712
+ const errors = {};
1713
+ results.forEach(({ key, data, error, success }) => {
1714
+ if (success) {
1715
+ this[key] = data;
1716
+ successfulResults[key] = data;
1717
+ } else {
1718
+ errors[key] = error;
1719
+ }
1720
+ });
1721
+ if (router.errorHandler) router.errorHandler.debug("RouteLoader", `Multiple data fetched for ${routeName}:`, successfulResults);
1722
+ if (Object.keys(successfulResults).length > 0) {
1723
+ this.$emit("data-loaded", successfulResults);
1724
+ }
1725
+ if (Object.keys(errors).length > 0) {
1726
+ this.$emit("data-error", errors);
1727
+ }
1728
+ } catch (error) {
1729
+ if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Failed to fetch multiple data for ${routeName}:`, error);
1730
+ this.$emit("data-error", error);
1731
+ } finally {
1732
+ this.$dataLoading = false;
1733
+ }
1734
+ },
1735
+ // 전체 데이터 새로고침 (명시적 메서드)
1736
+ async $fetchAllData() {
1737
+ if (typeof script.dataURL === "string") {
1738
+ await this.$fetchData();
1739
+ } else if (typeof script.dataURL === "object") {
1740
+ await this.$fetchMultipleData();
1741
+ }
1742
+ },
1743
+ // 🆕 자동 폼 바인딩 메서드
1744
+ $bindAutoForms() {
1745
+ const forms = document.querySelectorAll("form.auto-form, form[action]");
1746
+ forms.forEach((form) => {
1747
+ form.removeEventListener("submit", form._boundSubmitHandler);
1748
+ const boundHandler = (e) => this.$handleFormSubmit(e);
1749
+ form._boundSubmitHandler = boundHandler;
1750
+ form.addEventListener("submit", boundHandler);
1751
+ if (router.errorHandler) router.errorHandler.debug("RouteLoader", `Form auto-bound: ${form.getAttribute("action")}`);
1752
+ });
1753
+ },
1754
+ // 🆕 폼 서브밋 핸들러
1755
+ async $handleFormSubmit(event) {
1756
+ event.preventDefault();
1757
+ const form = event.target;
1758
+ let action = form.getAttribute("action");
1759
+ const method = form.getAttribute("method") || "POST";
1760
+ const successHandler = form.getAttribute("data-success-handler");
1761
+ const errorHandler = form.getAttribute("data-error-handler");
1762
+ const loadingHandler = form.getAttribute("data-loading-handler");
1763
+ const redirectTo = form.getAttribute("data-redirect");
1764
+ try {
1765
+ if (loadingHandler && this[loadingHandler]) {
1766
+ this[loadingHandler](true, form);
1767
+ }
1768
+ action = this.$processActionParams(action);
1769
+ if (!this.$validateForm(form)) {
1770
+ return;
1771
+ }
1772
+ const formData = new FormData(form);
1773
+ const data = Object.fromEntries(formData.entries());
1774
+ if (router.errorHandler) router.errorHandler.debug("RouteLoader", `Form submitting to: ${action}`, data);
1775
+ const response = await this.$submitFormData(action, method, data, form);
1776
+ if (successHandler && this[successHandler]) {
1777
+ this[successHandler](response, form);
1778
+ }
1779
+ if (redirectTo) {
1780
+ setTimeout(() => {
1781
+ this.navigateTo(redirectTo);
1782
+ }, 1e3);
1783
+ }
1784
+ } catch (error) {
1785
+ if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Form submission error:`, error);
1786
+ if (errorHandler && this[errorHandler]) {
1787
+ this[errorHandler](error, form);
1788
+ } else {
1789
+ console.error("Form submission error:", error);
1790
+ }
1791
+ } finally {
1792
+ if (loadingHandler && this[loadingHandler]) {
1793
+ this[loadingHandler](false, form);
1794
+ }
1795
+ }
1796
+ },
1797
+ // 🆕 액션 파라미터 처리 메서드 (간단한 템플릿 치환)
1798
+ $processActionParams(actionTemplate) {
1799
+ let processedAction = actionTemplate;
1800
+ const paramMatches = actionTemplate.match(/\{([^}]+)\}/g);
1801
+ if (paramMatches) {
1802
+ paramMatches.forEach((match) => {
1803
+ const paramName = match.slice(1, -1);
1804
+ try {
1805
+ let paramValue = null;
1806
+ paramValue = this.getParam(paramName);
1807
+ if (paramValue === null || paramValue === void 0) {
1808
+ paramValue = this[paramName];
1809
+ }
1810
+ if (paramValue === null || paramValue === void 0) {
1811
+ if (this.$options.computed && this.$options.computed[paramName]) {
1812
+ paramValue = this[paramName];
1813
+ }
1814
+ }
1815
+ if (paramValue !== null && paramValue !== void 0) {
1816
+ processedAction = processedAction.replace(
1817
+ match,
1818
+ encodeURIComponent(paramValue)
1819
+ );
1820
+ if (router.errorHandler) router.errorHandler.debug("RouteLoader", `Parameter resolved: ${paramName} = ${paramValue}`);
1821
+ } else {
1822
+ if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Parameter '${paramName}' not found in component data, computed, or route params`);
1823
+ }
1824
+ } catch (error) {
1825
+ if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Error processing parameter '${paramName}':`, error);
1826
+ }
1827
+ });
1828
+ }
1829
+ return processedAction;
1830
+ },
1831
+ // 🆕 폼 데이터 서브밋
1832
+ async $submitFormData(action, method, data, form) {
1833
+ const hasFile = Array.from(form.elements).some((el) => el.type === "file" && el.files.length > 0);
1834
+ const headers = {
1835
+ "Accept": "application/json",
1836
+ // 인증 토큰 자동 추가
1837
+ ...this.$getToken() && {
1838
+ "Authorization": `Bearer ${this.$getToken()}`
1839
+ }
1840
+ };
1841
+ let body;
1842
+ if (hasFile) {
1843
+ body = new FormData(form);
1844
+ } else {
1845
+ headers["Content-Type"] = "application/json";
1846
+ body = JSON.stringify(data);
1847
+ }
1848
+ const response = await fetch(action, {
1849
+ method: method.toUpperCase(),
1850
+ headers,
1851
+ body
1852
+ });
1853
+ if (!response.ok) {
1854
+ let error;
1855
+ try {
1856
+ error = await response.json();
1857
+ } catch (e) {
1858
+ error = { message: `HTTP ${response.status}: ${response.statusText}` };
1859
+ }
1860
+ throw new Error(error.message || `HTTP ${response.status}`);
1861
+ }
1862
+ try {
1863
+ return await response.json();
1864
+ } catch (e) {
1865
+ return { success: true };
1866
+ }
1867
+ },
1868
+ // 🆕 클라이언트 사이드 폼 검증
1869
+ $validateForm(form) {
1870
+ let isValid = true;
1871
+ const inputs = form.querySelectorAll("input, textarea, select");
1872
+ inputs.forEach((input) => {
1873
+ if (!input.checkValidity()) {
1874
+ isValid = false;
1875
+ input.classList.add("error");
1876
+ return;
1877
+ }
1878
+ const validationFunction = input.getAttribute("data-validation");
1879
+ if (validationFunction) {
1880
+ const isInputValid = this.$validateInput(input, validationFunction);
1881
+ if (!isInputValid) {
1882
+ isValid = false;
1883
+ input.classList.add("error");
1884
+ } else {
1885
+ input.classList.remove("error");
1886
+ }
1887
+ } else {
1888
+ input.classList.remove("error");
1889
+ }
1890
+ });
1891
+ return isValid;
1892
+ },
1893
+ // 🆕 개별 입력 검증
1894
+ $validateInput(input, validationFunction) {
1895
+ const value = input.value;
1896
+ if (typeof this[validationFunction] === "function") {
1897
+ try {
1898
+ return this[validationFunction](value, input);
1899
+ } catch (error) {
1900
+ if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Validation function '${validationFunction}' error:`, error);
1901
+ return false;
1902
+ }
1903
+ }
1904
+ if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Validation function '${validationFunction}' not found`);
1905
+ return true;
1632
1906
  }
1633
1907
  },
1634
1908
  _routeName: routeName
@@ -2245,13 +2519,19 @@ var ViewLogicRouter = class {
2245
2519
  this.authManager = new AuthManager(this, this.config);
2246
2520
  }
2247
2521
  if (this.config.useComponents) {
2248
- this.componentLoader = new ComponentLoader(this, {
2249
- ...this.config,
2250
- basePath: `${this.config.basePath}/components`,
2251
- cache: true,
2252
- componentNames: this.config.componentNames
2253
- });
2254
- await this.componentLoader.loadAllComponents();
2522
+ try {
2523
+ this.componentLoader = new ComponentLoader(this, {
2524
+ ...this.config,
2525
+ basePath: `${this.config.basePath}/components`,
2526
+ cache: true,
2527
+ componentNames: this.config.componentNames
2528
+ });
2529
+ await this.componentLoader.loadAllComponents();
2530
+ this.log("info", "ComponentLoader initialized successfully");
2531
+ } catch (componentError) {
2532
+ this.log("warn", "ComponentLoader initialization failed, continuing without components:", componentError.message);
2533
+ this.componentLoader = null;
2534
+ }
2255
2535
  }
2256
2536
  this.isReady = true;
2257
2537
  this.init();
@@ -2336,8 +2616,11 @@ var ViewLogicRouter = class {
2336
2616
  originalRoute: routeName,
2337
2617
  loginRoute: this.config.loginRoute
2338
2618
  });
2339
- const redirectUrl = routeName !== this.config.loginRoute ? `${this.config.loginRoute}?redirect=${encodeURIComponent(routeName)}` : this.config.loginRoute;
2340
- this.navigateTo(redirectUrl);
2619
+ if (routeName !== this.config.loginRoute) {
2620
+ this.navigateTo(this.config.loginRoute, { redirect: routeName });
2621
+ } else {
2622
+ this.navigateTo(this.config.loginRoute);
2623
+ }
2341
2624
  }
2342
2625
  return;
2343
2626
  }
@@ -2378,10 +2661,17 @@ var ViewLogicRouter = class {
2378
2661
  newVueApp.config.globalProperties.$router = {
2379
2662
  navigateTo: (route, params) => this.navigateTo(route, params),
2380
2663
  getCurrentRoute: () => this.getCurrentRoute(),
2664
+ // 통합된 파라미터 관리 (라우팅 + 쿼리 파라미터)
2665
+ getParams: () => this.queryManager?.getAllParams() || {},
2666
+ getParam: (key, defaultValue) => this.queryManager?.getParam(key, defaultValue),
2667
+ // 쿼리 파라미터 전용 메서드 (하위 호환성)
2381
2668
  getQueryParams: () => this.queryManager?.getQueryParams() || {},
2382
- getQueryParam: (key) => this.queryManager?.getQueryParam(key),
2669
+ getQueryParam: (key, defaultValue) => this.queryManager?.getQueryParam(key, defaultValue),
2383
2670
  setQueryParams: (params, replace) => this.queryManager?.setQueryParams(params, replace),
2384
2671
  removeQueryParams: (keys) => this.queryManager?.removeQueryParams(keys),
2672
+ // 라우팅 파라미터 전용 메서드
2673
+ getRouteParams: () => this.queryManager?.getRouteParams() || {},
2674
+ getRouteParam: (key, defaultValue) => this.queryManager?.getRouteParam(key, defaultValue),
2385
2675
  currentRoute: this.currentHash,
2386
2676
  currentQuery: this.queryManager?.getQueryParams() || {}
2387
2677
  };
@@ -2428,6 +2718,9 @@ var ViewLogicRouter = class {
2428
2718
  if (routeName !== this.currentHash && this.queryManager) {
2429
2719
  this.queryManager.clearQueryParams();
2430
2720
  }
2721
+ if (this.queryManager) {
2722
+ this.queryManager.setCurrentRouteParams(params);
2723
+ }
2431
2724
  this.updateURL(routeName, params);
2432
2725
  }
2433
2726
  getCurrentRoute() {