From 986038242ac0f2b0092f4678a388126d8a568c21 Mon Sep 17 00:00:00 2001 From: gdi2290 Date: Mon, 18 May 2015 09:24:52 -0700 Subject: [PATCH] feat(change_detection): json pipe Closes #1957 --- .../src/change_detection/change_detection.ts | 11 ++- .../src/change_detection/pipes/json_pipe.ts | 80 +++++++++++++++ modules/angular2/src/facade/lang.dart | 5 +- modules/angular2/src/facade/lang.ts | 12 ++- .../change_detection/pipes/json_pipe_spec.js | 97 +++++++++++++++++++ 5 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 modules/angular2/src/change_detection/pipes/json_pipe.ts create mode 100644 modules/angular2/test/change_detection/pipes/json_pipe_spec.js diff --git a/modules/angular2/src/change_detection/change_detection.ts b/modules/angular2/src/change_detection/change_detection.ts index 831b165b67..e2428d253c 100644 --- a/modules/angular2/src/change_detection/change_detection.ts +++ b/modules/angular2/src/change_detection/change_detection.ts @@ -7,6 +7,7 @@ import {ObservablePipeFactory} from './pipes/observable_pipe'; import {PromisePipeFactory} from './pipes/promise_pipe'; import {UpperCaseFactory} from './pipes/uppercase_pipe'; import {LowerCaseFactory} from './pipes/lowercase_pipe'; +import {JsonPipeFactory} from './pipes/json_pipe'; import {NullPipeFactory} from './pipes/null_pipe'; import {ChangeDetection, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces'; import {Injectable} from 'angular2/src/di/decorators'; @@ -55,12 +56,20 @@ export var uppercase: List < PipeFactory >= [new UpperCaseFactory(), new NullPip */ export var lowercase: List < PipeFactory >= [new LowerCaseFactory(), new NullPipeFactory()]; +/** + * Json stringify transform. + * + * @exportedAs angular2/pipes + */ +export var json: List < PipeFactory >= [new JsonPipeFactory(), new NullPipeFactory()]; + export var defaultPipes = { "iterableDiff": iterableDiff, "keyValDiff": keyValDiff, "async": async, "uppercase": uppercase, - "lowercase": lowercase + "lowercase": lowercase, + "json": json }; export var preGeneratedProtoDetectors = {}; diff --git a/modules/angular2/src/change_detection/pipes/json_pipe.ts b/modules/angular2/src/change_detection/pipes/json_pipe.ts new file mode 100644 index 0000000000..9a5e8049a2 --- /dev/null +++ b/modules/angular2/src/change_detection/pipes/json_pipe.ts @@ -0,0 +1,80 @@ +import {isBlank, isPresent, CONST, Json} from 'angular2/src/facade/lang'; +import {Pipe, PipeFactory} from './pipe'; + +// HACK: workaround for Traceur behavior. +// It expects all transpiled modules to contain this marker. +// TODO: remove this when we no longer use traceur +export var __esModule = true; + + +/** + * Implements json transforms to any object. + * + * # Example + * + * In this example we transform the user object to json. + * + * ``` + * @Component({ + * selector: "user-cmp" + * }) + * @View({ + * template: "User: {{ user | json }}" + * }) + * class Username { + * user:Object + * constructor() { + * this.user = { name: "PatrickJS" }; + * } + * } + * + * ``` + * + * @exportedAs angular2/pipes + */ +export class JsonPipe extends Pipe { + _latestRef: any; + _latestValue: any; + constructor() { + super(); + this._latestRef = null; + this._latestValue = null; + } + + onDestroy(): void { + if (isPresent(this._latestValue)) { + this._latestRef = null; + this._latestValue = null; + } + } + + supports(obj): boolean { return true; } + + transform(value): any { + if (value === this._latestRef) { + return this._latestValue; + } else { + return this._prettyPrint(value); + } + } + + _prettyPrint(value) { + this._latestRef = value; + this._latestValue = Json.stringify(value); + return this._latestValue; + } +} + +/** + * Provides a factory for [JsonPipeFactory]. + * + * @exportedAs angular2/pipes + */ +@CONST() +export class JsonPipeFactory extends PipeFactory { + constructor() { super(); } + + supports(obj): boolean { return true; } + + create(cdRef): Pipe { return new JsonPipe(); } +} diff --git a/modules/angular2/src/facade/lang.dart b/modules/angular2/src/facade/lang.dart index b22a260c21..59f12ab607 100644 --- a/modules/angular2/src/facade/lang.dart +++ b/modules/angular2/src/facade/lang.dart @@ -209,7 +209,10 @@ bool assertionsEnabled() { // Can't be all uppercase as our transpiler would think it is a special directive... class Json { static parse(String s) => convert.JSON.decode(s); - static String stringify(data) => convert.JSON.encode(data); + static String stringify(data) { + var encoder = new convert.JsonEncoder.withIndent(" "); + return encoder.convert(data); + } } class DateWrapper { diff --git a/modules/angular2/src/facade/lang.ts b/modules/angular2/src/facade/lang.ts index cf1b1f93ca..9c299e10c3 100644 --- a/modules/angular2/src/facade/lang.ts +++ b/modules/angular2/src/facade/lang.ts @@ -38,6 +38,7 @@ if (assertionsEnabled_) { int = {}; _global.assert = function() {}; } + export {int}; // This function is needed only to properly support Dart's const expressions @@ -45,10 +46,13 @@ export {int}; export function CONST_EXPR(expr: T): T { return expr; } + export function CONST() { return (target) => target; } + export class ABSTRACT {} + export class IMPLEMENTS {} export function isPresent(obj): boolean { @@ -246,7 +250,13 @@ export function print(obj) { } // Can't be all uppercase as our transpiler would think it is a special directive... -export var Json = _global.JSON; +export class Json { + static parse(s: string) { return _global.JSON.parse(s); } + static stringify(data): string { + // Dart doesn't take 3 arguments + return _global.JSON.stringify(data, null, 2); + } +} export class DateWrapper { static fromMillis(ms) { return new Date(ms); } diff --git a/modules/angular2/test/change_detection/pipes/json_pipe_spec.js b/modules/angular2/test/change_detection/pipes/json_pipe_spec.js new file mode 100644 index 0000000000..c46f252edd --- /dev/null +++ b/modules/angular2/test/change_detection/pipes/json_pipe_spec.js @@ -0,0 +1,97 @@ +import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, + AsyncTestCompleter, inject, proxy, SpyObject, IS_DARTIUM} from 'angular2/test_lib'; +import {Json, RegExp, NumberWrapper, StringWrapper} from 'angular2/src/facade/lang'; + +import {JsonPipe} from 'angular2/src/change_detection/pipes/json_pipe'; + +export function main() { + describe("JsonPipe", () => { + var regNewLine = new RegExp('\n'); + var canHasUndefined; // because Dart doesn't like undefined; + var inceptionObj; + var inceptionObjString; + var catString; + var pipe; + + function normalize(obj: string): string { + return StringWrapper.replace(obj, regNewLine, ''); + } + + beforeEach(() => { + inceptionObj = { + dream: { + dream: { + dream: 'Limbo' + } + } + }; + inceptionObjString = "{\n" + + " \"dream\": {\n" + + " \"dream\": {\n" + + " \"dream\": \"Limbo\"\n" + + " }\n" + + " }\n" + + "}"; + + + catString = 'Inception Cat'; + pipe = new JsonPipe(); + }); + + describe("supports", () => { + it("should support objects", () => { + expect(pipe.supports(inceptionObj)).toBe(true); + }); + + it("should support strings", () => { + expect(pipe.supports(catString)).toBe(true); + }); + + it("should support null", () => { + expect(pipe.supports(null)).toBe(true); + }); + + it("should support NaN", () => { + expect(pipe.supports(NumberWrapper.NaN)).toBe(true); + }); + + if (!IS_DARTIUM) { + it("should support undefined", () => { + expect(pipe.supports(canHasUndefined)).toBe(true); + }); + } + + }); + + describe("transform", () => { + it("should return JSON-formatted string", () => { + expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); + }); + + it("should return JSON-formatted string even when normalized", () => { + var dream1 = normalize(pipe.transform(inceptionObj)); + var dream2 = normalize(inceptionObjString); + expect(dream1).toEqual(dream2); + }); + + it("should return JSON-formatted string similar to Json.stringify", () => { + var dream1 = normalize(pipe.transform(inceptionObj)); + var dream2 = normalize(Json.stringify(inceptionObj)); + expect(dream1).toEqual(dream2); + }); + + it("should return same value when nothing has changed since the last call", () => { + expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); + expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); + }); + + }); + + describe("onDestroy", () => { + it("should do nothing when no latest value", () => { + expect(() => pipe.onDestroy()).not.toThrow(); + }); + }); + + }); +}