import { Component } from '@angular/core';


@Component({
  selector: 'my-app',
  template: `
  <h3>Tree Example </h3>
  
   <tree-view [dataReader]="'data'" [httpMethod]="'get'"  [httpUrl]="'./app/treedata.json'">
     
     <!-- Need a template for parent
       <ng-template #amexioTreeParent let-data>
          {{data.text}}
      </ng-template> 
     -->
     
     <!-- Need a template for leaf 
     <ng-template #amexioTreeLeaf let-leaf>
         {{leaf.text}}
     </ng-template> -->
     
   </tree-view>
  
    `
})
export class AppComponent {
  
}
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms'; // <-- NgModel lives here
import { HttpModule } from '@angular/http';
import { AppComponent }  from './app.component';
import { TreeViewService } from './treeview.service';
import { TreeViewComponent } from './treeview.component';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule, // <-- import the FormsModule before binding with [(ngModel)],
    HttpModule
  ],
  declarations: [
    AppComponent,
    TreeViewComponent
  ],
  bootstrap: [ AppComponent ],
  providers : [TreeViewService]
})
export class AppModule { }


/*
Copyright 2017 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);


/*
Copyright 2017 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
/* Master Styles */
h1 {
  color: #369;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 250%;
}
h2, h3 {
  color: #444;
  font-family: Arial, Helvetica, sans-serif;
  font-weight: lighter;
}
body {
  margin: 2em;
}
body, input[text], button {
  color: #888;
  font-family: Cambria, Georgia;
}
a {
  cursor: pointer;
  cursor: hand;
}
button {
  font-family: Arial;
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
  cursor: hand;
}
button:hover {
  background-color: #cfd8dc;
}
button:disabled {
  background-color: #eee;
  color: #aaa;
  cursor: auto;
}

/* Navigation link styles */
nav a {
  padding: 5px 10px;
  text-decoration: none;
  margin-right: 10px;
  margin-top: 10px;
  display: inline-block;
  background-color: #eee;
  border-radius: 4px;
}
nav a:visited, a:link {
  color: #607D8B;
}
nav a:hover {
  color: #039be5;
  background-color: #CFD8DC;
}
nav a.active {
  color: #039be5;
}

/* everywhere else */
* {
  font-family: Arial, Helvetica, sans-serif;
}


/*
Copyright 2017 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
<!DOCTYPE html>
<html>
  <head>
    <title>Angular Tree</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css">
    <script src="https://use.fontawesome.com/b39c91206a.js"></script>

    <!-- Polyfills -->
    <script src="https://unpkg.com/core-js/client/shim.min.js"></script>

    <script src="https://unpkg.com/zone.js@0.7.4?main=browser"></script>
    <script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>

    <script src="systemjs.config.js"></script>
    <script>
      System.import('main.js').catch(function(err){ console.error(err); });
    </script>
  </head>

  <body>
    <my-app>Loading...</my-app>
  </body>
</html>
/**
 * WEB ANGULAR VERSION
 * (based on systemjs.config.js in angular.io)
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
  System.config({
    // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
    transpiler: 'ts',
    typescriptOptions: {
      // Copy of compiler options in standard tsconfig.json
      "target": "es5",
      "module": "commonjs",
      "moduleResolution": "node",
      "sourceMap": true,
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true,
      "lib": ["es2015", "dom"],
      "noImplicitAny": true,
      "suppressImplicitAnyIndexErrors": true
    },
    meta: {
      'typescript': {
        "exports": "ts"
      }
    },
    paths: {
      // paths serve as alias
      'npm:': 'https://unpkg.com/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      'app': 'app',

      // angular bundles
      '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
      '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
      '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
      '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',

      // other libraries
      'rxjs':                      'npm:rxjs@5.0.1',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',
      'ts':                        'npm:plugin-typescript@5.2.7/lib/plugin.js',
      'typescript':                'npm:typescript@2.3.2/lib/typescript.js',

    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: './main.ts',
        defaultExtension: 'ts',
        meta: {
          './*.ts': {
            loader: 'systemjs-angular-loader.js'
          }
        }
      },
      rxjs: {
        defaultExtension: 'js'
      }
    }
  });

})(this);

/*
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;

module.exports.translate = function(load){
  if (load.source.indexOf('moduleId') != -1) return load;

  var url = document.createElement('a');
  url.href = load.address;

  var basePathParts = url.pathname.split('/');

  basePathParts.pop();
  var basePath = basePathParts.join('/');

  var baseHref = document.createElement('a');
  baseHref.href = this.baseURL;
  baseHref = baseHref.pathname;

  if (!baseHref.startsWith('/base/')) { // it is not karma
    basePath = basePath.replace(baseHref, '');
  }

  load.source = load.source
    .replace(templateUrlRegex, function(match, quote, url){
      var resolvedUrl = url;

      if (url.startsWith('.')) {
        resolvedUrl = basePath + url.substr(1);
      }

      return 'templateUrl: "' + resolvedUrl + '"';
    })
    .replace(stylesRegex, function(match, relativeUrls) {
      var urls = [];

      while ((match = stringRegex.exec(relativeUrls)) !== null) {
        if (match[2].startsWith('.')) {
          urls.push('"' + basePath + match[2].substr(1) + '"');
        } else {
          urls.push('"' + match[2] + '"');
        }
      }

      return "styleUrls: [" + urls.join(', ') + "]";
    });

  return load;
};
{
  "data": [
    {
    "text": "Web App",
    "expanded": true,
    "checked" : false,
    "icon" : "fa fa-plus-square-o fa-fw",
    "children": [
      {
        "text": "app",
        "expanded": true,
        "checked" : false,
        "children": [
          {
            "leaf": true,
            "checked" : false,
            "text": "Application.js"
          }
        ]
      },
      {
        "text": "button",
        "expanded": true,
        "checked" : false,
        "icon" : "fa fa-database fa-fw",
        "children": [
          {
            "leaf": true,
            "checked" : false,
            "text": "Button.js"
          },
          {
            "leaf": true,
            "checked" : false,
            "text": "Cycle.js"
          },
          {
            "leaf": true,
            "checked" : false,
            "text": "Split.js"
          }
        ]
      },
      {
        "text": "container",
        "expanded": true,
        "checked" : false,
        "icon" : "fa fa-database fa-fw",
        "children": [
          {
            "leaf": true,
            "checked" : false,
            "text": "ButtonGroup.js"
          },
          {
            "leaf": true,
            "checked" : false,
            "text": "Container.js"
          },
          {
            "leaf": true,
            "checked" : false,
            "text": "Viewport.js",
            "expanded" : false,
            "children":[],
            "lazy":{
              "httpUrl":"data/treeview.json",
              "httpMethod": "get"
            }
          }
        ]
      },
      {
        "text": "core",
        "expanded": true,
        "icon":"fa fa-database fa-fw",
        "children": [
          {
            "text": "dom",
            "expanded": false,
            "icon":"fa fa-database fa-fw",
            "children": [
              {
                "leaf": true,
                "text": "Element.form.js"
              },
              {
                "leaf": true,
                "text": "Element.static-more.js"
              }
            ]
          }
        ]
      }
    ]
  }]
}
import {
  OnInit, Input, Component, SimpleChange, EventEmitter, Output, SimpleChanges,
  ContentChildren, QueryList, TemplateRef
} from "@angular/core";
import {TreeViewService} from "./treeview.service";

@Component({
    selector : 'tree-view',
    template : `
   
      <ul style="list-style-type: none;">
        <li style="cursor: pointer" *ngFor="let treeData of data">
          <div >
            <i class="fa " [ngClass]="{'fa-angle-down': treeData.expanded, 'fa-angle-right': (!treeData.expanded && treeData.children)}" (click)="toggle(treeData)"> </i>
            
            <label style="cursor: pointer" (click)="emitData(treeData)">
              
              <!-- If user doesnt define custom template -->
              <!-- null check on parentTmp doesnt work ->
              
              <ng-container *ngIf="parentTmp.length == 0">{{ treeData.text }}</ng-container>
              
              <!-- Else user defines custom markup -->
              <ng-template *ngIf="parentTmp.length > 0" [ngTemplateOutlet]="parentTmp" [ngOutletContext]="{ $implicit: { text: treeData.text } }"></ng-template>
              
            </label>
            
          </div>
          
          <div *ngIf="treeData.expanded && treeData.expanded  == true">
            <ul style="list-style-type: none;">
              <li style="cursor: pointer" *ngFor="let leaf of treeData.children">
                <div>
                  <i class="fa " [ngClass]="{'fa-angle-down': leaf.expanded, 'fa-angle-right': (!leaf.expanded && leaf.children)}" (click)="toggle(leaf)"> </i>
                  
                  <label (click)="emitData(leaf)">
                    {{ leaf.text }}
                  </label>
                  
                </div>
                <div *ngIf="leaf.expanded && leaf.expanded  == true">
                  <tree-view [dataTableBindData]="leaf" [dataReader]="'children'" (selectedRecord)="setSelectedRecord($event)"></tree-view>
                </div>
              </li>
            </ul>

          </div>
        </li>
      </ul>
      
    `,
    providers :[TreeViewService]
})

export class TreeViewComponent implements  OnInit{

    @Input()
    httpUrl : string;

    @Input()
    httpMethod : string;

    @Input()
    dataReader : string;

    @Input()
    dataTableBindData : any;

    @Output()
    selectedRecord : any = new EventEmitter<any>();

    @ContentChildren('amexioTreeParent')   parentTmp : TemplateRef<any>;

    @ContentChildren('amexioTreeLeaf')     leafTmp : TemplateRef<any>;


    data : any;

    lazyNode : any;

    constructor (private  treeViewService : TreeViewService){
    }

    ngOnInit(){

    }

    ngAfterViewInit(){
        if(this.httpMethod && this.httpUrl){
            this.treeViewService.fetchData(this,this.httpUrl,this.httpMethod);
        }
        else if(this.dataTableBindData){
            this.setData(this.dataTableBindData);
        }
        // console.log(this.template);

        console.log(this.parentTmp);
        console.log(this.leafTmp);
    }

    ngOnChanges(change : SimpleChanges){
        if(change['dataTableBindData']){
            let data = change['dataTableBindData'].currentValue;
            if(data){
                this.setData(data)
            }
        }
    }

    setData(httpResponse: any){
        let treedata = this.getResponseData(httpResponse);
        if(treedata){
            this.data = treedata;
        }
    }

    getResponseData(httpResponse : any){
        let responsedata = httpResponse;
        let dr = this.dataReader.split(".");
        for(let ir = 0 ; ir<dr.length; ir++){
            responsedata = responsedata[dr[ir]];
        }
        return responsedata;
    }

    toggle(treeData : any){
        if(!treeData.leaf)
            treeData.expanded = !treeData.expanded;

        if(treeData.lazy && treeData.children && treeData.children.length<=0){
            this.lazyNode = treeData;
            this.treeViewService.fetchLazyData(this,treeData.lazy.httpUrl,treeData.lazy.httpMethod);
        }
    }

    setLazyData(httpResponse: any){
            delete this.lazyNode.leaf;
            this.lazyNode['expanded'] = true;
            for(let di = 0 ; di < httpResponse.data.length; di++){
                this.lazyNode.children.push(httpResponse.data[0]);
            }
    }
    setSelectedRecord(treeData : any){
        this.emitData(treeData);
    }

    emitData(treeData :any){
        this.selectedRecord.emit(JSON.parse(JSON.stringify(treeData)));
    }
}
import {Injectable} from "@angular/core";
import {Http, Headers, RequestOptions} from "@angular/http";

@Injectable()
export class  TreeViewService {

    parentRef : any;
    responseData : any;

    constructor(private _http : Http){
    }

    fetchData(parentRef : any, serviceUrl : string, methodType: string){
        this.parentRef = parentRef;
        let requestJson = {};
        let headers = new Headers({ 'Content-Type': 'application/json;charset=UTF-8'  });
        let options = new RequestOptions({headers : headers,method : methodType});
        if(methodType == "post"){
            this._http.post(serviceUrl,requestJson,options).subscribe(
                response=>{
                    this.responseData = response.json();
                },
                error=>{
                },
                ()=>{
                    this.setData();
                }
            );
        }else if(methodType == "get"){
            this._http.get(serviceUrl,options).subscribe(
                response=>{
                    this.responseData = response.json();
                },
                error=>{
                },
                ()=>{
                    this.setData();
                }
            );
        }
    }

    setData (){
        this.parentRef.setData(this.responseData);
    }


    fetchLazyData(parentRef : any, serviceUrl : string, methodType: string){
        this.parentRef = parentRef;
        let requestJson = {};
        let headers = new Headers({ 'Content-Type': 'application/json;charset=UTF-8'  });
        let options = new RequestOptions({headers : headers,method : methodType});
        if(methodType == "post"){
            this._http.post(serviceUrl,requestJson,options).subscribe(
                response=>{
                    this.responseData = response.json();
                },
                error=>{
                },
                ()=>{
                    this.setLazyData();
                }
            );
        }else if(methodType == "get"){
            this._http.get(serviceUrl,options).subscribe(
                response=>{
                    this.responseData = response.json();
                },
                error=>{
                },
                ()=>{
                    this.setLazyData();
                }
            );
        }
    }

    setLazyData (){
        this.parentRef.setLazyData(this.responseData);
    }

}