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:

Controlvuereactangular
ForbiddenForbiddenForbiddenForbiddenComponent
ErrorSummaryerror-summaryErrorSummaryerror-summary
Inputv-inputInputng-input
Selectv-selectSelect
CheckBoxv-checkboxCheckBoxng-checkbox
Buttonv-buttonButtonng-button
SvgImagev-svgSvgImage
Linkv-linkALinkng-link
LinkButtonlink-buttonLinkButtonlink-button
Navv-navNav
NavbarnavbarNavbarnavbar
NavLinknav-linkNavLinknav-link
NavButtonGroupnav-button-groupNavButtonGroupnav-button-group
NavLinkButtonnav-link-buttonNavLinkButtonnav-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());