viewlogic 1.2.2 → 1.2.3
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 +607 -863
- package/dist/viewlogic-router.js +280 -453
- package/dist/viewlogic-router.js.map +3 -3
- package/dist/viewlogic-router.min.js +3 -3
- package/dist/viewlogic-router.min.js.map +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
</a>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
|
-
>
|
|
15
|
+
> **Complete Vue 3 Framework**: Routing + State Management + Authentication + i18n + Caching + API + Forms
|
|
16
16
|
|
|
17
17
|
## 🎯 Core Philosophy: Simplicity Through Design
|
|
18
18
|
|
|
@@ -24,1068 +24,812 @@ ViewLogic Router revolutionizes Vue development with two fundamental core princi
|
|
|
24
24
|
### 🚀 Zero Build Development
|
|
25
25
|
**Zero build step required in development mode**. Work directly with source files, see changes instantly without any compilation, bundling, or build processes. True real-time development experience.
|
|
26
26
|
|
|
27
|
+
## 🚀 Why Choose ViewLogic Router?
|
|
28
|
+
|
|
29
|
+
**All-in-One Solution** - Replace 7+ libraries with one unified framework:
|
|
30
|
+
- 🔀 **Vue Router** → ViewLogic Routing
|
|
31
|
+
- 📦 **Pinia/Vuex** → StateHandler
|
|
32
|
+
- 🔐 **Auth Libraries** → AuthManager
|
|
33
|
+
- 🌐 **Vue I18n** → I18nManager
|
|
34
|
+
- 💾 **Cache Libraries** → CacheManager
|
|
35
|
+
- 🌐 **API Libraries** → ApiHandler
|
|
36
|
+
- 📝 **Form Libraries** → FormHandler
|
|
37
|
+
|
|
38
|
+
**Tiny Bundle Size** - Complete framework in just 51KB minified / 17KB gzipped!
|
|
39
|
+
|
|
40
|
+
**Easy Integration** - Drop-in UMD build available for instant usage without build tools.
|
|
41
|
+
|
|
27
42
|
## ✨ Key Features
|
|
28
43
|
|
|
29
|
-
- 🚀 **Ultra-Lightweight** - Complete routing system
|
|
30
|
-
- 🔄 **Multiple API Support** - Parallel data fetching from multiple APIs with named data storage
|
|
44
|
+
- 🚀 **Ultra-Lightweight** - Complete routing system with zero dependencies and optimized codebase
|
|
45
|
+
- 🔄 **Multiple API Support** - Parallel data fetching from multiple APIs with named data storage
|
|
31
46
|
- 📝 **Automatic Form Handling** - Revolutionary form submission with `{paramName}` variable parameters
|
|
32
47
|
- 🛠️ **Built-in Components** - Preloaded UI components including revolutionary DynamicInclude & HtmlInclude
|
|
33
48
|
- 🔗 **Query-Based Parameter System** - Simple query-only parameters (`/users?id=123`) instead of complex path parameters
|
|
34
49
|
- ⚡ **Optimized Production** - Pre-built individual route bundles for lightning-fast production
|
|
35
50
|
- 📁 **Intuitive Structure** - Organized folder structure for views, logic, styles, layouts, and components
|
|
36
|
-
- 💾 **Smart Caching** - Intelligent route and component caching
|
|
37
|
-
- 🔐 **Authentication** - Built-in auth management
|
|
38
|
-
- 🌐 **i18n Ready** - Built-in internationalization support
|
|
51
|
+
- 💾 **Smart Caching** - Intelligent route and component caching with TTL and LRU eviction
|
|
52
|
+
- 🔐 **Authentication** - Built-in JWT auth management with multiple storage options
|
|
53
|
+
- 🌐 **i18n Ready** - Built-in internationalization support with lazy loading
|
|
54
|
+
- 📊 **State Management** - Reactive state management without external dependencies
|
|
55
|
+
|
|
56
|
+
### What's Included
|
|
57
|
+
- ✅ Complete routing system with hash/history mode
|
|
58
|
+
- ✅ Advanced caching with TTL and size limits
|
|
59
|
+
- ✅ Built-in authentication with multiple storage options
|
|
60
|
+
- ✅ Internationalization system with lazy loading
|
|
61
|
+
- ✅ Form handling with automatic validation
|
|
62
|
+
- ✅ RESTful API client with parameter substitution
|
|
63
|
+
- ✅ Component loading and management
|
|
64
|
+
- ✅ Error handling and logging system
|
|
65
|
+
- ✅ Query parameter management
|
|
66
|
+
- ✅ Layout system with slot-based composition
|
|
67
|
+
- ✅ Global state management with reactivity
|
|
68
|
+
- ✅ Event system for component communication
|
|
39
69
|
|
|
40
|
-
##
|
|
70
|
+
## 🏗️ Project Structure
|
|
41
71
|
|
|
42
|
-
|
|
72
|
+
ViewLogic Router follows a clean, intuitive folder structure that promotes maintainability:
|
|
43
73
|
|
|
44
|
-
```bash
|
|
45
|
-
npm create viewlogic my-app
|
|
46
|
-
cd my-app
|
|
47
|
-
# Ready to go! No additional setup needed
|
|
48
74
|
```
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
75
|
+
project/
|
|
76
|
+
├── src/
|
|
77
|
+
│ ├── views/ # HTML templates (pure presentation)
|
|
78
|
+
│ │ ├── home.html
|
|
79
|
+
│ │ ├── user-profile.html
|
|
80
|
+
│ │ └── dashboard.html
|
|
81
|
+
│ ├── logic/ # Vue component logic (pure JavaScript)
|
|
82
|
+
│ │ ├── home.js
|
|
83
|
+
│ │ ├── user-profile.js
|
|
84
|
+
│ │ └── dashboard.js
|
|
85
|
+
│ ├── components/ # Reusable UI components
|
|
86
|
+
│ │ ├── UserCard.vue
|
|
87
|
+
│ │ └── NavigationMenu.vue
|
|
88
|
+
│ ├── layouts/ # Layout templates
|
|
89
|
+
│ │ ├── default.html
|
|
90
|
+
│ │ └── admin.html
|
|
91
|
+
│ ├── styles/ # CSS files
|
|
92
|
+
│ │ ├── home.css
|
|
93
|
+
│ │ └── global.css
|
|
94
|
+
│ └── locales/ # i18n files (optional)
|
|
95
|
+
│ ├── en.json
|
|
96
|
+
│ └── ko.json
|
|
97
|
+
├── index.html # Main entry point
|
|
98
|
+
├── package.json
|
|
99
|
+
└── config.json # Optional configuration
|
|
53
100
|
```
|
|
54
101
|
|
|
55
102
|
## 🚀 Quick Start
|
|
56
103
|
|
|
57
|
-
###
|
|
104
|
+
### Method 1: ES6 Modules (Recommended)
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npm install viewlogic
|
|
108
|
+
```
|
|
58
109
|
|
|
59
110
|
```html
|
|
60
111
|
<!DOCTYPE html>
|
|
61
112
|
<html>
|
|
62
113
|
<head>
|
|
63
|
-
<
|
|
64
|
-
<title>My ViewLogic App - Development</title>
|
|
65
|
-
<link rel="stylesheet" href="/css/base.css">
|
|
114
|
+
<title>My ViewLogic App</title>
|
|
66
115
|
</head>
|
|
67
116
|
<body>
|
|
68
117
|
<div id="app"></div>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<script>
|
|
75
|
-
// Development mode - loads files directly from src/
|
|
76
|
-
ViewLogicRouter({
|
|
77
|
-
environment: 'development',
|
|
118
|
+
<script type="module">
|
|
119
|
+
import { ViewLogicRouter } from 'viewlogic';
|
|
120
|
+
const router = new ViewLogicRouter({
|
|
121
|
+
authEnabled: true,
|
|
122
|
+
useI18n: true
|
|
78
123
|
});
|
|
79
124
|
</script>
|
|
80
125
|
</body>
|
|
81
126
|
</html>
|
|
82
127
|
```
|
|
83
128
|
|
|
84
|
-
###
|
|
129
|
+
### Method 2: UMD Build (No Build Tools)
|
|
85
130
|
|
|
86
131
|
```html
|
|
87
132
|
<!DOCTYPE html>
|
|
88
133
|
<html>
|
|
89
134
|
<head>
|
|
90
|
-
<meta charset="UTF-8">
|
|
91
135
|
<title>My ViewLogic App</title>
|
|
92
|
-
<
|
|
136
|
+
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script>
|
|
137
|
+
<script src="https://cdn.jsdelivr.net/npm/viewlogic/dist/viewlogic-router.umd.js"></script>
|
|
93
138
|
</head>
|
|
94
139
|
<body>
|
|
95
140
|
<div id="app"></div>
|
|
96
|
-
|
|
97
|
-
<!-- Vue 3 (production version) -->
|
|
98
|
-
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
|
|
99
|
-
<script src="https://cdn.jsdelivr.net/npm/viewlogic/dist/viewlogic-router.umd.js"></script>
|
|
100
|
-
|
|
101
141
|
<script>
|
|
102
|
-
|
|
103
|
-
ViewLogicRouter({
|
|
104
|
-
environment: 'production',
|
|
105
|
-
useI18n: true,
|
|
106
|
-
logLevel: 'error' // Only log errors
|
|
107
|
-
});
|
|
142
|
+
const router = new ViewLogicRouter();
|
|
108
143
|
</script>
|
|
109
144
|
</body>
|
|
110
145
|
</html>
|
|
111
146
|
```
|
|
112
147
|
|
|
113
|
-
###
|
|
114
|
-
|
|
115
|
-
```javascript
|
|
116
|
-
import { ViewLogicRouter } from 'js/viewlogic-router.js';
|
|
117
|
-
|
|
118
|
-
// Create router instance
|
|
119
|
-
const router = new ViewLogicRouter({
|
|
120
|
-
environment: 'development'
|
|
121
|
-
});
|
|
148
|
+
### Create Your First Route
|
|
122
149
|
|
|
123
|
-
|
|
150
|
+
**src/views/home.html**
|
|
151
|
+
```html
|
|
152
|
+
<div class="home">
|
|
153
|
+
<h1>{{ message }}</h1>
|
|
154
|
+
<p>Counter: {{ count }} | Global Count: {{ globalCount }}</p>
|
|
155
|
+
<button @click="increment">Local +1</button>
|
|
156
|
+
<button @click="incrementGlobal">Global +1</button>
|
|
157
|
+
<button @click="saveUser" :disabled="loading">
|
|
158
|
+
{{ loading ? 'Saving...' : 'Save User' }}
|
|
159
|
+
</button>
|
|
160
|
+
<div v-if="user">
|
|
161
|
+
<h3>{{ $t('welcome.message', { name: user.name }) }}</h3>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
124
164
|
```
|
|
125
165
|
|
|
126
|
-
|
|
127
|
-
|
|
166
|
+
**src/logic/home.js**
|
|
128
167
|
```javascript
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
168
|
+
export default {
|
|
169
|
+
data() {
|
|
170
|
+
return {
|
|
171
|
+
message: 'Welcome to ViewLogic!',
|
|
172
|
+
count: 0,
|
|
173
|
+
loading: false
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
computed: {
|
|
177
|
+
globalCount() {
|
|
178
|
+
return this.$state.get('globalCounter', 0);
|
|
179
|
+
},
|
|
180
|
+
user() {
|
|
181
|
+
return this.$state.get('currentUser');
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
methods: {
|
|
185
|
+
increment() {
|
|
186
|
+
this.count++;
|
|
187
|
+
},
|
|
188
|
+
incrementGlobal() {
|
|
189
|
+
const current = this.$state.get('globalCounter', 0);
|
|
190
|
+
this.$state.set('globalCounter', current + 1);
|
|
191
|
+
},
|
|
192
|
+
async saveUser() {
|
|
193
|
+
this.loading = true;
|
|
194
|
+
try {
|
|
195
|
+
const userData = {
|
|
196
|
+
name: 'John Doe',
|
|
197
|
+
email: 'john@example.com'
|
|
198
|
+
};
|
|
137
199
|
|
|
138
|
-
|
|
200
|
+
const savedUser = await this.$api.post('/api/users', userData);
|
|
201
|
+
this.$state.set('currentUser', savedUser);
|
|
139
202
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
│ │ ├── home.html
|
|
156
|
-
│ │ ├── about.html
|
|
157
|
-
│ │ └── products/
|
|
158
|
-
│ │ ├── list.html
|
|
159
|
-
│ │ └── detail.html
|
|
160
|
-
│ ├── logic/ # Business logic (JavaScript)
|
|
161
|
-
│ │ ├── home.js
|
|
162
|
-
│ │ ├── about.js
|
|
163
|
-
│ │ └── products/
|
|
164
|
-
│ │ ├── list.js
|
|
165
|
-
│ │ └── detail.js
|
|
166
|
-
│ ├── styles/ # Page-specific CSS
|
|
167
|
-
│ │ ├── home.css
|
|
168
|
-
│ │ ├── about.css
|
|
169
|
-
│ │ └── products/
|
|
170
|
-
│ │ ├── list.css
|
|
171
|
-
│ │ └── detail.css
|
|
172
|
-
│ ├── layouts/ # Layout templates
|
|
173
|
-
│ │ ├── default.html
|
|
174
|
-
│ │ └── admin.html
|
|
175
|
-
│ └── components/ # Reusable components
|
|
176
|
-
│ ├── Button.js
|
|
177
|
-
│ ├── Modal.js
|
|
178
|
-
│ └── Card.js
|
|
179
|
-
└── package.json
|
|
203
|
+
this.$toast?.success?.('User saved successfully!');
|
|
204
|
+
} catch (error) {
|
|
205
|
+
this.$toast?.error?.('Failed to save user');
|
|
206
|
+
} finally {
|
|
207
|
+
this.loading = false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
mounted() {
|
|
212
|
+
// Watch for global state changes
|
|
213
|
+
this.$state.watch('currentUser', (newUser) => {
|
|
214
|
+
console.log('User changed globally:', newUser);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
180
218
|
```
|
|
181
219
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
├── index.html
|
|
186
|
-
├── i18n/ # Language files
|
|
187
|
-
│ ├── ko.json
|
|
188
|
-
│ └── en.json
|
|
189
|
-
├── css/ # Global styles
|
|
190
|
-
│ └── base.css
|
|
191
|
-
├── js/ # Router system (or use CDN)
|
|
192
|
-
│ ├── viewlogic-router.umd.js
|
|
193
|
-
│ └── viewlogic-router.min.js
|
|
194
|
-
├── routes/ # Built & optimized route bundles
|
|
195
|
-
│ ├── home.js # Combined view + logic + style
|
|
196
|
-
│ ├── about.js
|
|
197
|
-
│ └── products/
|
|
198
|
-
│ ├── list.js
|
|
199
|
-
│ └── detail.js
|
|
200
|
-
└── assets/ # Static assets
|
|
201
|
-
├── images/
|
|
202
|
-
└── fonts/
|
|
203
|
-
|
|
204
|
-
Note: src/ folder is excluded from production deployment
|
|
205
|
-
```
|
|
220
|
+
## 🎯 Core APIs
|
|
221
|
+
|
|
222
|
+
### State Management
|
|
206
223
|
|
|
207
|
-
|
|
224
|
+
ViewLogic Router includes a powerful built-in state management system:
|
|
208
225
|
|
|
209
226
|
```javascript
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
cacheTTL: 300000, // Cache TTL in milliseconds
|
|
224
|
-
maxCacheSize: 50, // Maximum cache entries
|
|
225
|
-
|
|
226
|
-
// Components
|
|
227
|
-
useComponents: true, // Enable built-in components
|
|
228
|
-
componentNames: [ // Components to preload
|
|
229
|
-
'Button', 'Modal', 'Card', 'Toast',
|
|
230
|
-
'Input', 'Tabs', 'Checkbox', 'Alert'
|
|
231
|
-
],
|
|
232
|
-
|
|
233
|
-
// Internationalization
|
|
234
|
-
useI18n: true, // Enable i18n
|
|
235
|
-
defaultLanguage: 'ko', // Default language
|
|
236
|
-
|
|
237
|
-
// Authentication
|
|
238
|
-
authEnabled: false, // Enable authentication
|
|
239
|
-
loginRoute: 'login', // Login route name
|
|
240
|
-
protectedRoutes: [], // Protected route names
|
|
241
|
-
publicRoutes: ['login', 'register', 'home'],
|
|
242
|
-
authStorage: 'cookie', // 'cookie' or 'localStorage'
|
|
243
|
-
|
|
244
|
-
// Security
|
|
245
|
-
enableParameterValidation: true,
|
|
246
|
-
maxParameterLength: 1000,
|
|
247
|
-
maxParameterCount: 50,
|
|
248
|
-
|
|
249
|
-
// Development
|
|
250
|
-
logLevel: 'info', // 'debug', 'info', 'warn', 'error'
|
|
251
|
-
enableErrorReporting: true
|
|
252
|
-
};
|
|
253
|
-
```
|
|
227
|
+
// Set state (any component can access)
|
|
228
|
+
this.$state.set('user', { name: 'John', age: 30 });
|
|
229
|
+
this.$state.set('theme', 'dark');
|
|
230
|
+
this.$state.set('shoppingCart', []);
|
|
231
|
+
|
|
232
|
+
// Get state with optional default
|
|
233
|
+
const user = this.$state.get('user');
|
|
234
|
+
const theme = this.$state.get('theme', 'light');
|
|
235
|
+
|
|
236
|
+
// Check if state exists
|
|
237
|
+
if (this.$state.has('user')) {
|
|
238
|
+
console.log('User is logged in');
|
|
239
|
+
}
|
|
254
240
|
|
|
255
|
-
|
|
241
|
+
// Watch for changes (reactive)
|
|
242
|
+
this.$state.watch('user', (newValue, oldValue) => {
|
|
243
|
+
console.log('User changed:', newValue);
|
|
244
|
+
this.updateUI();
|
|
245
|
+
});
|
|
256
246
|
|
|
257
|
-
|
|
247
|
+
// Stop watching
|
|
248
|
+
this.$state.unwatch('user', callbackFunction);
|
|
258
249
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
i18nPath: '/i18n' // → https://example.com/i18n
|
|
250
|
+
// Bulk updates
|
|
251
|
+
this.$state.update({
|
|
252
|
+
theme: 'dark',
|
|
253
|
+
language: 'ko',
|
|
254
|
+
sidebar: 'collapsed'
|
|
265
255
|
});
|
|
266
256
|
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
basePath: 'src', // → https://example.com/myapp/src (relative)
|
|
270
|
-
routesPath: 'routes', // → https://example.com/myapp/routes (relative)
|
|
271
|
-
i18nPath: 'i18n', // → https://example.com/myapp/i18n (relative)
|
|
272
|
-
});
|
|
257
|
+
// Get all state
|
|
258
|
+
const allState = this.$state.getAll();
|
|
273
259
|
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
basePath: './src', // → https://example.com/projects/myapp/src
|
|
277
|
-
routesPath: '../shared/routes', // → https://example.com/projects/shared/routes
|
|
278
|
-
i18nPath: '/global/i18n' // → https://example.com/global/i18n (absolute)
|
|
279
|
-
});
|
|
260
|
+
// Clear specific state
|
|
261
|
+
this.$state.delete('temporaryData');
|
|
280
262
|
```
|
|
281
263
|
|
|
282
|
-
|
|
283
|
-
- **Absolute paths** (`/path`) → `https://domain.com/path`
|
|
284
|
-
- **Relative paths** (`path`, `./path`) → Resolved from current page location
|
|
285
|
-
- **Parent paths** (`../path`) → Navigate up directory levels
|
|
286
|
-
- **HTTP URLs** → Used as-is (no processing)
|
|
287
|
-
|
|
288
|
-
### 🔄 Hash vs History Mode in Subfolders
|
|
264
|
+
### Authentication System
|
|
289
265
|
|
|
290
|
-
|
|
266
|
+
Complete authentication management with multiple storage options:
|
|
291
267
|
|
|
292
268
|
```javascript
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
mode: 'hash' // Works anywhere, no server config needed
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
// History Mode (requires server configuration)
|
|
300
|
-
// URL: https://example.com/myapp/products?id=123
|
|
301
|
-
ViewLogicRouter({
|
|
302
|
-
mode: 'history' // Cleaner URLs, needs server setup
|
|
303
|
-
});
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
**History Mode Server Configuration:**
|
|
307
|
-
```nginx
|
|
308
|
-
# Nginx - redirect all subfolder requests to index.html
|
|
309
|
-
location /myapp/ {
|
|
310
|
-
try_files $uri $uri/ /myapp/index.html;
|
|
269
|
+
// Check authentication status
|
|
270
|
+
if (this.$isAuthenticated()) {
|
|
271
|
+
console.log('User is logged in');
|
|
311
272
|
}
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
```apache
|
|
315
|
-
# Apache .htaccess in /myapp/ folder
|
|
316
|
-
RewriteEngine On
|
|
317
|
-
RewriteBase /myapp/
|
|
318
|
-
RewriteRule ^index\.html$ - [L]
|
|
319
|
-
RewriteCond %{REQUEST_FILENAME} !-f
|
|
320
|
-
RewriteCond %{REQUEST_FILENAME} !-d
|
|
321
|
-
RewriteRule . /myapp/index.html [L]
|
|
322
|
-
```
|
|
323
273
|
|
|
324
|
-
|
|
274
|
+
// Login with token
|
|
275
|
+
this.$setToken('jwt-token-here');
|
|
325
276
|
|
|
326
|
-
|
|
277
|
+
// Login with options
|
|
278
|
+
this.$setToken('jwt-token', {
|
|
279
|
+
storage: 'localStorage', // 'localStorage', 'sessionStorage', 'cookie'
|
|
280
|
+
skipValidation: false // Skip JWT validation
|
|
281
|
+
});
|
|
327
282
|
|
|
328
|
-
|
|
283
|
+
// Get current token
|
|
284
|
+
const token = this.$getToken();
|
|
329
285
|
|
|
330
|
-
|
|
286
|
+
// Login success handling (redirects to protected route or default)
|
|
287
|
+
this.$loginSuccess('/dashboard');
|
|
331
288
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const router = new ViewLogicRouter({ environment: 'development' });
|
|
335
|
-
router.navigateTo('products', { id: 123, category: 'electronics' });
|
|
336
|
-
const current = router.getCurrentRoute();
|
|
289
|
+
// Logout (clears token and redirects to login)
|
|
290
|
+
this.$logout();
|
|
337
291
|
|
|
338
|
-
//
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
this.navigateTo('detail', { id }); // Navigate
|
|
344
|
-
console.log('Data loaded:', this.products); // From dataURL
|
|
345
|
-
|
|
346
|
-
// New $api pattern for RESTful API calls
|
|
347
|
-
const user = await this.$api.get('/api/users/{userId}');
|
|
348
|
-
await this.$api.post('/api/analytics', { pageView: 'products' });
|
|
349
|
-
|
|
350
|
-
if (this.$isAuthenticated()) { /* auth check */ }
|
|
351
|
-
const text = this.$t('welcome.message'); // i18n
|
|
352
|
-
}
|
|
353
|
-
};
|
|
292
|
+
// Manual auth check for specific route
|
|
293
|
+
const authResult = await this.$checkAuth('admin-panel');
|
|
294
|
+
if (authResult.allowed) {
|
|
295
|
+
// User can access admin panel
|
|
296
|
+
}
|
|
354
297
|
```
|
|
355
298
|
|
|
356
|
-
###
|
|
357
|
-
|
|
358
|
-
-
|
|
359
|
-
- **Data Fetching**: `$fetchData()` (with dataURL), `$api.get()`, `$api.post()`, `$api.put()`, `$api.patch()`, `$api.delete()`
|
|
360
|
-
- **Authentication**: `$isAuthenticated()`, `$getToken()`, `$logout()`
|
|
361
|
-
- **Forms**: Auto-binding with `action` attribute, duplicate prevention, validation
|
|
362
|
-
- **i18n**: `$t(key, params)` for translations
|
|
299
|
+
### API Management
|
|
300
|
+
|
|
301
|
+
Built-in HTTP client with automatic token injection and parameter substitution:
|
|
363
302
|
|
|
364
|
-
### Auto-Injected Properties
|
|
365
303
|
```javascript
|
|
366
|
-
//
|
|
367
|
-
|
|
368
|
-
```
|
|
304
|
+
// GET request
|
|
305
|
+
const users = await this.$api.get('/api/users');
|
|
369
306
|
|
|
370
|
-
|
|
307
|
+
// GET with query parameters
|
|
308
|
+
const filteredUsers = await this.$api.get('/api/users', {
|
|
309
|
+
params: { role: 'admin', active: true }
|
|
310
|
+
});
|
|
371
311
|
|
|
372
|
-
|
|
312
|
+
// POST with data
|
|
313
|
+
const newUser = await this.$api.post('/api/users', {
|
|
314
|
+
name: 'John',
|
|
315
|
+
email: 'john@example.com'
|
|
316
|
+
});
|
|
373
317
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
- **🔄 Hot Reload**: Instant changes without compilation or bundling
|
|
318
|
+
// PUT/PATCH/DELETE
|
|
319
|
+
await this.$api.put('/api/users/{userId}', userData);
|
|
320
|
+
await this.$api.patch('/api/users/{userId}', partialData);
|
|
321
|
+
await this.$api.delete('/api/users/{userId}');
|
|
379
322
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
- **Style**: `src/styles/products.css` - Pure CSS styles
|
|
323
|
+
// Parameter substitution (automatically uses route/query params)
|
|
324
|
+
// If current route is /users?userId=123, this becomes /api/users/123
|
|
325
|
+
const user = await this.$api.get('/api/users/{userId}');
|
|
384
326
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
327
|
+
// Custom headers
|
|
328
|
+
const response = await this.$api.post('/api/secure-endpoint', data, {
|
|
329
|
+
headers: { 'X-Custom-Header': 'value' }
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Automatic data loading in components
|
|
388
333
|
export default {
|
|
389
|
-
|
|
390
|
-
dataURL: '/api/
|
|
391
|
-
|
|
392
|
-
|
|
334
|
+
// Single API endpoint
|
|
335
|
+
dataURL: '/api/user/profile',
|
|
336
|
+
|
|
337
|
+
// Multiple endpoints
|
|
338
|
+
dataURL: {
|
|
339
|
+
profile: '/api/user/profile',
|
|
340
|
+
posts: '/api/posts?userId={userId}',
|
|
341
|
+
notifications: '/api/notifications'
|
|
393
342
|
},
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
343
|
+
|
|
344
|
+
mounted() {
|
|
345
|
+
// Data automatically loaded and available as:
|
|
346
|
+
// this.profile, this.posts, this.notifications
|
|
398
347
|
}
|
|
399
348
|
};
|
|
400
349
|
```
|
|
401
350
|
|
|
402
|
-
###
|
|
403
|
-
All separate files automatically combine into optimized bundles in `routes/` folder - maintaining the development philosophy while optimizing for production.
|
|
404
|
-
|
|
405
|
-
## 🔄 Zero Build Development vs Optimized Production
|
|
406
|
-
|
|
407
|
-
ViewLogic Router's **Zero Build Development** (core philosophy) vs optimized production:
|
|
351
|
+
### Internationalization
|
|
408
352
|
|
|
409
|
-
|
|
410
|
-
|------|------------|-------|----------|------------|
|
|
411
|
-
| **Development** | **Zero Build Required** | Separate files | 4 per route | **Real-time, instant changes** |
|
|
412
|
-
| **Production** | **Optimized Performance** | Single bundle | 1 per route | **Lightning-fast loading** |
|
|
353
|
+
Comprehensive i18n system with lazy loading and pluralization:
|
|
413
354
|
|
|
414
355
|
```javascript
|
|
415
|
-
//
|
|
416
|
-
|
|
356
|
+
// Simple translation
|
|
357
|
+
const message = this.$t('welcome.message');
|
|
417
358
|
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
```
|
|
359
|
+
// With parameters
|
|
360
|
+
const greeting = this.$t('hello.user', { name: 'John', role: 'admin' });
|
|
421
361
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
- 🚀 **Zero Setup** - No webpack, vite, or build tools required
|
|
425
|
-
- 🎯 **True Hot Reload** - Files load directly from src/ folder
|
|
426
|
-
- 🛠️ **Pure Development** - Focus on code, not build configuration
|
|
362
|
+
// Nested keys
|
|
363
|
+
const errorMsg = this.$t('errors.validation.email.required');
|
|
427
364
|
|
|
428
|
-
|
|
365
|
+
// Plural forms
|
|
366
|
+
const itemCount = this.$plural('items.count', count, { count });
|
|
367
|
+
// items.count.singular: "{count} item"
|
|
368
|
+
// items.count.plural: "{count} items"
|
|
429
369
|
|
|
430
|
-
|
|
370
|
+
// Check current language
|
|
371
|
+
const currentLang = this.$i18n.getCurrentLanguage();
|
|
431
372
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
- **Vue Router + Auth + i18n + Cache**: 50KB+ gzipped
|
|
373
|
+
// Change language (automatically reloads interface)
|
|
374
|
+
await this.$i18n.setLanguage('ko');
|
|
435
375
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
- ✅ Component lazy loading
|
|
443
|
-
- ✅ Layout system
|
|
444
|
-
- ✅ Error handling
|
|
445
|
-
- ✅ Development/production modes
|
|
446
|
-
- ✅ **Automatic data fetching with dataURL**
|
|
447
|
-
- ✅ **Revolutionary DynamicInclude & HtmlInclude components**
|
|
448
|
-
- ✅ **Automatic form handling with variable parameters**
|
|
449
|
-
- ✅ **10+ Built-in UI components (Button, Modal, Card, etc.)**
|
|
450
|
-
|
|
451
|
-
### Why So Small?
|
|
452
|
-
- **Zero Dependencies** - No external libraries required (except Vue 3)
|
|
453
|
-
- **Tree-Shakable** - Only includes what you use
|
|
454
|
-
- **Optimized Code** - Hand-crafted for minimal bundle size
|
|
455
|
-
- **Smart Bundling** - Efficient code organization and minification
|
|
456
|
-
|
|
457
|
-
### Performance Benefits
|
|
458
|
-
- **Faster Load Times** - 70% smaller than typical Vue router setups
|
|
459
|
-
- **Better UX** - Instant page loads with minimal JavaScript overhead
|
|
460
|
-
- **Mobile Optimized** - Perfect for mobile-first applications
|
|
461
|
-
- **CDN Friendly** - Small size ideal for CDN distribution
|
|
462
|
-
|
|
463
|
-
## 🏆 Performance Comparison
|
|
464
|
-
|
|
465
|
-
### Bundle Size Comparison
|
|
466
|
-
| Router System | Bundle Size (Gzipped) | Features Included |
|
|
467
|
-
|---------------|----------------------|------------------|
|
|
468
|
-
| **ViewLogic Router** | **13KB** | Routing + Auth + i18n + Cache + Query + Components |
|
|
469
|
-
| Vue Router | 12KB | Routing only |
|
|
470
|
-
| Vue Router + Pinia | 18KB | Routing + State |
|
|
471
|
-
| React Router | 15KB | Routing only |
|
|
472
|
-
| Next.js Router | 25KB+ | Routing + SSR |
|
|
473
|
-
| Nuxt Router | 30KB+ | Routing + SSR + Meta |
|
|
474
|
-
|
|
475
|
-
### Runtime Performance Comparison
|
|
476
|
-
|
|
477
|
-
#### Traditional SPA Routing
|
|
478
|
-
```
|
|
479
|
-
Route Change Process:
|
|
480
|
-
├── 1️⃣ Parse route
|
|
481
|
-
├── 2️⃣ Load component bundle
|
|
482
|
-
├── 3️⃣ Execute component code
|
|
483
|
-
├── 4️⃣ Load template (if separate)
|
|
484
|
-
├── 5️⃣ Load styles (if separate)
|
|
485
|
-
├── 6️⃣ Apply i18n translations
|
|
486
|
-
├── 7️⃣ Check authentication
|
|
487
|
-
└── 8️⃣ Render component
|
|
488
|
-
|
|
489
|
-
Total: Multiple operations + Bundle parsing
|
|
490
|
-
```
|
|
376
|
+
// Format dates/numbers according to locale
|
|
377
|
+
const date = this.$i18n.formatDate(new Date(), {
|
|
378
|
+
year: 'numeric',
|
|
379
|
+
month: 'long',
|
|
380
|
+
day: 'numeric'
|
|
381
|
+
});
|
|
491
382
|
|
|
492
|
-
|
|
383
|
+
const price = this.$i18n.formatNumber(1234.56, {
|
|
384
|
+
style: 'currency',
|
|
385
|
+
currency: 'USD'
|
|
386
|
+
});
|
|
493
387
|
```
|
|
494
|
-
Route Change Process:
|
|
495
|
-
├── 1️⃣ Load pre-built route bundle (all-in-one)
|
|
496
|
-
└── 2️⃣ Render component
|
|
497
388
|
|
|
498
|
-
|
|
499
|
-
```
|
|
389
|
+
### Navigation & Routing
|
|
500
390
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
-
|
|
506
|
-
- **💾 Superior Caching** - Route-level caching vs component-level caching
|
|
507
|
-
- **🔄 Zero Hydration** - No server-side rendering complexity
|
|
391
|
+
Simple yet powerful routing system:
|
|
392
|
+
|
|
393
|
+
```javascript
|
|
394
|
+
// Navigate to route
|
|
395
|
+
this.navigateTo('user-profile');
|
|
508
396
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
2. **All-in-One Bundles**: View + Logic + Style in single optimized file
|
|
512
|
-
3. **Zero Dependencies**: No additional libraries needed for full functionality
|
|
513
|
-
4. **Smart Caching**: Route-level caching with intelligent invalidation
|
|
514
|
-
5. **Optimized Architecture**: Purpose-built for maximum performance
|
|
515
|
-
6. **Revolutionary Components**: DynamicInclude & HtmlInclude for dynamic content loading
|
|
397
|
+
// With query parameters
|
|
398
|
+
this.navigateTo('user-profile', { userId: 123, tab: 'settings' });
|
|
516
399
|
|
|
517
|
-
|
|
400
|
+
// Object syntax
|
|
401
|
+
this.navigateTo({
|
|
402
|
+
route: 'search-results',
|
|
403
|
+
params: { query: 'vue', category: 'tutorials' }
|
|
404
|
+
});
|
|
518
405
|
|
|
519
|
-
|
|
406
|
+
// Get current route information
|
|
407
|
+
const currentRoute = this.$route.current;
|
|
408
|
+
const routeParams = this.$route.params;
|
|
409
|
+
const queryParams = this.$route.query;
|
|
520
410
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
page="login"
|
|
526
|
-
:use-cache="false"
|
|
527
|
-
loading-text="로그인 페이지 로딩 중..."
|
|
528
|
-
wrapper-class="test-dynamic-include"
|
|
529
|
-
:params="{
|
|
530
|
-
returnUrl: '/dashboard',
|
|
531
|
-
showWelcome: true,
|
|
532
|
-
theme: 'compact',
|
|
533
|
-
testMode: true
|
|
534
|
-
}"
|
|
535
|
-
/>
|
|
536
|
-
```
|
|
411
|
+
// Access specific parameters
|
|
412
|
+
const userId = this.$params.userId; // Route parameter
|
|
413
|
+
const tab = this.$query.tab; // Query parameter
|
|
414
|
+
const searchTerm = this.$param('search'); // Either route or query param
|
|
537
415
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
- **Parameter Injection** - Pass dynamic parameters to the URL
|
|
541
|
-
- **Event Handling** - React to loading states and errors
|
|
542
|
-
- **Slot Support** - Custom loading and error templates
|
|
543
|
-
- **Cache Integration** - Automatic caching with TTL support
|
|
416
|
+
// Check if route is protected
|
|
417
|
+
const isProtected = this.$route.isProtected('admin-dashboard');
|
|
544
418
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
:sanitize="true"
|
|
551
|
-
:use-cache="false"
|
|
552
|
-
loading-text="위젯 로딩 중..."
|
|
553
|
-
wrapper-class="test-html-include"
|
|
554
|
-
/>
|
|
419
|
+
// Navigate with state preservation
|
|
420
|
+
this.navigateTo('dashboard', {
|
|
421
|
+
preserveState: true,
|
|
422
|
+
scrollTop: false
|
|
423
|
+
});
|
|
555
424
|
```
|
|
556
425
|
|
|
557
|
-
|
|
558
|
-
- **Raw HTML Rendering** - Safely render dynamic HTML content
|
|
559
|
-
- **XSS Protection** - Built-in HTML sanitization
|
|
560
|
-
- **Vue Integration** - HTML content works with Vue reactivity
|
|
561
|
-
- **Fallback Support** - Default content when HTML is unavailable
|
|
562
|
-
- **Script Execution** - Optional JavaScript execution in HTML content
|
|
426
|
+
### Form Handling
|
|
563
427
|
|
|
564
|
-
|
|
428
|
+
Revolutionary form processing with automatic parameter substitution:
|
|
565
429
|
|
|
566
|
-
|
|
430
|
+
```html
|
|
431
|
+
<!-- Basic form with automatic handling -->
|
|
432
|
+
<form action="/api/users" method="POST">
|
|
433
|
+
<input name="name" v-model="userData.name" required>
|
|
434
|
+
<input name="email" v-model="userData.email" type="email" required>
|
|
435
|
+
<button type="submit">Create User</button>
|
|
436
|
+
</form>
|
|
567
437
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
// Data is already fetched and available!
|
|
582
|
-
console.log('Products loaded:', this.products);
|
|
583
|
-
console.log('Loading state:', this.$dataLoading);
|
|
584
|
-
},
|
|
585
|
-
methods: {
|
|
586
|
-
async refreshData() {
|
|
587
|
-
// Manual refresh if needed
|
|
588
|
-
await this.$fetchData();
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
};
|
|
438
|
+
<!-- Form with parameter substitution -->
|
|
439
|
+
<form action="/api/users/{userId}" method="PUT">
|
|
440
|
+
<input name="name" v-model="user.name">
|
|
441
|
+
<input name="email" v-model="user.email">
|
|
442
|
+
<button type="submit">Update User</button>
|
|
443
|
+
</form>
|
|
444
|
+
|
|
445
|
+
<!-- File upload form -->
|
|
446
|
+
<form action="/api/upload" method="POST" enctype="multipart/form-data">
|
|
447
|
+
<input name="avatar" type="file" accept="image/*">
|
|
448
|
+
<input name="description" v-model="description">
|
|
449
|
+
<button type="submit">Upload</button>
|
|
450
|
+
</form>
|
|
592
451
|
```
|
|
593
452
|
|
|
594
|
-
#### Multiple APIs (Advanced Usage) - 🆕 Revolutionary!
|
|
595
453
|
```javascript
|
|
596
|
-
//
|
|
454
|
+
// Programmatic form submission
|
|
597
455
|
export default {
|
|
598
|
-
name: 'DashboardMain',
|
|
599
|
-
dataURL: {
|
|
600
|
-
products: '/api/products',
|
|
601
|
-
categories: '/api/categories',
|
|
602
|
-
stats: '/api/dashboard/stats',
|
|
603
|
-
user: '/api/user/profile'
|
|
604
|
-
}, // ✨ Multiple APIs with named data!
|
|
605
|
-
data() {
|
|
606
|
-
return {
|
|
607
|
-
title: 'Dashboard'
|
|
608
|
-
// products: [], categories: [], stats: {}, user: {}
|
|
609
|
-
// All auto-populated from respective APIs!
|
|
610
|
-
};
|
|
611
|
-
},
|
|
612
|
-
mounted() {
|
|
613
|
-
// All APIs called in parallel, data available by name!
|
|
614
|
-
console.log('Products:', this.products);
|
|
615
|
-
console.log('Categories:', this.categories);
|
|
616
|
-
console.log('Stats:', this.stats);
|
|
617
|
-
console.log('User:', this.user);
|
|
618
|
-
console.log('Loading state:', this.$dataLoading);
|
|
619
|
-
},
|
|
620
456
|
methods: {
|
|
621
|
-
async
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
457
|
+
async submitForm() {
|
|
458
|
+
const formData = {
|
|
459
|
+
name: this.userData.name,
|
|
460
|
+
email: this.userData.email
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
const result = await this.$form.submit('/api/users', formData, {
|
|
465
|
+
method: 'POST',
|
|
466
|
+
onProgress: (progress) => {
|
|
467
|
+
this.uploadProgress = progress;
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
this.$state.set('newUser', result);
|
|
472
|
+
this.navigateTo('user-profile', { userId: result.id });
|
|
473
|
+
} catch (error) {
|
|
474
|
+
this.handleError(error);
|
|
475
|
+
}
|
|
632
476
|
}
|
|
633
477
|
}
|
|
634
478
|
};
|
|
635
479
|
```
|
|
636
480
|
|
|
637
|
-
|
|
638
|
-
- **Zero-Config API Calls** - Just define `dataURL` and data is automatically fetched
|
|
639
|
-
- **🆕 Multiple API Support** - Define multiple APIs with custom names
|
|
640
|
-
- **🚀 Parallel Processing** - Multiple APIs called simultaneously for best performance
|
|
641
|
-
- **🎯 Selective Refresh** - Refresh specific APIs independently
|
|
642
|
-
- **Query Parameter Integration** - Current route parameters are automatically sent to all APIs
|
|
643
|
-
- **Loading State Management** - `$dataLoading` property automatically managed
|
|
644
|
-
- **Advanced Error Handling** - Per-API error handling with detailed events
|
|
645
|
-
- **Named Data Storage** - Each API result stored with its defined name
|
|
646
|
-
- **Event Support** - `@data-loaded` and `@data-error` events with detailed info
|
|
647
|
-
|
|
648
|
-
### Why These Components Are Revolutionary
|
|
481
|
+
## ⚙️ Configuration
|
|
649
482
|
|
|
650
|
-
|
|
483
|
+
### Basic Configuration
|
|
651
484
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
- **Dynamic Content**: `<DynamicInclude page="login" :params="{ theme: 'compact' }" />`
|
|
658
|
-
- **HTML Includes**: `<HtmlInclude src="/widgets/weather.html" :sanitize="true" />`
|
|
485
|
+
```javascript
|
|
486
|
+
const router = new ViewLogicRouter({
|
|
487
|
+
// Core routing settings
|
|
488
|
+
basePath: '/', // Base path for the application
|
|
489
|
+
mode: 'hash', // 'hash' or 'history'
|
|
659
490
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
- ✅ **Parameter Integration** - Query params sent automatically
|
|
663
|
-
- ✅ **Loading States** - `$dataLoading` auto-managed
|
|
664
|
-
- ✅ **Built-in Security** - HTML sanitization included
|
|
665
|
-
- ✅ **Zero Setup** - Works immediately without configuration
|
|
491
|
+
// State management (always enabled)
|
|
492
|
+
// No configuration needed - StateHandler is always available
|
|
666
493
|
|
|
667
|
-
|
|
494
|
+
// Authentication settings
|
|
495
|
+
authEnabled: true, // Enable authentication system
|
|
496
|
+
loginRoute: 'login', // Route name for login page
|
|
497
|
+
protectedRoutes: ['dashboard', 'profile', 'admin'],
|
|
498
|
+
protectedPrefixes: ['admin/', 'secure/'],
|
|
499
|
+
publicRoutes: ['login', 'register', 'home', 'about'],
|
|
500
|
+
authStorage: 'localStorage', // 'localStorage', 'sessionStorage', 'cookie'
|
|
668
501
|
|
|
669
|
-
|
|
502
|
+
// Internationalization
|
|
503
|
+
useI18n: true, // Enable i18n system
|
|
504
|
+
defaultLanguage: 'en', // Default language
|
|
505
|
+
i18nPath: '/src/locales', // Path to language files
|
|
506
|
+
|
|
507
|
+
// Caching system
|
|
508
|
+
cacheMode: 'memory', // 'memory' only (others removed for simplicity)
|
|
509
|
+
cacheTTL: 300000, // Cache TTL in milliseconds (5 minutes)
|
|
510
|
+
maxCacheSize: 100, // Maximum number of cached items
|
|
511
|
+
|
|
512
|
+
// API settings
|
|
513
|
+
apiBaseURL: '/api', // Base URL for API requests
|
|
514
|
+
apiTimeout: 10000, // Request timeout in milliseconds
|
|
515
|
+
|
|
516
|
+
// Development settings
|
|
517
|
+
environment: 'development', // 'development' or 'production'
|
|
518
|
+
logLevel: 'info' // 'error', 'warn', 'info', 'debug'
|
|
519
|
+
});
|
|
520
|
+
```
|
|
670
521
|
|
|
671
|
-
###
|
|
522
|
+
### Advanced Configuration
|
|
672
523
|
|
|
673
524
|
```javascript
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
525
|
+
const router = new ViewLogicRouter({
|
|
526
|
+
// Custom authentication function
|
|
527
|
+
authEnabled: true,
|
|
528
|
+
checkAuthFunction: async (routeName) => {
|
|
529
|
+
const token = localStorage.getItem('authToken');
|
|
530
|
+
if (!token) return false;
|
|
531
|
+
|
|
679
532
|
try {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
content: 'Post content here'
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
// PUT request for updates
|
|
690
|
-
await this.$api.put('/api/users/{userId}', {
|
|
691
|
-
name: user.name,
|
|
692
|
-
email: user.email
|
|
533
|
+
const response = await fetch('/api/auth/verify', {
|
|
534
|
+
method: 'POST',
|
|
535
|
+
headers: {
|
|
536
|
+
'Authorization': `Bearer ${token}`,
|
|
537
|
+
'Content-Type': 'application/json'
|
|
538
|
+
}
|
|
693
539
|
});
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
540
|
+
|
|
541
|
+
if (response.ok) {
|
|
542
|
+
const userData = await response.json();
|
|
543
|
+
// Store user data in global state
|
|
544
|
+
router.stateHandler.set('currentUser', userData);
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
return false;
|
|
698
548
|
} catch (error) {
|
|
699
|
-
console.error('
|
|
700
|
-
|
|
549
|
+
console.error('Auth verification failed:', error);
|
|
550
|
+
return false;
|
|
701
551
|
}
|
|
702
|
-
}
|
|
703
|
-
};
|
|
704
|
-
```
|
|
552
|
+
},
|
|
705
553
|
|
|
706
|
-
|
|
554
|
+
// Custom route loading
|
|
555
|
+
routeResolver: (routeName) => {
|
|
556
|
+
// Custom logic for resolving route files
|
|
557
|
+
if (routeName.startsWith('admin/')) {
|
|
558
|
+
return {
|
|
559
|
+
viewPath: `/admin/views/${routeName.slice(6)}.html`,
|
|
560
|
+
logicPath: `/admin/logic/${routeName.slice(6)}.js`
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
return null; // Use default resolution
|
|
564
|
+
},
|
|
707
565
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
566
|
+
// Global error handler
|
|
567
|
+
onError: (error, context) => {
|
|
568
|
+
console.error('ViewLogic Error:', error, context);
|
|
569
|
+
// Send to error tracking service
|
|
570
|
+
if (window.errorTracker) {
|
|
571
|
+
window.errorTracker.log(error, context);
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
|
|
575
|
+
// Global route change handler
|
|
576
|
+
onRouteChange: (newRoute, oldRoute) => {
|
|
577
|
+
// Analytics tracking
|
|
578
|
+
if (window.analytics) {
|
|
579
|
+
window.analytics.track('page_view', {
|
|
580
|
+
route: newRoute,
|
|
581
|
+
previous_route: oldRoute
|
|
715
582
|
});
|
|
716
|
-
|
|
717
|
-
// File upload with FormData
|
|
718
|
-
const formData = new FormData();
|
|
719
|
-
formData.append('file', this.selectedFile);
|
|
720
|
-
await this.$api.post('/api/upload', formData);
|
|
721
|
-
|
|
722
|
-
// With query parameters (automatically added from current route)
|
|
723
|
-
// URL: /users?id=123 → API call includes ?id=123
|
|
724
|
-
const result = await this.$api.get('/api/user-data');
|
|
725
|
-
},
|
|
726
|
-
|
|
727
|
-
// Error handling patterns
|
|
728
|
-
async safeApiCall() {
|
|
729
|
-
try {
|
|
730
|
-
const user = await this.$api.get('/api/users/{userId}');
|
|
731
|
-
this.user = user;
|
|
732
|
-
|
|
733
|
-
} catch (error) {
|
|
734
|
-
if (error.message.includes('404')) {
|
|
735
|
-
this.showError('User not found');
|
|
736
|
-
} else if (error.message.includes('401')) {
|
|
737
|
-
this.navigateTo('login');
|
|
738
|
-
} else {
|
|
739
|
-
this.showError('Something went wrong');
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
583
|
}
|
|
743
584
|
}
|
|
744
|
-
};
|
|
585
|
+
});
|
|
745
586
|
```
|
|
746
587
|
|
|
747
|
-
|
|
588
|
+
## 📦 Bundle Size Comparison
|
|
748
589
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
590
|
+
| Solution | Minified | Gzipped |
|
|
591
|
+
|----------|----------|---------|
|
|
592
|
+
| **ViewLogic Router** (Complete) | **51KB** | **17KB** |
|
|
593
|
+
| Vue Router + Pinia + Vue I18n | 150KB+ | 45KB+ |
|
|
594
|
+
| React Router + Redux + i18next | 200KB+ | 60KB+ |
|
|
595
|
+
| Next.js (Runtime) | 300KB+ | 85KB+ |
|
|
755
596
|
|
|
756
|
-
|
|
597
|
+
*ViewLogic Router provides the functionality of 7+ libraries in a package smaller than most single-purpose libraries.*
|
|
757
598
|
|
|
758
|
-
|
|
599
|
+
## 🎯 Migration Guide
|
|
759
600
|
|
|
760
|
-
###
|
|
761
|
-
|
|
762
|
-
```html
|
|
763
|
-
<!-- src/views/contact.html -->
|
|
764
|
-
<div class="contact-page">
|
|
765
|
-
<h1>Contact Us</h1>
|
|
766
|
-
<form action="/api/contact" method="POST">
|
|
767
|
-
<input type="text" name="name" required placeholder="Your Name">
|
|
768
|
-
<input type="email" name="email" required placeholder="Your Email">
|
|
769
|
-
<textarea name="message" required placeholder="Your Message"></textarea>
|
|
770
|
-
<button type="submit">Send Message</button>
|
|
771
|
-
</form>
|
|
772
|
-
</div>
|
|
773
|
-
```
|
|
601
|
+
### From Vue Router + Pinia
|
|
774
602
|
|
|
603
|
+
**Before:**
|
|
775
604
|
```javascript
|
|
776
|
-
//
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
};
|
|
788
|
-
```
|
|
789
|
-
|
|
790
|
-
### Smart Form Features - 🆕 Enhanced!
|
|
605
|
+
// main.js - Multiple library setup
|
|
606
|
+
import { createApp } from 'vue';
|
|
607
|
+
import { createRouter, createWebHistory } from 'vue-router';
|
|
608
|
+
import { createPinia } from 'pinia';
|
|
609
|
+
import { createI18n } from 'vue-i18n';
|
|
610
|
+
import App from './App.vue';
|
|
611
|
+
|
|
612
|
+
const router = createRouter({
|
|
613
|
+
history: createWebHistory(),
|
|
614
|
+
routes: [/* complex route config */]
|
|
615
|
+
});
|
|
791
616
|
|
|
792
|
-
|
|
617
|
+
const pinia = createPinia();
|
|
618
|
+
const i18n = createI18n({/* complex i18n config */});
|
|
793
619
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
class="auto-form"
|
|
798
|
-
data-success-handler="handleSuccess"
|
|
799
|
-
data-error-handler="handleError"
|
|
800
|
-
data-loading-handler="handleLoading"
|
|
801
|
-
data-redirect="/profile">
|
|
802
|
-
|
|
803
|
-
<input type="text" name="name" required
|
|
804
|
-
data-validation="validateName">
|
|
805
|
-
<input type="email" name="email" required>
|
|
806
|
-
<button type="submit">Update Profile</button>
|
|
807
|
-
</form>
|
|
620
|
+
const app = createApp(App);
|
|
621
|
+
app.use(router).use(pinia).use(i18n);
|
|
622
|
+
app.mount('#app');
|
|
808
623
|
```
|
|
809
624
|
|
|
625
|
+
**After:**
|
|
810
626
|
```javascript
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
// Custom validation
|
|
814
|
-
validateName(value) {
|
|
815
|
-
return value.length >= 2 && value.length <= 50;
|
|
816
|
-
},
|
|
817
|
-
|
|
818
|
-
// Success handler
|
|
819
|
-
handleSuccess(response, form) {
|
|
820
|
-
this.showToast('Profile updated successfully!', 'success');
|
|
821
|
-
// Automatic redirect to /profile happens after this
|
|
822
|
-
},
|
|
823
|
-
|
|
824
|
-
// Error handler with smart error detection
|
|
825
|
-
handleError(error, form) {
|
|
826
|
-
if (error.message.includes('validation')) {
|
|
827
|
-
this.showToast('Please check your input', 'warning');
|
|
828
|
-
} else {
|
|
829
|
-
this.showToast('Update failed. Please try again.', 'error');
|
|
830
|
-
}
|
|
831
|
-
},
|
|
832
|
-
|
|
833
|
-
// Loading state handler
|
|
834
|
-
handleLoading(isLoading, form) {
|
|
835
|
-
const button = form.querySelector('button[type="submit"]');
|
|
836
|
-
button.disabled = isLoading;
|
|
837
|
-
button.textContent = isLoading ? 'Updating...' : 'Update Profile';
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
};
|
|
841
|
-
```
|
|
842
|
-
|
|
843
|
-
### Key Form Features
|
|
844
|
-
|
|
845
|
-
- **🚫 Duplicate Prevention**: Automatic duplicate submission blocking
|
|
846
|
-
- **⏱️ Timeout Management**: 30-second default timeout with abort capability
|
|
847
|
-
- **✅ Built-in Validation**: HTML5 + custom validation functions
|
|
848
|
-
- **🔄 Loading States**: Automatic loading state management
|
|
849
|
-
- **🎯 Smart Error Handling**: Network vs validation error distinction
|
|
850
|
-
- **📄 File Upload Support**: Automatic FormData vs JSON detection
|
|
851
|
-
- **🔀 Auto Redirect**: Post-success navigation
|
|
852
|
-
- **🏷️ Parameter Substitution**: Dynamic URL parameter replacement
|
|
627
|
+
// index.html - Single setup
|
|
628
|
+
import { ViewLogicRouter } from 'viewlogic';
|
|
853
629
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
<form action="/api/users/{userId}/posts" method="POST"
|
|
861
|
-
data-success="handlePostSuccess"
|
|
862
|
-
data-error="handlePostError">
|
|
863
|
-
<input type="text" name="title" required placeholder="Post Title">
|
|
864
|
-
<textarea name="content" required placeholder="Post Content"></textarea>
|
|
865
|
-
<button type="submit">Create Post</button>
|
|
866
|
-
</form>
|
|
867
|
-
|
|
868
|
-
<!-- Order update with dynamic order ID -->
|
|
869
|
-
<form action="/api/orders/{orderId}/update" method="PUT"
|
|
870
|
-
data-success="orderUpdated"
|
|
871
|
-
data-redirect="/orders">
|
|
872
|
-
<input type="number" name="quantity" required>
|
|
873
|
-
<select name="status">
|
|
874
|
-
<option value="pending">Pending</option>
|
|
875
|
-
<option value="processing">Processing</option>
|
|
876
|
-
<option value="completed">Completed</option>
|
|
877
|
-
</select>
|
|
878
|
-
<button type="submit">Update Order</button>
|
|
879
|
-
</form>
|
|
880
|
-
|
|
881
|
-
<!-- File upload support -->
|
|
882
|
-
<form action="/api/profile/{userId}/avatar" method="POST" enctype="multipart/form-data"
|
|
883
|
-
data-success="avatarUploaded">
|
|
884
|
-
<input type="file" name="avatar" accept="image/*" required>
|
|
885
|
-
<button type="submit">Upload Avatar</button>
|
|
886
|
-
</form>
|
|
630
|
+
const router = new ViewLogicRouter({
|
|
631
|
+
mode: 'history',
|
|
632
|
+
useI18n: true,
|
|
633
|
+
authEnabled: true
|
|
634
|
+
});
|
|
635
|
+
// Everything is auto-configured and ready to use!
|
|
887
636
|
```
|
|
888
637
|
|
|
638
|
+
### State Management Migration
|
|
639
|
+
|
|
640
|
+
**Before (Pinia):**
|
|
889
641
|
```javascript
|
|
890
|
-
//
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
642
|
+
// stores/user.js
|
|
643
|
+
import { defineStore } from 'pinia';
|
|
644
|
+
|
|
645
|
+
export const useUserStore = defineStore('user', {
|
|
646
|
+
state: () => ({
|
|
647
|
+
currentUser: null,
|
|
648
|
+
preferences: {}
|
|
649
|
+
}),
|
|
650
|
+
getters: {
|
|
651
|
+
isLoggedIn: (state) => !!state.currentUser,
|
|
652
|
+
userName: (state) => state.currentUser?.name || 'Guest'
|
|
898
653
|
},
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
654
|
+
actions: {
|
|
655
|
+
setUser(userData) {
|
|
656
|
+
this.currentUser = userData;
|
|
902
657
|
},
|
|
903
|
-
|
|
904
|
-
|
|
658
|
+
updatePreferences(prefs) {
|
|
659
|
+
this.preferences = { ...this.preferences, ...prefs };
|
|
905
660
|
}
|
|
906
661
|
}
|
|
907
|
-
};
|
|
908
|
-
```
|
|
909
|
-
|
|
910
|
-
### How Parameter Resolution Works
|
|
911
|
-
|
|
912
|
-
Parameters are resolved automatically from multiple sources in this order:
|
|
662
|
+
});
|
|
913
663
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
664
|
+
// In components
|
|
665
|
+
import { useUserStore } from '@/stores/user';
|
|
666
|
+
const userStore = useUserStore();
|
|
667
|
+
userStore.setUser(userData);
|
|
668
|
+
```
|
|
917
669
|
|
|
670
|
+
**After (ViewLogic):**
|
|
918
671
|
```javascript
|
|
919
|
-
//
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
},
|
|
928
|
-
computed: {
|
|
929
|
-
currentOrderId() { // Available as {currentOrderId} in action URLs
|
|
930
|
-
return this.getParam('orderId') || this.defaultOrderId;
|
|
931
|
-
}
|
|
672
|
+
// In any component - no store files needed!
|
|
673
|
+
this.$state.set('currentUser', userData);
|
|
674
|
+
this.$state.update({ preferences: newPrefs });
|
|
675
|
+
|
|
676
|
+
// Computed properties work naturally
|
|
677
|
+
computed: {
|
|
678
|
+
isLoggedIn() {
|
|
679
|
+
return !!this.$state.get('currentUser');
|
|
932
680
|
},
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
// {userId} will use 789 from URL, or fall back to data() value of 123
|
|
681
|
+
userName() {
|
|
682
|
+
return this.$state.get('currentUser')?.name || 'Guest';
|
|
936
683
|
}
|
|
937
|
-
}
|
|
684
|
+
}
|
|
938
685
|
```
|
|
939
686
|
|
|
940
|
-
|
|
941
|
-
```html
|
|
942
|
-
<form action="/api/subscribe" method="POST"
|
|
943
|
-
data-success="subscriptionSuccess" data-error="subscriptionError">
|
|
944
|
-
<input type="email" name="email" required>
|
|
945
|
-
<button type="submit">Subscribe</button>
|
|
946
|
-
</form>
|
|
947
|
-
```
|
|
948
|
-
```javascript
|
|
949
|
-
export default {
|
|
950
|
-
methods: {
|
|
951
|
-
subscriptionSuccess(response) { this.$toast('Success!', 'success'); },
|
|
952
|
-
subscriptionError(error) { this.$toast('Failed!', 'error'); }
|
|
953
|
-
}
|
|
954
|
-
};
|
|
955
|
-
```
|
|
687
|
+
## 🔧 Production Build
|
|
956
688
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
data-success="handleSuccess" data-error="handleError"
|
|
961
|
-
data-redirect="/success" data-confirm="Sure?"
|
|
962
|
-
enctype="multipart/form-data">
|
|
963
|
-
<input name="title" required>
|
|
964
|
-
<input type="file" name="file" accept=".pdf">
|
|
965
|
-
<button type="submit">Submit</button>
|
|
966
|
-
</form>
|
|
967
|
-
```
|
|
689
|
+
```bash
|
|
690
|
+
# Development mode (zero build)
|
|
691
|
+
npm run dev
|
|
968
692
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
<!-- Auth tokens automatically included for authenticated users -->
|
|
972
|
-
<form action="/api/protected/resource" method="POST">
|
|
973
|
-
<input name="data" required>
|
|
974
|
-
<button type="submit">Save</button>
|
|
975
|
-
</form>
|
|
976
|
-
<!-- Authorization: Bearer <token> header added automatically -->
|
|
977
|
-
```
|
|
693
|
+
# Production build with optimization
|
|
694
|
+
npm run build
|
|
978
695
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
<input type="password" name="password" minlength="8" required>
|
|
985
|
-
<button type="submit">Register</button>
|
|
986
|
-
</form>
|
|
696
|
+
# Preview production build
|
|
697
|
+
npm run serve
|
|
698
|
+
|
|
699
|
+
# Build with custom config
|
|
700
|
+
npm run build -- --config custom.config.js
|
|
987
701
|
```
|
|
988
702
|
|
|
989
|
-
###
|
|
990
|
-
```html
|
|
991
|
-
<!-- User profile with dynamic parameters -->
|
|
992
|
-
<form action="/api/users/{userId}" method="PUT" data-success="profileUpdated">
|
|
993
|
-
<input name="firstName" required>
|
|
994
|
-
<button type="submit">Update</button>
|
|
995
|
-
</form>
|
|
703
|
+
### Production Optimizations
|
|
996
704
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
705
|
+
ViewLogic Router automatically optimizes for production:
|
|
706
|
+
|
|
707
|
+
- **Code splitting**: Each route becomes a separate bundle
|
|
708
|
+
- **Tree shaking**: Unused features are eliminated
|
|
709
|
+
- **Minification**: Code is compressed and optimized
|
|
710
|
+
- **Caching**: Aggressive caching for static assets
|
|
711
|
+
- **Lazy loading**: Routes and components load on demand
|
|
712
|
+
|
|
713
|
+
## 🌟 Advanced Examples
|
|
1006
714
|
|
|
1007
|
-
###
|
|
1008
|
-
- ✅ **Zero Setup** - Just add `action` attribute vs manual event handlers
|
|
1009
|
-
- ✅ **Variable Parameters** - `{userId}` template syntax vs manual interpolation
|
|
1010
|
-
- ✅ **Auto Authentication** - Tokens injected automatically
|
|
1011
|
-
- ✅ **File Uploads** - Automatic multipart support
|
|
1012
|
-
- ✅ **Built-in Validation** - HTML5 + custom functions
|
|
715
|
+
### E-commerce Dashboard
|
|
1013
716
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
717
|
+
```javascript
|
|
718
|
+
// logic/dashboard.js
|
|
719
|
+
export default {
|
|
720
|
+
dataURL: {
|
|
721
|
+
overview: '/api/dashboard/overview',
|
|
722
|
+
sales: '/api/sales/recent',
|
|
723
|
+
products: '/api/products/trending'
|
|
724
|
+
},
|
|
1018
725
|
|
|
1019
|
-
|
|
726
|
+
computed: {
|
|
727
|
+
totalRevenue() {
|
|
728
|
+
return this.sales?.reduce((sum, sale) => sum + sale.amount, 0) || 0;
|
|
729
|
+
},
|
|
730
|
+
cartItems() {
|
|
731
|
+
return this.$state.get('shoppingCart', []);
|
|
732
|
+
}
|
|
733
|
+
},
|
|
1020
734
|
|
|
1021
|
-
|
|
735
|
+
methods: {
|
|
736
|
+
async addToCart(product) {
|
|
737
|
+
const cart = this.$state.get('shoppingCart', []);
|
|
738
|
+
const existingItem = cart.find(item => item.id === product.id);
|
|
1022
739
|
|
|
1023
|
-
|
|
740
|
+
if (existingItem) {
|
|
741
|
+
existingItem.quantity += 1;
|
|
742
|
+
} else {
|
|
743
|
+
cart.push({ ...product, quantity: 1 });
|
|
744
|
+
}
|
|
1024
745
|
|
|
1025
|
-
|
|
1026
|
-
1. **📍 Simple URLs**: `/product?id=123&category=electronics` (clear and readable)
|
|
1027
|
-
2. **🎯 Consistent Access**: Always use `this.getParam('id')` - never mix path/query paradigms
|
|
1028
|
-
3. **⚡ No Route Configuration**: No complex route definitions or parameter mappings needed
|
|
1029
|
-
4. **🔍 SEO Friendly**: Descriptive parameter names make URLs self-documenting
|
|
1030
|
-
5. **🌐 Universal Compatibility**: Query parameters work everywhere - no framework lock-in
|
|
746
|
+
this.$state.set('shoppingCart', cart);
|
|
1031
747
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
this.navigateTo('products', { id: 123, category: 'electronics' });
|
|
1036
|
-
// → /products?id=123&category=electronics
|
|
748
|
+
// Sync with backend
|
|
749
|
+
await this.$api.post('/api/cart/sync', { items: cart });
|
|
750
|
+
},
|
|
1037
751
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
752
|
+
async checkout() {
|
|
753
|
+
const items = this.$state.get('shoppingCart', []);
|
|
754
|
+
try {
|
|
755
|
+
const order = await this.$api.post('/api/orders', { items });
|
|
756
|
+
this.$state.set('shoppingCart', []);
|
|
757
|
+
this.$state.set('lastOrder', order);
|
|
758
|
+
this.navigateTo('order-confirmation', { orderId: order.id });
|
|
759
|
+
} catch (error) {
|
|
760
|
+
this.$toast?.error?.('Checkout failed. Please try again.');
|
|
761
|
+
}
|
|
762
|
+
}
|
|
1044
763
|
}
|
|
1045
764
|
};
|
|
1046
765
|
```
|
|
1047
766
|
|
|
1048
|
-
###
|
|
1049
|
-
**Traditional Routers**: Complex path parameters (`/users/:id/posts/:postId`) require route configuration, parameter extraction logic, and mixed paradigms.
|
|
767
|
+
### Multi-tenant Application
|
|
1050
768
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
769
|
+
```javascript
|
|
770
|
+
// Handle different tenants with state management
|
|
771
|
+
export default {
|
|
772
|
+
async mounted() {
|
|
773
|
+
const tenantId = this.$params.tenantId || this.$query.tenant;
|
|
1055
774
|
|
|
1056
|
-
|
|
775
|
+
if (tenantId !== this.$state.get('currentTenant')?.id) {
|
|
776
|
+
await this.switchTenant(tenantId);
|
|
777
|
+
}
|
|
778
|
+
},
|
|
1057
779
|
|
|
1058
|
-
|
|
780
|
+
methods: {
|
|
781
|
+
async switchTenant(tenantId) {
|
|
782
|
+
try {
|
|
783
|
+
const tenant = await this.$api.get('/api/tenants/{tenantId}', {
|
|
784
|
+
params: { tenantId }
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
// Update global state
|
|
788
|
+
this.$state.update({
|
|
789
|
+
currentTenant: tenant,
|
|
790
|
+
userPermissions: tenant.permissions,
|
|
791
|
+
theme: tenant.branding.theme
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
// Update language if tenant has preference
|
|
795
|
+
if (tenant.defaultLanguage) {
|
|
796
|
+
await this.$i18n.setLanguage(tenant.defaultLanguage);
|
|
797
|
+
}
|
|
1059
798
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
3. **Structure**: Deploy `routes/`, `css/`, `i18n/` folders (exclude `src/`)
|
|
799
|
+
// Update API base URL for tenant
|
|
800
|
+
this.$api.setBaseURL(`/api/tenants/${tenantId}`);
|
|
1063
801
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
802
|
+
} catch (error) {
|
|
803
|
+
this.navigateTo('tenant-not-found');
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
};
|
|
1070
808
|
```
|
|
1071
809
|
|
|
1072
810
|
## 🤝 Contributing
|
|
1073
811
|
|
|
1074
|
-
|
|
812
|
+
We welcome contributions! Please read our [Contributing Guide](CONTRIBUTING.md) for details on:
|
|
1075
813
|
|
|
1076
|
-
|
|
814
|
+
- Code style and conventions
|
|
815
|
+
- Testing requirements
|
|
816
|
+
- Pull request process
|
|
817
|
+
- Issue reporting guidelines
|
|
1077
818
|
|
|
1078
|
-
|
|
819
|
+
## 📄 License
|
|
1079
820
|
|
|
1080
|
-
|
|
821
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
1081
822
|
|
|
1082
|
-
|
|
823
|
+
## 🙏 Acknowledgments
|
|
1083
824
|
|
|
1084
|
-
|
|
825
|
+
Built with ❤️ for the Vue.js community. Special thanks to:
|
|
1085
826
|
|
|
1086
|
-
-
|
|
1087
|
-
-
|
|
827
|
+
- Vue.js team for the amazing framework
|
|
828
|
+
- The open-source community for inspiration and feedback
|
|
829
|
+
- All contributors who helped shape ViewLogic Router
|
|
1088
830
|
|
|
1089
831
|
---
|
|
1090
832
|
|
|
1091
|
-
|
|
833
|
+
**ViewLogic Router** - One framework to rule them all! 🚀
|
|
834
|
+
|
|
835
|
+
*Simplify your Vue development with the power of unified architecture.*
|