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
HTML/CSS
Parser
PostgreSQL
CSS Storage
XData API
JSON
TMS Web Core
Frontend

Core Principles

Complete Workflow

1. Design Phase (GrapeJS)

User designs a page visually in GrapeJS. When saved:

  1. GrapeJS exports HTML structure and CSS rules
  2. HTML is parsed and converted to Delphi form definition (.pas/.dfm)
  3. CSS rules are parsed into structured JSON

2. Storage Phase (Database)

  1. Call website.createtemplate() with the Delphi source
  2. Call css.importgrapejscss() with parsed CSS rules
  3. System automatically creates elements, attributes, media queries; detects and logs conflicts; stores version history

3. Retrieval Phase (Frontend)

  1. Frontend calls css.gettemplatecssasjson()
  2. Receives complete CSS as JSON including inherited styles
  3. Applies CSS programmatically to elements

Key Concepts

CSS Variables (Custom Properties)

CSS variables like --primary-color are stored in css.cssvariables and can be:

-- 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:

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

TablePurposeKey Fields
css.cssattributesCSS property definitionsattributename, datatype, validationrule
css.cssattributevaluesActual values assigned to bindingsfkbindingid, fkattributeid, attributevalue, isimportant
css.csselementsHTML element typeselementname, elementtype, isstandardhtml
css.cssvariablesCSS custom properties (--var-name)variablename, variablevalue, fktemplateid, scope
css.mediaqueriesResponsive breakpoint definitionsqueryname, minwidth, maxwidth, customquery
css.cssanimationsAnimation definitionsanimationname, keyframes, duration
css.cssconflictrulesConflict detection rulesfkattribute1id, resolutionstrategy
css.cssconflictlogLog of detected conflictsfktemplateid, conflictdetails

Website Schema Tables

TablePurposeKey Fields
website.templatesDelphi form templatestemplatename, passource, dfmsource, version
website.templatecssbindingsLinks templates to CSSfktemplateid, fkelementid, cssclassname, priorityorder
website.templatehierarchyParent-child relationshipsfkparenttemplateid, fkchildtemplateid
website.templateversionsVersion historyfktemplateid, 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)
LevelDescriptionProperties
ERRORCritical conflicts on bodyAny property on body
WARNINGLayout-affecting conflictsdisplay, position, transform, grid-*, flex-*, background
INFOMinor conflictsAll other properties

Priority System

  1. Priority Order: Higher priorityorder value wins
  2. !important: Values with isimportant = true override
  3. Specificity: IDs > Classes > Elements
  4. Inheritance: Direct bindings override inherited
  5. 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 !important except for critical overrides