You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -9,139 +9,86 @@ Components may sometimes manage state, or configuration. We encourage the use of
9
9
10
10
As Catalyst elements are really just Web Components, they have the `hasAttribute`, `getAttribute`, `setAttribute`, `toggleAttribute`, and `removeAttribute` set of methods available, as well as [`dataset`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/dataset), but these can be a little tedious to use; requiring null checking code with each call.
11
11
12
-
Catalyst includes the `@attr` decorator which provides nice syntax sugar to simplify, standardise, and encourage use of attributes. `@attr` has the following benefits over the basic `*Attribute` methods:
13
-
14
-
- It dasherizes a property name, making it safe for HTML serialization without conflicting with [built-in global attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes). This works the same as the class name, so for example `@attr pathName` will be `path-name` in HTML, `@attr srcURL` will be `src-url` in HTML.
15
-
- An `@attr` property automatically casts based on the initial value - if the initial value is a `string`, `boolean`, or `number` - it will never be `null` or `undefined`. No more null checking!
16
-
- It is automatically synced with the HTML attribute. This means setting the class property will update the HTML attribute, and setting the HTML attribute will update the class property!
17
-
- Assigning a value in the class description will make that value the _default_ value so if the HTML attribute isn't set, or is set but later removed the _default_ value will apply.
18
-
19
-
This behaves similarly to existing HTML elements where the class field is synced with the html attribute, for example the `<input>` element's `type` field:
20
-
21
-
```ts
22
-
const input =document.createElement('input')
23
-
console.assert(input.type==='text') // default value
24
-
console.assert(input.hasAttribute('type') ===false) // no attribute to override
25
-
input.setAttribute('type', 'number')
26
-
console.assert(input.type==='number') // overrides based on attribute
27
-
input.removeAttribute('type')
28
-
console.assert(input.type==='text') // back to default value
29
-
```
12
+
Catalyst includes the `@attr` decorator, which provides nice syntax sugar to simplify, standardise, and encourage use of attributes. `@attr` has the following benefits over the basic `*Attribute` methods:
30
13
31
-
{% capture callout %}
32
-
An important part of `@attr`s is that they _must_ comprise of two words, so that they get a dash when serialised to HTML. This is intentional, to avoid conflicting with [built-in global attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes). To see how JavaScript property names convert to HTML dasherized names, try typing the name of an `@attr` below:
33
-
{% endcapture %}{% include callout.md %}
14
+
- It maps whatever the property name is to `data-*`, [similar to how `dataset` does](https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/dataset#name_conversion), but with more intuitive naming (e.g. `URL` maps to `data-url` not `data--u-r-l`).
15
+
- An `@attr` property is limited to `string`, `boolean`, or `number`, it will never be `null` or `undefined` - instead it has an "empty" value. No more null checking!
16
+
- The attribute name is automatically [observed, meaning `attributeChangedCallback` will fire when it changes](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks).
17
+
- Assigning a value in the class description will make that value the _default_ value, so when the element is connected that value is set (unless the element has the attribute defined already).
34
18
35
-
<form>
36
-
<label>
37
-
<h4>I want my `@attr` to be named...</h4>
38
-
<input class="js-attr-dasherize-test mb-4">
39
-
</label>
40
-
<divhiddenclass="js-attr-dasherize-bad text-red">
41
-
{{ octx }} An attr name must be two words, so that the HTML version includes a dash!
if (!this.hasAttribute('foo-bar')) this.fooBar='Hello'
52
+
if (!this.hasAttribute('data-foo')) this.foo='Hello'
104
53
}
105
54
55
+
static observedAttributes = ['data-foo']
106
56
}
107
57
```
108
58
109
59
### Attribute Types
110
60
111
-
The _type_ of an attribute is automatically inferred based on the type it is first set to. This means once a value is initially set it cannot change type; if it is set a `string` it will never be anything but a `string`. An attribute can only be one of either a `string`, `number`, or `boolean`. The types have small differences in how they behave in the DOM.
61
+
The _type_ of an attribute is automatically inferred based on the type it is first set to. This means once a value is set it cannot change type; if it is set a `string` it will never be anything but a `string`. An attribute can only be one of either a `string`, `number`, or `boolean`. The types have small differences in how they behave in the DOM.
112
62
113
63
Below is a handy reference for the small differences, this is all explained in more detail below that.
114
64
115
-
| Type | When `get` is called | When `set` is called |
If an attribute is first set to a `string`, then it can only ever be a `string` during the lifetime of an element. The property will revert to the initial value if the attribute doesn't exist, and trying to set it to something that isn't a string will turn it into one before assignment.
73
+
If an attribute is first set to a `string`, then it can only ever be a `string` during the lifetime of an element. The property will return an empty string (`''`) if the attribute doesn't exist, and trying to set it to something that isn't a string will turn it into one before assignment.
this.setAttribute('data-foo', 'this value doesnt matter!')
116
+
console.assert(this.foo===true)
170
117
}
171
118
}
172
119
```
173
120
174
121
#### Number Attributes
175
122
176
-
If an attribute is first set to a number, then it can only ever be a number during the lifetime of an element. This is sort of like the [`maxlength` attribute on inputs](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/maxlength). The property will return the initial value if the attribute doesn't exist, and will be coerced to `Number` if it does - this means it is _possible_ to get back `NaN`. Negative numbers and floats are also valid.
123
+
If an attribute is first set to a number, then it can only ever be a number during the lifetime of an element. This is sort of like the [`maxlength` attribute on inputs](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/maxlength). The property will return `0` if the attribute doesn't exist, and will be coerced to `Number` if it does - this means it is _possible_ to get back `NaN`. Negative numbers and floats are also valid.
@@ -202,7 +148,7 @@ class HelloWorldElement extends HTMLElement {
202
148
When an element gets connected to the DOM, the attr is initialized. During this phase Catalyst will determine if the default value should be applied. The default value is defined in the class property. The basic rules are as such:
203
149
204
150
- If the class property has a value, that is the _default_
205
-
- When connected, if the element _does not_ have a matching attribute, the _default is_ applied.
151
+
- When connected, if the element _does not_ have a matching attribute, the default _is_ applied.
206
152
- When connected, if the element _does_ have a matching attribute, the default _is not_ applied, the property will be assigned to the value of the attribute instead.
207
153
208
154
{% capture callout %}
@@ -219,9 +165,9 @@ attr name: Maps to get/setAttribute('data-name')
@@ -241,45 +187,24 @@ data-name ".*": Will set the value of `name`
241
187
// This will render `Hello `
242
188
```
243
189
244
-
### Advanced usage
245
-
246
-
#### Determining when an @attr changes value
247
-
248
-
To be notified when an `@attr` changes value, you can use the decorator over
249
-
"setter" method instead, and the method will be called with the new value
250
-
whenever it is re-assigned, either through HTML or JavaScript:
251
-
252
-
```typescript
253
-
import { controller, attr } from "@github/catalyst"
254
-
@controller
255
-
class HelloWorldElement extends HTMLElement {
256
-
257
-
@attr get dataName() {
258
-
return 'World' // Used to get the intial value
259
-
}
260
-
// Called whenever `name` changes
261
-
set dataName(newValue: string) {
262
-
this.textContent = `Hello ${newValue}`
263
-
}
264
-
}
265
-
```
266
-
267
190
### What about without Decorators?
268
191
269
-
If you're not using decorators, then the `@attr` decorator has an escape hatch: You can define a staticclassfield using the `[attr.static]` computed property, as an array of key names. Like so:
192
+
If you're not using decorators, then you won't be able to use the `@attr` decorator, but there is still a way to achieve the same result. Under the hood `@attr` simply tags a field, but `initializeAttrs` and `defineObservedAttributes` do all of the logic.
193
+
194
+
Calling `initializeAttrs` in your connected callback, with the list of properties you'd like to initialize, and calling `defineObservedAttributes` with the class, can achieve the same result as `@attr`. The class fields can still be defined in your class, and they'll be overridden as described above. For example:
Copy file name to clipboardExpand all lines: docs/_guide/conventions.md
+3-8Lines changed: 3 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,18 +7,13 @@ subtitle: Common naming and patterns
7
7
8
8
Catalyst strives for convention over code. Here are a few conventions we recommend when writing Catalyst code:
9
9
10
-
### Suffix your controllers consistently, for symmetry
10
+
### Use `Element` to suffix your controller class
11
11
12
-
Catalyst components can be suffixed with `Element`, `Component` or `Controller`. We think elements should behave as closely to the built-ins as possible, so we like to use`Element` (existing elements do this, for example `HTMLDivElement`, `SVGElement`). If you're using a server side comoponent framework such as [ViewComponent](https://viewcomponent.org/), it's probably better to suffix `Component` for symmetry with that framework.
12
+
Built in HTML elements all extend from the `HTMLElement` constructor, and are all suffixed with`Element` (for example `HTMLElement`, `SVGElement`, `HTMLInputElement` and so on). Catalyst components should be no different, they should behave as closely to the built-ins as possible.
Copy file name to clipboardExpand all lines: docs/_guide/rendering.md
+6-28Lines changed: 6 additions & 28 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,15 +13,15 @@ Remember to _always_ make your JavaScript progressively enhanced, where possible
13
13
14
14
By leveraging the native [`ShadowDOM`](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) feature, Catalyst components can render complex sub-trees, fully encapsulated from the rest of the page.
15
15
16
-
[Actions]({{ site.baseurl }}/guide/actions) and [Targets]({{ site.baseurl }}/guide/targets) all work within an elements ShadowRoot.
16
+
Catalyst will automatically look for elements that match the `template[data-shadowroot]` selector, within your controller. If it finds one as a direct-child of your controller, it will use that to create a shadowRoot.
17
17
18
-
You can also leverage the [declarative shadow DOM](https://web.dev/declarative-shadow-dom/) and render a template inline to your HTML, which will automatically be attached (this may require a polyfill for browsers which are yet to support this feature).
18
+
Catalyst Controllers will search for a direct child of `template[data-shadowroot]` and load its contents as the `shadowRoot` of the element. [Actions]({{ site.baseurl }}/guide/actions) and [Targets]({{ site.baseurl }}/guide/targets) all work within an elements ShadowRoot.
@@ -43,34 +43,12 @@ class HelloWorldElement extends HTMLElement {
43
43
}
44
44
```
45
45
46
+
Providing the `<template data-shadowroot>` element as a direct child of the `hello-world` element tells Catalyst to render the templates contents automatically, and so all `HelloWorldElements` with this template will be rendered with the contents.
47
+
46
48
{% capture callout %}
47
-
Remember that _all_ instances of your controller _must_ add the `<template shadowroot>` HTML. If an instance does not have the `<template data-shadowroot>` as a direct child, then the shadow DOM won't be rendered for it!
49
+
Remember that _all_ instances of your controller _must_ add the `<template data-shadowroot>` HTML. If an instance does not have the `<template data-shadowroot>` as a direct child, then the shadow DOM won't be rendered for it!
48
50
{% endcapture %}{% include callout.md %}
49
51
50
-
51
-
It is also possible to attach a shadowRoot to your element during the `connectedCallback`, like so:
### Updating a Template element using JS templates
75
53
76
54
Sometimes you wont have a template that is server rendered, and instead want to make a template using JS. Catalyst does not support this out of the box, but it is possible to use another library: `@github/jtml`. This library can be used to write declarative templates using JS. Let's re-work the above example using `@github/jtml`:
0 commit comments