Skip to content

Commit e9c4b89

Browse files
committed
feat(editor): add fluent API for binding live updates to elements
- Introduced `LiveUpdatesBuilder` for chaining live update bindings in Blade (`text()`, `html()`, `outerHtml()`, `attr()`, `style()`). - Enhanced JavaScript logic to support new `data-live-update-*` attributes with type and target parsing. - Updated documentation with examples demonstrating fluent usage.
1 parent c36f76c commit e9c4b89

File tree

8 files changed

+210
-70
lines changed

8 files changed

+210
-70
lines changed

docs/building-theme/adding-sections/integrating-editor.md

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,36 +68,87 @@ Bagisto Visual supports this via:
6868

6969
### Option 1: `liveUpdate()` Blade Directives
7070

71-
Use `$section->liveUpdate()` or `$block->liveUpdate()` to wire an element to a setting.
71+
Use `$section->liveUpdate()` or `$block->liveUpdate()` to bind settings to elements.
7272

73-
#### Text Content
73+
These helpers inject metadata to enable the editor to update the live preview without requiring a server-side re-render.
74+
75+
#### `->text(string $settingId)`
76+
77+
**Updates the element's `textContent`** whenever the specified setting changes.
78+
79+
```blade
80+
<h1 {{ $section->liveUpdate()->text('heading') }}>
81+
{{ $section->settings->heading }}
82+
</h1>
83+
```
84+
85+
#### `->html(string $settingId)`
86+
87+
**Updates the element's `innerHTML`** with the new setting value.
7488

7589
```blade
76-
<h2 {{ $section->liveUpdate('title') }}>
77-
{{ $section->settings->title }}
78-
</h2>
90+
<div {{ $section->liveUpdate()->html('html_content') }}>
91+
{!! $section->settings->html_content !!}
92+
</div>
7993
```
8094

81-
#### Element Attributes
95+
#### `->outerHtml(string $settingId)`
96+
97+
**Replaces the entire element (`outerHTML`)** with the setting value.
8298

8399
```blade
84-
<img src="{{ $section->settings->image }}" {{ $section->liveUpdate('image', 'src') }}>
100+
<div {{ $section->liveUpdate()->outerHtml('html_block') }}>
101+
{!! $section->settings->html_block !!}
102+
</div>
85103
```
86104

87-
#### Inside Blocks
105+
#### `->attr(string $settingId, string $attributeName)`
106+
107+
**Updates the specified HTML attribute** (e.g. `src`, `href`, `alt`) with the setting value.
108+
109+
```blade
110+
<img
111+
src="{{ $section->settings->image }}"
112+
{{ $section->liveUpdate()->attr('image', 'src') }}>
113+
```
114+
115+
#### `->style(string $settingId, string $property)`
116+
117+
**Updates a specific CSS style property** on the element using the setting value.
118+
119+
```blade
120+
<div
121+
style="width: {{ $section->settings->width }}"
122+
{{ $section->liveUpdate()->style('width', 'width') }}>
123+
</div>
124+
```
125+
126+
#### 🔹 Multiple Bindings on a Single Element
127+
128+
You can bind multiple settings fluently to different parts of the same element:
129+
130+
```blade
131+
<a
132+
href="{{ $section->settings->link_url }}"
133+
{{ $section->liveUpdate()
134+
->text('link_text')
135+
->attr('link_url', 'href') }}>
136+
{{ $section->settings->link_text }}
137+
</a>
138+
```
139+
140+
#### 🔹 Working Inside Blocks
141+
142+
Works seamlessly inside dynamic or repeated blocks:
88143

89144
```blade
90145
@foreach ($section->blocks as $block)
91146
<p {{ $block->liveUpdate('text') }}>
92-
{{ $block->settings['text'] }}
147+
{{ $block->settings->text }}
93148
</p>
94149
@endforeach
95150
```
96151

97-
These helpers inject metadata to enable the editor to update content without requiring a server-side re-render.
98-
99-
---
100-
101152
### Option 2: JavaScript API (`Visual.handleLiveUpdate()`)
102153

103154
For more complex cases (e.g. multiple targets, transform logic, or styling), use `Visual.handleLiveUpdate()`:

public/vendor/bagistoplus/visual/editor/assets/injected-NBYyEDlC.js renamed to public/vendor/bagistoplus/visual/editor/assets/injected-CVeaO1fW.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/vendor/bagistoplus/visual/editor/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
]
4949
},
5050
"resources/assets/editor/injected.ts": {
51-
"file": "assets/injected-NBYyEDlC.js",
51+
"file": "assets/injected-CVeaO1fW.js",
5252
"name": "injected",
5353
"src": "resources/assets/editor/injected.ts",
5454
"isEntry": true

resources/assets/editor/injected.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -424,18 +424,44 @@ class ThemeEditor {
424424
settingValue: any;
425425
}): boolean {
426426
const { section, block, settingId, settingValue } = data;
427-
const key = [section?.id, block?.id, settingId].filter(Boolean).join(':');
428-
429-
const el = document.querySelector(`[data-live-update-key="${key}"]`) as HTMLElement;
427+
const key = [section?.id, block?.id, settingId].filter(Boolean).join('.');
428+
const attrName = `data-live-update-${key}`;
429+
const selector = `[${CSS.escape(attrName)}]`;
430430

431+
const el = document.querySelector(selector) as HTMLElement;
431432
if (!el) {
432433
return false;
433434
}
434435

435-
if (el.dataset.liveUpdateAttr) {
436-
el.setAttribute(el.dataset.liveUpdateAttr, settingValue);
437-
} else {
438-
el.textContent = settingValue;
436+
const type = el.getAttribute(attrName);
437+
const [updateType, updateKey] = type?.split(':') ?? ['text', undefined];
438+
439+
switch (updateType) {
440+
case 'text':
441+
el.textContent = settingValue;
442+
break;
443+
case 'html':
444+
el.innerHTML = settingValue;
445+
break;
446+
case 'outerHTML':
447+
el.outerHTML = settingValue;
448+
break;
449+
case 'attr':
450+
if (!settingValue) {
451+
el.removeAttribute(updateKey as string);
452+
} else {
453+
el.setAttribute(updateKey as string, settingValue);
454+
}
455+
break;
456+
case 'style':
457+
if (!settingValue) {
458+
el.style.removeProperty(updateKey as string);
459+
} else {
460+
el.style.setProperty(updateKey as string, settingValue);
461+
}
462+
break;
463+
default:
464+
console.warn(`Unknown live update type: ${updateType}`);
439465
}
440466

441467
return true;

src/Sections/Support/BlockData.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,18 @@ public function jsonSerialize(): mixed
5252
];
5353
}
5454

55-
public function liveUpdate(string $settingId, ?string $attribute = null): LiveUpdateData
55+
public function liveUpdate(?string $settingId = null, ?string $attr = null): LiveUpdatesBuilder
5656
{
57-
return new LiveUpdateData(
58-
settingId: $settingId,
59-
attribute: $attribute,
60-
sectionId: $this->sectionId,
61-
blockId: $this->id,
62-
);
57+
$builder = new LiveUpdatesBuilder(sectionId: $this->sectionId, blockId: $this->id);
58+
59+
if ($settingId && $attr) {
60+
return $builder->attr($settingId, $attr);
61+
}
62+
63+
if ($settingId) {
64+
return $builder->text($settingId);
65+
}
66+
67+
return $builder;
6368
}
6469
}

src/Sections/Support/LiveUpdateData.php

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
namespace BagistoPlus\Visual\Sections\Support;
4+
5+
use BagistoPlus\Visual\Facades\ThemeEditor;
6+
use Illuminate\Contracts\Support\Htmlable;
7+
8+
/**
9+
* This class is used to build live updates metadata for a specific section or block in the theme editor.
10+
* It allows adding various types of updates such as text, HTML, outer HTML, attributes, and styles.
11+
*/
12+
class LiveUpdatesBuilder implements Htmlable
13+
{
14+
protected string $sectionId;
15+
16+
protected ?string $blockId;
17+
18+
protected array $updates = [];
19+
20+
public function __construct(string $sectionId, ?string $blockId = null)
21+
{
22+
$this->sectionId = $sectionId;
23+
$this->blockId = $blockId;
24+
}
25+
26+
protected function makeKey(string $settingId): string
27+
{
28+
return collect([
29+
'section' => $this->sectionId,
30+
'block' => $this->blockId,
31+
'setting' => $settingId,
32+
])->filter()->implode('.');
33+
}
34+
35+
protected function add(string $settingId, string $type): self
36+
{
37+
$this->updates[] = [
38+
'key' => $this->makeKey($settingId),
39+
'type' => $type,
40+
];
41+
42+
return $this;
43+
}
44+
45+
public function text(string $settingId): self
46+
{
47+
return $this->add($settingId, 'text');
48+
}
49+
50+
public function html(string $settingId): self
51+
{
52+
return $this->add($settingId, 'html');
53+
}
54+
55+
public function outerHtml(string $settingId): self
56+
{
57+
return $this->add($settingId, 'outerHTML');
58+
}
59+
60+
public function attr(string $settingId, string $attr): self
61+
{
62+
return $this->add($settingId, 'attr:'.$attr);
63+
}
64+
65+
public function style(string $settingId, string $style): self
66+
{
67+
return $this->add($settingId, 'style:'.$style);
68+
}
69+
70+
public function toHtml(): string
71+
{
72+
return $this->__toString();
73+
}
74+
75+
public function __toString(): string
76+
{
77+
if (! ThemeEditor::inDesignMode()) {
78+
return '';
79+
}
80+
81+
return collect($this->updates)->map(function ($update) {
82+
$attr = 'data-live-update-'.$update['key'];
83+
84+
return $attr.'="'.htmlspecialchars($update['type'], ENT_QUOTES, 'UTF-8').'"';
85+
})->implode(' ');
86+
}
87+
}

src/Sections/Support/SectionData.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,18 @@ public function jsonSerialize(): mixed
8585
];
8686
}
8787

88-
public function liveUpdate(string $settingId, ?string $attribute = null): LiveUpdateData
88+
public function liveUpdate(?string $settingId = null, ?string $attr = null): LiveUpdatesBuilder
8989
{
90-
return new LiveUpdateData(
91-
settingId: $settingId,
92-
attribute: $attribute,
93-
sectionId: $this->id,
94-
);
90+
$builder = new LiveUpdatesBuilder(sectionId: $this->id, blockId: null);
91+
92+
if ($settingId && $attr) {
93+
return $builder->attr($settingId, $attr);
94+
}
95+
96+
if ($settingId) {
97+
return $builder->text($settingId);
98+
}
99+
100+
return $builder;
95101
}
96102
}

0 commit comments

Comments
 (0)