LP#1775466 Dynamic component via string/url support
authorBill Erickson <berickxx@gmail.com>
Wed, 13 Jun 2018 14:56:30 +0000 (10:56 -0400)
committerBill Erickson <berickxx@gmail.com>
Wed, 13 Jun 2018 14:56:30 +0000 (10:56 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/share/dynamic-component/dynamic.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/common.module.ts
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts

diff --git a/Open-ILS/src/eg2/src/app/share/dynamic-component/dynamic.component.ts b/Open-ILS/src/eg2/src/app/share/dynamic-component/dynamic.component.ts
new file mode 100644 (file)
index 0000000..5dba7ba
--- /dev/null
@@ -0,0 +1,69 @@
+import {Component, OnInit, ViewChild, Input} from '@angular/core';
+import {Compiler, ViewContainerRef, NgModule} from '@angular/core';
+import {HttpClient} from '@angular/common/http';
+
+/**
+ * Render HTML content derived from a string or a URL path, 
+ * interpolating the provided context data along the way.
+ */
+
+@Component({
+  selector: 'eg-dynamic-component',
+  template: '<ng-container #container></ng-container>'
+})
+
+export class DynamicComponent implements OnInit {
+
+    @ViewChild('container', {read: ViewContainerRef})
+    private container: ViewContainerRef;
+
+    constructor(
+        private compiler: Compiler,
+        private http: HttpClient
+    ) {}
+
+    ngOnInit() {
+    }
+
+    buildFromString(template: string, context: any = {}) {
+        this.addComponent(template, context);
+    }
+
+    // Returns a promise which resolves if the requested URL
+    // was found, rejected otherwise.
+    buildFromUrl(url: string, context: any = {}): Promise<void> {
+        return this.http.get(url, {responseType: 'text'}).toPromise()
+        .then(
+            html => {
+                console.debug(`Loaded dynamic content from: ${url}`);
+                this.addComponent(html, context);
+            },
+            notFound => {
+                console.debug(
+                    `Unable to fetch dynamic component URL: ${url}`, notFound);    
+            }
+        )
+    }
+
+    // Method below taken practically verbatim from
+    // https://stackoverflow.com/a/39507831
+    private addComponent(template: string, context: any) {
+        @Component({template}) class TemplateComponent {}
+        @NgModule({declarations: [TemplateComponent]}) class TemplateModule {}
+
+        const mod = 
+            this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
+
+        const factory = mod.componentFactories.find((comp) =>
+          comp.componentType === TemplateComponent
+        );
+
+        const component = this.container.createComponent(factory);
+        Object.assign(component.instance, context);
+        // If context changes at a later stage, the change detection
+        // may need to be triggered manually:
+        // component.changeDetectorRef.detectChanges();
+    }
+
+}
+
index 2dfbb3c..60d23d6 100644 (file)
@@ -1,4 +1,5 @@
 import {NgModule, ModuleWithProviders} from '@angular/core';
+import {HttpClientModule} from '@angular/common/http';
 import {EgCommonModule} from '@eg/common.module';
 import {StaffBannerComponent} from './share/staff-banner.component';
 import {OrgSelectComponent} from '@eg/share/org-select/org-select.component';
@@ -12,6 +13,7 @@ import {StringComponent} from '@eg/share/string/string.component';
 import {StringService} from '@eg/share/string/string.service';
 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
 import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
+import {DynamicComponent} from '@eg/share/dynamic-component/dynamic.component';
 
 /**
  * Imports the EG common modules and adds modules common to all staff UI's.
@@ -27,12 +29,15 @@ import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
     StringComponent,
     OpChangeComponent,
     FmRecordEditorComponent,
-    DateSelectComponent
+    DateSelectComponent,
+    DynamicComponent
   ],
   imports: [
+    HttpClientModule,
     EgCommonModule
   ],
   exports: [
+    HttpClientModule,
     EgCommonModule,
     StaffBannerComponent,
     OrgSelectComponent,
@@ -42,7 +47,8 @@ import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
     StringComponent,
     OpChangeComponent,
     FmRecordEditorComponent,
-    DateSelectComponent
+    DateSelectComponent,
+    DynamicComponent
   ]
 })
 
index bc6a8d6..87fc90d 100644 (file)
 <button class="btn btn-secondary" (click)="doPrint()">Test Print</button>
 <ng-template #printTemplate let-context>Hello, {{context.world}}!</ng-template>
 
+<br/><br/>
+
+<!-- dynamic template/component example -->
+<b>Dynamic content: </b>
+<eg-dynamic-component #dynamic></eg-dynamic-component>
+
+<b>Dynamic content via URL: </b>
+<eg-dynamic-component #dynamicUrl></eg-dynamic-component>
+
+
+<br/><br/>
 
 <!-- grid stuff -->
 <ng-template #cellTmpl let-row="row" let-col="col" let-userContext="userContext">
index 11cf154..7ff0ac0 100644 (file)
@@ -12,6 +12,7 @@ import {PcrudService} from '@eg/core/pcrud.service';
 import {Pager} from '@eg/share/util/pager';
 import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
 import {PrintService} from '@eg/share/print/print.service';
+import {DynamicComponent} from '@eg/share/dynamic-component/dynamic.component';
 
 @Component({
   templateUrl: 'sandbox.component.html'
@@ -27,6 +28,9 @@ export class SandboxComponent implements OnInit {
     @ViewChild('printTemplate')
     private printTemplate: TemplateRef<any>;
 
+    @ViewChild('dynamic') private dynamic: DynamicComponent;
+    @ViewChild('dynamicUrl') private dynamicUrl: DynamicComponent;
+
     // @ViewChild('helloStr') private helloStr: StringComponent;
 
     gridDataSource: GridDataSource = new GridDataSource();
@@ -66,6 +70,15 @@ export class SandboxComponent implements OnInit {
                 order_by: {cbt: 'name'}
             });
         };
+
+        this.dynamic.buildFromString(
+            '<b>HELLO {{world}}</b>', {world: 'world'});
+
+        /*
+        // Assumes a file on the server at this URL
+        this.dynamicUrl.buildFromUrl(
+            '/test-template.html', {world: 'world'});
+        */
     }
 
     doPrint() {
@@ -111,3 +124,4 @@ export class SandboxComponent implements OnInit {
     }
 }
 
+