@live-change/frontend-auto-form 0.9.197 → 0.9.199
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
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: @live-change/frontend-auto-form
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## @live-change/frontend-auto-form
|
|
6
|
+
|
|
7
|
+
`@live-change/frontend-auto-form` to moduł do **automatycznego generowania formularzy i CRUD-ów** na podstawie:
|
|
8
|
+
|
|
9
|
+
- definicji modeli i akcji po stronie serwera (Live Change)
|
|
10
|
+
- metadanych wygenerowanych przez `@live-change/framework` i dostępnych przez `@live-change/vue3-ssr`
|
|
11
|
+
|
|
12
|
+
Zapewnia:
|
|
13
|
+
|
|
14
|
+
- komponenty **pól formularza** (`AutoField`, `AutoInput`, `AutoEditor`, `ModelEditor`, `ModelView`, `ModelSingle`, `ActionForm`, itp.)
|
|
15
|
+
- provider-y konfiguracji (`provideAutoViewComponents`, `provideAutoInputConfiguration`, `provideMetadataBasedAutoInputConfiguration`)
|
|
16
|
+
- lokalizacje (`locales`) używane w aplikacjach takich jak `family-tree` i `speed-dating`
|
|
17
|
+
|
|
18
|
+
### Podstawowa integracja
|
|
19
|
+
|
|
20
|
+
W `App.vue` typowej aplikacji włączasz auto-form globalnie:
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
import {
|
|
24
|
+
provideAutoViewComponents,
|
|
25
|
+
provideAutoInputConfiguration,
|
|
26
|
+
provideMetadataBasedAutoInputConfiguration
|
|
27
|
+
} from '@live-change/frontend-auto-form'
|
|
28
|
+
|
|
29
|
+
provideAutoViewComponents()
|
|
30
|
+
provideAutoInputConfiguration()
|
|
31
|
+
provideMetadataBasedAutoInputConfiguration()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
oraz dodajesz lokalizacje do i18n w `front/src/config.js`:
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
import { locales as autoFormLocales } from '@live-change/frontend-auto-form'
|
|
38
|
+
|
|
39
|
+
export default {
|
|
40
|
+
i18n: {
|
|
41
|
+
messages: {
|
|
42
|
+
en: deepmerge.all([
|
|
43
|
+
baseLocales.en,
|
|
44
|
+
autoFormLocales.en,
|
|
45
|
+
// ...
|
|
46
|
+
]),
|
|
47
|
+
pl: deepmerge.all([
|
|
48
|
+
baseLocales.pl || {},
|
|
49
|
+
autoFormLocales.pl || {},
|
|
50
|
+
// ...
|
|
51
|
+
])
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Kluczowe komponenty
|
|
58
|
+
|
|
59
|
+
#### `AutoField`
|
|
60
|
+
|
|
61
|
+
Generuje pojedyncze pole formularza na podstawie definicji właściwości modelu lub akcji:
|
|
62
|
+
|
|
63
|
+
```vue
|
|
64
|
+
<AutoField
|
|
65
|
+
:definition="definition.properties.firstName"
|
|
66
|
+
v-model="editable.firstName"
|
|
67
|
+
:error="validationResult?.propertyErrors?.firstName"
|
|
68
|
+
:label="t('profile.firstName')"
|
|
69
|
+
/>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Możesz wstrzykiwać własny layout przez sloty (`#default`, `#label`, `#error`), jak w `ProfileSettings.vue` (`speed-dating`).
|
|
73
|
+
|
|
74
|
+
#### `AutoInput` i `AutoEditor`
|
|
75
|
+
|
|
76
|
+
`AutoInput` – pojedyncze pole edycyjne na podstawie definicji,
|
|
77
|
+
|
|
78
|
+
`AutoEditor` – edytor całego obiektu (modelu) na podstawie definicji:
|
|
79
|
+
|
|
80
|
+
```vue
|
|
81
|
+
<auto-editor
|
|
82
|
+
:definition="eventDefinition"
|
|
83
|
+
v-model="editable"
|
|
84
|
+
:rootValue="editable"
|
|
85
|
+
i18n="event."
|
|
86
|
+
/>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
W przykładzie z `speed-dating/front/src/pages/events/[event]/edit.vue`:
|
|
90
|
+
|
|
91
|
+
- `eventDefinition` pochodzi z `api.services.speedDating.models.Event`
|
|
92
|
+
- `editable` jest synchronizowane z widokiem i akcją poprzez `synchronized`
|
|
93
|
+
|
|
94
|
+
#### `ModelEditor`, `ModelView`, `ModelSingle`
|
|
95
|
+
|
|
96
|
+
Te komponenty łączą:
|
|
97
|
+
|
|
98
|
+
- definicję modelu
|
|
99
|
+
- widoki i akcje (DAO)
|
|
100
|
+
- generowane formularze i widoki list/szczegółów
|
|
101
|
+
|
|
102
|
+
Są używane m.in. w:
|
|
103
|
+
|
|
104
|
+
- `speed-dating/front/src/pages/events/[event]/surveys.vue` (`ModelEditor` dla ankiet)
|
|
105
|
+
- `family-tree` – ekrany ustawień i edytory szablonów
|
|
106
|
+
|
|
107
|
+
### Flow od modelu do CRUD
|
|
108
|
+
|
|
109
|
+
1. **Definicja modelu i akcji** w serwisie po stronie serwera (np. `Event`, `TreeSettings`).
|
|
110
|
+
2. **Eksport metadanych** przez Live Change i odczyt przez `@live-change/vue3-ssr` (`api.services[service].models[Model]`).
|
|
111
|
+
3. **Auto-form** generuje formularze i widoki:
|
|
112
|
+
- `AutoField` dla pojedynczych pól,
|
|
113
|
+
- `AutoEditor` / `ModelEditor` dla całych obiektów,
|
|
114
|
+
- `ActionForm` / `ActionEditor` dla akcji.
|
|
115
|
+
|
|
116
|
+
### Przykłady z projektów
|
|
117
|
+
|
|
118
|
+
#### `family-tree`
|
|
119
|
+
|
|
120
|
+
- `front/src/components/TreeSettings.vue`:
|
|
121
|
+
- `AutoField` użyty do edycji zagnieżdżonych ustawień (rootPerson, format, marginesy, tło, tytuł)
|
|
122
|
+
- integracja z PrimeVue (Dropdown, Slider, InputNumber) przez sloty
|
|
123
|
+
- `front/src/components/marketing/*Editor.vue`:
|
|
124
|
+
- `AutoField`, `editorData` do edycji szablonów i obrazów reklamowych
|
|
125
|
+
- `front/src/pages/tree/[tree]/settings.vue`:
|
|
126
|
+
- użycie `editorData` z `@live-change/frontend-auto-form` do spięcia formularza z modelem ustawień
|
|
127
|
+
|
|
128
|
+
#### `speed-dating`
|
|
129
|
+
|
|
130
|
+
- `front/src/components/profile/ProfileSettings.vue`:
|
|
131
|
+
- `AutoField` dla pól identyfikacji użytkownika, z własnymi layoutami i walidacją
|
|
132
|
+
- integracja z `synchronized` i serwisem `draft`
|
|
133
|
+
- `front/src/pages/events/[event]/edit.vue`:
|
|
134
|
+
- `AutoInput`, `AutoField`, `AutoEditor` do edycji eventu
|
|
135
|
+
- `front/src/pages/events/[event]/surveys.vue`:
|
|
136
|
+
- `ModelEditor` do edycji ankiet powiązanych z eventem
|
|
137
|
+
|
|
138
|
+
Te pliki są najlepszym odniesieniem, jeśli chcesz szybko zobaczyć, jak układa się flow od modelu do działającego CRUD-a.
|
|
139
|
+
|
|
@@ -107,8 +107,8 @@
|
|
|
107
107
|
required: true,
|
|
108
108
|
},
|
|
109
109
|
views: {
|
|
110
|
-
type:
|
|
111
|
-
default: () => ({})
|
|
110
|
+
type: Array,
|
|
111
|
+
default: () => ([{}])
|
|
112
112
|
},
|
|
113
113
|
})
|
|
114
114
|
const { service, model, views } = toRefs(props)
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
}, AutoObjectIdentification)
|
|
125
125
|
)
|
|
126
126
|
|
|
127
|
-
import { useApi, usePath, live } from '@live-change/vue3-ssr'
|
|
127
|
+
import { useApi, usePath, live, view } from '@live-change/vue3-ssr'
|
|
128
128
|
const api = useApi()
|
|
129
129
|
const path = usePath()
|
|
130
130
|
|
|
@@ -25,6 +25,7 @@ export default function editorData(options) {
|
|
|
25
25
|
recursive = true,
|
|
26
26
|
debounce = 600,
|
|
27
27
|
timeField = 'lastUpdate',
|
|
28
|
+
crudSource = 'crud',
|
|
28
29
|
|
|
29
30
|
savedToast = "Saved",
|
|
30
31
|
savedDraftToast = "Draft saved",
|
|
@@ -56,7 +57,7 @@ export default function editorData(options) {
|
|
|
56
57
|
const service = api.services[serviceName]
|
|
57
58
|
const model = service.models[modelName]
|
|
58
59
|
const {
|
|
59
|
-
crudMethods = model
|
|
60
|
+
crudMethods = model[crudSource],
|
|
60
61
|
identifiersNames = model.identifiers,
|
|
61
62
|
editableProperties = model.editableProperties ?? Object.keys(model.properties),
|
|
62
63
|
} = options
|
|
@@ -130,7 +131,8 @@ export default function editorData(options) {
|
|
|
130
131
|
const savedIdentifiers = {}
|
|
131
132
|
for(const identifier of identifiersNames) {
|
|
132
133
|
if(typeof identifier === 'object') {
|
|
133
|
-
savedIdentifiers[identifier.name] = savedData.value?.[identifier.
|
|
134
|
+
savedIdentifiers[identifier.name] = savedData.value?.[identifier.field] ?? savedData.value?.[identifier.name]
|
|
135
|
+
?? draftData.value?.data?.[identifier.field] ?? draftData.value?.data?.[identifier.name]
|
|
134
136
|
} else {
|
|
135
137
|
savedIdentifiers[identifier] = savedData.value?.[identifier] ?? draftData.value?.data?.[identifier]
|
|
136
138
|
}
|
|
@@ -196,7 +196,8 @@ export function prepareObjectRelations(objectType, object, api = useApi()) {
|
|
|
196
196
|
const name = 'rangeBy_' + objectType
|
|
197
197
|
const typeView = from.crud?.[name]
|
|
198
198
|
? name
|
|
199
|
-
: undefined
|
|
199
|
+
: undefined
|
|
200
|
+
|
|
200
201
|
if(typeView) {
|
|
201
202
|
views.push({
|
|
202
203
|
name: typeView,
|
|
@@ -205,18 +206,27 @@ export function prepareObjectRelations(objectType, object, api = useApi()) {
|
|
|
205
206
|
}
|
|
206
207
|
})
|
|
207
208
|
} else {
|
|
208
|
-
|
|
209
|
-
if(what[i] !== objectType) continue
|
|
210
|
-
const propertyName = relationConfig.propertyNames?.[i]
|
|
211
|
-
?? model[0].toLowerCase() + model.slice(1)
|
|
212
|
-
const name = 'rangeBy' + propertyName[0].toUpperCase() + propertyName.slice(1)
|
|
213
|
-
if(!from.crud?.[name]) continue
|
|
209
|
+
if(singular) {
|
|
214
210
|
views.push({
|
|
215
|
-
name,
|
|
211
|
+
name: 'read',
|
|
216
212
|
identifiers: {
|
|
217
|
-
[
|
|
213
|
+
[model[0].toLowerCase() + model.slice(1)]: object
|
|
218
214
|
}
|
|
219
215
|
})
|
|
216
|
+
} else {
|
|
217
|
+
for(let i = 0; i < what.length; i++) {
|
|
218
|
+
if(what[i] !== objectType) continue
|
|
219
|
+
const propertyName = relationConfig.propertyNames?.[i]
|
|
220
|
+
?? model[0].toLowerCase() + model.slice(1)
|
|
221
|
+
const name = 'rangeBy' + propertyName[0].toUpperCase() + propertyName.slice(1)
|
|
222
|
+
if(!from.crud?.[name]) continue
|
|
223
|
+
views.push({
|
|
224
|
+
name,
|
|
225
|
+
identifiers: {
|
|
226
|
+
[propertyName]: object
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
}
|
|
220
230
|
}
|
|
221
231
|
}
|
|
222
232
|
console.log(objectType, "VIEWS", views, "FROM", from, "SINGULAR", singular)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/frontend-auto-form",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.199",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"memDev": "node server/start.js memDev --enableSessions --initScript ./init.js --dbAccess",
|
|
6
6
|
"localDevInit": "rm tmp.db; lcli localDev --enableSessions --initScript ./init.js",
|
|
@@ -22,16 +22,16 @@
|
|
|
22
22
|
"type": "module",
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
|
25
|
-
"@live-change/cli": "^0.9.
|
|
26
|
-
"@live-change/dao": "^0.9.
|
|
27
|
-
"@live-change/dao-vue3": "^0.9.
|
|
28
|
-
"@live-change/dao-websocket": "^0.9.
|
|
29
|
-
"@live-change/framework": "^0.9.
|
|
30
|
-
"@live-change/image-frontend": "^0.9.
|
|
31
|
-
"@live-change/image-service": "^0.9.
|
|
32
|
-
"@live-change/session-service": "^0.9.
|
|
33
|
-
"@live-change/vue3-components": "^0.9.
|
|
34
|
-
"@live-change/vue3-ssr": "^0.9.
|
|
25
|
+
"@live-change/cli": "^0.9.199",
|
|
26
|
+
"@live-change/dao": "^0.9.199",
|
|
27
|
+
"@live-change/dao-vue3": "^0.9.199",
|
|
28
|
+
"@live-change/dao-websocket": "^0.9.199",
|
|
29
|
+
"@live-change/framework": "^0.9.199",
|
|
30
|
+
"@live-change/image-frontend": "^0.9.199",
|
|
31
|
+
"@live-change/image-service": "^0.9.199",
|
|
32
|
+
"@live-change/session-service": "^0.9.199",
|
|
33
|
+
"@live-change/vue3-components": "^0.9.199",
|
|
34
|
+
"@live-change/vue3-ssr": "^0.9.199",
|
|
35
35
|
"@vueuse/core": "^12.3.0",
|
|
36
36
|
"codeceptjs-assert": "^0.0.5",
|
|
37
37
|
"compression": "^1.7.5",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"vue3-scroll-border": "0.1.7"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@live-change/codeceptjs-helper": "^0.9.
|
|
55
|
+
"@live-change/codeceptjs-helper": "^0.9.199",
|
|
56
56
|
"codeceptjs": "^3.7.6",
|
|
57
57
|
"generate-password": "1.7.1",
|
|
58
58
|
"playwright": "1.49.1",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"author": "Michał Łaszczewski <michal@laszczewski.pl>",
|
|
64
64
|
"license": "ISC",
|
|
65
65
|
"description": "",
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "1900043a10cf9ad49b9cc33a539fb973706de962"
|
|
67
67
|
}
|