Skip to content

View

View is the base class for every screen, component and sub-component in a JOPS app. A view owns a layout (HTML), the DOM elements created from it, its subviews, and the lifecycle hooks the framework calls.

import { View } from "jops-core";
export default class MyView extends View { /* … */ }

Learn more: Views, Layouts, Event binding.

new View(layout?: string, props?: Record<string, string>)

Parameters

  • layout (optional): passed to loadLayout(layout) when the view is created. A path to a .js layout module, a path to an .html file, or an inline HTML string. An inline string is assigned to this.layout immediately.
  • props (optional): the view’s input props, for a view created in code. Views declared in a layout get their props from the tag’s attributes.

Subclasses usually call super() with no argument, because the layout comes from the tag’s layout= attribute or from an overridden loadLayout(). Pass an inline layout when the view builds its markup from data:

src/QuoteRow.js
import { View, layout } from "jops-core";
export default class QuoteRow extends View {
constructor(quote) {
super(layout`<tr><td>${quote.number}</td><td>${quote.companyName}</td></tr>`);
this.quote = quote;
}
}
// in the parent view:
await this.addSubView("rows", new QuoteRow(quote));
readonly props: Readonly<Record<string, string>>

The view’s input properties. For a declared view, the attributes of its jops tag; for a view created in code, the props passed to the constructor. {} when there are none.

  • Attribute names are converted to camelCase (item-id → itemId); values are always strings.
  • The object is frozen (read-only).
  • jops, type="jops", src, layout, path and jops-event-bind belong to JOPS and are not props. id, class and on* attributes are props only: they are not copied to the view’s element, and an onclick prop is not bound.
  • Props are available right after super() in the constructor, in loadLayout() and in onLayout().
src/layout/main.html
<Card jops src="/src/Card.js" title="Monthly report" item-id="42"></Card>
src/Card.js
import { View, layout } from "jops-core";
export default class Card extends View {
constructor() {
super();
this.layout = layout`<article><h3>${this.props.title}</h3></article>`;
this.inflate();
}
onLayout() {
console.log(this.props.itemId); // "42"
}
}

A view created in code passes props through the constructor: new View(layout, { title: "Hi" }), or super(undefined, props) in a subclass. Learn more: Tag props.

layout: string

The view’s layout HTML. Set it directly in the constructor (usually with the layout tag), or let loadLayout() set it.

domNode: HTMLElement

The first top-level element of the view’s layout, created by inflate(). For the root view, its container (see The root view).

domNodes: HTMLElement[]

Every top-level element of the layout, in order. A layout may have more than one top-level element; domNode is the first of them. Top-level text is ignored.

subViews: View[]

The view’s direct subviews: views declared in its layout (in declaration order) and views added with addSubView() / insertSubView(). Use the methods below to change it; don’t edit the array yourself.

The framework calls these methods in order. Override the ones you need.

Method When it is called
loadLayout(layout?) Before inflate(): loads or assigns this.layout
inflate() Converts this.layout into this.domNode / this.domNodes
onLayout() The DOM is live: add subviews, read initial data
onResume() The view comes to the front: on first display, on every return navigation, and when the same route is shown again with new parameters
bindEvents() Wires on* attributes on [jops-event-bind] elements to methods

Optional hooks, called by the Router:

Method When it is called
onPause() A different view comes to the front and this one stays alive
onBackPressed() The user navigates back from this view

onPause() and onResume() are not passed on to child views. The root view gets the full lifecycle too, after its declared children.

loadLayout(layout?: string): Promise<void>

Loads or assigns the view’s layout. Called by the framework before inflate(); override it to choose the layout in code.

Parameters

  • layout (optional): one of
    • a .js path: imported as an ES module whose default export is a layout string;
    • an .html path: fetched and cached for the lifetime of the page (a plain HTML fragment);
    • any other string: inline HTML, assigned to this.layout.

Returns a promise that resolves when the layout is set.

  • Loads once per view. Calls made while a load is in progress (with or without a path) wait for it; once a layout is set, later calls return at once.
  • A failed load (for example a 404) leaves the view without a layout, so it can be retried.
  • When you override it and pass a path, return or await super.loadLayout(…): the framework waits for the returned promise before inflate().
class QuoteRow extends View {
loadLayout() {
return super.loadLayout("/quotes/layout/quote_row.html");
}
}

See Layout sources for details.

inflate(): void

Parses this.layout into DOM elements and assigns them to this.domNode and this.domNodes. Idempotent: calling it again does nothing. Call it yourself after assigning this.layout in a constructor, so the view is ready before it is added.

onLayout(): void | Promise<void>

Optional hook. Called once the view’s DOM is live and its declared child views are rendered. Add subviews, fill elements, start loading data here. May be async.

onResume(): void | Promise<void>

Optional hook. Called when the view comes to the front: on first display, on every return navigation, and when the same route is shown again with new parameters (#details?id=1 → #details?id=2). A Router route is resumed only when it is shown; at startup only the selected route gets onResume(). Read the Store and the route params here.

onPause(): void

Optional hook. Called when a different view comes to the front and this one stays alive: another route is shown, a stacked view is opened on top of it with navigate(), or replace() shows another view. It always runs before the incoming view’s onResume().

Not called when the view is destroyed instead: Back from a stacked view, a stack closed by a route link, or the app ending.

onBackPressed(): void

Optional hook. Called when the user navigates back from this view: browser Back, Router.back(), or the Android back button in hybrid apps. Never called on forward navigation. For a route it runs before onPause(); for a stacked view it is the last call before the view is destroyed, so put closing logic here.

bindEvents(): void

Wires the on* attributes of elements marked jops-event-bind to the view’s methods with the same name. Called by the framework after onResume(); override it to add your own wiring.

<button jops-event-bind onclick="onSave">Save</button>
onSave(event) { /* … */ }
get(id: string): Element

Returns an Element wrapper for the first element with the given id, searching every top-level element of the layout.

this.get("username").set("Alice");

When no element has the id, the wrapper’s methods do nothing and log a warning (see Element).

find(id: string): Element[]
find(condition: (view: View) => boolean): View[]

Two overloads:

  • find(id): every element with the given id across all top-level elements of the layout and the DOM of its subviews, in page order, as Element wrappers.
  • find(condition): a depth-first search of the whole subview subtree; returns a flat array of every view for which condition returns true.
this.find("quote_id").forEach((el) => el.attr("tabindex", 0));
const forms = root.find((v) => v instanceof FormView);
addSubView(containerId: string, view: View): Promise<void>

Appends view into the element with id equal to containerId and runs its lifecycle: the views declared in its layout are rendered first, then onLayout() → onResume() → bindEvents().

The container is looked up like get(): in every top-level element of this view’s layout, in order (the first match wins), and it may be a top-level element itself. Before 1.1.7 only the first top-level element was searched.

Parameters

  • containerId: the id of the container element.
  • view: the view to add.

Returns a promise that resolves when the view is added.

  • If view was destroyed (removed earlier), it logs a warning and adds nothing: create a new instance.
  • It also warns and adds nothing when no element has the container id, or when the view’s layout failed to load.
  • await each call when the order of the added views in the DOM matters.
async onLayout() {
for (const item of items) {
await this.addSubView("list", new ItemRow(item));
}
}
insertSubView(view: View, index: number): Promise<void>

Inserts view at position index (zero-based) among the children of this.domNode (the first top-level element) and runs the same lifecycle as addSubView(). A destroyed view is refused with a warning.

removeSubView(view: View): void

Removes view from this.subViews and destroys it (see destroy()): its elements leave the DOM, its own subviews and its owned EventBus subscriptions are removed.

removeSubViews(condition?: (view: View) => boolean): void

Removes direct subviews, each through removeSubView(). Without condition, removes all of them; with it, only those it matches (the others keep their place and order). Not available on a Router (it logs a warning and does nothing).

this.removeSubViews((v) => v instanceof QuoteRow);
render(): Promise<void>

Renders the view: resolves the views declared in its layout (jops tags and <Router>), and runs each one’s lifecycle. Call it once on the root view, usually in App.js; nested views are handled automatically. Idempotent.

src/App.js
import { View } from "jops-core";
export default class App {
constructor() {
const root = new View();
root.loadLayout("/src/layout/main.html").then(() => root.render());
}
}
show(): void

Shows the view again by removing the inline display override from every top-level element.

hide(): void

Hides the view (display: none on every top-level element).

setStyle(style: string): void

Adds a stylesheet to <head>: a <link> when style is a path to a .css file, otherwise an inline <style> with the CSS text. The same stylesheet is never added twice.

Hosted mode No effect in hosted mode (Next.js, WordPress, embedded pages): the host page owns the CSS.

destroy(): void

Destroys the view and its subtree: calls destroy() on every subview, removes the view’s elements from the DOM, removes its owned EventBus subscriptions, and clears its references. Usually called for you by removeSubView(), the Router and unmount().

When you override it (for example to unsubscribe a global EventBus listener), call super.destroy().

A layout may have one or more top-level elements:

<h2 id="title">Quotes</h2>
<table><tbody id="rows"></tbody></table>

domNode is the first and domNodes holds all of them. get(), find(id), bindEvents(), show(), hide(), addSubView(), insertSubView() and destroy() act on all of them, in order. The Router’s slide and fade animations need single-root views, except in screens mode.

The root view is the view whose render() is called first. It uses its container as its domNode: <body>, the element named by data-root, or the element passed to mount(). Destroying the root view empties the container instead of removing it.