1
0
Fork 0
mirror of synced 2024-10-03 02:27:06 +13:00

Add event context to button actions to allow passing in params at run time and add corresponding data bindings

This commit is contained in:
Andrew Kingston 2022-03-15 11:16:51 +00:00
parent 4d01063383
commit 9500203515
10 changed files with 344 additions and 207 deletions

View file

@ -393,18 +393,45 @@ const getUrlBindings = asset => {
/** /**
* Gets all bindable properties exposed in a button actions flow up until * Gets all bindable properties exposed in a button actions flow up until
* the specified action ID. * the specified action ID, as well as context provided for the action
* setting as a whole by the component.
*/ */
export const getButtonContextBindings = (actions, actionId) => { export const getButtonContextBindings = (
asset,
componentId,
settingKey,
actions,
actionId
) => {
let bindings = []
// Check if any context bindings are provided by the component for this
// setting
const component = findComponent(asset.props, componentId)
const settings = getComponentSettings(component?._component)
const eventSetting = settings.find(setting => setting.key === settingKey)
if (!eventSetting) {
return bindings
}
if (eventSetting.context?.length) {
eventSetting.context.forEach(contextEntry => {
bindings.push({
readableBinding: contextEntry.label,
runtimeBinding: `${makePropSafe("eventContext")}.${makePropSafe(
contextEntry.key
)}`,
})
})
}
// Get the steps leading up to this value // Get the steps leading up to this value
const index = actions?.findIndex(action => action.id === actionId) const index = actions?.findIndex(action => action.id === actionId)
if (index == null || index === -1) { if (index == null || index === -1) {
return [] return bindings
} }
const prevActions = actions.slice(0, index) const prevActions = actions.slice(0, index)
// Generate bindings for any steps which provide context // Generate bindings for any steps which provide context
let bindings = []
prevActions.forEach((action, idx) => { prevActions.forEach((action, idx) => {
const def = ActionDefinitions.actions.find( const def = ActionDefinitions.actions.find(
x => x.name === action["##eventHandlerType"] x => x.name === action["##eventHandlerType"]
@ -418,6 +445,7 @@ export const getButtonContextBindings = (actions, actionId) => {
}) })
} }
}) })
return bindings return bindings
} }

View file

@ -2,9 +2,10 @@ import { createLocalStorageStore } from "@budibase/frontend-core"
export const getThemeStore = () => { export const getThemeStore = () => {
const themeElement = document.documentElement const themeElement = document.documentElement
const initialValue = { const initialValue = {
theme: "darkest", theme: "darkest",
options: ["lightest", "light", "dark", "darkest"], options: ["lightest", "light", "dark", "darkest", "nord"],
} }
const store = createLocalStorageStore("bb-theme", initialValue) const store = createLocalStorageStore("bb-theme", initialValue)
@ -21,6 +22,7 @@ export const getThemeStore = () => {
`spectrum--${option}`, `spectrum--${option}`,
option === state.theme option === state.theme
) )
themeElement.classList.add("spectrum--darkest")
}) })
}) })

View file

@ -12,11 +12,13 @@
import { getAvailableActions } from "./index" import { getAvailableActions } from "./index"
import { generate } from "shortid" import { generate } from "shortid"
import { getButtonContextBindings } from "builderStore/dataBinding" import { getButtonContextBindings } from "builderStore/dataBinding"
import { currentAsset, store } from "builderStore"
const flipDurationMs = 150 const flipDurationMs = 150
const EVENT_TYPE_KEY = "##eventHandlerType" const EVENT_TYPE_KEY = "##eventHandlerType"
const actionTypes = getAvailableActions() const actionTypes = getAvailableActions()
export let key
export let actions export let actions
export let bindings = [] export let bindings = []
@ -24,6 +26,9 @@
// These are ephemeral bindings which only exist while executing actions // These are ephemeral bindings which only exist while executing actions
$: buttonContextBindings = getButtonContextBindings( $: buttonContextBindings = getButtonContextBindings(
$currentAsset,
$store.selectedComponentId,
key,
actions, actions,
selectedAction?.id selectedAction?.id
) )

View file

@ -8,6 +8,7 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
export let key
export let value = [] export let value = []
export let name export let name
export let bindings export let bindings
@ -81,5 +82,6 @@
bind:actions={tmpValue} bind:actions={tmpValue}
eventType={name} eventType={name}
{bindings} {bindings}
{key}
/> />
</Drawer> </Drawer>

View file

@ -79,6 +79,7 @@
bindings={allBindings} bindings={allBindings}
name={key} name={key}
text={label} text={label}
{key}
{type} {type}
{...props} {...props}
/> />

View file

@ -264,7 +264,8 @@
{ {
"label": "Primary", "label": "Primary",
"value": "primary" "value": "primary"
}, { },
{
"label": "Secondary", "label": "Secondary",
"value": "secondary" "value": "secondary"
}, },
@ -626,28 +627,36 @@
"defaultValue": "M", "defaultValue": "M",
"showInBar": true, "showInBar": true,
"barStyle": "picker", "barStyle": "picker",
"options": [{ "options": [
{
"label": "Extra Small", "label": "Extra Small",
"value": "XS" "value": "XS"
}, { },
{
"label": "Small", "label": "Small",
"value": "S" "value": "S"
}, { },
{
"label": "Medium", "label": "Medium",
"value": "M" "value": "M"
}, { },
{
"label": "Large", "label": "Large",
"value": "L" "value": "L"
}, { },
{
"label": "Extra Large", "label": "Extra Large",
"value": "XL" "value": "XL"
}, { },
{
"label": "2XL", "label": "2XL",
"value": "XXL" "value": "XXL"
}, { },
{
"label": "3XL", "label": "3XL",
"value": "XXXL" "value": "XXXL"
}] }
]
}, },
{ {
"type": "color", "type": "color",
@ -689,27 +698,32 @@
"defaultValue": "left", "defaultValue": "left",
"showInBar": true, "showInBar": true,
"barStyle": "buttons", "barStyle": "buttons",
"options": [{ "options": [
{
"label": "Left", "label": "Left",
"value": "left", "value": "left",
"barIcon": "TextAlignLeft", "barIcon": "TextAlignLeft",
"barTitle": "Align left" "barTitle": "Align left"
}, { },
{
"label": "Center", "label": "Center",
"value": "center", "value": "center",
"barIcon": "TextAlignCenter", "barIcon": "TextAlignCenter",
"barTitle": "Align center" "barTitle": "Align center"
}, { },
{
"label": "Right", "label": "Right",
"value": "right", "value": "right",
"barIcon": "TextAlignRight", "barIcon": "TextAlignRight",
"barTitle": "Align right" "barTitle": "Align right"
}, { },
{
"label": "Justify", "label": "Justify",
"value": "justify", "value": "justify",
"barIcon": "TextAlignJustify", "barIcon": "TextAlignJustify",
"barTitle": "Justify text" "barTitle": "Justify text"
}] }
]
} }
] ]
}, },
@ -733,28 +747,36 @@
"defaultValue": "M", "defaultValue": "M",
"showInBar": true, "showInBar": true,
"barStyle": "picker", "barStyle": "picker",
"options": [{ "options": [
{
"label": "Extra Small", "label": "Extra Small",
"value": "XS" "value": "XS"
}, { },
{
"label": "Small", "label": "Small",
"value": "S" "value": "S"
}, { },
{
"label": "Medium", "label": "Medium",
"value": "M" "value": "M"
}, { },
{
"label": "Large", "label": "Large",
"value": "L" "value": "L"
}, { },
{
"label": "Extra Large", "label": "Extra Large",
"value": "XL" "value": "XL"
}, { },
{
"label": "2XL", "label": "2XL",
"value": "XXL" "value": "XXL"
}, { },
{
"label": "3XL", "label": "3XL",
"value": "XXXL" "value": "XXXL"
}] }
]
}, },
{ {
"type": "color", "type": "color",
@ -796,27 +818,32 @@
"defaultValue": "left", "defaultValue": "left",
"showInBar": true, "showInBar": true,
"barStyle": "buttons", "barStyle": "buttons",
"options": [{ "options": [
{
"label": "Left", "label": "Left",
"value": "left", "value": "left",
"barIcon": "TextAlignLeft", "barIcon": "TextAlignLeft",
"barTitle": "Align left" "barTitle": "Align left"
}, { },
{
"label": "Center", "label": "Center",
"value": "center", "value": "center",
"barIcon": "TextAlignCenter", "barIcon": "TextAlignCenter",
"barTitle": "Align center" "barTitle": "Align center"
}, { },
{
"label": "Right", "label": "Right",
"value": "right", "value": "right",
"barIcon": "TextAlignRight", "barIcon": "TextAlignRight",
"barTitle": "Align right" "barTitle": "Align right"
}, { },
{
"label": "Justify", "label": "Justify",
"value": "justify", "value": "justify",
"barIcon": "TextAlignJustify", "barIcon": "TextAlignJustify",
"barTitle": "Justify text" "barTitle": "Justify text"
}] }
]
} }
] ]
}, },
@ -837,16 +864,20 @@
"defaultValue": "M", "defaultValue": "M",
"showInBar": true, "showInBar": true,
"barStyle": "picker", "barStyle": "picker",
"options": [{ "options": [
{
"label": "Small", "label": "Small",
"value": "S" "value": "S"
}, { },
{
"label": "Medium", "label": "Medium",
"value": "M" "value": "M"
}, { },
{
"label": "Large", "label": "Large",
"value": "L" "value": "L"
}] }
]
}, },
{ {
"type": "color", "type": "color",
@ -1037,16 +1068,20 @@
"defaultValue": "M", "defaultValue": "M",
"showInBar": true, "showInBar": true,
"barStyle": "picker", "barStyle": "picker",
"options": [{ "options": [
{
"label": "Small", "label": "Small",
"value": "S" "value": "S"
}, { },
{
"label": "Medium", "label": "Medium",
"value": "M" "value": "M"
}, { },
{
"label": "Large", "label": "Large",
"value": "L" "value": "L"
}] }
]
}, },
{ {
"type": "color", "type": "color",
@ -1088,27 +1123,32 @@
"defaultValue": "left", "defaultValue": "left",
"showInBar": true, "showInBar": true,
"barStyle": "buttons", "barStyle": "buttons",
"options": [{ "options": [
{
"label": "Left", "label": "Left",
"value": "left", "value": "left",
"barIcon": "TextAlignLeft", "barIcon": "TextAlignLeft",
"barTitle": "Align left" "barTitle": "Align left"
}, { },
{
"label": "Center", "label": "Center",
"value": "center", "value": "center",
"barIcon": "TextAlignCenter", "barIcon": "TextAlignCenter",
"barTitle": "Align center" "barTitle": "Align center"
}, { },
{
"label": "Right", "label": "Right",
"value": "right", "value": "right",
"barIcon": "TextAlignRight", "barIcon": "TextAlignRight",
"barTitle": "Align right" "barTitle": "Align right"
}, { },
{
"label": "Justify", "label": "Justify",
"value": "justify", "value": "justify",
"barIcon": "TextAlignJustify", "barIcon": "TextAlignJustify",
"barTitle": "Justify text" "barTitle": "Justify text"
}] }
]
} }
] ]
}, },
@ -1165,7 +1205,15 @@
"type": "select", "type": "select",
"label": "Card Width", "label": "Card Width",
"key": "cardWidth", "key": "cardWidth",
"options": ["24rem", "28rem", "32rem", "40rem", "48rem", "60rem", "100%"], "options": [
"24rem",
"28rem",
"32rem",
"40rem",
"48rem",
"60rem",
"100%"
],
"defaultValue": "32rem" "defaultValue": "32rem"
}, },
{ {
@ -1785,11 +1833,7 @@
"icon": "Form", "icon": "Form",
"hasChildren": true, "hasChildren": true,
"illegalChildren": ["section", "form"], "illegalChildren": ["section", "form"],
"actions": [ "actions": ["ValidateForm", "ClearForm", "ChangeFormStep"],
"ValidateForm",
"ClearForm",
"ChangeFormStep"
],
"styles": ["size"], "styles": ["size"],
"settings": [ "settings": [
{ {
@ -1816,7 +1860,8 @@
{ {
"label": "Medium", "label": "Medium",
"value": "spectrum--medium" "value": "spectrum--medium"
}, { },
{
"label": "Large", "label": "Large",
"value": "spectrum--large" "value": "spectrum--large"
} }
@ -1947,27 +1992,32 @@
"defaultValue": "left", "defaultValue": "left",
"showInBar": true, "showInBar": true,
"barStyle": "buttons", "barStyle": "buttons",
"options": [{ "options": [
{
"label": "Left", "label": "Left",
"value": "left", "value": "left",
"barIcon": "TextAlignLeft", "barIcon": "TextAlignLeft",
"barTitle": "Align left" "barTitle": "Align left"
}, { },
{
"label": "Center", "label": "Center",
"value": "center", "value": "center",
"barIcon": "TextAlignCenter", "barIcon": "TextAlignCenter",
"barTitle": "Align center" "barTitle": "Align center"
}, { },
{
"label": "Right", "label": "Right",
"value": "right", "value": "right",
"barIcon": "TextAlignRight", "barIcon": "TextAlignRight",
"barTitle": "Align right" "barTitle": "Align right"
}, { },
{
"label": "Justify", "label": "Justify",
"value": "justify", "value": "justify",
"barIcon": "TextAlignJustify", "barIcon": "TextAlignJustify",
"barTitle": "Justify text" "barTitle": "Justify text"
}] }
]
} }
] ]
}, },
@ -2480,15 +2530,42 @@
"styles": ["size"], "styles": ["size"],
"editable": true, "editable": true,
"draggable": false, "draggable": false,
"illegalChildren": [ "illegalChildren": ["section"],
"section"
],
"settings": [ "settings": [
{ {
"type": "dataProvider", "type": "dataProvider",
"label": "Provider", "label": "Provider",
"key": "dataProvider" "key": "dataProvider"
}, },
{
"type": "field",
"label": "Latitude Key",
"key": "latitudeKey",
"dependsOn": "dataProvider"
},
{
"type": "field",
"label": "Longitude Key",
"key": "longitudeKey",
"dependsOn": "dataProvider"
},
{
"type": "field",
"label": "Title Key",
"key": "titleKey",
"dependsOn": "dataProvider"
},
{
"type": "event",
"label": "On marker click",
"key": "onMarkerClick",
"context": [
{
"label": "Clicked marker",
"key": "marker"
}
]
},
{ {
"type": "boolean", "type": "boolean",
"label": "Enable Fullscreen", "label": "Enable Fullscreen",
@ -2515,21 +2592,6 @@
"max": 100, "max": 100,
"min": 0 "min": 0
}, },
{
"type": "field",
"label": "Latitude Key",
"key": "latitudeKey"
},
{
"type": "field",
"label": "Longitude Key",
"key": "longitudeKey"
},
{
"type": "field",
"label": "Title Key",
"key": "titleKey"
},
{ {
"type": "text", "type": "text",
"label": "Tile URL", "label": "Tile URL",
@ -2538,7 +2600,7 @@
}, },
{ {
"type": "text", "type": "text",
"label": "Default Location (lat,lng)", "label": "Default Location",
"key": "defaultLocation", "key": "defaultLocation",
"defaultValue": "51.5072,-0.1276" "defaultValue": "51.5072,-0.1276"
}, },
@ -3192,7 +3254,6 @@
"key": "cardDescription", "key": "cardDescription",
"label": "Description", "label": "Description",
"nested": true "nested": true
}, },
{ {
"type": "text", "type": "text",

View file

@ -1,6 +1,7 @@
<script context="module"> <script context="module">
// Cache the definition of settings for each component type // Cache the definition of settings for each component type
let SettingsDefinitionCache = {} let SettingsDefinitionCache = {}
let SettingsDefinitionMapCache = {}
// Cache the settings of each component ID. // Cache the settings of each component ID.
// This speeds up remounting as well as repeaters. // This speeds up remounting as well as repeaters.
@ -74,6 +75,8 @@
// Component information derived during initialisation // Component information derived during initialisation
let constructor let constructor
let definition let definition
let settingsDefinition
let settingsDefinitionMap
// Set up initial state for each new component instance // Set up initial state for each new component instance
$: initialise(instance) $: initialise(instance)
@ -118,7 +121,7 @@
$: emptyState = empty && showEmptyState $: emptyState = empty && showEmptyState
// Enrich component settings // Enrich component settings
$: enrichComponentSettings($context) $: enrichComponentSettings($context, settingsDefinitionMap)
// Evaluate conditional UI settings and store any component setting changes // Evaluate conditional UI settings and store any component setting changes
// which need to be made. This is broken into 2 lines to avoid svelte // which need to be made. This is broken into 2 lines to avoid svelte
@ -168,12 +171,14 @@
} }
// Get the settings definition for this component, and cache it // Get the settings definition for this component, and cache it
let settingsDefinition
if (SettingsDefinitionCache[definition.name]) { if (SettingsDefinitionCache[definition.name]) {
settingsDefinition = SettingsDefinitionCache[definition.name] settingsDefinition = SettingsDefinitionCache[definition.name]
settingsDefinitionMap = SettingsDefinitionMapCache[definition.name]
} else { } else {
settingsDefinition = getSettingsDefinition(definition) settingsDefinition = getSettingsDefinition(definition)
settingsDefinitionMap = getSettingsDefinitionMap(settingsDefinition)
SettingsDefinitionCache[definition.name] = settingsDefinition SettingsDefinitionCache[definition.name] = settingsDefinition
SettingsDefinitionMapCache[definition.name] = settingsDefinitionMap
} }
// Parse the instance settings, and cache them // Parse the instance settings, and cache them
@ -190,7 +195,9 @@
dynamicSettings = instanceSettings.dynamicSettings dynamicSettings = instanceSettings.dynamicSettings
// Force an initial enrichment of the new settings // Force an initial enrichment of the new settings
enrichComponentSettings(get(context), { force: true }) enrichComponentSettings(get(context), settingsDefinitionMap, {
force: true,
})
} }
// Gets the component constructor for the specified component // Gets the component constructor for the specified component
@ -226,6 +233,14 @@
return settings return settings
} }
const getSettingsDefinitionMap = settingsDefinition => {
let map = {}
settingsDefinition?.forEach(setting => {
map[setting.key] = setting
})
return map
}
const getInstanceSettings = (instance, settingsDefinition) => { const getInstanceSettings = (instance, settingsDefinition) => {
// Get raw settings // Get raw settings
let settings = {} let settings = {}
@ -248,7 +263,7 @@
} else if (typeof value === "string" && value.includes("{{")) { } else if (typeof value === "string" && value.includes("{{")) {
// Strings can be trivially checked // Strings can be trivially checked
delete newStaticSettings[setting.key] delete newStaticSettings[setting.key]
} else if (value[0]?.["##eventHandlerType"] != null) { } else if (setting.type === "event") {
// Always treat button actions as dynamic // Always treat button actions as dynamic
delete newStaticSettings[setting.key] delete newStaticSettings[setting.key]
} else if (typeof value === "object") { } else if (typeof value === "object") {
@ -273,7 +288,11 @@
} }
// Enriches any string component props using handlebars // Enriches any string component props using handlebars
const enrichComponentSettings = (context, options = { force: false }) => { const enrichComponentSettings = (
context,
settingsDefinitionMap,
options = { force: false }
) => {
const contextChanged = context.key !== lastContextKey const contextChanged = context.key !== lastContextKey
if (!contextChanged && !options?.force) { if (!contextChanged && !options?.force) {
return return
@ -285,7 +304,11 @@
const enrichmentTime = latestUpdateTime const enrichmentTime = latestUpdateTime
// Enrich settings with context // Enrich settings with context
const newEnrichedSettings = enrichProps(dynamicSettings, context) const newEnrichedSettings = enrichProps(
dynamicSettings,
context,
settingsDefinitionMap
)
// Abandon this update if a newer update has started // Abandon this update if a newer update has started
if (enrichmentTime !== latestUpdateTime) { if (enrichmentTime !== latestUpdateTime) {

View file

@ -24,6 +24,7 @@
export let defaultLocation export let defaultLocation
export let tileURL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" export let tileURL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
export let mapAttribution export let mapAttribution
export let onMarkerClick
const { styleable, notificationStore } = getContext("sdk") const { styleable, notificationStore } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
@ -55,7 +56,8 @@
dataProvider?.rows, dataProvider?.rows,
latitudeKey, latitudeKey,
longitudeKey, longitudeKey,
titleKey titleKey,
onMarkerClick
) )
$: if (typeof mapInstance === "object" && mapMarkers.length > 0) { $: if (typeof mapInstance === "object" && mapMarkers.length > 0) {
mapInstance.setZoom(0) mapInstance.setZoom(0)
@ -195,7 +197,7 @@
maxZoomLevel, maxZoomLevel,
} }
const addMapMarkers = (mapInstance, rows, latKey, lngKey, titleKey) => { const addMapMarkers = (mapInstance, rows, latKey, lngKey, titleKey, onClick) => {
if (typeof mapInstance !== "object" || !rows || !latKey || !lngKey) { if (typeof mapInstance !== "object" || !rows || !latKey || !lngKey) {
return return
} }
@ -220,6 +222,15 @@
direction: "top", direction: "top",
offset: [0, -25] offset: [0, -25]
}).addTo(mapMarkerGroup) }).addTo(mapMarkerGroup)
if (onClick) {
marker.on("click", () => {
onClick({
marker: row
})
})
}
mapMarkers = [...mapMarkers, marker] mapMarkers = [...mapMarkers, marker]
}) })
} }

View file

@ -308,7 +308,7 @@ export const enrichButtonActions = (actions, context) => {
let buttonContext = context.actions || [] let buttonContext = context.actions || []
const handlers = actions.map(def => handlerMap[def["##eventHandlerType"]]) const handlers = actions.map(def => handlerMap[def["##eventHandlerType"]])
return async () => { return async eventContext => {
for (let i = 0; i < handlers.length; i++) { for (let i = 0; i < handlers.length; i++) {
try { try {
// Skip any non-existent action definitions // Skip any non-existent action definitions
@ -317,7 +317,11 @@ export const enrichButtonActions = (actions, context) => {
} }
// Built total context for this action // Built total context for this action
const totalContext = { ...context, actions: buttonContext } const totalContext = {
...context,
actions: buttonContext,
eventContext,
}
// Get and enrich this button action with the total context // Get and enrich this button action with the total context
let action = actions[i] let action = actions[i]

View file

@ -22,7 +22,7 @@ export const propsAreSame = (a, b) => {
* Enriches component props. * Enriches component props.
* Data bindings are enriched, and button actions are enriched. * Data bindings are enriched, and button actions are enriched.
*/ */
export const enrichProps = (props, context) => { export const enrichProps = (props, context, settingsDefinitionMap) => {
// Create context of all bindings and data contexts // Create context of all bindings and data contexts
// Duplicate the closest context as "data" which the builder requires // Duplicate the closest context as "data" which the builder requires
const totalContext = { const totalContext = {
@ -38,7 +38,7 @@ export const enrichProps = (props, context) => {
let normalProps = { ...props } let normalProps = { ...props }
let actionProps = {} let actionProps = {}
Object.keys(normalProps).forEach(prop => { Object.keys(normalProps).forEach(prop => {
if (prop?.toLowerCase().includes("onclick")) { if (settingsDefinitionMap?.[prop]?.type === "event") {
actionProps[prop] = normalProps[prop] actionProps[prop] = normalProps[prop]
delete normalProps[prop] delete normalProps[prop]
} }
@ -61,7 +61,7 @@ export const enrichProps = (props, context) => {
// Conditions // Conditions
if (enrichedProps._conditions?.length) { if (enrichedProps._conditions?.length) {
enrichedProps._conditions.forEach((condition, idx) => { enrichedProps._conditions.forEach((condition, idx) => {
if (condition.setting?.toLowerCase().includes("onclick")) { if (settingsDefinitionMap?.[condition.setting]?.type === "event") {
// Use the original condition action value to enrich it to a button // Use the original condition action value to enrich it to a button
// action // action
condition.settingValue = enrichButtonActions( condition.settingValue = enrichButtonActions(