Server-Driven UI (SDUI) is a technique which allows to render dynamic user interfaces for the web & native apps using structured data received from the server which describes different parts of the user interface & the data contained inside it. In this case, we call this structured data, a "template".
Here's a good explanation of SDUI: https://www.judo.app/blog/server-driven-ui/
This package allows the rendering of templates using React with structured template data received from the server.
Here are some of the examples of cards that have been rendered using this package which you can access using this demo link, while the structured template data for rendering these cards can be accessed using this link:
Each template consists of the following key-value pairs:
-
id
: A unique identification string for recognising the template. -
topElementId
: A unique identification string for recognising the topmost node element whose element data is stored in the element object. -
element
: An object storing the template elements in the form of key-value pair,template_element_id: template_element_object
. -
text
: An object containing the textual style objects in the form of key-value pair,text_style_id: text_style_object
, where eachtext_style_object
stores the textual styles (e.g.font-weight
,font-style
,text-decoration
) which will be used for styling the individual layout elements. -
design
: An object containing non-textual style objects in the form of key-value pair,design_style_id: design_style_object
, where eachdesign_style_object
stores the non-textual styles (e.g.height
,width
,background
,color
) which will be used by individual layout elements.
Each template
will have the following form:
{
id: <STRING>"UID",
topElementId: <STRING>"UID",
element: <OBJECT>{...},
design: <OBJECT>{...},
text: <OBJECT>{...}
}
An example of the design
style object is:
{
background: "#fc6a03"
color: "#fff"
height: "fit-content"
id: "3d87f501-3941-474e-8ce2-5489faf99ba2"
margin: "0px"
padding: "5px"
width: "fit-content"
}
An example of the text
style object is:
{
"id": "d0f71ec4-095b-485a-b145-ac242defa74b",
"fontFamily": "'Merriweather', serif",
"fontWeight": "400",
"fontStyle": "normal",
"textAlign": "left",
"textDecoration": "none"
}
NOTE: The obvious question here would be, why aren't we storing all the styles in a single style
object instead of separating the styles for a single layout element into text
& design
properties, despite the fact that during the execution, these styles are eventually merged into a single object that is being passed to the style
prop of the respective components?
The answer lies with the process of automating user interface styles, where any user interface can be generated by a combination of textual & non-textual properties which can be stored separately. Here's an example of the three cards with varying text & design properties from the demo link:
-
id
: It describes a unique identification string to recognise the layout element. -
type
: It describes the type of layout element. For now, we only have three layout element types:container
,text
&image
& this can be used to render any almost any static content.The reason this property has been used is that, in the end, each layout element has to be rendered or expressed in form of HTML tags (or JSX in case of react) & different tags have different relevant attributes. e.g.
src
attribute is relevant to an image tag, but it is not relevant to a div tag, similarly,href
attribute is relevant to an anchor tag, but it is not relevant to an img tag. -
parent
: It describes the unique identification string of the parent layout element. It can have two possible values,null
or string of a valid parent node or element. This is required because every HTML document has a tree structure, where there’s a parent tag & each parent tag can have 0 to N child elements, but each HTML tag (except for<html>
) only has one parent tag. -
children
: It is a array of strings that describes the unique identification string of the child elements of any given layout element. If it is an empty array, then it means that a layout element has no child elements, or simply, “It is an empty tag”, which is the case with<img>
tag.
-
text
: It describes a unique identification string for the text style objects that contains textual styling properties such asfont-size
,font-weight
,font-style
,text-decoration
etc.Using this string, the rendering script searches for the text style object present in the post data & once the required text style is found using the provided identification string, then it is added to an object which is assigned to a layout element’s style attributes.
-
design
: It describes a unique identification string for design style objects that contains non-textual styling properties such ascolor
,margin
,padding
,border
, etc. The above process for adding text style objects is also used for adding design style objects to a card’s elements. -
properties
: It is a array of strings that describes the classes &/or the attributes of a layout element. Since both attributes & classes are described in the same way, the rendering script differentiates the class name & the attribute name-value pair is quite simple: the attribute name-value pairs are expressed as a string in the formattrName=attrValue
, whereas class names are just plain text e.g.p1
,h2
.
So, each template element
will have the following form:
{
type: <STRING>"text"/<STRING>"container"/<STRING>"image"
id: <STRING>"element_id",
parent: <STRING>"parent_id"/null,
children: <ARRAY>["child_id_1", ... , "child_id_n"],
text: <STRING>"text_style_id",
design: <STRING>"design_style_id",
properties: <STRING>["class","attrName=attrVal"]
}
The package also comes with some styles such as:
m-card
: To be used with a container with a height of 58vh & aspect ratio of 0.75.m-rh-{5-100}
: Defines relative height with respect to card with +5% increment.m-rw-{5-100}
: Defines relative width with respect to card with +5% increment.
To underst& how this package works, let's look at this card structure & how it can be rendered using this package:
The above card structure consists of 4 template elements & for simplicity we're not going to use any custom design & text style properties, rather we're using Bootstrap 5
classes:
A container template element containing other template elements of the card:
{
id: "1",
type: "container"
design: "",
text: "",
parent: null,
children: ["2","3","4"],
properties: ["m-card","d-flex","flex-column","border"]
}
An image element contained in the above container element:
{
id: "2",
type: "image"
design: "",
text: "",
parent: "1",
children: [],
properties: ["m-rh-40","m-rw-100","src=https://sm.mashable.com/mashable_sea/photo/default/man-fakes-death-cat-q6u_2z9w.png"]
}
An text element for heading contained in the above container element:
{
id: "3",
type: "text"
design: "",
text: "",
parent: "1",
children: ["Lorem ipsum dolor sit amet, consectetur"],
properties: ["m-rh-90","h3"]
}
An text element for paragraph contained in the above container element:
{
id: "4",
type: "text"
design: "",
text: "",
parent: "1",
children: ["Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam mollis, mi sit amet lobortis iaculis, velit leo ultrices ipsum, vitae lobortis dui nibh vitae lacus. In commodo lacus a magna dapibus dignissim."],
properties: ["m-rh-90","small"]
}
The above-mentioned layout elements will can be used to create template which will be rendered in the above form:
{
id: 0,
topElementId: 1,
design: {},
text: {},
element: {
1: {
id: "1",
type: "container"
design: "",
text: "",
parent: null,
children: ["2","3","4"],
properties: ["m-card","d-flex","flex-column","border"]
},
2: {
id: "2",
type: "image"
design: "",
text: "",
parent: "1",
children: [],
properties: ["m-rh-40","m-rw-100","src=https://sm.mashable.com/mashable_sea/photo/default/man-fakes-death-cat-q6u_2z9w.png"]
},
3: {
id: "3",
type: "text"
design: "",
text: "",
parent: "1",
children: ["Lorem ipsum dolor sit amet, consectetur"],
properties: ["m-rh-90","h3"]
},
4: {
id: "4",
type: "text"
design: "",
text: "",
parent: "1",
children: ["Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam mollis, mi sit amet lobortis iaculis, velit leo ultrices ipsum, vitae lobortis dui nibh vitae lacus. In commodo lacus a magna dapibus dignissim."],
properties: ["m-rh-90","small"]
}
}
}
The components can be imported and used using:
import {Container, Image, Text} from "@psytech/react-render-engine";
<Container id={id} template={template} inputStyle={inputStyle} />
<Image id={id} template={template} inputStyle={inputStyle} />
<Text id={id} template={template} inputStyle={inputStyle} />
Where id
is the identification string of the template layout element which needs to be rendered, template
is the structured template data which will be used to render the user interface & inputStyle
is a style object which will be passed to all the child elements from a given node.
MIT
The template element structure was inspired by Notion's data model. Built from 🇮🇳, for the 🌐, by psytech.ai. For questions or suggestions drop us a mail at asxyzp-@-psytech-dot-ai.