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.
Constructor
Section titled “Constructor”new View(layout?: string, props?: Record<string, string>)Parameters
layout(optional): passed toloadLayout(layout)when the view is created. A path to a.jslayout module, a path to an.htmlfile, or an inline HTML string. An inline string is assigned tothis.layoutimmediately.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:
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));Properties
Section titled “Properties”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,pathandjops-event-bindbelong to JOPS and are not props.id,classandon*attributes are props only: they are not copied to the view’s element, and anonclickprop is not bound.- Props are available right after
super()in the constructor, inloadLayout()and inonLayout().
<Card jops src="/src/Card.js" title="Monthly report" item-id="42"></Card>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
Section titled “layout”layout: stringThe view’s layout HTML. Set it directly in the constructor (usually with the layout tag), or let loadLayout() set it.
domNode
Section titled “domNode”domNode: HTMLElementThe first top-level element of the view’s layout, created by inflate(). For the root view, its container (see The root view).
domNodes
Section titled “domNodes”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
Section titled “subViews”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.
Lifecycle
Section titled “Lifecycle”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()
Section titled “loadLayout()”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
.jspath: imported as an ES module whose default export is a layout string; - an
.htmlpath: fetched and cached for the lifetime of the page (a plain HTML fragment); - any other string: inline HTML, assigned to
this.layout.
- a
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,
returnorawaitsuper.loadLayout(…): the framework waits for the returned promise beforeinflate().
class QuoteRow extends View { loadLayout() { return super.loadLayout("/quotes/layout/quote_row.html"); }}See Layout sources for details.
inflate()
Section titled “inflate()”inflate(): voidParses 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()
Section titled “onLayout()”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()
Section titled “onResume()”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()
Section titled “onPause()”onPause(): voidOptional 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()
Section titled “onBackPressed()”onBackPressed(): voidOptional 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()
Section titled “bindEvents()”bindEvents(): voidWires 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) { /* … */ }Instance methods
Section titled “Instance methods”get(id: string): ElementReturns 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()
Section titled “find()”find(id: string): Element[]find(condition: (view: View) => boolean): View[]Two overloads:
find(id): every element with the givenidacross all top-level elements of the layout and the DOM of its subviews, in page order, asElementwrappers.find(condition): a depth-first search of the whole subview subtree; returns a flat array of every view for whichconditionreturnstrue.
this.find("quote_id").forEach((el) => el.attr("tabindex", 0));
const forms = root.find((v) => v instanceof FormView);addSubView()
Section titled “addSubView()”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: theidof the container element.view: the view to add.
Returns a promise that resolves when the view is added.
- If
viewwas 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.
awaiteach 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()
Section titled “insertSubView()”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()
Section titled “removeSubView()”removeSubView(view: View): voidRemoves 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()
Section titled “removeSubViews()”removeSubViews(condition?: (view: View) => boolean): voidRemoves 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()
Section titled “render()”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.
import { View } from "jops-core";
export default class App { constructor() { const root = new View(); root.loadLayout("/src/layout/main.html").then(() => root.render()); }}show()
Section titled “show()”show(): voidShows the view again by removing the inline display override from every top-level element.
hide()
Section titled “hide()”hide(): voidHides the view (display: none on every top-level element).
setStyle()
Section titled “setStyle()”setStyle(style: string): voidAdds 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()
Section titled “destroy()”destroy(): voidDestroys 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().
Layouts with several top-level elements
Section titled “Layouts with several top-level elements”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
Section titled “The root view”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.