Kentico Xperience Xplorations: Why We Should Be Using AlpineJs
Tags: xperience, vue, javascript, alpinejs
Published: 2021-02-22 15:07:37

---
title: Kentico Xperience Xplorations: Why We Should Be Using AlpineJs
published: true
description: Learn why AlpineJs might be the ideal JavaScript library for server-rendered sites like Kentico Xperience.
tags: xperience, vuejs, javascript, alpinejs
series: Kentico Xperience Xplorations
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/algcgsbuf2z4hv65yrw7.jpg
---

> In this post we'll be exploring why [AlpineJs](https://github.com/alpinejs/alpine/) is an ideal JavaScript library for integrating server-side HTML rendering and client-side JavaScript interactivity.

## 🧭 Starting Our Journey: Choosing Our Tools

For many ASP.NET developers, JavaScript on the web is synonymous with [jQuery](https://jquery.com/), and while jQuery certainly is [ubiquitous](https://trends.builtwith.com/javascript/jQuery), it's not the only option out there.

The primary benefits of jQuery in 2021 are its ease of use (just add a `<script>` element to the page!) and its [vast plugin ecosystem](https://www.npmjs.com/search?q=keywords:jquery-plugin). However, there are also some drawbacks that come with this library 😮.

### 🍝 jQuery Spaghetti

jQuery is largely concerned with providing a consistent (and flexible) API for manipulating the DOM and using browser features through JavaScript. It takes an [imperative](https://en.wikipedia.org/wiki/Imperative_programming) and [procedural](https://en.wikipedia.org/wiki/Imperative_programming) approach to these concerns, because jQuery is a low-level library.

The benefits of a consistent DOM API across browsers doesn't really apply to us anymore in the era of modern, evergreen browsers 🙏🏻.

Likewise, browsers have adopted the jQuery DOM selector syntax ([Sizzle](https://blog.jquery.com/2012/07/04/the-new-sizzle/)) in the `document.querySelector()` and `document.querySelectorAll()` functions 😎.

With these no-longer-benefits out of the way, what do we typically end up with when using jQuery in 2021?

Unfortunately, sometimes it's not pretty 🙄.

The pattern of building something with jQuery typically involves these steps:

1. Find some DOM elements (often by `id` or `class`)
1. Register some event handlers with those DOM elements so we can react to user interactions on the page
1. Write logic in JavaScript that is specific to our application
1. Repeat

Steps 1 and 2 are the ones that become more problematic as the complexity of our JavaScript grows.

Since jQuery is a low-level library, we are responsible for all the plumbing 👩🏽‍🔧 work. Plumbing is all the code we have to write to 'hook things up', and this plumbing gets spread throughout our business logic.

Not only does this make the important part of our code (the business logic) more confusing, but it's also something we need to maintain over the life of the application.

![Tangled spaghetti noodles](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mffcwq7sv4x7emu7dwt6.jpg)
<figcaption>A tasty meal, but not the ideal application architecture.</figcaption>
<figcaption>Photo by <a href="https://unsplash.com/@mindaugas">mindaugas</a></figcaption>

The term 'jQuery spaghetti' is meant to describe the kind of code we end up being forced to write when trying to build complex UIs with jQuery because the business logic code and plumbing code are all mixed together, and often tightly coupled.

Here's an example of jQuery spaghetti (maybe not a full plate 🍝):

```html
<form id="myForm">
  <input id="email" type="email" name="email" />
  <span class="error" style="display: none"></span>

  <button type="submit">Submit</button>
</form>
```

```javascript
$(function () {
    // Find our form
    const formEl = $('#myForm');

    if (!formEl) {
      console.error('Could not find form');
      return;
    }

    // Register an event listener
    $('#myForm').on('click', function (e) {
      e.preventDefault();

      // Find our form field
      const emailEl = $('form [name="email"]');

      if (!emailEl) {
        console.error('Could not email form field');
        return;
      }

      // Get the email value
      const email = emailEl.val();

      // find the error element
      const errorEl = $('form .error');

      if (!errorEl) {
        console.error('Could not find error message element');
        return;
      }

      if (!email) {
        // set the error message
        errorEl.text('An email address is required');
        errorEl.show();
      } else {
        errorEl.hide();
      }
    });
});
```

The code above is almost entirely focused on plumbing 💦, with only a few lines (checking for an email address and showing the error message) of business logic.

If we change the location of our error element and move it out of the form, our code stops working. If we change the class (`error`) on our error element, our code stops working.

Yes, there are best practices to help avoid these problems, but the fact remains that building something in jQuery requires engineering vigilance, careful decision making, a bit of work to just 'hook' things up 😕.

It does not lead to developers walking near the [pit of success](https://blog.codinghorror.com/falling-into-the-pit-of-success/).

So what are our alternatives 🤔?

### 💻 A Modern Component Framework

Modern JavaScript frameworks like [Svelte](https://svelte.dev/) [React](https://reactjs.org/), [Vue](https://vuejs.org/), and [Angular](https://angular.io/) were designed to help us solve the jQuery spaghetti problem.

These frameworks take care of all the plumbing and provide developers with APIs and patterns to ensure their business logic is not littered with finding DOM elements, hooking up event listeners, and explicitly updating the UI.

By taking on the responsibility of plumbing, these frameworks allow developers to grow their JavaScript code in both size and complexity in maintainable ways that result in readable code 😀.

The same functionality that we wrote in jQuery would look like this in Vuejs (including the HTML template for rendering):

```javascript
<template>
  <form @submit.prevent="onSubmit">
    <input id="email" v-model="email" type="email" name="email" />
    <span v-show="error">{{ error }}</span>

    <button type="submit">Submit</button>
  </form>
</template>

<script>
export default {
  data() {
    return { email: '', error: '', };
  },

  methods: {
    onSubmit(e) {
      this.error = !this.email
        ? 'An email address is required'
        : '';
      }
    },
  },
};
</script>
```

Notice how there's no plumbing here 🧐! The connection between the HTML and our JavaScript is **declarative**. We indicate we want the `onSubmit` function to handle submission of the form by adding the `@submit="onSubmit"` attribute ([directive](https://vuejs.org/v2/guide/syntax.html#v-on-Shorthand)) to the `<form>` element.

We also get access to the email input value and the error message by using the `v-model="email"` binding syntax and `{{ error }}` interpolation syntax, respectively.

This leaves us with some HTML enhanced by special syntax, which fortunately doesn't rely on HTML `id` or `class` attributes, and some business logic (the contents of our `onSubmit` function).

We are free to re-structure our HTML, change styles, and modify our business logic - all independently 👍🏽.

I'm a huge fan of browser based client-side HTML rendering frameworks like these, but they unfortunately can pose another problem 🤔!

> Before it's starts to sound like I'm advising against using any of the above frameworks, I want to clarify that I regularly use Vue in my Kentico Xperience applications and these sites have benefited significantly from our team's adoption of it 💪🏿.

> If you want to learn more, read my post [Kentico 12: Design Patterns Part 16 - Integrating Vue.js with MVC](https://dev.to/seangwright/kentico-12-design-patterns-part-16-integrating-vue-js-with-mvc-2m90)

These frameworks enable the functionality of jQuery without having to write the plumbing code, but unfortunately at the cost of losing control over the rendering of the DOM.

While jQuery can be used to create new DOM elements, it is most often used to change the state of elements already in the page.

Modern JavaScript frameworks like Vue, on the other hand, need to render all their DOM from scratch when they are loaded on the page 🤨.

If we were to look at the HTML send from the server for a traditional Single Page Application (SPA), we would see something like this:

```html
<!DOCTYPE html>
<html>
<head>
    <!-- references to our JavaScript app and libraries -->
</head>
<body>
    <div id="app"></div>
</body>
</html>
```

All the UI of the page is created by the framework as children of the `<div id="app"></div>` element, and this is what is meant by the phrase 'client-side rendering'.

This means that search engine crawlers would need to execute the JavaScript to see the final HTML and content of the page, and even if the search engine is able to run our JavaScript, it might penalize us for taking too long to render everything 🤦‍♀️.

This is in stark contrast to server-rendered HTML where the data sent from the server to the browser is going to include everything displayed to the user, so there are no requirements to execute JavaScript or delays to see what it renders on the page.

We ideally would like a library that exists somewhere in between the plumbing free coding of modern JavaScript frameworks, and jQuery's ability to manipulate and create DOM without owning it... 😉

### 🏆 AlpineJs Enters the Chat

[AlpineJs](https://github.com/alpinejs/alpine) fits our requirements exceptionally, and is described as offering us 

> the reactive and declarative nature of big frameworks like Vue or React at a much lower cost.

and

> You get to keep your DOM, and sprinkle in behavior as you see fit.

Well, this sounds great 👏🏾. So, how do we use it?

## 🗺 Our Destination: Using AlpineJs

Let's look at our HTML form example again, but this time with AlpineJs!

First we need to add a `<script>` element within the document's `<head>`:

```html
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.8.0/dist/alpine.min.js" defer></script>
```

Then, we define a `createFormComponent()` function, which is where our component state and methods are initialized:

```html
<script>
  (function () {
    'use strict';

    window.createFormComponent = function () {
      return {
        email: '',
        error: '',

        onSubmit($event) {
          this.error = !this.email 
            ? 'You must enter an email address'
            : '';
        },
      };
    };
  })();
</script>
```

Finally, we annotate our server-rendered HTML with some Alpine specific syntax:

```html
<form id="myForm"
  x-data="createFormComponent()" 
  @submit.prevent="onSubmit">

  <input id="email" type="text" name="email" 
    x-model="email" />
    
  <span class="error" style="display: none"
    x-show="error"
    x-text="error"
  ></span>

  <button type="submit">Submit</button>
</form>
```

Let's look at each part in detail!

---

The AlpineJs script works like most JavaScript libraries that we load into the browser without a build process - as soon as the script executes, it looks for "Alpine" stuff and initializes everything it can find on the page.

```html
<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.8.0/dist/alpine.min.js" defer></script>
```

This means that we can have a page full of existing HTML, rendered on the server and Alpine can hook into it and enable all of its powerful features 😄!

Alpine will look for initialization code (defined by `x-data` directives on our HTML), which can be an inline expression or a function defined the `window` object.

Speaking of initialization functions, let's look at ours next:

```html
<script>
  (function () {
    'use strict';

    window.createFormComponent = function () {
      return {
        email: '',
        error: '',

        onSubmit($event) {
          this.error = !this.email 
            ? 'You must enter an email address' 
            : '';
        },
      };
    };
  })();
</script>
```

This block defines an [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) 🤓 (an immediately invoked function expression), which assigns a function (`createFormComponent`) to the `window` object so that it's accessible to Alpine (functions and variables defined in an IIFE are not accessible outside of it).

The function we defined, `createFormComponent`, returns an object that includes the 'state' (`email`, and `error`) of our component. These are the values that Alpine ensures stay updated based on user interactions, and also ensures result in an update of the HTML when they change. This is the kind of plumbing we want to avoid, and thankfully Alpine takes care of it for us 🤩.

Our initialization function also defines a method, `onSubmit`, that can be called when the user interacts with the component in a specific way.

Note how it sets the value of `this.error`, which is the `error: ''` value in our component state.

It also has access to `this.email` which is the `email: ''` value in our component state.

Now we can look at our enhanced HTML form:

```html
<form id="myForm"
  x-data="createFormComponent()" 
  @submit.prevent="onSubmit">

  <input id="email" type="text" name="email" 
    x-model="email" />
    
  <span class="error" style="display: none"
    x-show="error"
    x-text="error"
  ></span>

  <button type="submit">Submit</button>
</form>
```

Alpine connects data and interactivity to our HTML with [directives](https://github.com/alpinejs/alpine#learn), so let's go through each directive we are using, [step-by-step](https://www.youtube.com/watch?v=sF7ZY3ycnUE).

```html
<form id="myForm"
  x-data="createFormComponent()" 
  @submit.prevent="onSubmit">

  <!-- ... -->

</form>
```

The `x-data="createFormComponent()"` tells Alpine to initialize this `<form>` element and all of its children elements into a component, and set the state and methods they can access to whatever was returned by `createFormComponent()` (in our case this is `email`, `error`, and `onSubmit`).

The `@submit.prevent="onSubmit"` connects our component's `onSubmit()` function to the `submit` event of the form (and also ensures `$event.preventDefault()` is called automatically with the `.prevent` event modifier 😎!)

```html
<input id="email" type="text" name="email" 
  x-model="email" />
```

We make sure the value of the `<input>` always stays up to date with our component's `email: ''` value by using the `x-model="email"` binding syntax. If our JavaScript changes `email`, the `<input>` element will immediately reflect that change - if the user types a new value into to `<input>` our JavaScript will have access to that new value.

```html
<span
  class="error"
  style="display: none"
  x-show="error"
  x-text="error"
></span>
```

We do something similar with the `<span class="error">` by conditionally showing it with `x-show="error"` which will show the element when our component's `error: ''` value is truthy and hide it when it is falsy.

The `x-text` directive sets the `innerText` of our `<span>` to whatever the value of `error` is.

Notice how none of our HTML is connected to our JavaScript through HTML `id` or `class` attribute values, which means it's not brittle to updating design or styles 🧠.

We also don't imperatively connect interactions with our HTML, or the values of our HTML. Instead, Alpine does all the plumbing 🚿 for us and we get to use our 👩🏽‍💻 developer skills to focus on business logic.

Here's a live demo of our AlpineJs solution:

{% codepen https://codepen.io/seangwright/pen/VwmMpyL default-tab=result %}

### Integration With Xperience

If we wanted to populate the error message for our form from Xperience, we could use Razor to set the value, since everything on the page is rendered on the Server:

```js
const errorMessage = '@Model.FormErrorMessage';

window.createFormComponent = function () {
  return {
    email: '',
    error: '',

    onSubmit($event) {
      this.error = !this.email 
        ? errorMessage 
        : '';
    },
  };
};
```

We can also make requests to our ASP.NET Core API, use the response to set our error message, and our form will be asynchronously validated:

```js
window.createFormComponent = function () {
  return {
    email: '',
    error: '',

    async onSubmit($event) {
      const result = await fetch(
        '/api/form', 
        { 
          method: 'POST', 
          body: JSON.stringify({ email: this.email }) 
        })
        .then(resp => resp.json());

      this.error = result.errorMessage;
    },
  };
};
```

Or, imagine a scenario where we have a `<table>` of data and we want to filter the results based on what a user types into an `<input>`:

```html
<div x-data="initializeFilterComponent()">
  <label for="filter">Filter:</label>
  <input id="filter" type="text" x-model="filter">

  <table>
    @foreach (var row in Model.Rows)
    {
      <tr x-show="isRowVisible('@row.Title')">
        <td>@row.Title</td>
      </tr>
    }
  </table>
</div>

<script>
(function () {
  'use strict';

   window.initializeFilterComponent = function () {
     return {
       filter: '',

       isRowVisible(title) {
         return title
           .toLowerCase()
           .includes(this.filter.toLowerCase());
       }
     }
   };
}());
</script>
```

In the example above, all of the table rows are initially displayed (great for SEO!) and are only filtered when the user starts typing in the `<input>` 🎉!

## ✈ Heading Home: Which Option Is the Best Choice?

Now that we've seen several different options for enhancing our HTML with JavaScript, which one makes the most sense for our use-case?

**jQuery**
- If we only need a few lines of code
- Existing jQuery plugins handle most of the complexity
- No state management in our 'components'

**Vue**/**React**/**Svelte**/**Angular**
- Complex state management
- Many components interacting together
- Client-side rendering is ok (SEO is not important)

**AlpineJs**
- More than a few lines of code, business logic
- SEO is important
- Custom UX (not from a jQuery plugin)

At [WiredViews](https://www.wiredviews.com/services/web-design-and-development), we've been using AlpineJs in our [Kentico Xperience](https://discover.wiredviews.com/dev-kentico-xperience-13-brochure-free-download/) projects, alongside Vuejs, and jQuery.

I recommend using the right tool 👩🏼‍🔧 for the right job, and fortunately AlpineJs fits in great with modern web development and Kentico Xperience.

As always, thanks for reading 🙏!

---

<figcaption>Photo by <a href="https://unsplash.com/@jordanmadrid">Jordan Madrid
</a> on <a href="https://unsplash.com">Unsplash</a></figcaption>

---

## References

- [AlpineJs on GitHub](https://github.com/alpinejs/alpine/)
- [VueJs Docs](https://vuejs.org/v2/guide/)
- [jQuery Docs](https://api.jquery.com/)
- [Kentico Xperience Docs](https://docs.xperience.io/)

---

We've put together a list over on [Kentico's GitHub account](https://github.com/Kentico/Home/blob/master/RESOURCES.md) of developer resources. Go check it out!

If you are looking for additional Kentico content, checkout the Kentico or Xperience tags here on DEV.

{% tag kentico %}

{% tag xperience %}

Or my [Kentico Xperience blog series](https://dev.to/seangwright/series), like:

- [Kentico Xperience Design Patterns](https://dev.to/seangwright/series/10963)
- [Kentico Xperience MVC Widget Experiments](https://dev.to/seangwright/series/9483)
- [Bits of Xperience](https://dev.to/seangwright/series/8740)