@brandup/ui-app
v2.0.16
Published
Basic infrastructure framework for web applications.
Readme
@brandup/ui-app
Lightweight, fast and extensible application framework. Full async support. Weight after minification is less than 10KB!
Installation
Install NPM package @brandup/ui-app.
npm i @brandup/ui-appThis package support TypeScript language.
Configure and run application
Configure your application with middlewares and run.
import { ApplicationBuilder } from "@brandup/ui-app";
import pages from "./middlewares/pages";
import "./styles.less";
// Customize application model
interface ExampleApplicationModel extends ApplicationModel {
}
// Customize application type
export class ExampleApplication extends Application<ExampleApplicationModel> {
}
const appModel: ExampleApplicationModel = {};
const builder = new ApplicationBuilder<ExampleApplicationModel>(appModel);
builder
.useApp(ExampleApplication)
.useMiddleware(pages);
const app = builder.build({ basePath: "/" });
app.run({ /*optional context params*/ })
.then(context: StartContext => { })
.catch(reason => { });Default HTMLElement of application is document.body. Set custom element:
const appElement = document.getElementById("app")
app.run({ /*optional context params*/ }, appElement);Navigation
Example links for application navigation app.nav({ url: "url" }):
<a href="url" class="applink">text</a>
<button data-nav-url="url">text</button>Replace current url app.nav({ url: "url", replace: true, scope: "replace scope" }):
<a href="url" class="applink" data-nav-replace>text</a>
<a href="url" class="applink" data-nav-replace data-nav-scope="replace scope">text</a>
<button data-nav-url="url" data-nav-replace>text</button>The element's click event will start a chain of navigate method calls for all middleware.
During navigation and until it is completed, the loading class is added to the element that started the navigation.
The
HTMLElementhelpersel.navUrl(url),el.nav(app, path?, query?, hash?),el.navReplace()andel.navScope(scope)build these links programmatically. They are opt-in and enabled automatically byApplication.run()— callenableNavExtensions()yourself only to use them before the application starts.
The element that started a navigation is available to middlewares as context.clickElem (set by the built-in hyperlink middleware).
Browser history
The built-in history middleware keeps the address bar in sync with navigations — pushing or replacing a history entry (and scrolling to top) once a navigation succeeds. It is opt-in; register it before any page-rendering middleware so the URL changes only after the new page is in place:
import { HistoryMiddleware } from "@brandup/ui-app";
builder.useMiddleware(HistoryMiddleware);Enable per-entry scroll-position memory (saved while scrolling, restored on back/forward) with the scrollRestoration option:
builder.useMiddleware(HistoryMiddleware, { scrollRestoration: true });It is dependency-free (History API only) and does not manage document.title, which stays a page concern.
Current page
The application exposes the current page via app.page. A page-rendering middleware assigns it with app.setPage(page); the application only holds the reference — it never renders or destroys the page itself — and clears it on destroy().
import { Application, Page } from "@brandup/ui-app";
// Recover the concrete page type in a subclass:
class ExampleApplication extends Application {
override get page(): MyPage | null { return super.page as MyPage | null; }
}
// From anywhere with access to the app:
const current = app.page;Page is a minimal contract ({ destroy?(): void | Promise<void> }); the concrete page — rendering and lifecycle — lives in the page layer and implements it.
Submit form
<form class="appform">
<input type="text" name="value" />
</form>The form's submit event will start a chain of submit method calls for all middleware. Class appform is required.
If the form method is GET, then navigation with the form data will start.
Middlewares
Inject to application lifecycle event methods. Middleware methods are called one after another in the order in which they were registered in the ApplicationBuilder.
class PagesMiddlewareImpl implements Middleware {
name = "pages"; // unique name of this middleware
start(context: StartContext<ExampleApplication>, next: MiddlewareNext) {
console.log("start");
// context.app - access to application members
// context.data - get or set context data
return next();
}
async loaded(context: StartContext<ExampleApplication>, next: MiddlewareNext) {
console.log("loaded");
return next();
}
async navigate(context: NavigateContext<ExampleApplication, PageNavigationData>, next: MiddlewareNext) {
if (context.replace)
location.replace(context.url);
else
location.assign(context.url);
return next();
}
async submit(context: SubmitContext<ExampleApplication>, next: MiddlewareNext) {
console.log("submit");
return next();
}
async stop(context: StopContext<ExampleApplication>, next: MiddlewareNext) {
console.log("stop");
return next();
}
}
/** External interface of middleware. */
export interface PagesMiddleware {
}
export default () => new PagesMiddlewareImpl();Example SPA navigation middleware: npm/brandup-ui-example/src/frontend/middlewares/pages.ts
Besides start, loaded, navigate, submit and stop, a middleware may implement visibility (see below).
Page visibility
The optional visibility method runs when the browser page visibility changes — tab switch, minimize, or bfcache enter/restore. It is deduplicated to a single call per real transition.
class ExampleMiddleware implements Middleware {
name = "visibility-demo";
async visibility(context: VisibilityContext, next: MiddlewareNext) {
if (context.visible) { /* resumed — restart timers/polling */ }
else { /* hidden — pause work, persist state */ }
await next();
}
}Access to middleware
Retrivie middleware by unique name:
const pages = app.middleware<PagesMiddleware>("pages");Async execution
export class PagesMiddleware implements Middleware {
async navigate(context: NavigateContext, next: MiddlewareNext) {
// Exec before next middleware
await next();
// Exec after next middleware
}
}Redirect navigation
export class AuthMiddleware implements Middleware {
async navigate(context: NavigateContext, next: MiddlewareNext) {
await context.redirect({ url: "url" });
}
async submit(context: SubmitContext, next: MiddlewareNext) {
await context.redirect({ url: "url" });
}
}The redirect method will always throw an exception with reason NAV_OVERIDE_ERROR constant and end the current navigation.
