Master custom layouts and UI design
Fractal Platform is designed with a unique philosophy: you focus on your database and business logic first, and the UI is automatically generated as a "skin" over your data model. This means you get a working interface for free, without spending time on initial UI development.
Most applications follow this progression:
Stage 1: Standard Auto-Generated UI β Stage 2: Dimension-Based Customization β Stage 3: Custom Layouts for Key Screens β Stage 4: Fully Custom Components
Building an Online Shop:
It's recommended to split your application's UI into two categories:
The system analyzes your document structure and automatically creates appropriate UI controls:
| Data Type | UI Control | Example |
|---|---|---|
| String, Number | TextBox with Label | "Name": "Bob" β Name: [Bob____] |
| Boolean | CheckBox with Label | "Active": true β β Active |
| Enum | ComboBox with Label | "Status" β Status: [Select βΌ] |
| Object | GroupBox | "Address": {...} β [Address Group] |
| Array | Grid | "Items": [...] β [Data Grid] |
| Deep Nesting (4+ levels) | TextBox with [E] button | Opens nested form |
{
"Name": "Bob",
"Age": 25,
"IsActive": true,
"Address": {
"Street": "Main St",
"City": "New York"
},
"Orders": [
{ "ID": 1, "Total": 100 },
{ "ID": 2, "Total": 150 }
]
}
This JSON automatically becomes:
Name: [Bob________] Age: [25_________] β IsActive [Address] Street: [Main St____] City: [New York___] Orders: ββββββ¬ββββββββ β ID β Total β ββββββΌββββββββ€ β 1 β 100 β β 2 β 150 β ββββββ΄ββββββββ
Dimensions are configuration documents that customize the Standard UI without writing HTML or code. They're the easiest way to improve your interface.
| Dimension | Purpose | Example Use |
|---|---|---|
| UI | Basic UI information | Set control types, locations, layouts |
| Enum | Define dropdown options | Status: Draft, Published, Archived |
| Validation | Restrict data types | Email format, number ranges |
| Menu | Add navigation menus | Action buttons on forms |
| ToolTip | Add help text | Field instructions for users |
| Edit | Control editability | Make fields read-only |
| Pagination | Multi-page forms | Split long forms into pages |
| Tab | Tabbed interface | Group related fields |
| Wizard | Step-by-step forms | Onboarding, registration |
| Filter | Add search box | Filter grid data |
| Theme | Color schemes | Dark mode, light mode |
| Language | Localization | Multi-language support |
{
"Layout": "MyCustomLayout",
"Title": {
"ControlType": "Label",
"Location": "HeaderArea"
},
"Description": {
"ControlType": "RichTextBox",
"Width": 500,
"Height": 200
},
"Status": {
"ControlType": "ComboBox"
}
}
A layout is an HTML container that defines where your controls appear on the page. Instead of the default vertical stack, you create your own HTML structure.
Part of Standard UI with header/footer
{
"Layout": "MyLayout"
}
β
Keep filter, buttons, theme selector
β
Override only center content
Complete custom HTML page
{
"Layout": "MyLayout",
"IsRawPage": true
}
β
Full design control
β οΈ Must include necessary scripts
Step 1: Create HTML file (MyLayout.html)
Welcome to My App!
This is a custom layout
Step 2: Configure UI Dimension
{
"Layout": "MyLayout",
"IsRawPage": false
}
STANDARD LAYOUT CUSTOM LAYOUT
ββββββββββββββββ ββββββββββββββββ
β Filter [?] β β HERO IMAGE β
ββββββββββββββββ€ β Welcome! β
β Name: [____] β ββββββββββββββββ€
β Age: [____] β β Name: Age: β
β β Active β β [___] [___] β
β β β β
β [Save][Cancelβ β β Active β
ββββββββββββββββ β [Save] [___] β
ββββββββββββββββ
Define control locations in UI Dimension, reference them in HTML:
Document:
{
"Title": "Hello World!",
"Subtitle": "Welcome to my app"
}
UI Dimension:
{
"Layout": "MyLayout",
"Title": {
"ControlType": "Label",
"Location": "TitleLocation"
},
"Subtitle": {
"ControlType": "Label",
"Location": "SubtitleLocation"
}
}
MyLayout.html:
@Value
Place controls directly in HTML without locations:
UI Dimension:
{
"Layout": "MyLayout",
"IsRawPage": true
}
MyLayout.html:
@Value
Use repeatable="true" to loop through arrays:
Document:
{
"Products": [
{ "Name": "Laptop", "Price": 999 },
{ "Name": "Mouse", "Price": 25 },
{ "Name": "Keyboard", "Price": 75 }
]
}
Layout:
Result:
Laptop
$999
Mouse
$25
Keyboard
$75
Use type="standard" to render built-in controls:
| Property | Description | Example |
|---|---|---|
| @Value | Control's value | |
| @Name | Control's name | |
| @Key | Attribute key | |
| @ToolTip | Help text |
| Property | Returns | Usage |
|---|---|---|
| @Enabled | "true" or "false" | if (@Enabled == "true") |
| @Disabled | "disabled" or "" | |
| @Checked | "checked" or "" | |
| @ReadOnly | "readonly" or "" | |
| @Visible | "true" or "false" | if (@Visible == "true") |
| Property | Description |
|---|---|
| @Width | Control width from UI Dimension |
| @Height | Control height from UI Dimension |
| Property | Description | Example |
|---|---|---|
| @ClickUrl | Button click URL | |
| @ClickSubmit | Button submit script | |
| @ChangeSubmit | Change event script |
| Property | Description |
|---|---|
| @AddRowUrl | URL to add new row |
| @EditRowUrl | URL to edit row |
| @DeleteRowUrl | URL to delete row |
| @AddRowScript | Script to add row |
| @EditRowScript | Script to edit row |
| @DeleteRowScript | Script to delete row |
| Property | Description |
|---|---|
| @SavePageScript | Save button script |
| @CancelPageScript | Cancel button script |
| @RefreshPageScript | Refresh button script |
| @NextPageScript | Next page script (pagination) |
| @PrevPageScript | Previous page script |
| @SavePageUrl | Save button URL |
| @CancelPageUrl | Cancel button URL |
| Property | Description | Example Value |
|---|---|---|
| @UserName | Current user's name | "john.doe" |
| @UserLanguage | User's language | "EN", "UA", "RU" |
| @UserTheme | User's theme | "Dark", "Light" |
| @UserAvatar | User's avatar URL | "/files/avatar.png" |
| @AppName | Application name | "MyApp" |
| @FormName | Current form name | "ProductEdit" |
| Property | Description |
|---|---|
| @BaseUrl | Base URL of platform |
| @BaseAppUrl | Base URL of application |
| @BaseFormUrl | Base URL with form name |
| @BaseFilesUrl | URL to files folder |
| Property | Description |
|---|---|
| @StartForm | Render form start tags |
| @EndForm | Render form end tags |
| @Styles | Inject custom styles |
| @ComboBoxItems | Render dropdown options |
| @FilterText | Current filter text |
@Top.FieldName.Value // Access top-level field from nested context @FieldName.Computed // Compute element value @FieldName.TextBox // Render as textbox attributes @FieldName.Button // Render as button attributes @FieldName.Link // Render as link attributes @FieldName.CheckBox // Render as checkbox attributes @FieldName.Picture // Render as image attributes
@StartForm@FormName
@EndForm
A component is a UI control that represents a segment (subset) of your JSON document, usually containing multiple fields. Components allow you to create rich, interactive controls beyond simple textboxes and checkboxes.
Step 1: Document Structure
{
"Tags": ["Design", "Development", "Marketing"]
}
Step 2: UI Dimension
{
"Tags": {
"ControlType": "Tags"
}
}
Step 3: Override RenderForm
public class MyRenderForm : BaseRenderForm
{
public override string RenderComponent(ComponentDOMControl domControl)
{
if (domControl.Component == "Tags")
{
return RenderTags(domControl);
}
return base.RenderComponent(domControl);
}
private string RenderTags(ComponentDOMControl domControl)
{
var tags = domControl.GetValue() as List;
var html = new StringBuilder();
html.Append("");
return html.ToString();
}
}
Components use a hidden input field to communicate between frontend and backend:
Your JavaScript must update this hidden field before form submission. The backend reads this JSON and applies it to your document.
For simpler components, you can compose them from standard controls instead of writing custom JavaScript:
public class MyRenderForm : BaseRenderForm
{
public override string RenderComponent(ComponentDOMControl domControl)
{
if (domControl.Component == "AddressCard")
{
var html = new StringBuilder();
// Render standard controls inside component
html.Append("");
html.Append(RenderTextBox(domControl.GetControl("Street")));
html.Append(RenderTextBox(domControl.GetControl("City")));
html.Append(RenderTextBox(domControl.GetControl("ZipCode")));
html.Append("");
return html.ToString();
}
return base.RenderComponent(domControl);
}
}
Check available standard components at: http://booben.com/jupiter/?app=ControlsGallery
The RenderForm class is responsible for converting your data and UI dimensions into actual HTML. By creating a custom RenderForm class, you can override how any control renders.
Step 1: Create Your Class
public class MyRenderForm : BaseRenderForm
{
// Override methods here
}
Step 2: Register in Application
public class MyApplication : Application
{
public override BaseRenderForm CreateRenderForm()
{
return new MyRenderForm();
}
}
| Method | Purpose |
|---|---|
| RenderStyles() | Inject custom CSS |
| RenderTextBox() | Customize textbox rendering |
| RenderButton() | Customize button rendering |
| RenderCheckBox() | Customize checkbox rendering |
| RenderComboBox() | Customize dropdown rendering |
| RenderGrid() | Customize grid/table rendering |
| RenderGroupBox() | Customize group container |
| RenderComponent() | Customize component rendering |
| Render() | Override entire form rendering |
public override string RenderTextBox(TextBoxDOMControl domControl)
{
var html = new StringBuilder();
html.Append($"");
html.Append($" ");
html.Append($" ");
html.Append($"");
return html.ToString();
}
public override string RenderGrid(GridDOMControl domControl)
{
var html = new StringBuilder();
html.Append("");
html.Append($"");
html.Append("");
html.Append(" ");
// Render headers
foreach (var column in domControl.Columns)
{
html.Append($"{column.Label} ");
}
html.Append("Actions ");
html.Append(" ");
html.Append(" ");
for (uint i = 0; i < domControl.RowCount; i++)
{
html.Append("");
foreach (var column in domControl.Columns)
{
html.Append($"{domControl.GetCellValue(i, column.Name)} ");
}
html.Append("");
html.Append($" ");
html.Append($" ");
html.Append(" ");
html.Append(" ");
}
html.Append(" ");
html.Append("
");
html.Append("");
return html.ToString();
}
| Property | Description |
|---|---|
| Application | Reference to Application instance |
| DOMForm | The form being rendered |
| Collection | Data source for the form |
// Get localized string
string localText = GetLocalizedValue("Hello World");
// Get tooltip
string tooltip = GetToolTip(domControl);
// Get full file URL
string imageUrl = GetFileUrl("avatar.png");
// Returns: booben.com/Jupiter/MyApp/files/avatar.png
// Get validation error style
string errorStyle = GetValidationErrorStyle(domControl);
public override string RenderMenu(DOMControl domControl)
{
var html = new StringBuilder();
html.Append("");
return html.ToString();
}
base.RenderXXX() for controls you don't want to customize. This ensures default behavior for other controls.
public class ShopRenderForm : BaseRenderForm
{
public override string RenderStyles()
{
return @"
";
}
public override string RenderPicture(PictureDOMControl domControl)
{
if (domControl.Name == "ProductImage")
{
return $@"
";
}
return base.RenderPicture(domControl);
}
public override string RenderTextBox(TextBoxDOMControl domControl)
{
if (domControl.Name == "Price")
{
return $@"
@ChangeSubmit
Change event script
lt;/span>
";
}
return base.RenderTextBox(domControl);
}
}
β οΈ Performance Note: RenderForm methods are called frequently. Keep logic simple and avoid heavy processing. Cache values when possible.