alpinejs-app

A Simple Single Page Application (SPA) library for Alpine.js.

The Motivation

The registry is just 2 global Javascript objects, templates and components.

How the registry is delivered to the browser depends on bundling or application, for example:

Features

Installation

npm

npm install @vseryakov/alpinejs-app

cdn

<script src="https://unpkg.com/alpinejs-app@1.x.x/dist/app.min.js"></script>

<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>

Getting Started

Here’s a simple hello world example.

Live demo is available at demo.

index.html

<head>
<script src="bundle.js"></script>
</head>

<body>
    <div id="app-main"></div>
</body>

<template id="index">
    <h5>This is the index component</h5>

    <button x-render="'hello/hi?reason=World'">Say Hello</button>
</template>

<template id="hello">
    <h5>This is the <span x-text=$name></span> component</h5>
    Param: <span x-text="params.param1"></span>
    <br>
    Reason: <span x-text="params.reason"></span>
    <br>

    <div x-template.show="template"></div>

    <button @click="toggle">Toggle</button>
    <button x-render="'index'">Back</button>
</template>

index.js

import '../dist/app.js'
import './hello'
import './dropdown'
import "./dropdown.html"
import "./example.html"

app.debug = 1
app.start();

hello.js

app.components.hello = class extends app.AlpineComponent {
    template = ""

    toggle() {
        this.template = !this.template ? "example" : "";
    }
}

Explanation:

Nothing much, all the work is done by Alpinejs actually.

Directive: x-template

Render a template or component inside the container from the expression which must return a template name or nothing to clear the container.

This can be an alternative to the x-if Alpine directive especially with multiple top elements because x-if only supports one top element.

<div x-template="template"></div>

<div x-template="show ? 'index' : ''"></div>

Modifiers:

The directive supports special parameters to be set in this.params, similar to x-render, for example:

<div x-template="'docs?$nohistory=1'">Show</div>

The component docs will have this.params.$nohistory set to 1 on creation.

The other way to pass params to be sure the component will not use by accident wrong data is to use modifier .params.NAME

<div x-data="{ opts: { $nohistory: 1, docid: 123 } }">
    <div x-template.params.opts="'docs'">Show</div>
</div

The component docs will have opts as the this.params.

Directive: x-render

Binds to click events to display components. Can set components via a syntax supporting names, paths, or URLs with parameters through parsePath. Nothing happens in case the expression is empty.

Special options include:

<a x-render="'hello/hi?reason=World'">Say Hello</a>

<button x-render="'index?$target=#div'">Show</button>

Directive: x-scope-level

Reduce data scope depth for the given element, it basically cuts off data inheritance at the requested depth. Useful for sub-components not to interfere with parent’s properties. In most cases declaring local properties would work but limiting scope for children might as well be useful.

<div x-scope-level></div>

<div x-scope-level=1></div>

Magic: $app

The $app object is an alias to the global app object to be called directly in the Alpine.js directives.

<a @click="$app.render('page/1/2?$target=#section')">Render Magic</a>

<a @click="$app.render({ name: 'page', params: { $target: '#section' }})">Render Magic</a>

Magic: $component

The $component magic returns the immediate component. It may be convenient to directly call the component method or access property to avoid confusion with intermediate x-data scopes esecially if same property names are used.

<a @click="$component.method()">Component Method</a>

Magic: $parent

The $parent magic returns a parent component for the immediate component i.e. a parent for $component magic.

<a @click="$parent.method()">Parent Method</a>

Component Lifecycle and Event Handling

While Alpine.js has several ways how to reuse the data this app makes it more unified, this is opinionated of course.

Here is the life-cycle of a component:

In extending the example:

Add a new button to the index template:

<button x-render="'hello2'">Say Hello2</button>

Introduce another component:

hello.js

    app.templates.hello2 = "#hello"

    app.components.hello2 = class extends app.components.hello {
        onCreate() {
            this.params.reason = "Hello2 World"
            this._timer = setInterval(() => { this.params.param1 = Date() }, 1000);
        }

        onDelete() {
            clearInterval(this._timer)
        }

        onToggle(data) {
            console.log("received toggle event:", data)
        }

        toggle() {
            super.toggle();
            app.emit(app.event, "toggle", this.template)
        }
    }

Additions to the previous example:

The hello2 component takes advantage of the lifecycle methods to customize the behaviour:

For complete interaction, access live demo at the index.html.

Custom Elements

Component classes are registered as Custom Elements with app- prefix,

using the example above hello component can be placed inside HTML as <app-hello></app-hello>.

See also how the dropdown component is implemented.

Examples

The examples/ folder contains more components to play around and a bundle.sh script to show a simple way of bundling components together.

Simple bundled example: index.html

An example to show very simple way to bundle .html and .js files into a single file.

It comes with pre-created bundle, to rebuild:

Esbuild app plugin

The examples/build.js script is an esbuild plugin that bundles templates from .html files to be used by the app.

Running node build.js in the examples folder will generate the bundle.js which includes all .js and .html files used by the index.html

API

Global settings

Rendering

Router

DOM utilities

Event emitter

The app implements very simple event emitter to handle internal messages separate from the DOM events.

There are predefined system events:

Methods:

General utilities

Advanced

Author

Vlad Seryakov

License

Licensed under MIT