Skip to content

Commit

Permalink
[FEATURE | BREAKING] Change semantics of in-element
Browse files Browse the repository at this point in the history
  • Loading branch information
chadhietala committed Feb 11, 2019
1 parent fca7da1 commit daee9a5
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 16 deletions.
2 changes: 1 addition & 1 deletion packages/@glimmer/debug/lib/opcode-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ METADATA[Op.PushRemoteElement] = {
name: 'PushRemoteElement',
mnemonic: 'apnd_remotetag',
before: null,
stackChange: -3,
stackChange: -4,
ops: [],
operands: 0,
check: true,
Expand Down
47 changes: 42 additions & 5 deletions packages/@glimmer/integration-tests/lib/suites/in-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,13 @@ export class InElementSuite extends RenderTest {
let initialContent = '<p>Hello there!</p>';
replaceHTML(externalElement, initialContent);

this.render(stripTight`{{#in-element externalElement}}[{{foo}}]{{/in-element}}`, {
externalElement,
foo: 'Yippie!',
});
this.render(
stripTight`{{#in-element externalElement insertBefore=null}}[{{foo}}]{{/in-element}}`,
{
externalElement,
foo: 'Yippie!',
}
);

equalsElement(externalElement, 'div', {}, `${initialContent}[Yippie!]`);
this.assertHTML('<!---->');
Expand All @@ -107,13 +110,47 @@ export class InElementSuite extends RenderTest {
@test
'With nextSibling'() {
let externalElement = this.delegate.createElement('div');
replaceHTML(externalElement, '<b>Hello</b><em>there!</em>');

this.render(
stripTight`{{#in-element externalElement nextSibling=nextSibling}}[{{foo}}]{{/in-element}}`,
{ externalElement, nextSibling: externalElement.lastChild, foo: 'Yippie!' }
);

equalsElement(externalElement, 'div', {}, '[Yippie!]');
this.assertHTML('<!---->');
this.assertStableRerender();

this.rerender({ foo: 'Double Yips!' });
equalsElement(externalElement, 'div', {}, '[Double Yips!]');
this.assertHTML('<!---->');
this.assertStableNodes();

this.rerender({ nextSibling: null });
equalsElement(externalElement, 'div', {}, '[Double Yips!]');
this.assertHTML('<!---->');
this.assertStableRerender();

this.rerender({ externalElement: null });
equalsElement(externalElement, 'div', {}, '');
this.assertHTML('<!---->');
this.assertStableRerender();

this.rerender({ externalElement, nextSibling: externalElement.lastChild, foo: 'Yippie!' });
equalsElement(externalElement, 'div', {}, '[Yippie!]');
this.assertHTML('<!---->');
this.assertStableRerender();
}

@test
'With nextSibling (retain content)'() {
let externalElement = this.delegate.createElement('div');
replaceHTML(externalElement, '<b>Hello</b><em>there!</em>');

this.render(
stripTight`{{#in-element externalElement nextSibling=nextSibling insertBefore=null}}[{{foo}}]{{/in-element}}`,
{ externalElement, nextSibling: externalElement.lastChild, foo: 'Yippie!' }
);

equalsElement(externalElement, 'div', {}, '<b>Hello</b>[Yippie!]<em>there!</em>');
this.assertHTML('<!---->');
this.assertStableRerender();
Expand Down
3 changes: 2 additions & 1 deletion packages/@glimmer/interfaces/lib/dom/attributes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export interface DOMStack {
pushRemoteElement(
element: SimpleElement,
guid: string,
nextSibling: Option<SimpleNode>
nextSibling: Option<SimpleNode>,
insertBefore: Option<unknown>
): Option<RemoteLiveBlock>;
popRemoteElement(): void;
popElement(): void;
Expand Down
5 changes: 3 additions & 2 deletions packages/@glimmer/node/lib/serialize-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,14 @@ class SerializeBuilder extends NewElementBuilder implements ElementBuilder {
pushRemoteElement(
element: SimpleElement,
cursorId: string,
nextSibling: Option<SimpleNode> = null
nextSibling: Option<SimpleNode> = null,
_insertBefore: Option<null>
): Option<RemoteLiveBlock> {
let { dom } = this;
let script = dom.createElement('script');
script.setAttribute('glmr', cursorId);
dom.insertBefore(element, script, nextSibling);
return super.pushRemoteElement(element, cursorId, nextSibling);
return super.pushRemoteElement(element, cursorId, nextSibling, null);
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/@glimmer/opcode-compiler/lib/syntax/builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export function populateBuiltins(

for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (key === 'nextSibling' || key === 'guid') {
if (key === 'nextSibling' || key === 'guid' || key === 'insertBefore') {
actions.push(op('Expr', values[i]));
} else {
throw new Error(`SYNTAX ERROR: #in-element does not take a \`${keys[0]}\` option`);
Expand All @@ -182,7 +182,7 @@ export function populateBuiltins(

actions.push(op('Expr', params[0]), op(Op.Dup, $sp, 0));

return { count: 4, actions };
return { count: 5, actions };
},

ifTrue() {
Expand Down
5 changes: 4 additions & 1 deletion packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ APPEND_OPCODES.add(Op.OpenDynamicElement, vm => {

APPEND_OPCODES.add(Op.PushRemoteElement, vm => {
let elementRef = check(vm.stack.pop(), CheckReference);
let insertBefore = check(vm.stack.pop(), CheckReference);
let nextSiblingRef = check(vm.stack.pop(), CheckReference);
let guidRef = check(vm.stack.pop(), CheckReference);

Expand All @@ -66,7 +67,9 @@ APPEND_OPCODES.add(Op.PushRemoteElement, vm => {
vm.updateWith(new Assert(cache));
}

let block = vm.elements().pushRemoteElement(element, guid, nextSibling);
let shouldClear = insertBefore.value();

let block = vm.elements().pushRemoteElement(element, guid, nextSibling, shouldClear);
if (block) vm.associateDestroyable(block);
});

Expand Down
16 changes: 13 additions & 3 deletions packages/@glimmer/runtime/lib/vm/element-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,18 +205,28 @@ export class NewElementBuilder implements ElementBuilder {
pushRemoteElement(
element: SimpleElement,
guid: string,
nextSibling: Option<SimpleNode> = null
nextSibling: Option<SimpleNode> = null,
insertBefore: Option<null>
): Option<RemoteLiveBlock> {
return this.__pushRemoteElement(element, guid, nextSibling);
return this.__pushRemoteElement(element, guid, nextSibling, insertBefore);
}

__pushRemoteElement(
element: SimpleElement,
_guid: string,
nextSibling: Option<SimpleNode>
nextSibling: Option<SimpleNode>,
insertBefore: Option<null>
): Option<RemoteLiveBlock> {
this.pushElement(element, nextSibling);

if (insertBefore !== null) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}

let block = new RemoteLiveBlock(element);

return this.pushLiveBlock(block, true);
}

Expand Down
9 changes: 8 additions & 1 deletion packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,11 +379,18 @@ export class RehydrateBuilder extends NewElementBuilder implements ElementBuilde
__pushRemoteElement(
element: SimpleElement,
cursorId: string,
nextSibling: Option<SimpleNode> = null
nextSibling: Option<SimpleNode> = null,
insertBefore: Option<null>
): Option<RemoteLiveBlock> {
let marker = this.getMarker(element as HTMLElement, cursorId);

if (marker.parentNode === element) {
if (insertBefore !== null) {
while (element.lastChild !== marker) {
element.removeChild(element.lastChild!);
}
}

let currentCursor = this.currentCursor;
let candidate = currentCursor!.candidate;

Expand Down
15 changes: 15 additions & 0 deletions packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,11 +442,20 @@ function addElementModifier(element: Tag<'StartTag'>, mustache: AST.MustacheStat

function addInElementHash(cursor: string, hash: AST.Hash, loc: AST.SourceLocation) {
let hasNextSibling = false;
let hasInsertBefore = false;
hash.pairs.forEach(pair => {
if (pair.key === 'guid') {
throw new SyntaxError('Cannot pass `guid` from user space', loc);
}

if (pair.key === 'insertBefore') {
if (pair.value.type !== 'NullLiteral') {
throw new SyntaxError('insertBefore only takes `null` as an argument', loc);
}

hasInsertBefore = true;
}

if (pair.key === 'nextSibling') {
hasNextSibling = true;
}
Expand All @@ -462,6 +471,12 @@ function addInElementHash(cursor: string, hash: AST.Hash, loc: AST.SourceLocatio
hash.pairs.push(nextSibling);
}

if (!hasInsertBefore) {
let undefinedLiteral = b.literal('UndefinedLiteral', undefined);
let beforeSibling = b.pair('insertBefore', undefinedLiteral);
hash.pairs.push(beforeSibling);
}

return hash;
}

Expand Down

0 comments on commit daee9a5

Please sign in to comment.