diff --git a/packages/builder/src/builderStore/api.js b/packages/builder/src/builderStore/api.js index b9fb1c5c3..cf7732c02 100644 --- a/packages/builder/src/builderStore/api.js +++ b/packages/builder/src/builderStore/api.js @@ -1,5 +1,3 @@ -import { isUndefined } from "lodash/fp"; - const apiCall = (method) => (url, body) => fetch(url, { method: method, diff --git a/packages/builder/src/userInterface/pagesParsing/types.js b/packages/builder/src/userInterface/pagesParsing/types.js index e2179e03f..b8cc2da4a 100644 --- a/packages/builder/src/userInterface/pagesParsing/types.js +++ b/packages/builder/src/userInterface/pagesParsing/types.js @@ -3,7 +3,8 @@ import { isBoolean, isNumber, isArray, - isObjectLike + isObjectLike, + some } from "lodash/fp"; const defaultDef = typeName => () => ({ @@ -47,6 +48,14 @@ export const expandPropsDefinition = propsDefinition => { } const isComponent = isObjectLike; +const isEvent = e => + isObjectLike(e) + && e.handlerType && isString(e.handlerType) + && e.parameters && isArray(e.parameters); + +const isEventList = e => + isArray(e) && !some(ev => !isEvent(ev)); + export const types = { string: propType(() => "", isString, defaultDef("string")), @@ -56,4 +65,5 @@ export const types = { options: propType(() => "", isString, defaultDef("options")), component: propType(() => ({_component:""}), isComponent, defaultDef("component")), asset: propType(() => "", isString, defaultDef("asset")), + event: propType(() => [], isEventList, defaultDef("event")) }; \ No newline at end of file diff --git a/packages/builder/tests/createDefaultProps.spec.js b/packages/builder/tests/createDefaultProps.spec.js index 050e0b157..e55e1a37e 100644 --- a/packages/builder/tests/createDefaultProps.spec.js +++ b/packages/builder/tests/createDefaultProps.spec.js @@ -103,6 +103,18 @@ describe("createDefaultProps", () => { expect(props.columns).toEqual([]); }); + it("should create a object with single empty array, when prop definition is 'event' ", () => { + const propDef = { + onClick: "event" + }; + + const { props, errors } = createProps("some_component",propDef); + + expect(errors).toEqual([]); + expect(props.onClick).toBeDefined(); + expect(props.onClick).toEqual([]); + }); + it("should create a object with single empty component props, when prop definition is 'component' ", () => { const propDef = { content: "component" diff --git a/packages/client/package.json b/packages/client/package.json index f3afea48f..6b045bc0e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -3,7 +3,7 @@ "version": "0.0.3", "license": "MPL-2.0", "main": "dist/budibase-client.js", - "module": "dist/budibase-client.js", + "module": "dist/budibase-client.esm.mjs", "scripts": { "build": "rollup -c", "test": "jest", diff --git a/packages/client/rollup.config.js b/packages/client/rollup.config.js index beffaa43c..5a017d4b7 100644 --- a/packages/client/rollup.config.js +++ b/packages/client/rollup.config.js @@ -26,12 +26,19 @@ const coreExternal = [ export default { input: 'src/index.js', - output: { - sourcemap: true, - format: 'iife', - name: 'app', - file: `./dist/budibase-client.js` - }, + output: [ + { + sourcemap: true, + format: 'iife', + name: 'app', + file: `./dist/budibase-client.js` + }, + { + file: 'dist/budibase-client.esm.mjs', + format: 'esm', + sourcemap: 'inline' + } + ], plugins: [ resolve({ diff --git a/packages/client/src/createApp.js b/packages/client/src/createApp.js new file mode 100644 index 000000000..86d3105b2 --- /dev/null +++ b/packages/client/src/createApp.js @@ -0,0 +1,46 @@ +import { + split, + last +} from "lodash/fp"; +import {writable} from "svelte/store"; +import { $ } from "./core/common"; +import { bindComponent } from "./stateBinding"; + +export const createApp = componentLibraries => { + + const initialiseComponent = (props, htmlElement) => { + + const {componentName, libName} = splitName(props._component); + + const component = new (componentLibraries[libName][componentName])({ + target: htmlElement, + props: {...props, _app} + }); + + bindComponent(store, component); + + } + + const store = writable({}); + + const _app = { + initialiseComponent, + store + }; + + return _app; + +} + + +const splitName = fullname => { + const componentName = $(fullname, [ + split("/"), + last + ]); + + const libName =fullname.substring( + 0, fullname.length - componentName.length - 1); + + return {libName, componentName}; +} \ No newline at end of file diff --git a/packages/client/src/index.js b/packages/client/src/index.js index a1555e182..4c6c2d729 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -1,5 +1,25 @@ -import { initialise } from "./initialise"; +import { createApp } from "./createApp"; const appDefinition = window["##BUDIBASE_APPDEFINITION##"]; -initialise(window.document, appDefinition); \ No newline at end of file +const componentLibraryUrl = (lib) => "./" + trimSlash(lib) + +const trimSlash = (str) => str.replace(/^\/+|\/+$/g, ''); + +(async () => { + + const componentLibraries = {}; + + for(let lib of appDefinition.componentLibraries) { + componentLibraries[lib.libName] = await import( + componentLibraryUrl(lib.importPath)); + } + + + const _app = createApp(componentLibraries); + + _app.initialiseComponent( + props, + document.body); + +})(); \ No newline at end of file diff --git a/packages/client/src/initialise.js b/packages/client/src/initialise.js deleted file mode 100644 index f8e3c71f7..000000000 --- a/packages/client/src/initialise.js +++ /dev/null @@ -1,22 +0,0 @@ -import { writable } from "svelte/store"; -import { initialiseComponent } from "./initialiseComponent"; - -export const initialise = async (document, appDefinition) => { - - const componentLibraries = {}; - - for(let lib of appDefinition.componentLibraries) { - componentLibraries[lib.libName] = await import( - componentLibraryUrl(lib.importPath)); - } - - const store = writable({}); - - initialiseComponent(componentLibraries, store)( - appDefinition.props, - document.body); -} -const componentLibraryUrl = (lib) => "./" + trimSlash(lib) - -const trimSlash = (str) => str.replace(/^\/+|\/+$/g, ''); - diff --git a/packages/client/src/initialiseComponent.js b/packages/client/src/initialiseComponent.js deleted file mode 100644 index 1f1b542c8..000000000 --- a/packages/client/src/initialiseComponent.js +++ /dev/null @@ -1,36 +0,0 @@ -import { - find, - isUndefined, - split, - last -} from "lodash/fp"; - -import { $ } from "./core/common"; - -export const initialiseComponent = (componentLibraries, store) => (props, htmlElement) => { - - const _app = { - initialiseComponent: initialiseComponent(componentLibraries, store), - store - }; - - const {componentName, libName} = splitName(props._component); - - new (componentLibraries[libName][componentName])({ - target: htmlElement, - props: {...props, _app} - }); - -} - -const splitName = fullname => { - const componentName = $(fullname, [ - split("/"), - last - ]); - - const libName =fullname.substring( - 0, fullname.length - componentName.length - 1); - - return {libName, componentName}; -} \ No newline at end of file diff --git a/packages/client/src/stateBinding.js b/packages/client/src/stateBinding.js new file mode 100644 index 000000000..ff145387b --- /dev/null +++ b/packages/client/src/stateBinding.js @@ -0,0 +1,59 @@ + +export const BB_STATE_INDICATOR = "##bbstate"; +export const BB_STATE_FALLBACK = "##bbstatefallback"; + +export const bindComponent = (store, component) => { + + const newProps = {...component.props}; + const boundProps = []; + + for(let propName in component.props) { + const val = newProps[propName]; + + if(!isState(val)) continue; + + const binding = stateBinding(val); + const fallback = stateFallback(val); + + boundProps.push({ + stateBinding:binding, + fallback, propName + }); + + } + + if(boundProps.length === 0) return; + + const unsubscribe = store.subscribe(s => { + const newProps = {...component.props}; + + for(let boundProp of boundProps) { + const val = boundValueFromStore( + s, + boundProp.stateBinding, + boundProp.fallback); + + if(val === undefined && newProps[boundProp.propName] !== undefined) { + delete newProps[boundProp.propName]; + } + + if(val !== undefined) { + newProps[boundProp.propName] = val; + } + } + + component.$set(newProps); + }); + + return unsubscribe; + +} + +const isState = (prop) => prop[BB_STATE_INDICATOR] !== undefined; +const stateBinding = (prop) => prop[BB_STATE_INDICATOR]; +const stateFallback = (prop) => prop[BB_STATE_FALLBACK]; +const boundValueFromStore = (s, binding, fallback) => { + const value = s[binding]; + if(value === undefined) return fallback; + return value; +} diff --git a/packages/server/utilities/builder/index.template.html b/packages/server/utilities/builder/index.template.html index 74c0d95c8..eacda707a 100644 --- a/packages/server/utilities/builder/index.template.html +++ b/packages/server/utilities/builder/index.template.html @@ -7,6 +7,13 @@