RizzyUI

Co-located JS Additional Examples

RizzyUI provides a first-class system for writing page-specific, co-located JavaScript using Alpine.js. This allows you to add rich, client-side interactivity to your server-rendered components in a structured, maintainable, and CSP-compliant way.

Basic Example

This example shows a simple Alpine module that updates a text element. The [RzAlpineCodeBehind] attribute enables the feature, and <RzAlpineComponent> loads the script and passes data from C# to the JS module via the Props parameter.

CoLocatedJSInfo.razor
<!-- Add this attribute to your component -->
@attribute [RzAlpineCodeBehind]

<RzAlpineComponent For="this" Name="coLocatedJsPage" Props="@(new { initialMessage = "Hello from C#!" })">
    <p>Message from Alpine: <strong x-ref="messageOutput"></strong></p>
    <RzButton x-on:click="updateMessage">Update Message from JS</RzButton>
</RzAlpineComponent>
CoLocatedJSInfo.razor.js
// The exported function is a factory for your Alpine.js data object.
export default () => ({
    message: '',

    // Alpine's `init()` is called after the component is ready.
    init() {
        const props = Rizzy.props(this.$el);
        this.message = props.initialMessage || 'Default message';
        this.$refs.messageOutput.innerText = this.message;
    },

    updateMessage() {
        this.message = 'The message was updated by our co-located Alpine module!';
        this.$refs.messageOutput.innerText = this.message;
    }
});

Multiple Instances with a Sub-Component

This pattern shines when reusing components. Each <Demo._UserCard> component instance gets its own independent Alpine.js scope, initialized with its own unique data from the C# loop. The _UserCard.razor.js module is loaded and registered only once.

ParentPage.razor (Loop)
<div class="flex flex-wrap gap-4">
@foreach (var user in _users)
{
    <UserCard User="user" />
}
</div>

@code {
private List<UserData> _users = new()
{
    new() { Id = 1, Name = "Alice", Email = "alice@example.com" },
    new() { Id = 2, Name = "Bob", Email = "bob@example.com" },
    new() { Id = 3, Name = "Charlie", Email = "charlie@example.com" }
};
}
UserCard.razor
@attribute [RzAlpineCodeBehind]

<RzAlpineComponent For="this" Name="userCard" Props="@User" LoadStrategy="visible" AsChild>
    <div class="border p-4 rounded-lg shadow-sm w-64">
        <h3 class="font-bold" x-text="user.name"></h3>
        <p class="text-sm text-muted-foreground" x-text="user.email"></p>
        <RzButton class="mt-2" x-on:click="showAlert">Show Alert</RzButton>
    </div>
</RzAlpineComponent>

@code {
[Parameter, EditorRequired]
public UserData User { get; set; } = default!;
}
UserCard.razor.js
export default () => ({
    user: {},

    init() {
        this.user = Rizzy.props(this.$el);
        console.log('UserCard initialized for:', this.user.name);
    },

    showAlert() {
        alert(`You clicked on ${this.user.name}'s card!`);
    }
});

Orchestrating RizzyUI Components

Your co-located Alpine module can act as a controller for other RizzyUI components. By giving a RizzyUI component an x-ref, you can get a handle to its DOM element and then use Rizzy.$data() to access its internal Alpine instance and API.

CoLocatedJSInfo.razor (Orchestration)
<RzAlpineComponent For="this" Name="coLocatedJsPage">
    <RzButton x-on:click="openConfirmationModal">Show Confirmation</RzButton>

    <RzDialog Title="Confirm Action" x-ref="confirmModal">
        <!-- Modal Content -->
    </RzDialog>
</RzAlpineComponent>
CoLocatedJSInfo.razor.js (Orchestration)
export default () => ({
    confirmModal: null,

    async init() {
        // Allow a tick for modal to transport to body (needed by RzDialog)
        await Alpine.nextTick();
        
        // Get the Alpine instance for the RzDialog component
        this.confirmModal = Rizzy.$data(this.$refs.confirmModal);
    },

    openConfirmationModal() {
        if (this.confirmModal) {
            this.confirmModal.openModal();
        }
    }
});