Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make inner mechanism of outlet API more obvious in documentation #663

Merged
merged 3 commits into from
Jun 15, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 37 additions & 36 deletions docs/reference/outlets.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ The use of Outlets helps with cross-controller communication and coordination as

They are conceptually similar to [Stimulus Targets](https://stimulus.hotwired.dev/reference/targets) but with the difference that they reference a Stimulus controller instance plus its associated controller element.

<meta data-controller="callout" data-callout-text-value='data-search-result-outlet=".result"'>
<meta data-controller="callout" data-callout-text-value='class="result"'>
<meta data-controller="callout" data-callout-text-value='data-chat-user-status-outlet=".online-user"'>
<meta data-controller="callout" data-callout-text-value='class="online-user"'>


```html
<div data-controller="search" data-search-result-outlet=".result">
<div>
marcoroth marked this conversation as resolved.
Show resolved Hide resolved
<div class="online-user" data-controller="user-status">...</div>
<div class="online-user" data-controller="user-status">...</div>
...
</div>

...

<div id="results">
<div class="result" data-controller="result">...</div>
<div class="result" data-controller="result">...</div>
<div data-controller="chat" data-chat-user-status-outlet=".online-user">
...
</div>
```
Expand All @@ -33,35 +33,36 @@ While a **target** is a specifically marked element **within the scope** of its

## Attributes and Names

The `data-search-result-outlet` attribute is called an _outlet attribute_, and its value is a [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) which you can use to refer to other controller elements which should be available as outlets on the _host controller_. The outlet identifier in the host controller must be the same as the target controller's name.
The `data-chat-user-status-outlet` attribute is called an _outlet attribute_, and its value is a [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) which you can use to refer to other controller elements which should be available as outlets on the _host controller_. The outlet identifier in the host controller must be the same as the target controller's identifier.

```html
data-[identifier]-[outlet]-outlet="[selector]"
```

<meta data-controller="callout" data-callout-text-value='data-search-result-outlet=".result"'>
<meta data-controller="callout" data-callout-text-value='data-chat-user-status-outlet=".online-user"'>


```html
<div data-controller="search" data-search-result-outlet=".result"></div>
<div data-controller="chat" data-chat-user-status-outlet=".online-user"></div>
```

## Definitions

Define controller identifiers in your controller class using the `static outlets` array. This array declares which other controller identifiers can be used as outlets on this controller:

<meta data-controller="callout" data-callout-text-value='static outlets'>
<meta data-controller="callout" data-callout-text-value='"result"'>
<meta data-controller="callout" data-callout-text-value='"user-status"'>
<meta data-controller="callout" data-callout-text-value='userStatus'>


```js
// search_controller.js
// chat_controller.js

export default class extends Controller {
static outlets = [ "result" ]
static outlets = [ "user-status" ]

connect () {
this.resultOutlets.forEach(result => ...)
this.userStatusOutlets.forEach(status => ...)
}
}
```
Expand All @@ -83,39 +84,39 @@ For each outlet defined in the `static outlets` array, Stimulus adds five proper
Since you get back a `Controller` instance from the `[name]Outlet` and `[name]Outlets` properties you are also able to access the Values, Classes, Targets and all of the other properties and functions that controller instance defines:

```js
this.resultOutlet.idValue
this.resultOutlet.imageTarget
this.resultOutlet.activeClasses
this.userStatusOutlet.idValue
this.userStatusOutlet.imageTarget
this.userStatusOutlet.activeClasses
```

You are also able to invoke any function the outlet controller may define:

```js
// result_controller.js
// user_status_controller.js

export default class extends Controller {
markAsSelected(event) {
// ...
}
}

// search_controller.js
// chat_controller.js

export default class extends Controller {
static outlets = [ "result" ]
static outlets = [ "user-status" ]

selectAll(event) {
this.resultOutlets.forEach(result => result.markAsSelected(event))
this.userStatusOutlets.forEach(status => status.markAsSelected(event))
}
}
```

Similarly with the Outlet Element, it allows you to call any function or property on [`Element`](https://developer.mozilla.org/en-US/docs/Web/API/Element):

```js
this.resultOutletElement.dataset.value
this.resultOutletElement.getAttribute("id")
this.resultOutletElements.map(result => result.hasAttribute("selected"))
this.userStatusOutletElement.dataset.value
this.userStatusOutletElement.getAttribute("id")
this.userStatusOutletElements.map(status => status.hasAttribute("selected"))
```

## Outlet Callbacks
Expand All @@ -125,16 +126,16 @@ Outlet callbacks are specially named functions called by Stimulus to let you res
To observe outlet changes, define a function named `[name]OutletConnected()` or `[name]OutletDisconnected()`.

```js
// search_controller.js
// chat_controller.js

export default class extends Controller {
static outlets = [ "result" ]
static outlets = [ "user-status" ]

resultOutletConnected(outlet, element) {
userStatusOutletConnected(outlet, element) {
// ...
}

resultOutletDisconnected(outlet, element) {
userStatusOutletDisconnected(outlet, element) {
// ...
}
}
Expand All @@ -145,35 +146,35 @@ export default class extends Controller {
When you access an Outlet property in a Controller, you assert that at least one corresponding Outlet is present. If the declaration is missing and no matching outlet is found Stimulus will throw an exception:

```html
Missing outlet element "result" for "search" controller
Missing outlet element "user-status" for "chat" controller
```

### Optional outlets

If an Outlet is optional or you want to assert that at least Outlet is present, you must first check the presence of the Outlet using the existential property:

```js
if (this.hasResultOutlet) {
this.resultOutlet.safelyCallSomethingOnTheOutlet()
if (this.hasUserStatusOutlet) {
this.userStatusOutlet.safelyCallSomethingOnTheOutlet()
}
```

### Referencing Non-Controller Elements

Stimulus will throw an exception if you try to declare an element as an outlet which doesn't have a corresponding `data-controller` and identifier on it:

<meta data-controller="callout" data-callout-text-value='data-search-result-outlet="#result"'>
<meta data-controller="callout" data-callout-text-value='id="result"'>
<meta data-controller="callout" data-callout-text-value='data-chat-user-status-outlet="#user-column"'>
<meta data-controller="callout" data-callout-text-value='id="user-column"'>


```html
<div data-controller="search" data-search-result-outlet="#result"></div>
<div data-controller="chat" data-chat-user-status-outlet="#user-column"></div>

<div id="result"></div>
<div id="user-column"></div>
```

Would result in:
```html
Missing "data-controller=result" attribute on outlet element for
"search" controller`
Missing "data-controller=user-status" attribute on outlet element for
"chat" controller`
```