# JavaScript Architecture — CMS Builder

This document describes the JavaScript architecture of the Bongo visual page builder. It covers the source code under `resources/backend/js/`, the compiled output under `public/backend/`, and the iframe-hosted pages that run inside the editor.

---

## Table of Contents

1. [Overview](#overview)
2. [Build System](#build-system)
3. [Two-Layer Architecture](#two-layer-architecture)
4. [ContentBox — Section Editor](#contentbox--section-editor)
5. [ContentBuilder — Cell Editor](#contentbuilder--cell-editor)
6. [Initialisation Sequence](#initialisation-sequence)
7. [Sub-Systems](#sub-systems)
   - [Grid / Row / Column](#grid--row--column)
   - [Rich Text Editor (RTE)](#rich-text-editor-rte)
   - [Element Handling](#element-handling)
   - [Element-Type Specific Handlers](#element-type-specific-handlers)
   - [Colour and Gradient Pickers](#colour-and-gradient-pickers)
   - [Utilities and Helpers](#utilities-and-helpers)
8. [Iframe-Hosted Pages](#iframe-hosted-pages)
9. [Plugin System](#plugin-system)
10. [Global Variables](#global-variables)
11. [DOM Structure and CSS Conventions](#dom-structure-and-css-conventions)
12. [CSS Architecture](#css-architecture)
13. [Event Patterns and Data Flow](#event-patterns-and-data-flow)
14. [Key Design Patterns](#key-design-patterns)

---

## Overview

The builder is a self-contained jQuery-based visual page editor. It operates entirely in the browser with no framework dependency beyond jQuery. The editor composes two cooperating layers: **ContentBox** manages top-level sections, and **ContentBuilder** manages the grid columns and inline content within each section.

```
Browser Page
└── $.contentbox(element, opts)          ← jQuery plugin entry point
    ├── ContentBox                        ← Section-level editor
    │   ├── Sidebar
    │   ├── Modal
    │   ├── EditBox (per-section settings)
    │   └── LinkCleaner
    └── ContentBuilder (one per content area)
        ├── Grid / Row / Column tools
        ├── RTE (Rich Text Editor)
        ├── ElementPanel + per-type handlers
        ├── ColorPicker / GradientPicker
        └── Plugins (buttons, symbols, ...)
```

---

## Build System

| Item | Path |
|------|------|
| JS entry point | `resources/backend/js/editor.js` |
| Compiled JS | `public/backend/editor.js` |
| SCSS entry point | `resources/backend/sass/editor.scss` |
| Compiled CSS | `public/backend/editor.css` |
| Build config | `webpack.mix.js` (Laravel Mix 6) |

Build commands (run from `cms/builder/`):

```bash
npm run dev    # development build with source maps
npm run watch  # rebuild on file change
npm run prod   # minified production build
```

`editor.js` imports all sub-module JS files and exports the jQuery plugin `$.contentbox`. The SCSS entry imports all partials from `resources/backend/sass/contentbuilder/` and `contentbox/`.

---

## Two-Layer Architecture

```
┌─────────────────────────────────────────────────────┐
│  ContentBox  (window._box)                          │
│  Section-level: add / delete / reorder sections,   │
│  section backgrounds, per-section EditBox settings  │
│                                                     │
│  ┌───────────────────────────────────────────────┐  │
│  │  ContentBuilder  (window._cb)                 │  │
│  │  Cell/column-level: grid editing, RTE,        │  │
│  │  element tools, images, links, modules        │  │
│  │                                               │  │
│  │  (one ContentBuilder instance per             │  │
│  │   opts.container content area)                │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
```

**ContentBox** (`contentbox/contentbox.js`) is the jQuery plugin wrapper. It sets `window._box = this` and owns the page-level sidebar panel. It creates one or more ContentBuilder instances.

**ContentBuilder** (`contentbuilder/contentbuilder.js`) is the main engine. It sets `window._cb = this`, tracks all active-element state, and coordinates every sub-system.

---

## ContentBox — Section Editor

File: `contentbox/contentbox.js`

Responsibilities:
- Add, delete, and reorder page sections
- Manage section background (colour, image, video)
- Open the sidebar panel for section-level settings
- Instantiate `EditBox` for per-section configuration

Sub-classes:

| Class | File | Purpose |
|-------|------|---------|
| `EditBox` | `contentbox/EditBox.js` | Per-box settings panel (General, Content, Text, Image, Module, HTML tabs) |
| `Sidebar` | `contentbox/Sidebar.js` | ContentBox sidebar panel |
| `Modal` | `contentbox/Modal.js` | Modal base class used by ContentBox |
| `LinkCleaner` | `contentbox/LinkCleaner.js` | Strip unwanted attributes from pasted links |

---

## ContentBuilder — Cell Editor

File: `contentbuilder/contentbuilder.js`

Active-element state tracked on the instance:

| Property | Holds |
|----------|-------|
| `activeCol` | Currently selected column element |
| `activeImage` | Selected `<img>` element |
| `activeIcon` | Selected icon element |
| `activeLink` | Selected `<a>` element |
| `activeModule` | Active module iframe element |
| `activeIframe` | Active iframe element |
| `activeSpacer` | Active spacer element |
| `activeCodeBlock` | Active `data-html` code block |

---

## Initialisation Sequence

The ContentBuilder constructor runs in this order:

```
1.  ContentBuilderConfig.js    — apply config defaults
2.  Language + legacy options  — normalise option names
3.  Dom (DomHelper wrapper), Util (builder helpers)
4.  Device detection, preferences, undo/redo
5.  SVG icons injection        — inject <svg> symbol defs into page
6.  Grid editor render         — build GridEditor floating panel HTML
7.  Snippet panel render       — build snippet sidebar HTML
8.  ColumnTool, ElementTool    — floating toolbars
9.  Element handlers           — ElementImage, ElementLink, etc.
10. Rte                        — Rich Text Editor
11. Tooltip                    — data-title hover tips
12. applyBehavior()            — make content areas contenteditable
13. init()                     — tabs, colour picker, plugins, event
                                 handlers, convenience variables
                                 (sets window._cb)
```

---

## Sub-Systems

### Grid / Row / Column

The grid is built on Bootstrap `col-md-*` classes. Column widths step through `col-md-1` to `col-md-12`.

```
resources/backend/js/contentbuilder/
├── grid.js          — pure mutation operations (no UI)
├── rowtool.js       — row toolbar
├── rowaddtool.js    — + button at row bottom
├── columntool.js    — column toolbar
├── quickadd.js      — quick-add popover
└── grideditor.js    — draggable floating grid editor panel
```

**grid.js** — stateless mutation functions: `moveColumnLeft`, `moveColumnRight`, `moveColumnUp`, `moveColumnDown`, `increaseColumn`, `decreaseColumn`, `addColumn`, `removeColumn`, `duplicateColumn`, `moveRowUp`, `moveRowDown`, `removeRow`, `duplicateRow`.

**rowtool.js** — row toolbar containing: drag handle, More popover (Move Up/Down, Duplicate, View HTML), Grid Editor toggle, Delete.

**rowaddtool.js** — the `+` button rendered at the bottom of each row; opens QuickAdd.

**columntool.js** — column toolbar containing: Add, More popover (Move Left/Right/Up/Down, Increase/Decrease width, Duplicate, View HTML), Delete.

**quickadd.js** — popover for inserting content elements. Supported types: Paragraph, Headline, H1–H3, Image, List, Quote, Pre, Table, Spacer, Button, More. Operates in modes `row`, `elm`, `cell-left`, `cell-right`.

**grideditor.js** — floating draggable panel that combines all row and column controls into a single unified interface.

---

### Rich Text Editor (RTE)

The RTE uses a component-based architecture with a custom event bus built on the browser `CustomEvent` API.

```
resources/backend/js/contentbuilder/rte/
├── RteComponent.js          — base class (emit/listen, lifecycle)
├── RteButton.js             — base for all buttons
├── dropdowns/
│   └── RteDropdown.js       — base for all dropdowns
├── RteMainToolbar.js        — text-editing toolbar
├── RteSecondaryToolbar.js   — element-editing toolbar (images, spacers, etc.)
├── RteClickedElement.js     — determines which toolbar to show
├── buttons/                 — 26 button classes
├── dropdowns/               — 12 dropdown classes
└── Rte.js                   — orchestrator
```

**Component lifecycle:**

```
RteComponent
  ├── emit(eventName, detail)     — window.dispatchEvent(CustomEvent)
  ├── listen(eventName, handler)  — window.addEventListener
  ├── toggle() / show() / hide()
  └── activate() / deactivate()
```

**RteButton** extends `RteComponent`. All buttons share `alignText()` for positioning relative to selected text.

**RteDropdown** extends `RteComponent`. The `show()` method calls `init()` (which is guarded by `hasBeenInitialised` to prevent duplicate event listener registration). The `position()` method positions the dropdown relative to the button that emitted the open event.

**Communication pattern:**

```
Button emits CustomEvent  →  Dropdown listens and opens/closes
                              Dropdown positions itself relative to
                              the emitting button's screen coordinates
```

Example: a colour button emits `rte-color-dropdown:toggle` with `{element: this.asDTO()}`, and the colour dropdown listens for that event name.

**Rte.js** orchestrator:
- Registers all button and dropdown components
- `init()` wires all event listeners
- `getState()` reads the current selection to set active/inactive feedback on buttons
- `handleClickedElement()` decides whether to show main or secondary toolbar based on the clicked element type

**RteClickedElement.js** maps the clicked DOM element to a toolbar decision. Images, spacers, iframes, and modules show the secondary toolbar. Text and inline elements show the main toolbar.

---

### Element Handling

```
resources/backend/js/contentbuilder/
├── elements.js              — aggregator, initialises all element sub-classes
├── elementtool.js           — floating toolbar on active element
└── elementpanel*.js         — side panel with 11 CSS editing tabs
```

**elementtool.js** — floating toolbar on the selected element. Actions: Add, Delete, Move Up, Move Down, Duplicate, Settings.

**Element panel tabs** (each in its own file):

| File | Tab | Controls |
|------|-----|---------|
| `elementpanel-box.js` | Box | Background colour/gradient, dimensions, background image |
| `elementpanel-spacing.js` | Spacing | Padding and margin per-side with unit selector |
| `elementpanel-border.js` | Border | Style, width, colour per-side |
| `elementpanel-corner.js` | Corners | Border-radius per-corner |
| `elementpanel-text.js` | Text | Font-size, weight, colour, line-height, letter-spacing, transform, align |
| `elementpanel-shadow.js` | Shadow | Box-shadow (x, y, blur, spread, colour, inset) |
| `elementpanel-display.js` | Display | Display type selector |
| `elementpanel-position.js` | Position | Position type, top/right/bottom/left, z-index |
| `elementpanel-effect.js` | Effects | Opacity, transform, overflow, cursor |
| `elementpanel-attribute.js` | Attributes | Arbitrary HTML attribute editor |
| `elementpanel-animation.js` | Animation | AOS type, delay, duration, offset, easing |
| `elementpanel-css.js` | CSS | Raw inline CSS + class editor modal |

---

### Element-Type Specific Handlers

Each content element type has a dedicated handler:

| File | Element | Key behaviour |
|------|---------|--------------|
| `elementimage.js` | `<img>` | Image Manager modal: src, library, upload, mode (crop/resize/fit), preset, quality, alt, lazy-load, lightbox, link CTA |
| `elementhyperlink.js` | `<a>` | Floating Edit/Duplicate/Delete bar; Create Link modal with URL, file select, new-window, text, title |
| `elementiframe.js` | `<iframe>` | YouTube/Vimeo src or raw embed code |
| `elementtable.js` | `<table>` | Style tab + Layout tab, apply-to scope selector |
| `elementspacer.js` | `.spacer` | Height increase/decrease cycling through `height-20` to `height-300` classes |
| `elementmodule.js` | module | Iframe-based settings; reads `data-module` attribute |
| `elementcode.js` | `data-html` | Custom code editor for raw HTML blocks |

---

### Colour and Gradient Pickers

**colorpicker.js** — Swatch grid of 21 colours, opacity slider, black/white quick-pick buttons, Clear, hex input field, More (expands to HSL controls). Used by: element panel Box tab, RTE colour button, table settings.

**gradientpicker.js** — 19 preset gradients, from/to colour selectors (each delegating to colorpicker), angle control.

**config/ColorPickerConfig.js** — Defines the default 21-colour palette used by colorpicker.js.

---

### Utilities and Helpers

**util.js** — Exposes two classes:
- `Dom` — wraps `DomHelper` for convenience
- `Util` — builder-specific helpers: `cellSelected`, `builderStuff`, `showModal`/`hideModal`, `confirm`, `addContent`, `clearControls`, `out` (i18n), `saveSelection`/`restoreSelection`, `pasteHtmlAtCaret`

**shared/DomHelper.js** — Low-level static DOM operations: `createElement`, `appendHtml`, `addClass`/`removeClass`, `parentsHasClass`/`parentsHasTag`/`parentsHasAttribute`, `selectElementContents`, `textSelection`, `getStyle`, `moveCursorToElement`.

**shared/UtilHelper.js** and **shared/StringHelper.js** — Shared utility helpers used across sub-systems.

**undoredo.js** — Stores up to 120 HTML snapshots. `saveForUndo()` checks for change before saving (deduplication). `undo()`/`redo()` restore via `document.createContextualFragment`.

**draggable.js** — Mouse and touch drag support for floating modals. Triggers on elements with the `.is-draggable` class.

**tooltip.js** — Shows tooltips on hover for elements with `data-title` attributes.

**dropdown.js** — Generic selectbox open/close/onChange handler for standard `<select>`-replacement dropdowns.

**tabs.js** — Tab-panel switcher for `.is-tabs` groups.

**saveimages.js** — Batch converts inline `data:` image sources to server URLs. Scans content for base64 `src` values, POSTs each to the server, and polls until complete.

**svgicons.js** — Injects a block of SVG `<symbol>` definitions into the page on load. All builder icons reference these symbols via `<use>`.

**html.js** — HTML source viewer modals: `viewhtml`, `viewhtmlformatted`, `viewhtmllarger`.

**preferences.js** — Preferences modal: builder mode, outline display, tool position, paste mode, and other editor behaviour settings.

---

## Iframe-Hosted Pages

These pages live under `public/backend/` and are loaded into `<iframe>` elements inside the editor. They communicate back to the parent window via `parent._cb` or `parent._box` references.

| Page | JS File | Parent callback |
|------|---------|----------------|
| Icon picker | `icons/js/icons.js` | `parent._cb.addIcon(className)` |
| Font picker | `fonts/js/fonts.js` | `parent._cb.setFont(family, style, display, provider)` or `clearFont()` |
| Image library | `images/js/images.js` | `parent.selectImage(src)` |
| File manager | `files/js/files.js` | `parent.selectFile(url)` |
| Design browser | `designs/js/designs.js` | `parent._cb.addSnippet(html)` |
| Snippet browser | `snippets/js/snippets.js` | `parent._cb.addSnippet(html)` |
| Style browser | `styles/js/styles.js` | Applies CSS classes to sections |
| Module settings | `modules/js/*.js` | Per-module (post, gallery-builder, menu-builder, form-builder, contact, review, project, slider, question) |

The iframe communication pattern is intentionally simple: the child page calls a function on `parent` (or `parent._cb`), passing a single value. No message-passing protocol is used.

---

## Plugin System

Plugins are loaded at runtime from the path configured in `opts.pluginPath`.

**Configuration file:** `public/backend/plugins/config.js`

```js
_cb.settings.plugins = ['buttons', 'symbols'];
```

Each plugin calls two methods on the ContentBuilder instance to inject toolbar buttons:

```js
_cb.addButton(pluginName, html, selector, callback)
_cb.addButton2(pluginName, html, selector, callback)
```

`addButton` injects into the main RTE toolbar; `addButton2` injects into the secondary toolbar.

**Active plugins:**

| Plugin | Purpose |
|--------|---------|
| `buttons` | Button Editor — style and link editor for `<a class="button">` elements |
| `symbols` | Special Characters — Unicode symbol insert picker |

**Available (inactive) plugins:**

| Plugin | Purpose |
|--------|---------|
| `showgrid` | Overlay column grid guide |
| `preview` | Full-page preview |
| `searchreplace` | Find and replace text |
| `wordcount` | Word and character count |

---

## Global Variables

| Variable | Set by | Value |
|----------|--------|-------|
| `window._cb` | `ContentBuilder.setConvenienceVariables()` | ContentBuilder instance |
| `window._box` | `ContentBox` constructor | ContentBox instance |
| `window.applyLargerImage` | ContentBuilder | Callback for image-size change from iframe |
| `window.selectFile` | ContentBuilder | Callback invoked by file manager iframe |
| `window.selectImage` | ContentBuilder | Callback invoked by image library iframe |
| `window.imageLoaded` | ContentBuilder | Callback after image upload completes |

---

## DOM Structure and CSS Conventions

### Root container

All builder UI (toolbars, modals, popovers) is injected into a single root element:

```html
<div id="_cbhtml">
  <!-- all builder UI lives here -->
</div>
```

Editable content areas carry `.is-builder`. The grid uses standard Bootstrap column classes (`col-md-*`) inside `.row` wrappers.

### State classes

| Class | Meaning |
|-------|---------|
| `.cell-active` | Currently selected column |
| `.row-active` | Row containing the active column |
| `.row-outline` | Row under hover |
| `.builder-active` | The active `.is-builder` content area |

### UI element classes

| Class | Element |
|-------|---------|
| `.is-rte-tool` | Main RTE toolbar |
| `.is-elementrte-tool` | Secondary RTE toolbar |
| `.rte-dropdown` | RTE dropdown panels |
| `.rte-more-options` | More dropdown (dark background, toolbar extension) |
| `.is-row-tool` | Row toolbar |
| `.is-column-tool` | Column toolbar |
| `.is-element-tool` | Element floating toolbar |
| `.is-modal` | All modals (`.is-modal.active` = visible) |
| `.is-pop` | Popover panels |
| `.is-sidebar` | Side panels |

---

## CSS Architecture

Entry: `resources/backend/sass/editor.scss` imports all partials from `contentbuilder/` and `contentbox/` subdirectories.

All styles are scoped under `#_cbhtml` or `body.web-editor`, preventing leakage into the host page.

**Key partial files:**

| Partial | Covers |
|---------|--------|
| `_rte_tool.scss` | RTE toolbar and all dropdown panels |
| `_button.scss` | Button reset and button variants |
| `_modal.scss` | Modal overlay and dialog |
| `_pop.scss` | Popover panels |
| `_tool.scss` | Row, column, and element floating toolbars |
| `_color_picker.scss` | Colour picker swatch and controls |
| `_sidebar.scss` | Sidebar panel layout |
| `_tabs.scss` | Tab group switcher |

**Z-index stack:**

```
modals          10004
pops / sidebar  10003
dropdowns       10002
toolbars        10001
```

**Animations:** Dropdown open/close uses `slideInDown` / `slideOutDown` keyframe animations (0.5 s) from the `animate.css` vendor library.

---

## Event Patterns and Data Flow

### RTE event bus

The RTE components communicate exclusively via `CustomEvent` on `window`:

```
button.emit('rte-align-dropdown:toggle', { element: this.asDTO() })
  → window.dispatchEvent(new CustomEvent(...))
    → dropdown.listen('rte-align-dropdown:toggle', handler)
```

No direct object references between buttons and dropdowns. Dropdowns position themselves using the coordinates in the event payload.

### Click-outside management

A single `doDocumentClick` handler on `document` manages click-outside close behaviour for all tools, modals, and dropdowns. Each component checks whether the click target is inside its own element before deciding to close.

### Text selection persistence

When a modal or dropdown opens, the active text selection is saved:

```js
builder.selection = util.saveSelection()  // stores Range object
```

On close, the selection is restored before applying the formatting action:

```js
util.restoreSelection(builder.selection)
```

This prevents the browser losing the selection when focus moves to a modal input.

### Mutation lifecycle

Every operation that changes content follows this sequence:

```
saveForUndo()        — snapshot current HTML
↓
mutation             — DOM change (grid, text, element)
↓
getState()           — update RTE button active states
↓
saveSelection()      — persist current selection
↓
onChange()           — fire user-supplied callback
```

---

## Key Design Patterns

**jQuery plugin entry point.** `$.contentbox(element, opts)` is the sole public API. All internal classes are private to the module closure.

**Singleton globals.** `window._cb` and `window._box` are set once and referenced by iframe child pages. This is intentional and required for cross-iframe communication.

**hasBeenInitialised guard.** RTE dropdowns use a boolean flag to ensure `init()` (which registers event listeners) is called only once, preventing duplicate handler accumulation across show/hide cycles.

**Aggregator pattern.** `elements.js` does not contain element logic itself; it instantiates and wires together all the element-type handlers (`ElementImage`, `ElementHyperlink`, etc.).

**Pure mutation functions.** `grid.js` contains only DOM mutation logic with no UI concerns. Toolbar files (`rowtool.js`, `columntool.js`) handle UI rendering and delegate mutations to `grid.js`.

**Config object defaults.** `config/ContentBuilderConfig.js` defines all option defaults, keeping the main constructor free of default-value clutter.

**Iframe delegation.** Complex pickers (icons, fonts, images, files, designs, snippets) run as independent HTML pages inside iframes. This isolates their CSS and JS from the main editor and simplifies their own styling.
