Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add oh-context component #2533

Merged
merged 15 commits into from
Jun 1, 2024
1 change: 1 addition & 0 deletions bundles/org.openhab.ui/doc/components/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ source: https://github.com/openhab/openhab-webui/edit/main/bundles/org.openhab.u
| [`oh-chart`](./oh-chart.html) | [Chart](./oh-chart.html) | Visualize series of data |
| [`oh-clock`](./oh-clock.html) | [Digital Clock](./oh-clock.html) | Display a digital clock |
| [`oh-colorpicker`](./oh-colorpicker.html) | [Colorpicker](./oh-colorpicker.html) | Control to pick a color |
| [`oh-context`](./oh-context.html) | [Context](./oh-context.html) | Non-rendered component with functions, constants, and scoped variables for widgets |
| [`oh-gauge`](./oh-gauge.html) | [Gauge](./oh-gauge.html) | Circular or semi-circular read-only gauge |
| [`oh-icon`](./oh-icon.html) | [Icon](./oh-icon.html) | Display an openHAB icon |
| [`oh-image`](./oh-image.html) | [Image](./oh-image.html) | Displays an image from a URL or an item |
Expand Down
101 changes: 101 additions & 0 deletions bundles/org.openhab.ui/doc/components/oh-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
title: oh-context - Widget Context
component: oh-context
label: Widget Context
description: Non-rendered component with functions, constants, and local variables for widgets
source: https://github.com/openhab/openhab-webui/edit/main/bundles/org.openhab.ui/doc/components/oh-context.md
prev: /docs/ui/components/
---

# oh-context - Widget Context

<!-- Put a screenshot here if relevant:
![](./images/oh-context/header.jpg)
-->

[[toc]]

<!-- Note: you can overwrite the definition-provided description and add your own intro/additional sections instead -->
<!-- DO NOT REMOVE the following comments if you intend to keep the definition-provided description -->
<!-- GENERATED componentDescription -->
Non-rendered component with functions, constants, and scoped variables for widgets
<!-- GENERATED /componentDescription -->

## Configuration

<!-- DO NOT REMOVE the following comments -->
<!-- GENERATED props -->
### General
<div class="props">
<PropGroup label="General">
<PropBlock type="TEXT" name="functions" label="Widget Functions">
<PropDescription>
Object with key:arrow-function pairs. Functions are available to expressions in all child components via the <code>fn</code> object.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="constants" label="Widget Constants">
<PropDescription>
Object with key:constant pairs. Constants are available to expressions in all child components via the <code>const</code> object.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="variables" label="Widget Variables">
<PropDescription>
Object with key:variable default value pairs. Variables are available to expressions in all child components via the <code>vars</code> object and take precedence over variables with the same name from higher contexts.
</PropDescription>
</PropBlock>
</PropGroup>
</div>


<!-- GENERATED /props -->

<!-- If applicable describe how properties are forwarded to a underlying component from Framework7, ECharts, etc.:
### Inherited Properties

-->

<!-- If applicable describe the slots recognized by the component and what they represent:
### Slots

#### `default`

The contents of the oh-context.

-->

<!-- Add as many examples as desired - put the YAML in a details container when it becomes too long (~150/200+ lines):
## Examples

### Example 1

![](./images/oh-context/example1.jpg)

```yaml
component: oh-context
config:
prop1: value1
prop2: value2
```

### Example 2

![](./images/oh-context/example2.jpg)

::: details YAML
```yaml
component: oh-context
config:
prop1: value1
prop2: value2
slots
```
:::

-->

<!-- Try to clean up URLs to the forum (https://community.openhab.org/t/<threadID>[/<postID>] should suffice)
## Community Resources

- [Community Post 1](https://community.openhab.org/t/12345)
- [Community Post 2](https://community.openhab.org/t/23456)
-->
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { pt, pb, pi, pn } from '../helpers.js'

export default () => [
pt('functions', 'Widget Functions', 'Object with key:arrow-function pairs. Functions are available to expressions in all child components via the <code>fn</code> object.'),
pt('constants', 'Widget Constants', 'Object with key:constant pairs. Constants are available to expressions in all child components via the <code>const</code> object.'),
pt('variables', 'Widget Variables', 'Object with key:variable default value pairs. Variables are available to expressions in all child components via the <code>vars</code> object and take precedence over variables with the same name from higher contexts.')
]
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import ColorpickerParameters from './colorpicker.js'
export const OhColorpickerDefinition = () => new WidgetDefinition('oh-colorpicker', 'Colorpicker', 'Control to pick a color')
.params(ColorpickerParameters())

import ContextParameters from './context.js'
export const OhContextDefinition = () => new WidgetDefinition('oh-context', 'Context', 'Non-rendered component with functions, constants, and scoped variables for widgets')
.params(ContextParameters())

import GaugeParameters from './gauge.js'
export const OhGaugeDefinition = () => new WidgetDefinition('oh-gauge', 'Gauge', 'Circular or semi-circular read-only gauge')
.params(GaugeParameters())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ function hintExpression (cm, line) {
{ text: 'props.', displayText: 'props', description: 'Access to the props of the parent root component' },
{ text: 'config.', displayText: 'config', description: 'Access to the configuration of the current component' },
{ text: 'vars.', displayText: 'vars', description: 'Access to context vars' },
{ text: 'fn.', displayText: 'fn', description: 'Access to oh-context functions' },
{ text: 'const.', displayText: 'const', description: 'Access to oh-context constants' },
{ text: 'loop.', displayText: 'loop', description: 'Access to oh-repeater loop variables' },
{ text: 'JSON.', displayText: 'JSON', description: 'Access to the JSON object functions' },
{ text: 'Math.', displayText: 'Math', description: 'Access to the Math object functions' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default {
return {
currentTab: 0,
vars: {},
ctxVars: {},
tabVars: {}
}
},
Expand All @@ -24,6 +25,7 @@ export default {
store: this.$store.getters.trackedItems,
props: this.modalConfig,
vars: this.vars,
ctxVars: this.ctxVars,
modalConfig: this.modalConfig // For configuration of oh- components
}
},
Expand Down Expand Up @@ -67,6 +69,7 @@ export default {
onTabChange (idx) {
this.currentTab = idx
this.$set(this, 'vars', {})
this.$set(this, 'ctxVars', {})
},
tabContext (tab) {
const page = this.$store.getters.page(tab.config.page.replace('page:', ''))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export { default as OhRepeater } from './oh-repeater.vue'
export { default as OhChart } from './oh-chart.vue'
export { default as OhClock } from './oh-clock.vue'
export { default as OhSipclient } from './oh-sipclient.vue'
export { default as OhContext } from './oh-context.vue'
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,29 @@ export default {
}
if (this.config.clearVariable && !this.config.clearVariableKey) {
if (Array.isArray(this.config.clearVariable)) {
this.config.clearVariable.forEach((v) => this.$set(this.context.vars, v, undefined))
this.config.clearVariable.forEach((v) => {
const clearVariableScope = this.getVariableScope(this.context.ctxVars, this.context.varScope, v)
const clearVariableLocation = (clearVariableScope) ? this.context.ctxVars[clearVariableScope] : this.context.vars
this.$set(clearVariableLocation, v, undefined)
})
} else if (typeof this.config.clearVariable === 'string') {
this.$set(this.context.vars, this.config.clearVariable, undefined)
const clearVariableScope = this.getVariableScope(this.context.ctxVars, this.context.varScope, this.config.clearVariable)
const clearVariableLocation = (clearVariableScope) ? this.context.ctxVars[clearVariableScope] : this.context.vars
this.$set(clearVariableLocation, this.config.clearVariable, undefined)
}
}
if (this.config.clearVariable && this.config.clearVariableKey) {
let value = this.context.vars[this.config.clearVariable]
if (Array.isArray(this.config.clearVariableKey)) {
this.config.clearVariableKey.forEach((key) => {
value = this.setVariableKeyValues(value, key, undefined)
const clearVariableScope = this.getVariableScope(this.context.ctxVars, this.context.varScope, this.config.clearVariable)
const clearVariableLocation = (clearVariableScope) ? this.context.ctxVars[clearVariableScope] : this.context.vars
value = this.setVariableKeyValues(clearVariableLocation, key, undefined)
})
} else if (typeof this.config.clearVariableKey === 'string') {
value = this.setVariableKeyValues(value, this.config.clearVariableKey, undefined)
const clearVariableScope = this.getVariableScope(this.context.ctxVars, this.context.varScope, this.config.clearVariable)
const clearVariableLocation = (clearVariableScope) ? this.context.ctxVars[clearVariableScope] : this.context.vars
value = this.setVariableKeyValues(clearVariableLocation, this.config.clearVariableKey, undefined)
}
this.$set(this.context.vars, this.config.clearVariable, value)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<template>
<fragment v-if="(context.component.slots && context.component.slots.default)">
<generic-widget-component v-for="(slotComponent, idx) in context.component.slots.default" :key="'default-' + idx" :context="childrenContext(slotComponent)" />
</fragment>
</template>

<script>
import mixin from '../widget-mixin'
import { OhContextDefinition } from '@/assets/definitions/widgets/system'

import { Fragment } from 'vue-fragment'

export default {
mixins: [mixin],
components: {
Fragment
},
widget: OhContextDefinition,
data () {
return {
varScope: (this.context.varScope || 'varScope') + '-' + this.$f7.utils.id()
}
},
computed: {
fn () {
if (!this.context || !this.context.component || !this.context.component.config) return {}
let evalFunc = {}
const sourceFunc = this.context.component.config.functions || {}
console.debug('oh-context: sourceFunc =', sourceFunc)
if (sourceFunc) {
if (typeof sourceFunc !== 'object') return {}
for (const key in sourceFunc) {
evalFunc[key] = this.evaluateExpression(key, sourceFunc[key])
}
}
console.debug('oh-context: evalFunc =', evalFunc)
return evalFunc
}
},
methods: {
childrenContext (childComp) {
const ctx = this.childContext(childComp)
const ctxFunctions = this.fn
if (this.context.fn) {
for (const funcKey in this.context.fn) {
if (!ctxFunctions[funcKey]) this.$set(ctxFunctions, funcKey, this.context.fn[funcKey])
}
}
this.$set(ctx, 'fn', ctxFunctions)

const ctxConstants = this.const
if (this.context.const) {
for (const constKey in this.context.const) {
if (!ctxConstants[constKey]) this.$set(ctxConstants, constKey, this.context.const[constKey])
}
}
this.$set(ctx, 'const', ctxConstants)

this.$set(ctx.ctxVars, this.varScope, this.ctxVars)

return ctx
}
},
beforeMount () {
const evaluateDefaults = () => {
if (!this.context || !this.context.component || !this.context.component.config) return

this.const = {}
const sourceConst = this.context.component.config.constants || {}
if (sourceConst) {
if (typeof sourceConst !== 'object') return
for (const key in sourceConst) {
this.$set(this.const, key, this.evaluateExpression(key, sourceConst[key]))
}
}

this.ctxVars = {}
const sourceCtxVars = this.context.component.config.variables || {}
if (sourceCtxVars) {
if (typeof sourceCtxVars !== 'object') return
for (const key in sourceCtxVars) {
this.$set(this.ctxVars, key, this.evaluateExpression(key, sourceCtxVars[key]))
}
}
}
evaluateDefaults()
}
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ export default {
widget: OhGaugeDefinition,
computed: {
value () {
if (this.config.variable) return this.context.vars[this.config.variable]
if (this.config.variable) {
const variableScope = this.getVariableScope(this.context.ctxVars, this.context.varScope, this.config.variable)
const variableLocation = (variableScope) ? this.context.ctxVars[variableScope] : this.context.vars
return variableLocation[this.config.variable]
}
let value = (this.config.item) ? this.context.store[this.config.item].state : this.config.value
// use as a brightness indicator for HSB values
if (value.split && value.split(',').length === 3) value = value.split(',')[2]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,18 @@ export default {
},
computed: {
value () {
let variableLocation = this.context.vars
if (this.config.variable) {
const variableScope = this.getVariableScope(this.context.ctxVars, this.context.varScope, this.config.variable)
if (variableScope) variableLocation = this.context.ctxVars[variableScope]
}
if (this.config.variable && this.config.variableKey) {
const keyValue = this.getLastVariableKeyValue(this.context.vars[this.config.variable], this.config.variableKey)
const keyValue = this.getLastVariableKeyValue(variableLocation[this.config.variable], this.config.variableKey)
if (keyValue) {
return keyValue
}
} else if (this.config.variable && this.context.vars[this.config.variable] !== undefined) {
return this.context.vars[this.config.variable]
} else if (this.config.variable && variableLocation[this.config.variable] !== undefined) {
return variableLocation[this.config.variable]
} else if (this.config.sendButton && this.pendingUpdate !== null) {
return this.pendingUpdate
} else if (this.config.item && this.context.store[this.config.item].state !== 'NULL' && this.context.store[this.config.item].state !== 'UNDEF' && this.context.store[this.config.item].state !== 'Invalid Date') {
Expand Down Expand Up @@ -107,10 +112,12 @@ export default {
this.$set(this, 'pendingUpdate', value)
}
if (this.config.variable) {
const variableScope = this.getVariableScope(this.context.ctxVars, this.context.varScope, this.config.variable)
const variableLocation = (variableScope) ? this.context.ctxVars[variableScope] : this.context.vars
if (this.config.variableKey) {
value = this.setVariableKeyValues(this.context.vars[this.config.variable], this.config.variableKey, value)
value = this.setVariableKeyValues(variableLocation[this.config.variable], this.config.variableKey, value)
}
this.$set(this.context.vars, this.config.variable, value)
this.$set(variableLocation, this.config.variable, value)
}
},
sendButtonClicked () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,29 @@ export default {
}
if (this.config.clearVariable && !this.config.clearVariableKey) {
if (Array.isArray(this.config.clearVariable)) {
this.config.clearVariable.forEach((v) => this.$set(this.context.vars, v, undefined))
this.config.clearVariable.forEach((v) => {
const clearVariableScope = this.getVariableScope(this.context.ctxVars, this.context.varScope, v)
const clearVariableLocation = (clearVariableScope) ? this.context.ctxVars[clearVariableScope] : this.context.vars
this.$set(clearVariableLocation, v, undefined)
})
} else if (typeof this.config.clearVariable === 'string') {
this.$set(this.context.vars, this.config.clearVariable, undefined)
const clearVariableScope = this.getVariableScope(this.context.ctxVars, this.context.varScope, this.config.clearVariable)
const clearVariableLocation = (clearVariableScope) ? this.context.ctxVars[clearVariableScope] : this.context.vars
this.$set(clearVariableLocation, this.config.clearVariable, undefined)
}
}
if (this.config.clearVariable && this.config.clearVariableKey) {
let value = this.context.vars[this.config.clearVariable]
if (Array.isArray(this.config.clearVariableKey)) {
this.config.clearVariableKey.forEach((key) => {
value = this.setVariableKeyValues(value, key, undefined)
const clearVariableScope = this.getVariableScope(this.context.ctxVars, this.context.varScope, this.config.clearVariable)
const clearVariableLocation = (clearVariableScope) ? this.context.ctxVars[clearVariableScope] : this.context.vars
value = this.setVariableKeyValues(clearVariableLocation, key, undefined)
})
} else if (typeof this.config.clearVariableKey === 'string') {
value = this.setVariableKeyValues(value, this.config.clearVariableKey, undefined)
const clearVariableScope = this.getVariableScope(this.context.ctxVars, this.context.varScope, this.config.clearVariable)
const clearVariableLocation = (clearVariableScope) ? this.context.ctxVars[clearVariableScope] : this.context.vars
value = this.setVariableKeyValues(clearVariableLocation, this.config.clearVariableKey, undefined)
}
this.$set(this.context.vars, this.config.clearVariable, value)
}
Expand Down
Loading
Loading