I am in the process of converting every page on this site.
This first project I needed to complete was this site. Without this site I would not be able to show you all of my other projects. The timeline should give you an idea of how long something like this takes. Understand that I have a fulltime job, a 6 month old puppy, am finishing my degree, plus a wife and kids.
Each paragraph, each component (except where the page explicitly states its static), all driven by the database. Example: this section that you are reading.. it is one component with the type of TYPOGRAPHY with properties of text:"", variant:"", align:"", etc.
This is the 4th element in the array of components for this page.
The UIComponent RCFT stores the thought, each json element. The RCFT classifies the Component (is is a page, a text value, an array) while the base record represents the actual key
The Reference RCFT is used to relate two UIComponents together so we can recursively build the JSON Payload for the site.
The Flag RCFT is used to flag a UIComponent as being a function or not. This allows us to use other data in out system for the ui.
This is a complex module consisting of the entire Security domain and its attributes, it locks down specific rows.
This is another complex module consisting of the entire Display domain and its attributes, it drives the content you see.
1
2
3fnc_uicomponent_get_typeid(1,'WEB','CONTAINER','PUBLIC','PAGE')
4fnc_uicomponent_get_typeid(1,'WEB','CONTAINER','PUBLIC','COMPONENT')
5fnc_uicomponent_get_typeid(1,'WEB','CONTAINER','PRIVATE','PAGE')
6fnc_uicomponent_get_typeid(1,'WEB','CONTAINER','PRIVATE','COMPONENT')
7fnc_uicomponent_get_typeid(1,'DATA','COMPONENT','JSON','ARRAY')
8fnc_uicomponent_get_typeid(1,'DATA','COMPONENT','JSON','ELEMENT')
9fnc_uicomponent_get_typeid(1,'DATA','COMPONENT','JSON','NAMELESS ELEMENT')
10fnc_uicomponent_get_typeid(1,'DATA','COMPONENT','VALUE','TEXT')
11fnc_uicomponent_get_typeid(1,'DATA','COMPONENT','VALUE','PROPERTY')
12fnc_uicomponent_get_typeid(1,'DATA','COMPONENT','VALUE','COMPONENT_TYPE')
13fnc_uicomponent_get_typeid(1,'DATA','COMPONENT','VALUE','VISIBLE_FLAG')
14fnc_uicomponent_get_typeid(1,'DATA','COMPONENT','VALUE','DISPLAY_ORDER')
15fnc_uicomponent_get_typeid(1,'DATA','COMPONENT','VALUE','NUMERIC')
16fnc_uicomponent_get_typeid(1,'DATA','COMPONENT','VALUE','URL')
17
18
The definitions above are split into a few obvious pieces, but some are not as obvious. WEB, CONTAINER, PUBLIC and PRIVATE pages / components (require sessions and permissions to view). JSON structures for a NAMELESS ELEMENT, an array of objects, ARRAY and our standard ELEMENT with children. The family of VALUEs (coincidental), is where I added some additional forethought.
I am going to be translating this site into several languages. But I will be storing the data, I wont be using an API all the time. This saves me a ton of money, but more importantly: our content system is more powerful and capable than 99% of those that exist. I can change content based on what I know about YOU. Gender specific, your local time, how many times you view this page... anything. So that is where TEXT differs from the other 6 UI Component Types. A PROPERTY is something like: body1, or primary.main. Something we do not want to be translated.
I will go over the content model in greater detail later, but for now... Assume that every single piece of readable text you see, originates from one Subject (Display). Every single table that has content, has records in this table.
TLDR: A UI Component is typed / classified, and is just an item. It contains 0 content. It does have ATTRIBUTEs, though.
Again, UI Component does not have a TEXT value. Every record in our system has a JSONName column. I used to call it header, but this is 2024.
1
2
3fnc_reference_get_typeid(1,'UICOMPONENT','UICOMPONENT','DIRECT','CHILD')
4fnc_flag_get_typeid(1,'UICOMPONENT','FUNCTIONALITY','DATA_RETRIEVAL','IS_FUNCTION')
5
6
The two primary things we need to know about a UI Component (outside of its own RCFT). Does it call a function? Does it have children? Calling a function allows us to pull data from other parts of the system (which will also be translated). The reference piece drives the recursive fetch of the function that compiles UI Component data for this site.
While I love sharing code, nothing is free in this world. You dont get the full code, just concepts.
The top most function takes in a _session_guid, _tenant_guid, _uicomponent_guid, and a _json_payload. _json_payload lets me modify returns without adjusting the inputs to the function. The guids allow me to move data between systems. Nothing worse than sending in an Id and merging a system where that Id is taken.
1
2
3_uijson jsonb = (
4 select json_agg(vtbl_uicomponent_select)
5 from vtbl_uicomponent_select
6 where uicomponent_groupid_tenant = _tenant
7 and uicomponent_guid = _uicomponent_guid
8 )
9
10
This is the quickest way to gain access to everything in the record. We do not know if we need to add a feature later, and what we need from the row... so we get it all. Trust me, this dramatically reduces insert and update complexities and dramatically reduces new feature development.
Next, get the children.
1
2
3_uijson_child = fnc_uicomponent_get_children_json(
4 _tenant
5 , (_uicomp->>'uicomponentid') :: udt_foreignkey
6 );
7
8
I like the simplicity of this paradigm. Get the children, loop over children, and recursively call this procedure. If there are no children, you have hit a base record that probably has content or calls a function.
1
2
3IF _uijson_child IS NOT NULL THEN
4 FOR _uicomp_child IN SELECT * FROM jsonb_array_elements(_uijson_child)
5 LOOP
6 _this_json := fnc__api_uicomponent_get(_tenant ,( _uicomp_child->>'uicomponent_guid'):: udt_rowguid);
7 IF _this_typekeyname = 'ARRAY' THEN
8 -- Add to JSON Parent
9 ELSE
10 -- Concat JSON
11 END IF;
12 _this_count:= _this_count+1;
13 END LOOP;
14
15 IF _this_typekeyname NOT IN ('ARRAY') THEN
16 -- Add to JSON Parent
17 END IF;
18ELSE
19
20-- where we get the actual values
21 IF _flagtype_isfunction = true THEN
22 _this_value = fnc__uicomponent_execute(_tenant,_session,_uicomp);
23 -- Add to JSON as value / array / element
24 ELSE
25 _this_value = fnc__display_get_content(_tenant,_session,_uicomp);
26 -- Add to json as value
27 END IF;
28END IF;
29
30
and thats it, the JSON is sent to the UI.
Nothing much to see here. Get JSON, .map(GetDynamicComponent) based on the COMPONENT_TYPE defined. Because the COMPONENT_TYPE value is considered display content... I can change the value based on where you are and what I know about you.. providing a truly customizable experience for the user.
1
2
3{
4 'TYPOGRAPHY':<CustomTypographyComponent {..x}/>
5 ,'CODE HIGHLIGHT':<CustomCodeHighlightComponent {..x}/>
6 ...
7}
8
9
Each component is expecting particular properties, each of these properties are UIComponents. This allows us to dramatically change the UI based on the user, or group (client, company, tenant). I will demonstate this later, once I get logins and auths setup.
When I am finished with the data entry part (about 1 page per 20 mins + new component design time), every page on this site will look like this:
1
2
3const { pagedata } = props
4
5let pagedata_filtered = pagedata ?
6 pagedata.page.dynamic_components.filter(x => { return x.visible === '1' })
7 : []
8let pagedata_sorted = _.sortBy(pagedata_filtered
9 , function (obj) {
10 return parseInt(obj.order, 10)
11 })
12
13return (
14
15 <Box my={2}>
16 <Grid container direction={"row"} alignItems={"center"} justifyContent={"center"} spacing={2}>
17 {
18 pagedata_sorted.map((x, i) => {
19 return (
20 <Grid item xs={12} md={8} key={"dynamic_component_" + i}>
21 <UIComponent component={x} />
22 </Grid>
23 )
24 })
25 }
26 </Grid>
27 </Box>
28)
29
30