closure-builder
Version:
Simple Closure, Soy and JavaScript Build system
98 lines (86 loc) • 3.48 kB
text/typescript
/**
* @fileoverview
* Contains types and objects necessary for Soy-Idom runtime.
*/
import {assert} from 'goog:goog.asserts'; // from //javascript/closure/asserts
import SanitizedContentKind from 'goog:goog.soy.data.SanitizedContentKind'; // from //javascript/closure/soy:data
import {IjData} from 'goog:soy'; // from //javascript/template/soy:soy_usegoog_js
import {IdomFunctionMembers} from 'goog:soydata'; // from //javascript/template/soy:soy_usegoog_js
import * as incrementaldom from 'incrementaldom'; // from //third_party/javascript/incremental_dom:incrementaldom
import {IncrementalDomRenderer} from './api_idom';
/** Function that executes Idom instructions */
export type PatchFunction = (a?: {}) => void;
/** Function that executes before a patch and determines whether to proceed. */
export type SkipHandler = <T>(prev: T, next: T) => boolean;
/** Base class for a Soy element. */
export abstract class SoyElement<TData extends {}|null, TInterface extends {}> {
// Node in which this object is stashed.
private node: HTMLElement|null = null;
private skipHandler:
((prev: TInterface, next: TInterface) => boolean)|null = null;
constructor(protected data: TData, protected ijData?: IjData) {}
/**
* Patches the current dom node.
*/
render() {
assert(this.node);
incrementaldom.patchOuter(this.node!, () => {
// If there are parameters, they must already be specified.
this.renderInternal(new IncrementalDomRenderer(), this.data!, true);
});
}
/**
* Stores the given node in this element object. Invoked (in the
* generated idom JS) when rendering the open element of a template.
*/
protected setNodeInternal(node: HTMLElement|undefined) {
/**
* This is null because it is possible that no DOM has been generated
* for this Soy element
* (see http://go/soy/reference/velog#the-logonly-attribute)
*/
if (!node) {
return;
}
this.node = node;
// tslint:disable-next-line:no-any
(node as any).__soy = this;
}
setSkipHandler(skipHandler: (prev: TInterface, next: TInterface) => boolean) {
assert(!this.skipHandler, 'Only one skip handler is allowed.');
this.skipHandler = skipHandler;
}
/**
* Makes idom patch calls, inside of a patch context.
* This returns true if the skip handler runs (after initial render) and
* returns true.
*/
protected renderInternal(
renderer: IncrementalDomRenderer, data: TData,
ignoreSkipHandler = false) {
const newNode = new (
this.constructor as
{new (a: TData): SoyElement<TData, TInterface>})(data);
if (!ignoreSkipHandler && this.node && this.skipHandler &&
this.skipHandler(
this as unknown as TInterface, newNode as unknown as TInterface)) {
this.data = newNode.data;
// This skips over the current node.
renderer.alignWithDOM(
this.node.localName, incrementaldom.getKey(this.node) as string);
return true;
}
this.data = newNode.data;
return false;
}
}
/**
* Type for transforming idom functions into functions that can be coerced
* to strings.
*/
export interface IdomFunction extends IdomFunctionMembers {
(idom: IncrementalDomRenderer): void;
contentKind: SanitizedContentKind;
toString: (renderer?: IncrementalDomRenderer) => string;
toBoolean: () => boolean;
}