Single Page App Components

SPA Component Libraries

To lay the foundation for richer and more tightly integrated UI controls, we've created UI and common component libraries for the 3 most popular JS frameworks:

@servicestack/vue

@servicestack/react

@servicestack/angular

All new Single Page App Project Templates have been pre-configured to use these libraries which will make it a lot easier to deliver new UI components and updates to existing SPA Apps with just an npm upgrade.

UI Component List

Currently the component libraries include common Bootstrap UI Form Controls, Navigation Components and a generic Forbidden page to handle when users don't have access to a protected route.

Side-by-side comparison displaying the names for the different Component Type in each JS Framework:

Control vue react angular
Forbidden Forbidden Forbidden ForbiddenComponent
ErrorSummary error-summary ErrorSummary error-summary
Input v-input Input ng-input
Select v-select Select
CheckBox v-checkbox CheckBox ng-checkbox
Button v-button Button ng-button
SvgImage v-svg SvgImage
Link v-link ALink ng-link
LinkButton link-button LinkButton link-button
Nav v-nav Nav
Navbar navbar Navbar navbar
NavLink nav-link NavLink nav-link
NavButtonGroup nav-button-group NavButtonGroup nav-button-group
NavLinkButton nav-link-button NavLinkButton nav-link-button

Bootstrap UI Form Controls

The Bootstrap UI form controls include built-in support for validation where they can render validation errors from ServiceStack's ResponseStatus object, e.g the SignIn.vue page used in all Vue project templates:

<form @submit.prevent="submit" :class="{ error:responseStatus, loading }" >
    <div class="form-group">
        <error-summary except="userName,password" :responseStatus="responseStatus" />
    </div>
    <div class="form-group">
        <v-input id="userName" v-model="userName" placeholder="Username" :responseStatus="responseStatus" 
                 label="Email" help="Email you signed up with" />
    </div>
    <div class="form-group">
        <v-input type="password"  id="password" v-model="password" placeholder="Password" 
                :responseStatus="responseStatus" label="Password" help="6 characters or more" />
    </div>
    <div class="form-group">
        <v-checkbox id="rememberMe" v-model="rememberMe" :responseStatus="responseStatus">
            Remember Me
        </v-checkbox>
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-lg btn-primary">Login</button>
        <link-button href="/signup" lg outline-secondary class="ml-2">Register New User</link-button>
    </div>
</form>

Initially renders the following UI:

All form validation is typically performed the same way, by sending a populated ServiceStack Request DTO and capturing any Service Client exceptions in the components responseStatus property, e.g:

protected async submit() {
    try {
        this.loading = true;
        this.responseStatus = null;
        const response = await client.post(new Authenticate({
            provider: 'credentials',
            userName: this.userName,
            password: this.password,
            rememberMe: this.rememberMe,
        }));
        bus.$emit('signin', response);
        redirect(this.$route.query.redirect as string || Routes.Home);
    } catch (e) {
        this.responseStatus = e.responseStatus || e;
    } finally {
        this.loading = false;
    }
}

Where it automatically applies the field validation error next to their respective control:

Conversely you can unset the responseStatus to reset all form validation errors:

this.responseStatus = null;

All navigation components are populated the same way for all JavaScript FX's where it embeds the navigation data structure in the page by serializing the response of the GetNavItems Service to JSON that's embedded in the layout page where it's only loaded once upon the initial page request (immediately, without an Ajax network request):

{‎{#script}‎}
NAV_ITEMS = {‎{ 'GetNavItems'  |> execService |> json }‎};
AUTH      = {‎{ 'Authenticate' |> execService({ ifErrorReturn: "null" }) |> json }‎};
{‎{/script}‎}

The navigation items data structure is used with new Navigation Components for each JavaScript FX to render the menu navigation which is initially captured in a state object containing the NavItems data structure, the Users Session and the list of User Attributes generated from the Authenticated Users Session (if any), e.g:

Vue

In Vue the Nav and User Information is maintained in a global store object which uses UserAttributes.fromSession() from the @servicestack/client library to generate the list of User Attributes:

export const store: State = {
  nav: global.NAV_ITEMS as GetNavItemsResponse,
  userSession: global.AUTH as AuthenticateResponse,
  userAttributes: UserAttributes.fromSession(global.AUTH),
};

The built-in list of User Attributes include:

  • auth - Authenticated User
  • role:TheRole - Authenticated User with TheRole role.
  • perm:ThePermission - Authenticated User with ThePermission permission.

This list can be further extended to include your own custom User Attributes, these are used to control whether to display the navigation item based on if the attribute is an exact match for the Show and Hide properties of the NavItem. E.g. Navigation Items populated with:

  "NavItems": [
    { "href":"/login",      "label":"Sign In", "hide":"auth" },
    { "href":"/profile",    "label":"Profile", "show":"auth" },
    { "href":"/admin",      "label":"Admin",   "show":"role:Admin" }
  ],

Will hide the Sign In and show the Profile nav items to Authenticated Users and only show the Admin nav item to Admin Users.

The navbar component uses these data structures to render the main menu:

<navbar :items="store.nav.results" :attributes="store.userAttributes" />

The rendering of the component can be further customized using any of the NavOptions properties, in camelCase.

Which also applies to the list of registered OAuth provider buttons rendered with <nav-button-group>:

<nav-button-group :items="store.nav.navItemsMap.auth" :attributes="store.userAttributes" 
                  :baseHref="store.nav.baseUrl" block lg />

In addition to NavOptions properties, new Bootstrap UI Controls (in each JavaScript FX) can also use these common bootstrap attributes to stylize their components:

export declare class BootstrapBase extends Vue {
    primary?: boolean;
    outlinePrimary?: boolean;
    secondary?: boolean;
    outlineSecondary?: boolean;
    success?: boolean;
    outlineSuccess?: boolean;
    info?: boolean;
    outlineInfo?: boolean;
    warning?: boolean;
    outlineWarning?: boolean;
    danger?: boolean;
    outlineDanger?: boolean;
    light?: boolean;
    outlineLight?: boolean;
    dark?: boolean;
    outlineDark?: boolean;
    lg?: boolean;
    md?: boolean;
    sm?: boolean;
    xs?: boolean;
    block?: boolean;
    vertical?: boolean;
    horizontal?: boolean;
}

camelCase properties like outlinePrimary are exposed as kebab-case in components, e.g. outline-primary

React

These same components are available in React from the new @servicestack/react library, except the JSX Components use PascalCase, e.g:

<Navbar items={state.nav.results} attributes={state.userAttributes} />

Likewise for NavButtonGroup:

<NavButtonGroup items={state.nav.navItemsMap.auth} attributes={state.userAttributes} 
                baseHref={state.nav.baseUrl} block lg />

Angular

Likewise for Angular from the new @servicestack/angular package where the main menu is rendered using the navbar component:

<navbar [items]="nav.results" [attributes]="userAttributes"></navbar>

And the OAuth Button list is rendered using the nav-button-group component in kebab-case:

 <nav-button-group [items]="nav.navItemsMap.auth" [attributes]="userAttributes" 
                   [baseHref]="nav.baseUrl" block lg></nav-button-group>

Mobile and Desktop Apps

Whilst there are no native components developed for different Mobile and Desktop UI's, the same navigation information can be accessed by calling the GetNavItems Service, e.g:

var response = await client.GetAsync(new GetNavItems());