Skip to content

Commit 6b22d59

Browse files
authored
Merge pull request #289 from github/merge-v2-docs
merge v2 docs into guide-v2
2 parents b8720f1 + f7a89aa commit 6b22d59

10 files changed

+251
-93
lines changed

Diff for: docs/_guide/anti-patterns-2.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
version: 2
3-
chapter: 13
3+
chapter: 15
44
title: Anti Patterns
55
subtitle: Things to avoid building components
66
permalink: /guide-v2/anti-patterns

Diff for: docs/_guide/attrs-2.md

+133-57
Original file line numberDiff line numberDiff line change
@@ -10,86 +10,139 @@ Components may sometimes manage state, or configuration. We encourage the use of
1010

1111
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.
1212

13-
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+
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:
14+
15+
- 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.
16+
- 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!
17+
- 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!
18+
- 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.
19+
20+
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:
21+
22+
```ts
23+
const input = document.createElement('input')
24+
console.assert(input.type === 'text') // default value
25+
console.assert(input.hasAttribute('type') === false) // no attribute to override
26+
input.setAttribute('type', 'number')
27+
console.assert(input.type === 'number') // overrides based on attribute
28+
input.removeAttribute('type')
29+
console.assert(input.type === 'text') // back to default value
30+
```
1431

15-
- 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`).
16-
- 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!
17-
- 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).
18-
- 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).
32+
{% capture callout %}
33+
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:
34+
{% endcapture %}{% include callout.md %}
1935

20-
To use the `@attr` decorator, attach it to a class field, and it will get/set the value of the matching `data-*` attribute.
36+
<form>
37+
<label>
38+
<h4>I want my `@attr` to be named...</h4>
39+
<input class="js-attr-dasherize-test mb-4">
40+
</label>
41+
<div hidden class="js-attr-dasherize-bad text-red">
42+
{{ octx }} An attr name must be two words, so that the HTML version includes a dash!
43+
</div>
44+
<div hidden class="js-attr-dasherize-good text-green">
45+
{{ octick }} This will be <code></code> in HTML.
46+
</div>
47+
<script type="module">
48+
import {mustDasherize} from 'https://unpkg.com/@github/catalyst/lib/index.js'
49+
document.querySelector('.js-attr-dasherize-test').addEventListener('input', () => {
50+
let name = event.target.value
51+
const goodEl = document.querySelector('.js-attr-dasherize-good')
52+
const badEl = document.querySelector('.js-attr-dasherize-bad')
53+
if (name === '') {
54+
goodEl.hidden = true
55+
badEl.hidden = true
56+
return
57+
}
58+
let pass = true
59+
try {
60+
name = mustDasherize(name)
61+
} catch (e) {
62+
pass = false
63+
}
64+
goodEl.querySelector('code').textContent = name
65+
goodEl.hidden = !pass
66+
badEl.hidden = pass
67+
})
68+
</script>
69+
</form>
70+
71+
To use the `@attr` decorator, attach it to a class field, and it will get/set the value of the matching dasherized HTML attribute.
2172

2273
### Example
2374

2475
<!-- annotations
25-
attr foo: Maps to get/setAttribute('datafoo')
76+
attr fooBar: Maps to get/setAttribute('foo-bar')
2677
-->
2778

2879
```js
2980
import { controller, attr } from "@github/catalyst"
3081

3182
@controller
3283
class HelloWorldElement extends HTMLElement {
33-
@attr foo = 'hello'
84+
@attr fooBar = 'hello'
3485
}
3586
```
3687

37-
This is the equivalent to:
88+
This is somewhat equivalent to:
3889

3990
```js
4091
import { controller } from "@github/catalyst"
4192

4293
@controller
4394
class HelloWorldElement extends HTMLElement {
44-
get foo(): string {
45-
return this.getAttribute('data-foo') || ''
95+
get fooBar(): string {
96+
return this.getAttribute('foo-bar') || ''
4697
}
4798

48-
set foo(value: string): void {
49-
return this.setAttribute('data-foo', value)
99+
set fooBar(value: string): void {
100+
return this.setAttribute('foo-bar', value)
50101
}
51102

52103
connectedCallback() {
53-
if (!this.hasAttribute('data-foo')) this.foo = 'Hello'
104+
if (!this.hasAttribute('foo-bar')) this.fooBar = 'Hello'
54105
}
55106

56-
static observedAttributes = ['data-foo']
57107
}
58108
```
59109

60110
### Attribute Types
61111

62-
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+
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.
63113

64114
Below is a handy reference for the small differences, this is all explained in more detail below that.
65115

66-
| Type | "Empty" value | When `get` is called | When `set` is called |
67-
|:----------|:--------------|----------------------|:---------------------|
68-
| `string` | `''` | `getAttribute` | `setAttribute` |
69-
| `number` | `0` | `getAttribute` | `setAttribute` |
70-
| `boolean` | `false` | `hasAttribute` | `toggleAttribute` |
116+
| Type | When `get` is called | When `set` is called |
117+
|:----------|----------------------|:---------------------|
118+
| `string` | `getAttribute` | `setAttribute` |
119+
| `number` | `getAttribute` | `setAttribute` |
120+
| `boolean` | `hasAttribute` | `toggleAttribute` |
71121

72122
#### String Attributes
73123

74-
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.
124+
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.
75125

76126
<!-- annotations
77-
attr foo: Maps to get/setAttribute('data-foo')
127+
attr foo: Maps to get/setAttribute('foo-bar')
78128
-->
79129

80130
```js
81131
import { controller, attr } from "@github/catalyst"
82132

83133
@controller
84134
class HelloWorldElement extends HTMLElement {
85-
@attr foo = 'Hello'
135+
@attr fooBar = 'Hello'
86136

87137
connectedCallback() {
88-
console.assert(this.foo === 'Hello')
89-
this.foo = null // TypeScript won't like this!
90-
console.assert(this.foo === 'null')
91-
delete this.dataset.foo // Removes the attribute
92-
console.assert(this.foo === '') // If the attribute doesn't exist, its an empty string!
138+
console.assert(this.fooBar === 'Hello')
139+
this.fooBar = 'Goodbye'
140+
console.assert(this.fooBar === 'Goodbye'')
141+
console.assert(this.getAttribute('foo-bar') === 'Goodbye')
142+
143+
this.removeAttribute('foo-bar')
144+
// If the attribute doesn't exist, it'll output the initial value!
145+
console.assert(this.fooBar === 'Hello')
93146
}
94147
}
95148
```
@@ -107,39 +160,40 @@ import { controller, attr } from "@github/catalyst"
107160
108161
@controller
109162
class HelloWorldElement extends HTMLElement {
110-
@attr foo = false
163+
@attr fooBar = false
111164
112165
connectedCallback() {
113-
console.assert(this.hasAttribute('data-foo') === false)
114-
this.foo = true
115-
console.assert(this.hasAttribute('data-foo') === true)
116-
this.setAttribute('data-foo', 'this value doesnt matter!')
117-
console.assert(this.foo === true)
166+
console.assert(this.hasAttribute('foo-bar') === false)
167+
this.fooBar = true
168+
console.assert(this.hasAttribute('foo-bar') === true)
169+
this.setAttribute('foo-bar', 'this value doesnt matter!')
170+
console.assert(this.fooBar === true)
118171
}
119172
}
120173
```
121174

122175
#### Number Attributes
123176

124-
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.
177+
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.
125178

126179
<!-- annotations
127-
attr foo: Maps to get/setAttribute('data-foo')
180+
attr foo: Maps to get/setAttribute('foo-bar')
128181
-->
129182

130183
```js
131184
import { controller, attr } from "@github/catalyst"
132185
133186
@controller
134187
class HelloWorldElement extends HTMLElement {
135-
@attr foo = 1
188+
@attr fooBar = 1
136189
137190
connectedCallback() {
138-
console.assert(this.getAttribute('data-foo') === '1')
139-
this.setAttribute('data-foo', 'not a number')
140-
console.assert(Number.isNaN(this.foo))
141-
this.foo = -3.14
142-
console.assert(this.getAttribute('data-foo') === '-3.14')
191+
this.fooBar = 2
192+
console.assert(this.getAttribute('foo-bar') === '2')
193+
this.setAttribute('foo-bar', 'not a number')
194+
console.assert(Number.isNaN(this.fooBar))
195+
this.fooBar = -3.14
196+
console.assert(this.getAttribute('foo-bar') === '-3.14')
143197
}
144198
}
145199
```
@@ -149,7 +203,7 @@ class HelloWorldElement extends HTMLElement {
149203
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:
150204

151205
- If the class property has a value, that is the _default_
152-
- When connected, if the element _does not_ have a matching attribute, the default _is_ applied.
206+
- When connected, if the element _does not_ have a matching attribute, the _default is_ applied.
153207
- 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.
154208

155209
{% capture callout %}
@@ -166,9 +220,9 @@ attr name: Maps to get/setAttribute('data-name')
166220
import { controller, attr } from "@github/catalyst"
167221
@controller
168222
class HelloWorldElement extends HTMLElement {
169-
@attr name = 'World'
223+
@attr dataName = 'World'
170224
connectedCallback() {
171-
this.textContent = `Hello ${this.name}`
225+
this.textContent = `Hello ${this.dataName}`
172226
}
173227
}
174228
```
@@ -188,24 +242,45 @@ data-name ".*": Will set the value of `name`
188242
// This will render `Hello `
189243
```
190244
191-
### What about without Decorators?
245+
### Advanced usage
192246
193-
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.
247+
#### Determining when an @attr changes value
194248
195-
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:
196-
197-
```js
198-
import {initializeAttrs, defineObservedAttributes} from '@github/catalyst'
249+
To be notified when an `@attr` changes value, you can use the decorator over
250+
"setter" method instead, and the method will be called with the new value
251+
whenever it is re-assigned, either through HTML or JavaScript:
199252
253+
```typescript
254+
import { controller, attr } from "@github/catalyst"
255+
@controller
200256
class HelloWorldElement extends HTMLElement {
201-
foo = 1
202257
203-
connectedCallback() {
204-
initializeAttrs(this, ['foo'])
258+
@attr get dataName() {
259+
return 'World' // Used to get the intial value
205260
}
261+
// Called whenever `name` changes
262+
set dataName(newValue: string) {
263+
this.textContent = `Hello ${newValue}`
264+
}
265+
}
266+
```
267+
268+
### What about without Decorators?
269+
270+
If you're not using decorators, then the `@attr` decorator has an escape hatch: You can define a static class field using the `[attr.static]` computed property, as an array of key names. Like so:
271+
272+
```js
273+
import {controller, attr} from '@github/catalyst'
206274
275+
controller(
276+
class HelloWorldElement extends HTMLElement {
277+
// Same as @attr fooBar
278+
[attr.static] = ['fooBar']
279+
280+
// Field can still be defined
281+
fooBar = 1
207282
}
208-
defineObservedAttributes(HelloWorldElement, ['foo'])
283+
)
209284
```
210285

211286
This example is functionally identical to:
@@ -215,6 +290,7 @@ import {controller, attr} from '@github/catalyst'
215290
216291
@controller
217292
class HelloWorldElement extends HTMLElement {
218-
@attr foo = 1
293+
@attr fooBar = 1
219294
}
220295
```
296+

Diff for: docs/_guide/conventions-2.md

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
version: 2
3+
chapter: 13
4+
title: Conventions
5+
subtitle: Common naming and patterns
6+
---
7+
8+
Catalyst strives for convention over code. Here are a few conventions we recommend when writing Catalyst code:
9+
10+
### Suffix your controllers consistently, for symmetry
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.
13+
14+
```typescript
15+
@controller
16+
class UserListElement extends HTMLElement {} // `<user-list />`
17+
```
18+
19+
```typescript
20+
@controller
21+
class UserListComponent extends HTMLElement {} // `<user-list />`
22+
```
23+
24+
### The best class-names are two word descriptions
25+
26+
Custom elements are required to have a `-` inside the tag name. Catalyst's `@controller` will derive the tag name from the class name - and so as such the class name needs to have at least two capital letters, or to put it another way, it needs to consist of at least two CamelCased words. The element name should describe what it does succinctly in two words. Some examples:
27+
28+
- `theme-picker` (`class ThemePickerElement`)
29+
- `markdown-toolbar` (`class MarkdownToolbarElement`)
30+
- `user-list` (`class UserListElement`)
31+
- `content-pager` (`class ContentPagerElement`)
32+
- `image-gallery` (`class ImageGalleryElement`)
33+
34+
If you're struggling to come up with two words, think about one word being the "what" (what does it do?) and another being the "how" (how does it do it?).
35+
36+
### Keep class-names short (but not too short)
37+
38+
Brevity is good, element names are likely to be typed out a lot, especially throughout HTML in as tag names, and `data-target`, `data-action` attributes. A good rule of thumb is to try to keep element names down to less than 15 characters (excluding the `Element` suffix), and ideally less than 10. Also, longer words are generally harder to spell, which means mistakes might creep into your code.
39+
40+
Be careful not to go too short! We'd recommend avoiding contracting words such as using `Img` to mean `Image`. It can create confusion, especially if there are inconsistencies across your code!
41+
42+
### Method names should describe what they do
43+
44+
A good method name, much like a good class name, describes what it does, not how it was invoked. While methods can be given most names, you should avoid names that conflict with existing methods on the `HTMLElement` prototype (more on that in [anti-patterns]({{ site.baseurl }}/guide/anti-patterns#avoid-shadowing-method-names)). Names like `onClick` are best avoided, overly generic names like `toggle` should also be avoided. Just like class names it is a good idea to ask "how" and "what", so for example `showAdmins`, `filterUsers`, `updateURL`.
45+
46+
### `@target` should use singular naming, while `@targets` should use plural
47+
48+
To help differentiate the two `@target`/`@targets` decorators, the properties should be named with respective to their cardinality. That is to say, if you're using an `@target` decorator, then the name should be singular (e.g. `user`, `field`) while the `@targets` decorator should be coupled with plural property names (e.g. `users`, `fields`).
49+

Diff for: docs/_guide/create-ability.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ version: 2
33
chapter: 9
44
title: Create Ability
55
subtitle: Create your own abilities
6+
permalink: /guide-v2/create-ability
67
---
78

89
Catalyst provides the functionality to create your own abilities, with a few helper methods and a `controllable` base-level ability. These are explained in detail below, but for a quick summary they are:

Diff for: docs/_guide/lazy-elements-2.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
---
22
version: 2
3-
chapter: 15
3+
chapter: 16
44
title: Lazy Elements
55
subtitle: Dynamically load elements just in time
6+
permalink: /guide-v2/lazy-elements
67
---
78

89
A common practice in modern web development is to combine all JavaScript code into JS "bundles". By bundling the code together we avoid the network overhead of fetching each file. However the trade-off of bundling is that we might deliver JS code that will never run in the browser.

Diff for: docs/_guide/patterns-2.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
version: 2
3-
chapter: 12
3+
chapter: 14
44
title: Patterns
55
subtitle: Best Practices for behaviours
66
permalink: /guide-v2/patterns

0 commit comments

Comments
 (0)