Skip to content

Commit

Permalink
Improve getting started guide
Browse files Browse the repository at this point in the history
  • Loading branch information
bencroker committed Dec 7, 2024
1 parent 1000e2e commit 21749ef
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 54 deletions.
2 changes: 1 addition & 1 deletion site/routes_home.templ
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ templ Home() {
></script>`
}}
{{
usageSample := `<input data-bind-title type="text">
usageSample := `<input data-bind-title />
<div data-text="title.value.toUpperCase()"></div>
<button data-on-click="sse('/endpoint', {method: 'post'})">Save</button>`
}}
Expand Down
112 changes: 59 additions & 53 deletions site/static/md/guide/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ Datastar uses signals to manage state. You can think of signals as reactive vari
Datastar provides us with a way to set up two-way data binding on an element using the [`data-bind`](/reference/plugins_dom#bind) attribute, which can be placed on any HTML element that users can directly input data or choices from (`input`, `textarea`, `select`, `checkbox` and `radio` elements).

```html
<input data-bind-input type="text" />
<input data-bind-input />
```

This creates a new signal called `input`, and binds it to the element's value. If either is changed, the other automatically updates.

An alternative syntax also exists for `data-bind`, in which the value is used as the signal name. This can be useful depending on the templating language you are using.
An alternative syntax exists for `data-bind`, in which the value is used as the signal name. This can be useful depending on the templating language you are using.

```html
<input data-bind="input" type="text" />
<input data-bind="input" />
```

### `data-text`
Expand All @@ -86,7 +86,7 @@ To see this in action, we can use the [`data-text`](/reference/plugins_dom#text)
</div>
</div>

This sets the text content of an element to the value of the signal `input.value`. The `.value` is required to denote a the use of a signal's *value* in the expression.
This sets the text content of an element to the value of the signal `input`. The `.value` is required to denote the signal's *value* in the expression.

The value of the `data-text` attribute is an expression that is evaluated, meaning that we can use JavaScript in it.

Expand Down Expand Up @@ -115,13 +115,15 @@ The [`data-computed`](/reference/plugins_core#computed) attribute creates a new

```html
<div data-computed-repeated="input.value.repeat(2)">
<input data-bind-input type="text" />
<input data-bind-input />
<div data-text="repeated.value">
Will be replaced with the contents of the repeated signal
</div>
</div>
```

This results in the `repeated` signal's value always being equal to the value of the `input` signal repeated twice. Computed signals are useful for memoizing expressions containing other signals.

<div data-signals-input3="''" data-computed-repeated="input3.value.repeat(2)" class="flex items-start justify-between p-8 alert">
<div class="flex flex-col gap-4">
<div class="flex items-center">
Expand All @@ -137,13 +139,13 @@ The [`data-computed`](/reference/plugins_core#computed) attribute creates a new

### `data-show`

The [`data-show`](/reference/plugins_browser#show) attribute can be used to show or hide an element based on whether a JavaScript expression evaluates to `true` or `false`.
The [`data-show`](/reference/plugins_browser#show) attribute can be used to show or hide an element based on whether an expression evaluates to `true` or `false`.

```html
<button data-show="input.value != ''">Save</button>
```

This results in the button being visible only when the input is _not_ empty.
This results in the button being visible only when the input is _not_ an empty string (this could also be written as `!input.value`).

<div class="flex items-start justify-between p-8 alert">
<div class="flex flex-col gap-4">
Expand All @@ -169,11 +171,7 @@ The [`data-class`](/reference/plugins_dom#class) attribute allows us to add or r
<button data-class-hidden="input.value == ''">Save</button>
```

Since the expression evaluates to `true` or `false`, we can rewrite this as `!input.value`.

```html
<button data-class-hidden="!input.value">Save</button>
```
If the expression evaluates to `true`, the `hidden` class is added to the element; otherwise, it is removed.

<div class="flex items-start justify-between p-8 alert">
<div class="flex flex-col gap-4">
Expand All @@ -186,12 +184,12 @@ Since the expression evaluates to `true` or `false`, we can rewrite this as `!in
<div data-text="input5.value" class="output"></div>
</div>
</div>
<button data-class-hidden="!input5.value" class="btn btn-primary">
<button data-class-hidden="input5.value != ''" class="btn btn-primary">
Save
</button>
</div>

The `data-class` attribute can also be used to add or remove multiple classes from an element using a set of key-value pairs, where keys represent class names and values represent expressions.
The `data-class` attribute can also be used to add or remove multiple classes from an element using a set of key-value pairs, where the keys represent class names and the values represent expressions.

```html
<button data-class="{hidden: input.value == '', bold: input.value == 1}">Save</button>
Expand All @@ -205,7 +203,7 @@ The [`data-attributes`](/reference/plugins_dom#attributes) attribute can be used
<button data-attributes-disabled="input.value == ''">Save</button>
```

This results in the button being given the `disabled` attribute whenever the input is empty.
This results in a `disabled` attribute being given the value `true` whenever the input is an empty string.

<div class="flex items-start justify-between p-8 alert" data-signals-input6="''">
<div class="flex flex-col gap-4">
Expand All @@ -223,34 +221,34 @@ This results in the button being given the `disabled` attribute whenever the inp
</button>
</div>

The `data-attributes` attribute can also be used to add or remove multiple classes from an element using a set of key-value pairs, where keys represent class names and values represent expressions.
The `data-attributes` attribute can also be used to set the values of multiple attributes on an element using a set of key-value pairs, where the keys represent attribute names and the values represent expressions.

```html
<button data-attributes="{disabled: input.value == '', title: input.value}">Save</button>
```

### `data-signals`

So far, we've created signals on the fly using `data-bind` and `data-computed-*`. All signals are merged into a global set of signals that are accessible from anywhere in the DOM.
So far, we've created signals on the fly using `data-bind` and `data-computed`. All signals are merged into a global set of signals that are accessible from anywhere in the DOM.

We can create a signal using the [`data-signals`](/reference/plugins_core#signals) attribute.
We can also create signals using the [`data-signals`](/reference/plugins_core#signals) attribute.

```html
<div data-signals-input="1"></div>
```

Adding `data-signals` to multiple elements is allowed, as signals are _merged_ into the existing signals (values defined later in the DOM tree override those defined earlier).
Using `data-signals` _merges_ one or more signals into the existing signals. Values defined later in the DOM tree override those defined earlier.

Signals are nestable using dot-notation, which can be useful for namespacing.

```html
<div data-signals-primary.input="1"></div>
<div data-signals-form.input="2"></div>
```

The `data-signals` attribute can also be used to merge multiple signals using a set of key-value pairs, where keys represent signal names and values represent expressions.
The `data-signals` attribute can also be used to merge multiple signals using a set of key-value pairs, where the keys represent signal names and the values represent expressions.

```html
<div data-signals="{primary: {input: 1}, secondary: {input: 2}}"></div>
<div data-signals="{input: 1, form: {input: 2}}"></div>
```

### `data-on`
Expand All @@ -261,7 +259,7 @@ The [`data-on`](/reference/plugins_dom#on) attribute can be used to attach an ev
<button data-on-click="input.value = ''">Reset</button>
```

This results in the `input` signal's value being set to an empty string when the button element is clicked. If the `input` signal is used elsewhere, its value will automatically update. This can be used with any valid event name such as `data-on-keydown`, `data-on-mouseover`, etc.
This results in the `input` signal's value being set to an empty string whenever the button element is clicked. This can be used with any valid event name such as `data-on-keydown`, `data-on-mouseover`, etc.

<div class="flex items-start justify-between p-8 alert" >
<div class="flex flex-col gap-4">
Expand All @@ -279,8 +277,9 @@ This results in the `input` signal's value being set to an empty string when the
</button>
</div>

So what else can we do with these expressions? Anything we want, really.
See if you can follow the code below _before_ trying the demo.
So what else can we do now that we have declarative signals and expressions? Anything we want, really.

See if you can follow the code below based on what you've learned so far, _before_ trying the demo.

```html
<div
Expand Down Expand Up @@ -323,15 +322,15 @@ We've just scratched the surface of frontend reactivity. Now let's take a look a

## Backend Setup

Datastar uses [Server-Sent Events](https://en.wikipedia.org/wiki/Server-sent_events) or SSE. There's no special backend plumbing required to use SSE, just some special syntax. Fortunately, SSE is straightforward and [provides us with some advantages](/essays/event_streams_all_the_way_down).
Datastar uses [Server-Sent Events](https://en.wikipedia.org/wiki/Server-sent_events) (SSE). There's no special backend plumbing required to use SSE, just some syntax. Fortunately, SSE is straightforward and [provides us with some advantages](/essays/event_streams_all_the_way_down).

First, set up your backend in the language of your choice. Using one of the helper SDKs (available for [Go](https://github.com/starfederation/datastar/tree/main/sdk/go) and [PHP](https://github.com/starfederation/datastar/tree/main/sdk/php), with more on the way) will help you get up and running faster. We're going to use the SDKs in the examples below, which set the appropriate headers and format the events for us, but this is optional.

The following code would exist in a controller action endpoint in your backend.

!!!CODE_SNIPPET:getting_started/setup!!!

The `mergeFragments()` method merges the provided HTML fragment into the DOM, replacing the element with `id="question"`. An element with the ID `question` must already exist in the DOM.
The `mergeFragments()` method merges the provided HTML fragment into the DOM, replacing the element with `id="question"`. An element with the ID `question` must _already_ exist in the DOM.

The `mergeSignals()` method merges the `response` and `answer` signals into the frontend signals.

Expand Down Expand Up @@ -383,15 +382,12 @@ Now when the `Fetch a question` button is clicked, the server will respond with

### `data-indicator`

The [`data-indicator`](/reference/plugins_backend#data-indicator) attribute sets the value of the provided signal name to `true` while the request is in flight. We can use this signal to show a loading indicator, which may be desirable for slower responses.

Note that elements using the `data-indicator` attribute _must_ have a unique ID attribute.
The [`data-indicator`](/reference/plugins_backend#data-indicator) attribute sets the value of a signal to `true` while the request is in flight, otherwise `false`. We can use this signal to show a loading indicator, which may be desirable for slower responses.

```html
<div id="question"></div>
<div data-class-loading="fetching.value" class="indicator"></div>
<button
id="fetch-a-question"
data-on-click="sse('/actions/quiz')"
data-indicator-fetching
>
Expand All @@ -403,21 +399,31 @@ Note that elements using the `data-indicator` attribute _must_ have a unique ID
<div class="pb-3 space-y-3">
<div id="question3"></div>
<div class="flex items-center gap-2">
<button id="fetch-a-question" data-on-click="sse('/examples/quiz_slow/data')" data-indicator="fetching" class="btn btn-secondary">
<button id="fetch-a-question" data-on-click="sse('/examples/quiz_slow/data')" data-indicator-fetching class="btn btn-secondary">
Fetch a question
</button>
<div data-class-loading="fetching.value" class="indicator"></div>
</div>
</div>
</div>

We're not limited to just `GET` requests. We can also send `GET`, `POST`, `PUT`, `PATCH` and `DELETE` requests, using the `sse()` with a `method:'post'` for example.
The `data-indicator` attribute can also be written with the value used as the signal name.

```html
<button
data-on-click="sse('/actions/quiz')"
data-indicator="fetching"
>
```

We're not limited to just `GET` requests. We can send `GET`, `POST`, `PUT`, `PATCH` and `DELETE` requests, using the `method` option of the `sse()` action.

Here's how we could send an answer to the server for processing, using a `POST` request.

```html
<button data-on-click="sse('/actions/quiz', {method:'post'})">Submit answer</button>
<button data-on-click="sse('/actions/quiz', {method: 'post'})">
Submit answer
</button>
```

One of the benefits of using SSE is that we can send multiple events (HTML fragments, signal updates, etc.) in a single response.
Expand All @@ -426,45 +432,45 @@ One of the benefits of using SSE is that we can send multiple events (HTML fragm

## Actions

Actions in Datastar are helper functions that are available in `data-*` attributes and have the syntax `actionName()`. We already saw the `sse` action above. Here are a few other common actions.
Actions in Datastar are helper functions that are available in `data-*` attributes and have the syntax `actionName()`. We already saw the `sse()` action above. Here are a few other common actions.

### `setAll()`

The `setAll()` action sets the values of multiple signals at once. It takes a path prefix that is used to match against signals, and a value to set them to, as arguments.

```html
<button data-on-click="setAll('form_', true)"></button>
<button data-on-click="setAll('form.', true)"></button>
```

This sets the values of all signals containing `form_` to `true`, which could be useful for enabling input fields in a form.
This sets the values of all signals nested under the `form` signal to `true`, which could be useful for enabling input fields in a form.

```html
<input type="checkbox" data-bind-checkbox_1 /> Checkbox 1
<input type="checkbox" data-bind-checkbox_2 /> Checkbox 2
<input type="checkbox" data-bind-checkbox_3 /> Checkbox 3
<button data-on-click="setAll('checkbox_', true)">Check All</button>
<input type="checkbox" data-bind-checkboxes.checkbox1 /> Checkbox 1
<input type="checkbox" data-bind-checkboxes.checkbox2 /> Checkbox 2
<input type="checkbox" data-bind-checkboxes.checkbox3 /> Checkbox 3
<button data-on-click="setAll('checkboxes.', true)">Check All</button>
```

<div class="flex flex-col items-start gap-2 p-8 alert">
<div class="form-control">
<label class="gap-2 cursor-pointer label">
<span class="label-text">Checkbox 1</span>
<input type="checkbox" class="toggle" data-bind-checkbox_1_1/>
<input type="checkbox" data-bind-checkboxes1.checkbox1 class="toggle" />
</label>
</div>
<div class="form-control">
<label class="gap-2 cursor-pointer label">
<span class="label-text">Checkbox 2</span>
<input type="checkbox" class="toggle" data-bind-checkbox_1_2/>
<input type="checkbox" data-bind-checkboxes1.checkbox2 class="toggle" />
</label>
</div>
<div class="form-control">
<label class="gap-2 cursor-pointer label">
<span class="label-text">Checkbox 3</span>
<input type="checkbox" class="toggle" data-bind-checkbox_1_3/>
<input type="checkbox" data-bind-checkboxes1.checkbox3 class="toggle" />
</label>
</div>
<button data-on-click="setAll('checkbox_1_', true)" class="mt-4 btn btn-secondary">
<button data-on-click="setAll('checkboxes1.', true)" class="mt-4 btn btn-secondary">
Check All
</button>
</div>
Expand All @@ -480,29 +486,29 @@ The `toggleAll()` action toggles the values of multiple signals at once. It take
This toggles the values of all signals containing `form.` (to either `true` or `false`), which could be useful for toggling input fields in a form.

```html
<input type="checkbox" data-bind-checkbox_1 /> Checkbox 1
<input type="checkbox" data-bind-checkbox_2 /> Checkbox 2
<input type="checkbox" data-bind-checkbox_3 /> Checkbox 3
<button data-on-click="toggleAll('checkbox_')">Toggle All</button>
<input type="checkbox" data-bind-checkboxes.checkbox1 /> Checkbox 1
<input type="checkbox" data-bind-checkboxes.checkbox2 /> Checkbox 2
<input type="checkbox" data-bind-checkboxes.checkbox3 /> Checkbox 3
<button data-on-click="toggleAll('checkboxes.')">Toggle All</button>
```

<div class="flex flex-col items-start gap-2 p-8 alert">
<div class="form-control">
<label class="gap-2 cursor-pointer label">
<span class="label-text">Checkbox 1</span>
<input type="checkbox" class="toggle" data-bind-checkbox_2_1/>
<input type="checkbox" data-bind-checkboxes2.checkbox_1 class="toggle" />
</label>
</div>
<div class="form-control">
<label class="gap-2 cursor-pointer label">
<span class="label-text">Checkbox 2</span>
<input type="checkbox" class="toggle" data-bind-checkbox_2_2/>
<input type="checkbox" data-bind-checkboxes2.checkbox_2 class="toggle" />
</label>
</div>
<div class="form-control">
<label class="gap-2 cursor-pointer label">
<span class="label-text">Checkbox 3</span>
<input type="checkbox" class="toggle" data-bind-checkbox_2_3/>
<input type="checkbox" data-bind-checkboxes2.checkbox_3 class="toggle" />
</label>
</div>
<button data-on-click="toggleAll('checkbox_2_')" class="mt-4 btn btn-secondary">
Expand All @@ -518,7 +524,7 @@ Using [`data-*`](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_d
- Set the text content of an element to an expression.: `data-text="foo.value"`
- Create a computed signal: `data-computed-foo="bar.value + 1"`
- Show or hide an element using an expression: `data-show="foo.value"`
- Modify the classes on an element: `data-class-bold="foo.value"`
- Modify the classes on an element: `data-class-bold="foo.value == 1"`
- Bind an expression to an HTML attribute: `data-attributes-disabled="foo.value == ''"`
- Merge signals into the signals: `data-signals-foo=""`
- Execute an expression on an event: `data-on-click="sse(/endpoint)"`
Expand Down

0 comments on commit 21749ef

Please sign in to comment.