Skip to content

Commit 8c9bcfe

Browse files
committed
Add web components basic docs
1 parent e9c5d86 commit 8c9bcfe

File tree

10 files changed

+356
-1
lines changed

10 files changed

+356
-1
lines changed

Writerside/boson.tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<toc-element topic="scripts-api.md" />
4242
<toc-element topic="bindings-api.md" />
4343
<toc-element topic="data-api.md" />
44+
<toc-element topic="web-components-api.md" />
4445
</toc-element>
4546
<toc-element topic="events.md" />
4647
</toc-element>
3.16 KB
Loading
3.92 KB
Loading
3.46 KB
Loading

Writerside/labels.list

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<!DOCTYPE labels SYSTEM "https://resources.jetbrains.com/writerside/1.0/labels-list.dtd">
33
<labels xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
44
xsi:noNamespaceSchemaLocation="https://resources.jetbrains.com/writerside/1.0/labels.xsd">
5-
<primary-label id="experimental" name="Experimental" short-name="Experimental" color="red">
5+
<primary-label id="experimental" name="Experimental Feature" short-name="E" color="red">
66
This is a non-stable functionality which is highly likely
77
to be changed or removed in the future
88
</primary-label>

Writerside/topics/webview/bindings-api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Bindings API
22

3+
<show-structure for="chapter" depth="2"/>
4+
35
You can register custom PHP functions to call them from the client.
46

57
The API is available in the `WebView::$bindings` property.

Writerside/topics/webview/data-api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Data API
22

3+
<show-structure for="chapter" depth="2"/>
4+
35
This API should be used to receive arbitrary data from the client.
46

57
The API is available in the `WebView::$data` property.

Writerside/topics/webview/schemes-api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Schemes API
22

3+
<show-structure for="chapter" depth="2"/>
4+
35
You can register custom scheme/protocols and intercept standard one.
46

57
This API allows you to intercept all calls to addresses according to

Writerside/topics/webview/scripts-api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Scripts API
22

3+
<show-structure for="chapter" depth="2"/>
4+
35
This API should be used to register and call arbitrary
46
JavaScript code in WebView.
57

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
# Web Components API
2+
3+
<primary-label ref="experimental"/>
4+
<show-structure for="chapter" depth="2"/>
5+
6+
You can create your own custom web components (html elements) processed by PHP.
7+
8+
The API is available in the `WebView::$components` property.
9+
10+
```php
11+
$app = new Boson\Application();
12+
13+
$app->webview->components; // Access to Web Components API
14+
```
15+
16+
17+
## Creation
18+
19+
For creating your own component, you should use the `add()` method, passing
20+
there the tag name and a reference to the existing component class.
21+
22+
<note>
23+
The tag name for a custom component must contain a dash (`-`) character.
24+
</note>
25+
26+
```php
27+
class MyExampleComponent {}
28+
29+
$app = new Boson\Application();
30+
31+
// Tag component name
32+
$tag = 'my-element';
33+
34+
// Component class name
35+
$component = MyExampleComponent::class;
36+
37+
$app->webview->components->add($tag, $component);
38+
39+
$app->webview->html = '<my-element>Example</my-element>';
40+
```
41+
42+
For more convenient component management, you can use inheritance from the
43+
`Boson\WebView\Api\WebComponentsApi\WebComponent` class.
44+
45+
```php
46+
use Boson\WebView\Api\WebComponentsApi\WebComponent;
47+
48+
class MyExampleComponent extends WebComponent
49+
{
50+
// do something
51+
}
52+
```
53+
54+
## Template Rendering
55+
56+
By default, the component does not contain any HTML content (it uses the
57+
default body one passed to it). If you want to decorate it somehow or define
58+
custom content, you should add the
59+
`Boson\WebView\Api\WebComponentsApi\HasTemplateInterface` interface and
60+
implement `render()` method.
61+
62+
```php
63+
use Boson\WebView\Api\WebComponentsApi\HasTemplateInterface;
64+
65+
class MyExampleComponent implements HasTemplateInterface
66+
{
67+
public function render(): string
68+
{
69+
return '<b>This is SPARTAAAAAAAAAAaaaAAAA!!!</b>';
70+
}
71+
}
72+
```
73+
74+
<img src="web-component-content.png" alt="Web Component Template" />
75+
76+
## Shadow DOM
77+
78+
In order to switch to shadow house rendering mode, you should implement
79+
the `Boson\WebView\Api\WebComponentsApi\HasShadowDomInterface` interface.
80+
81+
The shadow DOM is similar to the regular renderer, but isolates its behavior
82+
from global styles and supports slots.
83+
84+
To include the content of an element inside a rendered template, the `<slot />`
85+
tag should be used.
86+
87+
<note>
88+
See more information about templates and slots in
89+
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots">MDN Documentation</a>
90+
</note>
91+
92+
```php
93+
use Boson\WebView\Api\WebComponentsApi\HasShadowDomInterface;
94+
95+
class MyExampleComponent implements HasShadowDomInterface
96+
{
97+
public function render(): string
98+
{
99+
//
100+
// Please note that using the short "<slot />" version
101+
// instead of full "<slot></slot>" may not work correctly.
102+
//
103+
return '<b>This is <slot></slot>!!!</b>';
104+
}
105+
}
106+
```
107+
108+
If you try to render the contents of a `<slot />` without a Shadow DOM (using
109+
`HasTemplateInterface`), no data will be received.
110+
111+
<img src="web-component-content-slot.png" alt="Web Component Content Slot" />
112+
113+
When you turn on the Shadow DOM (using `HasShadowDomInterface`), the contents
114+
will be passed to the `<slot />`.
115+
116+
<img src="web-component-shadow-dom-slot.png" alt="Web Component Shadow DOM Slot" />
117+
118+
## Lifecycle Callbacks
119+
120+
Creating a new PHP component instance means physically creating the object,
121+
including through JavaScript.
122+
123+
```js
124+
const component = document.createElement('my-element');
125+
//
126+
// At this point, a PHP component instance will be created.
127+
// That is, the MyExampleComponent::__construct() will be called.
128+
//
129+
```
130+
131+
In order to accurately determine that an element is connected to any physical
132+
node of the DOM document, the
133+
`Boson\WebView\Api\WebComponentsApi\HasLifecycleCallbacksInterface`
134+
interface should be implemented.
135+
136+
```php
137+
use Boson\WebView\Api\WebComponentsApi\HasLifecycleCallbacksInterface;
138+
139+
class MyExampleComponent implements HasLifecycleCallbacksInterface
140+
{
141+
public function onConnect(): void
142+
{
143+
var_dump('Component is connected to document');
144+
}
145+
146+
public function onDisconnect(): void
147+
{
148+
var_dump('Component is disconnected from document');
149+
}
150+
}
151+
```
152+
153+
## Component Methods
154+
155+
Each web component supports the ability to create methods and process them.
156+
157+
```html
158+
<my-element onclick="this.update()">Example</my-element>
159+
```
160+
161+
If you leave the code as is, then when you click on the element,
162+
a JS error will be thrown: `Uncaught TypeError: this.update is not a function`.
163+
164+
To implement a method (for example, "`update()`"), you should implement the
165+
`Boson\WebView\Api\WebComponentsApi\HasMethodsInterface` interface.
166+
167+
```php
168+
class MyExampleComponent implements HasMethodsInterface
169+
{
170+
public function onMethodCalled(string $method, array $args = []): mixed
171+
{
172+
// ...
173+
}
174+
175+
public static function getMethodNames(): array
176+
{
177+
// ...
178+
}
179+
}
180+
```
181+
182+
If you want to add support for method `update()`, then `getMethodNames()` method
183+
must return the corresponding name.
184+
185+
```php
186+
public static function getMethodNames(): array
187+
{
188+
return [ 'update' ];
189+
}
190+
```
191+
192+
After this, when you click on the element, the `onMethodCalled` method will
193+
be called with the `$method` argument equal to the name of the called method
194+
`"update"` and empty arguments.
195+
196+
```php
197+
class MyExampleComponent implements HasMethodsInterface
198+
{
199+
public function onMethodCalled(string $method, array $args = []): mixed
200+
{
201+
if ($method !== 'update') {
202+
throw new \BadMethodCallException('Invalid method ' . $method);
203+
}
204+
205+
var_dump($method . ' has been invoked with passed arguments');
206+
// update has been invoked with passed arguments
207+
var_dump($args);
208+
// array(0) {}
209+
210+
return null;
211+
}
212+
213+
public static function getMethodNames(): array
214+
{
215+
return [ 'update' ];
216+
}
217+
}
218+
```
219+
220+
Don't be afraid to return exceptions from methods,
221+
they can be handled correctly.
222+
223+
```php
224+
public function onMethodCalled(string $method, array $args = []): mixed
225+
{
226+
throw new \BadMethodCallException('Invalid method ' . $method);
227+
}
228+
```
229+
230+
After calling method `update()` you will get the following JS error:
231+
```
232+
Uncaught (in promise) Error:
233+
BadMethodCallException: Invalid method update in .../test.php
234+
on line 12
235+
at <anonymous>:1:61
236+
```
237+
238+
You can also call these methods from the JS directly.
239+
240+
```js
241+
const component = document.createElement('my-element');
242+
243+
try {
244+
let result = await component.update();
245+
} catch(e) {
246+
// Catch PHP Exception
247+
}
248+
```
249+
250+
## Component Attributes
251+
252+
In addition to methods, each HTML element has attributes. You can subscribe
253+
to change, add or remove an attribute by implementing the
254+
`Boson\WebView\Api\WebComponentsApi\HasObservedAttributesInterface` interface.
255+
256+
```php
257+
use Boson\WebView\Api\WebComponentsApi\HasObservedAttributesInterface;
258+
259+
class MyExampleComponent implements HasObservedAttributesInterface
260+
{
261+
public function onAttributeChanged(
262+
string $attribute,
263+
?string $value,
264+
?string $previous,
265+
): void {
266+
// ...
267+
}
268+
269+
public static function getObservedAttributeNames(): array
270+
{
271+
// ...
272+
}
273+
}
274+
```
275+
276+
Method `getObservedAttributeNames()` must return a list of attributes
277+
(strings) to be processed.
278+
279+
Method `onAttributeChanged()` contains a callback that is called when
280+
the attribute value changes.
281+
282+
| `$value` | `$previous` | Meaning |
283+
|----------|-------------|-----------------------------------|
284+
| `string` | `null` | Attribute has been added |
285+
| `string` | `string` | Attribute value has been changed |
286+
| `null` | `string` | Attribute has been removed |
287+
288+
## Reactive Context
289+
290+
The reactive context allows you to modify and retrieve values from a
291+
component programmatically.
292+
293+
By default, it is passed to the constructor as the first argument.
294+
295+
```php
296+
use Boson\WebView\Api\WebComponentsApi\ReactiveContext;
297+
use Boson\WebView\WebView;
298+
299+
class MyExampleComponent
300+
{
301+
public function __construct(
302+
private ReactiveContext $ctx,
303+
) {}
304+
}
305+
```
306+
307+
The context contains methods for working with attributes.
308+
309+
```php
310+
class MyExampleComponent implements HasMethodsInterface
311+
{
312+
public function __construct(
313+
private ReactiveContext $ctx,
314+
) {}
315+
316+
public function update(): void
317+
{
318+
// Add attribute `some="any"` in case of attribute is not defined
319+
if (!$this->ctx->attributes->has('some')) {
320+
$this->ctx->attributes->set('some', 'any');
321+
}
322+
}
323+
324+
/// Delegate "onMethodCalled()" call to the update() method...
325+
}
326+
```
327+
328+
To work with content, you can use the `$content` property.
329+
330+
```php
331+
class MyExampleComponent implements HasMethodsInterface
332+
{
333+
public function __construct(
334+
private ReactiveContext $ctx,
335+
) {}
336+
337+
public function update(): void
338+
{
339+
if (!$this->ctx->content->html === '') {
340+
$this->ctx->content->html = '<b>Hello World!</b>';
341+
}
342+
}
343+
344+
/// Delegate "onMethodCalled()" call to the update() method...
345+
}
346+
```

0 commit comments

Comments
 (0)