Low Code x

Intro

Martin McKeaveney

Co-Founder and CTO @ Budibase

What is Low Code?

"Low-code is a visual approach to software development. With low-code, you can abstract and automate every step of the application lifecycle to streamline delivery of a variety of solutions. "

Low code augments code. It does not seek to replace it entirely.

What is Budibase?

Budibase is an open-source low code platform built for developers - designed for speed and customisation.

What Budibase Does

Builder - Data

Builder - Design

Builder

Client

Visually build the definition for an app

Read Definition -> Application

Svelte @ Budibase

  • No Runtime
  • Lightweight
  • Portable
  • Performant
  • Simple
  • Reactive
  • Compiled

Svelte as Bytecode

BuilderĀ  -> Client Library -> Svelte Web App

Stack

Styling and Design System

Builder Routing

Development

Builder UI, Client Applications

BBUI Design System

Our entire design system is built using Spectrum CSS and svench

App Creation Pipeline

Screens

Layouts

Components

Screens

{
  "_id": "screen_4fdbcf20029444ef8de16444da0f9801",
  "_rev": "1-c1d08db5275a1237bfe4559efd68190a",
  "description": "",
  "url": "",
  "layoutId": "layout_public_master",
  "props": {
    "_instanceName": "LoginScreenContainer",
    "_id": "5beb4c7b-3c8b-49b2-b8b3-d447dc76dda7",
    "_component": "@budibase/standard-components/container",
    "_styles": {
      "normal": {
        "flex": "1 1 auto",
        "display": "flex",
        "flex-direction": "column",
        "justify-content": "center",
        "align-items": "center"
      },
      "hover": {},
      "active": {},
      "selected": {}
    },
    "_transition": "fade",
    "type": "div",
    "_children": [
      {
        "_id": "781e497e-2e7c-11eb-adc1-0242ac120002",
        "_component": "@budibase/standard-components/login",
        "_styles": {
          "normal": {
            "padding": "64px",
            "background": "rgba(255, 255, 255, 0.4)",
            "border-radius": "0.5rem",
            "margin-top": "0px",
            "box-shadow": "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
            "font-size": "16px",
            "font-family": "Inter",
            "flex": "0 1 auto"
          },
          "hover": {},
          "active": {},
          "selected": {}
        },
        "logo": "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg",
        "title": "Log in to Some App",
        "buttonText": "Log In",
        "_children": [],
        "_instanceName": "Login"
      }
    ]
  },
  "routing": {
    "route": "/",
    "roleId": "PUBLIC"
  },
  "name": "login-screen"
}

Routing

<script>
  import { getContext } from "svelte"
  import Router from "svelte-spa-router"
  import { routeStore } from "../store"
</script>
  
<div use:styleable={$component.styles}>
  <Router routes={config.routes} />
</div>

Screens and Layouts Demo

Components

"button": {
    "name": "Button",
    "description": "A basic html button.",
    "icon": "ri-share-box-line",
    "styleable": true,
    "settings": [
      {
        "type": "text",
        "label": "Text",
        "key": "text"
      },
      {
        "type": "boolean",
        "label": "Disabled",
        "key": "disabled"
      },
      {
        "type": "event",
        "label": "On Click",
        "key": "onClick"
      }
    ]
  }
}

Components Demo

Data Providers

- Components are generally presentational

- Data is fetched by a utility component called a Data Provider

- Fetches data on mount and keeps it up to date

<script>
  $: fetchData(dataSource)
  $: filteredRows = filterRows(allRows, filter)
  $: sortedRows = sortRows(filteredRows, sortColumn, sortOrder)
  $: rows = limitRows(sortedRows, limit)
  $: getSchema(dataSource)
  $: actions = [
    {
      type: ActionTypes.RefreshDatasource,
      callback: () => fetchData(dataSource),
      metadata: { dataSource },
    },
  ]
  $: dataContext = {
    rows,
    schema,
    rowsLength: rows.length,
    loading,
    loaded,
  }
</script>

<Provider {actions} data={dataContext}>
  <slot />
</Provider>

Example: Repeater

{
  "repeater": {
    "name": "Repeater",
    "description": "A configurable data list that attaches to your backend tables.",
    "icon": "ri-list-check-2",
    "styleable": true,
    "hasChildren": true,
    "settings": [
      {
        "type": "dataProvider",
        "label": "Data",
        "key": "dataProvider"
      },
      {
        "type": "text",
        "label": "Empty Text",
        "key": "noRowsMessage",
        "defaultValue": "No rows found."
      },
      {
        "type": "filter",
        "label": "Filtering",
        "key": "filter"
      }
    ],
    "context": {
      "type": "schema"
    }
  }
}

Repeater and Data Provider Demo

Svelte App

<script>
  // Provide contexts
  setContext("sdk", SDK)
  setContext("component", writable({}))
  setContext("context", createContextStore())

  // Load app config
  onMount(async () => {
    await initialise()
    await authStore.actions.fetchUser()
  })

  // Register this as a refreshable datasource so that user changes cause
  // the user object to be refreshed
  $: actions = [
    {
      type: ActionTypes.RefreshDatasource,
      callback: () => authStore.actions.fetchUser(),
      metadata: { dataSource: { type: "table", tableId: TableNames.USERS } },
    },
  ]
</script>

{#if loaded && $screenStore.activeLayout}
  <Provider key="user" data={$authStore} {actions}>
    <Component definition={$screenStore.activeLayout.props} />
    <NotificationDisplay />
  </Provider>
{/if}

Rendering the Tree

<script>
  import * as ComponentLibrary from "@budibase/standard-components"

  export let definition = {}

  // Props that will be passed to the component instance
  let componentProps

  // Get contexts
  const context = getContext("context")

  // Create component context
  const componentStore = writable({})
  setContext("component", componentStore)

  // Extract component definition info
  $: constructor = getComponentConstructor(definition._component)
  $: children = definition._children || []
  $: id = definition._id
  $: updateComponentProps(definition, $context)
</script>

{#if constructor && componentProps}
  <svelte:component this={constructor} {...componentProps}>
    {#if children.length        
      {#each children as child (child._id)}
        <svelte:self definition={child} />
      {/each}
    {/if}
  </svelte:component>
{/if}

Full App Demo

Extending Budibase

  • Component Libraries

  • Templates

  • Data Sources

  • Integrations

  • Authentication Providers

  • Automations

Future Work

Compile time optimisations

Sveltes Future Is Bright

  • Production ready

  • No need for endless libraries

  • Svelte is extremely powerful for building low code and no code platforms

  • Svelte as bytecode

Thanks!

Low Code x Svelte

By Martin McKeaveney

Low Code x Svelte

  • 965