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

Implemented template loader with block type cache. #34

Merged
merged 4 commits into from
Jul 9, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/angular.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'debug.dart';
part 'block.dart';
part 'block_list.dart';
part 'block_type.dart';
part 'cache.dart';
part 'compiler.dart';
part 'directive.dart';
part 'directives/ng_bind.dart';
Expand Down Expand Up @@ -124,7 +125,10 @@ class AngularModule extends Module {
type(Scope, Scope);
type(Parser, Parser);
type(Interpolate, Interpolate);
type(CacheFactory, CacheFactory);
type(Http, Http);
type(BlockCache, BlockCache);
type(TemplateCache, TemplateCache);

value(ScopeDigestTTL, new ScopeDigestTTL(5));

Expand Down
95 changes: 78 additions & 17 deletions lib/block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ class Block implements ElementWrapper {
if (ref.directive.isComponent) {
//nodeModule.factory(type, new ComponentFactory(node, ref.directive), visibility: visibility);
// TODO(misko): there should be no need to wrap function like this.
nodeModule.factory(type, (Injector injector, Compiler compiler, Scope scope, Parser parser, Http $http) =>
(new ComponentFactory(node, ref.directive))(injector, compiler, scope, parser, $http),
nodeModule.factory(type, (Injector injector, Compiler compiler, Scope scope, Parser parser, BlockCache $blockCache) =>
(new ComponentFactory(node, ref.directive))(injector, compiler, scope, parser, $blockCache),
visibility: visibility);
} else {
nodeModule.type(type, type, visibility: visibility);
Expand Down Expand Up @@ -266,36 +266,45 @@ class ComponentFactory {

ComponentFactory(this.element, this.directive);

dynamic call(Injector injector, Compiler compiler, Scope scope, Parser parser, Http $http) {
dynamic call(Injector injector, Compiler compiler, Scope scope,
Parser parser, BlockCache $blockCache) {
this.compiler = compiler;
shadowDom = element.createShadowRoot();
shadowScope = scope.$new(true);
createAttributeMapping(scope, shadowScope, parser);
var controller = createShadowInjector(injector).get(directive.type);
if (directive.$cssUrl != null) {
shadowDom.innerHtml = '<style>@import "${directive.$cssUrl}"</style>';
}
TemplateLoader templateLoader;
if (directive.$template != null) {
compileTemplate(directive.$template);
var blockFuture = new async.Future.value().then((_) =>
attachBlockToShadowDom($blockCache.fromHtml(directive.$template)));
templateLoader = new TemplateLoader(blockFuture);
} else if (directive.$templateUrl != null) {
$http.
getString(directive.$templateUrl).
then((data) => shadowScope.$apply(() => compileTemplate(data)));
var blockFuture = $blockCache.fromUrl(directive.$templateUrl)
.then((BlockType blockType) => attachBlockToShadowDom(blockType));
templateLoader = new TemplateLoader(blockFuture);
}
var controller =
createShadowInjector(injector, templateLoader).get(directive.type);
if (directive.$publishAs != null) {
shadowScope[directive.$publishAs] = controller;
}
return controller;
}

compileTemplate(html) {
shadowDom.innerHtml += html;
compiler(shadowDom.nodes)(shadowInjector, shadowDom.nodes);
attachBlockToShadowDom(BlockType blockType) {
var block = blockType(shadowInjector);
shadowDom.nodes.addAll(block.elements);
shadowInjector.get(Scope).$digest();
return shadowDom;
}

createShadowInjector(injector) {
var shadowModule = new ScopeModule(shadowScope);
shadowModule.type(directive.type, directive.type);
createShadowInjector(injector, TemplateLoader templateLoader) {
var shadowModule = new ScopeModule(shadowScope)
..type(directive.type, directive.type)
..value(TemplateLoader, templateLoader)
..value(dom.ShadowRoot, shadowDom);
shadowInjector = injector.createChild([shadowModule]);
// TODO(misko): creazy hack to mark injector
shadowInjector.instances[_SHADOW] = injector;
Expand Down Expand Up @@ -333,18 +342,70 @@ class ComponentFactory {
}
}

class BlockCache {
Cache _blockCache;
Http $http;
TemplateCache $templateCache;
Compiler compiler;

BlockCache(CacheFactory $cacheFactory, Http this.$http,
TemplateCache this.$templateCache, Compiler this.compiler) {
_blockCache = $cacheFactory('blocks');
}

BlockType fromHtml(String html) {
BlockType blockType = _blockCache.get(html);
if (blockType == null) {
var div = new dom.Element.tag('div');
div.innerHtml = html;
blockType = compiler(div.nodes);
_blockCache.put(html, blockType);
}
return blockType;
}

async.Future<BlockType> fromUrl(String url) {
return $http.getString(url, cache: $templateCache).then((String tmpl) {
return fromHtml(tmpl);
});
}
}

/**
* A convinience wrapper for "templates" cache.
*/
class TemplateCache implements Cache {
Cache _cache;

TemplateCache(CacheFactory $cacheFactory) {
_cache = $cacheFactory('templates');
}

Object get(key) => _cache.get(key);
Object put(key, Object value) => _cache.put(key, value);
void remove(key) => _cache.remove(key);
void removeAll() => _cache.removeAll();
CacheInfo info() => _cache.info();
void destroy() => _cache.destroy();
}

class TemplateLoader {
final async.Future<dom.ShadowRoot> _template;
async.Future<dom.ShadowRoot> get template => _template;
TemplateLoader(this._template);
}

attrAccessorFactory(dom.Element element, String name) {
return ([String value]) {
if (value != null) {
if (value == null) {
element.removeAttribute(name);
element.attributes.remove(name);
} else {
element.setAttribute(name, value);
element.attributes[name] = value;
}
return value;
} else {
return element.getAttribute(name);
return element.attributes[name];
}
};
}
Expand Down
98 changes: 98 additions & 0 deletions lib/cache.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
part of angular;

/**
* A simple map-backed cache.
* TODO(pavelgj): add LRU support.
*/
class Cache {
final String _id;
Map<String, Object> _data = <String, Object>{};
CacheFactory _factory;

Cache._newCache(String this._id, CacheFactory this._factory);

Object get(key) {
_checkIfDestroyed();
key = _stringifyKey(key);
return _data[key];
}

Object put(key, Object value) {
_checkIfDestroyed();
if (value == null) {
return null;
}
key = _stringifyKey(key);
return _data[key] = value;
}

void remove(key) {
_checkIfDestroyed();
key = _stringifyKey(key);
_data.remove(key);
}

void removeAll() {
_checkIfDestroyed();
_data.clear();
}

void _checkIfDestroyed() {
if (_data == null) {
throw "[\$cacheFactory:iid] CacheId '$_id' is already destroyed!";
}
}

String _stringifyKey(key) {
if (!(key is String)) {
key = key.toString();
}
return key;
}

CacheInfo info() => new CacheInfo(size: _data.length, id: _id);

void destroy() {
_data.clear();
_data = null;
_factory._cacheMap.remove(_id);
}
}

class CacheFactory {
Map<String, Cache> _cacheMap = <String, Cache>{};

Cache call(String cacheId) {
var cache = _cacheMap[cacheId];
if (cache != null) {
throw "[\$cacheFactory:iid] CacheId '$cacheId' is already taken!";
}
_cacheMap[cacheId] = cache = new Cache._newCache(cacheId, this);
return cache;
}

Cache get(String cacheId) {
return _cacheMap[cacheId];
}

Map<String, CacheInfo> info() {
Map<String, CacheInfo> info = <String, CacheInfo>{};
_cacheMap.keys.forEach((cacheId) {
info[cacheId] = _cacheMap[cacheId].info();
});
return info;
}
}

class CacheInfo {
final int size;
final String id;

CacheInfo({this.size, this.id});

bool operator ==(other) {
return other is CacheInfo && size == other.size && id == other.id;
}

String toString() => '{size: $size, id: $id}';
}
26 changes: 24 additions & 2 deletions lib/http.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
part of angular;

class Http {
async.Future<String> getString(String url, {bool withCredentials, void onProgress(dom.ProgressEvent e)}) =>
dom.HttpRequest.getString(url, withCredentials: withCredentials, onProgress: onProgress);
Map<String, async.Future<String>> _pendingRequests = <String, async.Future<String>>{};

async.Future<String> getString(String url, {bool withCredentials, void onProgress(dom.ProgressEvent e), Cache cache}) {
// We return a pending request only if caching is enabled.
if (cache != null && _pendingRequests.containsKey(url)) {
return _pendingRequests[url];
}
var cachedValue = cache != null ? cache.get(url) : null;
if (cachedValue != null) {
return new async.Future.value(cachedValue);
}
var result = dom.HttpRequest.getString(url, withCredentials: withCredentials, onProgress: onProgress).then((value) {
if (cache != null) {
cache.put(url, value);
}
_pendingRequests.remove(url);
return value;
}, onError: (error) {
_pendingRequests.remove(url);
throw error;
});
_pendingRequests[url] = result;
return result;
}
}
40 changes: 31 additions & 9 deletions test/_http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,44 @@ import 'dart:html';
import 'package:angular/angular.dart';

class MockHttp extends Http {
Map<String, String> gets = {};
Map<String, MockHttpData> gets = {};
List futures = [];

expectGET(url, content) {
gets[url] = content;
expectGET(String url, String content, {int times: 1}) {
gets[url] = new MockHttpData(content, times);
}

flush() => gets.length == 0 ? Future.wait(futures) :
flush() => Future.wait(futures);

assertAllGetsCalled() {
if (gets.length != 0) {
throw "Expected GETs not called $gets";
}
}

Future<String> getString(String url, {bool withCredentials, void onProgress(ProgressEvent e)}) {
if (!gets.containsKey(url)) throw "Unexpected URL $url";
var f = new Future.value(gets.remove(url));
futures.add(f);
return f;
Future<String> getString(String url, {bool withCredentials, void onProgress(ProgressEvent e), Cache cache}) {
if (!gets.containsKey(url)) throw "Unexpected URL $url $gets";
var data = gets[url];
data.times--;
if (data.times <= 0) {
gets.remove(url);
}
var expectedValue = data.value;
if (cache != null) {
cache.put(url, expectedValue);
}
var future = new Future.value(expectedValue);
futures.add(future);
return future;
}
}

class MockHttpData {
String value;
int times;
MockHttpData(this.value, this.times);

toString() => value;
}

main() {}
Loading