Skip to content

Commit da20e4f

Browse files
authored
Merge pull request #200 from Jenesius/issue_199
Issue 199
2 parents 6a75ba2 + 51c3eae commit da20e4f

File tree

16 files changed

+454
-23
lines changed

16 files changed

+454
-23
lines changed

docs/guide/form.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,25 @@ Accepts `true` or `false`, changes during read/save. When changing this property
115115
event `Form.EVENT_WAIT` is created:
116116
```ts
117117
form.on(Form.EVENT_WAIT, () => {...})
118+
```
119+
120+
### autonomic
121+
122+
Sometimes you want a child form to be dependent on its parent, but rely on its state
123+
(changes, values, availabilities). In this case, save/read will be called from the parent form. Also, the changed
124+
value will be read from the parent form.
125+
126+
```ts
127+
const parent = new Form();
128+
const child = new Form({
129+
parent,
130+
name: "user",
131+
autonomic: true
132+
});
133+
134+
parent.change({
135+
"user.name": "Jack"
136+
})
137+
child.values // {}
138+
parent.values // { user: { name: "Jack" } }
118139
```

docs/ru/guide/form.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,25 @@ form.version = 15; // Upgrade. Version: 15
114114
создаётся событие `Form.EVENT_WAIT`:
115115
```ts
116116
form.on(Form.EVENT_WAIT, () => {...})
117+
```
118+
119+
### autonomic
120+
121+
Иногда вам нужно, чтобы дочерняя форма зависела от родительской формы, но полагалась на ее состояние.
122+
(изменения, значения, наличие). В этом случае сохранение/чтение будет вызываться из родительской формы.
123+
Кроме того, измененный значение будет прочитано из родительской формы.
124+
125+
```ts
126+
const parent = new Form();
127+
const child = new Form({
128+
parent,
129+
name: "user",
130+
autonomic: true
131+
});
132+
133+
parent.change({
134+
"user.name": "Jack"
135+
})
136+
child.values // {}
137+
parent.values // { user: { name: "Jack" } }
117138
```

examples/autonomic-form/App.vue

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<template>
2+
<div class="container">
3+
<form-field name = "username" label = "Username"/>
4+
5+
<station-form />
6+
7+
<button @click = "showChanges">Changes</button>
8+
</div>
9+
</template>
10+
11+
<script setup lang='ts'>
12+
13+
import {Form, FormField} from "../../src";
14+
import StationForm from "./station-form.vue";
15+
16+
const form = new Form();
17+
// @ts-ignore
18+
window.theForm = form;
19+
function showChanges() {
20+
console.log(form.changes);
21+
}
22+
</script>
23+
24+
<style scoped>
25+
26+
</style>

examples/autonomic-form/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Form changes
2+
3+
This example demonstrates working with values, namely with changes. When working with forms, you need to keep track of which
4+
values have been changed. To do this, the library provides a mechanism for marking a field as changed.
5+
How it works:
6+
1. Imagine that in the form we have the values
7+
```json
8+
{
9+
"name": "Jenesius",
10+
"age": 23
11+
}
12+
```
13+
2. We decided to change the value of age to 24.
14+
```ts
15+
form.change({age: 24})
16+
```
17+
3. In this case, the values will take the following form:
18+
```json
19+
{
20+
"name": "Jenesius",
21+
"age": 24
22+
}
23+
```
24+
This will change the change mark snapshot:
25+
26+
```json
27+
{
28+
"age": true
29+
}
30+
```
31+
4. Now when receiving form.changes, we will only see those values that are marked as true.
32+
```ts
33+
form.changes // { age: 24 }
34+
```
35+
36+
This example uses:
37+
- [form.changes](https://form.jenesius.com/guide/form-state.html#changes)
38+
- [form.change](https://form.jenesius.com/guide/form-methods.html#change)
39+
- [form.setValues](https://form.jenesius.com/guide/form-methods.html#setvalues)
40+
- [form.cleanChanges](https://form.jenesius.com/guide/form-methods.html#cleanchanges)
41+
- [form.cleanValues](https://form.jenesius.com/guide/form-methods.html#cleanvalues)

examples/autonomic-form/main.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { createApp } from 'vue'
2+
import App from './App.vue';
3+
import {config} from "./../../src/index";
4+
5+
config({
6+
debug: true
7+
})
8+
9+
createApp(App).mount('#app')
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script setup lang="ts">
2+
import {Form, FormField} from "../../src";
3+
4+
const form = new Form({
5+
name: "Station",
6+
autonomic: true
7+
})
8+
9+
</script>
10+
11+
<template>
12+
<div>
13+
<h2>Station:</h2>
14+
<form-field name = "name" label = "Name"/>
15+
<form-field name = "code" label = "Station Code" />
16+
<form-field name = "bandwidth" label = "Available bandwidth" />
17+
</div>
18+
</template>

project/pages/index/App.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<p><a href = "/input-country" class = "example-link">Input Country</a></p>
99
<p><a href = "/input-otp" class = "example-link">Input One-Time Password</a></p>
1010
<p><a href = "/all-inputs" class = "example-link">All Inputs</a></p>
11+
<p><a href = "/form-autonomic" class = "example-link">Form Autonomic</a></p>
1112
</div>
1213
<div class = "container-examples">
1314
<h3 class = "container-examples-title">Test:</h3>

src/classes/DependencyQueue.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ export default class DependencyQueue<T extends (DependencyItem & Record<string,
3232
this.array.indexOf(object), 1
3333
)
3434
}
35-
35+
find(expression: (elem: T, index: number) => boolean) {
36+
return this.array.find(expression);
37+
}
3638
forEach(callback: (elem: T) => void) {
3739
this.array.forEach(callback);
3840
}

src/classes/Form.ts

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import findNearestPrefixFromArray from "../utils/find-nearest-prefix-from-array"
2323
import AvailabilityEvent from "./AvailabilityEvent";
2424
import bypassObject from "../utils/bypass-object";
2525
import isIterablePoint from "../utils/is-iterable-point";
26+
import FormError from "./FormError";
2627

2728
/**
2829
* Main principe : GMN
@@ -63,7 +64,6 @@ export default class Form extends EventEmitter implements FormDependence {
6364
if (elem.parent) return `${Form.restoreFullName(elem.parent)}.${elem.name}`;
6465
return elem.name || '';
6566
}
66-
6767
static getTargetName<T extends { name?: string, parent?: any }>(elem: T): string {
6868
const array = [];
6969

@@ -80,19 +80,40 @@ export default class Form extends EventEmitter implements FormDependence {
8080
* @description Name of Entity.
8181
* */
8282
name?: string
83+
/**
84+
* @description If set to true, then values,changes are standalone and independent of the parent form.
85+
*/
86+
87+
#autonomic: boolean | undefined = undefined;
88+
/**
89+
* @description Класс является автономным, если не указан родитель или если свойство #autonomic установлено в true
90+
*/
91+
get autonomic():boolean {
92+
// Если есть родитель, то проверяем на autonomic
93+
if (this.parent) return this.#autonomic === true;
94+
// Родитель отсутствует, в таком случае форма всегда является автономной
95+
return true;
96+
}
97+
set autonomic(value: boolean) {
98+
if (value === false && !this.parent) throw FormError.AutonomicFormWithoutParent();
99+
this.#autonomic = value;
100+
101+
debug.msg(`The form's %c${Form.restoreFullName(this)}%c autonomic is %c${value}%c.`);
102+
}
103+
83104
/**
84105
* @description Внутренний объект изменений. Хранит в себе значения полей, которые были установлены, используя флаг
85106
* changes: true в методе setValues или используя метод change.
86107
* */
87108
#changes = {};
88109
get changes(): any {
89-
if (this.parent) return getPropFromObject(this.parent.changes, Form.getTargetName(this));
110+
if (this.parent && !this.autonomic) return getPropFromObject(this.parent.changes, Form.getTargetName(this));
90111
return this.#changes;
91112
}
92113

93114
#values = {}
94115
get values(): any {
95-
if (this.parent) {
116+
if (this.parent && !this.autonomic) {
96117
return this.parent.getValueByName(this.name as string) || {};
97118
}
98119
return mergeObjects({}, this.#values, this.#changes)
@@ -101,7 +122,7 @@ export default class Form extends EventEmitter implements FormDependence {
101122
* @description Чистые значения формы. Которые изменяются при помощи setValues без опции change.
102123
* */
103124
get pureValues():any {
104-
if (this.parent) return getPropFromObject(this.parent.pureValues, this.name as string) || {}
125+
if (this.parent && !this.autonomic) return getPropFromObject(this.parent.pureValues, this.name as string) || {}
105126
return this.#values;
106127
}
107128

@@ -126,20 +147,23 @@ export default class Form extends EventEmitter implements FormDependence {
126147
this.#parent = parent;
127148
}
128149

150+
/**
151+
* !!!!!!!!!!!
152+
* CONSTRUCTOR
153+
* !!!!!!!!!!!
154+
*/
129155
constructor(params: Partial<FormParams> = {}) {
130156
super();
131157

132158
this.name = params.name;
133159
const currentInstance = !!getCurrentInstance();
134-
135160
debug.msg(`new form %c${Form.restoreFullName(this)}%c`, debug.colorName, debug.colorDefault, this);
136-
if (currentInstance) {
137-
const parent = Form.getParentForm();
138-
if (parent && !(params.parent === false || params.parent === null)) {
139-
parent.subscribe(this);
140-
}
141-
}
161+
162+
const parent = (currentInstance ? Form.getParentForm() : null) || params.parent
163+
if (parent) parent.subscribe(this);
142164
if (params.provide !== false && currentInstance) provideVue(Form.PROVIDE_NAME, this); // Default providing current form for children.
165+
166+
if (typeof params.autonomic === 'boolean') this.autonomic = params.autonomic;
143167
}
144168

145169
setValues(values: any, options: Partial<FormSetValuesOptions> = {}): void {
@@ -155,13 +179,13 @@ export default class Form extends EventEmitter implements FormDependence {
155179
if (!checkNameInObject(values, target)) insertByName(values, target)
156180
*/
157181

158-
if (!options.executedFrom) {
182+
if (!options.executedFrom && !this.autonomic) {
159183
debug.msg(`Executed from not founded in options, values will be %c${Form.getTargetName(this)}`, debug.colorSuccess)
160184
options.executedFrom = Form.getTargetName(this);
161185
}
162186

163187
// Текущий элемент имеет родителя - отправляем изменения наверх.
164-
if (this.parent) {
188+
if (this.parent && !this.autonomic) {
165189
debug.msg(`%c${this.name}%c emit changes to parent [%c${this.parent.name}%c]`, debug.colorName, debug.colorDefault, debug.colorFocus,debug.colorDefault);
166190
return void this.parent.setValues(values, options);
167191
}
@@ -395,8 +419,13 @@ export default class Form extends EventEmitter implements FormDependence {
395419
/**
396420
* @description Return true if form includes changes, otherwise false.
397421
* */
398-
get changed() {
399-
return !!(this.changes && Object.keys(this.changes).length !== 0);
422+
get changed(): boolean {
423+
return !!(
424+
(this.changes && Object.keys(this.changes).length !== 0)
425+
|| this.dependencies.find(
426+
elem => (elem instanceof Form && elem.changed)
427+
)
428+
);
400429
}
401430

402431
subscribe(element: any) {
@@ -494,7 +523,7 @@ export default class Form extends EventEmitter implements FormDependence {
494523
* для данного поля будет стёрто из объекта changes.
495524
* */
496525
cleanChangesByField(fieldName: string): void {
497-
if (this.parent) return void this.parent.cleanChangesByField(concatName(this.name, fieldName));
526+
if (this.parent && !this.autonomic) return void this.parent.cleanChangesByField(concatName(this.name, fieldName));
498527

499528
// Если значение есть в pureValues - устанавливаем его
500529
// Иначе undefined
@@ -512,7 +541,7 @@ export default class Form extends EventEmitter implements FormDependence {
512541
revert() {
513542
debug.msg('revert changes');
514543

515-
if (this.parent) return void this.parent.cleanChangesByField(this.name as string);
544+
if (this.parent && !this.autonomic) return void this.parent.cleanChangesByField(this.name as string);
516545

517546
this.setValues(this.pureValues, {
518547
change: true,
@@ -670,7 +699,7 @@ export default class Form extends EventEmitter implements FormDependence {
670699
return !this.enabled;
671700
}
672701
get enabled() {
673-
if (this.parent) return !this.parent.checkFieldDisable(this.name as string);
702+
if (this.parent && !this.autonomic) return !this.parent.checkFieldDisable(this.name as string);
674703
return this.isAvailable;
675704
}
676705

@@ -700,7 +729,7 @@ export default class Form extends EventEmitter implements FormDependence {
700729
* Далее передаём объект в dispatchEvent.
701730
* */
702731
available(type: boolean, names: string[]):void {
703-
if (this.parent) return this.parent.available(type, names.length ? names.map(k => concatName(this.name, k)) : [this.name as string])
732+
if (this.parent && !this.autonomic) return this.parent.available(type, names.length ? names.map(k => concatName(this.name, k)) : [this.name as string])
704733
debug.group(`AVAILABILITY %c${Form.getTargetName(this)}%c to %c${type}`, debug.colorName, debug.colorDefault, debug.colorFocus);
705734

706735
const oldAvailable = this.isAvailable;
@@ -754,7 +783,7 @@ export default class Form extends EventEmitter implements FormDependence {
754783
* @description Вернёт true, если переданное поле является disabled.
755784
* */
756785
checkFieldDisable(fieldName: string): boolean {
757-
if (this.parent) return this.parent.checkFieldDisable(concatName(this.name, fieldName));
786+
if (this.parent && !this.autonomic) return this.parent.checkFieldDisable(concatName(this.name, fieldName));
758787
const nearestName = findNearestNameFromArray(Object.keys(this.#availabilities), fieldName);
759788
if (!nearestName) return this.disabled;
760789

@@ -784,7 +813,12 @@ export default class Form extends EventEmitter implements FormDependence {
784813
interface FormParams {
785814
name: string,
786815
provide: boolean,
787-
parent: Form | null | false
816+
parent: Form | null | false,
817+
/**
818+
* @description The form will be self-contained. They want and will have the opportunity to receive higher education
819+
* from their parents, however values, changes, enabling and disabling will be stored inside this form.
820+
*/
821+
autonomic: boolean
788822
}
789823

790824
interface FormDependence {

src/classes/FormError.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,7 @@ export default class FormError extends Error{
3333
static DependencyNotFounded() {
3434
return new FormError(`Dependency was not subscribe on form or unsubscribed early.`)
3535
}
36+
static AutonomicFormWithoutParent() {
37+
return new FormError(`The form will always be autonomic unless a parent is specified.`)
38+
}
3639
}

0 commit comments

Comments
 (0)