CSS Database System
Complete documentation for storing, managing, and serving CSS entirely from the PostgreSQL database. This system eliminates CSS file management issues, prevents duplicates and conflicts, and provides a single source of truth for all styling.
PostgreSQL
XData API
GrapeJS
TMS Web Core
Delphi
System Architecture
GrapeJS
Visual Editor
Visual Editor
→
HTML/CSS
Parser
Parser
→
PostgreSQL
CSS Storage
CSS Storage
→
XData API
JSON
JSON
→
TMS Web Core
Frontend
Frontend
Core Principles
- Single Source of Truth: All CSS lives in the database, never in files
- No Duplicates: Unique constraints prevent duplicate selectors/properties
- No Conflicts: Built-in conflict detection and resolution
- Inheritance: Templates can inherit CSS from parent templates
- Versioning: Full history of template changes
- JSON API: Frontend receives CSS as structured JSON, applies it programmatically
Complete Workflow
1. Design Phase (GrapeJS)
User designs a page visually in GrapeJS. When saved:
- GrapeJS exports HTML structure and CSS rules
- HTML is parsed and converted to Delphi form definition (.pas/.dfm)
- CSS rules are parsed into structured JSON
2. Storage Phase (Database)
- Call
website.createtemplate()with the Delphi source - Call
css.importgrapejscss()with parsed CSS rules - System automatically creates elements, attributes, media queries; detects and logs conflicts; stores version history
3. Retrieval Phase (Frontend)
- Frontend calls
css.gettemplatecssasjson() - Receives complete CSS as JSON including inherited styles
- Applies CSS programmatically to elements
Key Concepts
CSS Variables (Custom Properties)
CSS variables like --primary-color are stored in css.cssvariables and can be:
- Global: Available to all templates (
fktemplateid = NULL) - Template-specific: Override globals for specific templates
-- Set a global variable
SELECT * FROM css.setvariable('--primary-color', '#66b3ff');
-- Set a template-specific override
SELECT * FROM css.setvariable('--primary-color', '#ff6b6b', 123);
CSS Bindings
A "binding" connects a template element to its CSS properties. Each binding specifies:
- Selector: Class name, ID, or element name
- Pseudo-state: Optional (:hover, :active, etc.)
- Media query: Optional responsive breakpoint
- Priority: Order for conflict resolution
Template Hierarchy
Templates can inherit CSS from parent templates:
INSERT INTO website.templatehierarchy
(fkparenttemplateid, fkchildtemplateid, inheritanceorder)
VALUES (1, 2, 1); -- Template 2 inherits from Template 1
CSS Schema Tables
| Table | Purpose | Key Fields |
|---|---|---|
css.cssattributes | CSS property definitions | attributename, datatype, validationrule |
css.cssattributevalues | Actual values assigned to bindings | fkbindingid, fkattributeid, attributevalue, isimportant |
css.csselements | HTML element types | elementname, elementtype, isstandardhtml |
css.cssvariables | CSS custom properties (--var-name) | variablename, variablevalue, fktemplateid, scope |
css.mediaqueries | Responsive breakpoint definitions | queryname, minwidth, maxwidth, customquery |
css.cssanimations | Animation definitions | animationname, keyframes, duration |
css.cssconflictrules | Conflict detection rules | fkattribute1id, resolutionstrategy |
css.cssconflictlog | Log of detected conflicts | fktemplateid, conflictdetails |
Website Schema Tables
| Table | Purpose | Key Fields |
|---|---|---|
website.templates | Delphi form templates | templatename, passource, dfmsource, version |
website.templatecssbindings | Links templates to CSS | fktemplateid, fkelementid, cssclassname, priorityorder |
website.templatehierarchy | Parent-child relationships | fkparenttemplateid, fkchildtemplateid |
website.templateversions | Version history | fktemplateid, versionnumber, passourcesnapshot |
CSS Retrieval Functions
css.gettemplatecssasjson(fktemplateid_, includeinherited_)
Returns all CSS for a template as JSON, including inherited styles. Primary function for frontend CSS loading.
SELECT * FROM css.gettemplatecssasjson(123, true);
css.generatetemplatecss(fktemplateid_, includeinherited_, fkmediaqueryid_)
Generates actual CSS text from stored values. Useful for server-side rendering or debugging.
SELECT * FROM css.generatetemplatecss(123, true, NULL);
css.getvariablesasjson(fktemplateid_)
Returns CSS variables for :root definition.
SELECT * FROM css.getvariablesasjson(NULL); -- Global only
SELECT * FROM css.getvariablesasjson(123); -- Include template overrides
css.getmediaqueries()
Returns all defined media queries with breakpoint values.
SELECT * FROM css.getmediaqueries();
css.getallattributes() / css.getallelements()
Returns all available CSS attributes or HTML elements for dropdown population.
SELECT * FROM css.getallattributes();
SELECT * FROM css.getallelements();
CSS Storage Functions
css.importgrapejscss(fktemplateid_, cssrules_)
Bulk import CSS rules from GrapeJS output. Handles selector parsing, element/attribute creation.
SELECT * FROM css.importgrapejscss(123, '[
{
"selector": ".card",
"priority": 1,
"properties": [
{"name": "background", "value": "#ffffff"},
{"name": "border-radius", "value": "8px"}
]
}
]'::jsonb);
css.addcssbinding(...)
Add a single CSS binding to a template.
SELECT * FROM css.addcssbinding(123, 45, 'btn-primary', NULL, 1, NULL, 'hover');
css.setattributevalue(...)
Set or update a CSS attribute value (upsert).
SELECT * FROM css.setattributevalue(456, 78, '#66b3ff', false, NULL, NULL);
css.setvariable(...)
Set or update a CSS variable.
SELECT * FROM css.setvariable('--primary-color', '#66b3ff');
SELECT * FROM css.setvariable('--primary-color', '#ff6b6b', 123, 'template');
css.cleartemplatecss(fktemplateid_, keepinherited_)
Remove all CSS bindings for a template. Call before reimporting.
SELECT * FROM css.cleartemplatecss(123, true);
Template Functions
website.createtemplate(...)
Create a new template with Delphi source code.
SELECT * FROM website.createtemplate(
'LoginForm', 'User login form', 'Form',
'unit LoginForm; interface...', 'object frmLogin...'
);
website.updatetemplate(...)
Update template source and auto-increment version.
SELECT * FROM website.updatetemplate(123, 'unit...', 'object...', 'Added feature');
website.gettemplate(fktemplateid_)
Retrieve complete template data including CSS as JSON.
SELECT * FROM website.gettemplate(123);
website.listtemplates(templatetype_)
List all templates, optionally filtered by type.
SELECT * FROM website.listtemplates('Form');
SELECT * FROM website.listtemplates(NULL); -- All
Frontend JSON Format
The css.gettemplatecssasjson() returns structured JSON:
{
"templateId": 123,
"variables": [
{"variableName": "--primary-color", "variableValue": "#66b3ff", "scope": "global"}
],
"bindings": [
{
"bindingId": 456,
"selector": ".btn-primary",
"elementName": "button",
"className": "btn-primary",
"cssId": null,
"pseudoState": null,
"mediaQuery": null,
"priority": 1,
"inherited": false,
"attributes": [
{"attributeName": "background-color", "attributeValue": "var(--primary-color)", "isImportant": false},
{"attributeName": "color", "attributeValue": "#ffffff", "isImportant": false}
]
}
]
}
Applying CSS in Frontend
Delphi/TMS Web Core
procedure TWebForm.ApplyCSSFromDatabase(const TemplateId: Integer);
var
Dataset: TAPIDataset;
CSSData: TJSONObject;
begin
Dataset := TAPIDataset.Create(nil);
try
Dataset.Connection := XDataWebConnection;
Dataset.SQL := 'SELECT * FROM css.gettemplatecssasjson(:tid, true)';
Dataset.ParamByName('tid').AsInteger := TemplateId;
Dataset.Open;
if not Dataset.IsEmpty then
begin
CSSData := TJSONObject.ParseJSONValue(
Dataset.FieldByName('cssdata_').AsString) as TJSONObject;
try
ApplyJSON(CSSData);
finally
CSSData.Free;
end;
end;
finally
Dataset.Free;
end;
end;
procedure TWebForm.InjectCSS(const CSS: string);
var
StyleElement: TJSHTMLElement;
begin
StyleElement := TJSHTMLElement(document.getElementById('db-css'));
if not Assigned(StyleElement) then
begin
StyleElement := TJSHTMLElement(document.createElement('style'));
StyleElement.id := 'db-css';
document.head.appendChild(StyleElement);
end;
StyleElement.innerText := CSS;
end;
GrapeJS Integration
// Extract CSS from GrapeJS
function extractCSSRules(editor) {
const css = editor.getCss();
const rules = [];
const sheet = new CSSStyleSheet();
sheet.replaceSync(css);
for (const rule of sheet.cssRules) {
if (rule instanceof CSSStyleRule) {
const properties = [];
for (let i = 0; i < rule.style.length; i++) {
const prop = rule.style[i];
properties.push({
name: prop,
value: rule.style.getPropertyValue(prop),
important: rule.style.getPropertyPriority(prop) === 'important'
});
}
rules.push({ selector: rule.selectorText, priority: rules.length + 1, properties });
}
}
return rules;
}
// Save to database
async function saveTemplate(editor, templateId) {
const cssRules = extractCSSRules(editor);
await api.call('css.cleartemplatecss', { fktemplateid_: templateId });
await api.call('css.importgrapejscss', { fktemplateid_: templateId, cssrules_: cssRules });
}
Conflict Detection
SELECT * FROM css.checkcss(123);
-- Returns:
-- ERROR | selector=body | property=display | effective=flex (prio 2) | all=block (prio 1) | flex (prio 2)
-- WARNING | selector=.card | property=background-color | effective=#fff (prio 2)
| Level | Description | Properties |
|---|---|---|
| ERROR | Critical conflicts on body | Any property on body |
| WARNING | Layout-affecting conflicts | display, position, transform, grid-*, flex-*, background |
| INFO | Minor conflicts | All other properties |
Priority System
- Priority Order: Higher
priorityordervalue wins - !important: Values with
isimportant = trueoverride - Specificity: IDs > Classes > Elements
- Inheritance: Direct bindings override inherited
- Media Queries: More specific queries override general
Best Practices
- Use CSS variables for theme colors
- Keep priority numbers spaced (1, 10, 20) for insertions
- Use inheritance for base styles
- Run
css.checkcss()after major changes - Avoid
!importantexcept for critical overrides