10000 Merge pull request #194 from Jenesius/issue_193 · Jenesius/vue-form@faff9cc · GitHub
[go: up one dir, main page]

Skip to content

Commit faff9cc

Browse files
authored
Merge pull request #194 from Jenesius/issue_193
Issue 193
2 parents 691210e + 475dfdd commit faff9cc

File tree

8 files changed

+187
-15
lines changed

8 files changed

+187
-15
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
#### 3.0.13 (2024-02-20)
2+
3+
##### New Features
4+
5+
* add limit attr to input-select ([03b2a72d](https://github.com/Jenesius/vue-form/commit/03b2a72db66e7ed6f3e5bb6fb3590b937af51052))
6+
* add `multiple` attr, update title and select handler. ([907b3fe2](https://github.com/Jenesius/vue-form/commit/907b3fe2f1b9b031428dfb4f9b5fbaf28723bc87))
7+
8+
##### Tests
9+
10+
* add tests for multiple select. ([900ac31f](https://github.com/Jenesius/vue-form/commit/900ac31fe93d3268c7af187eb4d1b8212249f4a6))
11+
112
#### 3.0.12 (2024-02-01)
213

314
##### Documentation Changes

docs/inputs/input-select.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,19 @@ If this array is specified, then the selection will not contain the elements spe
5151

5252
If no value is selected, a text label is shown.
5353

54+
### multiple <Badge type = "info">Optional</Badge>
55+
56+
- Type `boolean`
57+
58+
Данный параметр позволяет использовать множественную выборку. В таком случае modelValue будет обрабатываться как массив.
59+
60+
### limit <Badge type = "info">Optional</Badge>
61+
62+
- Type `number`
63+
64+
Данный параметр устанавливает предельное количество выбираемых элементов, если используется атрибут `multiple`.
65+
66+
5467
____
5568

5669
Also, all parameters common to all `FormField`. Information about them can be viewed
@@ -60,7 +73,8 @@ on [this page](./form-field.md#params).
6073
## Value
6174

6275
On clicking or selecting the corresponding `select` element, the value will be set to
63-
according to the value in the `value` field of the passed `options`.
76+
according to the value in the `value` field of the passed `options`. Если установлен атрибут `multiple`, то значение
77+
будет проверяться на наличие в modelValue: если не было найдено, то будет добавлено, иначе - исключено.
6478

6579
## Specification
6680

@@ -73,6 +87,8 @@ according to the value in the `value` field of the passed `options`.
7387
- An additional search controller is shown for a long list.
7488
- Blocking fields cancels navigation using `Tab`. There is also a change in the style of `select`.
7589
- If the validation fails, the field should change the style of the `select`.
90+
- При использовании аттрибута `multiple` выборка не должна закрывать выпадающий список.
91+
7692

7793
## Examples
7894

@@ -133,6 +149,16 @@ ____
133149
Using `hiddenValues` and setting the value to `['blue', 'purple', 'pink', 'brown', 'grey']`:
134150
<FormField :options = "colors" hiddenValues = "['blue', 'purple', 'pink', 'brown', 'grey']" type = "select" name = "color" label = "Filtered colors" />
135151

152+
----
153+
154+
Использование `multiple`:
155+
<FormField :options = "colors" type = "select" name = "multiple-color" multiple label = "Multiple colors" />
156+
157+
----
158+
159+
Использование `limit` = `2` вместе с `multiple`:
160+
<FormField :options = "colors" type = "select" name = "multiple-color" multiple label = "Multiple colors" limit = "2" />
161+
136162

137163
----
138164
The current state of the form:

examples/input-select/App.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@
88
<div class="wrap-app ">
99
<input-field type="select" name = "sex" :options = "sexOptions"/>
1010
<input-field type = "select" name = "language" :options = "languageOptions"/>
11+
<input-field type = "select" name = "languages" :options = "languageOptions" multiple label = "Multi languages"/>
12+
<input-field type = "select" name = "languages" :options = "languageOptions" multiple label = "Multi languages" disabled/>
13+
<input-field type = "select" name = "languages" :options = "languageOptions" multiple label = "Multi languages" limit = "2"/>
1114

1215

1316
<input-field type= "select" name="programming-language" :options = "programLanguageOptions" :hidden-values = "['2','1']"/>
1417
<input-field type = "select" name = "year" :options = "yearOptions" label = "Year"/>
18+
<input-field type = "select" name = "year" :options = "yearOptions" label = "Year"/>
1519

1620
<button class="button" @click="form.cleanValues()">Clean Form</button>
1721
</div>

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "jenesius-vue-form",
3-
"version": "3.0.12",
3+
"version": "3.0.13",
44
"description": "Heavy form system for Vue.js",
55
"main": "dist/jenesius-vue-form.cjs.js",
66
"module": "dist/jenesius-vue-form.es.js",

src/utils/toggle-value-from-array.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* @description The method is used to add an element to the array if it does not exist, and to remove an element from the array when
3+
* Help with splicing in any case.
4+
* @param array
5+
* @param value
6+
* @param limit Предельное число элементов в массиве.
7+
*/
8+
export default function toggleValueFromArray<T>(array: T[], value: T, limit?: number) {
9+
const index = array.indexOf(value);
10+
if (index === -1) {
11+
if (limit === undefined || (typeof limit === 'number' && Number.isFinite(limit) && array.length < limit))
12+
array.push(value);
13+
}
14+
else array.splice(index, 1);
15+
16+
return array;
17+
}

src/widgets/inputs/input-select/input-select.vue

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
'input-select_error': errors.length,
88
'input-select_active': isActive
99
}"
10-
:tabindex="!disabled ? 0 : 'none'"
10+
:tabindex="!disabled ? 0 : 'none' "
1111

1212
@focusout = "deactivate()"
1313
@keyup.enter="setActive()"
@@ -34,11 +34,11 @@
3434
<p
3535
v-for = "option in filteredOptions"
3636
:key = "option.value"
37-
:class="{'input-select-option-list-item_active': modelValue === option.value}"
37+
:class="{'input-select-option-list-item_active': isActiveItem(option.value)}"
3838
class="input-select-option-list-item"
3939
:title = "option.value"
4040

41-
@click = "onInput(option.value), setActive(false)"
41+
@click = "handleSelect(option.value)"
4242
>{{getLabelFromOptionRow(option)}}</p>
4343
</div>
4444

@@ -59,6 +59,7 @@ import getLabelFromOptionRow from "../../../utils/get-label-from-option-row";
5959
import FieldWrap from "../field-wrap.vue";
6060
import debounce from "../../../utils/debounce";
6161
import store from "../../../config/store";
62+
import toggleValueFromArray from "../../../utils/toggle-value-from-array";
6263
6364
const props = defineProps<{
6465
label?: string,
@@ -67,7 +68,9 @@ const props = defineProps<{
6768
options: OptionRow[],
6869
placeholder?: string,
6970
errors: string[],
70-
hiddenValues?: OptionRow['value'][]
71+
hiddenValues?: OptionRow['value'][],
72+
multiple?: boolean,
73+
limit?: number | string
7174
}>()
7275
const emit = defineEmits<{
7376
(e: 'update:modelValue', v: any): void
@@ -87,16 +90,23 @@ function setActive(v = !isActive.value) {
8790
if (!v) filter.value = '';
8891
if (v) nextTick(scrollToActiveItem.bind(null,'auto'))
8992
}
90-
function onInput(v: any) {
91-
if (props.disabled) return;
92-
emit('update:modelValue', v)
93-
}
93+
9494
/**
95-
* @description Метка отображаемая в поле.
95+
* @description Метка отображаемая в поле. В случае с одиночной выборкой отображается либо текущий элемент, либо placeholder.
96+
* В случае мн F438 ожественной выборки (multiple) - отображается первый выбранный элемент. Если элементов больше одного,
97+
* то отображается ещё + N, где N - количество выбранных элементов - 1
9698
* */
9799
const inputTitle = computed(() => {
98-
const selected = props.options.find(x => x.value === props.modelValue);
99-
if (selected) return getLabelFromOptionRow(selected);
100+
101+
const value = props.multiple ? props.modelValue?.[0] : props.modelValue;
102+
103+
const selected = props.options.find(x => x.value === value);
104+
if (selected) {
105+
const resultLabel = getLabelFromOptionRow(selected);
106+
if (!props.multiple) return resultLabel;
107+
108+
return resultLabel + (props.modelValue.length > 1 ? ` + ${props.modelValue.length - 1}` : '')
109+
}
100110
101111
return props.disabled ? '' : props.placeholder || '';
102112
})
@@ -145,6 +155,30 @@ const filteredOptions = computed(() => {
145155
)
146156
})
147157
158+
/**
159+
* @description Wrapper over data input.
160+
*/
161+
function handleSelect(value: any) {
162+
onInput(value);
163+
if (!props.multiple) setActive(false)
164+
}
165+
function onInput(value: any) {
166+
167+
if (props.disabled) return;
168+
169+
const limit = typeof props.limit === 'number' ? props.limit : (typeof props.limit === 'string' ? Number.parseInt(props.limit, 10) : undefined);
170+
171+
const resultValue = props.multiple ? toggleValueFromArray(Array.isArray(props.modelValue) ? props.modelValue : [], value, limit) : value
172+
emit('update:modelValue', resultValue)
173+
}
174+
175+
/**
176+
* @description Является ли данный элемент активным(modelValue) содержит значение
177+
*/
178+
function isActiveItem(value: any) {
179+
return props.multiple ? (Array.isArray(props.modelValue) ? props.modelValue : []).includes(value) : props.modelValue === value
180+
}
181+
148182
</script>
149183

150184
<style scoped>
@@ -204,6 +238,7 @@ const filteredOptions = computed(() => {
204238
.input-select-option-list-item_active {
205239
background-color: #f4f4f4;
206240
border-radius: 3px;
241+
color: var(--vf-input-active);
207242
}
208243
.input-select-option-list {
209244
overflow: auto;

tests/integrations/inputs/input-select.spec.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,4 +201,83 @@ describe("Input Select Testing", () => {
201201
await app.vm.$nextTick();
202202
expect(input.element.getAttribute('tabindex')).toBe("none")
203203
})
204+
205+
test("Multiple attr should show first selected item as title", async () => {
206+
const wrap = defaultMount(defineSelectComponent({
207+
multiple: true,
208+
options: defaultOptions
209+
}))
210+
const form = (wrap.vm as any).form;
211+
form.setValues({
212+
[name]: [defaultOptions[0].value]
213+
})
214+
await wrap.vm.$nextTick();
215+
expect(wrap.text()).toBe(defaultOptions[0].label);
216+
})
217+
test("Multiple attr should show first selected item + N as title if was selected more then one", async () => {
218+
const wrap = defaultMount(defineSelectComponent({
219+
multiple: true,
220+
options: defaultOptions
221+
}))
222+
const form = (wrap.vm as any).form;
223+
form.setValues({
224+
[name]: defaultOptions.map(i => i.value)
225+
})
226+
await wrap.vm.$nextTick();
227+
expect(wrap.text()).toBe(defaultOptions[0].label + ' + ' + (defaultOptions.length - 1));
228+
})
229+
230+
test("Multiple attr should show selected items", async () => {
231+
const wrap = defaultMount(defineSelectComponent({
232+
multiple: true,
233+
options: defaultOptions
234+
}))
235+
const form = (wrap.vm as any).form as Form;
236+
form.setValues({
237+
[name]: [defaultOptions[1].value]
238+
})
239+
currentItem = wrap.find('.container-input-select-current')
240+
expect(currentItem.exists()).toBe(true);
241+
await currentItem.trigger('click');
242+
243+
expect(wrap.findAll('.input-select-option-list-item_active').map(item => item.text())).toEqual([defaultOptions[1].label])
244+
})
245+
test("Selecting items should update value(multiple attr", async () => {
246+
const wrap = defaultMount(defineSelectComponent({
247+
multiple: true,
248+
options: defaultOptions
249+
}))
250+
const form = (wrap.vm as any).form as Form;
251+
currentItem = wrap.find('.container-input-select-current')
252+
await currentItem.trigger('click');
253+
254+
await wrap.findAll('.input-select-option-list-item').reduce((acc, item) => {
255+
return acc.then(() => item.trigger('click'))
256+
}, Promise.resolve())
257+
258+
expect(form.getValueByName(name)).toEqual(defaultOptions.map(item => item.value))
259+
260+
await wrap.findAll('.input-select-option-list-item').reduce((acc, item) => {
261+
return acc.then(() => item.trigger('click'))
262+
}, Promise.resolve())
263+
264+
expect(form.getValueByName(name)).toEqual([])
265+
})
266+
267+
test("Using limit should reject selecting more then provided in limit attr.", async () => {
268+
const wrap = defaultMount(defineSelectComponent({
269+
multiple: true,
270+
options: defaultOptions,
271+
limit: 2
272+
}))
273+
const form = (wrap.vm as any).form as Form;
274+
currentItem = wrap.find('.container-input-select-current')
275+
await currentItem.trigger('click');
276+
277+
await wrap.findAll('.input-select-option-list-item').reduce((acc, item) => {
278+
return acc.then(() => item.trigger('click'))
279+
}, Promise.resolve())
280+
281+
expect(form.getValueByName(name)).toEqual(defaultOptions.map(item => item.value).slice(0, 2))
282+
})
204283
})

0 commit comments

Comments
 (0)
0