Page Builder — Bản đồ file & Kiến trúc
Tài liệu tham chiếu để agent/dev chỉnh sửa Builder KHÔNG phải mò lại từng file. Đọc file này trước. Số dòng (
:NNN) là mốc gần đúng, có thể lệch sau khi sửa — dùng để định vị, luôn xác nhận lại bằng grep.
1. Builder là gì
Page Builder (Element Builder) là trình dựng trang kéo-thả trong admin. Kiến trúc 2 runtime giao tiếp qua postMessage:
- Editor (trang cha) —
element-builder.js: sidebar element, cây DOM ẩn, cấu hình, undo/redo, autosave, publish. - Preview (iframe) —
element-builder-review.js: bản xem trước "thật", nhận message để vá DOM / cập nhật.
Nguồn dữ liệu duy nhất là BuilderDataStore (JS, rows tree). Khi publish, cây được ghi xuống DB và render ra site cho khách bằng các builder theme-side (Theme{Header,Footer,Home,Page,Layout}).
Có 5 loại section: header, footer, home, page (theo từng post/page/category), layout (layout tùy chỉnh, vd trang danh sách bài viết).
2. Lưu trữ (DB, 4 bảng — cột JSON)
Model (SkillDo\Cms\Models\) | Bảng | Vai trò |
|---|---|---|
ElementBuilderSection | element_builder_sections | Bản chính. 1 row / (key,type). Cột setting (global JSON) + builder (cây widget JSON) + scope. |
ElementBuilderDraft | element_builder_draft | Autosave nháp, 1 row / key_section. |
ElementBuilderHistories | element_builder_histories | Lịch sử publish (append, version tăng dần). Giữ tối đa 20 bản/section. |
ElementBuilderBlock | element_builder_blocks | Block (row) tái sử dụng. |
3. Điểm vào (routes)
Admin (routes/admin.php:94-100) → App\Controllers\Admin\BuilderController:
GET /admin/theme/builder→index(trang tổng builder, viewbuilder-index)GET /admin/theme/builder/{header,footer,home,page}→ mở editor (viewbuilder-page)GET /admin/theme/builder/create→create(viewbuilder-create)GET /admin/theme/builder/layout/{id}→layout
Web preview (routes/web.php:20-25) → App\Controllers\Web\BuilderReviewController (render iframe từ JSON base64 POST lên):
/review/{header,footer,home,page,block,layout}
4. Backend PHP
Engine — packages/skilldo/cms/src/Element/
| File | Vai trò | Hàm chính (mốc dòng) |
|---|---|---|
ElementBuilder.php | Engine trung tâm: đọc/ghi section, render HTML, sinh CSS/asset. | getSection :45 (có memoize/forgetSection :87), header/footer/home/page/layout :20-40, save/saveBuildContent/saveSetting :92-110, handleSectionData :173 (sanitize khi save), buildAssets :721 (thu CSS/JS + column/responsive CSS — walker widgetCss :544), builderColum :855 (instantiate + widget() → HTML), renderColumn :943 (bọc wrapper .elementor-widget), render :1054, rowColumnCss/columnCss :273/:330, sanitizeId/Class :463/:473. |
ElementManager.php | Registry element (singleton). Đọc elements.json (cache theme_elements 24h), lazy new $class(). | getElement (memoized), getElements (eager — dùng cho palette), getIcon, getCategory |
Element.php | Base class abstract cho element. Selector CSS .{key}_{id}. | cssSelector :41 |
ElementPermission.php | Phân quyền builder (role builder cần key kích hoạt, lưu session('builder_permission')). | jsPermission, getTime, hasDownload |
Controllers — app/Controllers/
| File | Vai trò |
|---|---|
Admin/BuilderController.php | Render khung editor (chỉ truyền metadata; cây do JS tải qua ajax). Methods: index/header/footer/home/page/create/layout. |
Web/BuilderReviewController.php | Render preview trong iframe từ JSON base64 (data param). 1 method/scope. Mỗi lần duyệt cây 2 lần: buildAssets(...,'review') + render(...,'review'). |
Ajax — views/admin/app/Ajax/ (đăng ký ở views/admin/bootstrap/ajax.php:85-113)
BuilderAjax.php (21 action):
- Tải:
loadData:158 (cây),loadElement:190 (palette),loadDraft:1075,loadNavigatorLayouts:1134 - Render mảnh:
renderWidget:407 (1 widget → HTML+<style>+classes),renderCss:643,render{Widget,Row,Nested,Column,Settings}Form - Ghi:
save:730 (publish: handleSectionData → 1 UPDATE →Theme*::build→ history + prune 20 → xóa draft),saveDraft:999 (autosave),delete,deleteDraft - Block/Layout:
saveBlock/getBlocks/deleteBlock,createLayout/deleteLayout/setDefaultLayout
BuilderServiceAjax.php (element store + key kích hoạt cho role builder): load/categories/download/install/requestPermission/activePermission/deletePermission.
Render site cho khách — views/theme-store/app/Builders/
Theme{Header,Footer,Home,Page,Layout}.php — mỗi cái có build() (dựng bundle assets/bundle/{scope}.min.css/js), assets() (đăng ký, lazy build nếu file thiếu), render() (đọc section → ElementBuilder::render → HTML). Điểm vào: packages/skilldo/cms/src/Support/ThemeLayoutView.php.
CSS/JS live được cache ra file bundle; HTML KHÔNG cache (dựng lại mỗi request).
getSectionmemoize theo request.
5. Frontend JS
Source ở views/admin/assets/js/bundle/ (KHÔNG phải file chạy). Xem [[js-bundle-build-pipeline]] — build bằng cms:build:js (DevTool terminal) → minify → obfuscator.io.
| Source | Chạy trong | Vai trò |
|---|---|---|
element-builder.js (~3900 dòng) | Editor (cha) → gộp vào script.bundle.js | window.BUILDER_ACTIONS :37 (protocol), BuilderHistoryManager :88 (undo/redo 20 + autosave 10s), SectionBuilder :722 (1 section: sortable, drop, config, sync; insertWidgetToPreview = chèn widget mới), ElementBuilderGlobal :3923 (điều phối, message listener từ iframe :4098, nút header). |
builder-data-store.js | Editor → script.bundle.js | BuilderDataStore — cây rows, this.settings === window.WIDGET_SETTINGS (dùng chung ref). Mutations: addRow/addColumn/addWidget/addNestedRow/moveWidget/reorderWidgets/removeColumn/duplicate*. |
element-builder-studio.js | Editor → script.bundle.js | Modal element store (ElementBuilderStudio) + key kích hoạt. |
element-builder-navigator.js | Editor | Modal chuyển đổi builder. |
element-builder-review.js (~3900 dòng) | Iframe preview → build RIÊNG ra element-builder-review.bundle.js (KHÔNG qua cms:build:js) | Message listener :3600, các handler vá DOM, drag/resize/highlight, init widget. |
File chạy thực tế (đã build): views/admin/assets/js/script.bundle.js (obfuscate) + element-builder-review.bundle.js. Init: builder-page.blade gọi ElementBuilderGlobal.init().
Widget init trong iframe: packages/scripts/core/element.js — elementorFrontend.utils.reloadWidgetContent :105 (thay nội dung wrapper + chạy script), elementsHandler.runReadyTrigger :36 (bắn hook frontend/ready/{data-widget_type}).