From cc73c7698156a9810d44a3af58f72c68869639fb Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Mon, 10 Sep 2018 19:55:48 +0100 Subject: [PATCH] refactor(hdom): rename HDOMOps => HDOMImplementation, add docs - rename `__ops` attrib => `__impl` --- packages/hdom/src/api.ts | 93 ++++++++++++++++++++++++++++++++++----- packages/hdom/src/diff.ts | 42 +++++++++--------- packages/hdom/src/dom.ts | 6 +-- 3 files changed, 106 insertions(+), 35 deletions(-) diff --git a/packages/hdom/src/api.ts b/packages/hdom/src/api.ts index e78a255488..5e1c5b3f29 100644 --- a/packages/hdom/src/api.ts +++ b/packages/hdom/src/api.ts @@ -52,18 +52,89 @@ export interface HDOMOpts { } /** - * This interface defines the underlying DOM update operations used by - * `diffElement()`. See `DEFAULT_OPS` (diff.ts) for the default - * implementations. + * This interface defines the underlying target update operations used + * by `diffElement()` and `createDOM()`. It allows thi.ng/hdom to be + * used as general purpose tree definition & differential update + * mechanism, rather than being restricted to only work with an HTML + * DOM. See `DEFAULT_IMPL` (diff.ts) for the default implementations + * dealing with the latter. Note: Depending on use case and tree + * configuration, not all of these methods are required. + * + * Custom element-local implementations can also be provided via the + * special `__impl` hdom element/component attribute. In this case the + * element itself and all of its children will be processed with those + * custom operations. */ -export interface HDOMOps { - createTree(element: T, tree: any, insert?: number): T | T[]; - getChild(element: T, child: number): T; - removeChild(element: T, child: number); - replaceChild(element: T, child: number, newTree: any); - setAttrib(element: T, id: string, value: any, attribs?: any); - removeAttribs(element: T, attribs: string[], prevAttribs: any); - setContent(element: T, value: any); +export interface HDOMImplementation { + /** + * Realizes the given hdom tree in the target below the `parent` + * node, e.g. in the case of the browser DOM, creates all required + * DOM elements encoded by the hdom tree. If `parent` is null the + * result tree won't be attached to any parent. See `createDOM()` + * for further details. If `insert` is given, the new elements will + * be inserted at given child index. + * + * @param parent + * @param tree + * @param insert + */ + createTree(parent: T, tree: any, insert?: number): T | T[]; + /** + * Retrieves child of `parent` node at index `i`. + * + * @param parent + * @param i + */ + getChild?(parent: T, i: number): T; + /** + * Removes the child of `parent` at index `i` in the target. + * + * @param parent + * @param i + */ + removeChild?(parent: T, i: number); + /** + * A (potentially) optimized version of these 2 calls: + * + * ``` + * impl.removeChild(parent, child); + * impl.createTree(parent, child, newTree); + * ``` + * + * @param parent + * @param child + * @param newTree + */ + replaceChild?(parent: T, child: number, newTree: any); + /** + * Sets the given attribute `id` to new `value`. Note: `value` + * itself can be a function and if so, the default behavior is to + * call this function with also provided `attribs` object to allow + * it to produce a derived value. See `setAttrib()` (dom.ts) for + * details. + * + * @param element + * @param id + * @param value + * @param attribs + */ + setAttrib?(element: T, id: string, value: any, attribs?: any); + /** + * Removes given `attribs` from target `element`. The attributes + * from the previous tree are provided for reference (e.g. to be + * able to remove DOM event listeners). + * + * @param element + * @param attribs + * @param prevAttribs + */ + removeAttribs?(element: T, attribs: string[], prevAttribs: any); + /** + * Sets target `element`'s text/body content. + * @param element + * @param value + */ + setContent?(element: T, value: any); } export const DEBUG = false; diff --git a/packages/hdom/src/diff.ts b/packages/hdom/src/diff.ts index 158290fe73..c9907f35ef 100644 --- a/packages/hdom/src/diff.ts +++ b/packages/hdom/src/diff.ts @@ -6,7 +6,7 @@ import { diffArray } from "@thi.ng/diff/array"; import { diffObject } from "@thi.ng/diff/object"; import { equiv } from "@thi.ng/equiv"; -import { HDOMOps } from "./api"; +import { HDOMImplementation } from "./api"; import { createDOM, getChild, @@ -21,7 +21,7 @@ const isArray = isa.isArray; const isString = iss.isString; const max = Math.max; -const DEFAULT_OPS: HDOMOps = { +const DEFAULT_IMPL: HDOMImplementation = { createTree: createDOM, getChild, replaceChild, @@ -49,17 +49,17 @@ const DEFAULT_OPS: HDOMOps = { * @param root * @param prev previous tree * @param curr current tree - * @param ops hdom implementation + * @param impl hdom implementation */ export const diffElement = ( root: T, prev: any[], curr: any[], - ops: HDOMOps = DEFAULT_OPS) => - _diffElement(ops, root, prev, curr, 0); + impl: HDOMImplementation = DEFAULT_IMPL) => + _diffElement(impl, root, prev, curr, 0); const _diffElement = ( - ops: HDOMOps, + impl: HDOMImplementation, parent: T, prev: any[], curr: any[], @@ -67,7 +67,7 @@ const _diffElement = ( if (curr[1].__diff === false) { releaseDeep(prev); - ops.replaceChild(parent, child, curr); + impl.replaceChild(parent, child, curr); return; } // TODO use optimized equiv? @@ -76,7 +76,7 @@ const _diffElement = ( return; } const edits = delta.linear; - const el = ops.getChild(parent, child); + const el = impl.getChild(parent, child); let i: number; let j: number; let idx: number; @@ -88,14 +88,14 @@ const _diffElement = ( if (edits[0][0] !== 0 || prev[1].key !== curr[1].key) { // DEBUG && console.log("replace:", prev, curr); releaseDeep(prev); - ops.replaceChild(parent, child, curr); + impl.replaceChild(parent, child, curr); return; } if ((val = (prev).__release) && val !== (curr).__release) { releaseDeep(prev); } if (edits[1][0] !== 0) { - diffAttributes(ops, el, prev[1], curr[1]); + diffAttributes(impl, el, prev[1], curr[1]); } const equivKeys = extractEquivElements(edits); const numEdits = edits.length; @@ -119,30 +119,30 @@ const _diffElement = ( eq = equivKeys[k]; k = eq[0]; // DEBUG && console.log(`diff equiv key @ ${k}:`, prev[k], curr[eq[2]]); - _diffElement(ops, el, prev[k], curr[eq[2]], offsets[k]); + _diffElement(impl, el, prev[k], curr[eq[2]], offsets[k]); } else { idx = e[1]; // DEBUG && console.log("remove @", offsets[idx], val); releaseDeep(val); - ops.removeChild(el, offsets[idx]); + impl.removeChild(el, offsets[idx]); for (j = prevLength; j >= idx; j--) { offsets[j] = max(offsets[j] - 1, 0); } } } else if (isString(val)) { - ops.setContent(el, ""); + impl.setContent(el, ""); } // element added/inserted? } else if (status === 1) { if (isString(val)) { - ops.setContent(el, val); + impl.setContent(el, val); } else if (isArray(val)) { k = val[1].key; if (k === undefined || (k && equivKeys[k][0] === undefined)) { idx = e[1]; // DEBUG && console.log("insert @", offsets[idx], val); - ops.createTree(el, val, offsets[idx]); + impl.createTree(el, val, offsets[idx]); for (j = prevLength; j >= idx; j--) { offsets[j]++; } @@ -157,19 +157,19 @@ const _diffElement = ( } }; -const diffAttributes = (ops: HDOMOps, el: T, prev: any, curr: any) => { +const diffAttributes = (impl: HDOMImplementation, el: T, prev: any, curr: any) => { let i, e, edits; const delta = diffObject(prev, curr); - ops.removeAttribs(el, delta.dels, prev); + impl.removeAttribs(el, delta.dels, prev); let value = SEMAPHORE; for (edits = delta.edits, i = edits.length; --i >= 0;) { e = edits[i]; const a = e[0]; if (a.indexOf("on") === 0) { - ops.removeAttribs(el, [a], prev); + impl.removeAttribs(el, [a], prev); } if (a !== "value") { - ops.setAttrib(el, a, e[1], curr); + impl.setAttrib(el, a, e[1], curr); } else { value = e[1]; } @@ -177,13 +177,13 @@ const diffAttributes = (ops: HDOMOps, el: T, prev: any, curr: any) => { for (edits = delta.adds, i = edits.length; --i >= 0;) { e = edits[i]; if (e !== "value") { - ops.setAttrib(el, e, curr[e], curr); + impl.setAttrib(el, e, curr[e], curr); } else { value = curr[e]; } } if (value !== SEMAPHORE) { - ops.setAttrib(el, "value", value, curr); + impl.setAttrib(el, "value", value, curr); } }; diff --git a/packages/hdom/src/dom.ts b/packages/hdom/src/dom.ts index 5b88213e9c..5780c4d1d5 100644 --- a/packages/hdom/src/dom.ts +++ b/packages/hdom/src/dom.ts @@ -4,7 +4,7 @@ import * as isi from "@thi.ng/checks/is-iterable"; import * as iss from "@thi.ng/checks/is-string"; import { SVG_NS, SVG_TAGS } from "@thi.ng/hiccup/api"; import { css } from "@thi.ng/hiccup/css"; -import { HDOMOps } from "./api"; +import { HDOMImplementation } from "./api"; const isArray = isa.isArray; const isFunction = isf.isFunction; @@ -31,8 +31,8 @@ export const createDOM = (parent: Element, tag: any, insert?: number) => { return createDOM(parent, t.apply(null, tag.slice(1))); } const attribs = tag[1]; - if (attribs.__ops) { - return (>attribs.__ops).createTree(parent, tag, insert); + if (attribs.__impl) { + return (>attribs.__impl).createTree(parent, tag, insert); } const el = createElement(parent, t, attribs, insert); if ((tag).__init) {