<!DOCTYPE html>
<html>

  <head>
    <base href="." />
    <script type="text/javascript" charset="utf-8">
      window.AngularVersionForThisPlunker = 'latest'
    </script>
    <title>angular playground</title>
    <link rel="stylesheet" href="style.css" />
    <script src="https://unpkg.com/core-js@2.4.1/client/shim.min.js"></script>
    <script src="https://unpkg.com/zone.js/dist/zone.js"></script>
    <script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
    <script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
    <script src="config.js"></script>
    <script>
    System.import('app')
      .catch(console.error.bind(console));
  </script>
  </head>

  <body>
    <my-app>
    loading...
  </my-app>
  </body>

</html>
/* Styles go here */

.json-formatter-dark.json-formatter-row,.json-formatter-row{font-family:monospace}.json-formatter-dark.json-formatter-row .toggler.open:after,.json-formatter-row .toggler.open:after{transform:rotate(90deg)}.json-formatter-row,.json-formatter-row a,.json-formatter-row a:hover{color:#000;text-decoration:none}.json-formatter-row .json-formatter-row{margin-left:1em}.json-formatter-row .children.empty{opacity:.5;margin-left:1em}.json-formatter-row .children.empty.object:after{content:"No properties"}.json-formatter-row .children.empty.array:after{content:"[]"}.json-formatter-row .string{color:green;white-space:pre;word-wrap:break-word}.json-formatter-row .number{color:#00f}.json-formatter-row .boolean{color:red}.json-formatter-row .null{color:#855a00}.json-formatter-row .undefined{color:#ca0b69}.json-formatter-row .function{color:#ff20ed}.json-formatter-row .date{background-color:rgba(0,0,0,.05)}.json-formatter-row .url{text-decoration:underline;color:#00f;cursor:pointer}.json-formatter-row .bracket{color:#00f}.json-formatter-row .key{color:#00008b;cursor:pointer}.json-formatter-row .constructor-name{cursor:pointer}.json-formatter-row .toggler{font-size:.8em;line-height:1.2em;vertical-align:middle;opacity:.6;cursor:pointer}.json-formatter-row .toggler:after{display:inline-block;transition:transform .1s ease-in;content:"►"}.json-formatter-row>a>.thumbnail-text{opacity:0;transition:opacity .15s ease-in;font-style:italic}.json-formatter-row:hover>a>.thumbnail-text{opacity:.6}.json-formatter-dark.json-formatter-row,.json-formatter-dark.json-formatter-row a,.json-formatter-dark.json-formatter-row a:hover{color:#fff;text-decoration:none}.json-formatter-dark.json-formatter-row .json-formatter-row{margin-left:1em}.json-formatter-dark.json-formatter-row .children.empty{opacity:.5;margin-left:1em}.json-formatter-dark.json-formatter-row .children.empty.object:after{content:"No properties"}.json-formatter-dark.json-formatter-row .children.empty.array:after{content:"[]"}.json-formatter-dark.json-formatter-row .string{color:#31f031;white-space:pre;word-wrap:break-word}.json-formatter-dark.json-formatter-row .number{color:#66c2ff}.json-formatter-dark.json-formatter-row .boolean{color:#ec4242}.json-formatter-dark.json-formatter-row .null{color:#eec97d}.json-formatter-dark.json-formatter-row .undefined{color:#ef8fbe}.json-formatter-dark.json-formatter-row .function{color:#fd48cb}.json-formatter-dark.json-formatter-row .date{background-color:rgba(255,255,255,.05)}.json-formatter-dark.json-formatter-row .url{text-decoration:underline;color:#027bff;cursor:pointer}.json-formatter-dark.json-formatter-row .bracket{color:#9494ff}.json-formatter-dark.json-formatter-row .key{color:#23a0db;cursor:pointer}.json-formatter-dark.json-formatter-row .constructor-name{cursor:pointer}.json-formatter-dark.json-formatter-row .toggler{font-size:.8em;line-height:1.2em;vertical-align:middle;opacity:.6;cursor:pointer}.json-formatter-dark.json-formatter-row .toggler:after{display:inline-block;transition:transform .1s ease-in;content:"►"}.json-formatter-dark.json-formatter-row>a>.thumbnail-text{opacity:0;transition:opacity .15s ease-in;font-style:italic}.json-formatter-dark.json-formatter-row:hover>a>.thumbnail-text{opacity:.6}
### Angular Starter Plunker - Typescript
var angularVersion;
if(window.AngularVersionForThisPlunker === 'latest'){
  angularVersion = ''; //picks up latest
}
else {
  angularVersion = '@' + window.AngularVersionForThisPlunker;
}

System.config({
  //use typescript for compilation
  transpiler: 'typescript',
  //typescript compiler options
  typescriptOptions: {
    emitDecoratorMetadata: true
  },
  paths: {
    'npm:': 'https://unpkg.com/'
  },
  //map tells the System loader where to look for things
  map: {
    
    'app': './src',
    '@angular/core': 'npm:@angular/core'+ angularVersion + '/bundles/core.umd.js',
    '@angular/common': 'npm:@angular/common' + angularVersion + '/bundles/common.umd.js',
    '@angular/compiler': 'npm:@angular/compiler' + angularVersion  + '/bundles/compiler.umd.js',
    '@angular/platform-browser': 'npm:@angular/platform-browser' + angularVersion + '/bundles/platform-browser.umd.js',
    '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic' + angularVersion + '/bundles/platform-browser-dynamic.umd.js',
    '@angular/http': 'npm:@angular/http' + angularVersion + '/bundles/http.umd.js',
    '@angular/router': 'npm:@angular/router' + angularVersion +'/bundles/router.umd.js',
    '@angular/forms': 'npm:@angular/forms' + angularVersion + '/bundles/forms.umd.js',
    '@angular/animations': 'npm:@angular/animations' + angularVersion + '/bundles/animations.umd.js',
    '@angular/platform-browser/animations': 'npm:@angular/platform-browser' + angularVersion + '/bundles/platform-browser-animations.umd.js',
    '@angular/animations/browser': 'npm:@angular/animations' + angularVersion + '/bundles/animations-browser.umd.js',
    
    '@angular/core/testing': 'npm:@angular/core' + angularVersion + '/bundles/core-testing.umd.js',
    '@angular/common/testing': 'npm:@angular/common' + angularVersion + '/bundles/common-testing.umd.js',
    '@angular/compiler/testing': 'npm:@angular/compiler' + angularVersion + '/bundles/compiler-testing.umd.js',
    '@angular/platform-browser/testing': 'npm:@angular/platform-browser' + angularVersion + '/bundles/platform-browser-testing.umd.js',
    '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic' + angularVersion + '/bundles/platform-browser-dynamic-testing.umd.js',
    '@angular/http/testing': 'npm:@angular/http' + angularVersion + '/bundles/http-testing.umd.js',
    '@angular/router/testing': 'npm:@angular/router' + angularVersion + '/bundles/router-testing.umd.js',
    'tslib': 'npm:tslib@1.6.1',
    'rxjs': 'npm:rxjs',
    'typescript': 'npm:typescript@2.2.1/lib/typescript.js'
  },
  //packages defines our app package
  packages: {
    app: {
      main: './main.ts',
      defaultExtension: 'ts'
    },
    rxjs: {
      defaultExtension: 'js'
    }
  }
});
//main entry point
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app';

platformBrowserDynamic().bootstrapModule(AppModule)
//our root app component
import {Component, NgModule, VERSION} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { JsonFormatterModule } from './json-formatter/json-formatter.module';

@Component({
  selector: 'my-app',
  template: `
  <h2>Json formatter angular2 based on https://github.com/mohsen1/json-formatter </h2>
   <json-formatter [open]="true" [json]="obj"></json-formatter>
  `,
})
export class App {
  obj = {
    numbers: [
        1,
        2,
        3
    ],
    boolean: true,
    'null': null,
    number: 123,
    anObject: {
        a: 'b',
        c: 'd',
        e: 'f\"'
    },
    string: 'Hello World',
    url: 'https://github.com/mohsen1/json-formatter',
    date: 'Sun Aug 03 2014 20:46:55 GMT-0700 (PDT)',
    func: function add(a, b) {
        return a + b;
    }
  }
}

@NgModule({
  imports: [ BrowserModule, JsonFormatterModule ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {}
import { NgModule } from '@angular/core';

import { JsonFormatterComponent } from './json-formatter.component';
import { CommonModule } from '@angular/common';

@NgModule({
    imports: [CommonModule],
    exports: [JsonFormatterComponent],
    declarations: [JsonFormatterComponent],
    providers: [],
})
export class JsonFormatterModule { }
import { Component, Input, OnInit } from '@angular/core';
import { getObjectName, getPreview, getType, getValuePreview } from './utils';


const JSONFormatterConfig = {
  hoverPreviewEnabled: false,
  hoverPreviewArrayCount: 100,
  hoverPreviewFieldCount: 5
};

@Component({
  selector: 'json-formatter',
  template: `
    <div class="json-formatter-row">
      <a (click)="toggleOpen()">
        <span class="toggler" [class.open]="isOpen" *ngIf="isObject"></span>
        <span class="key" *ngIf="hasKey"><span class="key-text">{{key}}</span><span class="colon">:</span></span>
        <span class="value">
        <span *ngIf="isObject">
          <span class="constructor-name">{{ constructorName }}</span>
          <span *ngIf="isArray"><span class="bracket">[</span><span class="number">{{json.length}}</span><span
            class="bracket">]</span></span>
        </span>
        <span *ngIf="!isObject" (click)="openLink(isUrl)" class="{{type}}"
              [ngClass]="{date: isDate, url: isUrl}">{{parseValue(json)}}</span>
      </span>
        <span *ngIf="showThumbnail()" class="thumbnail-text">
        {{getThumbnail()}}
      </span>
      </a>
      <div class="children" *ngIf="keys?.length && isOpen">
        <json-formatter *ngFor="let key of keys; trackBy: trackByFn" [json]="json[key]" [key]="key"
                        [open]="childrenOpen()"></json-formatter>
      </div>
      <div class="children empty object" *ngIf="isEmptyObject()"></div>
      <div class="children empty array" *ngIf="!keys?.length && isOpen && isArray"></div>
    </div>
  `
})
export class JsonFormatterComponent implements OnInit {
  @Input() json: any;
  @Input() key;
  @Input() open: number;

  isArray;
  isObject;
  isUrl;
  isDate;
  type;

  hasKey;
  keys: any;
  isOpen: boolean;
  constructorName: string;

  ngOnInit() {
    this.isArray = Array.isArray(this.json);
    this.isObject = this.json != null && typeof this.json === 'object';
    this.type = getType(this.json);
    this.hasKey = typeof this.key !== 'undefined';
    this.isOpen = this.open && this.open > 0

    this.constructorName = getObjectName(this.json);
    if (this.isObject) {
      this.keys = Object.keys(this.json).map((key) => key === '' ? '""' : key);
    }
    if (this.type === 'string') {
      if ((new Date(this.json)).toString() !== 'Invalid Date') {
        this.isDate = true;
      }
      if (this.json.indexOf('http') === 0) {
        this.isUrl = true;
      }
    }
  }

  isEmptyObject() {
    return this.keys && !this.keys.length && this.isOpen && !this.isArray;
  }

  toggleOpen() {
    this.isOpen = !this.isOpen;
  }

  childrenOpen() {
    return this.open > 1 ? this.open - 1 : 0;
  }

  openLink(isUrl) {
    if (isUrl) {
      window.location.href = this.json;
    }
  }

  parseValue(value) {
    return getValuePreview(this.json, value);
  }

  showThumbnail() {
    return this.isObject && !this.isOpen;
  }

  getThumbnail() {
    if (this.isArray) {
      // if array length is greater then 100 it shows "Array[101]"
      if (this.json.length > JSONFormatterConfig.hoverPreviewArrayCount) {
        return 'Array[' + this.json.length + ']';
      } else {
        return '[' + this.json.map(getPreview).join(', ') + ']';
      }
    } else {
      // the first five keys (like Chrome Developer Tool)
      const narrowKeys = this.keys.slice(0, JSONFormatterConfig.hoverPreviewFieldCount);
      // json value schematic information
      const kvs = narrowKeys.map(key => key + ':' + getPreview(this.json[key]));

      // if keys count greater then 5 then show ellipsis
      const ellipsis = this.keys.length >= 5 ? '…' : '';

      return '{' + kvs.join(', ') + ellipsis + '}';
    }
  }

  trackByFn(i) {
    return i;
  }
}
export function escapeString(str) {
    return str.replace('"', '\"');
}


// From http://stackoverflow.com/a/332429
export function getObjectName(object) {
    if (object === undefined) {
        return '';
    }
    if (object === null) {
        return 'Object';
    }
    if (typeof object === 'object' && !object.constructor) {
        return 'Object';
    }

    //ES6 default gives name to constructor
    if (object.__proto__ !== undefined && object.__proto__.constructor !== undefined && object.__proto__.constructor.name !== undefined) {
        return object.__proto__.constructor.name;
    }

    var funcNameRegex = /function (.{1,})\(/;
    var results = (funcNameRegex).exec((object).constructor.toString());
    if (results && results.length > 1) {
        return results[1];
    } else {
        return '';
    }
}

export function getType(object) {
    if (object === null) { return 'null'; }
    return typeof object;
}

export function getValuePreview (object, value) {
    var type = getType(object);

    if (type === 'null' || type === 'undefined') { return type; }

    if (type === 'string') {
        value = '"' + escapeString(value) + '"';
    }
    if (type === 'function'){
        // Remove content of the function
        return object.toString()
                .replace(/[\r\n]/g, '')
                .replace(/\{.*\}/, '') + '{…}';

    }
    return value;
}

export function getPreview(object) {
    var value = '';
    if (typeof object === 'object') {
        value = getObjectName(object);
        if (Array.isArray(object)) {
            value += '[' + object.length + ']';
        }
    } else {
        value = getValuePreview(object, object);
    }
    return value;
}