bd-core [namespace]
Description
Backdraft is a library for building browser-hosted user interfaces. It is fanatical about four key design values:
Pure JavaScript
Backdraft-defined components are expressed in pure JavaScript. There is no markup (HTML, JSX, etc.); there is no templating. A compilation/built step is never required. This makes development easier and faster.
Unopinionated
The Backdraft programming model completely separates the user-interface from program logic. Backdraft components work with any application design model because they do not impose any constraints on the design model. This decreases program construction complexity by definitively separating concerns.
Embrace the DOM
Modern DOM is powerful. There is no need to add abstraction layers like virtual DOM or synthetic event systems. Backdraft includes direct, unfiltered access to the DOM, but separates these details from program logic...once again, decreasing construction complexity by separating concerns.
Self-contained, Minimal, and Small
Backdraft has no dependencies and requires no special tooling. It provides precisely the machinery necessary to build user-interface components quickly and efficiently, and no more. It is small and easy to grok. This makes it easy to learn and extend.
Requirements
Backdraft is built with 100% modern Javascript (ES6). It requires a modern browser:
- Chrome v69+
- Firefox v62+
- Safari v12+
- Edge v42+
- iOS v10+
If you want to target some old, decrepit browser, build your code in a modern environment and then cross-compile it to target the old environment. Here's a good reference to start you on this task. Note that Backdraft is intended to build user interfaces for applications--not advertising web pages. Most users of applications have access to a modern browser so, in the normal case, this is never an issue.
Installation and Basic Usage
See Installation for installation basic usage instructions.
Package Organization
Backdraft is distributed with three versions of the code:
- the root module of the raw source is located at package-root/lib.js; each source file is an ES6 module
- a rollup of the library into a single ES6 module is located at package-root/dist/lib.js
- a rollup of the library into a single UMD module is located at package-root/dist/lib-umd.js; when this module is injected into an HTML document with a script element, it defines the global variable bd which contains all of the Backdraft public functions, classes, and variables
classes [namespace]
functions [namespace]
mixins [namespace]
Description
A mixin is a function that follows this pattern:
function mixin(superClass) {
return class extends superClass {
// properties and methods added by mixin
};
}
The key point of the mixin pattern is that the mixin can be defined before the class that it extends (its superclass) is defined. This pattern is discussed in more detail in Simple Mixins.
Mixin classes can be used to simulate multiple inheritance. For example Backdraft defines the mixin classes watchHub and eventHub. watchHub and eventHub are completely independent of each other. Indeed, there are cases where a new class would like to use either one but not the other as a base class. Clearly it would be a design error to have them serve in a superclass-subclass relationship. On the other hand Component requires both classes' features. Since they are both mixins, all of these problems are easily solved:
class SomeClassWithWatchHub extends watchHub(class{}){
// properties and methods for SomeClassWithWatchHub
}
class SomeClassWithEventHub extends evehtHub(class{}){
// properties and methods for SomeClassWithEventHub
}
class Component extends watchHub(eventHub(class{})) {
// properties and methods defined by Component
}
For Component, eventHub extends class{}, watchHub extends eventHub, and Component extends watchHub. The prototype chain looks like:
Component -> watchHub -> eventHub -> class{}
Since watchHub and eventHub are both mixins, they are available for use in client-defined classes that do not derive from Component.
Mixin classes work almost the same as normal classes with one notable exception: instanceof does not work. For example, even though Component has all of the properties and methods of watchHub, an instance of Component is not an instance of eventHub since eventHub is not a constructor function (it is a function that returns a constructor function). To solve this problem, mixins typically provide a property isUnique-mixin-name that returns true. For example, eventHub defines the property eventHub.isBdEventHub:
class MyClass extends eventHub() {
}
let instance = new MyClass();
instance instanceof MyClass; // true
instance instanceof EventHub; // false
instance.isBdEventHub; // true
instance new Component(); // Backdraft Component baseclass
instance instanceof Component; // true
instance instanceof EventHub; // false
instance instanceof WatchHub; // false
instance.isBdEventHub; // true
instance.isBdWatchHub; // true
post-processing-functions [namespace]
Description
When a component is rendered, the Element tree returned by Component.bdElements is transformed into a tree of DOM nodes and Component instances as given by the nodes in that tree. The transformation for each node takes three steps:
- Instantiate the DOM node or component instance. Important: when describing the operation of post-processing functions, this node/instance is termed the ppfTarget.
- Initialize the properties of the new node/instance as given by Element.ctorProps (note: if a new component instance is being created, this step is actually part of Step 1 since ctorProps are passed as constructor arguments when the component is created).
- Apply the post-processing functions to the ppfTarget as given by Element.ppFuncs.
- the component instance whose Element tree is being rendered is termed the ppfOwner
- the particular node in that tree that is being instantiated, initialized, and post-processed with the post-processing functions is termed the ppfTarget
- each key in Reflect.ownKeys(ppFuncs) is termed a ppfProcId
- each ppfProcId identifies a post-processing function that is located at getPostProcessingFunction(ppfProcId); this function is termed a ppfProc
- each value at ppFuncs[ppfProcId] is termed the ppfArgs for the ppfProc given by ppfProcId
A post-processing function has the signature (ppfOwner, ppfTarget, args). if ppfArgs is an array, then it is spread when it the ppfProc is applied. For example, if ppfArgs is [arg1, arg2, arg3] for a particular ppfProcId, then getPostProcessingFunction(ppfProcId)(ppfOwner, ppfTarget, ...ppfArgs) is applied. Let's look at an example to drive this all home:
class MyButton extends Component {
bdElements(){
return e("div", {
className: "bd-button",
bdAdvise: {click: this.onClick.bind(this)},
bdReflect: {innerHTML: ["label", label => label.toUpperCase()]}
});
}
}
let button = new MyButton({});
button.render();
For the node e("div", ...),
- bdAdvise and bdReflect are ppfProcIds and the indicate the post-processing functions getPostProcessingFunction("bdReflect") and getPostProcessingFunction("bdReflect"), respectively.
- button is the ppfOwner when the post-processing functions are applied
- the DIV DOM node is the ppfTarget when the post-processing functions are applied
- bdAdvise has ppfArgs === {click: this.onClick.bind(this)}; bdReflect has ppfArgs === {innerHTML: ["label", label => label.toUpperCase()]}.
When button is rendered, Component retrieves button's Element tree by applying bdElements. The tree contains a single DIV node with a ctorProps value of {className: "bd-button"} and a ppFuncs value of {bdAdvise: {...}, bdReflect: {...}}. After the DIV node is created (Step 1) and the className property set to "bd-button" (Step 2), the post-processing functions bdAdvise and bdReflect are applied. For example bdAdvise will be processed as follows:
getPostProcessingFunction("bdAdvise")(button, node, {click: this.onClick.bind(this)})
If you look at the documentation for bdAdvise, you'll see that this statement results in the following:
button.ownWhileRendered(connect(node, "click", this.onClick.bind(this)))
Looking back at the definition of the Element node, you will notice that there is one Hash that holds both ctorProps and ppFuncs. The element factory e determines whether or not a particular property in the hash is a post-processing function by checking for its existence in getPostProcessingFunction.
ppfArgs is often a single Hash; further, actual ppfArgs values are often a Hash with a single property. Backdraft supports some syntax sugar, termed the underscore optimization, to improve the expression in such cases:
{somePpf: {foo: "someArg"}}
{someOtherPpf: {bar: [x, y, z}}
are equivalent to
{somePpf_foo: "someArg"}
{someOtherPpf_bar: [x, y, z]}
types [namespace]
variables [namespace]
Collection [class]
Superclasses
Component | base class used to build a user interface components |
Description
Collection defines the property collection (an array). When rendered, Collection manages a collection of homogeneous child components to ensure there is a 1-to-1, onto mapping between each item in collection and each child component. If the collection is watchable, then if/when the array changes size, the rendered children components are automatically adjusted (inserted/deleted) to maintain the 1-to-1, onto mapping.
Collection also signals children of certain state changes in the underlying data:
- If the length of the collection changes, the the optional child method onMutateCollectionLength is applied (if it exists) and any watchers connected to the child's collectionLength property are signaled.
- If the index of the particular item to which a particular child is associated changes, then the optional child method onMutateCollectionIndex is applied (if it exists) and any watchers connected to the child's collectionIndex property are signaled.
- If the item to which a particular child is associated changes, then the child's item property is mutated.
Collection is very frugal in mutating the item associated with a child. When the underlying data is re-ordered or items are inserted/deleted, the children associated with existing items are not destroyed and re-created.
The Backdraft Polygraph example uses Collection data extensively. You can explore Polygraph in this Pen or load the example directly into your browser.
CollectionChild [class]
Superclasses
Component | base class used to build a user interface components |
Description
CollectionChild provides core machinery for components intended to be used as one of a collection of homogeneous component instances created consequent to a homogeneous collection of data actualized by an array.
CollectionChild takes the keyword constructor arguments parent and collectionIndex. parent gives the component instance that is creating and will contain the CollectionChild instance. CollectionChild assumes parent maintains the underlying data collection at parent.collection. The keyword argument collectionIndex gives the index into parent.collection of the data item that is associated with the particular CollectionChild instance.
The core functionality of CollectionChild is to provide, in coordination with a parent like Collection, the watchable properties collectionItem, collectionIndex, and collectionLength, which reflect the data item, data item index, and collection associated with the instance. Further, withWatchables provides machinery to automatically provide watchable properties that reflect properties in collectionItem.
CollectionChild assumes the parent will automatically signal collectionLength property mutations on the CollectionChild instance; this is the case with Collection. If the collection array changes in the parent (that is, a different array is set compared to a mutation on the same array), the the parent is responsible for setting the collectionItem. Otherwise, CollectionChild automatically watches underlying data referenced by collectionItem and signals its own mutations accordingly.
The Backdraft Polygraph example uses CollectionChild extensively. You can explore Polygraph in this Pen or load the example directly into your browser.
Component [class]
Superclasses
eventHub | provides machinery to signal events |
watchHub | provides machinery to signal mutations within an object |
Description
Component abstracts a user interface component. Subclasses derived from Component provide a public interface that is independent of the DOM, and Component provides machinery to aid in constructing the protected implementation that bridges the gap between that public interface and the DOM.
Consider the example of a combo box component. From the view of the application logic, a combo box is simply an input mechanism to collect a value from a list of (value, choice-text) pairs:
e(ComboBox, {
list: someList, value: initialValue, watch: {value: valueWatcher}
});
The protected implementation of ComboBox constructs the DOM tree that presents the user interface and responds to DOM events as the user interacts with the component. For an example of a complete combo box implementation see bd-widgets.ComboBox.
Component provides machinery to aid in constructing the protected implementation, including:
- Declarative composition
- Life cycle management
- Child management
- Property managment
- Event management
- Focus management
- IO state and behavior management
Subclasses derived from Component are intended to be based on composition and specialization patterns; a Component subclass can be defined by:
- composing several other component subclasses
- specializing/deriving from/subclassing another component class
- any combination of the above
Declarative Composition
The actualization of a browser-hosted user interface component is a DOM tree or forest. Component uses declarative composition to describe the structure, properties, and connections of the DOM tree/forest prescribed by a particular Component subclass. This is to say that the tree/forest is functionally declared as as an abstraction as opposed to being built with actual DOM nodes by an imperative process, and the components used in the declaration may be other Component subclasses. Here is an example:
e("div", {className: "renameDialog"},
e("div",
e(Labeled, {label:"Current Name", value: this.currentName}),
e(Labeled, {label:"New Name", value: e(Input, {size:50, bdReflect:"name"})})
),
e("div", {className: "bottomButtons"},
e(Button, {label: "Cancel"}),
e(Button, {label: "OK"})
)
);
The example is from a rename dialog that shows a current name, collects a new name, and provides "OK" and "Cancel" buttons to terminate the dialog. See the Rename Dialog Example for a working example.
The tree is an abstraction of the the actual DOM tree associated with a particular Component instance. The abstraction is composed of Element nodes that contain other Element nodes; e is a factory function that creates such nodes.
- Each node that has a tag name for the first argument to the e factory indicates a DOM element (all the "div" nodes).
- Each node that has a constructor function for the first argument indicates a Component subclass (Labeled, Input, and Button).
Subclasses found in the tree will have their own trees of Element nodes: this is the composition aspect of the declarative composition design. Component's internal machinery transforms an abstract Element tree in to a tree of DOM nodes; this is described in detail below.
Component subclasses may define their DOM as either a single-rooted tree or a multi-rooted forest, and all Component machinery is designed and implemented to handle both cases. When rendered, a reference to the DOM created by a component is maintained at instance.bdDom.root (see bdDom). For a single-rooted tree, bdDom.root will reference a single DOM node; similarly, for a multi-rooted forest, bdDom.root will reference an array of DOM nodes. Unless there is an important difference between the behavior of a single-rooted tree compared to a multi-rooted forest, we shall write in terms of a single-rooted tree for the remainder of Component's reference documentation.
Notice that the entirety of this system resides within the standard JavaScript environment. There is never a need to "compile" or otherwise transform an Element tree.
Since the Element tree is declared with standard JavaScript, the full power of the language is available in the declaration. There is never the need to "escape" out of a templating/markup language to include state-dependent structure/value in the declaration. Here is another example where the structure of the declaration is affected by state:
e("div", {className:"welcome-message"},
e("p", `Welcome ${fname} ${lname}`),
customerProps.important && e("p", {className:"important"}, "We're very happy to see you again!"),
e("div", {className:"suggestion-list"},
e("p", "Please consider buying the following stuff!"),
e("ul", customerProps.suggestions.map(item => e("li", item)))
)
)
)
The protected method bdElements returns the Element tree that describes the structure, properties, and connections of the DOM tree prescribed by a particular Component subclass. An override for bdElements is almost-always provided in subclass definitions.
The method render transforms the declaration into an actual DOM tree and saves a reference to the root at instance.bdDom.root (see bdDom). The method unrender destroys the DOM tree and any resources consequent rendering.
Life cycle methods like render and unrender are rarely applied explicitly; this is discussed in the section Typical Life Cycles, later.
Life Cycle
Component instances have the following life cycle states:
- Unrendered: the component's DOM tree has not been instantiated. rendered and attachedToDoc are false. The component's public API is fully functional according to the semantics of that API. For example, addClassName and removeClassName can be applied to an unrendered component, and when that component is finally rendered, the className property of bdDom.root (see bdDom) will be set accordingly. Of course some public functions are nonsensical when applied to an unrendered component. For example, focus simply results in a no-op when applied to an unrendered component.
- Rendered: the component's DOM tree has been instantiated, but not attached to the document. rendered is true, but attachedToDoc is false.
- Attached: the component's DOM tree has been instantiated and is attached to the document body. rendered and attachedToDoc are both true.
- Destroyed: destroy has been applied to the component. The component's DOM tree has been destroyed (if it was ever rendered) and all resources (handles to watchers and events, references to dom nodes, etc.) have been destroyed. Once destroyed, the component instance is in an irreversible dead state that should be easily marked for garbage collection by the JavaScript runtime environment.
Upon creating an component ("newing" up a component type), the component is in the unrendered state. A component may transition to/from the rendered and/or attached states any number of times by applying life cycle methods with the exception of destroy (see above).
Remember, life cycle methods like render and unrender are rarely applied explicitly; this is discussed in the section Typical Life Cycles, later.
Applying destroy to a Component instance is not strictly required to ensure garbage collection of an instance that is no longer in use so long as all references to that instance have been deleted. The most common places to miss references to a Component instance are found in event handler and watcher connections that reference the component. So long as handlers and watchers are connected through eventHub.advise and watchHub.watch, all connections will be automatically destroyed by destroy. This is the raison d'ĂȘtre for destroy.
Child Managment
Component instances may contain other Component instances. These other instances are termed "children" and an instance that contains a child is termed the "parent" of that child. insChild and delChild allow children to be explicitly inserted/deleted to/from the children collection of a parent. While these methods are useful for some Component subclasses that implement collections like lists and grids, usually children are created, rendered, and inserted automatically when a parent is rendered as a consequence of the parent's Element tree containing Component nodes. Recall the rename element tree we saw earlier; here it is in a more-complete context. It contains four children--two Labeled instances and two Button instances.
class RenameDialog extends Dialog {
bdElements(){
return e("div", {className: "renameDialog"},
e("div",
e(Labeled, {label:"Current Name", value: this.currentName}),
e(Labeled, {label:"New Name", value: e(Input, {size:50, bdReflect:"name"})})
),
e("div", {className: "bottomButtons"},
e(Button, {label: "Cancel"}),
e(Button, {label: "OK"})
)
);
}
// other stuff...
}
// such a dialog may be used like this...
Dialog.show(RenameDialog, {currentName: theCurrentName}).then(
result => {
if(result){
// result is the new name...rename whatever
}//else the dialog was canceled
}
);
Notice how bdElements in the example above describes a DOM tree and a set of Component instances used to "decorate" that tree. All children mentioned in the Element tree given by bdElements are automatically instantiated and rendered when the parent component is rendered. Naturally, each child instance provides its own DOM tree and decorations and so on until the component instance is completely rendered. This is a good example of a Component subclass that is both a specialization of another Component subclass, namely Dialog, and a composition of other Component subclasses, namely Labeled and Button.
When children instances are created implicitly during rendering as given by bdElements, the root of a particular child's DOM tree is appended to the parent DOM node as given by the Element tree; this is obvious from the example above. When a child is explicitly inserted with insChild, the child may be appended to the parents DOM tree at several different locations. See insChild for details.
Children may exist only when the parent is rendered. All children of a particular parent are automatically destroyed when that parent is unrendered. If this default behavior is not appropriate for a particular subclass design, an override to unrender can be provided; for example:
unrender(){
this.cachedChildren = this.children.map(child => this.delChild(child, true));
super.unrender();
}
By applying delChild with a preserve argument of true, the child will be preserved (not destroyed) upon removing it from the children collection (see delChild). Presumably, cachedChildren would be used in some way by other parts of the implementation--almost certainly during render. Caching designs can be used to dramatically improve performance for certain kinds of components.
insChild and delChild are most often used in Component subclasses that present collections, for example lists and grids. Since collections ofter require some kind of re-ordering functionality, Component provides reorderChildren, which allows the children of a parent to be reordered in-place. This machinery is the most performant design theoretically possible; hand-tuned designs will be no faster.
Owing to the design of Component's child management machinery, rendering and unrendering a particular Component instance automatically creates, renders, unrenders, and destroys that instance's children as the instance lives its lifetime. Therefore constructing an application is a matter of explicitly rendering a top-level component and then allowing that component to insert/remove children as required given user stimulation and that component's semantics. Each parent will manage its own children semantics similarly. Indeed, many kinds of applications include a single, explicit render of the top-level component instance.
Typical Life Cycles
The following examples demonstrate the typical methods used to manage life cycles. In the examples Panel is a subclass of Component that implements a home automation dimmer panel interface; panel is an instance of Panel. Similarly, TopFrame is a subclass that implements the top container of a home automation application and top is an instance of TopFrame.
An application creates, renders, and attaches a single Component instance with the render function:
let top = render(e(TopFrame), "root");
Children are created, rendered, and attached to existing Components with insChild:
let panel = top.insChild(e(Panel, {id:"bathroom"}));
Children are detached, unrendered, and destroyed with delChild:
top.delChild(panel); // panel is dead forever
Children can be detached and unrendered, but not destroyed by providing true for the preserve argument of delChild.
top.delChild(panel, true); // panel can be rendered/attached again at some time in the future
Usually, children are inserted consequent to some event caused by the user:
globalEventGenerator.advise( "onPanelDemand", (panelId) => top.insChild(e(Panel, {id:panelId})));
Putting this all together, the "onPanelDemand" event handler may cache panels, perhaps, something like this:
let panelCache = {}; let currentPanel = 0; globalEventGenerator.advise("onPanelDemand", (panelId) => { top.delChild(currentPanel, true); currentPanel = panelCache[panelId] = top.insChild(panelCache[panelId] || e(Panel, {id:panelId})); });
Most applications use the methods demonstrated above most of the time. However, there are many additional methods to create, render, unrender, attach, detach, and destroy Component instances. See constructor, render, unrender, insChild, and delChild for further details.
Property Managment and Protected Names
Component inherits the mixin class watchHub, which provides an interface to register property watchers and fire mutation notifications to those watchers. Here's how this works: For each watchable public property name, a Component subclass defines an associated protected name that is unlikely to clash with any potential public name--public names that may be desirable not only in the subclass being defined, but also in any potential subclasses of the subclass. All Backdraft-defined protected names begin with the prefix "bd", for example, "bdElements", "bdClassName", "bdHasFocus", and so on. The actual, protected property value is stored at the protected name. Then a getter, and, if the property is mutable, a setter are provided to give public access to the protected property. Finally, watchHub.bdMutate is used to detect and signal mutations. For example, here is how the public property "foo" could be implemented in an imaginary Backdraft component.
class SomeComponent extends Component{
set foo(){
this.bdMutate("foo", "bdFoo", value);
}
get foo(){
return this.bdFoo;
}
}
let c = new MyComponent();
c.watch("foo", (newValue, oldValue) => console.log("***", newValue, oldValue));
c.foo = "bar" // => *** "bar" undefined
c.foo = "baz" // => *** "baz" "bar"
c.foo = "baz" // "baz"==="baz" => the watcher is not applied
The example uses "bd" to prefix "foo" when defining the protected property address; this prefix is reserved by the Backdraft library and client code should choose another prefix. Prefixes should be selected so they are likely universally unique.
In this pattern, we say...
- bdFoo is a protected property
- foo is a public property
- foo reflects bdFoo
- bdFoo is reflected by foo
Noice that, but not supplying a getter, a public property could be made read-only while the protected property remains mutable. This is a common pattern where a public property reflects an internal state, but that internal state is only allowed to be changed by some internal process. This idea can be taken a step further where the protected property doesn't exist at all and the read-only public property is a reflection of some internal state calculation.
Here is a list of the public properties defined by Component together with the protected properties/state, they reflect.
id - > read-only property defined at construction |
rendered - > !!bdDom, bdDom exists iff the component has been rendered |
attachedToDoc - > bdAttachedToDoc |
className - > bdClassName |
hasFocus - > bdHasFocus, automatically set by focusManager |
enabled - > !bdDisabled |
disabled - > bdDisabled |
tabIndex - > bdTabIndex |
visible - > reflects/mutates "bd-hidden" in className |
title - > bdTitle |
Event Managment
Component inherits the mixin class eventHub, which provides an interface to register event handlers and fire event notifications to registered handlers. Component does not define any events itself. See eventHub.advise and eventHub for further details.
Element [class]
Description
Element instances are used to describe the DOM tree defined by a Component subclass as returned by Component.bdElements. Each Element instance represents a node in a tree that describes either a DOM node or a Component instance; further, each instance can contain children, allowing a tree structure to be defined.
Elements are immutable and encapsulate four things:
either a native DOM node tag or a subclass of Component
a Hash of (property -> value) that is used to initialize the node when it is created
a Hash of (post-processing-function -> arguments) that gives a list of functions that are executed immediately after the node is created and initialized
a list of children Element instances
Typically, Element instances are created with the factory functions e or svg.
WatchableRef [class]
Description
WatchableRef encapsulates two operations on a particular property within watchHub or Watchable instance:
- retrieving and formatting the current value of the property
- connecting watchers to the property, ensuring new/old values applied to the watchers are formatted and the watchers are called only if the formatted new/old values mutate
The property of interest in the watchable object is termed the "reference property" and the owning object is termed the "reference object". Both the reference property and reference object are initialized at construction and are immutable for the lifetime of a WatchableRef instance.
The example below sets up a WatchableRef on the property a of data. Further a formatter is provided that converts the number to either the string "odd" or "even" depending upon its value.
let data = toWatchable({a:1, b:2});
let ref = new WatchableRef(data, "a", value => value % 2 ? "odd" : "even");
// ref.value gives the current value of the reference property in the
// reference object applied to the formatter
console.log(ref.value); // => odd
data.a = 2;
console.log(ref.value); // => even
// set up a watch on the ref...
ref.watch((newValue, oldValue, refObj, prop) => {
console.log(`new=${newValue}, old=${oldValue}, prop=${prop}, ${refObj===data}`);
});
data.a = 3; // => new=odd, old=even, prop=a, true
data.a = 5; // since the formatted value did not change, the watcher is not called
// note: the formatter was called
data.a = 6 // => new=even, old=odd, prop=a, true
WatchableRefs may be set up to reference all properties on a reference object by omitting the reference property at construction. Refs set up in this manner are termed "star" refs. For star refs, the value property returns the result of the formatter applied to the reference object. The arguments provided to watchers connected to star refs are also different as follows:
- newValue is the result of the formatter applied to the current value of the reference object
- if the mutated property is a non-scalar type, then oldValue is the constant UNKNOWN_OLD_VALUE
- prop is an array that lists the property path to the particular property in the reference object that mutated
For example:
let data = toWatchable([{a:1, b:2}, {c:3, d:4}]);
let ref = new WatchableRef(data, value => {
console.log("in formatter", value===data);
return JSON.stringify(data);
});
// ref.value returns the formatted reference object
console.log(ref.value);
// => in formatter true
// => [{"a":1,"b":2},{"c":3,"d":4}]
data[0].b= 3.14;
console.log(ref.value);
// => in formatter true
// => [{"a":1,"b":3.14},{"c":3,"d":4}]
ref.watch((newValue, oldValue, referenceObject, prop) => {
console.log("newValue: ", newValue);
console.log(oldValue===WatchableRef.UNKNOWN_OLD_VALUE);
console.log(referenceObject===data);
console.log(prop);
});
data[1].c = "test";
// => in formatter true
// => newValue: [{"a":1,"b":3.14},{"c":"test","d":4}]
// => true
// => true
// => (2)Â ["1", "c"]
The formatter is optional; if missing, the formatter defaults to the identity function; see constructor.
WatchableRefs are an advanced featured. WatchableRefs are used internally by Backdraft to set up reflectors.
biBind [function]
Synopsis
(src, srcProp, dest, destProp) => handles[]
src watchHub | Watchable | the source object to watch |
srcProp string | symbol | the property in src to watch |
dest watchHub | Watchable | the source object to watch |
destProp string | symbol | the property in src to watch |
handles Destroyable[] | Destroyable objects that can be used to terminate the binding |
Description
Initializes src[srcProp] to dest[destProp], then sets up watchers on both properties so that any mutation on one is propagated to the other. Really just syntax sugar:
biBind(src, srcProp, dest, destProp)
is equivalent to
src[srcProp] = dest[destProp];
return [bind(src, srcProp, dest, destProp), bind(dest, destProp, src, srcProp)];
bind [function]
Synopsis
(src, srcProp, dest, destProp) => handle
src watchHub | Watchable | the source object to watch |
srcProp string | symbol | the property in src to watch |
dest Object | the destination object |
destProp string | symbol | the property in dest to mutate |
handle Destroyable | Destroyable object that can be used to terminate the binding |
Description
Initializes dest[destProp] to src[srcProp], then sets up a watcher on src[srcProp] to propagate any mutations to dest[destProp]. Really just syntax sugar:
bind(src, srcProp, dest, destProp)
is equivalent to
dest[destProp] = src[srcProp];
if(src.isBdWatchHub){
return src.watch(srcProp, newValue => dest[destProp] = newValue);
}else if(isWatchable(src)){
return watch(srcProp, newValue => dest[destProp] = newValue);
}else{
throw new Error("src is not watchable");
}
connect [function]
Synopsis
(eventType, eventSourceType1, handler, useCapture) => handle
(eventType, eventSourceType2, handler) => handle
eventType string | symbol | the event to connect |
eventSourceType1 object that contains addEventListener method | the source of the event |
eventSourceType2 object that contains advise method | the source of the event |
handler (eventObject) => any | the event handler |
handle Destroyable | Destroyable object that can be used to terminate the advise |
Description
Connects listener to the event eventType generated by eventSourceType1/eventSourceType2 and returns a Destroyable that destroys the connection. eventSourceType1 source objects (typically DOM nodes) are connected with addEventListener; eventSourceType2 source objects are connected with advise with semantics as given by eventHub.advise. Any target that implements addEventListener and removeEventListener works with connect.
useCapture has semantics as given by eventSourceType1.addEventListener.
If the document supports touch events, then event types mousedown, mousemove, and mouseup result in connecting listener to touchstart, touchmove, and touchend, respectively in addition to requested event type. In this case, the returned Destroyable will destroy both connections.
create [function]
Synopsis
(htmlTag, props) => domNode
(namespacedTag, props = {}) => domNode
htmlTag string | an HTML tag name |
namespacedTag [namespace, tag] |
namespace (a string) is a www.w3.org namespace; tag (a string) gives a element tag within that namespace |
props Hash |
optional, default = {}
hash of (attributeName -> value) pairs to initialize new DOM node
|
domNode DOM node | the new DOM node |
Description
If htmlTag is given, then creates a new DOM node with document.createElement; if namespacedTag is given, then creates a new DOM node with document.createElementNS. After the node is created, attributes values are initialized by applying setAttr(node, hash).
destroyDomChildren [function]
Synopsis
(node) => void
node DOM node | the parent DOM node of which children are to be destroyed |
Description
Removes all children of node.
destroyDomNode [function]
Synopsis
(node) => void
node DOM node | the DOM node to destroy |
Description
Removes the node from its parent.
e [function]
Synopsis
(htmlTag, props = {}, ...children) => element
(namespacedTag, props = {}, ...children) => element
(component, prop s= {}, ...children) => element
htmlTag string | an HTML tag name |
namespacedTag [namespace, tag] |
namespace (a string) is a www.w3.org namespace; tag (a string) gives a element tag within that namespace |
component subclass of Component constructor | component type |
props Hash |
optional, default = {}
The Hash of (property -> value) and (post-processing-function -> arguments) pairs that describe how to initialize Element.ctorProps and Element.ppFuncs.
|
children falsey | Element | children[] |
optional children can be falsey | Element | children[], thereby allowing arbitrarily nested arrays of children.
|
element Element | the new Element |
Description
Syntax sugar:
e(arguments)
is equivalent to
new Element(arguments)
svg provides syntax sugar for SVG elements.
eql [function]
Synopsis
(refValue, otherValue) => boolean
refValue any | one side of the comparison; used to find an eql comparator |
otherValue any | one other side of the comparison |
Description
Compares two values according to the semantics of the comparator located at eqlComparators.get(refValue.constructor), if any; otherwise the comparison uses ===. Used by all Backdraft watch machinery to determine if a potential mutation is an actual mutation. Here is the complete code for eql:
function eql(refValue, otherValue){
if(!refValue){
return otherValue === refValue;
}else{
let comparator = eqlComparators.get(refValue.constructor);
if(comparator){
return comparator(refValue, otherValue);
}else{
return refValue === refValue;
}
}
}
Client code can add comparators for client-defined types. For example:
class Point {
constructor(x, y){
this.x = x;
this.y = y;
}
}
eqlComparators.set(Point, (lhs, rhs) => {
return lhs instanceof Point && rhs instanceof Point && lhs.x===rhs.x && lhs.y===rhs.y;
});
let p1 = new Point(1, 2);
let p2 = new Point(1, 2);
let p3 = new Point(3, 4);
eql(p1, p2); // true;
eql(p1, p3); // false;
eql(p1, {x:1, y:2}); // false;
getAttr [function]
Synopsis
(node, attributeName) => string
(id, attributeName) => string
(component, attributeName) => string
node DOM node | source DOM node |
id string | source DOM node is given by document.getElementById(id) |
component Component | source DOM node is given by component.bdDom.root |
attributeName string | name of the attribute whose value you want to get |
Description
Returns the value of a specified attribute on the source DOM node. The first signature is the only substantive signature, the remaining signatures are syntax sugar:
getAttr(component, attributeName)
getAttr("someId", attributeName)
are equivalent to:
getAttr(component.bdDom.root, attributeName)
getAttr(document.getElementById("someId"), attributeName)
getComputedStyle [function]
Synopsis
(node) => CSSStyleDeclaration
(id) => CSSStyleDeclaration
(component) => CSSStyleDeclaration
node DOM node | source DOM node |
id string | source DOM node is given by document.getElementById(id) |
component Component | source DOM node is given by component.bdDom.root |
CSSStyleDeclaration CSSStyleDeclaration | live CSSStyleDeclaration |
Description
Returns a live CSSStyleDeclaration object via window.getComputedStyle.
The first signature is the only substantive signature, the remaining signatures are syntax sugar:
getComputedStyle(component)
getComputedStyle("someId")
are equivalent to:
getComputedStyle(component.bdDom.root)
getComputedStyle(document.getElementById("someId"))
getPosit [function]
Synopsis
(node) => Posit
(component) => Posit
(id) => Posit
node DOM node | source DOM node |
id string | source DOM node is given by document.getElementById(id) |
component Component | source DOM node is given by component.bdDom.root |
Description
Returns a Posit object as computed by node.getBoundingClientRect. The maxH, maxW, and z properties of the returned Posit object are not defined.
The first signature is the only substantive signature, the remaining signatures are syntax sugar:
getPosit(component)
getPosit("someId")
are equivalent to:
getPosit(component.bdDom.root)
getPosit(document.getElementById("someId"))
getPostProcessingFunction [function]
Synopsis
(ppfProcId) => (ppfOwner, ppfTarget, ...args) => void
ppfProcId string | symbol | target post-processing function identifier |
Description
Returns the requested post-processing function if it exists in Backdraft's private catalog of post-processing functions; undefined otherwise. See post-processing-functions for definitions of ppfProcId, ppfOwner, and ppfTarget and other details.
Function are inserted into the catalog with insPostProcessingFunction and replacePostProcessingFunction.
getStyle [function]
Synopsis
(node, styleName) => string
(id, styleName) => string
(component, styleName) => string
node DOM node | source DOM node |
id string | source DOM node is given by document.getElementById(id) |
component Component | source DOM node is given by component.bdDom.root |
styleName string | name of the style whose value you want to get |
Description
Returns the value of a specified style on the source DOM node. If the style is a pixel value, then it is returned as a number.
The first signature is the only substantive signature, the remaining signatures are syntax sugar:
getStyle(component, styleName)
getStyle("someId", styleName)
are equivalent to:
getStyle(component.bdDom.root, styleName)
getStyle(document.getElementById("someId"), styleName)
getStyles [function]
Synopsis
(node, ...styleNames) => string
(id, ...styleNames) => string
(component, ...styleNames) => string
node DOM node | source DOM node |
id string | source DOM node is given by document.getElementById(id) |
component Component | source DOM node is given by component.bdDom.root |
styleNames string | Hash | string[] | the list of styles to query |
Description
Processes ...styleNames to get a list of style names:
- strings then are added to the list
- string[]s are concatenated to the list
- Object.getOwnPropertyNames(hash) are concatenated to the list
Then, given the list of style names, list, returns result which is computed as follows:
result = {};
list.forEach(name => result[name] = getStyle(node, name));
The first signature is the only substantive signature, the remaining signatures are syntax sugar:
getStyles(component, ...styleNames)
getStyles("someId", ...styleNames)
are equivalent to:
getStyles(component.bdDom.root, ...styleNames)
getStyles(document.getElementById("someId"), ...styleNames)
getWatchableRef [function]
Synopsis
(referenceObject, referenceProp = WatchableRef.STAR, formatter = x => x)
referenceObject watchHub | Watchable | the reference watchable object |
referenceProp string | symbol |
optional, default = STAR the property within referenceObject to reference
|
formatter function(any) => any |
optional, default = x => x
applied to the reference property value to compute WatchableRef.value and applied to newValue/oldValue args before applying watchers
|
Description
Syntax sugar:
getWatchableRef(args)
is equivalent to
new WatchableRef(args
insPostProcessingFunction [function]
Synopsis
(ppfProcId, ppf) => void
ppfProcId string | symbol | the identifier of the post-processing function |
ppf function(ppfOwner, ppfTarget, function-dependent-parameters) | the post-processing function |
Description
Inserts ppf at ppfProcId into Backdraft's private catalog of post-processing functions. An exception is thrown if a function already exists for ppfProcId. See replacePostProcessingFunction to replace an already-existing function. See post-processing-functions for definitions of ppfProcId, ppfOwner, and ppfTarget and other details.
insert [function]
Synopsis
(node, refNode, position = "last") => node | nodes[] | void
node DOM node | DOM node to insert |
refNode DOM node | reference DOM node |
position Position | location to insert node with respect to reference node |
node DOM node | refNode when position==="replace" |
nodes array of DOM node | children of refNode when position==="only" |
Description
Inserts node with respect to refNode as given by position:
"first" | insert node as the first child of refNode |
"last" | insert node as the last child of refNode |
"only" | insert node as the only child of refNode; all existing children are removed and returned. |
"replace" | remove refNode from its parent and replace it with node; refNode is returned |
"before" | insert node as a sibling of refNode, before refNode |
"after" | insert node as a sibling of refNode, after refNode |
isWatchable [function]
Synopsis
(source) => boolean
source any | value to test |
render [function]
Synopsis
(componentClass, kwargs, node, position) => component
(componentClass, kwargs, id, position) => component
(componentInstance, node, position) => component
(componentInstance, id, position) => component
(element) => component
(element, node, position) => component
(element, id, position) => component
componentClass subclass of/or Component constructor | the component class to create |
componentInstance instance of Component | the component instance to append to the document |
element Element | describes the component to create |
node DOM node |
optional the reference node to which to insert the component's rendered DOM
|
id string |
optional equivalent to providing node===document.getElementById(id)
|
position Position |
optional, default = last position relative to node to insert the component's rendered DOM
|
Description
Optionally creates a new component instance, unconditionally renders that instance, and optionally inserts the rendered DOM tree with respect to node.
The most common usage of render is with the signature (componentClass, kwargs, id), which instantiates a new componentClass instance, renders that instance, and appends the root of the DOM rendering to document.getElementById(id). In code:
import SomeComponentClass from "./SomeComponentClass.js";
let kwargs = {/* as required */};
render(SomeComponentClass, kwargs, "root");
// is equivalent to...
let instance = new SomeComponentClass(kwargs);
instance.render();
insert(instance.bdDom.root, document.getElementById("root"), "last");
insert is the Backdraft insert function. Since position was not given in the example, it defaulted to "last". In general, the position argument controls where the rendered DOM is inserted as described by insert.
If the node argument is missing, the rendered DOM is not inserted anywhere. This applies for all signatures.
The second most common usage of render is to render and insert an already-created component instance. In code:
import SomeComponentClass from "./SomeComponentClass.js";
let instance = new SomeComponentClass(kwargs);
// at some point later...
render(instance, "root");
// is equivalent to...
instance.render();
insert(instance.bdDom.root, document.getElementById("root"), "last");
Applying render to an Element instance is unusual, but is provided for completeness. If element.type references a tag name (see Element.type, then render creates the component new Component({element:element}); otherwise render creates the component new element.type(element).
The component instance is returned by all signatures.
replacePostProcessingFunction [function]
Synopsis
(ppfProcId, ppf) => void
ppfProcId string | symbol | the identifier of the post-processing function |
ppf function(ppfOwner, ppfTarget, function-dependent-parameters) | the post-processing function |
Description
Inserts ppf at ppfProcId into Backdraft's private catalog of post-processing functions. If a function already exists for ppfProcId, then that function is replaced with ppf. See post-processing-functions for definitions of ppfProcId, ppfOwner, and ppfTarget and other details.
setAttr [function]
Synopsis
(node, attributeName, value) => void
(component, attributeName, value) => void
(id, attributeName, value) => void
(node, hash) => void
(component, hash) => void
(id, hash) => void
node DOM node | source DOM node |
id string | source DOM node is given by document.getElementById(id) |
component Component | source DOM node is given by component.bdDom.root |
attributeName string | name of the attribute whose value you want to set |
value string | value to assign to attribute |
hash Hash (attributeName -> value) | list of (attributeName -> value) pairs to set |
Description
Set the value of a specified attribute on the source DOM node. The first signature is the only substantive signature, the remaining signatures are syntax sugar:
setAttr(component, attributeName, value)
setAttr("someId", attributeName, value)
setAttr(node, hash)
setAttr(component, hash)
setAttr("someId", hash)
are equivalent to:
setAttr(component.bdDom.root, attributeName, value)
setAttr(document.getElementById("someId"), attributeName, value)
Object.getOwnPropertyNames(hash).forEach(p => setAttr(node, p, hash[p])
Object.getOwnPropertyNames(hash).forEach(p => setAttr(component.bdDom.root, p, hash[p])
Object.getOwnPropertyNames(hash).forEach(p => setAttr(document.getElementById("someId"), p, hash[p])
setPosit [function]
Synopsis
(node, posit) => void
(component, posit) => void
(id, posit) => void
node DOM node | source DOM node |
id string | source DOM node is given by document.getElementById(id) |
component Component | source DOM node is given by component.bdDom.root |
posit Posit | position properties to set |
Description
Sets the DOM style size and/or position values in pixels as given by posit. posit need not provide all possible values. Any property in posit that is not a property defined by Posit is ignored.
The first signature is the only substantive signature, the remaining signatures are syntax sugar:
setPosit(component, posit)
setPosit("someId", posit)
are equivalent to:
setPosit(component.bdDom.root, posit)
setPosit(document.getElementById("someId"), posit)
setStyle [function]
Synopsis
(node, styleName, value) => void
(component, styleName, value) => void
(id, styleName, value) => void
(node, hash) => void
(component, hash) => void
(id, hash) => void
node DOM node | source DOM node |
id string | source DOM node is given by document.getElementById(id) |
component Component | source DOM node is given by component.bdDom.root |
styleName string | name of the style to set |
value string | value to assign to style |
hash Hash (styleName -> value) | list of (styleName -> value) pairs to set |
Description
Set the value of a specified style on the source DOM node. The first signature is the only substantive signature, the remaining signatures are syntax sugar:
setStyle(component, styleName, value)
setStyle("someId", styleName, value)
setStyle(node, hash)
setStyle(component, hash)
setStyle("someId", hash)
are equivalent to:
setStyle(component.bdDom.root, styleName, value)
setStyle(document.getElementById("someId"), styleName, value)
Object.getOwnPropertyNames(hash).forEach(p => setStyle(node, p, hash[p])
Object.getOwnPropertyNames(hash).forEach(p => setStyle(component.bdDom.root, p, hash[p])
Object.getOwnPropertyNames(hash).forEach(p => setStyle(document.getElementById("someId"), p, hash[p])
stopEvent [function]
Synopsis
(eventObject) => void
eventObject Object |
optional, default = undefined the object applied to an event listener
|
Description
Syntax sugar:
stopEvent(event)
is equivalent to
if(event && event.preventDefault){
event.preventDefault();
event.stopPropagation();
}
svg [function]
Synopsis
(svgTag = "svg", props = {}, ...children)
svgTag string |
optional, default = "svg"
a SVG tag name
|
props Hash |
optional, default = {}
The Hash of (property -> value) and (post-processing-function -> arguments) pairs that describe how to initialize Element.ctorProps and Element.ppFuncs.
|
children falsey | Element | children [] |
optional children can be falsey | Element | [children], thereby allowing arbitrarily nested arrays of children.
|
Description
Syntax sugar:
svg(svgTag, arguments)
is equivalent to
new Element(["http://www.w3.org/2000/svg", svgTag], arguments)
toWatchable [function]
Synopsis
(source) => Watchable
source Object | Any Javascript Object, including arrays. |
Description
toWatchable transforms any JavaScript Object, including arrays, so that property mutations throughout the object's property hierarchy (not prototype chain) can be observed by connecting a watcher with watch or WatchableRef. Mutations detected include inserting and deleting properties as well as mutating property values. Any inserted properties that are themselves JavaScript Objects, including arrays, are inserted by value (not reference) and the value is converted to a watchable before insertion.
The conversion of the target object is both in-place and deep:
- Each property of the target object that has an object value, including arrays, is converted to a Watchable
- The entire object hierarchy is replaced
Typically, source objects must be pure Objects or Arrays (that is, not subclasses of Object or Array). As the property hierarchy is traversed during transformation, any property value that is not a pure Object or Array is treated as a scalar value and its contents are not watchable. For example, consider the following object:
class DoB extends Data {
get day() { return this.getDate(); }
get month() { return this.getMonth() + 1; }
get year() { return this.getFullYear(); }
}
{
name: {
fname: "John",
lname: "Doe"
},
dob: new DoB(1960, 5, 7)
}
name, name.fname, name.lname, and dob are all watable. However, dob.day, dob.month, and dob.year are not watchable.
See watch and Watchable Data for examples.
watch [function]
Synopsis
(watchable, prop = STAR, watcher) => handle
(watchable, props, watcher) => handles[]
(watchable, hash) => handles[]
watchable Watchable | the object to watch |
prop string | symbol |
optional, default = STAR the property to watch
|
watcher Watcher | watcher to apply upon mutation of prop |
handle Destroyable | Destroyable object that can be used to terminate the watch |
handles Destroyable | array of Destroyable object that can be used to terminate the watches |
Description
The first signature is the only substantive signature, the remaining signatures are syntax sugar discussed at the end.
When watch is applied with a prop argument other than STAR and the watcher is not being signaled consequent to a mutation bubbling up (see below) then watcher will be applied to the following arguments:
- newValue: the value of watchable[prop] after mutation
- oldValue: the value of watchable[prop] before mutation
- prop: prop provided when watch was applied to connect the watcher
- target: the just-mutated watchable provided when watch was applied to connect the watcher
Otherwise, the watchers will be applied to the following arguments:
- newValue: same as target
- oldValue: UNKNOWN_OLD_VALUE
- prop: an array that lists the property path to the particular watchable property that mutated
- target: the just-mutated watchable provided when watch was applied to connect the watcher
All signatures return a handle or handles to all watches that were connected.
Example:
let target = toWatchable([{fname: "John", lname: "Doe"}]);
// connect to fname
let h1 = watch(target[0], "fname", (newValue, oldValue, target, prop) => {
console.log(
"target[0].fname watcher:",
"newValue=", JSON.stringify(newValue),
"| oldValue=", JSON.stringify(oldValue),
"| target=", JSON.stringify(target),
"| property=", prop);
});
// connect to the first item in tager
let h2 = watch(target, 0, (newValue, oldValue, target, prop) => {
console.log(
"target[0] watcher:",
"newValue=", JSON.stringify(newValue),
"| oldValue=", JSON.stringify(oldValue),
"| target=", JSON.stringify(target),
"| property=", prop);
});
// connect to the entire target
let h3 = watch(target, (newValue, oldValue, target, prop) => {
console.log(
"target watcher:",
"newValue=", JSON.stringify(newValue),
"| oldValue=", JSON.stringify(oldValue),
"| target=", JSON.stringify(target),
"| property=", prop);
});
target[0].fname = "Joe";
// target[0].fname watcher: newValue= "Joe" | oldValue= "John" | target= {"fname":"Joe","lname":"Doe"} | property= ["fname"]
// target[0] watcher: newValue= {"fname":"Joe","lname":"Doe"} | oldValue= {"value":"UNKNOWN_OLD_VALUE"} | target= [{"fname":"Joe","lname":"Doe"}] | property= (2)Â ["0", "fname"]
// target watcher: newValue= [{"fname":"Joe","lname":"Doe"}] | oldValue= {"value":"UNKNOWN_OLD_VALUE"} | target= [{"fname":"Joe","lname":"Doe"}] | property= (2)Â ["0", "fname"]
target[0].lname = "Smith";
// target[0] watcher: newValue= {"fname":"Joe","lname":"Smith"} | oldValue= {"value":"UNKNOWN_OLD_VALUE"} | target= [{"fname":"Joe","lname":"Smith"}] | property= (2)Â ["0", "lname"]
// target watcher: newValue= [{"fname":"Joe","lname":"Smith"}] | oldValue= {"value":"UNKNOWN_OLD_VALUE"} | target= [{"fname":"Joe","lname":"Smith"}] | property= (2)Â ["0", "lname"]
h1.destroy();
h2.destroy();
target[0].fname = "Adam";
// since we destroyed the first two watchers, we don't see their output; the third watcher is still connected...
// target watcher: newValue= [{"fname":"Adam","lname":"Smith"}] | oldValue= {"value":"UNKNOWN_OLD_VALUE"} | target= [{"fname":"Adam","lname":"Smith"}] | property= (2)Â ["0", "fname"]
Notice that when a property within a data hierarchy is mutated, notifications of that mutation bubble up through the hierarchy. For example, mutating data[0].fname signals any watchers connected to data[0].fname, data[0], and data. As notifications are bubbled up, newValues and oldValues provided to the watchers change. For example watchers connected to data[0].fname get new/old values of data[0].fname while watchers connected to data[0] and data get new/old values of data[0] and data respectively. This ensures the type of the new/old value is constant even though the source of the mutation may be different (for example mutating the complete data[0] object compared to just mutating data[0].fname).
In some cases the old value is not provided, but rather a known constant, namely UNKNOWN_OLD_VALUE, is provided. This happens when a contained property is mutated. For example, fname is contained by data[0] and data; similarly, data[0] is contained by data. Backdraft does not supply the old value because it is computationally expensive to provide the value. Think about a data hierarchy that is an array of 10,000 items, each item is an array of 100 items (a 10,000 x 100 grid), and each item is an object with 20 properties. If the old value was provided as mutation signals bubbled up, two complete copies of the 20M item data structure would be required. This is never a problem as the old value is rarely used in watcher functions that answer to bubbled-up signals, and if some particular watcher design requires some aspect of the old value, then such watchers can understand exactly what changed by looking at the prop argument and cache old values as is required by the particular design.
eventHub [mixin]
Description
eventHub is a mixin class (see mixins) that provides machinery to signal events and further to allow clients to register handlers to receive such signals. The method advise allows clients to register a handler, and the method bdNotify applies all handlers registered to a particular event type. For example:
class SuperClass {
superclassMethod(){
console.log("in superclass method");
}
}
class MyClass extends eventHub(SuperClass){
stimulate(number){
if(number % 2){
this.bdNotify({type: "odd", number: number});
}else{
this.bdNotify({type: "even", number: number});
}
}
let test = new MyClass();
// SuperClass is a superclass of MyClass...
test.superclassMethod(); // => in superclass method
// only print out "odd" events...
test.advise("odd", (e) => console.log(e));
test.stimulate(100); // no output, "even" event was notified
test.stimulate(101); // => {type: "odd", number: 101}
watchHub [mixin]
Description
watchHub is a mixin class (see mixins) that provides machinery to signal mutations within an object and further to allow clients to register watchers to receive such signals. The method watch allows clients to register a Watcher, and the method bdMutateNotify applies all watchers registered to a particular property.
Often watchable properties are implemented by defining a protected property on an object and providing getter/setter proxies on that property. The method bdMutate can be used to signal mutations with this design. See Watchable Properties for details. For example:
class SuperClass {
superclassMethod(){
console.log("in superclass method");
}
}
class MyClass extends watchHub(SuperClass) {
get myProp(){
return this._myProp();
}
set myProp(value){
this.bdMutate("myProp", "_myProp", value);
}
}
let test = new MyClass();
// SuperClass is a superclass of MyClass...
test.superclassMethod(); // => in superclass method"
// connect a watcher...
test.watch("myProp", (newValue, oldValue)=>console.log("new:", newValue, "old:", oldValue));
test.myProp = 3.14; // new: 3.14 old: undefined
test.myProp = "foo"; // new: foo old: 3.14
withWatchables [mixin]
Synopsis
withWatchables(superClass = class{}, ...propertyDefs) => class
Description
Defines a new class that has superClass as its base class and contains getters and setters for each property definition in propertyDefs. Each property definition can either give a public-property-name or a (public-property-name, private-property-name) pair. If the first form is given, then that public-property-name must be a string and the private-property-name is automatically calculated to be the public-property-name prefixed by an underscore.
Given a (public-property-name, private-property-name) pair, say ("x", "_x"), withWatchables causes a class with a getter/setter to be defined as demonstrated below.
class MyClass extends withWatchables(Component, ["x", "_x"]){
// properties and methods for MyClass
}
// is equivalent to...
let temp = class extends Component{
get x(){
return this._x;
}
set x(value){
this.bdMutate("x", "_x", value);
}
}
class MyClass extends temp {
// properties and methods for MyClass
}
withWatchables is really just sugar to simplify expressing routine class machinery. See also Component.withWatchables for more sugar.
bdAdvise [post-processing-function]
Description
This description uses some of the terms ppfOwner, ppfTarget, ppfProcId, ppfProc, and ppfArgs; see post-processing-functions for a definition of these terms.
bdAdvise takes a hash from event type, eventType, to handler function, handler. If ppfTarget is a Component, then ppfTarget.advise is applied to (eventType, handler) (see eventHub.advise); otherwise handler is connected by applying connect to (ppfTarget, eventType, handler) and the handle returned is applied to ppfOwner.ownWhileRendered (see Component.ownWhileRendered). For example:
class MyClass extends Component {
bdElements(){
return e("div",
e("div", {bdAdvise: {click: this.onClick.bind(this)}}),
e(SomeComponent, {bdAdvise: {someEvent: this.onSomeEvent.bind(this)}})
};
}
}
The bdAdvise for the freshly-created DIV node, node, causes the following connection:
ppfOwner.ownWhileRendered(connect(node, "click", this.onClick.bind(this)))
And the bdAdvise for freshly-instantiated SomeComponent, instance, causes the following connection:
instance.advise("someEvent" this.onClick.bind(this)))
If a string or symbol is given for the handler, then ppfOwner[handler] is assumed. For example:
e("div", {bdAdvise: {click: "onClick"}})
is equivalent to
e("div", {bdAdvise: {click: this.onClick.bind(this)}})
As described in post-processing-functions, when ppfArgs is a hash, as in the case of bdAdvise, the underscore optimization may be employed. The following is equivalent to DIV connection in the original example:
e("div", {bdAdvise_click: "onClick"})
bdAttach [post-processing-function]
Description
This description uses some of the terms ppfOwner, ppfTarget, ppfProcId, ppfProc, and ppfArgs; see post-processing-functions for a definition of these terms.
When ppfArgs is a string or symbol, prop, bdAttach causes the following processing:
ppfOwner[prop] = ppfTarget;
ppfOwner.ownWhileRendered({
destroy: function(){
delete ppfOwner[prop];
}
});
When ppfArgs is a x => void, func, bdAttach causes the following processing:
func(ppfTarget);
bdChildrenAttachPoint [post-processing-function]
Description
This description uses some of the terms ppfOwner, ppfTarget, ppfProcId, ppfProc, and ppfArgs; see post-processing-functions for a definition of these terms.
Designates ppfTarget as the node to which children are attached.
bdReflect [post-processing-function]
Description
This description uses some of the terms ppfOwner, ppfTarget, ppfProcId, ppfProc, and ppfArgs; see post-processing-functions for a definition of these terms. Arguments
bdReflect takes a hash from property, termed the targetProperty, in ppfTarget to the arguments (watchable, prop, formatter). watchable, optional, is either a watchHub a Watchable; if missing, watchable defaults to ppfOwner. prop is a string or symbol to watch in watchable. formatter, optional, is a formatter function to apply to watchable[prop] before reflecting into ppfTarget[targetProperty], if missing, formatter defaults to x => x.
To reflect means ppfTarget[targetProperty] is initialized with formatter(watchable[prop]) when ppfTarget is created and further the property is updated to reflect the current formatted value of formatter(watchable[prop]) any time that value mutates. For example, consider the following component:
class MyComponent extends Component.withWatchables("myProp") {
bdElements(){
e("div", {bdReflect: {innerHTML: [this, "myProp", v => v ? v : "?"]});
}
}
The innerHTML of the DIV node will be initialized to this.myProp ? this.myProp : "?" and any time that the value this.myProp ? this.myProp : "?" mutates, innerHTML will be updated accordingly.
The arguments for an particular targetProperty are given as an array; if both watchable and formatter are missing, then arguments may be given as a single scalar prop value. For example:
{bdRefect: {innerHTML: [this, "myProp", v => v ? v : "?"]}}
// arguments: (this, "myProp", v => v ? v : "?")
{bdRefect: {innerHTML: ["myProp"]}}
// arguments: (ppfOwner, "myProp", x => x)
{bdRefect: {innerHTML: "myProp"}}
// arguments: (ppfOwner, "myProp", x => x)
When reflecting a property in the ppfOwner, the watchable argument may be omitted. The following is equivalent to the original example:
bdElements(){
e("div", {bdReflect: {innerHTML: ["myProp", v => v ? v : "?"]);
}
As described in post-processing-functions, when ppfArgs is a hash, as in the case of bdReflect, the underscore optimization may be employed. The following is equivalent to the original example:
bdElements(){
e("div", {bdReflect_innerHTML: ["myProp", v => v ? v : "?"]);
}
bdReflect includes an extra expressive optimization by assuming innerHTML if the target property is missing. This allows bdReflect to accept ppfArgs that gives an argument list rather than a hash. The following is equivalent to the original example:
bdElements(){
e("div", {bdReflect: ["myProp", v => v ? v : "?"]);
}
As noted in bdReflect's signature, the formatter is always optional. So the common case of reflecting a property value in a component into the innerHTML without formatting can be stated quite tersely as follows:
bdElements(){
e("div", {bdReflect: "myProp");
}
bdReflectClass [post-processing-function]
Description
This description uses some of the terms ppfOwner, ppfTarget, ppfProcId, ppfProc, and ppfArgs; see post-processing-functions for a definition of these terms.
Reflects one or more formatted watchables into ppfOwner.className. ppfArgs is given as a list or arguments (an array). The list is transformed into a set of triples of (watchable, prop, formatter) and each triple causes formatter(watchable[prop]) to be reflected into ppfOwner.className. Each triple is a completely separate reflection. For example:
class MyClass extends Component.withWatchables("error", "value") {
bdElements(){
return e("div" {
bdReflectClass:[
this, "error", v => v ? "error" : "",
this, "value", v = v=="" ? "no-value" : ""
],
// other ctorProps and ppFuncs
},
// children, if any
);
}
}
The two CSS classes, namely "error" and "no-value", will be added/removed from ppfOwner.className depending upon the error and value property values of a particular MyClass instance when that instance is rendered.
Since the prop argument is always required, it is possible to omit optional arguments. The following example gives a bdReflectClass ppfArgs with four triples:
// the following ppfArgs has gives three triples with
bdReflectClass:[
this, "p1", v => v ? "p1" : "", // all three args
"p2", "p2", v => v ? "p2" : "", // missing first arg; equivalent to (this, "p1", v => v ? "p1" : "")
this, "p3", // missing last arg; equivalent to (this, "p1", x => x)
"p4"] // missing first and last arg; equivalent to (this, "p1", x => x)
bdTitleNode [post-processing-function]
Description
This description uses some of the terms ppfOwner, ppfTarget, ppfProcId, ppfProc, and ppfArgs; see post-processing-functions for a definition of these terms.
Sets bdDom.titleNode to ppfTarget. See Component.title for details about how a title is reflected.
bdWatch [post-processing-function]
Description
This description uses some of the terms ppfOwner, ppfTarget, ppfProcId, ppfProc, and ppfArgs; see post-processing-functions for a definition of these terms.
bdWatch takes a hash from property, watchProp, to watcher function, watcher. ppfTarget must be a Component and bdWatch connects the watcher to the watchProp by applying ppfTarget.watch to (watchProp, watcher) (see watchHub.watch).
bdWatch defines an optimization that allows watcher to be a string or symbol in which case the actual watcher is computed as ppfOwner[watcher].
CSSStyleDeclaration [type]
Description
A CSS declaration block, that exposes style information and various style-related methods and properties. See CSSStyleDeclaration.
Destroyable [type]
Definition
interface Destroyable {
destroy: () => void;
}
Description
Provides the method destroy(), which terminates the lifetime of some other object, typically a watch or event handle. destroy() may be called multiple times without harm and applying destroy() on an instance that references an object that has already been destroyed by some other means results in a harmless no-op.
Throughout Backdraft, an object that provides Destroyable is created and returned consequent to connecting some event (e.g., eventHub.advise), watch (e.g., watchHub.watch), or similar type of construct (perhaps in client code), thereby providing a means to terminate the connection. For example:
function doOnceOnClick(node, callback){
let h = connect(node, "click", (e) => {
h.destroy();
callback();
});
}
Hash [type]
Definition
interface Hash {
[string]: any
[symbol]: any
}
Description
A plain JavaScript Object used solely as an associative array where the keys may be strings or symbols and the values may be any type.
Posit [type]
Definition
interface Destroyable {
t?: number; // top
b?: number; // bottom
l?: number; // left
r?: number; // right
h?: number; // height
w?: number; // width
maxH?: number; // maxHeight
maxW?: number; // maxWidth
z?: number; // zIndex
}
Description
A plain JavaScript Object that gives position and size values.
Position [type]
Definition
"first" | "last" | "before" | "after" | "only" | "replace"
Description
A string that indicates how to insert a node relative to another node.
Watchable [type]
Definition
toWatchable(target)
Description
See toWatchable.
Watcher [type]
Definition
function(newValue, oldValue, target, prop)
STAR [constant]
Definition
const STAR = Symbol("bd-star")
Description
In addition to the Backdraft STAR export, a reference to STAR is located at WatchableRef.STAR
UNKNOWN_OLD_VALUE [constant]
Definition
const UNKNOWN_OLD_VALUE = {value: "UNKNOWN_OLD_VALUE"};
Description
See watch for a detailed description and example.
In addition to the Backdraft UNKNOWN_OLD_VALUE export, a reference to UNKNOWN_OLD_VALUE is located at WatchableRef.UNKNOWN_OLD_VALUE
eqlComparators [variable]
Definition
let eqlComparators = new Map()
Description
Used by eql to compute equality. Client code should add entries to the map for intelligent equality calculations.
focusManager [variable]
Definition
let focusManager = new class extends eventHub() {/* ... */}
Description
focusManager is a singleton object that signals focus changes through connectable events and provides focus state values through read-only properties. The following properties are defined:
- focusedComponent: the component that currently has the focus
- focusedNode: the DOM node that currently has the focus
- focusedStack: the stack of components (focusedComponent, parent, grand-parent, etc.) that currently have the focus
- previousFocusedComponent: the component that had the focus just before focusedComponent gained the focus
- previousFocusedNode: the DOM node that had the focus just before focusedNode gained the focus
As the focus moves from one component to another, the stack of components from the particular component that holds the DOM node that has the focus to its parent, grand-parent, and so on up through the top-most component changes. This movement of focus can be visualized as popping components off a stack as they lose the focus, and pushing components on a stack as they gain focus; blurComponent and focusComponent signal these pops and pushes respectively. Events are ordered by first signaling all blurred components as they are popped off the stack, then signaling all focused components as the are pushed on the stack, and finally signalling the top-most component with focusedComponent. The following events are defined:
- blurComponent: a component in the focus stack has lost the focus; the event object applied to handlers provides the component that lost the focus at the property component.
- focusComponent: a component has been added to the focus stack; the event object applied to handlers provides the component that gained the focus at the property component.
- focusedComponent: the focusedComponent property changed; the event object applied to handlers provides the component at the property component.
version [constant]
Definition
const version = "2.3.1"
Description
The version number of the Backdraft library.
viewportWatcher [variable]
Definition
let viewportWatcher = new class extends eventHub() {/* ... */}
Description
viewportWatcher is a singleton object that signals document scroll and viewport size changes. The following events are defined:
- scroll: the document was scrolled
- resize: the viewport was resized
collection [property]
Type
Description
Each child component is associated with a single item in collection. See insChild for details.
If the array is watchable, then Collection will ensure that its children are inserted/deleted if/when collection changes size.
constructor
Synopsis
new Collection(kwargs)
kwargs Hash | Collection defines the keywords collection and childType. See Component.constructor for other keywords. |
Description
Creates a new Collection instance.
Keyword argument collection initializes the collection property; see collection.
Keyword argument childType gives the constructor (class) for a component type that is used to create children. See insChild.
insChild [method]
Synopsis
instance.insChild(i) => child
i integer | The index into collection that is associated with the new child. |
child Component | the inserted child |
Description
insChild creates a new component of type kwargs.childType. The constructor arguments
{index: i, mix: {collection: this.collection}}
are provided, where this.collection is the value of collection. Children component types should be designed so they synchronize their presentation/semantics with collection[i]. Collection guarantees that a particular child instance is always associated with the same item in collection.
It is the responsibility of the child to watch for mutations on the particular collection item to which it is associated. If the items in collection mutate during the lifetime of a Collection instance, then it is important the collection be set to a Watchable type.
This is a complete override to Component.insChild; signatures and semantics of Component.insChild; are invalid for Collection.
render [method]
Synopsis
instance.render(callback) => rootDomNode
callback () => (Destroyable | Destroyable[] | void) |
optional if provided, applied after the instance is rendered. Any Destroyable instances returned are applied to Component.ownWhileRendered.
|
rootDomNode DOM node | the root DOM node for the component |
Description
If the instance is already in the rendered state, then no-op; otherwise, apply Component.render, then apply insChild for each item in collection.
protected [namespace]
Description
These properties and methods should only be used within the implementations of subclasses derived from Collection.
collectionIndex [property]
Type
Description
Each CollectionChild component is associated with the data given by this.parent.collection[this.collectionIndex].
collectionItem [property]
Type
Description
Each CollectionChild component is associated with the data given by this.parent.collection[this.collectionIndex].
collectionLength [property]
Type
Description
Each CollectionChild component is associated with the data given by this.parent.collection[this.collectionIndex].
constructor
Synopsis
new CollectionChild(kwargs)
kwargs Hash | CollectionChild defines the keywords parent and collectionIndex. See Component.constructor for other keywords. |
Description
Creates a new CollectionChild instance.
Keyword argument parent initializes the instance's parent. Unlike the normal lifecycle sequence which sets a component's parent after the component is rendered, CollectionChild sets the parent at construction and the parent is immutable. The parent holds the data item to which the particular instance is associated at parent.collection[this.collectionIndex].
Keyword argument collectionIndex gives index into parent.collection of the data item associated with the instance.
protected [namespace]
Description
These properties and methods should only be used within the implementations of subclasses derived from CollectionChild.
static [namespace]
attachedToDoc [property]
Type
Description
true when the instance is rendered and its DOM is a descendant of document.body; otherwise, false.
attachedToDoc reflects bdAttachedToDoc
children [property]
Type
Description
The list of children contained by the instance. Children are only contained when the instance is rendered. If the instance is not rendered or had no children, then children is undefined.className [property]
Type
Description
A sequence of words separated by single spaces that are reflected to bdDom.root.className (see bdDom) when the instance is rendered. The property is maintained and may be mutated whether or not the component is rendered.
When the component is rendered, the concatenation of staticClassName and className is reflected to the DOM root's className attribute, and mutating className causes the DOM root's className attribute to mutate. For example:
class C1 extends Component {
bdElements(){
return e("div");
}
}
// define the static className on the class
C1.className = "base";
let c1 = new C1({});
c1.className; // ""
c1.className = "foo";
c1.render();
c1.className; // "foo"
c1.bdDom.root.className; // "base foo"
c1.addClassName("bar");
c1.className; // "foo bar"
c1.bdDom.root.className; // "base foo bar"
className is initialized during construction and may be mutated at any time directly or through the convenience methods addClassName, removeClassName, toggleClassName. Further, if a className property is provided on the root element returned by bdElements, then the contents of that property is automatically added to the current className value when the component is rendered. Here is an example to illustrate this design.
class C1 extends Component {
bdElements(){
return e("div");
}
}
let c1 = new C1({});
c1.className; // ""
c1.className = "baz";
c1.render();
c1.className; // "baz"
class C2 extends Component {
bdElements(){
return e("div", {className:"foo bar"});
}
}
let c2 = new C2({});
c2.className; // ""
c2.className = "baz";
c1.render();
c1.className; // "foo bar baz"
staticClassName is defined at construction (see constructor) and is immutable during the lifetime of the component; conversely, className is mutable in any life cycle state so long as the component is not destroyed. className does not provide any access--read or write--to staticClassName.
className reflects bdClassName
disabled [property]
Type
Description
false if the instance is enabled; otherwise true. If false/true, the CSS class "bd-disabled" as automatically added/removed to/from className.enabled [property]
Type
Description
true if the instance is enabled; otherwise false. If true/false, the CSS class "bd-disabled" as automatically removed/added from/to className.hasFocus [property]
Type
Description
When a DOM node is the document.activeElement (has the browser focus) and that node is contained in the DOM tree of a component, hasFocus is true for that component and all of its ancestors; hasFocus is false otherwise.
hasFocus reflects bdHasFocus; bdHasFocus is mutated by the private methods bdOnFocus and bdOnBlur which are applied by the focusManager.
id [property]
Type
Description
A unique identifier for the Component instance; reflected to bdDom.root.id (see bdDom) when the instance is rendered.
parent [property]
Type
Description
The Component instance which holds this instance in its children collection (if any); otherwise, undefined.rendered [property]
Type
staticClassName [property]
Type
Description
Component defines a "static className" at construction which may not be mutated during the lifetime of the component. See constructor for details on initialization. When the component is rendered, the concatenation of staticClassName and className is reflected in the className property of the root node of the component's DOM.
tabIndex [property]
Type
Description
The tabIndex value. When rendered, this value is reflected into the tabIndex attribute of the tabIndex node given by bdDom.tabIndexNode.; see bdDom.title [property]
Type
Description
When a component's Element tree (as given by bdElements) is rendered, one node in the tree is designated the "title node". A reference to this node is maintained at bdDom.titleNode (see bdDom). If not explicitly set by some other means, the bdDom.titleNode is taken as bdDom.root.
visible [property]
Type
Description
true if the instance is visible; otherwise false. If true/false, the CSS class "bd-hidden" as automatically removed/added from/to className.addClassName [method]
Synopsis
instance.addClassName(...args) => this
arg string | a component to add |
arg (string | falsey)[] | zero or more components to add |
arg falsey | ignored |
Description
Adds all of the string components provided by args to className. Multiples of the same component are only added once. If a component is provided that already exists in the className, then that component is not added again. For example:
let c = new Component({});
c.className = "foo";
c.className; // => "foo";
c.addClassName("foo");
c.className; // => "foo";
c.addClassName("bar");
c.className; // => "foo bar";
c.addClassName("baz",
false,
["this", 0, false, null],
"bar", "foofoo"
);
c.className; // => "foo bar this foofoo";
constructor
Synopsis
new Component(kwargs)
kwargs ConstructorKeywordArgs | Gives a hash of values from which to initialize instance data. ConstructorKeywordArgs lists the properties consumed by Component; all other properties may be defined by subclasses according to their own semantics. |
Description
Creates a new Component instance. A reference to kwargs is saved at this.kwargs.
If kwargs.id.toString() is a non-empty string, then the instance property id is defined with the value kwargs.id.toString(); otherwise, the instance property id is set to undefined. Note that id is read-only for the lifetime of the component instance and can never be mutated after construction.
Remaining Component-defined instance properties are initialized as follows:
staticClassName | this.constructor.className || "" |
className | "" |
tabIndex | "" |
title | "" |
disabled | false |
enabled | true |
kwargs may define properties that override the default values described above as follows:
kwargs.staticClassName | staticClassName is initialized to kwargs.staticClassName |
kwargs.className | className is initialized to kwargs.className |
kwargs.tabIndex | tabIndex is initialized to kwargs.tabIndex |
kwargs.title | title is initialized to kwargs.title |
kwargs.disabled | disabled is initialized to kwargs.disabled; enabled is initialized to !kwargs.disabled; if both kwargs.disabled and kwargs.enabled are provided, kwargs.disabled wins. |
kwargs.enabled | enabled is initialized to kwargs.enabled; disabled is initialized to !kwargs.enabled; if both kwargs.disabled and kwargs.enabled are provided, kwargs.disabled wins. |
kwargs.postRender | this.postRender is set to kwargs.postRender |
kwargs.mix | for each key in Reflect.ownKeys(kwargs.mix), this[key] is set to kwargs.mix[key]. This allows per-instance overrides of any method or property and/or the ad hoc addition of per-instance methods/properties at construction. |
kwargs.callbacks | for each watchableProp in Reflect.ownKeys(kwargs.callbacks) that is also contained in the array this.constructor.watchables, watchHub.watch is applied to (watchableProp, kwargs.callbacks[watchableProp]) for each eventType in Reflect.ownKeys(kwargs.callbacks) that is also contained in the array this.constructor.events, eventHub.advise is applied to (eventType, kwargs.callbacks[eventType]) see watchables and events |
Subclasses of Component may override or define addition semantics on kwargs as required by the design of the subclass.
Upon construction, an instance is in the unrendered state.
containsClassName [method]
Synopsis
instance.containsClassName(value) => boolean
value string | the component string to test |
Description
this.className must contain the complete component given by value. Consider the following example:
let c = new Component({});
c.className = "foofoo bar baz";
c.containsClassname("foofoo"); // => true
c.containsClassname("foo"); // => false
c.containsClassname("foo bar"); // => false
c.containsClassname("foofoo bar"); // => true
c.containsClassname("bar"); // => true
c.containsClassname("bar baz"); // => true
c.containsClassname("baz bar"); // => false!!
delChild [method]
Synopsis
instance.delChild(child, preserve) => deletedChild
child Component | the child to delete |
preserve boolean |
optional if truthy, then destroy is not applied to the child; otherwise destroy is applied to the child
|
deletedChild Component | the deleted child (if any) |
Description
If the child does not exist in the children collection, then no processing occurs and false is returned; this is not considered an error. Otherwise...
The child's dom root(s) are removed from the instances's DOM tree, false is applied to bdAttachToDoc on the child, and the child is deleted from children. If preserve is true, then the child is returned with no other processing (the child will be in the rendered state). If preserve is false, then destroy is applied to the child and false is returned.
Notice that false is returned in two cases: (1) the child does not exist in the children collection, (2) the child was destroyed. Since a destroyed Component instance has no internal state and cannot be further manipulated, there is no purpose in returning such an instance.
destroy [method]
Synopsis
instance.destroy() => void
Description
Destroy all resources and references owned by the instance, thereby making the instance readily available for garbage collection. In particular, the following is accomplished:
- Unrenders the instance (if rendered) and destroys all resources (DOM nodes, event connections, etc.) acquired during the time the component was rendered.
- Destroys all watchers on instance properties.
- Destroys all handlers on instance events.
- Destroys all Destroyable instances published to own.
- Deletes this.kwargs.
focus [method]
Synopsis
instance.focus() => void
Description
Applies the DOM node method focus() to the node in the component's DOM tree that has a non-empty tabIndex property.
insChild [method]
Synopsis
instance.insChild(element, node, position) => insertedChild
instance.insChild(ComponentSubclass, kwargs, node, position) => insertedChild
instance.insChild(child, node, position) => insertedChild
element Element | child to be inserted is created as new Component({elements:element}) |
node DOM node | string |
optional the reference node to attach the child's root(s); if a string is provided, then node is computed as document.getElementById(node); see position
|
position Position |
optional if missing, defaults to "last" first, last => attach the child's DOM root(s) as the first/last children of node before, after => attach the child's DOM root(s) as before/after siblings of node only => attach the child's DOM root(s) as the sole child of node after removing and destroying any existing children of node replace => attach the child's DOM root(s) in the position that node exists with respect to it's siblings; remove and destroy node |
ComponentSubclass function | a constructor function that creates an instance of Component; child to be inserted is new ComponentSubclass(kwargs) |
kwargs ConstructorKeywordArgs | keyword arguments to be provided to ComponentSubclass during construction; if kwargs is missing, then {} is provided by default |
child instance of Component | the child to be inserted |
insertedChild instance of Component | the child that was inserted |
Description
Creates a new component (if an element or ComponentSubclass was provided), deletes the child from its current parent (if child was provided that has a parent), renders the child (if necessary), inserts the child DOM root(s) into the instance DOM tree, and pushed child into children. If the instance is attached to the document, bdAttachToDoc is applied on the child.
If node is provided, then the DOM root(s) of the new child are inserted with respect to node as give by position; see the description of the position parameter, above.
If node is not provided, then the DOM root(s) of the new child are appended to the first node that exists from the following choices:
- child.bdParentAttachPoint; see bdParentAttachPoint
- this.bdChildrenAttachPoint; see bdChildrenAttachPoint
- this.bdDom.root; see bdDom
Note that an instance must be rendered before attempting to insert children.
own [method]
Synopsis
instance.own(...args) => void
arg Destroyable | Destroyable instance to be destroyed upon component destruction. |
arg (Destroyable | falsey)[] | array of Destroyable instances to be destroyed upon component destruction; any falsey elements are ignored. |
arg falsey | ignored |
Description
own simplifies management of destroyable resources (e.g., the objects returned by watchHub.watch, eventHub.advise, connect) by guaranteeing such resources are destroyed upon component destruction. All methods that return destroyable instances automatically apply those instances to own. Client code can leverage own to manage destroyable resources that are created by machinery outside of Component's implementation. For example:
let child = someParent.insChild(SomeChildType, {/* ... */});
child.watch("someChildProperty", (newValue) => {/* ... */});
child.own(
globalEventGenerator.advise(
"someEvent", child.someMethodThatProcessesSomeEvent.bind(child)
)
);
In the example above, notice that it was not necessary to apply own to the result of child.watch since the design of Component takes care of that task automatically; on the other hand, there is no way for child to know about the destroyable event handler that was connected by globalEventGenerator.advise; therefore, own is called explicitly. Consequent to this application, when child is destroyed, the event handler will automatically be destroyed.
ownWhileRendered [method]
Synopsis
instance.ownWhileRendered(...args) => void
arg Destroyable | Destroyable instance to be destroyed upon component destruction. |
arg (Destroyable | falsey)[] | array of Destroyable instances to be destroyed upon component destruction; any falsey elements are ignored. |
arg falsey | ignored |
Description
ownWhileRendered functions similarly to own except that any Destroyable instances are collected and destroyed between render and unrender.
postRender [method]
Synopsis
instance.postRender() => handles
handles Destroyable |Destroyable[] | see description |
Description
Upon entry to postRender the DOM tree(s) associated with the instance have been created and their root(s) stored at bdDom.root (see bdDom). The default implementation of postRender is a no-op. See render for further details.
Any handles returned by postRender are applied to ownWhileRendered.
removeClassName [method]
Synopsis
instance.removeClassName(...args) => this
arg string | a component to add |
arg (string | falsey)[] | zero or more components to add |
arg falsey | ignored |
Description
Removes all of the string components provided by args from className. No error is generated when className does not contain a target component. Components are broken down to their word atoms before attempting remove, so removing "foo bar" will remove the same components as "bar foo", and either "foo bar" or "bar foo" will remove all components from either "foo bar" or "bar foo".
render [method]
Synopsis
instance.render(callback) => void
callback () => (Destroyable | Destroyable[] | void) |
optional if provided, applied after the instance is rendered. Any Destroyable instances returned are applied to ownWhileRendered.
|
Description
If the instance is already in the rendered state, then no-op; otherwise:
- Generate the Element tree(s) by applying bdElements.
- Create the DOM tree(s) decribed by the Element tree(s) generated in Step 1.
- Store the new tree(s) at bdDom.root (see bdDom).
- Publish the new tree(s) in the Component catalog (see get).
- Apply postRender.
- Apply callback (if provided).
reorderChildren [method]
Synopsis
instance.reorderChildren(children) => void
children Component[] | this.children, reordered as desired |
Description
This method requires all children passed in children have the same DOM node parent for their root DOM nodes. The children root DOM nodes are reordered as given by children.
toggleClassName [method]
Synopsis
instance.toggleClassName(...args) => this
arg string | a component to add |
arg (string | falsey)[] | zero or more components to add |
arg falsey | ignored |
unrender [method]
Synopsis
instance.unrender() => void
Description
If the instance is in the unrendered state, then no-op; otherwise:
- Deletes the Component's tree(s) from the Component catalog (see get):
- Deletes itself by applying delChild on itself to its parent (if any).
- Destroys all of its own children by applying destroy to every child in its children collection.
- Destroys all destroyable objects collected by ownWhileRendered.
protected [namespace]
Description
These properties and methods should only be used by within the implementations of subclasses of Component.
static [namespace]
types [namespace]
children [property]
Type
Description
The children of the Element.
ctorProps [property]
Type
Description
When type gives a Component subclass constructor, ctorProps gives the arguments applied to the constructor. For example, given an Element instance element where element.type===MyClass, the Component instance would be created as follows:
new MyClass(element.ctorProps)
When type gives a DOM node tag, ctroProps gives the (property -> value) pairs used to initialize the actualized node immediately after it is created., For example, given an Element instance element, and a newly created node n, n would be initialized as follows:
Reflect.ownKeys(element.ctorProps).forEach(p => setAttr(n, p, element.ctorProps[p])
ppFuncs [property]
Type
Description
Immediately after the node implied by type is instantiated and initialized by ctorProps, each post-processing-function given by ppFuncs is executed as follows:
Reflect.ownKeys(element.ppFuncs).forEach(ppfProcId =>
getPostProcessingFunction(ppfProcId)(ppfOwner, ppfTarget, ...(element.ppFuncs[ppf]))
)
See post-processing-functions for an explanation of ppfProcId, ppfOwner, and ppfTarget. See getPostProcessingFunction.
type [property]
Type
Description
Describes the type of the actualized node:
- If a string, then type gives an HTML element type, for example "div".
- If a [namespace, tag], then namespace (a string) gives a www.w3.org namespace and tag (a string) gives the element type within that namespace, for example ["http://www.w3.org/2000/svg", "circle"].
- Otherwise type is a subclass of Component constructor.
constructor
Synopsis
new Element(htmlTag, props = {}, ...children)
new Element(namespacedTag, props = {}, ...children)
new Element(component, props = {}, ...children)
htmlTag string | an HTML tag name |
namespacedTag [namespace, tag] |
namespace (a string) is a www.w3.org namespace; tag (a string) gives a element tag within that namespace |
component subclass of Component constructor | component type |
props Hash |
optional, default = {}
The Hash of (property -> value) and (post-processing-function -> arguments) pairs that describe how to initialize ctorProps and ppFuncs.
|
children falsey | Element | children[] |
optional children can be falsey | Element | children[], thereby allowing arbitrarily nested arrays of children.
|
Description
Initializes a new instance.
children is flattened into a single array and all falsey values are removed.
An example of the [namespace, tag] form is ["http://www.w3.org/2000/svg", "circle"]. Backdraft provides svg to avoid this verbose form for the SVG namespace.
props is sifted into ctorProps and ppFuncs: any property p in props such that getPostProcessingFunction(p) returns a function is placed in ppFuncs; otherwise p is placed in ctorProps.
User-defined post-processing-instructions must be added to the Backdraft post-processing-instruction catalog with insPostProcessingFunction and/or replacePostProcessingFunction before creating Element nodes that reference those user-defined post-processing-instructions.
value [property]
Type
Description
Given referenceObject, referenceProp, and formatter at construction, value is returned as follows:
if(referenceProp===WatchableRef.STAR){
return formatter(referenceObject);
}else{
return formatter(referenceObject[referenceProp]);
}
Recall that the formatter defaults to the identify function; see constructor.
constructor
Synopsis
new WatchableRef(referenceObject, referenceProp = STAR, formatter = x => x)
referenceObject watchHub | Watchable | the reference watchable object |
referenceProp string | symbol | STAR |
optional, default = STAR the property within referenceObject to reference
|
formatter function(any) => any |
optional, default = x => x
applied to the reference property value to compute value and applied to newValue/oldValue args before applying watchers
|
Description
Initializes a new WatchableRef instance.
When referenceProp===STAR, value return formatted referenceObject and watchers are applied upon any mutation within referenceObject; see WatchableRef.
destroy [method]
Synopsis
instance.destroy() => void
Description
Destroys all registered watchers. The WatchableRef instance is not dead; other watchers can be connected by applying watch after applying destroy.
watch [method]
Synopsis
instance.watch(watcher) => handle
watcher Watcher | applied when the formatted value of the reference property mutates |
handle Destroyable | Destroyable that terminates the watch |
Description
Connects watcher so that watcher is applied when the formatted value of the reference property mutates. Note carefully that the watcher is applied only if a substantive mutation is detected, taking into account the formatter.
When watch is applied when the reference property is other than STAR, then watchers will be applied to the following arguments:
- newValue: the value of the property after mutation
- oldValue: the value of the property before mutation
- prop: the same as prop provided when watch was applied to connect the watcher
- object: the object to which watch was applied to connect the watcher
When watch is applied when the reference property is STAR, then watchers will be applied to the following arguments:
- newValue: the value of the particular property that mutated, after mutation
- oldValue: UNKNOWN_OLD_VALUE
- prop: an array that lists the property path to the particular watchable property that mutated
- object: the object to which watch was applied to connect the watcher
See example at WatchableRef.
When the following three conditions exist:
- the reference object is an instance of watchHub
- the reference object is not STAR
- referenceObject[referenceProp] is also a watchable
Then, any connected watchers will be signaled if either referenceObject[referenceProp] mutates or some mutation occurs within referenceObject[referenceProp].
static [namespace]
isBdEventHub [property]
Type
Description
Since eventHub is a mixin class (see mixins), instanceof does not function as expected. Instead, isBdEventHub can be used to test if a particular instance defines the eventHub interface. For example:
class SuperClass {};
class MyClass extends eventHub(SuperClass) {}
let test = new MyClass();
test instanceof MyClass; // true;
test instanceof SuperClass; // true;
test instanceof eventHub; // false;
test instanceof EventHub; // false;
test.isBdEventHub; // true
advise [method]
Synopsis
instance.advise(eventType, handler) => handle
instance.advise(eventTypes, handler) => handles[]
instance.advise(hash) => handles[]
event string | symbol | the event to advise |
events [string | symbol] | list of events to advise |
hash Hash (event -> handler) | list of (event -> handler) pairs to advise |
handler function(eventObject) | handler to apply upon event |
handle Destroyable | Destroyable object that can be used to terminate the advise |
handles Destroyable[] | Destroyable objects that can be used to terminate the advise |
Description
The first signature is the only substantive signature, the remaining signatures are syntax sugar discussed at the end.
Given an event object, eo, upon the owning instance applying bdNotify(eo), all handlers previously registered to eventType===eo.type are applied to eo. See example at eventHub.
All signatures return a handle or handles to all advises that were connected. If this implements the method Component.own, then all handles are owned. This ensures the advises are automatically terminated when the owning object is destroyed with Component.destroy
The remaining signatures are syntax sugar:
instance.advise(events, handler)
instance.advise(hash)
are essentially equivalent to
(instance.own || noop)(props.map(p => this.advise(p, handler))
(instance.own || noop)(Reflect.ownKeys(hash).map(p => this.advise(p, hash[p]))
We say "essentially" because the Destroyable objects created with each watch connection are returned to the caller and this detail is not depicted in the code above.
destroyAdvise [method]
Synopsis
instance.destroyAdvise(eventType) => void
eventType string | symbol | undefined | destroy all watchers on a single event (if given); all events, otherwise |
Description
If eventType is provided, than all handlers connected the given event are destroyed; otherwise, all handlers on all events are destroyed.
protected [namespace]
Description
These properties and methods should only be used by within the implementations of subclasses derived from eventHub.
isBdWatchHub [property]
Type
Description
Since watchHub is a mixin class (see mixins), instanceof does not function as expected. Instead, isBdWatchHub can be used to test if a particular instance defines the watchHub interface. For example:
class SuperClass {};
class MyClass extends watchHub(SuperClass) {}
let test = new MyClass();
test instanceof MyClass; // true;
test instanceof SuperClass; // true;
test instanceof WatchHub; // false;
test instanceof watchHub; // false;
test.isBdWatchHub; // true
destroyWatch [method]
Synopsis
instance.destroyWatch(prop) => void
prop string | symbol | undefined |
optional destroy all watchers on a single property (if given)
|
Description
If prop is provided, than all watchers watching the given property are destroyed; otherwise, all watchers on all properties are destroyed.
getWatchableRef [method]
Synopsis
instance.getWatchableRef(prop = STAR, formatter=x => x)
prop string | symbol |
optional, default = STAR the reference property
|
formatter any => any |
optional, default = x => x the formatter used by the WatchableRef instance
|
Description
Syntax sugar:
instance.getWatchableRef(prop, formatter)
is equivalent to
(this.own || noop)(getWatchableRef(this, prop, formatter))
watch [method]
Synopsis
instance.watch(prop = STAR, watcher) => handle
instance.watch(props, watcher) => handles[]
instance.watch(hash) => handles[]
instance.watch(watchable, prop = STAR, watcher) => handle
instance.watch(watchable, props, watcher) => handles[]
instance.watch(watchable, hash) => handles[]
instance.watch(watchable) => handles[]
prop string | symbol |
optional, default = STAR the property to watch
|
props (string | symbol)[] | list of properties to watch |
hash Hash (property -> watcher) | list of (property -> watcher) pairs to watch |
watcher Watcher | watcher to apply upon mutation of prop |
watchable watchHub | Watchable | object to watch |
handle Destroyable | Destroyable object that can be used to terminate the watch |
handles Destroyable[] | Destroyable objects that can be used to terminate the watch |
Description
The first signature is the only substantive signature, the remaining signatures are syntax sugar discussed at the end.
When watch is applied with a prop argument other than STAR, then watchers will be applied to the following arguments:
- newValue: the value of the property after mutation
- oldValue: the value of the property before mutation
- prop: the same as prop provided when watch was applied to connect the watcher
- target: the object to which watch was applied to connect the watcher
When watch is applied with a prop === STAR, then watchers will be applied to the following arguments:
- newValue: the value of the particular property that mutated, after mutation
- oldValue: see UNKNOWN_OLD_VALUE
- prop: an array that lists the property path to the particular watchable property that mutated
- target: the object to which watch was applied to connect the watcher
All signatures return a handle or handles to all watches that were connected. If this implements the method Component.own, then all handles are owned. This ensures the watches are automatically terminated when the owning object is destroyed with Component.destroy
The remaining signatures are syntax sugar:
instance.watch(props, watcher)
instance.watch(hash, watcher)
instance.watch(watchable, prop, watcher)
instance.watch(watchable, props, watcher)
instance.watch(watchable, hash)
instance.watch(watchable)
are essentially equivalent to
(instance.own || noop)(props.map(p => this.watch(p, watcher))
(instance.own || noop)(Reflect.ownKeys(hash).map(p => this.watch(p, hash[p]))
(instance.own || noop)(watch(watchable, props, watcher))
(instance.own || noop)(watch(watchable, hash))
(instance.own || noop)(watch(watchable))
We say "essentially" because the Destroyable objects created with each watch connection are returned to the caller and this detail is not depicted in the code above.
protected [namespace]
Description
These properties and methods should only be used by within the implementations of subclasses derived from eventHub.
bdCollection [protected property]
Type
Description
bdCollection is reflected and mutated by collection.
This is a protected property and is not intended to be accessed by client code.
bdSynchChildren [protected method]
Synopsis
instance.bdSynchChildren() => void
Description
When rendered inserts or deletes children as required so there is a 1-to-1, onto map between the items in collection and children components.
bdCollectionIndex [protected property]
Type
Description
bdCollectionIndex is reflected and mutated by collectionIndex.
This is a protected property and is not intended to be accessed by client code.
bdCollectionItem [protected property]
Type
Description
bdCollectionItem is reflected and mutated by collectionItem.
This is a protected property and is not intended to be accessed by client code.
withWatchables [static method]
Type
Description
CollectionChild.withWatchables is an extension to withWatchables that allows the additional property specifier:
"item: prop [,prop] [,prop] ..."
Each prop, "pname", causes a watchable property "pname" to be implemented on the resulting subclass that reflects the the "pname" property in this.parent.collection[this.collectionIndex].
When CollectionChild.withWatchables is not provided a superclass for its first argument, it assumes CollectionChild.
For example, given a collection defined as follows:
let stats = toWatchable([
{label: "A", value: 100},
{label: "B", value: 100}
]);
withWatchables could be used to defined the class StatChild, a subclass of CollectionChild, that defines the watchable property "value" which reflects the "value" property of the particular item in stats to which a particular StatChild instance is associated as follows:
class StatChild extends CollectionChild.withWatchables("item:value"){
}
The Backdraft Polygraph example uses CollectionChild.withWatchables extensively. You can explore Polygraph in this Pen or load the example directly into your browser.
bdAttachedToDoc [protected property]
Type
Description
bdAttachedToDoc is reflected by attachedToDoc; bdAttachedToDoc is mutated by internal, protected processes, primarily bdAttachToDoc.
This is a protected property and is not intended to be accessed by client code.
bdChildrenAttachPoint [protected property]
Type
Description
Gives the property address within the component instance that holds the DOM node to which the component should append children DOM roots.
This is a protected property and is not intended to be accessed by client code.
bdClassName [protected property]
Type
Description
bdClassName is mutated by bdSetClassName; bdClassName is reflected by className.
This is a protected property and is not intended to be accessed by client code.
bdDisabled [protected property]
Type
bdDom [protected property]
Type
Description
bdDom holds various references/data pertaining to a component's rendering. Component defines the following properties:
- bdDom.root: the root DOM node or nodes of the component's rendering
- bdDom.titleNode: the DOM node to which the title property is reflected (title is reflected to bdDom.titleNode.title)
- bdDom.tabIndexNode: the DOM node to which the tabIndex property is reflected (tabIndex is reflected to bdDom.tabIndexNode.tabIndex)
- bdDom.handles: the list of Destroyable objects that have been ownWhileRendered.
Subclasses of Component are free to add to bdDom so long as they do not step on already-existing definitions.
This is a protected property and is not intended to be accessed by client code.
bdHasFocus [protected property]
Type
bdParent [protected property]
Type
bdParentAttachPoint [protected property]
Type
Description
Gives the property address within a prospective parent object that holds the DOM node to which the component's DOM root should be appended.
This is a protected property and is not intended to be accessed by client code.
bdTabIndex [protected property]
Type
Description
bdTabIndex is reflected by tabIndex
This is a protected property and is not intended to be accessed by client code.
bdTitle [protected property]
Type
Description
bdTitle is reflected by title
This is a protected property and is not intended to be accessed by client code.
bdAdopt [protected method]
Synopsis
instance.bdAdopt(child) => void
child Component | the child that is being adopted |
Description
- Push the child into children.
- Mutate the child's parent property to reflect the new parent.
- If this.attachedToDoc===true, than apply true to bdAttachToDoc on the child.
bdAttachToDoc [protected method]
Synopsis
instance.bdAttachToDoc(attach) => boolean
attach boolean | the new value of bdAttachedToDoc |
Description
If attach!==bdAttachedToDoc, then mutate bdAttachedToDoc to the new value, recursively apply to all children, and return true; otherwise, no-op and return false.
bdElements [protected method]
Description
bdElements is applied immediately before rendering the instance to create the Element tree(s) that describe the structure, properties, and connections of the DOM tree(s) associated with the instance. Typically, classes derived from Component override bdElements to define a particular structure as required by the semantics and design of the subclass. bdElements may also be overridden on a per-instance basis by providing kwargs.elements at construction or setting the instance bdElement method explicitly.
The default implementation generates the Element tree new Element("div", {}).
bdOnBlur [protected method]
Synopsis
instance.bdOnBlur() => void
Description
This protected method is applied by the focusManager when the component has the focus and then the focus moves to a component other than itself or one if its descendants. The default processing sets bdHasFocus to false and executes this.removeClass("bd-focused").
This is a protected method and is not intended to be accessed by client code.
bdOnFocus [protected method]
Synopsis
instance.bdOnFocus() => void
Description
This protected method is applied by the focusManager when a DOM node within the component's DOM or one of its descendants' DOM receives the focus. The default processing sets bdHasFocus to true and executes this.addClassName("bd-focused").
This is a protected method and is not intended to be accessed by client code.
bdSetClassName [protected method]
Synopsis
instance.bdSetClassName(newValue, oldValue) => void
Description
Mutates bdClassName and signals all className watchers.
This is a protected method and is not intended to be accessed by client code.
events [static property]
Type
Description
As a best practice, all Backdraft-defined classes derived from eventHub define the list of event names which the class may notify. This list is a static property located at events.
watchables [static property]
Type
Description
As a best practice, all Backdraft-defined classes derived from watchHub define the list of property names which are watchabe. This list is a static property located at watchables.
get [static method]
Synopsis
Component.get(node) => Component | undefined
Description
Given a DOM node that is the root of a Component instance, returns that instance; undefined otherwise.
withWatchables [static method]
Type
Description
Syntax sugar:
class MyClass extends Component.withWatchables(propertyDefs) { /* ... */ }
is equivalent to
class MyClass extends withWatchables(Component, propertyDefs) { /* ... */ }
It's not shorter, but it reads better.
ConstructorKeywordArgs [type]
Definition
interface ConstructorKeywordArgs {
id?: any truthy value convertible to string;
staticClassName?: string;
className?: string | string[];
tabIndex?: number | falsey;
title?: string;
disabled?: boolean;
enabled?: boolean;
elements?: Element | Element[] | () => (Element | Element[]);
postRender?: () => (Destroyable | Destroyable[] | void);
overrides?: Hash;
callbacks?: Hash;
mix?: Hash;
[string]: any
[symbol]: any
}
Description
See Component.constructor.UNKNOWN_OLD_VALUE [static property]
Type
Description
See UNKNOWN_OLD_VALUE.
bdNotify [protected method]
Synopsis
instance.bdNotify(eo) => void
eo Object | any object with the property type |
Description
All handlers previously registered to event type eo.type are applied to eo.
bdMutate [protected method]
Synopsis
instance.bdMutate(publicName, privateName, newValue) => void
instance.bdMutate(list) => void
publicName string | symbol | a property name registerable with watch |
privateName string | symbol | the property name that holds the actual value |
newValue any | the value to assign to the property |
list array of [publicName, privateName, newValue] | see description |
Description
bdMutate compares this[privateName] to newValue using eql. If a mutation is detected, this[privateName] is set to newValue and all watchers registered on publicName are applied via bdMutateNotify.
A list of triples is used to give the illusion that several property mutations occur atomically. First, each property is mutated, then, each mutation is notified. This algorithm results in applying watchers after all mutations have been completed, thereby preventing watchers from being applied when the underlying object is in a possibly-illegal state.
bdMutateNotify [protected method]
Synopsis
instance.bdMutateNotify(prop, newValue, oldValue) => void
instance.bdMutateNotify(list) => void
prop string | symbol | a property name registerable with watch |
newValue any | the value of the mutated property, after the mutation |
oldValue any | the value of the mutated property, before the mutation |
list array of [publicName, newValue, oldValue] | see description |
Description
Applies (newValue, oldValue, this, prop) to all watchers connected via watch to the property prop. A list of triples is used to signal several mutations at once:
this.bdMutateNotify(list)
is equivalent to
list.forEach(item => this.bdMutateNotify(...item))
This signature can be used to give the illusion that several different properties mutated atomically. See Watchable Properties for details.