Skip to content

Commit 8dc7696

Browse files
authored
Various enhancements & fixes to config parameters (#3460)
- thing context: Fix parameter fails with JS error in widgets. - number context: Validate on any input, not only blur. - config-sheet: Fix advanced value for parameter groups ignored. - month context: Initial contribution. - Add support for serial-port context. - text parameter: Set input type for email, telephone, color - time context: Fix picker styling. --------- Signed-off-by: Florian Hotze <[email protected]>
1 parent ebbb7bf commit 8dc7696

File tree

8 files changed

+207
-12
lines changed

8 files changed

+207
-12
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// type definitions for DTOs returned from /rest/config-descriptions endpoints
2+
3+
/**
4+
* The supported data types a configuration parameter can take (From ConfigDescriptionParameter.java).
5+
*/
6+
export type Type = 'TEXT' | 'INTEGER' | 'DECIMAL' | 'BOOLEAN';
7+
8+
/**
9+
* Data transfer object for a static selection list option. (From ParameterOption.java structure)
10+
*/
11+
export interface ParameterOption {
12+
/** The value of the option. */
13+
value: string;
14+
/** A human-readable label for the option. */
15+
label: string;
16+
}
17+
18+
/**
19+
* Data transfer object for filter criteria for a dynamic selection list.
20+
* (Inferred structure based on usage of FilterCriteria in the core model)
21+
*/
22+
export interface FilterCriteria {
23+
/** The name of the criterion. */
24+
name: string;
25+
/** The value of the criterion. */
26+
value: string;
27+
}
28+
29+
/**
30+
* Data transfer object used to serialize a parameter of a configuration description.
31+
* Based on org.openhab.core.config.core.dto.ConfigDescriptionParameterDTO.java.
32+
*/
33+
export interface ConfigDescriptionParameter {
34+
/** The name of the configuration parameter (must not be null or empty). */
35+
name: string;
36+
/** The data type of the configuration parameter (must not be null). */
37+
type: Type;
38+
/** The context of the configuration parameter, a hint for user interfaces and input validators. */
39+
context?: string;
40+
/** The default value of the configuration parameter (JSON field 'default' or 'defaultValue'). */
41+
default?: string;
42+
/** The default value of the configuration parameter (JSON field 'default' or 'defaultValue'). */
43+
defaultValue?: string;
44+
/** A human-readable description for the configuration parameter. */
45+
description?: string;
46+
/** A human-readable label for the configuration parameter. */
47+
label?: string;
48+
/** The minimal value for numeric types, minimal length for strings, or minimal number of selected options. */
49+
min?: number | string;
50+
/** The maximal value for numeric types, maximal length for strings, or maximal number of selected options. */
51+
max?: number | string;
52+
/** The value granularity for a numeric value. By setting the step size to 0, any granularity is allowed. */
53+
stepsize?: number | string;
54+
/** The regular expression pattern for a text type. */
55+
pattern?: string;
56+
/** Specifies whether the value is required. */
57+
required?: boolean;
58+
/** Specifies whether the value is read-only. */
59+
readOnly?: boolean;
60+
/** Specifies whether multiple selections of options are allowed. */
61+
multiple?: boolean;
62+
/** The maximum number of options that can be selected when multiple is true. */
63+
multipleLimit?: number;
64+
/** A string used to group parameters together into logical blocks so that the UI can display them together. */
65+
groupName?: string;
66+
/** Specifies if this is an advanced parameter. An advanced parameter can be hidden in the UI. */
67+
advanced?: boolean;
68+
/** Specifies whether the parameter should be considered dangerous and alert the user. */
69+
verify?: boolean;
70+
/** Specifies that the user's input is limited to the provided options). */
71+
limitToOptions?: boolean;
72+
/** Specifies the unit of measurements for the configuration parameter. */
73+
unit?: string;
74+
/** Specifies the unit label for the configuration parameter. */
75+
unitLabel?: string;
76+
/** A list of element definitions of a static selection list. */
77+
options?: ParameterOption[];
78+
/** A list of filter criteria for a dynamically created selection list. */
79+
filterCriteria?: FilterCriteria[];
80+
}

bundles/org.openhab.ui/web/src/components/config/config-parameter.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
</template>
3939

4040
<script>
41+
import { f7 } from 'framework7-vue'
42+
4143
// import ScriptEditorPopup from './config/script-editor-popup.vue'
4244
import ParameterThing from './controls/parameter-thing.vue'
4345
import ParameterBoolean from './controls/parameter-boolean.vue'
@@ -58,7 +60,7 @@ import ParameterProps from './controls/parameter-props.vue'
5860
import ParameterTriggerChannel from './controls/parameter-triggerchannel.vue'
5961
import ParameterText from './controls/parameter-text.vue'
6062
import ParameterQrcode from './controls/parameter-qrcode.vue'
61-
import { f7 } from 'framework7-vue'
63+
import ParameterMonth from '@/components/config/controls/parameter-month.vue'
6264
6365
export default {
6466
props: {
@@ -86,7 +88,7 @@ export default {
8688
},
8789
control () {
8890
const configDescription = this.configDescription
89-
if (configDescription.options?.length && configDescription.limitToOptions && (!configDescription.context || configDescription.context === 'network-interface')) {
91+
if (configDescription.options?.length && configDescription.limitToOptions && (!configDescription.context || configDescription.context === 'network-interface' || configDescription.context === 'serial-port')) {
9092
return ParameterOptions
9193
} else if (configDescription.type === 'INTEGER' || configDescription.type === 'DECIMAL') {
9294
return ParameterNumber
@@ -98,6 +100,8 @@ export default {
98100
return ParameterLocation
99101
} else if (configDescription.type === 'TEXT' && configDescription.context === 'cronexpression') {
100102
return ParameterCronExpression
103+
} else if (configDescription.type === 'TEXT' && configDescription.context === 'month') {
104+
return ParameterMonth
101105
} else if (configDescription.type === 'TEXT' && configDescription.context === 'dayOfWeek') {
102106
return ParameterDayOfWeek
103107
} else if (configDescription.type === 'TEXT' && configDescription.context === 'time') {

bundles/org.openhab.ui/web/src/components/config/config-sheet.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
2525
</f7-row>
2626
</f7-block>
2727
</f7-col>
28-
<f7-col v-if="parameterGroups.length">
28+
<f7-col v-if="displayedParameterGroups.length">
2929
<f7-block width="100"
3030
class="parameter-group"
31-
v-for="group in parameterGroups"
31+
v-for="group in displayedParameterGroups"
3232
:key="group.name">
3333
<f7-row v-if="displayedParameters.some((p) => p.groupName === group.name)">
3434
<f7-col>
@@ -124,6 +124,11 @@ export default {
124124
hasAdvanced () {
125125
return this.parameters.length > 0 && this.parameters.some((p) => p.advanced)
126126
},
127+
displayedParameterGroups () {
128+
if (!this.parameterGroups || !this.parameterGroups.length) return []
129+
if (this.showAdvanced) return this.parameterGroups
130+
return this.parameterGroups.filter((pg) => !pg.advanced)
131+
},
127132
displayedParameters () {
128133
function notNullNotUndefined (value) {
129134
return value !== null && value !== undefined
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<template>
2+
<ul>
3+
<f7-list-item
4+
:title="configDescription.label"
5+
smart-select
6+
:smart-select-params="smartSelectParams"
7+
ref="itemRef">
8+
<select :name="configDescription.name"
9+
@change="updateValue"
10+
:multiple="configDescription.multiple"
11+
:required="configDescription.required">
12+
<option v-if="!configDescription.required && !configDescription.multiple" :value="undefined" :selected="value === null || value === undefined" />
13+
<option v-for="(day, idx) in values"
14+
:value="day"
15+
:key="day"
16+
:selected="isSelected(day)">
17+
{{ labels[idx] }}
18+
</option>
19+
</select>
20+
</f7-list-item>
21+
</ul>
22+
</template>
23+
24+
<script setup lang="ts">
25+
import { ref } from 'vue'
26+
import { f7 } from 'framework7-vue'
27+
28+
import type { ConfigDescriptionParameter } from '@/components/config/config-descriptions.d.ts'
29+
30+
const props = defineProps<{
31+
configDescription: ConfigDescriptionParameter;
32+
value?: string | string[] | null;
33+
}>()
34+
35+
const emit = defineEmits<{
36+
(e: 'input', value: any): void;
37+
}>()
38+
39+
const labels = [
40+
'January', 'February', 'March', 'April', 'May', 'June',
41+
'July', 'August', 'September', 'October', 'November', 'December'
42+
]
43+
44+
const values = [
45+
'01', '02', '03', '04', '05', '06',
46+
'07', '08', '09', '10', '11', '12'
47+
]
48+
49+
const itemRef = ref<any>(null)
50+
51+
const smartSelectParams = {
52+
view: f7 ? f7.view.main : null,
53+
openIn: 'popover',
54+
closeOnSelect: !props.configDescription.multiple
55+
}
56+
57+
const updateValue = () => {
58+
const el = itemRef.value?.$el
59+
60+
if (el && el.children[0]) {
61+
// injected by Framework7 into the DOM node
62+
const selectEl = el.children[0] as any
63+
64+
if (selectEl.f7SmartSelect) {
65+
const val = selectEl.f7SmartSelect.getValue()
66+
emit('input', val)
67+
}
68+
}
69+
}
70+
71+
const isSelected = (option: string) => {
72+
if (props.value === null || props.value === undefined) return false
73+
74+
if (!props.configDescription.multiple) {
75+
return props.value === option
76+
} else {
77+
return props.value.indexOf(option) >= 0
78+
}
79+
}
80+
</script>

bundles/org.openhab.ui/web/src/components/config/controls/parameter-number.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
@input="updateValue"
1212
:required="configDescription.required"
1313
validate
14-
validate-on-blur
1514
:clear-button="false"
1615
type="number" />
1716
</ul>

bundles/org.openhab.ui/web/src/components/config/controls/parameter-text.vue

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
no-hairline
1212
:key="idx"
1313
:type="controlType"
14-
:pattern="configDescription.pattern"
14+
:pattern="pattern"
1515
:autocomplete="options ? 'off' : ''"
1616
:clear-button="true"
1717
@input:clear="removeValueIdx(idx)"
@@ -22,7 +22,7 @@
2222
v-if="!configDescription.readOnly"
2323
ref="input"
2424
:type="controlType"
25-
:pattern="configDescription.pattern"
25+
:pattern="pattern"
2626
:autocomplete="options ? 'off' : ''"
2727
:clear-button="false"
2828
@input:notempty="addValue"
@@ -38,9 +38,10 @@
3838
:value="value"
3939
:autocomplete="options ? 'off' : ''"
4040
:placeholder="configDescription.placeholder"
41-
:pattern="configDescription.pattern"
41+
:pattern="pattern"
4242
:required="configDescription.required"
43-
validate
43+
:validate="!(pattern && pattern.length > 40)"
44+
:validate-on-blur="pattern && pattern.length > 40"
4445
:clear-button="!configDescription.required && configDescription.context !== 'password'"
4546
@input="updateValue"
4647
:readonly="configDescription.readOnly"
@@ -72,8 +73,21 @@ export default {
7273
computed: {
7374
controlType () {
7475
if (this.configDescription.context === 'password' && !this.showPassword) return 'password'
76+
if (this.configDescription.context === 'email') return 'email'
77+
if (this.configDescription.context === 'telephone') return 'tel'
78+
if (this.configDescription.context === 'color') return 'color'
7579
return 'text'
7680
},
81+
pattern () {
82+
// validation doesn't work as soon as hostname RegEx is added
83+
// if (this.configDescription.context === 'network-address') {
84+
// const hostname = /(?:([a-zA-Z0-9-]+)\.)?([a-zA-Z0-9-]+)\.([a-zA-Z0-9]+)/
85+
// const ipv4 = /((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}/
86+
// const ipv6 = /((?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,7}:|:(?::[0-9A-Fa-f]{1,4}){1,7}|(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){1,5}(?::[0-9A-Fa-f]{1,4}){1,2}|(?:[0-9A-Fa-f]{1,4}:){1,4}(?::[0-9A-Fa-f]{1,4}){1,3}|(?:[0-9A-Fa-f]{1,4}:){1,3}(?::[0-9A-Fa-f]{1,4}){1,4}|(?:[0-9A-Fa-f]{1,4}:){1,2}(?::[0-9A-Fa-f]{1,4}){1,5}|[0-9A-Fa-f]{1,4}:(?:(?::[0-9A-Fa-f]{1,4}){1,6})|:(?:(?::[0-9A-Fa-f]{1,4}){1,6}))/
87+
// return `^(?:${ipv4.source}|${ipv6.source}|${hostname.source})$`
88+
// }
89+
return this.configDescription.pattern
90+
},
7791
multiple () {
7892
return this.configDescription?.multiple
7993
},

bundles/org.openhab.ui/web/src/components/config/controls/parameter-thing.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<thing-picker :title="configDescription.label"
33
:value="value"
44
@input="updateValue"
5-
:filter-uid="configDescription.options.map((o) => o.value)"
5+
:filter-uid="configDescription.options?.map((o) => o.value)"
66
:multiple="configDescription.multiple"
77
:required="configDescription.required" />
88
</template>
@@ -11,6 +11,7 @@
1111
import ThingPicker from './thing-picker.vue'
1212
1313
export default {
14+
inheritAttrs: false,
1415
props: {
1516
configDescription: Object,
1617
value: [String, Array]

bundles/org.openhab.ui/web/src/components/config/controls/parameter-time.vue

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<ul>
2+
<ul class="parameter-time">
33
<f7-list-input
44
ref="input"
55
:floating-label="theme.md"
@@ -10,7 +10,7 @@
1010
validate
1111
:clear-button="!configDescription.required"
1212
@input="updateValue">
13-
<template #content-end>
13+
<template #root>
1414
<div class="display-flex justify-content-center">
1515
<div ref="picker" />
1616
</div>
@@ -19,6 +19,18 @@
1919
</ul>
2020
</template>
2121

22+
<style lang="stylus">
23+
.parameter-time
24+
.picker-columns
25+
--f7-picker-mask-bg-color transparent
26+
.picker-center-highlight
27+
height var(--f7-picker-item-height)
28+
left 0
29+
margin-top calc(var(--f7-picker-item-height) * -1/2)
30+
pointer-events none
31+
width 100%
32+
</style>
33+
2234
<script>
2335
import { f7, theme } from 'framework7-vue'
2436
import { nextTick } from 'vue'

0 commit comments

Comments
 (0)