Custom components

You can create a custom component to add functionality that is not present in the UI Bakery components list.

Basics

As a developer, you can create a custom component to use in UI Bakery. This component can have its own logic and interface that is defined by you. Additionally, the custom component can communicate with other features in UI Bakery by triggering events and receiving data to display.

Custom components can be written in pure JavaScript or can import custom libraries, such as jQuery or React.

Please note that the custom component is rendered inside of an iframe, thus we recommend using it only for the fix-sized elements and avoid overlays and popups inside of it.

Additionally, you can use the Unrestricted custom component to render any HTML and JavaScript without any restrictions. This component is not rendered inside of an iframe.

Component anatomy

A Custom component is an HTML page embedded within an iframe that can contain any HTML, CSS, and JavaScript. You can provide its code in the Code setting. The Custom component can communicate with other UI Bakery components, actions, and app state. It can receive data and fire events.

A typical custom component would have the following structure:

  • 3rd party scripts

  • custom styles

  • root element (usually a <div> tag)

  • custom JavaScript script tag

<!-- 3rd party scripts and styles -->
<script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

<!-- custom styles -->
<style>
  body {
    padding: 1rem;
  }

  p {
    margin-top: 0;
  }

  button {
    margin-bottom: 1rem;
  }

  .container {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }
</style>

<!-- root element where the component will be rendered -->
<div id="root"></div>

<!-- custom logic -->
<script type="text/babel">
  function CustomComponent() {
  
    // receive data from UI Bakery
    const data = UB.useData();

    return (
      <div className="container">
        <p>Data from UI Bakery: {data.title}</p>
        <button onClick={() => UB.triggerEvent("Data from custom component")}>Trigger Event</button>
        <input onChange={(event) => UB.updateValue(event.target.value)} placeholder="Set state"></input>
      </div>
    );
  }

  const Component = UB.connectReactComponent(CustomComponent);
  ReactDOM.render(<Component />, document.getElementById("root"));
</script>

By default, the custom component is rendered inside an iframe, so there are no specific limitations to the code and styles specified by the developer.

Passing data inside of the component

To pass the data into your custom component use the "Data" setting. This can be done by passing a JavaScript object that contains the necessary data, such as:

{
  data: [1,2,3],
  display: 'only_new',
}

Additionally, you can pass the data using JS API in your actions:

ui.customComponent.setData({ ... })

To access this data within the custom component, use the following code:

const data = UB.useData()

You can also subscribe to updates of the data using the following code:

UB.onData(data => {
    console.log('new data', data);
});

Receiving data and triggering actions from the component

If your custom component produces events or needs to trigger an action, use the following code:

  • To set the value of the component, use the following code inside the custom component:

UB.updateValue('Data from custom component');

Once executed, the new value is available as {{ui.customComponent.value}};

  • To trigger an action, use the following code inside the custom component:

UB.triggerEvent('Data from custom component');

Then, subscribe your action to the On Event trigger of the custom component. Once the UB.triggerEvent('data') is executed, the assigned action will be triggered.

The data supplied to the triggerEvent() function is available as {{ui.customComponent.value}} as well as {{params}} variable in the assigned action.

jQuery example

Copy and paste the whole example into the custom component Code field:

<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>

<style>
  body {
    padding: 1rem;
  }

  p { margin-top: 0 }

  button { margin-bottom: 1rem }

  .container {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }
</style>

<div class="container">
  <p>Data from UI Bakery: <span id="uibakeryData"></span></p>
  <button id="triggerEvent">Trigger Event</button>
  <input id="updateValue" placeholder="Set state"></input>
</div>

<script>
  $('#triggerEvent').click(() => UB.triggerEvent('Data from custom component'));
  $('#updateValue').change(event => UB.updateValue(event.target.value));

  UB.onData(({ title }) => {
    $('#uibakeryData').text(title);
  });
</script>

React example

Copy and paste the whole example into the custom component Code field:

<script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

<div id="root"></div>

<style>
  body {
    padding: 1rem;
  }

  p { margin-top: 0 }

  button { margin-bottom: 1rem }

  .container {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }
</style>

<script type="text/babel">
  function CustomComponent() {
  	const data = UB.useData();

    return (
	  <div className="container">
        <p>Data from UI Bakery: {data.title}</p>
		<button onClick={() => UB.triggerEvent('Data from custom component')}>Trigger Event</button>
      	<input onChange={event => UB.updateValue(event.target.value)} placeholder="Set state"/>
      </div>
    );
  }

  const Component = UB.connectReactComponent(CustomComponent);
  ReactDOM.render(<Component />, document.getElementById('root'));
</script>

Custom Calendar example

In this example, we will create a custom calendar to display the appointments.

  1. Start with loading the data: add an action - Load Table with appointments data.

  2. Find a Custom component and place it onto the working area.

  3. In the component's Data field, assign the newly created action:{ events: {{ actions.loadAppointments.data }} }.

4. In the Code field, type in the below code:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.css"> 
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.js"></script>

<div class="container">
  <div id="calendar"></div>
</div>

<style>
   body, html {
     height: 100%;
     padding: 0;
     margin: 0;
   }

  .container {
    background: white;
    padding: 2rem;
    height: 100%;
    overflow: hidden;
    border-radius: 0.25rem;
    border: 0.0625rem solid #dde1eb;
    box-shadow: 0 0.5rem 1rem 0 rgb(44 51 73 / 10%);
  }

  .fc-daygrid-event-harness {
    cursor: pointer;
  }
</style>

<script>
  document.addEventListener('DOMContentLoaded', function() {
    var calendarEl = document.getElementById('calendar');
    var calendar = new FullCalendar.Calendar(calendarEl, {
      initialView: 'dayGridMonth',
      eventClick: (info) => {
        // Update UI variable value
        UB.updateValue({ id: info.event.id });
        // Event triggering
        UB.triggerEvent({ id: info.event.id });
      }
    });
    calendar.render();
    
    // Callback to process new data in custom component
    UB.onData(data => {
      calendar.removeAllEvents();

      const events = data && data.events ? data.events : [];
      if (events && events[0]) {
        // In case of new data, the first event is automatically selected
        UB.updateValue({ id: events[0].id });
        UB.triggerEvent({ id: events[0].id });
      }
      events.forEach(event => {
        calendar.addEvent(event);
      });
    });
  });
</script>

Your calendar is ready now!

Google Charts example

Google Charts offer a vast variety of charts, so you can connect the charts library to UI Bakery and code your own chart.

Now, let's create a project milestones Gantt chart. We will use the data about each project milestone from the data source and show its timeline and details on a Gantt chart.

  1. Load your data and add a Table to display it.

For this example, we are using Google Sheets as a data source. In the table, there are the following columns:

  • Id

  • Task

  • Team

  • Responsible

  • Start date

  • ETA

  • Percent done.

2. Create a new Action - Code step. This action is to map the data to later use it in the chat configuration:

const rawRows = await {{actions.loadPhases.trigger()}};
const msInDay = 24 * 60 * 60 * 1000;
return rawRows.map((row) => [
  // Task ID
  row.id.toString(),
  // Task name
  row.Task,
  // Task's start date
  row['Start Date'],
  // Task's end date can be omitted (null), if you have duration
  null,
  // Duration in milliseconds. Use null if you have an end date
  row.ETA*msInDay,
  // % of completion
  row['Percent done'] * 100,
  // List dependencies, if necessary. Need to be listed comma-separated, e.g.'Development,NDA'
  null,
]);

3. Find a Custom Component and drag it onto the working area.

4. In the Data field of the component, assign the newly created action: {{actions.mapData.data}}.

5. In the component's Code field, you'll need to specify the code for the Gantt. We'll take the sample from the documentation and then make adjustments according to your data:

<html>
  <script src="https://www.gstatic.com/charts/loader.js"></script>
  <head>
    <script>
      // Variables initialization, that will be used in a chart
      let isLoaded = false;
      let rows = [];
      let chart;
      let chartData;
      // Loading Gantt chart library
      google.charts.load("current", { packages: ["gantt"] });
      // Chart initialization only after it's loaded
      google.charts.setOnLoadCallback(() => {
        // Chatt initialization
        chart = new google.visualization.Gantt(document.getElementById("chart_div"));
        chartData = new google.visualization.DataTable();

        // Setting up chart structure
        chartData.addColumn("string", "Task Id");
        chartData.addColumn("string", "Task Name");
        chartData.addColumn("date", "Start Date");
        chartData.addColumn("date", "End Date");
        chartData.addColumn("number", "Duration");
        chartData.addColumn("number", "Percent Complete");
        chartData.addColumn("string", "Dependencies");

        isLoaded = true;
        drawChart();
      });

      // Callback to process new data in custom component
      UB.onData((data) => {
        rows = data ?? [];
        // Convert dates to Date objects
        for (let i = 0; i < rows.length; i++) {
          rows[i][2] = new Date(rows[i][2]); // Convert Start Date
        }
        // The data can be received before the chart is initialiased.
        // That's why rendering of a chart with new data should be done after chart initialization
        if (isLoaded) {
          drawChart();
        }
      });

      function drawChart() {
      // Clear previous data before adding new rows
      chartData.removeRows(0, chartData.getNumberOfRows());
      chartData.addRows(rows);

        const options = {
          height: 600,
          gantt: {
            trackHeight: 30,
          },
        };

        chart.draw(chartData, options);
      }
    </script>
  </head>
  <body>
    <div id="chart_div"></div>
  </body>
</html>

Check the component now - you will see a Gantt chart populated. When you hover over a milestone, you will see additional details about it.

MUI React library template example

Here's an example of how to connect and use the MUI library to build custom components in UI Bakery:

Copy and paste the following code into the custom component Code setting:

<!-- React -->
<script src="https://unpkg.com/react@latest/umd/react.development.js" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@latest/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@latest/babel.min.js" crossorigin="anonymous"></script>

<!-- MUI -->
<script src="https://unpkg.com/@mui/material@latest/umd/material-ui.development.js" crossorigin="anonymous"></script>
<!-- Fonts to support Material Design -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/>
<!-- Icons to support Material Design -->
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"/>

<div id="root"></div>

<script type="text/babel">
    const {
        Avatar,
        Button,
        CssBaseline,
        TextField,
        FormControlLabel,
        Checkbox,
        Link,
        Grid,
        Box,
        Typography,
        Container,
        createTheme,
        ThemeProvider
    } = MaterialUI;

    const theme = createTheme();

    function Copyright(props) {
        return (
            <Typography variant="body2" color="text.secondary" align="center" {...props}>
                {'Copyright © '}
                <Link color="inherit" href="https://mui.com/">
                    Your Website
                </Link>{' '}
                {new Date().getFullYear()}
                {'.'}
            </Typography>
        );
    }

    function App() {
        const handleSubmit = (event) => {
            event.preventDefault();
            const data = new FormData(event.currentTarget);
            console.log({
                email: data.get('email'),
                password: data.get('password'),
            });
        };

        return (
            <ThemeProvider theme={theme}>
                <Container component="main" maxWidth="xs">
                    <CssBaseline />
                    <Box
                        sx={{
                            marginTop: 8,
                            display: 'flex',
                            flexDirection: 'column',
                            alignItems: 'center',
                        }}
                    >
                        <Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}/>
                        <Typography component="h1" variant="h5">
                            Sign in
                        </Typography>
                        <Box component="form" onSubmit={handleSubmit} noValidate sx={{ mt: 1 }}>
                            <TextField
                                margin="normal"
                                required
                                fullWidth
                                id="email"
                                label="Email Address"
                                name="email"
                                autoComplete="email"
                                autoFocus
                            />
                            <TextField
                                margin="normal"
                                required
                                fullWidth
                                name="password"
                                label="Password"
                                type="password"
                                id="password"
                                autoComplete="current-password"
                            />
                            <FormControlLabel
                                control={<Checkbox value="remember" color="primary" />}
                                label="Remember me"
                            />
                            <Button
                                type="submit"
                                fullWidth
                                variant="contained"
                                sx={{ mt: 3, mb: 2 }}
                            >
                                Sign In
                            </Button>
                            <Grid container>
                                <Grid item xs>
                                    <Link href="#" variant="body2">
                                        Forgot password?
                                    </Link>
                                </Grid>
                                <Grid item>
                                    <Link href="#" variant="body2">
                                        {"Don't have an account? Sign Up"}
                                    </Link>
                                </Grid>
                            </Grid>
                        </Box>
                    </Box>
                    <Copyright sx={{ mt: 8, mb: 4 }} />
                </Container>
            </ThemeProvider>
        );
    }

    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<App/>);

</script>

Last updated

© 2024 UI Bakery