class Component

Component is the key abstraction offered by the library: it abstracts a user interface component, separating the details of the DOM from the application. Subclasses derived from Component provide an API that is independent of the input-output subsystem (the DOM). Component provides machinery to aid in constructing this separation of concerns including child management, variable watch, and event machinery as well as machinery that is common among user interface components such as focus management, visibility, mutability, and state-implied visual updates. Resource conservation and performance tuning is facilitated by separating the instance lifecycle, the render lifecycle, and the active lifecycle. Component is intended to be used in both traditional composition and derivation patterns.

Typically, Component instances are instantiate by:

  • The JavaScript new operator.
  • Rendering a Component which defines Element trees which contain other Components.
  • The function render().

Component instances are rendered by:

  • Applying the render() method on an instance.
  • Rendering a Component which defines Element trees which contain other Components.
  • The function render(), when a node argument is provided.

Component instances are unrendered rendered by:

  • Applying the unrender() method on an instance.
  • Unrendering a Component which contains child Component instances.

Components are destroyed by:

  • Applying the destroy() method on an instance.
  • Deleting children from a parent component, when the destroy argument is true.

In a typical application, the function render() is called to create a single top-level component, and then children are inserted/deleted into that component. Likewise, various descendants of the top-level component control their content by inserting/deleting children.

In performance critical/sensitive applications, performance can be tuned and dramatically improved by judiciously caching instantiated components. Depending upon the particular problem being solved, the cached components may be maintained in either a rendered or unrendered state.

Superclasses

EventHub

WatchHub

Constructor

constructor(kwargs)

kwargs [Object]

Gives a hash of values from which to initialize instance data. The properties below are reserved by Component, all other properties may be used by subclasses according to their own semantics. A shallow copy of all properties not reserved by Component is saved at the instance property kwargs.

  • id [optional, any]: initializes public property id.
  • staticClassName [optional, string]: initializes private property Component.ppStaticClassName; See discussion at className.
  • className [optional, string]: initializes public property className; See discussion at className
  • tabIndex [optional, integer]: initializes public property tabIndex.
  • title [optional, string]: initializes public property title.
  • enabled [optional, boolean]: initializes public property enabled.
  • elements [optional, Element | Array of Element | function():[Element | Array of Element]]: initializes the protected method _elements. If provided, causes a per-instance override of _elements().
  • postRender [optional, function():[void | handle | array of handle]]: initializes public method postRender(). If provided, causes a per-instance override of postRender().

Public Properties

kwargs [read-write, Object]

Initialized at construction to contain a shallow copy of the first argument provided to the constructor, exclusive of the properties id, staticClassName, className, tabIndex, title, enabled, elements, and postRender. this.kwargs is not used by Component and subclasses are free manipulate the property in any way they see fit (including replacing and deleting) in their constructors.

id [read-only, any]

Defined if and only if kwargs.id is provided at construction. If so provided, and the rendered DOM tree associated with the component defines a single root node, then that node's id property is set to the value if this.id.

className [watchable, read-write, string]

Component provides a means to define a base className that should be constant for all instances of a particular Component subclass as well as a means to define and mutate a variable className on a per-instance basis, the latter often dependent upon some instance-dependent state. These two types of classNames are maintained in the private properties [Component.ppStaticClassName] and [Component.ppClassName] respectively, which are concatenated to set the className property on the root DOM node(s) associated with the component during rendering. Thereafter, any changes to [Component.ppClassName] are reflected in the the className property on the root DOM node(s). Client code should never mutate [Component.ppClassName] directly, but rather should use the public interfaces as follows:

  • kwargs.className at construction
  • the DOM className property (if any) of the root node when the component is rendered. Note carefully, that the existence of a non-empty DOM node className property does not overwrite any existing value at [Component.ppClassName], but rather the value of className on the node immediately following rendering is concatenated to the current value of [Component.ppClassName]
  • directly assigning to this.className; the setter will mutate the private property.
  • applying any of the methods addClassName, removeClassName, or toggleClassName

Similarly, client code should never mutate [Component.ppStaticClassName] directly, but rather should use the public interfaces as follows:

  • kwargs.staticClassName at construction. Note that this value (if any) will be concatenated with the class static property className (if any).
  • defining the class static property className. Note that this value (if any) will be concatenated with any value provided by kwargs.staticClassName.

Notice that neither of these methods allows for modifying [Component.ppStaticClassName] after instance construction; this is the intent.

Here is some sample code demonstrating the capabilities and intention of the design.

class Person extends Component {
	// person interface goes here
}
// set the static className by defining a static property...
Person.staticClassName = "person";

let p = render(Person);
// the root dom node of p will have className==="person"

let vip = render(Person, {className:"important"});
// the root dom node of p will have className==="person important"

vip.addClassname("loser");
// the root dom node of vip will have className==="person important loser"

vip.className("big loooooser");
// the root dom node of vip will have className==="big loooooser"
// notice everything other than the static className was is affected

// set the static className by providing a constructor argument..
let s = render(Person, {staticClassName: "student"});
// the root dom node of s will have className==="person student"

class Manager extends Person {
	_elements(){
		e("div", {className: "manager"})
	}
}

let m = render(Manager, {className:"accounting"});
// the root dom node of m will have className==="person manager accounting"

m.className("bad");
// the root dom node of m will have className==="person bad"
// we stepped on accounting...probably OK, likely an instance state
// but also stepped on manager...probably not an instance state
// manager should probably be a static className

// className can be mutated before rendering with effect
p = new Person();
p.addClass("new");
p.addClass("training");
p.render();
// the root dom node of p will have className==="person new training"

rendered [watchable, read-only, boolean]

Returns true if the instance is rendered, false otherwise.

attachToDoc [watchable, read-only, boolean]

Returns true if the instance is rendered and its DOM tree is attached to the document, false otherwise.

parent [watchable, read-only, Component]

Returns the Component instance which contains this instance in its children collection (if any), undefined otherwise.

visible [watchable, read-write, boolean]

Returns !this.containsClassName("bd-hidden").

Setting true causes "bd-hidden" to be removed from the className, and conversely.

enabled [watchable, read-write, boolean]

Returns the value of the private instance data this[Component.ppEnabled].

Setting mutates the private instance data this[Component.ppEnabled]. Subclasses should override to effect meaningful semantics.

tabIndex [watchable, read-write, integer]

Returns the value of the private instance data this[Component.ppTabIndex].

Setting mutates the private instance data this[Component.ppTabIndex].

this[Component.ppTabIndex] is reflected to the tabIndex property of the root DOM node when the component is rendered.

title [watchable, read-write, string]

Returns the value of the private instance data this[Component.ppTitle].

Setting mutates the private instance data this[Component.ppTitle].

this[Component.ppTitle] is reflected to the title property of the root DOM node when the component is rendered.

focused [watchable, read-only, boolean]

Returns true if the instance contains a DOM node that currently owns the focus, or contains a child such that child.focused is true; false otherwise.

Public Methods

render(callback):[void]

callback [optional, function()]

If the instance is rendered, then no-op; otherwise:

  1. Creates the DOM tree(s) associated with the instance as given by the property _elements.
  2. Stores the new tree(s) at this._dom.root.
  3. Inserts the association from the new tree(s) to the instance in Component.catalog.
  4. Applies callback (if provided).

destroy():[void]

Unrenders the instance and destroys all connections to event handlers and property watchers.

unrender():[void]

If the instance is unrendered, then no-op; otherwise:

  1. Removes all references to this instance from Component.catalog
  2. Applies delChild() this instance on the parent (if any).
  3. Applies destroy() to every child in its children collection.
  4. Destroys all connections to event handlers and property watchers that were connected during rendering and/or via ownWhileRendered()

own(handle...):[void]

handle [Handle | array of Handle]

Ensures that all handles passed during the lifetime of the Component instance are destroyed when the component's destroy() method is applied.

Handle is an instance of some class that provides the method destroy(); typically, it represents some kind of a connection handle (e.g., to an event or watcher) and destroy() causes the connection to be terminated. connect(), EventHub.advise(), and WatchHub.watch() return such handles.

ownWhileRendered(handle...):[void]

handle [Handle | array of Handle]

Similar to own(), except that all handles passed are destroyed when the component is unrendered.

insChild(child, node, position):[void]

child [Component | component-derived-constructor [, kwargs] | Element]

ref [optional, existing-child | instance-reference-to-DOM-node]

position [optional, "first", "last", "before", "after", number]

Creates (if required), renders (if required), and finally adds a child to the children collection and inserts the dom tree associated with that child at a position with respect to ref; a component instance must be rendered prior to applying insChild.

The child is created as follows:

  1. when child is an instance-of-component: nothing created, instance-of-component is the child.
  2. when child is an Element that describes a Component type: child is created and rendered as if the Element was contained in the Element tree given by the protected method _elements().
  3. when child is an Element that describes a DOM node type: same as [2], with e(Component, {elements: child}).
  4. when child is a component-derived-constructor [, kwargs] same as [2], with e(component-derived-constructor [, kwargs] ).

In all cases, the child.render() is applied

The insertion procedure is always executed with reference to a particular DOM node:

  • If ref is a instance-reference-to-DOM-node, then ref is the reference node.
  • If ref is a child component, then child._dom.root is the reference node. In this case, "before", "after", "only", and "replace" are the only legal values for position.
  • If ref is not provided, then this._dom.root is the reference node.

The child's DOM tree is inserted with respect to this reference node as follows:

  • If no position is given, then appends child._dom.root to this._dom.root.
  • If position==="first", then inserts child._dom.root into reference node as its first child.
  • If position==="last", then appends child._dom.root to reference node.
  • If position==="before", then inserts child._dom.root into the parent of the reference node before reference node.
  • If position==="after", then inserts child._dom.root into the parent of reference node after reference node.
  • If position==="only", then inserts child._dom.root into reference node after removing all other child nodes in reference node.
  • If position==="replace", then inserts child._dom.root into the parent of reference node after removing reference node.
  • If position is the number n, then inserts child._dom.root as the nth child of reference node.

If the reference node is removed and the node indicates a child, then the child is removed

The child's parent is set to the instance and the child is inserted into the instance's children collection.

Note: the instance must be rendered before children can be explicitly inserted. To insert children before rendering, design the subclasses so that state data causes _elements to return an element tree that contains children.

delChild(child, destroy):[Component | false]

child [Component]

destroy [optional, boolean]

Removes child from the instance DOM tree and children collection; sets the child's parent to undefined. If destroy is true, then applies the child's destroy() method to the child. Returns the deleted child if successful, false otherwise.

reorderChildren(children):[void]

children [array of Component]

Reorders root dom nodes of the children as given by children.

containsClassName(name):[boolean]

name [string]

Returns true is this.className contains name.

removeClassName(name...):[this]

name [string]

Removes each name from this.className; returns this to allow chaining. See className.

addClassName(name...):[this]

name [string]

Adds each name to this.className; returns this to allow chaining. See className.

toggleClassName(name..., insert):[this]

name [string]

insert [optional, boolean]

Executes this[insert? "addClassName" : "removeClassName"](name...)

Protected Properties

_dom [Object]

Defined if and only if when the component is rendered. Holds data associated with the rendering. In particular, this._dom.root holds the root DOM node (a single DOM node) or nodes (an array of DOM nodes) consequent to rendering the component. Other data is also maintained (e.g., the handles the should be destroyed when the component is unrendered). Subclasses may add additional members to the object according to their own semantics when they need to maintain state information that is defined if and only if the component is rendered. The following names are reserved by the library: "root", "titleNode", "indexNode", "parentNode", "handles", and any name that begins with "bd_".

Protected Methods

_elements() [Element | array of Element]

Getter that returns the Element(s) that represent the DOM tree associated with the Component. The default implementation return Element("div"); subclasses typically override. May return either a single Element or an array of Elements.

_renderElements():[void]

Traverses the element or elements returned by the protected method _elements() and creates the DOM nodes and/or Component instances as given be the individual element nodes. This routine is central to the Backdraft library as should only be overridden by expert users implementing unusual use cases. Refer to the code for detailed operation.

Private Properties

[Component.ppClassName] [string]

The private className property; See className.

[Component.ppStaticClassName] [string]

The private static className property; See className.

[Component.ppEnabled] [boolean]

The private enabled property; See enabled.

[Component.ppParent] [Component | undefined]

The private parent property; See parent.

[Component.ppTabIndex] [integer | undefined]

The private tabIndex property; See tabIndex.

[Component.ppHasFocus] [boolean]

The private ppHasFocus property; See hasFocus.

[Component.ppAttachedToDoc] [boolean]

The private ppAttachedToDoc property; See attachedToDoc.

Private Methods

[Component.ppOnFocus]():[void]

Called by the focus machinery to inform the Component instance that a DOM node associated with the instance or a descendant component has received the focus. This may be overridden to effect processing when a component receives the focus; however, it is important that Component[Component.ppOnFocus] is applied in the override to effect default processing.

[Component.ppOnBlur]():[void]

Called by the focus machinery to inform the Component instance that a DOM node associated with the instance or a descendant component has received the focus. This may be overridden to effect processing when a component loses the focus; however, it is important that Component[ppOnBlur.ppOnFocus] is applied in the override to effect default processing./p>

Static Properties

ppClassName [symbol]

Defines the symbol at which the private className property is located; See className.

ppStaticClassName [symbol]

Defines the symbol at which the private static className property data is located; See className.

ppEnabled [symbol]

Defines the symbol at which the private enabled property is located; See enabled.

ppParen [symbol]t

Defines the symbol at which the private parent property is located; See parent.

ppTabIndex [symbol]

Defines the symbol at which the private tabIndex property is located; See tabIndex.

ppHasFocus [symbol]

Defines the symbol at which the private ppHasFocus property is located; See hasFocus.

catalog [Map:DOM node --> Component]

Defines a JS6 Map from this._dom.root to this for every rendered Component. This provides a method to discover a Component instance given a dom node.

Static Methods

render(type, props, attachPoint):[Component]

type [Component | Element]

props [optional, Object]

attachPoint [optional, DOM node]

Alias to render()