Parameter text: Improve support for multiple text values (#3092)
Previously, the text parameter used a text area and line breaks to separate multiple entries. This has been reworked to now use a list of single line text fields, and autocompletion for multiple values has been enhanced as well. Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com>pull/3141/head
parent
eb18d69a8e
commit
62aaf75c49
|
@ -1,12 +1,42 @@
|
||||||
<template>
|
<template>
|
||||||
<ul>
|
<ul v-if="multiple" ref="inputs">
|
||||||
|
<f7-block-header class="no-margin">
|
||||||
|
<div class="margin-horizontal item-label"
|
||||||
|
style="padding-top: var(--f7-list-item-padding-vertical); color: var(--f7-text-color)">
|
||||||
|
{{ configDescription.label }}
|
||||||
|
</div>
|
||||||
|
</f7-block-header>
|
||||||
|
<f7-list-input
|
||||||
|
v-for="(v, idx) in values"
|
||||||
|
no-hairline
|
||||||
|
:key="idx"
|
||||||
|
:type="controlType"
|
||||||
|
:pattern="configDescription.pattern"
|
||||||
|
:autocomplete="options ? 'off' : ''"
|
||||||
|
:clear-button="true"
|
||||||
|
@input:clear="removeValueIdx(idx)"
|
||||||
|
@input="updateValueIdx(idx, $event)"
|
||||||
|
@focus="gotFocus"
|
||||||
|
:value="v" />
|
||||||
|
<f7-list-input
|
||||||
|
v-if="!configDescription.readOnly"
|
||||||
|
ref="input"
|
||||||
|
:type="controlType"
|
||||||
|
:pattern="configDescription.pattern"
|
||||||
|
:autocomplete="options ? 'off' : ''"
|
||||||
|
:clear-button="false"
|
||||||
|
@input:notempty="addValue"
|
||||||
|
@focus="gotFocus"
|
||||||
|
:placeholder="configDescription.placeholder" />
|
||||||
|
</ul>
|
||||||
|
<ul v-else>
|
||||||
<f7-list-input
|
<f7-list-input
|
||||||
ref="input"
|
ref="input"
|
||||||
:floating-label="$theme.md"
|
:floating-label="$theme.md"
|
||||||
:label="configDescription.label"
|
:label="configDescription.label"
|
||||||
:name="configDescription.name"
|
:name="configDescription.name"
|
||||||
:value="formattedValue"
|
:value="value"
|
||||||
:autocomplete="autoCompleteOptions ? 'off' : ''"
|
:autocomplete="options ? 'off' : ''"
|
||||||
:placeholder="configDescription.placeholder"
|
:placeholder="configDescription.placeholder"
|
||||||
:pattern="configDescription.pattern"
|
:pattern="configDescription.pattern"
|
||||||
:required="configDescription.required" validate
|
:required="configDescription.required" validate
|
||||||
|
@ -29,50 +59,152 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
controlType () {
|
controlType () {
|
||||||
if (this.configDescription.context === 'password' && !this.showPassword) return 'password'
|
if (this.configDescription.context === 'password' && !this.showPassword) return 'password'
|
||||||
if (this.configDescription.multiple) return 'textarea'
|
|
||||||
return 'text'
|
return 'text'
|
||||||
},
|
},
|
||||||
formattedValue () {
|
multiple () {
|
||||||
if (this.configDescription.multiple) return (this.value) ? this.value.join('\n') : ''
|
return this.configDescription?.multiple
|
||||||
return this.value
|
},
|
||||||
|
options () {
|
||||||
|
if (this.configDescription?.options && this.configDescription.options.length > 0) {
|
||||||
|
return this.configDescription.options.map((o) => {
|
||||||
|
return {
|
||||||
|
id: o.value,
|
||||||
|
text: (o.label) ? (o.value !== o.label ? `${o.label} (${o.value})` : o.label) : o.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
autoCompleteOptions: null,
|
autoCompleteOptions: null,
|
||||||
showPassword: false
|
showPassword: false,
|
||||||
|
values: [], // Used for multiple values parameters only
|
||||||
|
suspendEvents: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
created () {
|
||||||
|
this.setValues()
|
||||||
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
if (this.configDescription.options && this.configDescription.options.length > 0) {
|
if (!this.multiple && this.options) {
|
||||||
const options = this.configDescription.options.map((o) => {
|
|
||||||
return {
|
|
||||||
id: o.value,
|
|
||||||
text: (o.label) ? (o.value !== o.label) ? `${o.label} (${o.value})` : o.label : o.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const inputControl = this.$refs.input
|
const inputControl = this.$refs.input
|
||||||
if (!inputControl || !inputControl.$el) return
|
if (!inputControl || !inputControl.$el) return
|
||||||
const inputElement = this.$$(inputControl.$el).find(this.configDescription.multiple ? 'textarea' : 'input')
|
const inputElement = this.$$(inputControl.$el).find('input')
|
||||||
|
const options = this.options
|
||||||
this.autoCompleteOptions = this.$f7.autocomplete.create({
|
this.autoCompleteOptions = this.$f7.autocomplete.create({
|
||||||
inputEl: inputElement,
|
inputEl: inputElement,
|
||||||
openIn: 'dropdown',
|
openIn: 'dropdown',
|
||||||
requestSourceOnOpen: true,
|
requestSourceOnOpen: true,
|
||||||
source (query, render) {
|
source (query, render) {
|
||||||
render(options.filter((o) => o.text.toLowerCase().indexOf(query.toLowerCase()) >= 0))
|
render(query ? options.filter((o) => o.text.toLowerCase().includes(query.toLowerCase())) : options)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
if (this.autoCompleteOptions) {
|
this.destroyAutoCompleteOptions()
|
||||||
this.$f7.autocomplete.destroy(this.autoCompleteOptions)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateValue (event) {
|
updateValue (event) {
|
||||||
const value = (this.configDescription.multiple) ? event.target.value.split('\n') : event.target.value
|
if (this.multiple) return
|
||||||
this.$emit('input', value)
|
this.$emit('input', event.target.value)
|
||||||
|
},
|
||||||
|
updateValueIdx (idx, event) {
|
||||||
|
if (!this.multiple || idx < 0 || !this.values || idx >= this.values.length) return
|
||||||
|
const newValues = [...this.values]
|
||||||
|
newValues[idx] = event.target.value
|
||||||
|
this.$set(this, 'values', newValues)
|
||||||
|
this.emitValues()
|
||||||
|
},
|
||||||
|
addValue (event) {
|
||||||
|
if (this.suspendEvents || !this.multiple || !event) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const v = event.target?.value
|
||||||
|
if (!v) return
|
||||||
|
let newValues = this.values.filter((val, idx) => val && this.values.indexOf(val) === idx)
|
||||||
|
if (newValues.some((val) => val === v)) return
|
||||||
|
newValues.push(v)
|
||||||
|
this.suspendEvents = true
|
||||||
|
this.$set(this, 'values', newValues)
|
||||||
|
this.emitValues()
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const inputControl = this.$refs.input
|
||||||
|
if (inputControl && inputControl.$el) {
|
||||||
|
const inputElements = this.$$(inputControl.$el).find('input')
|
||||||
|
if (inputElements && inputElements.length > 0) {
|
||||||
|
const inputElement = inputElements[0]
|
||||||
|
inputElement.value = ''
|
||||||
|
let prev = this.findAncestor(inputElement, 'li')?.previousElementSibling
|
||||||
|
if (prev) {
|
||||||
|
let prevInput = this.$$(prev).find('input')
|
||||||
|
if (prevInput) {
|
||||||
|
prevInput.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.suspendEvents = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeValueIdx (idx) {
|
||||||
|
if (this.suspendEvents || !this.multiple || idx < 0 || !this.values || idx >= this.values.length) return
|
||||||
|
let newValues = [...this.values]
|
||||||
|
newValues.splice(idx, 1)
|
||||||
|
this.suspendEvents = true
|
||||||
|
this.$set(this, 'values', newValues)
|
||||||
|
this.emitValues()
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.suspendEvents = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setValues () {
|
||||||
|
if (this.multiple) {
|
||||||
|
let result
|
||||||
|
if (!this.value) {
|
||||||
|
result = []
|
||||||
|
} else if (Array.isArray(this.value)) {
|
||||||
|
result = this.value
|
||||||
|
} else {
|
||||||
|
result = [this.value]
|
||||||
|
}
|
||||||
|
this.$set(this, 'values', result)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emitValues () {
|
||||||
|
this.$emit('input', this.values.filter((v, idx) => v && this.values.indexOf(v) === idx))
|
||||||
|
},
|
||||||
|
findAncestor (el, selector) {
|
||||||
|
while ((el = el.parentElement) && !el.matches(selector));
|
||||||
|
return el
|
||||||
|
},
|
||||||
|
gotFocus (event) {
|
||||||
|
if (!event?.target || !this.options?.length) return
|
||||||
|
if (this.autoCompleteOptions) {
|
||||||
|
if (this.autoCompleteOptions.inputEl === event.target) return
|
||||||
|
this.destroyAutoCompleteOptions()
|
||||||
|
}
|
||||||
|
const options = this.values?.length ? this.options.filter((o) => !this.values.some((v) => v.toLowerCase() === o.id.toLowerCase())) : this.options
|
||||||
|
if (!options?.length) return
|
||||||
|
this.autoCompleteOptions = this.$f7.autocomplete.create({
|
||||||
|
inputEl: event.target,
|
||||||
|
openIn: 'dropdown',
|
||||||
|
requestSourceOnOpen: true,
|
||||||
|
source (query, render) {
|
||||||
|
render(query ? options.filter((o) => o.text.toLowerCase().includes(query.toLowerCase())) : options)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.autoCompleteOptions.open()
|
||||||
|
},
|
||||||
|
destroyAutoCompleteOptions () {
|
||||||
|
if (this.autoCompleteOptions) {
|
||||||
|
this.autoCompleteOptions.close()
|
||||||
|
this.$f7.autocomplete.destroy(this.autoCompleteOptions)
|
||||||
|
}
|
||||||
|
this.autoCompleteOptions = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue