110 lines
3.4 KiB
JavaScript
110 lines
3.4 KiB
JavaScript
import { enrichDataBindings } from "./enrichDataBinding"
|
|
import { enrichButtonActions } from "./buttonActions"
|
|
import { decodeJSBinding } from "@budibase/string-templates"
|
|
|
|
/**
|
|
* Deeply compares 2 props using JSON.stringify.
|
|
* Does not consider functions, as currently only button actions have a function
|
|
* prop and it's cheaper to just always re-render buttons than it is to deeply
|
|
* compare them.
|
|
*/
|
|
export const propsAreSame = (a, b) => {
|
|
if (a === b) {
|
|
return true
|
|
}
|
|
if (typeof a === "function" || typeof b === "function") {
|
|
return false
|
|
}
|
|
return JSON.stringify(a) === JSON.stringify(b)
|
|
}
|
|
|
|
/**
|
|
* Enriches component props.
|
|
* Data bindings are enriched, and button actions are enriched.
|
|
*/
|
|
export const enrichProps = (props, context) => {
|
|
// Create context of all bindings and data contexts
|
|
// Duplicate the closest context as "data" which the builder requires
|
|
const totalContext = {
|
|
...context,
|
|
|
|
// This is only required for legacy bindings that used "data" rather than a
|
|
// component ID.
|
|
data: context[context.closestComponentId],
|
|
}
|
|
|
|
// We want to exclude any button actions from enrichment at this stage.
|
|
// Extract top level button action settings.
|
|
let normalProps = { ...props }
|
|
let actionProps = {}
|
|
Object.keys(normalProps).forEach(prop => {
|
|
if (prop?.toLowerCase().includes("onclick")) {
|
|
actionProps[prop] = normalProps[prop]
|
|
delete normalProps[prop]
|
|
}
|
|
})
|
|
|
|
// Store the original conditions so that we can restore parts of them after
|
|
// enrichment
|
|
let rawConditions = normalProps._conditions
|
|
|
|
// Enrich all props except button actions
|
|
let enrichedProps = enrichDataBindings(normalProps, totalContext)
|
|
|
|
// Enrich button actions.
|
|
// Actions are enriched into a function at this stage, but actual data
|
|
// binding enrichment is done dynamically at runtime.
|
|
Object.keys(actionProps).forEach(prop => {
|
|
enrichedProps[prop] = enrichButtonActions(actionProps[prop], totalContext)
|
|
})
|
|
|
|
// Conditions
|
|
if (enrichedProps._conditions?.length) {
|
|
enrichedProps._conditions.forEach((condition, idx) => {
|
|
if (condition.setting?.toLowerCase().includes("onclick")) {
|
|
// Use the original condition action value to enrich it to a button
|
|
// action
|
|
condition.settingValue = enrichButtonActions(
|
|
rawConditions[idx].settingValue,
|
|
totalContext
|
|
)
|
|
|
|
// Since we can't compare functions, we need to assume that conditions
|
|
// change after every enrichment
|
|
condition.rand = Math.random()
|
|
}
|
|
})
|
|
}
|
|
|
|
return enrichedProps
|
|
}
|
|
|
|
/**
|
|
* Checks if a props object references a particular context binding.
|
|
* e.g. if props are { foo: "My name is {{ person.name }}" }, and we search for" +
|
|
* "person", then this function wil return true - the props do a context key
|
|
* called "person".
|
|
* @param props the props object to search
|
|
* @param bindingKey the key to search for
|
|
*/
|
|
export const propsUseBinding = (props, bindingKey) => {
|
|
if (!Object.keys(props || {}).length) {
|
|
return false
|
|
}
|
|
const string = JSON.stringify(props)
|
|
const usedInHBS = string.includes(`[${bindingKey}]`)
|
|
if (usedInHBS) {
|
|
return true
|
|
}
|
|
const jsBindingRegex = new RegExp("{{ js [^}]+ }}", "g")
|
|
const jsBindings = [...string.matchAll(jsBindingRegex)]
|
|
for (let jsBinding of jsBindings) {
|
|
const encoded = jsBinding[0]
|
|
const js = decodeJSBinding(encoded)
|
|
if (js?.includes(`$("[${bindingKey}]`)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|