diff --git a/app/static/js/core/examples.js b/app/static/js/core/examples.js index d454e990..07d6c3e0 100644 --- a/app/static/js/core/examples.js +++ b/app/static/js/core/examples.js @@ -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); @@ -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; }, diff --git a/app/templates/base.html b/app/templates/base.html index 8214becf..16ef67eb 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -32,7 +32,7 @@ {# Alpine.js CSP boilerplate (must come before custom JS, which must come before alpinejs library) #} {% if settings.enable_alpinejs_csp %} - + {% endif %} {# JS Config #} diff --git a/app/templates/examples/alpine_form/README.md b/app/templates/examples/alpine/README.md similarity index 66% rename from app/templates/examples/alpine_form/README.md rename to app/templates/examples/alpine/README.md index 9ea14ddb..e0c69b96 100644 --- a/app/templates/examples/alpine_form/README.md +++ b/app/templates/examples/alpine/README.md @@ -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: @@ -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", ) ``` diff --git a/app/templates/examples/alpine_form/taskpane_form.html b/app/templates/examples/alpine/taskpane.html similarity index 82% rename from app/templates/examples/alpine_form/taskpane_form.html rename to app/templates/examples/alpine/taskpane.html index fe042ce6..408c66cb 100644 --- a/app/templates/examples/alpine_form/taskpane_form.html +++ b/app/templates/examples/alpine/taskpane.html @@ -5,8 +5,8 @@

1. Visibility

Show and hide an element.

- - The Magic of Alpine.js! + + The Magic of Alpine.js!

2. Slider

@@ -17,7 +17,10 @@

2. Slider

3. Names

-

Concatenate the first and last name as you type.

+

+ Concatenates the first and last name as you type. You can activate the First Name input box via the + / keyboard shortcut. +

@@ -28,6 +31,7 @@

3. Names

class="form-control" :value="firstName" @input="handleInput" + @keydown.window.slash.prevent="focus" autocorrect="off" />
diff --git a/docs/alpinejs.md b/docs/alpinejs.md index 13e718dc..c01bf733 100644 --- a/docs/alpinejs.md +++ b/docs/alpinejs.md @@ -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 +
+ + The Magic of Alpine.js! +
+``` + +- `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 + +``` + +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) diff --git a/docs/bootstrap.md b/docs/bootstrap.md index 82790792..da6bd9ce 100644 --- a/docs/bootstrap.md +++ b/docs/bootstrap.md @@ -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. @@ -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)