Using constructor factories

Constructor factories provide a way to create views using a series of nested function calls. Typescene provides a large number of built-in UI components, but custom view components are also supported.

What are constructor factories?

A constructor factory is a static method that creates a constructor, by taking a base class and a set of properties, and producing a constructor function that not only creates the base class but also assigns the given properties right away.

Here, we take the UILabel class, and produce another class (constructor) that works exactly like the original UILabel class but also sets the text property on all instances. We can then create UILabel objects with this same text over and over:

const MyLabel = UILabel.with({ text: "Hello, world!" });
const l1 = new MyLabel();
l1.text  // => "Hello, world!"
const l2 = new MyLabel();
l2.text  // => "Hello, world!"

We call the resulting constructors preset constructors because they contain ‘preset’ properties and nested content. Some components contain more specific constructor factories, such as UILabel itself–which contains a withText method that accepts the text property as a parameter.

Views

Typescene views are made up of these preset constructors, so that associated activities can create view instances as and when required from the constructor alone. The following qualifies as a Typescene view:

const MyView = UICell.with(
    { background: "yellow" },
    UICenterRow.with(
        UILabel.withText("Hello, world!")
    )
)

Note that by itself, this view is never updated with different values or layout. To update the view once it is created by the activity, we can use the following methods:

  • Bindings observe property values on the activity and update view properties immediately.
  • Event handlers can be specified as method names (e.g. "doSomething()"), propagated events (e.g. "+ItemClicked"), or as functions (although this is discouraged since all logic should be added to the activity, not the view).

Note: bindings in Typescene are always one-way bindings, i.e. they apply values from the activity to the view, but not the other way around. To handle user input, you can use event handlers or use the UIForm component and bind an object instead of a single value.

// view.ts
import { bind, UIForm, UIRow, UILabel, UITextField,
    UIOppositeRow, UIButton } from "typescene";

export default UIForm.with(
  { onSubmit: "formSubmitted()" },
  UIRow.with(UILabel.withText(bind("foo"))),
  UIRow.with(UITextField.with({ value: bind("foo") })),
  UIOppositeRow.with(
    UIButton.withLabel("OK", "+Submit")
  )
)

// activity.ts
import { PageViewActivity } from "typescene";
import view from "./view";

export class MyActivity
  extends PageViewActivity.with(view) {

  // ... properties and event handlers here
  foo = "Hello, world";
  formSubmitted() { /* ... */ }
}

Custom view components

Sometimes it’s useful to define a class that encapsulates a partial view, which can be reused in other places.

We can assign the result of constructor factory to a variable and reuse it later:

const MyHeading = UIRow.with(
  UIHeading1.withText("Hello")
);
const View = UICell.with(MyHeading);

This is mostly useful for breaking up larger views into smaller pieces, since the reused view will always be exactly the same.

To be able to reuse views with different properties and contents, we can instead create custom view components using the ViewComponent.template method. The resulting view components allow other views to pass property values or bindings using the .with method.

// CustomBlock.ts
export const CustomBlockView = ViewComponent.template(
  (properties: { foo: string }, ...content) =>
    UIRow.with(
      UILabel.withText(properties.foo)  // text or binding
      UIColumn.with(...content)
    )
);

Here, we use an arrow function (i.e. (...) => result) to describe our template. The view component expects a foo property, and any number of view components as its contents. We can now create a CustomBlockView constructor using the .with factory method like so:

const MyBlock = CustomBlockView.with(
  { foo: 5 },
  UILabel.withText("Hi")
);

// or:
const MyView = UICell.with(
  MyBlock,
  CustomBlockView.with(
    { foo: bind("someOtherFoo") },
    UILabel.withText("One"),
    UISeparator,
    UILabel.withText("Two")
  )
)

View component classes

View components created like this are very flexible because they accept properties and content, but they can’t contain their own logic.

To create a view component that does have its own methods, such as event handlers, we can directly extend the result of ViewComponent.template as our own class. Bindings and event handlers within the encapsulated view can be used within the view component class without affecting other views.

// CustomBlock.ts
export class CustomBlockView extends ViewComponent.template(
  (properties: { foo: number }, ...content) =>
    UIRow.with(
      UILabel.with({
        text: properties.foo,
        onClick: "handleMe()"
      }),
      UIColumn.with(...content)
    )
) {
  // properties (with defaults) and event handlers can go here:
  foo = 0;
  handleMe() { /* ... */ }
}

All UI Components by type

The following sections list all the UI components that are included in the typescene main module. Refer to the linked reference documentation for details about each component.

Containers

  • UICell, UICoverCell, UIFlowCell — cells contain other components, and can be decorated with a border, drop shadow, etc. Cover cells are positioned to cover their parent container; flow cells are not stretched in the primary dimension.
  • UIRow, UICloseRow, UICenterRow, UIOppositeRow — rows contain other components in a horizontal configuration. Close rows do not apply padding between components; center/opposite rows align components accordingly.
  • UIColumn, UICloseColumn — columns contain other components in a vertical configuration. Close columns do not apply padding between components.
  • UIScrollContainer — scroll containers allow their content to scroll in one or two directions, if the cell and its parents is restricted in those directions (e.g. within a cover cell).

Controls

Others

  • ViewComponent — base class for custom UI components. Acts as a composite parent for child view components.
  • UIConditional — controller (wrapper for a single component) that renders a its contents only if one of its properties is set to true.
  • UIForm — container that introduces a form context binding, which can be used by some controls.
  • UIFormContextController — controller (wrapper for a single component) that introduces a form context binding without its own output element, which can be used by some controls.
  • UIListController — controller for a managed list, which renders a list item adapter for every list item.
  • UIListCellAdapter — list item adapter (for use with list controller) that encapsulates a cell with arbitrary content.
  • UIMenu — menu controller; often used with a modal controller to show a modal menu.
  • UIModalController — modal controller for a single component that shows another component modally when an event occurs on the first one (e.g. Button showing a modal dropdown menu).
  • UISelectionController — controller for a hierarchy of components that deselects other components when one is selected (i.e. when a Select event occurs, often on a cell).
  • UIStyleController — controller (wrapper for a single component) that sets the style of a component based on the value of one of its properties, and a list of styles.
  • UIViewRenderer — component that renders another component in its place dynamically (e.g. a bound activity or view component).

Methods

The following methods are also helpful within preset UI component structures. Refer to the linked reference documentation for details.

  • tl — creates a ‘translated text label’, i.e. a label component with translated text, and properties taken from tags in its string parameter.
  • bind, bindf — bind properties on one component to values on its composite parent (not specific to UI, see Understanding components).