Skip to content

Commit

Permalink
refactor(hdom): rename HDOMOps => HDOMImplementation, add docs
Browse files Browse the repository at this point in the history
- rename `__ops` attrib => `__impl`
  • Loading branch information
postspectacular committed Sep 10, 2018
1 parent 525d90d commit cc73c76
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 35 deletions.
93 changes: 82 additions & 11 deletions packages/hdom/src/api.ts
Expand Up @@ -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<T> {
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<T> {
/**
* 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;
42 changes: 21 additions & 21 deletions packages/hdom/src/diff.ts
Expand Up @@ -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,
Expand All @@ -21,7 +21,7 @@ const isArray = isa.isArray;
const isString = iss.isString;
const max = Math.max;

const DEFAULT_OPS: HDOMOps<any> = {
const DEFAULT_IMPL: HDOMImplementation<any> = {
createTree: createDOM,
getChild,
replaceChild,
Expand Down Expand Up @@ -49,25 +49,25 @@ const DEFAULT_OPS: HDOMOps<any> = {
* @param root
* @param prev previous tree
* @param curr current tree
* @param ops hdom implementation
* @param impl hdom implementation
*/
export const diffElement = <T>(
root: T,
prev: any[],
curr: any[],
ops: HDOMOps<T> = DEFAULT_OPS) =>
_diffElement(ops, root, prev, curr, 0);
impl: HDOMImplementation<T> = DEFAULT_IMPL) =>
_diffElement(impl, root, prev, curr, 0);

const _diffElement = <T>(
ops: HDOMOps<T>,
impl: HDOMImplementation<T>,
parent: T,
prev: any[],
curr: any[],
child: number) => {

if (curr[1].__diff === false) {
releaseDeep(prev);
ops.replaceChild(parent, child, curr);
impl.replaceChild(parent, child, curr);
return;
}
// TODO use optimized equiv?
Expand All @@ -76,7 +76,7 @@ const _diffElement = <T>(
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;
Expand All @@ -88,14 +88,14 @@ const _diffElement = <T>(
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 = (<any>prev).__release) && val !== (<any>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;
Expand All @@ -119,30 +119,30 @@ const _diffElement = <T>(
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]++;
}
Expand All @@ -157,33 +157,33 @@ const _diffElement = <T>(
}
};

const diffAttributes = <T>(ops: HDOMOps<T>, el: T, prev: any, curr: any) => {
const diffAttributes = <T>(impl: HDOMImplementation<T>, 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];
}
}
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);
}
};

Expand Down
6 changes: 3 additions & 3 deletions packages/hdom/src/dom.ts
Expand Up @@ -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;
Expand All @@ -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 (<HDOMOps<any>>attribs.__ops).createTree(parent, tag, insert);
if (attribs.__impl) {
return (<HDOMImplementation<any>>attribs.__impl).createTree(parent, tag, insert);
}
const el = createElement(parent, t, attribs, insert);
if ((<any>tag).__init) {
Expand Down

0 comments on commit cc73c76

Please sign in to comment.