Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Commit

Permalink
feat(blockhole): Change blockhole to have the insert / remove / move …
Browse files Browse the repository at this point in the history
…methods.

Closes #689
  • Loading branch information
codelogic authored and mhevery committed Mar 7, 2014
1 parent 4453e3e commit c1e70ce
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 197 deletions.
119 changes: 38 additions & 81 deletions lib/core_dom/block.dart
Original file line number Diff line number Diff line change
@@ -1,104 +1,61 @@
part of angular.core.dom;

/**
* ElementWrapper is an interface for [Block]s and [BlockHole]s. Its purpose is
* to allow treating [Block] and [BlockHole] under same interface so that
* [Block]s can be added after [BlockHole].
*/
abstract class ElementWrapper {
List<dom.Node> elements;
ElementWrapper next;
ElementWrapper previous;
}

/**
* A Block is a fundamental building block of DOM. It is a chunk of DOM which
* can not be structural changed. It can only have its attributes changed.
* A Block can have [BlockHole]s embedded in its DOM. A [BlockHole] can
* contain other [Block]s and it is the only way in which DOM can be changed
* structurally.
* can not be structurally changed. A Block can have [BlockHole] placeholders
* embedded in its DOM. A [BlockHole] can contain other [Block]s and it is the
* only way in which DOM structure can be modified.
*
* A [Block] is a collection of DOM nodes
*
* A [Block] can be created from [BlockFactory].
*
*/
class Block implements ElementWrapper {
List<dom.Node> elements;
ElementWrapper next;
ElementWrapper previous;
class Block {
final List<dom.Node> nodes;
Block(this.nodes);
}

/**
* A BlockHole maintains an ordered list of [Block]'s. It contains a
* [placeholder] node that is used as the insertion point for block nodes.
*/
class BlockHole {
final dom.Node placeholder;
final NgAnimate _animate;
final List<Block> _blocks = <Block>[];

Block(this.elements, this._animate);

Block insertAfter(ElementWrapper previousBlock) {
// Update Link List.
next = previousBlock.next;
if (next != null) {
next.previous = this;
}
previous = previousBlock;
previousBlock.next = this;
BlockHole(this.placeholder, this._animate);

// Update DOM
List<dom.Node> previousElements = previousBlock.elements;
dom.Node previousElement = previousElements[previousElements.length - 1];
dom.Node insertBeforeElement = previousElement.nextNode;
dom.Node parentElement = previousElement.parentNode;
void insert(Block block, { Block insertAfter }) {
dom.Node previousNode = _lastNode(insertAfter);
_blocksInsertAfter(block, insertAfter);

_animate.insert(elements, parentElement, insertBefore: insertBeforeElement);

return this;
_animate.insert(block.nodes, placeholder.parentNode,
insertBefore: previousNode.nextNode);
}

Block remove() {
bool preventDefault = false;

_animate.remove(elements);

// Remove block from list
if (previous != null && (previous.next = next) != null) {
next.previous = previous;
}
next = previous = null;
return this;
void remove(Block block) {
_blocks.remove(block);
_animate.remove(block.nodes);
}

Block moveAfter(ElementWrapper previousBlock) {
var previousElements = previousBlock.elements,
previousElement = previousElements[previousElements.length - 1],
insertBeforeElement = previousElement.nextNode,
parentElement = previousElement.parentNode;

elements.forEach((el) => parentElement.insertBefore(el, insertBeforeElement));
void move(Block block, { Block moveAfter }) {
dom.Node previousNode = _lastNode(moveAfter);
_blocks.remove(block);
_blocksInsertAfter(block, moveAfter);

// Remove block from list
previous.next = next;
if (next != null) {
next.previous = previous;
}
// Add block to list
next = previousBlock.next;
if (next != null) {
next.previous = this;
}
previous = previousBlock;
previousBlock.next = this;
return this;
_animate.move(block.nodes, placeholder.parentNode,
insertBefore: previousNode.nextNode);
}
}

/**
* A BlockHole is an instance of a hole. BlockHoles designate where child
* [Block]s can be added in parent [Block]. BlockHoles wrap a DOM element,
* and act as references which allows more blocks to be added.
*/
class BlockHole extends ElementWrapper {
List<dom.Node> elements;
ElementWrapper previous;
ElementWrapper next;
void _blocksInsertAfter(Block block, Block insertAfter) {
int index = (insertAfter != null) ? _blocks.indexOf(insertAfter) : -1;
_blocks.insert(index + 1, block);
}

BlockHole(this.elements);
dom.Node _lastNode(Block insertAfter) =>
insertAfter == null
? placeholder
: insertAfter.nodes[insertAfter.nodes.length - 1];
}

13 changes: 7 additions & 6 deletions lib/core_dom/block_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ class BlockFactory implements Function {
BoundBlockFactory bind(Injector injector) =>
new BoundBlockFactory(this, injector);

Block call(Injector injector, [List<dom.Node> elements]) {
if (elements == null) elements = cloneElements(templateElements);
Block call(Injector injector, [List<dom.Node> nodes]) {
if (nodes == null) nodes = cloneElements(templateElements);
var timerId;
try {
assert((timerId = _perf.startTimer('ng.block')) != false);
var block = new Block(elements, injector.get(NgAnimate));
_link(block, elements, directivePositions, injector);
var block = new Block(nodes);
_link(block, nodes, directivePositions, injector);
return block;
} finally {
assert(_perf.stopTimer(timerId) != false);
Expand Down Expand Up @@ -176,7 +176,8 @@ class BlockFactory implements Function {
if (annotation.children == NgAnnotation.TRANSCLUDE_CHILDREN) {
// Currently, transclude is only supported for NgDirective.
assert(annotation is NgDirective);
blockHoleFactory = (_) => new BlockHole([node]);
blockHoleFactory = (_) => new BlockHole(node,
parentInjector.get(NgAnimate));
blockFactory = (_) => ref.blockFactory;
boundBlockFactory = (Injector injector) =>
ref.blockFactory.bind(injector);
Expand Down Expand Up @@ -384,7 +385,7 @@ class _ComponentFactory implements Function {

attachBlockToShadowDom(BlockFactory blockFactory) {
var block = blockFactory(shadowInjector);
shadowDom.nodes.addAll(block.elements);
shadowDom.nodes.addAll(block.nodes);
return shadowDom;
}

Expand Down
8 changes: 4 additions & 4 deletions lib/directive/ng_if.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ abstract class _NgUnlessIfAttrDirectiveBase {
if (_block == null) {
_childScope = _scope.createChild(new PrototypeMap(_scope.context));
_block = _boundBlockFactory(_childScope);
var insertBlock = _block;
var block = _block;
_scope.rootScope.domWrite(() {
insertBlock.insertAfter(_blockHole);
_blockHole.insert(block);
});
}
}

void _ensureBlockDestroyed() {
if (_block != null) {
var removeBlock = _block;
var block = _block;
_scope.rootScope.domWrite(() {
removeBlock.remove();
_blockHole.remove(block);
});
_childScope.destroy();
_block = null;
Expand Down
22 changes: 11 additions & 11 deletions lib/directive/ng_include.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,29 @@ class NgIncludeDirective {
final Injector injector;
final DirectiveMap directives;

Block _previousBlock;
Scope _previousScope;
Block _block;
Scope _scope;

NgIncludeDirective(this.element, this.scope, this.blockCache, this.injector, this.directives);

_cleanUp() {
if (_previousBlock == null) return;
if (_block == null) return;

_previousBlock.remove();
_previousScope.destroy();
_block.nodes.forEach((node) => node.remove);
_scope.destroy();
element.innerHtml = '';

_previousBlock = null;
_previousScope = null;
_block = null;
_scope = null;
}

_updateContent(createBlock) {
// create a new scope
_previousScope = scope.createChild(new PrototypeMap(scope.context));
_previousBlock = createBlock(injector.createChild([new Module()
..value(Scope, _previousScope)]));
_scope = scope.createChild(new PrototypeMap(scope.context));
_block = createBlock(injector.createChild([new Module()
..value(Scope, _scope)]));

_previousBlock.elements.forEach((elm) => element.append(elm));
_block.nodes.forEach((node) => element.append(node));
}


Expand Down
19 changes: 10 additions & 9 deletions lib/directive/ng_repeat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class _Row {
Block block;
dom.Element startNode;
dom.Element endNode;
List<dom.Element> elements;
List<dom.Element> nodes;

_Row(this.id);
}
Expand Down Expand Up @@ -177,20 +177,21 @@ class NgRepeatDirective {
}
// remove existing items
_rows.forEach((key, row) {
row.block.remove();
_blockHole.remove(row.block);
row.scope.destroy();
});
_rows = newRows;
return newRowOrder;
}

_onCollectionChange(Iterable collection) {
dom.Node previousNode = _blockHole.elements[0]; // current position of the node
dom.Node previousNode = _blockHole.placeholder; // current position of the
// node
dom.Node nextNode;
Scope childScope;
Map childContext;
Scope trackById;
ElementWrapper cursor = _blockHole;
Block cursor;

List<_Row> newRowOrder = _computeNewRows(collection, trackById);

Expand All @@ -211,7 +212,7 @@ class NgRepeatDirective {

if (row.startNode != nextNode) {
// existing item which got moved
row.block.moveAfter(cursor);
_blockHole.move(row.block, moveAfter: cursor);
}
previousNode = row.endNode;
} else {
Expand All @@ -237,10 +238,10 @@ class NgRepeatDirective {
_rows[row.id] = row
..block = block
..scope = childScope
..elements = block.elements
..startNode = row.elements[0]
..endNode = row.elements[row.elements.length - 1];
block.insertAfter(cursor);
..nodes = block.nodes
..startNode = row.nodes[0]
..endNode = row.nodes[row.nodes.length - 1];
_blockHole.insert(block, insertAfter: cursor);
}
cursor = row.block;
}
Expand Down
12 changes: 7 additions & 5 deletions lib/directive/ng_switch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class NgSwitchDirective {
set value(val) {
currentBlocks
..forEach((_BlockScopePair pair) {
pair.block.remove();
pair.hole.remove(pair.block);
pair.scope.destroy();
})
..clear();
Expand All @@ -83,8 +83,10 @@ class NgSwitchDirective {
(cases.containsKey(val) ? cases[val] : cases['?'])
.forEach((_Case caze) {
Scope childScope = scope.createChild(new PrototypeMap(scope.context));
var block = caze.blockFactory(childScope)..insertAfter(caze.anchor);
currentBlocks.add(new _BlockScopePair(block, childScope));
var block = caze.blockFactory(childScope);
caze.anchor.insert(block);
currentBlocks.add(new _BlockScopePair(block, caze.anchor,
childScope));
});
if (onChange != null) {
onChange();
Expand All @@ -94,9 +96,10 @@ class NgSwitchDirective {

class _BlockScopePair {
final Block block;
final BlockHole hole;
final Scope scope;

_BlockScopePair(this.block, this.scope);
_BlockScopePair(this.block, this.hole, this.scope);
}

class _Case {
Expand All @@ -121,7 +124,6 @@ class NgSwitchWhenDirective {
set value(String value) => ngSwitch.addCase('!$value', hole, blockFactory);
}


@NgDirective(
children: NgAnnotation.TRANSCLUDE_CHILDREN,
selector: '[ng-switch-default]')
Expand Down
22 changes: 11 additions & 11 deletions lib/routing/ng_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ class NgViewDirective implements NgDetachAware, RouteProvider {
final Scope scope;
RouteHandle _route;

Block _previousBlock;
Scope _previousScope;
Block _block;
Scope _scope;
Route _viewRoute;

NgViewDirective(this.element, this.blockCache,
Expand Down Expand Up @@ -116,23 +116,23 @@ class NgViewDirective implements NgDetachAware, RouteProvider {
var newDirectives = viewInjector.get(DirectiveMap);
blockCache.fromUrl(templateUrl, newDirectives).then((blockFactory) {
_cleanUp();
_previousScope = scope.createChild(new PrototypeMap(scope.context));
_previousBlock = blockFactory(
_scope = scope.createChild(new PrototypeMap(scope.context));
_block = blockFactory(
viewInjector.createChild(
[new Module()..value(Scope, _previousScope)]));
[new Module()..value(Scope, _scope)]));

_previousBlock.elements.forEach((elm) => element.append(elm));
_block.nodes.forEach((elm) => element.append(elm));
});
}

_cleanUp() {
if (_previousBlock == null) return;
if (_block == null) return;

_previousBlock.remove();
_previousScope.destroy();
_block.nodes.forEach((node) => node.remove());
_scope.destroy();

_previousBlock = null;
_previousScope = null;
_block = null;
_scope = null;
}

Route get route => _viewRoute;
Expand Down
Loading

0 comments on commit c1e70ce

Please sign in to comment.