diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js
index 00eaaf0249..887695d41e 100644
--- a/packages/builder/src/builderStore/dataBinding.js
+++ b/packages/builder/src/builderStore/dataBinding.js
@@ -1,6 +1,10 @@
import { cloneDeep } from "lodash/fp"
import { get } from "svelte/store"
-import { findComponent, findComponentPath } from "./storeUtils"
+import {
+ findComponent,
+ findComponentPath,
+ findAllMatchingComponents,
+} from "./storeUtils"
import { store } from "builderStore"
import { tables as tablesStore, queries as queriesStores } from "stores/backend"
import { makePropSafe } from "@budibase/string-templates"
@@ -18,7 +22,9 @@ export const getBindableProperties = (asset, componentId) => {
const userBindings = getUserBindings()
const urlBindings = getUrlBindings(asset)
const deviceBindings = getDeviceBindings()
+ const stateBindings = getStateBindings()
return [
+ ...stateBindings,
...deviceBindings,
...urlBindings,
...contextBindings,
@@ -256,6 +262,18 @@ const getDeviceBindings = () => {
return bindings
}
+/**
+ * Gets all state bindings that are globally available.
+ */
+const getStateBindings = () => {
+ const safeState = makePropSafe("state")
+ return getAllStateVariables().map(key => ({
+ type: "context",
+ runtimeBinding: `${safeState}.${makePropSafe(key)}`,
+ readableBinding: `State.${key}`,
+ }))
+}
+
/**
* Gets all bindable properties from URL parameters.
*/
@@ -458,3 +476,49 @@ export function runtimeToReadableBinding(bindableProperties, textWithBindings) {
"readableBinding"
)
}
+
+/**
+ * Returns an array of the keys of any state variables which are set anywhere
+ * in the app.
+ */
+export const getAllStateVariables = () => {
+ let allComponents = []
+
+ // Find all onClick settings in all layouts
+ get(store).layouts.forEach(layout => {
+ const components = findAllMatchingComponents(
+ layout.props,
+ c => c.onClick != null
+ )
+ allComponents = allComponents.concat(components || [])
+ })
+
+ // Find all onClick settings in all screens
+ get(store).screens.forEach(screen => {
+ const components = findAllMatchingComponents(
+ screen.props,
+ c => c.onClick != null
+ )
+ allComponents = allComponents.concat(components || [])
+ })
+
+ // Add state bindings for all state actions
+ let bindingSet = new Set()
+ allComponents.forEach(component => {
+ if (!Array.isArray(component.onClick)) {
+ return
+ }
+ component.onClick.forEach(action => {
+ if (
+ action["##eventHandlerType"] === "Update State" &&
+ action.parameters?.type === "set" &&
+ action.parameters?.key &&
+ action.parameters?.value
+ ) {
+ bindingSet.add(action.parameters.key)
+ }
+ })
+ })
+
+ return Array.from(bindingSet)
+}
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte
new file mode 100644
index 0000000000..6c2867d4bf
--- /dev/null
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/UpdateState.svelte
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+ {#if parameters.type === "set"}
+
+ (parameters.value = e.detail)}
+ />
+
+
+
+
+ Persisted values will remain even after reloading the page or closing the
+ browser.
+
+ {/if}
+
+
+
diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js
index 9cf2461b77..6c285939ac 100644
--- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js
+++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/EventsEditor/actions/index.js
@@ -8,6 +8,7 @@ import LogOut from "./LogOut.svelte"
import ClearForm from "./ClearForm.svelte"
import CloseScreenModal from "./CloseScreenModal.svelte"
import ChangeFormStep from "./ChangeFormStep.svelte"
+import UpdateStateStep from "./UpdateState.svelte"
// Defines which actions are available to configure in the front end.
// Unfortunately the "name" property is used as the identifier so please don't
@@ -57,4 +58,8 @@ export default [
name: "Change Form Step",
component: ChangeFormStep,
},
+ {
+ name: "Update State",
+ component: UpdateStateStep,
+ },
]
diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte
index e0464764f3..cdc39ac225 100644
--- a/packages/client/src/components/ClientApp.svelte
+++ b/packages/client/src/components/ClientApp.svelte
@@ -18,6 +18,7 @@
import PeekScreenDisplay from "components/overlay/PeekScreenDisplay.svelte"
import UserBindingsProvider from "components/context/UserBindingsProvider.svelte"
import DeviceBindingsProvider from "components/context/DeviceBindingsProvider.svelte"
+ import StateBindingsProvider from "components/context/StateBindingsProvider.svelte"
import SettingsBar from "components/preview/SettingsBar.svelte"
import SelectionIndicator from "components/preview/SelectionIndicator.svelte"
import HoverIndicator from "components/preview/HoverIndicator.svelte"
@@ -85,28 +86,30 @@
{:else if $screenStore.activeLayout}
-