Component Overview

Table of Contents


Introduction

The View is a tree of Component objects. Components can implement higher-level functionality than standard DOM objects. A component can choose to create DOM nodes directly create child components, or both to implement its functionality.

Component instances are created by their parents. The component constructor is passed a pointer to its parent and a Template object that specifies the properties for that instance and the desired children. The instance sets its tag, props, and children properties from the Template object so that other component methods can easily reference them. It also records the Template object in its template property.

Mounting

Once a component is created, it is ready to mount. First, the instance’s render() method is called with a clone of the Template object used to create the instance. The Template argument makes the props and children values available as arguments like React, in addition to the similarly named properties in the Component. The render() method can return a Template object, a string, a number, a DOM node, or an array containing a mix of these types. The returned value is passed to the buildContents() method to transform it into an array containing only DOM nodes and/or Components with each component’s subtree similarly built. This array is assigned to the component’s contents property. An array containing only the components from the contents array is assigned to the component’s children property.

The component is not fully mounted until its DOM nodes are connected to the active DOM. This happens when an ancestor node (not necessarily the direct parent) wants to associate a DOM node with a subtree containing this instance. The ancestor render() method that is creating a DOM node calls the getNodes() method on the root components of the subtrees that should be the children of the node. The getNodes() method assembles a flattened array of DOM nodes for the subtree by recursively descending the components in the contents array and adding their DOM nodes in order. The ancestor then uses those nodes as children of the new DOM node.

During initialization, the View establishes a root View component then mounts the component subtree specified by the app’s startup code template and installs the nodes retrieved by the getNodes() method in the View’s root DOM node. After the subtree is fully mounted, the View calls the didMount() method on the View component, which recursively calls the same method on all components in the tree. The didMount() method signals that the component is fully connected to the DOM and that it’s safe to establish event listeners or search for DOM nodes in the subtree.

Unmounting

When a subtree is to be discarded, the Component will call the unmount() method on it. This will first call the component's willUnmount() method that tells it that it is about to be discarded and should remove any event listeners, or any other state outside of the component that uses its DOM nodes. After that, it removes any DOM nodes in its contents array from their parent node and then calls the unmount() method on any child components. This will recursively descend the component subtree unmounting each component. Lastly, it clears the contents array.

Remounting

A component may have to completely redo a sub-tree when the form of the tree is incorrect for the current circumstances. To do that, it calls the remount() method on itself, which does the following:

The render() instance method

The render() method allows great flexibility in implementing a component using DOM nodes and/or other sub-components. However, most components only require sub-components specified by a template. Here’s an example of a simple component that creates a button where the text can either be an attribute or the children:

view.defineComponentTag('Button', class extends Component {
	render () {
		const text = (
			this.props.text ? tml(this.props.text) : this.children
		);
		return(tml`<button type=button ...${this.props}>${text}</>`);
	}
});

In the example, tml(this.props.text) uses the tml function to process the text for embedded custom character entities.

contextProps

There is often a need for a component to pass information to its entire subtree. You could pass properties down through every sub-component, but that is both tedious and error-prone. Components each have a contextProps property that contains an object. The object’s properties contain information from ancestor components. When a component is created, the contextProps property is copied from its parent and any new properties from the object in any contextProps attribute are merged over it. The copy operation is shallow, so if a property in contextProps is an object, information can be passed up the component tree as well as down. It is also possible for ancestor components to pass a function to lower components in a subtree.

The are several default properties in contextProps:

CSS class names

Each component instance can have CSS class name(s) associated with it using the className attribute. This is fixed at the time the component is instantiated. It’s also possible to have a dynamically variable class name by using a classSv attribute with a View state variable instance as the value.

The default component implementation will set the first node returned by getNodes() to a combination of props.className and the value of the classSv and will adjust that value when the classSv value is modified. The component instance can modify this behavior in several ways. First it can override the currentClassName() method to return something other than the combination of props.className and the value of the classSv. Secondly, it can provide its own sync() method to synchronize the value when classSv changes. Lastly, it can override the default setClassName() method to set the className of different nodes or otherwise modify the class name string.

The sync() instance method and SvMonitors

After a user input, the Model runs, which usually changes many state variables. The sync() method lets components react to bulk changes in state variables in an ordered, efficient way. When a View or Model state variable is changed, it asks the View to schedule a synchronization by calling view.sync(). This recursively calls the sync() method for every component in the View moving from the root down. This lets components in the tree adapt to changes above it only once.

The sync() method interrogates whether a state variable has changed using an SvMonitor created in the constructor or render() methods. By convention, all SvMonitor objects are stored in the private _mon property object, to easily find them during debugging. Here’s an example of the default sync() method:

sync () {
	if (this._mon.classSv && this._mon.classSv.hasChanged()) {
		this.setClassName(this.currentClassName());
	}
	this.syncChildren();
}

The component ID

Every component is assigned an ID string. This takes the form '<prefix>_<uniqueNumber>'. By default, the prefix is the ID of the page that is the ancestor of the component. Otherwise, the prefix is view.

The data-cid attribute

The component ID is also automatically added in the cmp-data property in the props object by the Component constructor. The component should pass the cmp-data property in the props for the outmost rendered child component or node. The Html Component does this by default. This aids debugging because you can call view.getComponentById() in the console to retrieve the component instance in charge of a node. However, it is up to the component implementation.

Tag namespaces

Tag namespaces allow a subtree to use a different tag “language” without polluting or colliding with tag strings defined globally by view.defineComponentTag(). If a component has a tagNamespace property, all components in its subtree will first search the tagNamespace property’s Map object to find components given a tag string. If the component is not found there, the global tag namespace is used. If a descendant component also defines a tagNamespace, then the Maps are stacked and searched last-in first for that component's descendants.