Skip to content

Commit 591dbe9

Browse files
authored
Merge pull request #109 from Jenesius/issue_105
Issue 105
2 parents fdd13d8 + edc8d09 commit 591dbe9

File tree

13 files changed

+262
-51
lines changed

13 files changed

+262
-51
lines changed

docs/.vitepress/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ function sidebar() {
6363
{
6464
text: "Best Practices",
6565
items: [
66-
{ text: "Depth information", link: "/guide/form-in-depth" }
66+
{ text: "Depth information", link: "/guide/form-in-depth" },
67+
{ text: 'Converting Options', link: "/utils/convert-options-object" }
6768
]
6869
},
6970

docs/guide/custom-widgets.md

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,20 @@ overwrite part of it. This method is described [here](/guide/configuration#input
1010
Any *props* passed will also be passed to your component.
1111
But! There are some differences worth mentioning:
1212

13-
### options
14-
Options will **always** be cast as follows:
13+
- **options** Array of options, used for select/radio. Read more [here](/utils/convert-options-object). The data undergoes additional transformation and always has a place to be:
1514

16-
```json
17-
[
18-
{
19-
"title": "Title", "value": 1
20-
},
21-
{
22-
"title": "Jenesius", "value": 2
23-
}
24-
]
25-
```
26-
27-
### disabled and errors
28-
These properties will be internal, they can be overridden, but then the form will not be able to
29-
influence them:
30-
```html
31-
<input-field disabled name = "input"/>
32-
```
3315
```ts
34-
form.enable('input'); // Not working
35-
```
36-
Similarly, with `errors`, in this case the `validate` method will not be able to show errors.
16+
type Options = Array<{
17+
label: string,
18+
value: any
19+
}>
20+
3721

38-
----
39-
This is where the restrictions end. If this method or restrictions are not allowed
40-
for your project, you can use the following method.
22+
23+
```
24+
- **disabled** Will be dynamically pulled from the form. Changed using *disable/enable* methods
25+
- **errors** Is an array of strings
26+
- **changed** Dynamically pulled out of the mold. True - if the field is marked as modified in the form.
4127

4228
## Own Components
4329

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,76 @@
11
# Convert options object
22

3-
Здесь будет описано трансформация options.
3+
For better compatibility with previous solutions, as well as ease of development, the options property can be
4+
two kinds:
5+
6+
## Object
7+
This view is an object with **value** on the left and **label** on the right. For example
8+
country enumeration has been selected:
9+
```ts
10+
const options = {
11+
'en': 'Great Britain',
12+
'us': 'United States',
13+
'jp': 'Japan',
14+
'ru': 'Russia'
15+
}
16+
```
17+
18+
:::tip Why Value on left side?
19+
Primarily because the value must be unique. Because object is used for
20+
simple variants of this property was the most significant.
21+
:::
22+
23+
The disadvantage of this approach is that the value can only be a string. **Important** to remember that
24+
even a numeric property will be converted to a string.
25+
26+
## Array based
27+
This method is more flexible, because allows you to set any data to the value. In this case, *options*
28+
takes the following form:
29+
```ts
30+
const options = [
31+
{ value: 'en', label: 'Great Britain' },
32+
{ value: 'us', label: 'United States' },
33+
{ value: 'jp', label: 'Japan' },
34+
{ value: 'ru', label: 'Russia' },
35+
]
36+
```
37+
This method is more cumbersome than the first, but more flexible, because now we can use the following:
38+
```ts
39+
const options = [
40+
{
41+
value: { code: 1, mark: 'en' }, label: 'Great Britain'
42+
}
43+
]
44+
```
45+
46+
## Converting
47+
48+
When using **input-field** remember that you will always get options as an output as an array!
49+
This is easier to show with an example:
50+
51+
```vue
52+
53+
<template>
54+
<input-field :options = "data" type = "custom-input"/>
55+
</template>
56+
<script setup>
57+
import {InputField} from "./index";
58+
const data = {
59+
'1': 'M',
60+
'2': 'W'
61+
}
62+
</script>
63+
```
64+
```vue
65+
<!--custom-input-->
66+
<div>
67+
{{options}}
68+
</div>
69+
<script setup>
70+
const props = defineProps({
71+
options: Array
72+
})
73+
74+
props.options; // [ { value: '1', label: 'M' }, { value: '2', label: 'W' } ]
75+
</script>
76+
```

plugin/classes/Form.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import deletePropByName from "../utils/delete-prop-by-name";
1414
import getPropFromObject from "../utils/get-prop-from-object";
1515
import {IComparisonResult, searchByComparison, searchChangesByComparison} from "../utils/search-changes-by-comparison";
1616
import debug from "../debug/debug";
17+
import checkNameInObject from "../utils/check-name-in-object";
1718

1819
export default class Form extends EventEmitter implements FormDependence{
1920
static PROVIDE_NAME = 'form-controller';
@@ -125,7 +126,14 @@ export default class Form extends EventEmitter implements FormDependence{
125126
get changed() {
126127
return !!Object.keys(this.#changes).length || !!this.dependencies.find(d => d.changed);
127128
}
128-
129+
130+
/**
131+
* @description Method check name for including in changes.
132+
* */
133+
checkDependenceForChangedStatus(dependenceName: string) {
134+
return checkNameInObject(this.changes, dependenceName);
135+
}
136+
129137
/**
130138
* Getter/Setter for values.
131139
*/

plugin/classes/Input.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ export default class Input extends EventEmitter {
3434
this.parentForm?.input(this.name, v);
3535
}
3636

37+
/**
38+
* @description Boolean value, true if parent form mark current field like changed.
39+
* */
40+
get changed() {
41+
if (!this.name) {
42+
return false;
43+
}
44+
if (!this.parentForm) {
45+
return false;
46+
}
47+
return this.parentForm.checkDependenceForChangedStatus(this.name);
48+
}
49+
3750
/**
3851
* @description Run all validations. Input is not validated If on
3952
* e of guard don't return true.

plugin/hooks/use-input-state.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import {onUnmounted, reactive, watch} from "vue";
1+
import {onUnmounted, reactive} from "vue";
22
import Input from "../classes/Input";
3+
import debug from "../debug/debug";
34

45
export default function useInputState(name: string, validation: any[] = []) {
56

@@ -18,19 +19,32 @@ function useInputController(input: Input) {
1819
const state = reactive<{
1920
value: any,
2021
disabled: boolean,
21-
errors: string[]
22+
errors: string[],
23+
changed: boolean
2224
}>({
2325
value: input.value,
2426
disabled: input.disabled,
25-
errors: []
27+
errors: [],
28+
changed: input.changed
2629
})
27-
30+
31+
/**
32+
* @description setTimeout used for wait short time after values will be marked like changed inside Form.
33+
* */
34+
function updateChanged() {
35+
setTimeout(() => {
36+
state.changed = input.changed;
37+
}, 0)
38+
}
39+
2840
const controls = {
2941
change: (v:any) => {
3042
state.value = v;
43+
updateChanged();
3144
},
3245
setValues(v: any) {
3346
state.value = v;
47+
updateChanged();
3448
},
3549
disable: () => {
3650
state.disabled = true;
@@ -64,6 +78,7 @@ function useInputController(input: Input) {
6478
function updateName(name: string) {
6579
off?.();
6680
input.name = name;
81+
if (!input.parentForm) debug.msg(`input:${name}`, `Can't found parent form.`)
6782
off = input.parentForm?.dependInput(name, controls);
6883
}
6984

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import splitName from "./split-name";
2+
3+
/**
4+
* @description Return true if object include name, otherwise - false. Object should be granted!
5+
* @example
6+
* { user: { id: 2, address: {city: "Mars"} } }
7+
* user.id -> true
8+
* user -> true
9+
* user.name -> false
10+
* user.name.label -> false
11+
* */
12+
export default function checkNameInObject<T extends Record<string, any>>(object: T, searchName: string ) {
13+
const names = splitName(searchName);
14+
15+
for(let i = 0; i < names.length; i++) {
16+
const name = names[i];
17+
try {
18+
if ((name in object) === false || !object.hasOwnProperty(name)) return false;
19+
} catch (e) {
20+
return false;
21+
}
22+
object = object[name];
23+
}
24+
25+
return true;
26+
}

plugin/utils/split-name.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function splitName(name: string) {
2+
return name.split('.');
3+
}

plugin/widgets/input-field.vue

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
<template>
2-
<component
3-
:is="componentItem"
4-
:name="name"
5-
6-
:modelValue="props.name ? state.value : modelValue"
7-
@update:modelValue="handleInput"
8-
9-
:label="props.label"
10-
:disabled="state.disabled"
11-
:errors="state.errors"
12-
:options="parseOptions(options)"
13-
:autofocus="autofocus"
14-
/>
2+
<component
3+
:is="componentItem"
4+
:name="name"
5+
6+
:modelValue="props.name ? state.value : modelValue"
7+
@update:modelValue="handleInput"
8+
9+
:label="label"
10+
:disabled="state.disabled"
11+
:errors="state.errors"
12+
:options="parseOptions(options)"
13+
:autofocus="autofocus"
14+
:changed="state.changed"
15+
/>
1516
</template>
1617

1718
<script setup lang="ts">
@@ -90,6 +91,9 @@ function parseOptions(v: typeof props.options) {
9091
9192
function handleInput(v: any) {
9293
input.change(v);
94+
/**
95+
* Events for user interface. You can use both variant.
96+
* */
9397
emits('update:modelValue', v)
9498
emits('input', v)
9599
}

src/pages/test/App.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,22 @@
1010
<input-field type = "text" name = "sun" label = "Username" prefix = "MMM" :pretty = "splitPoint"/>
1111
<input-field type = "text" name = "sun" label = "numeric" prefix = "MMM" :modify = "[]" numeric />
1212

13-
13+
<widget-child/>
1414
</div>
1515
</template>
1616

1717
<script setup lang='ts'>
1818
import InputField from "../../../plugin/widgets/input-field.vue";
19-
import {Form} from "../../../plugin";
19+
import {Form, useInputState} from "../../../plugin";
2020
import {onMounted, ref} from "vue";
21+
import WidgetChild from "@/pages/test/widget-child.vue";
2122
2223
const form = new Form();
2324
2425
2526
onMounted(() => setTimeout(() => form.validate(), 1000))
2627
28+
2729
function m1(a: string) {
2830
return a.replace(/\D/,'')
2931
}

0 commit comments

Comments
 (0)