Splitting logic for property control from logic to create UI from schema

This commit is contained in:
Julian Stirling 2025-07-27 18:30:32 +01:00
parent e7e3a08210
commit 7f4b4ed1f3
3 changed files with 236 additions and 175 deletions

View file

@ -0,0 +1,216 @@
<template>
<div>
<label v-if="dataType == 'number'" class="uk-form-label"
>{{ label }}
<div class="input-and-buttons-container">
<input
v-model="internalValue"
class="uk-form-small numeric-setting-line-input"
type="number"
@focusin="focusIn"
@focusout="focusOut"
@keydown="keyDown"
/>
<a class="button-next-to-input" @click="requestUpdate">
<span class="material-symbols-outlined">refresh</span>
</a>
</div>
</label>
<div v-if="dataType == 'boolean'" class="input-and-buttons-container">
<label class="uk-form-label numeric-setting-line-input">
<input
ref="checkbox"
v-model="internalValue"
class="uk-checkbox"
type="checkbox"
@change="sendValue"
/>
{{ label }}
</label>
<a class="button-next-to-input" @click="requestUpdate">
<span class="material-symbols-outlined">refresh</span>
</a>
</div>
<label v-if="dataType == 'number_array'" class="uk-form-label"
>{{ label }}
<div class="input-and-buttons-container">
<input
v-for="i in valueLength"
:key="i"
v-model="internalValue[i - 1]"
class="uk-form-small numeric-setting-line-input"
type="number"
@focusin="focusIn"
@focusout="focusOut"
@keydown="keyDown"
/>
<a class="button-next-to-input" @click="requestUpdate">
<span class="material-symbols-outlined">refresh</span>
</a>
</div>
</label>
<label v-if="dataType == 'number_object'" class="uk-form-label"
>{{ label }}
<div class="input-and-buttons-container">
<input
v-for="(_v, key) in value"
:key="key"
v-model="internalValue[key]"
class="uk-form-small numeric-setting-line-input"
type="number"
@focusin="focusIn"
@focusout="focusOut"
@keydown="keyDown"
/>
<a class="button-next-to-input" @click="requestUpdate">
<span class="material-symbols-outlined">refresh</span>
</a>
</div>
</label>
<label v-if="dataType == 'other'" class="uk-form-label"
>{{ label }}
<div class="input-and-buttons-container">
<input
v-model="internalValue"
class="uk-form-small numeric-setting-line-input"
type="text"
disabled="true"
/>
</div>
</label>
</div>
</template>
<script>
export default {
name: "InputFromSchema",
props: {
dataSchema: {
type: Object
},
value: {
type: null
},
label: {
type: String,
default: ""
},
},
data() {
return {
internalValue: this.value,
valueOnEnter: undefined,
focused: false
};
},
watch: {
value(newValue) {
this.internalValue = newValue;
}
},
computed: {
valueLength: function() {
if (this.dataType == "number_array") {
if (this.internalValue == undefined) {
return 0;
}
return this.internalValue.length;
} else {
return 1;
}
},
dataType: function() {
let prop = this.dataSchema;
if (prop == undefined) {
return "undefined";
}
const num_types = ["integer", "float", "number"];
if (num_types.includes(prop.type)) {
return "number";
}
if (prop.type == "array") {
if (num_types.includes(prop.items.type)) {
return "number_array";
}
if (Array.isArray(prop.items)) {
if (prop.items.every(t => num_types.includes(t.type))) {
return "number_array";
}
}
}
if (prop.type == "boolean") {
return "boolean";
}
if (prop.type == "object") {
let numeric = true;
for (let key in prop.properties) {
if (!num_types.includes(prop.properties[key].type)) {
numeric = false;
break;
}
}
if (numeric) {
return "number_object";
}
}
return "other";
}
},
methods: {
requestUpdate: async function() {
this.$emit("requestUpdate")
},
sendValue: async function() {
this.$emit("sendValue", this.internalValue)
},
checkboxUpdated: function() {
if (this.internalValue != this.$refs.checkbox.checked) {
this.internalValue = this.$refs.checkbox.checked;
this.sendValue();
}
},
focusIn: function(event) {
this.valueOnEnter = event.target.value;
},
focusOut: function(event) {
if (this.valueOnEnter != event.target.value) {
this.sendValue(event.target.value);
}
},
keyDown: function(event) {
// Pressing enter should set the property, whether or not we think it's changed.
if (event.keyCode == 13) {
this.sendValue();
}
}
}
};
</script>
<style scoped>
.input-and-buttons-container {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-content: stretch;
align-items: center;
width: 100%;
}
.numeric-setting-line-input {
flex-grow: 1;
margin-left: 5px;
margin-right: 5px;
width: 6em;
}
.button-next-to-input {
flex-grow: 0;
padding-left: 5px;
padding-right: 5px;
vertical-align: middle;
cursor: pointer;
}
</style>

View file

@ -1,90 +1,23 @@
<template>
<div>
<label v-if="dataType == 'number'" class="uk-form-label"
>{{ label }}
<div class="input-and-buttons-container">
<input
v-model="value"
class="uk-form-small numeric-setting-line-input"
type="number"
@focusin="focusIn"
@focusout="focusOut"
@keydown="keyDown"
/>
<a class="button-next-to-input" @click="readProperty">
<span class="material-symbols-outlined">refresh</span>
</a>
</div>
</label>
<div v-if="dataType == 'boolean'" class="input-and-buttons-container">
<label class="uk-form-label numeric-setting-line-input">
<input
ref="checkbox"
v-model="value"
class="uk-checkbox"
type="checkbox"
@change="writeProperty"
/>
{{ label }}
</label>
<a class="button-next-to-input" @click="readProperty">
<span class="material-symbols-outlined">refresh</span>
</a>
</div>
<label v-if="dataType == 'number_array'" class="uk-form-label"
>{{ label }}
<div class="input-and-buttons-container">
<input
v-for="i in valueLength"
:key="i"
v-model="value[i - 1]"
class="uk-form-small numeric-setting-line-input"
type="number"
@focusin="focusIn"
@focusout="focusOut"
@keydown="keyDown"
/>
<a class="button-next-to-input" @click="readProperty">
<span class="material-symbols-outlined">refresh</span>
</a>
</div>
</label>
<label v-if="dataType == 'number_object'" class="uk-form-label"
>{{ label }}
<div class="input-and-buttons-container">
<input
v-for="(_v, key) in value"
:key="key"
v-model="value[key]"
class="uk-form-small numeric-setting-line-input"
type="number"
@focusin="focusIn"
@focusout="focusOut"
@keydown="keyDown"
/>
<a class="button-next-to-input" @click="readProperty">
<span class="material-symbols-outlined">refresh</span>
</a>
</div>
</label>
<label v-if="dataType == 'other'" class="uk-form-label"
>{{ label }}
<div class="input-and-buttons-container">
<input
:value="value"
class="uk-form-small numeric-setting-line-input"
type="text"
disabled="true"
/>
</div>
</label>
</div>
<input-from-schema
v-model="value"
:data-schema="propertyDescription"
:label="label"
@requestUpdate="readProperty"
@sendValue="writeProperty"
/>
</template>
<script>
import InputFromSchema from "./inputFromSchema.vue";
export default {
name: "PropertyControl",
components: {
InputFromSchema
},
props: {
label: {
type: String,
@ -110,25 +43,13 @@ export default {
}
},
data: () => {
data() {
return {
value: undefined,
valueOnEnter: undefined,
focused: false
value: undefined
};
},
computed: {
valueLength: function() {
if (this.dataType == "number_array") {
if (this.value == undefined) {
return 0;
}
return this.value.length;
} else {
return 1;
}
},
propertyDescription: function() {
try {
return this.thingDescription(this.thingName).properties[
@ -137,42 +58,6 @@ export default {
} catch (error) {
return undefined;
}
},
dataType: function() {
let prop = this.propertyDescription;
if (prop == undefined) {
return "undefined";
}
const num_types = ["integer", "float", "number"];
if (num_types.includes(prop.type)) {
return "number";
}
if (prop.type == "array") {
if (num_types.includes(prop.items.type)) {
return "number_array";
}
if (Array.isArray(prop.items)) {
if (prop.items.every(t => num_types.includes(t.type))) {
return "number_array";
}
}
}
if (prop.type == "boolean") {
return "boolean";
}
if (prop.type == "object") {
let numeric = true;
for (let key in prop.properties) {
if (!num_types.includes(prop.properties[key].type)) {
numeric = false;
break;
}
}
if (numeric) {
return "number_object";
}
}
return "other";
}
},
@ -201,9 +86,10 @@ export default {
this.value = data;
return data;
},
writeProperty: async function() {
writeProperty: async function(requestedValue) {
try {
let requestedValue = this.value;
console.log(requestedValue);
this.value=requestedValue;
await this.writeThingProperty(
this.thingName,
this.propertyName,
@ -225,51 +111,9 @@ export default {
} catch (error) {
this.modalError(error); // Let mixin handle error
}
},
checkboxUpdated: function() {
if (this.value != this.$refs.checkbox.checked) {
this.value = this.$refs.checkbox.checked;
this.writeProperty();
}
},
focusIn: function(event) {
this.valueOnEnter = event.target.value;
},
focusOut: function(event) {
if (this.valueOnEnter != event.target.value) {
this.writeProperty(event.target.value);
}
},
keyDown: function(event) {
// Pressing enter should set the property, whether or not we think it's changed.
if (event.keyCode == 13) {
this.writeProperty();
}
}
}
};
</script>
<style scoped>
.input-and-buttons-container {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-content: stretch;
align-items: center;
width: 100%;
}
.numeric-setting-line-input {
flex-grow: 1;
margin-left: 5px;
margin-right: 5px;
width: 6em;
}
.button-next-to-input {
flex-grow: 0;
padding-left: 5px;
padding-right: 5px;
vertical-align: middle;
cursor: pointer;
}
</style>
<style scoped></style>

View file

@ -3,6 +3,7 @@
:thing="propertyData.thing"
:property-name="propertyData.property_name"
:thing-name="propertyData.thing"
:label="propertyData.label"
:read-back="propertyData.read_back"
:read-back-delay="propertyData.read_back_delay"
/>