viewlogic 1.1.0 → 1.1.2
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.
- package/README.md +359 -641
- package/dist/viewlogic-router.js +237 -7
- package/dist/viewlogic-router.js.map +2 -2
- package/dist/viewlogic-router.min.js +3 -3
- package/dist/viewlogic-router.min.js.map +3 -3
- package/package.json +1 -1
package/dist/viewlogic-router.js
CHANGED
|
@@ -1635,8 +1635,14 @@ ${template}`;
|
|
|
1635
1635
|
await script.mounted.call(this);
|
|
1636
1636
|
}
|
|
1637
1637
|
if (script.dataURL) {
|
|
1638
|
-
|
|
1638
|
+
if (typeof script.dataURL === "string") {
|
|
1639
|
+
await this.$fetchData();
|
|
1640
|
+
} else if (typeof script.dataURL === "object") {
|
|
1641
|
+
await this.$fetchMultipleData();
|
|
1642
|
+
}
|
|
1639
1643
|
}
|
|
1644
|
+
await this.$nextTick();
|
|
1645
|
+
this.$bindAutoForms();
|
|
1640
1646
|
},
|
|
1641
1647
|
methods: {
|
|
1642
1648
|
...script.methods,
|
|
@@ -1658,21 +1664,245 @@ ${template}`;
|
|
|
1658
1664
|
$removeToken: (storage) => router.authManager?.removeAccessToken(storage) || null,
|
|
1659
1665
|
$getAuthCookie: () => router.authManager?.getAuthCookie() || null,
|
|
1660
1666
|
$getCookie: (name) => router.authManager?.getCookieValue(name) || null,
|
|
1661
|
-
// 데이터 fetch
|
|
1662
|
-
async $fetchData() {
|
|
1667
|
+
// 데이터 fetch (단일 API 또는 특정 API)
|
|
1668
|
+
async $fetchData(apiName) {
|
|
1663
1669
|
if (!script.dataURL) return;
|
|
1664
1670
|
this.$dataLoading = true;
|
|
1665
1671
|
try {
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
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
|
+
}
|
|
1670
1688
|
} catch (error) {
|
|
1671
1689
|
if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Failed to fetch data for ${routeName}:`, error);
|
|
1672
1690
|
this.$emit("data-error", error);
|
|
1673
1691
|
} finally {
|
|
1674
1692
|
this.$dataLoading = false;
|
|
1675
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;
|
|
1676
1906
|
}
|
|
1677
1907
|
},
|
|
1678
1908
|
_routeName: routeName
|