events hooked up and working
This commit is contained in:
parent
25d8f9a8f8
commit
14aa33705f
|
@ -9,7 +9,6 @@
|
|||
CircleIndicator,
|
||||
EventsIcon,
|
||||
} from "components/common/Icons/"
|
||||
import EventsEditor from "./EventsEditor"
|
||||
import panelStructure from "./temporaryPanelStructure.js"
|
||||
import CategoryTab from "./CategoryTab.svelte"
|
||||
import DesignView from "./DesignView.svelte"
|
||||
|
@ -21,7 +20,6 @@
|
|||
let categories = [
|
||||
{ value: "settings", name: "Settings" },
|
||||
{ value: "design", name: "Design" },
|
||||
{ value: "events", name: "Events" },
|
||||
]
|
||||
let selectedCategory = categories[0]
|
||||
|
||||
|
@ -109,8 +107,6 @@
|
|||
displayNameField={displayName}
|
||||
onChange={onPropChanged}
|
||||
screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} />
|
||||
{:else if selectedCategory.value === 'events'}
|
||||
<EventsEditor component={componentInstance} />
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
import { store, backendUiStore } from "builderStore"
|
||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||
import SaveFields from "./SaveFields.svelte"
|
||||
import {
|
||||
readableToRuntimeBinding,
|
||||
runtimeToReadableBinding,
|
||||
} from "builderStore/replaceBindings"
|
||||
|
||||
export let parameters
|
||||
|
||||
|
@ -14,25 +18,36 @@
|
|||
})
|
||||
|
||||
let idFields
|
||||
let recordId
|
||||
$: {
|
||||
idFields = bindableProperties.filter(
|
||||
bindable =>
|
||||
bindable.type === "context" && bindable.runtimeBinding.endsWith("._id")
|
||||
)
|
||||
// ensure recordId is always defaulted - there is usually only one option
|
||||
if (idFields.length > 0 && !parameters.recordId) {
|
||||
parameters.recordId = idFields[0].runtimeBinding
|
||||
if (idFields.length > 0 && !parameters._id) {
|
||||
recordId = idFields[0].runtimeBinding
|
||||
parameters = parameters
|
||||
} else if (!recordId && parameters._id) {
|
||||
recordId = parameters._id
|
||||
.replace("{{", "")
|
||||
.replace("}}", "")
|
||||
.trim()
|
||||
}
|
||||
}
|
||||
|
||||
$: parameters._id = `{{ ${recordId} }}`
|
||||
|
||||
// just wraps binding in {{ ... }}
|
||||
const toBindingExpression = bindingPath => `{{ ${bindingPath} }}`
|
||||
const toBindingExpression = bindingPath => {
|
||||
console.log("yeo")
|
||||
return `{{ ${bindingPath} }}`
|
||||
}
|
||||
|
||||
// finds the selected idBinding, then reads the table/view
|
||||
// from the component instance that it belongs to.
|
||||
// then returns the field names for that schema
|
||||
const fieldNamesFromIdBinding = recordId => {
|
||||
const modelInfoFromIdBinding = recordId => {
|
||||
if (!recordId) return []
|
||||
|
||||
const idBinding = bindableProperties.find(
|
||||
|
@ -52,14 +67,18 @@
|
|||
const model = $backendUiStore.models.find(
|
||||
m => m._id === instance[component.context]
|
||||
)
|
||||
|
||||
parameters.modelId = modelId
|
||||
return Object.keys(model.schema)
|
||||
}
|
||||
|
||||
$: fieldNames =
|
||||
parameters && parameters.recordId
|
||||
? fieldNamesFromIdBinding(parameters.recordId)
|
||||
: []
|
||||
let fieldNames
|
||||
$: {
|
||||
if (parameters && recordId) {
|
||||
fieldNames = modelInfoFromIdBinding(recordId)
|
||||
} else {
|
||||
fieldNames = []
|
||||
}
|
||||
}
|
||||
|
||||
const onFieldsChanged = e => {
|
||||
parameters.fields = e.detail
|
||||
|
@ -74,16 +93,17 @@
|
|||
</div>
|
||||
{:else}
|
||||
<Label size="m" color="dark">Record Id</Label>
|
||||
<Select secondary bind:value={parameters.recordId}>
|
||||
<Select secondary bind:value={recordId}>
|
||||
<option value="" />
|
||||
{#each idFields as idField}
|
||||
<option value={toBindingExpression(idField.runtimeBinding)}>
|
||||
<option value={idField.runtimeBinding}>
|
||||
{idField.readableBinding}
|
||||
</option>
|
||||
{/each}
|
||||
</Select>
|
||||
{/if}
|
||||
|
||||
{#if parameters.recordId}
|
||||
{#if recordId}
|
||||
<SaveFields
|
||||
parameterFields={parameters.fields}
|
||||
schemaFields={fieldNames}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export { default } from "./EventsEditor.svelte"
|
|
@ -53,7 +53,21 @@ const apiOpts = {
|
|||
delete: del,
|
||||
}
|
||||
|
||||
const createRecord = async params =>
|
||||
await post({ url: `/api/${params.modelId}/records`, body: params.fields })
|
||||
|
||||
const updateRecord = async params => {
|
||||
const record = params.fields
|
||||
record._id = params._id
|
||||
await patch({
|
||||
url: `/api/${params.modelId}/records/${params._id}`,
|
||||
body: record,
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
authenticate: authenticate(apiOpts),
|
||||
triggerWorkflow: triggerWorkflow(apiOpts),
|
||||
createRecord,
|
||||
updateRecord,
|
||||
}
|
||||
|
|
|
@ -50,7 +50,6 @@ export const createApp = ({
|
|||
treeNode,
|
||||
onScreenSlotRendered,
|
||||
setupState: stateManager.setup,
|
||||
getCurrentState: stateManager.getCurrentState,
|
||||
})
|
||||
|
||||
return getInitialiseParams
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import setBindableComponentProp from "./setBindableComponentProp"
|
||||
import { attachChildren } from "../render/attachChildren"
|
||||
import store from "../state/store"
|
||||
|
||||
export const trimSlash = str => str.replace(/^\/+|\/+$/g, "")
|
||||
|
||||
export const bbFactory = ({
|
||||
componentLibraries,
|
||||
onScreenSlotRendered,
|
||||
getCurrentState,
|
||||
runEventActions,
|
||||
}) => {
|
||||
const apiCall = method => (url, body) => {
|
||||
return fetch(url, {
|
||||
|
@ -26,13 +27,6 @@ export const bbFactory = ({
|
|||
delete: apiCall("DELETE"),
|
||||
}
|
||||
|
||||
const safeCallEvent = (event, context) => {
|
||||
const isFunction = obj =>
|
||||
!!(obj && obj.constructor && obj.call && obj.apply)
|
||||
|
||||
if (isFunction(event)) event(context)
|
||||
}
|
||||
|
||||
return (treeNode, setupState) => {
|
||||
const attachParams = {
|
||||
componentLibraries,
|
||||
|
@ -44,12 +38,17 @@ export const bbFactory = ({
|
|||
return {
|
||||
attachChildren: attachChildren(attachParams),
|
||||
props: treeNode.props,
|
||||
call: safeCallEvent,
|
||||
call: async eventName =>
|
||||
eventName &&
|
||||
(await runEventActions(
|
||||
treeNode.props[eventName],
|
||||
store.getState(treeNode.contextStoreKey)
|
||||
)),
|
||||
setBinding: setBindableComponentProp(treeNode),
|
||||
api,
|
||||
parent,
|
||||
// these parameters are populated by screenRouter
|
||||
routeParams: () => getCurrentState()["##routeParams"],
|
||||
routeParams: () => store.getState()["##routeParams"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,39 @@
|
|||
import api from "../api"
|
||||
import renderTemplateString from "./renderTemplateString"
|
||||
|
||||
export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"
|
||||
|
||||
export const eventHandlers = routeTo => {
|
||||
const handler = (parameters, execute) => ({
|
||||
execute,
|
||||
parameters,
|
||||
})
|
||||
|
||||
return {
|
||||
"Navigate To": handler(["url"], param => routeTo(param && param.url)),
|
||||
"Create Record": handler(["url"], param => param),
|
||||
"Update Record": handler(["url"], param => param),
|
||||
"Trigger Workflow": handler(["workflow"], api.triggerWorkflow),
|
||||
const handlers = {
|
||||
"Navigate To": param => routeTo(param && param.url),
|
||||
"Create Record": api.createRecord,
|
||||
"Update Record": api.updateRecord,
|
||||
"Trigger Workflow": api.triggerWorkflow,
|
||||
}
|
||||
|
||||
// when an event is called, this is what gets run
|
||||
const runEventActions = async (actions, state) => {
|
||||
if (!actions) return
|
||||
for (let action of actions) {
|
||||
const handler = handlers[action[EVENT_TYPE_MEMBER_NAME]]
|
||||
const parameters = createParameters(action.parameters, state)
|
||||
if (handler) {
|
||||
await handler(parameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return runEventActions
|
||||
}
|
||||
|
||||
export const isEventType = prop =>
|
||||
Array.isArray(prop) &&
|
||||
prop.length > 0 &&
|
||||
!prop[0][EVENT_TYPE_MEMBER_NAME] === undefined
|
||||
const createParameters = (parameterTemplateObj, state) => {
|
||||
const parameters = {}
|
||||
for (let key in parameterTemplateObj) {
|
||||
if (typeof parameterTemplateObj[key] === "string") {
|
||||
parameters[key] = renderTemplateString(parameterTemplateObj[key], state)
|
||||
} else if (typeof parameterTemplateObj[key] === "object") {
|
||||
parameters[key] = createParameters(parameterTemplateObj[key], state)
|
||||
}
|
||||
}
|
||||
return parameters
|
||||
}
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
isEventType,
|
||||
eventHandlers,
|
||||
EVENT_TYPE_MEMBER_NAME,
|
||||
} from "./eventHandlers"
|
||||
import { eventHandlers } from "./eventHandlers"
|
||||
import { bbFactory } from "./bbComponentApi"
|
||||
import renderTemplateString from "./renderTemplateString"
|
||||
import appStore from "./store"
|
||||
|
@ -25,33 +21,23 @@ export const createStateManager = ({
|
|||
onScreenSlotRendered,
|
||||
routeTo,
|
||||
}) => {
|
||||
let handlerTypes = eventHandlers(routeTo)
|
||||
|
||||
// creating a reference to the current state
|
||||
// this avoids doing store.get() ... which is expensive on
|
||||
// hot paths, according to the svelte docs.
|
||||
// the state object reference never changes (although it's internals do)
|
||||
// so this should work fine for us
|
||||
let currentState
|
||||
appStore.subscribe(s => (currentState = s))
|
||||
const getCurrentState = () => currentState
|
||||
let runEventActions = eventHandlers(routeTo)
|
||||
|
||||
const bb = bbFactory({
|
||||
getCurrentState,
|
||||
componentLibraries,
|
||||
onScreenSlotRendered,
|
||||
runEventActions,
|
||||
})
|
||||
|
||||
const setup = _setup({ handlerTypes, getCurrentState, bb })
|
||||
const setup = _setup(bb)
|
||||
|
||||
return {
|
||||
setup,
|
||||
destroy: () => {},
|
||||
getCurrentState,
|
||||
}
|
||||
}
|
||||
|
||||
const _setup = ({ handlerTypes, getCurrentState, bb }) => node => {
|
||||
const _setup = bb => node => {
|
||||
const props = node.props
|
||||
const initialProps = { ...props }
|
||||
|
||||
|
@ -70,53 +56,10 @@ const _setup = ({ handlerTypes, getCurrentState, bb }) => node => {
|
|||
node.stateBound = true
|
||||
}
|
||||
}
|
||||
|
||||
if (isEventType(propValue)) {
|
||||
const state = appStore.getState(node.contextStoreKey)
|
||||
const handlersInfos = []
|
||||
for (let event of propValue) {
|
||||
const handlerInfo = {
|
||||
handlerType: event[EVENT_TYPE_MEMBER_NAME],
|
||||
parameters: event.parameters,
|
||||
}
|
||||
|
||||
const resolvedParams = {}
|
||||
for (let paramName in handlerInfo.parameters) {
|
||||
const paramValue = handlerInfo.parameters[paramName]
|
||||
resolvedParams[paramName] = () =>
|
||||
renderTemplateString(paramValue, state)
|
||||
}
|
||||
|
||||
handlerInfo.parameters = resolvedParams
|
||||
handlersInfos.push(handlerInfo)
|
||||
}
|
||||
|
||||
if (handlersInfos.length === 0) {
|
||||
initialProps[propName] = doNothing
|
||||
} else {
|
||||
initialProps[propName] = async context => {
|
||||
for (let handlerInfo of handlersInfos) {
|
||||
const handler = makeHandler(handlerTypes, handlerInfo)
|
||||
await handler(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setup = _setup({ handlerTypes, getCurrentState, bb })
|
||||
const setup = _setup(bb)
|
||||
initialProps._bb = bb(node, setup)
|
||||
|
||||
return initialProps
|
||||
}
|
||||
|
||||
const makeHandler = (handlerTypes, handlerInfo) => {
|
||||
const handlerType = handlerTypes[handlerInfo.handlerType]
|
||||
return async context => {
|
||||
const parameters = {}
|
||||
for (let paramName in handlerInfo.parameters) {
|
||||
parameters[paramName] = handlerInfo.parameters[paramName](context)
|
||||
}
|
||||
await handlerType.execute(parameters)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,8 +181,7 @@ const maketestlib = window => ({
|
|||
currentProps = Object.assign(currentProps, props)
|
||||
if (currentProps.onClick) {
|
||||
node.addEventListener("click", () => {
|
||||
const testText = currentProps.testText || "hello"
|
||||
currentProps._bb.call(props.onClick, { testText })
|
||||
currentProps._bb.call("onClick")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ const newid = require("../../db/newid")
|
|||
|
||||
exports.patch = async function(ctx) {
|
||||
const db = new CouchDB(ctx.user.instanceId)
|
||||
const record = await db.get(ctx.params._id)
|
||||
const record = await db.get(ctx.params.id)
|
||||
const model = await db.get(record.modelId)
|
||||
const patchfields = ctx.request.body
|
||||
|
||||
|
@ -13,6 +13,8 @@ exports.patch = async function(ctx) {
|
|||
record[key] = patchfields[key]
|
||||
}
|
||||
|
||||
coerceFieldsToCorrectType(record, model)
|
||||
|
||||
const validateResult = await validate({
|
||||
record,
|
||||
model,
|
||||
|
@ -47,6 +49,8 @@ exports.save = async function(ctx) {
|
|||
|
||||
const model = await db.get(record.modelId)
|
||||
|
||||
coerceFieldsToCorrectType(record, model)
|
||||
|
||||
const validateResult = await validate({
|
||||
record,
|
||||
model,
|
||||
|
@ -182,6 +186,32 @@ exports.validate = async function(ctx) {
|
|||
ctx.body = errors
|
||||
}
|
||||
|
||||
// this function modifies an incoming record, to allow for things like
|
||||
// "boolField": "true" (instead of mandating "boolField": true)
|
||||
// this allows us to use mustash templating to send non-string fields in a request
|
||||
const coerceFieldsToCorrectType = (record, model) => {
|
||||
for (let fieldName in record) {
|
||||
const fieldValue = record[fieldName]
|
||||
if (model.schema[fieldName]) {
|
||||
if (
|
||||
model.schema[fieldName].type === "boolean" &&
|
||||
typeof fieldValue !== "boolean"
|
||||
) {
|
||||
if (fieldValue === "true") record[fieldName] = true
|
||||
if (fieldValue === "false") record[fieldName] = false
|
||||
continue
|
||||
}
|
||||
|
||||
if (model.schema[fieldName].type === "number") {
|
||||
const val = parseFloat(fieldValue)
|
||||
if (!isNaN(val)) {
|
||||
record[fieldName] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function validate({ instanceId, modelId, record, model }) {
|
||||
if (!model) {
|
||||
const db = new CouchDB(instanceId)
|
||||
|
|
|
@ -25,7 +25,7 @@ router
|
|||
.patch(
|
||||
"/api/:modelId/records/:id",
|
||||
authorized(WRITE_MODEL, ctx => ctx.params.modelId),
|
||||
recordController.save
|
||||
recordController.patch
|
||||
)
|
||||
.post(
|
||||
"/api/:modelId/records/validate",
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,7 +2,6 @@
|
|||
export let className = "default"
|
||||
export let disabled = false
|
||||
export let text
|
||||
export let onClick
|
||||
|
||||
export let _bb
|
||||
let theButton
|
||||
|
@ -11,7 +10,7 @@
|
|||
theButton && _bb.attachChildren(theButton)
|
||||
|
||||
const clickHandler = () => {
|
||||
_bb.call(onClick)
|
||||
_bb.call("onClick")
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
if (containerElement) {
|
||||
_bb.attachChildren(containerElement)
|
||||
if (!hasLoaded) {
|
||||
_bb.call(onLoad)
|
||||
_bb.call("onLoad")
|
||||
hasLoaded = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
if (itemContainer) {
|
||||
_bb.attachChildren(itemContainer)
|
||||
if (!hasLoaded) {
|
||||
_bb.call(onLoad)
|
||||
_bb.call("onLoad")
|
||||
hasLoaded = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,10 @@
|
|||
export let _bb
|
||||
|
||||
const rowClickHandler = row => () => {
|
||||
_bb.call(onRowClick, row)
|
||||
// call currently only accepts one argument, so passing row does nothing
|
||||
// however, we do not expose this event anyway. I am leaving this
|
||||
// in for the future, as can and probably should hande this
|
||||
_bb.call("onRowClick", row)
|
||||
}
|
||||
|
||||
const cellValue = (colIndex, row) => {
|
||||
|
|
Loading…
Reference in New Issue