Skip to content

Commit

Permalink
more work on alpine.js samples/docs
Browse files Browse the repository at this point in the history
  • Loading branch information
fzumstein committed Nov 5, 2024
1 parent 94d98bd commit 9a55d2d
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 19 deletions.
20 changes: 11 additions & 9 deletions app/static/js/core/examples.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
const visibility = {
open: false,
isOpen: false,
label: "Show",

toggle(event) {
this.open = !this.open;
this.label = this.open ? "Hide" : "Show";
toggle() {
this.isOpen = !this.isOpen;
this.label = this.isOpen ? "Hide" : "Show";
},
};
registerAlpineComponent("visibility", visibility);

const slider = {
percentage: 50,
update(event) {
this.percentage = event.target.value;
update() {
this.percentage = this.$el.value;
},
};
registerAlpineComponent("slider", slider);
Expand All @@ -21,8 +20,11 @@ const nameForm = {
firstName: "",
lastName: "",
fullName: "(empty)",
handleInput(event) {
this[event.target.name] = event.target.value;
focus() {
this.$el.focus();
},
handleInput() {
this[this.$el.name] = this.$el.value;
const fullName = `${this.firstName} ${this.lastName}`;
this.fullName = !this.firstName && !this.lastName ? "(empty)" : fullName;
},
Expand Down
2 changes: 1 addition & 1 deletion app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<script type="module" src="{{ settings.static_url_path }}/js/core/xlwingsjs/alert.js" defer></script>
{# Alpine.js CSP boilerplate (must come before custom JS, which must come before alpinejs library) #}
{% if settings.enable_alpinejs_csp %}
<script src="{{ settings.static_url_path }}/js/core/alpinejs-csp-boilerplate.js" defer></script>
<script src="{{ settings.static_url_path }}/js/core/alpinejs-csp-boilerplate.js"></script>
{% endif %}
{# JS Config #}
<!-- prettier-ignore-start -->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Alpine.js form sample
# Alpine.js sample

When you type in a name in the `First Name` and `Last Name` form fields, Alpine.js will live-update the `Full Name`.
This example demonstrates a few common Alpine.js features. They are explained on the task pane itself.

To try it out, replace `app/routers/taskpane.py` with the following code:

Expand All @@ -16,7 +16,7 @@ router = APIRouter()
async def taskpane(request: Request):
return TemplateResponse(
request=request,
name="examples/alpine_form/taskpane_form.html",
name="examples/alpine/taskpane.html",
)
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<h1 class="mt-4">1. Visibility</h1>
<p>Show and hide an element.</p>
<div x-data="visibility">
<button @click="toggle" class="btn btn-primary w-25" x-text="label"></button>
<span class="ms-4" x-show="open">The Magic of Alpine.js!</span>
<button @click="toggle" class="btn btn-primary w-25" x-text="label" x-init="testinit"></button>
<span class="ms-4" x-show="isOpen">The Magic of Alpine.js!</span>
</div>

<h1 class="mt-4">2. Slider</h1>
Expand All @@ -17,7 +17,10 @@ <h1 class="mt-4">2. Slider</h1>
</div>

<h1>3. Names</h1>
<p>Concatenate the first and last name as you type.</p>
<p>
Concatenates the first and last name as you type. You can activate the First Name input box via the
<code>/</code> keyboard shortcut.
</p>
<div x-data="nameForm">
<form>
<div class="mb-3">
Expand All @@ -28,6 +31,7 @@ <h1>3. Names</h1>
class="form-control"
:value="firstName"
@input="handleInput"
@keydown.window.slash.prevent="focus"
autocorrect="off" />
</div>

Expand Down
189 changes: 187 additions & 2 deletions docs/alpinejs.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,188 @@
# Alpine.js CSP build
# Alpine.js CSP

TODO
Alpine.js is a lightweight yet powerful JavaScript framework. When you want to avoid a full server roundtrip with [](htmx.md), you can use Alpine.js to make your HTML page interactive. Compared to writing vanilla JavaScript, Alpine.js CSP brings structure to your code and simplifies many tasks. However, minimal JavaScript knowledge is still required.

The standard version of Alpine.js isn't compatible with the [CSP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). Therefore, xlwings Server includes the [CSP build](https://alpinejs.dev/advanced/csp) by default. The main difference is that you place all your code outside of the HTML tags, meaning the standard Alpine.js documentation needs adaptation for use.

xlwings Server provides an additional command, `registerAlpineComponent()`, to make handling the JavaScript code easier.

Throughout the rest of this page, when we refer to Alpine or Alpine.js, we mean the CSP build of Alpine.js.

## First Steps

To use Alpine.js, add the `x-data` directive to the part of your HTML that you want to bring to life. This converts the specified section into an Alpine component. For optimal performance, apply it to an HTML tag that wraps the smallest possible amount of code.

Let's have a look at the first example under [`app/templates/examples/alpine`](https://github.com/xlwings/xlwings-server/tree/main/app/templates/examples/alpine). For clarity, the [](bootstrap.md) classes that are used in the example aren't shown here so that we can concentrate on the Alpine.js directives:

```html
<div x-data="visibility">
<button x-text="label" @click="toggle"></button>
<span x-show="isOpen">The Magic of Alpine.js!</span>
</div>
```

- `x-data` turns the `div` into an Alpine component.
- `x-text` sets the text content of an element, in our case the button, to the value of `label`.
- `x-show` shows or hides an element based on whether `isOpen` is `true` or `false`.
- `@click` runs the `toggle()` method when the button's click event is fired (i.e., when the button is clicked.)

To bring the Alpine component to life, we have to implement the `visibility` object with the `isOpen` and `label` properties and the `toggle()` method. To finalize, the component has to be registered by calling `registerAlpineComponent()`. This function connects the name given in `x-data` with the JavaScript object.

```js
const visibility = {
isOpen: false,
label: "Show",

toggle() {
this.isOpen = !this.isOpen;
this.label = this.isOpen ? "Hide" : "Show";
},
};
registerAlpineComponent("visibility", visibility);
```

## Where to write the Alpine.js JavaScript code

The JavaScript sample code lives in [`app/static/js/core/examples.js`](https://github.com/xlwings/xlwings-server/blob/main/app/static/js/core/examples.js).

If you only have a few Alpine components, you can place the JavaScript code into [`app/static/js/main.js`](https://github.com/xlwings/xlwings-server/blob/main/app/static/js/main.js).

If your task pane turns into a complex app, you could also introduce a proper structure along the following lines:

```none
app/
static/
js/
components/
auth/
login-form.js
signup-form.js
dashboard/
data-table.js
metrics-chart.js
shared/
notification.js
modal.js
```

You could load these JS files on-demand in your templates via

```jinja
{% block extra_head %}
...
{% endblock extra_head %}
```

1. `alpinejs-csp-boilerplate.js`
2. Your own JS module
3. `vendor/@alpinejs/csp/dist/cdn.min.js`

## Directives

Here are the most useful Alpine directives (click on the name to get to the official docs):

- [`x-data`](https://alpinejs.dev/directives/data)
- [`x-show`](https://alpinejs.dev/directives/show)
- [`x-bind`](https://alpinejs.dev/directives/bind) This is usually used via colon, e.g., `:value`.
- [`x-on`](https://alpinejs.dev/directives/on) This is usually used via `@`, e.g., `@click`.
- [`x-text`](https://alpinejs.dev/directives/text)
- [`x-if`](https://alpinejs.dev/directives/if)
- [`x-init`](https://alpinejs.dev/directives/init)

For a complete list, see the [official docs]().

## Init and destroy functions

- If your object contains an `init()` method, Alpine executes it before it renders the component. If your component also has an `x-init` directive, `init()` function will be called first.
- If your object contains a `destroy()` method, Alpine executes it before cleaning up the component.

See the [official docs](https://alpinejs.dev/globals/alpine-data#init-functions).

## Magics

Magic properties and methods start with a `$`, e.g., `$el` and they give you access to a few powerful features. With the CSP build, you have to use them via `this`, e.g., to access the current DOM element:

```js
this.$el
```

For example, you would use this to get the button element that triggered the click event. You can see `$el` in action in the next section about [](#event-object).

For an overview of all magic properties, see the [official docs](https://alpinejs.dev/magics/el).

## Keyboard shortcuts

Alpine.js makes it easy to build keyboard shortcuts. If you want them to be global, them to the `window` object. For example, to focus on a form input element when typing `/`, it's enough to add this attribute to the respective HTML tag:

```html
<input @keydown.window.slash.prevent="focus" />
```

with the corresponding JavaScript method:

```js
focus() {
this.$el.focus();
}
```

Here's an explanation:

- `keydown` event fires when you press any key
- `window` attaching the event to the `window` object allows you to listen to it globally
- `slash` this is the key pressed
- `prevent` this prevents the default behavior, as otherwise it would write `/` into the text box.

You can try this example out by going to the name example under [`app/templates/examples/alpine`](https://github.com/xlwings/xlwings-server/tree/main/app/templates/examples/alpine).

## Event object

If you have advanced needs, you can access the `event` object when handling one. This is JavaScript part of the slider example:

```js
const slider = {
percentage: 50,
update() {
this.percentage = this.$el.value;
},
};
```

Instead of using the `$el` magic, you could also use the `event` object directly like so:

```js
const slider = {
percentage: 50,
update(event) {
this.percentage = event.target.value;
},
};
```

Alternatively, you could also use Alpine's `$event` magic:

```js
const slider = {
percentage: 50,
update() {
this.percentage = this.$event.target.value;
},
};
```

However, using the CSP build, explicitly passing the `event` means less typing and is arguably more elegant.

## Alpine.js vs Alpine.js CSP build

The CSP build doesn't support everything from standard Alpine.js. Here's a list of limitations:

- `x-model` isn't supported. Instead, use a combination of `:value` and `@input`, see the name example in [`app/templates/examples/alpine`](https://github.com/xlwings/xlwings-server/tree/main/app/templates/examples/alpine)
- When you work with AI, prompt it to deliver the solution using `Alpine.data()`, as this is essentially what the CSP build uses behind the scenes.

## Alternatives

- [Vanilla JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
- [jQuery](https://jquery.com/)
- [Stimulus](https://stimulus.hotwired.dev/)
- [Hyperscript](https://hyperscript.org/)
- [Vue.js](https://vuejs.org/guide/quick-start.html#using-vue-from-cdn)
11 changes: 10 additions & 1 deletion docs/bootstrap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Bootstrap

xlwings Server uses Bootstrap 5 as its UI (user interface) toolkit. Bootstrap is responsible for making fonts, buttons, etc. look beautiful across different browsers, operating systems, and screen sizes. It also offers a range of useful components, such as navbars, dropdowns and many more.
By default, xlwings Server uses Bootstrap 5 as its UI (user interface) toolkit. Bootstrap is responsible for making fonts, buttons, etc. look beautiful across different browsers and operating systems. It also offers a range of useful components, such as navbars, dropdowns and many more.

It is _responsive_, which means that it can adopt to different screen sizes by rearranging components or by changing the size of pictures. This is useful as the task pane can be extended to cover half of Excel's grid.

Expand All @@ -22,7 +22,16 @@ To get a deeper understanding of Bootstrap, have a look at the following topics:
- [Grid](https://getbootstrap.com/docs/5.3/layout/grid/)
- Bootstrap is based on flexbox. Understanding it will help you position your components correctly on the screen. You can get an introduction on [mdn web doc](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Basic_concepts_of_flexbox) before having a look at the [Bootstrap-specific docs](https://getbootstrap.com/docs/5.3/utilities/flex/).

## Alternatives

- [Pico CSS](https://picocss.com/)
- [Bulma](https://bulma.io/)
- [Tailwind CSS](https://tailwindcss.com/)

For more options, see [Awesome CSS Frameworks](https://github.com/troxler/awesome-css-frameworks).

## References

- [Bootstrap Homepage](https://getbootstrap.com/)
- [Boostrap Docs](https://getbootstrap.com/docs)
- [Bootstrap-xlwings](https://github.com/xlwings/bootstrap-xlwings)

0 comments on commit 9a55d2d

Please sign in to comment.