feat(dom_renderer): add setBindingDebugInfo method
This is used for setting property binding values as attributes on elements when running in dev mode. This implementation will also serialize binding information to template placeholder comment nodes. Closes #5227
This commit is contained in:
parent
fe1dd77d94
commit
79399e1c51
|
@ -173,7 +173,7 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||||
logBindingUpdate(b: BindingTarget, value: any): void {
|
logBindingUpdate(b: BindingTarget, value: any): void {
|
||||||
if (b.isDirective() || b.isElementProperty()) {
|
if (b.isDirective() || b.isElementProperty()) {
|
||||||
var elementRef = this.elementRefs[this.elementOffset + b.elementIndex];
|
var elementRef = this.elementRefs[this.elementOffset + b.elementIndex];
|
||||||
this.renderer.setElementAttribute(
|
this.renderer.setBindingDebugInfo(
|
||||||
elementRef, `${REFLECT_PREFIX}${camelCaseToDashCase(b.name)}`, `${value}`);
|
elementRef, `${REFLECT_PREFIX}${camelCaseToDashCase(b.name)}`, `${value}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -300,6 +300,9 @@ export abstract class Renderer {
|
||||||
abstract setElementAttribute(location: RenderElementRef, attributeName: string,
|
abstract setElementAttribute(location: RenderElementRef, attributeName: string,
|
||||||
attributeValue: string);
|
attributeValue: string);
|
||||||
|
|
||||||
|
abstract setBindingDebugInfo(location: RenderElementRef, propertyName: string,
|
||||||
|
propertyValue: string);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a (CSS) class on the Element specified via `location`.
|
* Sets a (CSS) class on the Element specified via `location`.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import {Inject, Injectable, OpaqueToken} from 'angular2/src/core/di';
|
import {Inject, Injectable, OpaqueToken} from 'angular2/src/core/di';
|
||||||
import {AnimationBuilder} from 'angular2/src/animate/animation_builder';
|
import {AnimationBuilder} from 'angular2/src/animate/animation_builder';
|
||||||
|
|
||||||
|
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {
|
import {
|
||||||
isPresent,
|
isPresent,
|
||||||
isBlank,
|
isBlank,
|
||||||
|
Json,
|
||||||
RegExpWrapper,
|
RegExpWrapper,
|
||||||
CONST_EXPR,
|
CONST_EXPR,
|
||||||
stringify,
|
stringify,
|
||||||
|
@ -41,11 +43,13 @@ import {
|
||||||
} from 'angular2/src/core/render/view';
|
} from 'angular2/src/core/render/view';
|
||||||
import {ViewEncapsulation} from 'angular2/src/core/metadata';
|
import {ViewEncapsulation} from 'angular2/src/core/metadata';
|
||||||
|
|
||||||
// TODO move it once DomAdapter is moved
|
// TODO move it once DdomAdapter is moved
|
||||||
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
|
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
|
||||||
|
|
||||||
const NAMESPACE_URIS =
|
const NAMESPACE_URIS =
|
||||||
CONST_EXPR({'xlink': 'http://www.w3.org/1999/xlink', 'svg': 'http://www.w3.org/2000/svg'});
|
CONST_EXPR({'xlink': 'http://www.w3.org/1999/xlink', 'svg': 'http://www.w3.org/2000/svg'});
|
||||||
|
const TEMPLATE_COMMENT_TEXT = 'template bindings={}';
|
||||||
|
var TEMPLATE_BINDINGS_EXP = /^template bindings=(.*)$/g;
|
||||||
|
|
||||||
export abstract class DomRenderer extends Renderer implements NodeFactory<Node> {
|
export abstract class DomRenderer extends Renderer implements NodeFactory<Node> {
|
||||||
abstract registerComponentTemplate(template: RenderComponentTemplate);
|
abstract registerComponentTemplate(template: RenderComponentTemplate);
|
||||||
|
@ -118,7 +122,7 @@ export abstract class DomRenderer extends Renderer implements NodeFactory<Node>
|
||||||
dehydrateView(viewRef: RenderViewRef) { resolveInternalDomView(viewRef).dehydrate(); }
|
dehydrateView(viewRef: RenderViewRef) { resolveInternalDomView(viewRef).dehydrate(); }
|
||||||
|
|
||||||
createTemplateAnchor(attrNameAndValues: string[]): Node {
|
createTemplateAnchor(attrNameAndValues: string[]): Node {
|
||||||
return this.createElement('script', attrNameAndValues);
|
return DOM.createComment(TEMPLATE_COMMENT_TEXT);
|
||||||
}
|
}
|
||||||
abstract createElement(name: string, attrNameAndValues: string[]): Node;
|
abstract createElement(name: string, attrNameAndValues: string[]): Node;
|
||||||
abstract mergeElement(existing: Node, attrNameAndValues: string[]);
|
abstract mergeElement(existing: Node, attrNameAndValues: string[]);
|
||||||
|
@ -145,6 +149,31 @@ export abstract class DomRenderer extends Renderer implements NodeFactory<Node>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used only in debug mode to serialize property changes to comment nodes,
|
||||||
|
* such as <template> placeholders.
|
||||||
|
*/
|
||||||
|
setBindingDebugInfo(location: RenderElementRef, propertyName: string,
|
||||||
|
propertyValue: string): void {
|
||||||
|
var view: DefaultRenderView<Node> = resolveInternalDomView(location.renderView);
|
||||||
|
var element = view.boundElements[location.boundElementIndex];
|
||||||
|
var dashCasedPropertyName = camelCaseToDashCase(propertyName);
|
||||||
|
if (DOM.isCommentNode(element)) {
|
||||||
|
var existingBindings = RegExpWrapper.firstMatch(
|
||||||
|
TEMPLATE_BINDINGS_EXP, StringWrapper.replaceAll(DOM.getText(element), /\n/g, ''));
|
||||||
|
var parsedBindings = Json.parse(existingBindings[1]);
|
||||||
|
if (isPresent(propertyValue)) {
|
||||||
|
parsedBindings[dashCasedPropertyName] = propertyValue;
|
||||||
|
} else {
|
||||||
|
StringMapWrapper.delete(parsedBindings, dashCasedPropertyName);
|
||||||
|
}
|
||||||
|
DOM.setText(element, StringWrapper.replace(TEMPLATE_COMMENT_TEXT, '{}',
|
||||||
|
Json.stringify(parsedBindings)));
|
||||||
|
} else {
|
||||||
|
this.setElementAttribute(location, propertyName, propertyValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setElementClass(location: RenderElementRef, className: string, isAdd: boolean): void {
|
setElementClass(location: RenderElementRef, className: string, isAdd: boolean): void {
|
||||||
var view = resolveInternalDomView(location.renderView);
|
var view = resolveInternalDomView(location.renderView);
|
||||||
var element = view.boundElements[location.boundElementIndex];
|
var element = view.boundElements[location.boundElementIndex];
|
||||||
|
|
|
@ -58,6 +58,8 @@ export class MessageBasedRenderer {
|
||||||
bind(this._renderer.setElementProperty, this._renderer));
|
bind(this._renderer.setElementProperty, this._renderer));
|
||||||
broker.registerMethod("setElementAttribute", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
|
broker.registerMethod("setElementAttribute", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
|
||||||
bind(this._renderer.setElementAttribute, this._renderer));
|
bind(this._renderer.setElementAttribute, this._renderer));
|
||||||
|
broker.registerMethod("setBindingDebugInfo", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
|
||||||
|
bind(this._renderer.setBindingDebugInfo, this._renderer));
|
||||||
broker.registerMethod("setElementClass", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
|
broker.registerMethod("setElementClass", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
|
||||||
bind(this._renderer.setElementClass, this._renderer));
|
bind(this._renderer.setElementClass, this._renderer));
|
||||||
broker.registerMethod("setElementStyle", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
|
broker.registerMethod("setElementStyle", [WebWorkerElementRef, PRIMITIVE, PRIMITIVE],
|
||||||
|
|
|
@ -196,6 +196,17 @@ export class WebWorkerRenderer implements Renderer {
|
||||||
this._messageBroker.runOnService(args, null);
|
this._messageBroker.runOnService(args, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setBindingDebugInfo(location: RenderElementRef, propertyName: string,
|
||||||
|
propertyValue: string): void {
|
||||||
|
var fnArgs = [
|
||||||
|
new FnArg(location, WebWorkerElementRef),
|
||||||
|
new FnArg(propertyName, null),
|
||||||
|
new FnArg(propertyValue, null)
|
||||||
|
];
|
||||||
|
var args = new UiArguments("setBindingDebugInfo", fnArgs);
|
||||||
|
this._messageBroker.runOnService(args, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a class on an element.
|
* Sets a class on an element.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -89,7 +89,7 @@ import {ViewContainerRef} from 'angular2/src/core/linker/view_container_ref';
|
||||||
import {ViewRef, ViewRef_} from 'angular2/src/core/linker/view_ref';
|
import {ViewRef, ViewRef_} from 'angular2/src/core/linker/view_ref';
|
||||||
|
|
||||||
import {Compiler} from 'angular2/src/core/linker/compiler';
|
import {Compiler} from 'angular2/src/core/linker/compiler';
|
||||||
import {ElementRef} from 'angular2/src/core/linker/element_ref';
|
import {ElementRef, ElementRef_} from 'angular2/src/core/linker/element_ref';
|
||||||
import {TemplateRef} from 'angular2/src/core/linker/template_ref';
|
import {TemplateRef} from 'angular2/src/core/linker/template_ref';
|
||||||
|
|
||||||
import {DomRenderer} from 'angular2/src/platform/dom/dom_renderer';
|
import {DomRenderer} from 'angular2/src/platform/dom/dom_renderer';
|
||||||
|
@ -1597,6 +1597,22 @@ export function main() {
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should reflect property values on template comments',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
var tpl = '<template [ng-if]="ctxBoolProp"></template>';
|
||||||
|
tcb.overrideView(MyComp, new ViewMetadata({template: tpl, directives: [NgIf]}))
|
||||||
|
|
||||||
|
.createAsync(MyComp)
|
||||||
|
.then((fixture) => {
|
||||||
|
fixture.debugElement.componentInstance.ctxBoolProp = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(DOM.getInnerHTML(fixture.debugElement.nativeElement))
|
||||||
|
.toContain('"ng\-reflect\-ng\-if"\: "true"');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('different proto view storages', () => {
|
describe('different proto view storages', () => {
|
||||||
|
@ -1773,6 +1789,7 @@ export function main() {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (DOM.supportsDOMEvents()) {
|
if (DOM.supportsDOMEvents()) {
|
||||||
describe('svg', () => {
|
describe('svg', () => {
|
||||||
it('should support svg elements',
|
it('should support svg elements',
|
||||||
|
@ -1928,8 +1945,8 @@ class PushCmpWithAsyncPipe {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class MyComp {
|
class MyComp {
|
||||||
ctxProp: string;
|
ctxProp: string;
|
||||||
ctxNumProp;
|
ctxNumProp: number;
|
||||||
ctxBoolProp;
|
ctxBoolProp: boolean;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.ctxProp = 'initial value';
|
this.ctxProp = 'initial value';
|
||||||
this.ctxNumProp = 0;
|
this.ctxNumProp = 0;
|
||||||
|
|
|
@ -56,6 +56,7 @@ import {
|
||||||
ServiceMessageBrokerFactory_
|
ServiceMessageBrokerFactory_
|
||||||
} from 'angular2/src/web_workers/shared/service_message_broker';
|
} from 'angular2/src/web_workers/shared/service_message_broker';
|
||||||
import {WebWorkerEventDispatcher} from 'angular2/src/web_workers/worker/event_dispatcher';
|
import {WebWorkerEventDispatcher} from 'angular2/src/web_workers/worker/event_dispatcher';
|
||||||
|
import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection';
|
||||||
|
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
|
@ -112,6 +113,8 @@ export function main() {
|
||||||
var workerRenderProtoViewStore = new RenderProtoViewRefStore(true);
|
var workerRenderProtoViewStore = new RenderProtoViewRefStore(true);
|
||||||
var workerRenderViewStore = new RenderViewWithFragmentsStore(true);
|
var workerRenderViewStore = new RenderViewWithFragmentsStore(true);
|
||||||
return [
|
return [
|
||||||
|
provide(ChangeDetectorGenConfig,
|
||||||
|
{useValue: new ChangeDetectorGenConfig(true, true, false)}),
|
||||||
provide(RenderProtoViewRefStore, {useValue: workerRenderProtoViewStore}),
|
provide(RenderProtoViewRefStore, {useValue: workerRenderProtoViewStore}),
|
||||||
provide(RenderViewWithFragmentsStore, {useValue: workerRenderViewStore}),
|
provide(RenderViewWithFragmentsStore, {useValue: workerRenderViewStore}),
|
||||||
provide(Renderer,
|
provide(Renderer,
|
||||||
|
@ -183,6 +186,22 @@ export function main() {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should update any template comment property/attributes',
|
||||||
|
inject([TestComponentBuilder, Renderer, AsyncTestCompleter],
|
||||||
|
(tcb: TestComponentBuilder, renderer: Renderer, async) => {
|
||||||
|
var tpl = '<template [ng-if]="ctxBoolProp"></template>';
|
||||||
|
tcb.overrideView(MyComp, new ViewMetadata({template: tpl, directives: [NgIf]}))
|
||||||
|
|
||||||
|
.createAsync(MyComp)
|
||||||
|
.then((fixture) => {
|
||||||
|
(<MyComp>fixture.debugElement.componentInstance).ctxBoolProp = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
var el = getRenderElement(fixture.debugElement.elementRef);
|
||||||
|
expect(DOM.getInnerHTML(el)).toContain('"ng-reflect-ng-if": "true"');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should add and remove fragments',
|
it('should add and remove fragments',
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
tcb.overrideView(MyComp, new ViewMetadata({
|
tcb.overrideView(MyComp, new ViewMetadata({
|
||||||
|
@ -232,7 +251,7 @@ export function main() {
|
||||||
class MyComp {
|
class MyComp {
|
||||||
ctxProp: string;
|
ctxProp: string;
|
||||||
ctxNumProp;
|
ctxNumProp;
|
||||||
ctxBoolProp;
|
ctxBoolProp: boolean;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.ctxProp = 'initial value';
|
this.ctxProp = 'initial value';
|
||||||
this.ctxNumProp = 0;
|
this.ctxNumProp = 0;
|
||||||
|
|
Loading…
Reference in New Issue