Explore the best approaches to working with components in UI Bakery.
UI Bakery offers various methods of validating user input. You can use our built-in validators or create custom ones. In this article, we'll explore them in more details.
Here, we talk about Text, Text input, Form, and Detail components.
UI Bakery features a range of built-in validators for different inputs, such as Min/Max, Required, Regexp, etc. Validation occurs once a user inputs a value. By default, errors are shown after the input loses focus. But you can change this by clearing the Show error after touched checkbox, and then errors will be shown immediately.
You can also create logic based on validation status. For this purpose, you can use the {{ui.input.valid}}
variable that holds a Boolean value indicating the input's validity.
You can configure default validators for Form/Detail components' fields. To do so, follow the instruction below:
Select your component and click on the field you want to add validation to.
Expand the Edit settings section and scroll to the list of validators.
Set the Min setting value as 100, for example, and then input a value less than 100 in the Id field. The Form component will react to the input validation, showing an error.
You can adjust this behavior by selecting the Disable submit when invalid checkbox which disables submission altogether if the input value is invalid.
Besides using built-in validators, UI Bakery also allows creating custom validators. They are executed when the input changes, displaying an error next to the input.
Custom validators work according to the following rules:
When the input's value changes, the validator is executed
The {{params}}
variable holds the current input value, enabling the creation of validation conditions
If the validator returns a String or an array of Strings, these will be displayed as errors
If the validator returns null
or an empty value, validation is considered successful - no errors are shown, and the input is deemed valid
During validation the input is considered invalid
Validation is complete once all assigned validators have been executed
If the input value is empty, the validator will display the following error:
Custom validators can be assigned to different components. Check out the instruction below to learn how to create and assign a custom validator to a Text input component. The same way, you can do it for other components as well.
Select the Text input component and navigate to the Validation section.
In the Custom validators dropdown, click Create action.
Select a JavaScript Code action type and specify the following code to modify the validator condition:
Also, you can add a Text component and specify for it the following variables to check input status programmatically:
{{ui.input.valid}}
- indicates whether an input is valid
{{ui.input.validating}}
- indicates if a validator is currently running
Custom validators can also execute asynchronous validation operations, for example, making an API request to check if an email address is available. Let's review this example in the instruction below.
Select the Text input component and navigate to the Validation section.
In the Custom validators dropdown, click Create action.
For the first step, add an HTTP Request action type to verify if a user with the provided email address exists:
https://example-data.draftbit.com/users?email={{params}}
And for the second step, add a JavaScript Code action type.
Specify the following condition to verify the presence of a user from the API response:
If the user list is not empty, the error message that you specified will be displayed.
You can also assign custom validators to a Form component. These validators receive the entire form object as {{params}}
input and must return an object where each key is an input name and each value is an error message. This allows the form validator to assign errors to multiple fields in a single run.
To do so, you simply need to create a JavaScript Code action with the code below and assign it as a custom validator for the component:
setErrors
You can also set errors manually for a Form. For example, in some cases, the form should be submitted, but based on the API's error response, it should either succeed or display errors.
For this purpose, you can use the {{ui.form.setErrors()}}
method to set errors accordingly.
Form and Detail components can also display a global error message if an error occurs during the On Submit action. Follow the instruction below to enable this functionality:
Select your Form component and navigate to the Appearance section.
Here, select the Show error message checkbox and add your error message (for example, 'Id field is not valid').
Next, assign a new action for the On Submit trigger.
Select a JavaScript Code action type and specify the following logic to throw a JS error:
Next, click the Submit button to submit the form.
While building your app, you can use multiple components in UI Bakery, and the best thing is that you can link them to share data and state between them. In this article, we'll explore two of the most common scenarios of linking components.
Let’s say you have a Table and you would like to have a separate form to view the details of a specific record from the table. That means you need to connect the Table with the Detail component.
In our example, we will use a Products table and we will add a Detail component to display product details. Let's dive in!
Start by adding a Table to your working area.
Next, you need to load your data and then bind it to the Table.
Now, you can add a Detail component. By default, it is connected to the previous Action, that’s why you’ll see the same fields as in the previous dataset.
Remove this default action from the Data field of the Detail component.
Instead, specify the value of the selected Table row - use the selectedRow.data
variable.
Start typing ui...
- the autocomplete will suggest all the available options.
As simple as that! Now you can check the result - product details will change as you select different records in the Table.
Let's say you have a Products table and you would like to have a separate form, where you could both see and update record details. In this case, you could use a Form component instead of a Detail one. Let’s dive into how you can do that!
Start by adding a Form component to the working area.
If you're starting anew, check out the previous instruction and repeat steps 1-2 first.
In its Data field, specify the value of the selected Table row - use the selectedRow.data
variable.
Done! The Table and Form are now linked and have the same structure. If you click on a Table row, the row values will appear in the Form.
If you want to customize it even further to be able to update record details from the form and send it back to the data source, then be sure to check out this page.
When you have two Forms in your working area, one for adding data and the other for updating it, finding space for more components may be challenging. For this purpose, to save some space, you may use a single Form for both data operations. Here's how you can do that.
Here, we talk about the Form component.
Drag a Form component to your working area.
Next, to populate the data of a table's selected row, specify the {{selectedRow.data}}
variable in the form's Data field.
Create a new action of the Condition type and specify the following code in the code field:
3. Next, add an if condition:
specify for it a Create Row action
specify the Form value object as {{ui.form.value}}
in the Configure Row section.
4. Add an else condition:
specify for it an Update Row action
configure the identifier in the Filters section as id = {{ui.form.value.id}}
specify the Form value object as {{ui.form.value}}
in the Configure Row section.
5. Next, navigate to the Finish step of the action and assign your load data action to the On Success trigger to update the data in the table.
6. Finally, assign the create/update action you've created before to the On Submit trigger of the Form.
The Table component allows you to view and interact with your data. Since your data may contain a large number of records, you may want to add a search bar to filter the data by a certain column/columns. Let's find out how you can do that.
Here, we talk about Table and Text input components.
Drag a Text input component and drop it above the Table.
Add a descriptive label or placeholder for the Input.
It's a good practice to rename components so that they have a unique identifier, and you don't confuse them in the process of building your app. For example, we'll rename the Text input component here to productCode
.
Next, scroll down to its Triggers section and assign the action that loads your data to the On Change trigger.
For the next step, you need add a filter to your load data action to see only the records corresponding to a certain criteria.
Go to the action - set a column that will be used for filtering and reference the input value as the {{ui.inputName.value}}
variable.
Done! Now, anytime you insert a value into the input, the action will be executed. It will process the filter criteria and return only the required values displayed in the Table.
If your dataset contains thousands of records, you may struggle with load time and app performance. To improve performance, you can configure server-side pagination allowing you to load only the records on the current page.
It's important to keep in mind that when server-side pagination is enabled, table inline filters and sorting don't work automatically and need to be implemented separately.
In this article, we'll explore two types of server-side pagination:
To implement page-based server-side pagination, follow the steps below:
For the Table component, select the Server side pagination checkbox in the right side panel.
Next, create a new action of the SQL Query type and use the {{ui.table.pageSize}}
and {{ui.table.paginationOffset}}
variables to control the data you need to load:
With these variables, you can configure your action to only load the page that the table requests, by sending the pageSize
and paginationOffset
variables to your API or Load table action.
Now, assign this action to the On Page Change trigger of the Table to ensure the data is reloaded with each table page navigation.
Finally, set the Table's Show loading setting to true while your SQL Query action is loading - add the {{actions.loadData.loading}}
variable.
Done! Now the Table will display only the records for a specific page.
Let's review cursor-based pagination based on the Stripe API example. Say you want to select a list of customers from Stripe API. To do that, follow the instruction below:
For the Table component, select the Server side pagination checkbox in the right side panel.
Next, create a new action of the HTTP Request type and specify the {{ui.table.pageSize}}
and {{ui.table.afterCursor}}
variables in JS mode - Query Params.
With these variables, you can configure your action to only load the page that the table requests, by sending pageSize
and afterCursor
variables to your API.
Now, in Table settings, set the following:
{{_.last(actions.customers.data.data).id}}
to the Next cursor field (the identifier used for the next set of results),
{{actions.customers.data.has_more}}
to the Has next page field to enable or disable the next page button according to API info,
where actions.customers is the action we created in Step 2.
Assign your HTTP Request action to the On Page Change trigger of the table to ensure the data is reloaded with each table page navigation.
By default, the Table doesn't know the total number of items and can't disable the Next page button if the limit is reached. To display the total item count and make the table more intuitive, you can set the Total row count setting. Based on it, the number of pages will be calculated and displayed according to the items per page.
To retrieve the total row count, you simply need to create an action that will retrieve the total number of items or get this info from the API you are using. For example, you can use an action of the SQL Query type:
When using a Load Table action type, you can obtain the row count using {{actions.loadData.res.total}}
. In this case, you may not need the additional action to calculate the total number of records and you can use this property to show the pager.
When server-side pagination is enabled, table filters and sorting don't work automatically, as the Table cannot filter and sort the data while it's being loaded page by page.
To configure server-side filters, you can use the {{ui.table.filters}}
variable in your action to send this data to your API/SQL Query or Load Table action.
When using filters, make sure the SQL query is configured in a way that it returns all records when the filters are empty, instead of trying to search for empty records. For instance, for most SQL databases you would need to use a LIKE operator combined with the % sign, which represents zero, one, or multiple characters:
And don't forget to assign the action with the filter variable to the On Filters Change trigger.
To enable sorting, use the {{ui.table.sortColumn}}
and {{ui.table.sortDirection}}
variables in your action.
By default, dynamic sorting doesn't work as the Convert SQL queries to prepared statements option is enabled. You need to turn this setting OFF to allow dynamic sorting. But be cautious - disabling this setting can cause SQL injection!
After you disable the Prepared statement setting, your SQL Query will look like this:
You can set the default sort column and direction in JavaScript using the ??
operator. In our example here, it means - "if sortColumn
is empty, use id
column instead".
Notice how the name filter now requires quotes around the variable name:
CONCAT('%',
'
{{ ui.table.filters.name}}
'
, '%')
As the query is not converted to prepared statements anymore, you need to add proper quotes around string values manually.
And don't forget to assign the action with the sorting variables to the On Sort Change trigger.
If everything is configured properly, but the action does not return the expected values, check the following:
There is a number of components that allow you to display and enter date and time values, such as Date & Time, Date picker, Date & Time picker. By default, these components use the browser time zone, i.e. the local time zone of the user to display and operate with date and time values.
Here, we talk about Date & Time, Date picker, and Date & Time picker components.
In this article, we'll explore in more details some additional options that will allow you to manage time zones in your app:
Date & Time, Date picker, and Date & Time picker components can display date and time in a specific time zone. You can achieve this by setting the component's Timezone property to the desired time zone. The time zone can be specified as a UTC offset:
Or as an IANA time zone name:
Here is an example of the Date & Time picker component that accepts the date in the user +1 hour time zone and displays it in the America/New_York time zone:
The time zone settings affect only the display of date and time. Under the hood, the Date object is always stored in the browser time zone.
By default, the Date picker and Date & Time picker components display the current date as their default values when no date is selected. You can change the default value, though, to the one you need, for example, start of month or week.
This is the current date set as the default value for a Date picker component:
Alternatively, you can set it to the start of the month:
By default, when you use Create Row or Update Row actions, the Date object is first converted to the UTC time zone and then sent to the database server. That means, that the user value entered by the user in the browser may be different from the value sent to the database.
Let's review the following example: the user selects December 10, 2024 13:00 in the Date picker, but the value sent to the database is December 10, 2024 12:00 (because of the time shift due to conversion to UTC). Now, you can prevent this behavior and send the Date object without any time zone conversion, just like it is displayed in the browser. You simply need to go to your Data source settings and select the Ignore Browser Timezone checkbox. After it's applied, you'll see that the Date is sent without conversion.
If you are using an HTTP data source, you can also send the time component of the Date object as is, but it will require some additional steps. Let's explore two ways of how you can do that:
Convert the Date object to the ISO format.
When sending the date, use the following code to convert the Date object to the ISO format with no time zone offset conversion:
After executing the action, you'll see time sent as it is displayed in the browser.
Set the UTC time zone offset to the Date picker component.
First, you need to set the component's Timezone value to +00. After that, use the following code in your action:
Setting +00 timezone on a component and using .utcOffset(0, true)
will result in an additional negative time zone offset.
Here, we talk about Menu, Context menu, and Horizontal menu components.
UI Bakery gives you the ability to configure specific menu items, for Menu components, which users can access based on their roles. Below, is an example that you can modify according to the roles in your organization. Once you're ready, switch to JS mode in the Items property of the Menu component and specify the following code:
Complex components (Table, Form, Detail) support various column/field types, such as:
🔠 String
🗓️ Date
🔠 Long Text
⌛ Time
🔗 Link
✔️ Select/Tag
🔢 Number
☑️ Multiselect/Tags
⭐ Rating
{;} JSON
💲 Currency
🔘 Button
📈 Percent
🔽 Context menu button
✅ Boolean
🖼️ Image
📅 Date & Time
🖇️ File
These field types share their settings among all supported components, meaning that, for example, configuring dropdowns for a Table or Form is exactly the same.
Using these types, you can build a component that will support most of the common use cases.
Form, Table, Detail and some other components support autogeneration of field types based on the component data. Autogeneration works in the following cases:
Creating an Action and adding a Component.
This is the scenario when you first create and run an action, then add a component and assign your action to it. All the fields and their types are generated automatically.
Such components as Table, Chart, List View, and Grid View now do not require manual action assignment - you just have to click the Data field dropdown to see all available actions and variables - Suggested, Page, and App - and easily switch between them. The component structure will automatically regenerate based on your selection.
Using the Generate structure button.
Here, you connect your action (state variable or another component property) to an already added component and generate its structure from there. This scenario applies to all the components, except for Table, Chart, List View, and Grid View, since they do not require manual structure regeneration.
No need to configure similar Components over and over again.
When generating a structure based on another Component property, UI Bakery not only creates the same structure but also copies the properties of all its field types.
You can also configure fields and columns of various types manually. Follow this instruction to learn how you can do that:
Click the plus sign at the bottom of the Columns list.
In the Field panel that pops up, configure the following properties:
If your component is connected to a data source, select a proper field from the Field name dropdown. If you can't find the field you need in the list, enter a name manually. This way, a new field/column will be added but not mapped to the connected dataset.
Select a field type from the Type dropdown.
Configure the remaining properties as needed.
All field types support View and Edit modes. These modes are extremely helpful when you need to edit data inline in a Table or Detail component without creating a separate Form for it.
This is quite simple: you just need to add your component (say, a Detail) and turn on the Inline editable toggle for a specific field. Here, you can switch between Edit modes (Always/On click) and Submit triggers (Blur/Change) options. The field you specified will become editable and end-users will be able to quickly adjust field value directly from the component.
Similarly, you can also turn on the View only mode for fields, for example, in a Form component. In this case, the fields will be displayed but your end-users won't be able to edit them.
You can set a placeholder that will be displayed in View mode for nearly all field types, except for Boolean, Image, Button, and JSON Editor. You need to open the field's Common settings section and add the placeholder - it will be displayed when the field's value is blank.
All field types in UI Bakery support validation but these settings vary from one type to another. You can find validation settings in the field's Edit settings section.
You may have cases when you need your components to take up all the available width and height of the page regardless of the user's screen size, for example, to avoid getting a scroll bar. In such cases, you can use our Expand content to fit feature. Let's explore it in more details.
Let's say you have added a Table to your page and you want it to occupy all the available space. You simply need to select the Expand content to fit checkbox in page settings on the right side panel.
Now, let's say you've also added a Button to the page, for example, and now you want both Table and Button to occupy all available space. If you try selecting the Expand content to fit checkbox now, you'll notice that it's disabled.
This is because the feature is designed to ensure that a single child component can stretch to fully occupy its parent container’s available space.
When multiple child components are present, the layout must accommodate all of them, making it impossible for one element to expand to 100% without conflicting with the others.
In such cases, you need to add a Card component to the page and put your components inside the Card.
First, select the Expand content to fit checkbox in page settings on the right side panel.
Next, drag and drop a Card component into your working area.
Now, select the Expand content to fit checkbox for the Card as well.
Then, drop a Table component to the Card body - it should occupy all available space.
Add other components you need to the Card header, for example, Text input - to add some filters.
Additionally, for the Card, you can also Disable container styles and set a transparent background. The final result will look like your components are not placed inside any additional container.
That's it! Now you get a page that looks excellent on every screen size and is not limited to single component only.
Here, we talk about the Table component as an example, but the guide applies to all available components.
We've previously touched upon configuring component visibility using the Show condition setting here. Check it out if you need a reminder and come back to learn about the two main configurations of this setting:
Just a quick recap how this setting works: basically setting it to false
hides the component, while setting it to true
displays it.
Now let's dive into its configurations 👇
By default, hidden components do not occupy space on the canvas. However, if you want to you can preserve their space when hidden by enabling the corresponding setting for a specific component.
In this case, a component's layout will remain intact regardless of its visibility. This ensures that components maintain their fixed positioning, whether they are visible or hidden.
Check the screen below: the Preserve space when hidden setting is disabled for a table
component and enabled for a table2
component.
Breaking changes
If you are using component visibility to switch which component is visible in the same place or if you have components that occupy the same location in the layout, you must pay attention to their position after the layout v2 upgrade as they may be broken.
If something does break after the update, there is no need to worry since end-users are not affected. However, you will need to manually reconfigure the layouts where something went wrong.
Hidden is the default value for the Hide mode. When this value is selected, a component is always rendered during page initialization. Even if a component is inside another component which renders children dynamically, it will still be rendered on page initialization, and the On Init
trigger will be activated.
For example, if you place a Table inside a Modal, this component will be rendered during page initialization and the On Init
trigger will be activated.
The Not rendered option allows you to completely remove the component from the page when its Show Condition is false
. This means the component will be rendered each time the Show Condition value changes from false
to true
. The On Init
trigger will be fired each time the component is rendered.
If you have a component that renders children dynamically, then this setting will also affect its children.
For example, if you place a Table inside a Modal, the table will not be rendered during page initialization. Each time you open this modal dialog, the table will be rendered from scratch, and the On Init
trigger will be activated.
Which option to choose in the Hide mode is entirely up to you but we would recommend the following:
Use the Hidden option for all components that should be visible on the page immediately after loading.
Use the Not rendered option for components that display their children dynamically (e.g., Modal, Tabset, Stepper) to improve app performance.