commit
416a888ccc
|
@ -4,13 +4,11 @@ import {
|
|||
BB_STATE_BINDINGPATH,
|
||||
BB_STATE_FALLBACK,
|
||||
BB_STATE_BINDINGSOURCE,
|
||||
} from "@budibase/client/src/state/isState"
|
||||
isBound,
|
||||
parseBinding,
|
||||
} from "@budibase/client/src/state/parseBinding"
|
||||
|
||||
export const isBinding = value =>
|
||||
!isString(value) &&
|
||||
value &&
|
||||
isString(value[BB_STATE_BINDINGPATH]) &&
|
||||
value[BB_STATE_BINDINGPATH].length > 0
|
||||
export const isBinding = isBound
|
||||
|
||||
export const setBinding = ({ path, fallback, source }, binding = {}) => {
|
||||
if (isNonEmptyString(path)) binding[BB_STATE_BINDINGPATH] = path
|
||||
|
@ -19,10 +17,15 @@ export const setBinding = ({ path, fallback, source }, binding = {}) => {
|
|||
return binding
|
||||
}
|
||||
|
||||
export const getBinding = binding => ({
|
||||
path: binding[BB_STATE_BINDINGPATH] || "",
|
||||
fallback: binding[BB_STATE_FALLBACK] || "",
|
||||
source: binding[BB_STATE_BINDINGSOURCE] || "store",
|
||||
})
|
||||
export const getBinding = val => {
|
||||
const binding = parseBinding(val)
|
||||
return binding
|
||||
? binding
|
||||
: {
|
||||
path: "",
|
||||
source: "store",
|
||||
fallback: "",
|
||||
}
|
||||
}
|
||||
|
||||
const isNonEmptyString = s => isString(s) && s.length > 0
|
||||
|
|
|
@ -14,7 +14,7 @@ import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"
|
|||
import {
|
||||
isBound,
|
||||
BB_STATE_BINDINGPATH,
|
||||
} from "@budibase/client/src/state/isState"
|
||||
} from "@budibase/client/src/state/parseBinding"
|
||||
|
||||
const defaultDef = typeName => () => ({
|
||||
type: typeName,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createProps } from "../src/userInterface/pagesParsing/createProps"
|
||||
import { keys, some } from "lodash/fp"
|
||||
import { BB_STATE_BINDINGPATH } from "@budibase/client/src/state/isState"
|
||||
import { BB_STATE_BINDINGPATH } from "@budibase/client/src/state/parseBinding"
|
||||
import { stripStandardProps } from "./testData"
|
||||
|
||||
describe("createDefaultProps", () => {
|
||||
|
|
|
@ -31,6 +31,7 @@ export const createApp = (
|
|||
componentLibraries,
|
||||
uiFunctions,
|
||||
onScreenSlotRendered: () => {},
|
||||
routeTo,
|
||||
})
|
||||
const getAttchChildrenParams = attachChildrenParams(stateManager)
|
||||
screenSlotNode.props._children = [screen.props]
|
||||
|
@ -44,7 +45,11 @@ export const createApp = (
|
|||
currentUrl = url
|
||||
}
|
||||
|
||||
routeTo = screenRouter(frontendDefinition.screens, onScreenSelected)
|
||||
routeTo = screenRouter(
|
||||
frontendDefinition.screens,
|
||||
onScreenSelected,
|
||||
frontendDefinition.appRootPath
|
||||
)
|
||||
routeTo(currentUrl || window.location.pathname)
|
||||
}
|
||||
|
||||
|
@ -69,6 +74,8 @@ export const createApp = (
|
|||
componentLibraries,
|
||||
uiFunctions,
|
||||
onScreenSlotRendered,
|
||||
// seems weird, but the routeTo variable may not be available at this point
|
||||
routeTo: url => routeTo(url),
|
||||
})
|
||||
|
||||
const initialisePage = (page, target, urlPath) => {
|
||||
|
|
|
@ -23,14 +23,14 @@ export const loadBudibase = async (opts) => {
|
|||
temp: false,
|
||||
}
|
||||
|
||||
const rootPath =
|
||||
frontendDefinition.appRootPath =
|
||||
frontendDefinition.appRootPath === ""
|
||||
? ""
|
||||
: "/" + trimSlash(frontendDefinition.appRootPath)
|
||||
|
||||
if (!componentLibraries) {
|
||||
|
||||
const componentLibraryUrl = lib => rootPath + "/" + trimSlash(lib)
|
||||
const componentLibraryUrl = lib => frontendDefinition.appRootPath + "/" + trimSlash(lib)
|
||||
componentLibraries = {}
|
||||
|
||||
for (let lib of frontendDefinition.componentLibraries) {
|
||||
|
@ -52,7 +52,7 @@ export const loadBudibase = async (opts) => {
|
|||
)
|
||||
|
||||
const route = _window.location
|
||||
? _window.location.pathname.replace(rootPath, "")
|
||||
? _window.location.pathname.replace(frontendDefinition.appRootPath, "")
|
||||
: "";
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import regexparam from "regexparam"
|
||||
import { writable } from "svelte/store"
|
||||
|
||||
export const screenRouter = (screens, onScreenSelected) => {
|
||||
const routes = screens.map(s => s.route)
|
||||
export const screenRouter = (screens, onScreenSelected, appRootPath) => {
|
||||
const makeRootedPath = url => (appRootPath ? `${appRootPath}/${url}` : url)
|
||||
|
||||
const routes = screens.map(s => makeRootedPath(s.route))
|
||||
let fallback = routes.findIndex(([p]) => p === "*")
|
||||
if (fallback < 0) fallback = 0
|
||||
|
||||
let current
|
||||
|
||||
function route(url) {
|
||||
|
||||
const _url = url.state || url
|
||||
const _url = makeRootedPath(url.state || url)
|
||||
current = routes.findIndex(
|
||||
p => p !== "*" && new RegExp("^" + p + "$").test(_url)
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { getStateOrValue } from "./getState"
|
||||
import { setState, setStateFromBinding } from "./setState"
|
||||
import { trimSlash } from "../common/trimSlash"
|
||||
import { isBound } from "./isState"
|
||||
import { isBound } from "./parseBinding"
|
||||
import { attachChildren } from "../render/attachChildren"
|
||||
|
||||
export const bbFactory = ({
|
||||
|
|
|
@ -7,7 +7,7 @@ import { getNewChildRecordToState, getNewRecordToState } from "./coreHandlers"
|
|||
|
||||
export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"
|
||||
|
||||
export const eventHandlers = (store, coreApi, rootPath) => {
|
||||
export const eventHandlers = (store, coreApi, rootPath, routeTo) => {
|
||||
const handler = (parameters, execute) => ({
|
||||
execute,
|
||||
parameters,
|
||||
|
@ -44,6 +44,8 @@ export const eventHandlers = (store, coreApi, rootPath) => {
|
|||
getNewRecordToState(coreApi, setStateWithStore)
|
||||
),
|
||||
|
||||
"Navigate To": handler(["url"], param => routeTo(param && param.url)),
|
||||
|
||||
Authenticate: handler(["username", "password"], api.authenticate),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import { isUndefined, isObject } from "lodash/fp"
|
||||
import {
|
||||
isBound,
|
||||
BB_STATE_BINDINGPATH,
|
||||
BB_STATE_FALLBACK,
|
||||
takeStateFromStore,
|
||||
} from "./isState"
|
||||
import { parseBinding, isStoreBinding } from "./parseBinding"
|
||||
|
||||
export const getState = (s, path, fallback) => {
|
||||
if (!s) return fallback
|
||||
|
@ -39,20 +34,12 @@ export const getState = (s, path, fallback) => {
|
|||
export const getStateOrValue = (globalState, prop, currentContext) => {
|
||||
if (!prop) return prop
|
||||
|
||||
if (isBound(prop)) {
|
||||
const stateToUse = takeStateFromStore(prop) ? globalState : currentContext
|
||||
const binding = parseBinding(prop)
|
||||
|
||||
return getState(
|
||||
stateToUse,
|
||||
prop[BB_STATE_BINDINGPATH],
|
||||
prop[BB_STATE_FALLBACK]
|
||||
)
|
||||
}
|
||||
if (binding) {
|
||||
const stateToUse = isStoreBinding(binding) ? globalState : currentContext
|
||||
|
||||
if (prop.path && prop.source) {
|
||||
const stateToUse = prop.source === "store" ? globalState : currentContext
|
||||
|
||||
return getState(stateToUse, prop.path, prop.fallback)
|
||||
return getState(stateToUse, binding.path, binding.fallback)
|
||||
}
|
||||
|
||||
return prop
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
export const BB_STATE_BINDINGPATH = "##bbstate"
|
||||
export const BB_STATE_BINDINGSOURCE = "##bbsource"
|
||||
export const BB_STATE_FALLBACK = "##bbstatefallback"
|
||||
|
||||
export const isBound = prop =>
|
||||
prop !== undefined && prop[BB_STATE_BINDINGPATH] !== undefined
|
||||
|
||||
export const takeStateFromStore = prop =>
|
||||
prop[BB_STATE_BINDINGSOURCE] === undefined ||
|
||||
prop[BB_STATE_BINDINGSOURCE] === "store"
|
||||
|
||||
export const takeStateFromContext = prop =>
|
||||
prop[BB_STATE_BINDINGSOURCE] === "context"
|
||||
|
||||
export const takeStateFromEventParameters = prop =>
|
||||
prop[BB_STATE_BINDINGSOURCE] === "event"
|
|
@ -0,0 +1,59 @@
|
|||
export const BB_STATE_BINDINGPATH = "##bbstate"
|
||||
export const BB_STATE_BINDINGSOURCE = "##bbsource"
|
||||
export const BB_STATE_FALLBACK = "##bbstatefallback"
|
||||
|
||||
export const isBound = prop => !!parseBinding(prop)
|
||||
|
||||
export const parseBinding = prop => {
|
||||
if (!prop) return false
|
||||
if (isBindingExpression(prop)) {
|
||||
return parseBindingExpression(prop)
|
||||
}
|
||||
|
||||
if (isAlreadyBinding(prop)) {
|
||||
return {
|
||||
path: prop.path,
|
||||
source: prop.source || "store",
|
||||
fallback: prop.fallback,
|
||||
}
|
||||
}
|
||||
|
||||
if (hasBindingObject(prop)) {
|
||||
return {
|
||||
path: prop[BB_STATE_BINDINGPATH],
|
||||
fallback: prop[BB_STATE_FALLBACK] || "",
|
||||
source: prop[BB_STATE_BINDINGSOURCE] || "store",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const isStoreBinding = binding => binding && binding.source === "store"
|
||||
export const isContextBinding = binding => binding && binding.source === "context"
|
||||
export const isEventBinding = binding => binding && binding.source === "event"
|
||||
|
||||
const hasBindingObject = prop =>
|
||||
typeof prop === "object" && prop[BB_STATE_BINDINGPATH] !== undefined
|
||||
|
||||
const isAlreadyBinding = prop => typeof prop === "object" && prop.path
|
||||
|
||||
const isBindingExpression = prop =>
|
||||
typeof prop === "string" &&
|
||||
(prop.startsWith("store.") ||
|
||||
prop.startsWith("context.") ||
|
||||
prop.startsWith("event.") ||
|
||||
prop.startsWith("route."))
|
||||
|
||||
const parseBindingExpression = prop => {
|
||||
let source = prop.split(".")[0]
|
||||
let path = prop.replace(`${source}.`, "")
|
||||
if (source === "route") {
|
||||
source = "store"
|
||||
path = `##routeParams.${path}`
|
||||
}
|
||||
const fallback = ""
|
||||
return {
|
||||
fallback,
|
||||
source,
|
||||
path,
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { isObject } from "lodash/fp"
|
||||
import { BB_STATE_BINDINGPATH } from "./isState"
|
||||
import { parseBinding } from "./parseBinding"
|
||||
|
||||
export const setState = (store, path, value) => {
|
||||
if (!path || path.length === 0) return
|
||||
|
@ -30,5 +30,8 @@ export const setState = (store, path, value) => {
|
|||
})
|
||||
}
|
||||
|
||||
export const setStateFromBinding = (store, binding, value) =>
|
||||
setState(store, binding[BB_STATE_BINDINGPATH], value)
|
||||
export const setStateFromBinding = (store, binding, value) => {
|
||||
const parsedBinding = parseBinding(binding)
|
||||
if (!parsedBinding) return
|
||||
return setState(store, parsedBinding.path, value)
|
||||
}
|
||||
|
|
|
@ -1,179 +0,0 @@
|
|||
import {
|
||||
isEventType,
|
||||
eventHandlers,
|
||||
EVENT_TYPE_MEMBER_NAME,
|
||||
} from "./eventHandlers"
|
||||
|
||||
import { getState } from "./getState"
|
||||
|
||||
import {
|
||||
isBound,
|
||||
takeStateFromStore,
|
||||
takeStateFromContext,
|
||||
takeStateFromEventParameters,
|
||||
BB_STATE_FALLBACK,
|
||||
BB_STATE_BINDINGPATH,
|
||||
BB_STATE_BINDINGSOURCE,
|
||||
} from "./isState"
|
||||
|
||||
const doNothing = () => {}
|
||||
doNothing.isPlaceholder = true
|
||||
|
||||
const isMetaProp = propName =>
|
||||
propName === "_component" ||
|
||||
propName === "_children" ||
|
||||
propName === "_id" ||
|
||||
propName === "_style" ||
|
||||
propName === "_code"
|
||||
|
||||
export const setupBinding = (store, rootProps, coreApi, context, rootPath) => {
|
||||
const rootInitialProps = { ...rootProps }
|
||||
|
||||
const getBindings = (props, initialProps) => {
|
||||
const boundProps = []
|
||||
const contextBoundProps = []
|
||||
const componentEventHandlers = []
|
||||
|
||||
for (let propName in props) {
|
||||
if (isMetaProp(propName)) continue
|
||||
|
||||
const val = props[propName]
|
||||
|
||||
if (isBound(val) && takeStateFromStore(val)) {
|
||||
const binding = BindingPath(val)
|
||||
const source = BindingSource(val)
|
||||
const fallback = BindingFallback(val)
|
||||
|
||||
boundProps.push({
|
||||
path: binding,
|
||||
fallback,
|
||||
propName,
|
||||
source,
|
||||
})
|
||||
|
||||
initialProps[propName] = fallback
|
||||
} else if (isBound(val) && takeStateFromContext(val)) {
|
||||
const binding = BindingPath(val)
|
||||
const fallback = BindingFallback(val)
|
||||
const source = BindingSource(val)
|
||||
|
||||
contextBoundProps.push({
|
||||
path: binding,
|
||||
fallback,
|
||||
propName,
|
||||
source,
|
||||
})
|
||||
|
||||
initialProps[propName] = !context
|
||||
? val
|
||||
: getState(context, binding, fallback, source)
|
||||
} else if (isEventType(val)) {
|
||||
const handlers = { propName, handlers: [] }
|
||||
componentEventHandlers.push(handlers)
|
||||
|
||||
for (let e of val) {
|
||||
handlers.handlers.push({
|
||||
handlerType: e[EVENT_TYPE_MEMBER_NAME],
|
||||
parameters: e.parameters,
|
||||
})
|
||||
}
|
||||
|
||||
initialProps[propName] = doNothing
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
contextBoundProps,
|
||||
boundProps,
|
||||
componentEventHandlers,
|
||||
initialProps,
|
||||
}
|
||||
}
|
||||
|
||||
const bind = rootBindings => component => {
|
||||
if (
|
||||
rootBindings.boundProps.length === 0 &&
|
||||
rootBindings.componentEventHandlers.length === 0
|
||||
)
|
||||
return
|
||||
|
||||
const handlerTypes = eventHandlers(store, coreApi, rootPath)
|
||||
|
||||
const unsubscribe = store.subscribe(rootState => {
|
||||
const getPropsFromBindings = (s, bindings) => {
|
||||
const { boundProps, componentEventHandlers } = bindings
|
||||
const newProps = { ...bindings.initialProps }
|
||||
|
||||
for (let boundProp of boundProps) {
|
||||
const val = getState(s, boundProp.path, boundProp.fallback)
|
||||
|
||||
if (val === undefined && newProps[boundProp.propName] !== undefined) {
|
||||
delete newProps[boundProp.propName]
|
||||
}
|
||||
|
||||
if (val !== undefined) {
|
||||
newProps[boundProp.propName] = val
|
||||
}
|
||||
}
|
||||
|
||||
for (let boundHandler of componentEventHandlers) {
|
||||
const closuredHandlers = []
|
||||
for (let h of boundHandler.handlers) {
|
||||
const handlerType = handlerTypes[h.handlerType]
|
||||
closuredHandlers.push(eventContext => {
|
||||
const parameters = {}
|
||||
for (let pname in h.parameters) {
|
||||
const p = h.parameters[pname]
|
||||
parameters[pname] = !isBound(p)
|
||||
? p
|
||||
: takeStateFromStore(p)
|
||||
? getState(s, p[BB_STATE_BINDINGPATH], p[BB_STATE_FALLBACK])
|
||||
: takeStateFromEventParameters(p)
|
||||
? getState(
|
||||
eventContext,
|
||||
p[BB_STATE_BINDINGPATH],
|
||||
p[BB_STATE_FALLBACK]
|
||||
)
|
||||
: takeStateFromContext(p)
|
||||
? getState(
|
||||
context,
|
||||
p[BB_STATE_BINDINGPATH],
|
||||
p[BB_STATE_FALLBACK]
|
||||
)
|
||||
: p[BB_STATE_FALLBACK]
|
||||
}
|
||||
handlerType.execute(parameters)
|
||||
})
|
||||
}
|
||||
|
||||
newProps[boundHandler.propName] = async context => {
|
||||
for (let runHandler of closuredHandlers) {
|
||||
await runHandler(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newProps
|
||||
}
|
||||
|
||||
const rootNewProps = getPropsFromBindings(rootState, rootBindings)
|
||||
|
||||
component.$set(rootNewProps)
|
||||
})
|
||||
|
||||
return unsubscribe
|
||||
}
|
||||
|
||||
const bindings = getBindings(rootProps, rootInitialProps)
|
||||
|
||||
return {
|
||||
initialProps: rootInitialProps,
|
||||
bind: bind(bindings),
|
||||
boundProps: bindings.boundProps,
|
||||
contextBoundProps: bindings.contextBoundProps,
|
||||
}
|
||||
}
|
||||
|
||||
const BindingPath = prop => prop[BB_STATE_BINDINGPATH]
|
||||
const BindingFallback = prop => prop[BB_STATE_FALLBACK]
|
||||
const BindingSource = prop => prop[BB_STATE_BINDINGSOURCE]
|
|
@ -7,15 +7,7 @@ import { bbFactory } from "./bbComponentApi"
|
|||
import { getState } from "./getState"
|
||||
import { attachChildren } from "../render/attachChildren"
|
||||
|
||||
import {
|
||||
isBound,
|
||||
takeStateFromStore,
|
||||
takeStateFromContext,
|
||||
takeStateFromEventParameters,
|
||||
BB_STATE_FALLBACK,
|
||||
BB_STATE_BINDINGPATH,
|
||||
BB_STATE_BINDINGSOURCE,
|
||||
} from "./isState"
|
||||
import { parseBinding } from "./parseBinding"
|
||||
|
||||
const doNothing = () => {}
|
||||
doNothing.isPlaceholder = true
|
||||
|
@ -36,8 +28,9 @@ export const createStateManager = ({
|
|||
componentLibraries,
|
||||
uiFunctions,
|
||||
onScreenSlotRendered,
|
||||
routeTo,
|
||||
}) => {
|
||||
let handlerTypes = eventHandlers(store, coreApi, rootPath)
|
||||
let handlerTypes = eventHandlers(store, coreApi, rootPath, routeTo)
|
||||
let currentState
|
||||
|
||||
// any nodes that have props that are bound to the store
|
||||
|
@ -180,35 +173,25 @@ const _setup = (
|
|||
|
||||
const val = props[propName]
|
||||
|
||||
if (isBound(val) && takeStateFromStore(val)) {
|
||||
const path = BindingPath(val)
|
||||
const source = BindingSource(val)
|
||||
const fallback = BindingFallback(val)
|
||||
const binding = parseBinding(val)
|
||||
const isBound = !!binding
|
||||
if (isBound) binding.propName = propName
|
||||
|
||||
storeBoundProps.push({
|
||||
path,
|
||||
fallback,
|
||||
propName,
|
||||
source,
|
||||
})
|
||||
if (isBound && binding.source === "store") {
|
||||
storeBoundProps.push(binding)
|
||||
|
||||
initialProps[propName] = !currentStoreState
|
||||
? fallback
|
||||
? binding.fallback
|
||||
: getState(
|
||||
currentStoreState,
|
||||
BindingPath(val),
|
||||
BindingFallback(val),
|
||||
BindingSource(val)
|
||||
binding.path,
|
||||
binding.fallback,
|
||||
binding.source
|
||||
)
|
||||
} else if (isBound(val) && takeStateFromContext(val)) {
|
||||
} else if (isBound && binding.source === "context") {
|
||||
initialProps[propName] = !context
|
||||
? val
|
||||
: getState(
|
||||
context,
|
||||
BindingPath(val),
|
||||
BindingFallback(val),
|
||||
BindingSource(val)
|
||||
)
|
||||
: getState(context, binding.path, binding.fallback, binding.source)
|
||||
} else if (isEventType(val)) {
|
||||
const handlersInfos = []
|
||||
for (let e of val) {
|
||||
|
@ -219,31 +202,28 @@ const _setup = (
|
|||
const resolvedParams = {}
|
||||
for (let paramName in handlerInfo.parameters) {
|
||||
const paramValue = handlerInfo.parameters[paramName]
|
||||
if (!isBound(paramValue)) {
|
||||
const paramBinding = parseBinding(paramValue)
|
||||
if (!paramBinding) {
|
||||
resolvedParams[paramName] = () => paramValue
|
||||
continue
|
||||
} else if (takeStateFromContext(paramValue)) {
|
||||
} else if (paramBinding.source === "context") {
|
||||
const val = getState(
|
||||
context,
|
||||
paramValue[BB_STATE_BINDINGPATH],
|
||||
paramValue[BB_STATE_FALLBACK]
|
||||
paramBinding.path,
|
||||
paramBinding.fallback
|
||||
)
|
||||
resolvedParams[paramName] = () => val
|
||||
} else if (takeStateFromStore(paramValue)) {
|
||||
} else if (paramBinding.source === "store") {
|
||||
resolvedParams[paramName] = () =>
|
||||
getState(
|
||||
getCurrentState(),
|
||||
paramValue[BB_STATE_BINDINGPATH],
|
||||
paramValue[BB_STATE_FALLBACK]
|
||||
paramBinding.path,
|
||||
paramBinding.fallback
|
||||
)
|
||||
continue
|
||||
} else if (takeStateFromEventParameters(paramValue)) {
|
||||
} else if (paramBinding.source === "event") {
|
||||
resolvedParams[paramName] = eventContext => {
|
||||
getState(
|
||||
eventContext,
|
||||
paramValue[BB_STATE_BINDINGPATH],
|
||||
paramValue[BB_STATE_FALLBACK]
|
||||
)
|
||||
getState(eventContext, paramBinding.path, paramBinding.fallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -282,7 +262,3 @@ const makeHandler = (handlerTypes, handlerInfo) => {
|
|||
handlerType.execute(parameters)
|
||||
}
|
||||
}
|
||||
|
||||
const BindingPath = prop => prop[BB_STATE_BINDINGPATH]
|
||||
const BindingFallback = prop => prop[BB_STATE_FALLBACK]
|
||||
const BindingSource = prop => prop[BB_STATE_BINDINGSOURCE]
|
||||
|
|
|
@ -39,6 +39,23 @@ describe("initialiseApp (binding)", () => {
|
|||
expect(rootDiv.className.includes("newvalue")).toBe(true)
|
||||
})
|
||||
|
||||
it("should update root element from store, using binding expression", async () => {
|
||||
const { dom, app } = await load(
|
||||
makePage({
|
||||
_component: "testlib/div",
|
||||
className: "store.divClassName",
|
||||
})
|
||||
)
|
||||
|
||||
app.pageStore().update(s => {
|
||||
s.divClassName = "newvalue"
|
||||
return s
|
||||
})
|
||||
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
expect(rootDiv.className.includes("newvalue")).toBe(true)
|
||||
})
|
||||
|
||||
it("should populate child component with store value", async () => {
|
||||
const { dom } = await load(
|
||||
makePage({
|
||||
|
|
|
@ -16,6 +16,20 @@ describe("screenRouting", () => {
|
|||
expect(screenRoot.children[0].children[0].innerText).toBe("screen 2")
|
||||
})
|
||||
|
||||
it("should load correct screen, for initial URL, when appRootPath is something", async () => {
|
||||
const { page, screens } = pageWith3Screens()
|
||||
const { dom } = await load(page, screens, "/testApp/screen2", "/testApp")
|
||||
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
expect(rootDiv.children.length).toBe(1)
|
||||
|
||||
const screenRoot = rootDiv.children[0]
|
||||
|
||||
expect(screenRoot.children.length).toBe(1)
|
||||
expect(screenRoot.children[0].children.length).toBe(1)
|
||||
expect(screenRoot.children[0].children[0].innerText).toBe("screen 2")
|
||||
})
|
||||
|
||||
it("should be able to route to the correct screen", async () => {
|
||||
const { page, screens } = pageWith3Screens()
|
||||
const { dom, app } = await load(page, screens, "/screen2")
|
||||
|
@ -31,6 +45,26 @@ describe("screenRouting", () => {
|
|||
expect(screenRoot.children[0].children[0].innerText).toBe("screen 3")
|
||||
})
|
||||
|
||||
it("should be able to route to the correct screen, when appRootPath is something", async () => {
|
||||
const { page, screens } = pageWith3Screens()
|
||||
const { dom, app } = await load(
|
||||
page,
|
||||
screens,
|
||||
"/testApp/screen2",
|
||||
"/testApp"
|
||||
)
|
||||
|
||||
app.routeTo()("/screen3")
|
||||
const rootDiv = dom.window.document.body.children[0]
|
||||
expect(rootDiv.children.length).toBe(1)
|
||||
|
||||
const screenRoot = rootDiv.children[0]
|
||||
|
||||
expect(screenRoot.children.length).toBe(1)
|
||||
expect(screenRoot.children[0].children.length).toBe(1)
|
||||
expect(screenRoot.children[0].children[0].innerText).toBe("screen 3")
|
||||
})
|
||||
|
||||
it("should destroy and unsubscribe all components on a screen whe screen is changed", async () => {
|
||||
const { page, screens } = pageWith3Screens()
|
||||
const { app } = await load(page, screens, "/screen2")
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
import { setupBinding } from "../src/state/stateBinding"
|
||||
import {
|
||||
BB_STATE_BINDINGPATH,
|
||||
BB_STATE_FALLBACK,
|
||||
BB_STATE_BINDINGSOURCE,
|
||||
} from "../src/state/isState"
|
||||
import { EVENT_TYPE_MEMBER_NAME } from "../src/state/eventHandlers"
|
||||
import { writable } from "svelte/store"
|
||||
import { isFunction } from "lodash/fp"
|
||||
|
||||
describe("setupBinding", () => {
|
||||
it("should correctly create initials props, including fallback values", () => {
|
||||
const { store, props, component } = testSetup()
|
||||
|
||||
const { initialProps } = testSetupBinding(store, props, component)
|
||||
|
||||
expect(initialProps.boundWithFallback).toBe("Bob")
|
||||
expect(initialProps.boundNoFallback).toBeUndefined()
|
||||
expect(initialProps.unbound).toBe("hello")
|
||||
|
||||
expect(isFunction(initialProps.eventBound)).toBeTruthy()
|
||||
initialProps.eventBound()
|
||||
})
|
||||
|
||||
it("should update component bound props when store is updated", () => {
|
||||
const { component, store, props } = testSetup()
|
||||
|
||||
const { bind } = testSetupBinding(store, props, component)
|
||||
bind(component)
|
||||
|
||||
store.update(s => {
|
||||
s.FirstName = "Bobby"
|
||||
s.LastName = "Thedog"
|
||||
s.Customer = {
|
||||
Name: "ACME inc",
|
||||
Address: "",
|
||||
}
|
||||
s.addressToSet = "123 Main Street"
|
||||
return s
|
||||
})
|
||||
|
||||
expect(component.props.boundWithFallback).toBe("Bobby")
|
||||
expect(component.props.boundNoFallback).toBe("Thedog")
|
||||
expect(component.props.multiPartBound).toBe("ACME inc")
|
||||
})
|
||||
|
||||
it("should not update unbound props when store is updated", () => {
|
||||
const { component, store, props } = testSetup()
|
||||
|
||||
const { bind } = testSetupBinding(store, props, component)
|
||||
bind(component)
|
||||
|
||||
store.update(s => {
|
||||
s.FirstName = "Bobby"
|
||||
s.LastName = "Thedog"
|
||||
s.Customer = {
|
||||
Name: "ACME inc",
|
||||
Address: "",
|
||||
}
|
||||
s.addressToSet = "123 Main Street"
|
||||
return s
|
||||
})
|
||||
|
||||
expect(component.props.unbound).toBe("hello")
|
||||
})
|
||||
|
||||
it("should update event handlers on state change", () => {
|
||||
const { component, store, props } = testSetup()
|
||||
|
||||
const { bind } = testSetupBinding(store, props, component)
|
||||
bind(component)
|
||||
|
||||
expect(component.props.boundToEventOutput).toBe("initial address")
|
||||
component.props.eventBound()
|
||||
expect(component.props.boundToEventOutput).toBe("event fallback address")
|
||||
|
||||
store.update(s => {
|
||||
s.addressToSet = "123 Main Street"
|
||||
return s
|
||||
})
|
||||
|
||||
component.props.eventBound()
|
||||
expect(component.props.boundToEventOutput).toBe("123 Main Street")
|
||||
})
|
||||
|
||||
it("event handlers should recognise event parameter", () => {
|
||||
const { component, store, props } = testSetup()
|
||||
|
||||
const { bind } = testSetupBinding(store, props, component)
|
||||
bind(component)
|
||||
|
||||
expect(component.props.boundToEventOutput).toBe("initial address")
|
||||
component.props.eventBoundUsingEventParam({
|
||||
addressOverride: "Overridden Address",
|
||||
})
|
||||
expect(component.props.boundToEventOutput).toBe("Overridden Address")
|
||||
|
||||
store.update(s => {
|
||||
s.addressToSet = "123 Main Street"
|
||||
return s
|
||||
})
|
||||
|
||||
component.props.eventBound()
|
||||
expect(component.props.boundToEventOutput).toBe("123 Main Street")
|
||||
|
||||
component.props.eventBoundUsingEventParam({
|
||||
addressOverride: "Overridden Address",
|
||||
})
|
||||
expect(component.props.boundToEventOutput).toBe("Overridden Address")
|
||||
})
|
||||
|
||||
it("should bind initial props to supplied context", () => {
|
||||
const { component, store, props } = testSetup()
|
||||
|
||||
const { bind } = testSetupBinding(store, props, component, {
|
||||
ContextValue: "Real Context Value",
|
||||
})
|
||||
bind(component)
|
||||
|
||||
expect(component.props.boundToContext).toBe("Real Context Value")
|
||||
})
|
||||
})
|
||||
const testSetupBinding = (store, props, component, context) => {
|
||||
const setup = setupBinding(store, props, undefined, context)
|
||||
component.props = setup.initialProps // svelte does this for us in real life
|
||||
return setup
|
||||
}
|
||||
const testSetup = () => {
|
||||
const c = {}
|
||||
|
||||
c.props = {}
|
||||
c.$set = propsToSet => {
|
||||
for (let pname in propsToSet) c.props[pname] = propsToSet[pname]
|
||||
}
|
||||
|
||||
const binding = (path, fallback, source) => {
|
||||
const b = {}
|
||||
b[BB_STATE_BINDINGPATH] = path
|
||||
b[BB_STATE_FALLBACK] = fallback
|
||||
b[BB_STATE_BINDINGSOURCE] = source || "store"
|
||||
return b
|
||||
}
|
||||
|
||||
const event = (handlerType, parameters) => {
|
||||
const e = {}
|
||||
e[EVENT_TYPE_MEMBER_NAME] = handlerType
|
||||
e.parameters = parameters
|
||||
return e
|
||||
}
|
||||
|
||||
const props = {
|
||||
boundWithFallback: binding("FirstName", "Bob"),
|
||||
boundNoFallback: binding("LastName"),
|
||||
unbound: "hello",
|
||||
multiPartBound: binding("Customer.Name", "ACME"),
|
||||
boundToEventOutput: binding("Customer.Address", "initial address"),
|
||||
boundToContext: binding("ContextValue", "context fallback", "context"),
|
||||
eventBound: [
|
||||
event("Set State", {
|
||||
path: "Customer.Address",
|
||||
value: binding("addressToSet", "event fallback address"),
|
||||
}),
|
||||
],
|
||||
eventBoundUsingEventParam: [
|
||||
event("Set State", {
|
||||
path: "Customer.Address",
|
||||
value: binding("addressOverride", "", "event"),
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
return {
|
||||
component: c,
|
||||
store: writable({}),
|
||||
props,
|
||||
}
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
import { JSDOM } from "jsdom"
|
||||
import { loadBudibase } from "../src/index"
|
||||
|
||||
export const load = async (page, screens = [], url = "/") => {
|
||||
export const load = async (page, screens, url, appRootPath) => {
|
||||
screens = screens || []
|
||||
url = url || "/"
|
||||
appRootPath = appRootPath || ""
|
||||
const dom = new JSDOM("<!DOCTYPE html><html><body></body><html>", {
|
||||
url: `http://test${url}`,
|
||||
})
|
||||
|
@ -10,7 +13,7 @@ export const load = async (page, screens = [], url = "/") => {
|
|||
autoAssignIds(s.props)
|
||||
}
|
||||
setAppDef(dom.window, page, screens)
|
||||
addWindowGlobals(dom.window, page, screens, uiFunctions, {
|
||||
addWindowGlobals(dom.window, page, screens, appRootPath, uiFunctions, {
|
||||
hierarchy: {},
|
||||
actions: [],
|
||||
triggers: [],
|
||||
|
@ -28,6 +31,7 @@ const addWindowGlobals = (
|
|||
window,
|
||||
page,
|
||||
screens,
|
||||
appRootPath,
|
||||
uiFunctions,
|
||||
appDefinition
|
||||
) => {
|
||||
|
@ -35,7 +39,7 @@ const addWindowGlobals = (
|
|||
window["##BUDIBASE_FRONTEND_DEFINITION##"] = {
|
||||
page,
|
||||
screens,
|
||||
appRootPath: "",
|
||||
appRootPath,
|
||||
}
|
||||
window["##BUDIBASE_FRONTEND_FUNCTIONS##"] = uiFunctions
|
||||
}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
export default ({ indexes, helpers }) =>
|
||||
indexes.map(i => ({
|
||||
name: `Table based on index: ${i.name} `,
|
||||
props: tableProps(i, helpers.indexSchema(i)),
|
||||
props: tableProps(
|
||||
i,
|
||||
helpers.indexSchema(i).filter(c => !excludedColumns.includes(c.name))
|
||||
),
|
||||
}))
|
||||
|
||||
const excludedColumns = ["id", "key", "sortKey", "type", "isNew"]
|
||||
|
||||
const tableProps = (index, indexSchema) => ({
|
||||
_component: "@budibase/materialdesign-components/Datatable",
|
||||
_children: [
|
||||
|
|
Loading…
Reference in New Issue