Skip to content

Commit

Permalink
Literal.fromTemplate and .fromFragment
Browse files Browse the repository at this point in the history
  • Loading branch information
stephband committed Jul 27, 2024
1 parent f8c3d62 commit aab80e9
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 104 deletions.
36 changes: 17 additions & 19 deletions element/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function assignProperty(properties, entry) {
return properties;
}

export default function LiteralElement(tag, lifecycle = {}, properties = {}, consts = {}) {
export default function LiteralElement(tag, lifecycle = {}, properties = {}) {
if (window.DEBUG && typeof src === 'string' && !/^#/.test(src)) {
console.error('TODO: Support external templates?');
// requestTemplate(value).then((template) => {
Expand All @@ -100,16 +100,27 @@ export default function LiteralElement(tag, lifecycle = {}, properties = {}, con
if (window.DEBUG) document.head.appendChild(create('comment', ' Templates for ' + name));
document.head.append.apply(document.head, templates);

const life = {
// Assemble properties
const props = properties ?
entries(properties).reduce(assignProperty, {}) :
{} ;

const message = window.DEBUG ?
'literal element stephen.band/literal/element/' :
//+ (keys(consts).length ? '\n Imports ' + keys(scope).join(', ') : '') :
'' ;

// tag, lifecycle, properties, stylesheet, message
return element(tag, {
// DEBUG stylesheet for in-DOM prints of errors and logs
shadow: window.DEBUG ? '<link rel="stylesheet" href="../module.css">' : '',

construct: function(shadow, internals) {
// Render data
// Data object
internals.object = {};

// template, parent, consts, data, options
const renderer = new Literal(template, this, assign({ host: this, shadow, internals }, consts), undefined);
const consts = { host: this, shadow, internals };
const renderer = Literal.fromTemplate(template, this, consts);
shadow.appendChild(renderer.content);

// Call lifecycle.construct()
Expand Down Expand Up @@ -152,18 +163,5 @@ export default function LiteralElement(tag, lifecycle = {}, properties = {}, con
disable: lifecycle.disable && function disable(shadow, internals) { lifecycle.disable.call(this, shadow, internals, internals.data); },
reset: lifecycle.reset && function reset(shadow, internals) { lifecycle.reset.call(this, shadow, internals, internals.data); },
restore: lifecycle.restore && function restore(shadow, internals) { lifecycle.restore.call(this, shadow, internals, internals.data); }
};

// Assemble properties
const props = properties ?
entries(properties).reduce(assignProperty, {}) :
{} ;

const message = window.DEBUG ?
'literal element stephen.band/literal/element/'
+ (keys(consts).length ? '\n Imports ' + keys(scope).join(', ') : '') :
'' ;

// tag, lifecycle, properties, stylesheet, message
return element(tag, life, props, null, message);
}, props, null, message);
}
4 changes: 2 additions & 2 deletions literal-html/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Signal from '../../fn/modules/signal.js';
import element, { getInternals as Internals } from '../../dom/modules/element.js';
import assignDataset from '../modules/dom/assign-dataset.js';
import requestData from '../modules/request-data.js';
import Template from '../modules/template.js';
import DOMRenderer from '../modules/template.js';
import { printError } from '../modules/scope/print.js';

const assign = Object.assign;
Expand All @@ -32,7 +32,7 @@ export default element('<template is="literal-html">', {
internals.initialised = false;
internals.pushed = false;
internals.data = Signal.of();
internals.renderer = new Template(this, this.parentElement);
internals.renderer = DOMRenderer.fromTemplate(this, this.parentElement);
},

connect: function(shadow) {
Expand Down
8 changes: 4 additions & 4 deletions modules/compile/compile-attribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const assign = Object.assign;
compileAttributes(array, element, attribute, path, options[, debug])
**/

export default function compileAttribute(array, element, attribute, path, options, template) {
export default function compileAttribute(array, element, attribute, path, options, debug) {
const source = attribute.value;
if (!isLiteralString(source)) { return; }

Expand All @@ -46,10 +46,10 @@ export default function compileAttribute(array, element, attribute, path, option
property === 'checked' ? CheckedRenderer :
typeof element[property] === 'boolean' ? BooleanRenderer :
typeof element[property] === 'object' && element[property].add && element[property].remove ? TokensRenderer :
AttributeRenderer :
AttributeRenderer ;
AttributeRenderer :
AttributeRenderer ;

const target = { template, path, name, source, upgradeable, Renderer };
const target = { path, name, source, Renderer, upgradeable, debug };

if (window.DEBUG) {
const code = truncate(64, '<'
Expand Down
6 changes: 3 additions & 3 deletions modules/compile/compile-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,17 @@ const compileNode = overload((targets, node) => toType(node), {
return targets;
},

'text': (targets, node, path, options, template) => {
'text': (targets, node, path, options, debug) => {
const string = node.nodeValue;
if (!isLiteralString(string)) return targets;

const source = decode(string);
const target = {
template,
path,
name: indexOf(node),
source,
Renderer: TextRenderer
Renderer: TextRenderer,
debug
};

if (window.DEBUG) {
Expand Down
6 changes: 3 additions & 3 deletions modules/renderer/renderer-text.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Data from '../../../fn/modules/data.js';
import { isCommentNode, isElementNode, isFragmentNode, isTextNode } from '../../../dom/modules/node.js';
import include from '../scope/include.js';
import deleteRange from '../dom/delete-range.js';
import Template from '../template.js';
import DOMRenderer from '../template.js';
import print, { printError } from '../scope/print.js';
import toText from './to-text.js';
import Renderer, { stats } from './renderer.js';
Expand Down Expand Up @@ -55,7 +55,7 @@ function objectToContents(state, object, i) {

// If object is not a node or renderer, append to string. Array.isArray()
// does return true for a proxy of an array.
if (!(object instanceof Template) && !(object instanceof Node) && !Array.isArray(object)) {
if (!(object instanceof DOMRenderer) && !(object instanceof Node) && !Array.isArray(object)) {
state.string += toText(object);
return i;
}
Expand Down Expand Up @@ -92,7 +92,7 @@ function objectToContents(state, object, i) {
}

// Object is a freshly rendered Literal Template
if (object instanceof Template) {
if (object instanceof DOMRenderer) {
contents[++i].before(toContent(object));
if (window.DEBUG) ++stats.add;
contents.splice(i, 0, object);
Expand Down
15 changes: 8 additions & 7 deletions modules/scope/include.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,19 @@ ${ data.array.map(include('#list-item')) }

import Data from '../../../fn/modules/data.js';
import getById from '../dom/get-by-id.js';
import Template from '../template.js';
import Literal from '../template.js';
import requestTemplate from '../request-template.js';
import requestData from '../request-data.js';

function pipe(template, data, element, consts, options) {
const renderer = new Template(template, element, consts, options);
function pipe(template, data, element, consts) {
const renderer = Literal.fromTemplate(template, element, consts, data);
//const renderer = new Template(template, element, consts, options);
data.each((data) => renderer.push(data));
renderer.done(data);
return renderer;
}

export default function include(src, data, element, consts, options) {
export default function include(src, data, element, consts) {
// Operate on target to be sure we are not registering gets in
// parent renderer's signal
const object = Data.objectOf(data);
Expand All @@ -45,7 +46,7 @@ export default function include(src, data, element, consts, options) {

// Support JSON or module URLs
if (dataRequest) {
return dataRequest.then((data) => new Template(template, element, consts, data, options));
return dataRequest.then((data) => Literal.fromTemplate(template, element, consts, data));
}

// Support a stream of data
Expand All @@ -54,7 +55,7 @@ export default function include(src, data, element, consts, options) {
}

// Support object or ... ?
return new Template(template, element, consts, object, options);
return Literal.fromTemplate(template, element, consts, data);
}

// Template is external to document
Expand All @@ -72,7 +73,7 @@ export default function include(src, data, element, consts, options) {

return Promise
.all([templateRequest, dataRequest])
.then(([template, data]) => new Template(template, element, consts, data, options));
.then(([template, data]) => Literal.fromTemplate(template, element, consts, data));
}


125 changes: 59 additions & 66 deletions modules/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,49 +83,88 @@ function removeRange(first, last, fragment) {
fragment.appendChild(dom);
}

export class LiteralDOM {
export default class LiteralDOM {
static compile(fragment, options, src) {
if (window.DEBUG) {
groupCollapsed('compile', src, 'yellow');
const targets = compileNode(fragment, options, src);
groupEnd();
return targets;
}

return compileNode(fragment, options);
}

static isTemplate(object) {
return object instanceof LiteralRenderer;
}

static of(html) {
return LiteralRenderer.from(create('template', html));
}

static fromFragment(fragment, identifier, element, consts = {}, data, options) {
const compiled = cache[identifier] || (
cache[identifier] = LiteralDOM.compile(fragment, options, identifier)
);

// fragment, targets, element, consts, data, options
return new LiteralDOM(fragment.cloneNode(true), compiled, element, consts, data, options);
}

static fromTemplate(template, element, consts = {}, data) {
const id = identify(template, 'literal-');
const fragment = template.content;

const options = {
nostrict: template.hasAttribute && template.hasAttribute('nostrict')
};

return LiteralDOM.fromFragment(fragment, '#' + id, element, consts, data, options);
}

#first;
#last;
#data;

constructor(content, targets, parent = template.parentElement, consts = {}, data, options = defaults) {
const children = content.childNodes;
// fragment, targets, element, consts, data, options
constructor(fragment, targets, parent = template.parentElement, consts = {}, data, options = defaults) {
const children = fragment.childNodes;

// The first node may change. The last node is always the last node.
this.#data = Signal.of(Data.objectOf(data));
this.#first = children[0];
this.#last = children[children.length - 1];

this.content = content;
this.element = parent;
this.consts = consts;
this.contents = targets
this.#data = Signal.of(Data.objectOf(data));
this.#first = children[0];
this.#last = children[children.length - 1];
this.content = fragment;
this.element = parent;
this.consts = consts;
this.contents = targets
// We must find targets in cloned content
.map(this.#toRendererParams, this)
.map(this.#toCompiled, this)
// before we create renderers for them, as renderers may mutate the DOM
.map(this.#toRenderer, this);
}

#toRendererParams(target) {
const { path, name, literal, message, template } = target;
#toCompiled(compiled) {
const { path, name } = compiled;

// Where `.path` exists find the element at the end of the path
const element = path ? getElement(path, this.content) : this.element ;

// Text renderer expects a text node that must always come from the
// cloned content fragment
const n = typeof name === 'number' ?
const node = typeof name === 'number' ?
path ? element.childNodes[name] :
this.content.childNodes[name] :
name;

// Parameters for Renderer.create():
// signal, literal, consts, element, nameOrNode
return [this.#data, literal, this.consts, element, n, target];
// Parameters for new Renderer()
return { element, node, compiled };
}

#toRenderer(parameters) {
const renderer = Renderer.create(...parameters);
#toRenderer({ element, node, compiled }) {
const { Renderer, literal } = compiled;
const renderer = new Renderer(this.#data, literal, this.consts, element, node, compiled);
this.done(renderer);
return renderer;
}
Expand Down Expand Up @@ -255,49 +294,3 @@ assign(LiteralDOM.prototype, {

done: Renderer.prototype.done
});

export default class LiteralRenderer extends LiteralDOM {
static isTemplate(object) {
return object instanceof LiteralRenderer;
}

static of(html) {
return LiteralRenderer.from(create('template', html));
}

static from(template, parent) {
const id = identify(template, 'literal-');
const fragment = template.content;
const compiled = cache[id]
|| (cache[id] = LiteralRenderer.compile(fragment, options, '#' + id));

return new LiteralDOM(compiled, fragment.cloneNode(true), parent = template.parentElement);
}

static compile(fragment, options, src) {
let targets;

if (window.DEBUG) {
groupCollapsed('compile', src, 'yellow');
targets = compileNode(fragment, options, src);
groupEnd();
}
else {
targets = compileNode(fragment, options);
}

return targets;
}

constructor(template, parent = template.parentElement, consts = {}, data, o = defaults) {
const id = identify(template, 'literal-');
const options = assign({}, o, {
nostrict: template.hasAttribute && template.hasAttribute('nostrict')
});

const compiled = cache[id]
|| (cache[id] = LiteralRenderer.compile(template.content, options, '#' + id));

super(template.content.cloneNode(true), compiled, parent, consts, data, options);
}
}

0 comments on commit aab80e9

Please sign in to comment.