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>
|
||||
<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
|
||||
ref="input"
|
||||
:floating-label="$theme.md"
|
||||
:label="configDescription.label"
|
||||
:name="configDescription.name"
|
||||
:value="formattedValue"
|
||||
:autocomplete="autoCompleteOptions ? 'off' : ''"
|
||||
:value="value"
|
||||
:autocomplete="options ? 'off' : ''"
|
||||
:placeholder="configDescription.placeholder"
|
||||
:pattern="configDescription.pattern"
|
||||
:required="configDescription.required" validate
|
||||
|
@ -29,50 +59,152 @@ export default {
|
|||
computed: {
|
||||
controlType () {
|
||||
if (this.configDescription.context === 'password' && !this.showPassword) return 'password'
|
||||
if (this.configDescription.multiple) return 'textarea'
|
||||
return 'text'
|
||||
},
|
||||
formattedValue () {
|
||||
if (this.configDescription.multiple) return (this.value) ? this.value.join('\n') : ''
|
||||
return this.value
|
||||
multiple () {
|
||||
return this.configDescription?.multiple
|
||||
},
|
||||
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 () {
|
||||
return {
|
||||
autoCompleteOptions: null,
|
||||
showPassword: false
|
||||
showPassword: false,
|
||||
values: [], // Used for multiple values parameters only
|
||||
suspendEvents: false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.setValues()
|
||||
},
|
||||
mounted () {
|
||||
if (this.configDescription.options && this.configDescription.options.length > 0) {
|
||||
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
|
||||
}
|
||||
})
|
||||
if (!this.multiple && this.options) {
|
||||
const inputControl = this.$refs.input
|
||||
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({
|
||||
inputEl: inputElement,
|
||||
openIn: 'dropdown',
|
||||
requestSourceOnOpen: true,
|
||||
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 () {
|
||||
if (this.autoCompleteOptions) {
|
||||
this.$f7.autocomplete.destroy(this.autoCompleteOptions)
|
||||
}
|
||||
this.destroyAutoCompleteOptions()
|
||||
},
|
||||
methods: {
|
||||
updateValue (event) {
|
||||
const value = (this.configDescription.multiple) ? event.target.value.split('\n') : event.target.value
|
||||
this.$emit('input', value)
|
||||
if (this.multiple) return
|
||||
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