LP#1775466 Print repairs; rm-ing legacy template parser
authorBill Erickson <berickxx@gmail.com>
Mon, 25 Jun 2018 15:37:35 +0000 (11:37 -0400)
committerBill Erickson <berickxx@gmail.com>
Wed, 5 Sep 2018 14:05:23 +0000 (10:05 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/share/print/print.component.html
Open-ILS/src/eg2/src/app/share/print/print.component.ts
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
Open-ILS/src/eg2/src/app/staff/share/legacy-template.service.ts [deleted file]

index 2f72bda..094e5b7 100644 (file)
@@ -1,10 +1,15 @@
+<!-- 
+Global print container.  
+There should only be one print component active in a page.
+-->
 
-<!-- there should only be one container active at a time -->
-<div id='eg-print-container'>
-  <ng-container *ngIf="template">
-    <!-- refs to compiled templates are inserted inline -->
-    <ng-container *ngTemplateOutlet="template; context:context">
-    </ng-container>
+<!-- container for inline template compilation -->
+<ng-container *ngIf="template">
+  <ng-container *ngTemplateOutlet="template; context:context">
   </ng-container>
+</ng-container>
+
+<!-- container for pre-compiled HTML -->
+<div id='eg-print-container'>
 </div>
 
index 8102831..09ef244 100644 (file)
@@ -18,12 +18,18 @@ export class PrintComponent implements OnInit {
     // Insertion point for externally-compiled templates
     htmlContainer: Element;
 
+    isPrinting: boolean;
+
+    printQueue: PrintRequest[];
+
     constructor(
         private renderer: Renderer2,
         private elm: ElementRef,
         private store: StoreService,
-        private printer: PrintService
-    ) {}
+        private printer: PrintService) {
+        this.isPrinting = false;
+        this.printQueue = [];
+    }
 
     ngOnInit() {
         this.printer.onPrintRequest$.subscribe(
@@ -34,10 +40,22 @@ export class PrintComponent implements OnInit {
     }
 
     handlePrintRequest(printReq: PrintRequest) {
+
+        if (this.isPrinting) {
+            // Avoid print collisions by queuing requests as needed.
+            this.printQueue.push(printReq);
+            return;
+        }
+
+        this.isPrinting = true;
+
         this.applyTemplate(printReq);
 
         // Give templates a chance to render before printing
-        setTimeout(() => this.dispatchPrint(printReq));
+        setTimeout(() => {
+            this.dispatchPrint(printReq)
+            this.reset();
+        });
     }
 
     applyTemplate(printReq: PrintRequest) {
@@ -62,6 +80,18 @@ export class PrintComponent implements OnInit {
         }
     }
 
+    // Clear the print data 
+    reset() {
+        this.isPrinting = false;
+        this.template = null;
+        this.context = null;
+        this.htmlContainer.innerHTML = '';
+
+        if (this.printQueue.length) {
+            this.handlePrintRequest(this.printQueue.pop());
+        }
+    }
+
     dispatchPrint(printReq: PrintRequest) {
 
         if (!printReq.text) {
index 98de443..2dfbb3c 100644 (file)
@@ -12,7 +12,6 @@ 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 {LegacyTemplateService} from '@eg/staff/share/legacy-template.service';
 
 /**
  * Imports the EG common modules and adds modules common to all staff UI's.
@@ -52,7 +51,6 @@ export class StaffCommonModule {
         return {
             ngModule: StaffCommonModule,
             providers: [ // Export staff-wide services
-                LegacyTemplateService,
                 AccessKeyService,
                 StringService,
                 ToastService
index 2f8cea4..dbea15a 100644 (file)
 
 <br/><br/>
 
-<button class="btn btn-info" (click)="testParser()">Test Template Parser</button>
-
-<br/><br/>
-
 <!-- grid stuff -->
 <ng-template #cellTmpl let-row="row" let-col="col" let-userContext="userContext">
   HELLO {{userContext.hello}}
index 63bb2fa..7aaec92 100644 (file)
@@ -13,7 +13,6 @@ import {OrgService} from '@eg/core/org.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 {LegacyTemplateService} from '@eg/staff/share//legacy-template.service';
 
 @Component({
   templateUrl: 'sandbox.component.html'
@@ -54,8 +53,7 @@ export class SandboxComponent implements OnInit {
         private pcrud: PcrudService,
         private strings: StringService,
         private toast: ToastService,
-        private printer: PrintService,
-        private parser: LegacyTemplateService
+        private printer: PrintService
     ) {}
 
     ngOnInit() {
@@ -91,6 +89,12 @@ export class SandboxComponent implements OnInit {
             contextData: {world : this.world},
             printContext: 'default'
         });
+
+        this.printer.print({
+            text: '<b>hello</b>',
+            printContext: 'default'
+        });
+
     }
 
     changeDate(date) {
@@ -126,68 +130,6 @@ export class SandboxComponent implements OnInit {
                 .then(txt => this.toast.success(txt));
         }, 4000);
     }
-
-    testParser() {
-        const template = `
-            <div>
-              <div>This item needs to be routed to
-                <b>{{dest_location.shortname}} AKA {{dest_location.name}}</b>
-              </div>
-              <div ng-if="foo">i should disappear</div>
-              <div ng-if="boolFalse">i should also disappear</div>
-              <div ng-if="boolTrue">i should stick around</div>
-              <div ng-if="dest_address">
-                <div>{{dest_address.street1}}</div>
-                <div>{{dest_address.street2}}</div>
-              </div>
-              <div>Slip Date: {{today | date:foo}}</div>
-              <div>Cost: {{cost | currency}}</div>
-              <ol>
-                <li ng-repeat="copy in copies">
-                  <div>Barcode: {{copy.barcode}}</div>
-                  <div>Title: {{copy.title | limitTo:10}}</div>
-                  <div ng-repeat="part in copy.parts">
-                    part = {{part}}
-                  </div>
-                </li>
-              </ol>
-              <div ng-switch="dest_location.shortname">
-                <div ng-switch-when="BR2">THIS IS BR2</div>
-                <div ng-switch-when="BR1">
-                  THIS IS BR1
-                  <div ng-repeat="cp in copies">
-                    swith bc = {{cp.barcode}}
-                  </div>
-                </div>
-              </div>
-        `;
-
-        const context = {
-            dest_location: {
-                shortname: 'BR1',
-                name: 'Branch Uno',
-            },
-            dest_address: {
-                street1: '123 Pineapple Rd',
-                street2: 'APT #3',
-            },
-            boolTrue: true,
-            boolFalse: false,
-            today: new Date(),
-            cost: 23.3,
-            copies: [
-                {barcode: 'abc123', title: 'welcome to the jungle', parts: ['a', 'b', 'c']},
-                {barcode: 'def456', title: 'hello mudda, hello fadda', parts: ['x', 'y']}
-            ]
-        };
-
-        this.parser.apply(template, context).then(html => {
-            this.printer.print({
-                text: html,
-                printContext: 'default'
-            });
-        });
-    }
 }
 
 
diff --git a/Open-ILS/src/eg2/src/app/staff/share/legacy-template.service.ts b/Open-ILS/src/eg2/src/app/staff/share/legacy-template.service.ts
deleted file mode 100644 (file)
index fbd6265..0000000
+++ /dev/null
@@ -1,295 +0,0 @@
-/**
- * USING EVAL() OPENS A LARGE SECURITY HOLE.  
- * DEPRECATE ME.
- * AngularJS-style minimal template parser.
- * Original use case is supporting AngularJS style print templates.
- * Template context data is applied only once at parsing time, there
- * is no data binding.
- *
- * Supports the following template constructs:
- *  {{variables}}
- *  <element ng-if="expression">
- *  <element ng-repeat="expression">
- *  <element ng-switch="expression">
- *    <child-element ng-switch-when="string">
- */
-import {Injectable, EventEmitter} from '@angular/core';
-import {FormatService} from '@eg/share/util/format.service';
-
-// Internal class for modeling a single template parsing instance.
-class ParserInstance {
-
-    private context: any;
-    private format: FormatService;
-
-    // FormatService injected by ParserService
-    constructor(format: FormatService) {
-        this.format = format;
-    }
-
-    // Given an HTML string and an interpolation context/scope,
-    // process the HTML template declarations, apply variable
-    // replacements, and return the restulting HTML string.
-    parse(html: string, context: any): string {
-
-        // Shallow copy the context since we modify the keys internally
-        this.context = Object.assign({}, context);
-
-        const parser = new DOMParser();
-        const doc = parser.parseFromString(html, 'text/html');
-
-        // Parsing as html wraps the content in an <html><body> wrapper
-        const domNode = doc.getElementsByTagName('body')[0];
-
-        this.traverse(domNode);
-
-        return domNode.innerHTML;
-    }
-
-    // Process each node in the in-progress document.
-    traverse(node: Node) {
-        if (!node) { return; }
-
-        switch (node.nodeType) {
-            case Node.ELEMENT_NODE:
-                const complete = this.processElementNode(node as Element);
-                if (complete) {
-                    return;
-                }
-                break;
-
-            case Node.TEXT_NODE:
-                this.processTextNode(node as Text);
-                break;
-        }
-
-        // Array.from() avoids TS compiler warnings
-        Array.from(node.childNodes).forEach(child => this.traverse(child));
-    }
-
-    // Process expressions found on each element.
-    // Returns true if the node was processed within and needs no
-    // further processing, false otherwise.
-    processElementNode(node: Element): boolean {
-
-        const switchExp = node.getAttribute('ng-switch');
-        if (switchExp) {
-            node.removeAttribute('ng-switch');
-            if (!this.processSwitchExpression(node, switchExp)) {
-                // We removed all child nodes in the switch
-                return true;
-            }
-        }
-
-        const ifExp = node.getAttribute('ng-if');
-        if (ifExp) {
-            node.removeAttribute('ng-if');
-            if (!this.testIfExpression(ifExp)) {
-                // A failing IF expression means the node does not render.
-                node.remove();
-                return true;
-            }
-        }
-
-        const loopExp = node.getAttribute('ng-repeat');
-        if (loopExp) {
-            node.removeAttribute('ng-repeat');
-            this.processLoopExpression(node, loopExp);
-            return true;
-        }
-
-        return false;
-    }
-
-    // Returns true if any of the switch expressions resulted
-    // in true, thus allowing a child node to remain and require
-    // future processing.
-    processSwitchExpression(node: Element, expr: string): boolean {
-
-        // ng-switch only works on string values
-        const targetVal = this.getContextStringValue(expr);
-        let matchNode: Node;
-
-        // Find the switch-matching child node
-        Array.from(node.childNodes).forEach(child => {
-            if (!matchNode && child.nodeType === Node.ELEMENT_NODE) {
-                const elm: Element = child as Element;
-                const val = elm.getAttribute('ng-switch-when');
-                if (val === null || val === undefined) { return; }
-                if (val + '' === targetVal) {
-                    matchNode = child;
-                }
-            }
-        });
-
-        // Remove all child nodes
-        while (node.firstChild) {
-            node.removeChild(node.firstChild);
-        }
-
-        // Add the matching node back if found
-        if (matchNode) {
-            node.appendChild(matchNode);
-            return true;
-        }
-
-        return false; // no matching switch values
-    }
-
-    processLoopExpression(node: Element, expr: string) {
-        const parts = expr.split('in');
-        const listItemKey = parts[0].trim();
-        const listPath = parts[1].trim();
-        const list = this.getContextValueAt(listPath);
-
-        if (!Array.isArray(list)) {
-            throw new Error(`Template value ${listPath} is not an Array`);
-        }
-
-        // Loop over the repeat array and for each iteration, add the
-        // loop variable as a new value in the (temporary) context so
-        // it can be referenced in the eval context.
-        const origContext = this.context;
-        let prevNode: Element = node;
-
-        list.forEach(listItem => {
-            const listCtx = {};
-            listCtx[listItemKey] = listItem;
-            this.context = Object.assign(this.context, listCtx);
-            const newNode = node.cloneNode(true) as Element;
-            // appendChild used internally when needed.
-            prevNode.parentNode.insertBefore(newNode, prevNode.nextSibling);
-            this.traverse(newNode);
-            prevNode = newNode;
-        });
-
-        // recover the original context sans any loop data.
-        this.context = origContext;
-
-        // get rid of the source/template node
-        node.remove();
-    }
-
-    // To evaluate free-form expressions, create an environment
-    // where references to elements in the context are available.
-    // For example:  ng-if="foo.bar" -- the 'foo' key from the
-    // context must be defined in advance for evaluation to succeed.
-    generateEvalEnv() {
-        let env = '';
-        Object.keys(this.context).forEach(key => {
-            env += `var ${key} = this.context['${key}'];\n`;
-        });
-        return env;
-    }
-
-    // Returns true of the IF expression evaluates to true,
-    // false otherwise.
-    testIfExpression(expr: string): boolean {
-
-        const env = this.generateEvalEnv();
-        const evalStr = `${env}; Boolean(${expr})`;
-        // console.debug('ng-if eval string: ', evalStr);
-
-        try {
-            return eval(evalStr);
-        } catch (err) {
-            // console.debug('IF expression failed with: ', err);
-            return false;
-        }
-    }
-
-    // Replace variable {{...}} instances in a given Text node
-    // with the matching data from the context.
-    processTextNode(node: Text) {
-        if (!node || !node.data) { return; }
-
-        const matches = node.data.match(/{{.*?}}/g);
-        if (!matches) { return; }
-
-        matches.forEach(match => {
-            const dotpath = match.replace(/[{}]/g, '');
-            node.replaceData(
-                node.data.indexOf(match),
-                match.length,
-                this.getContextStringValue(dotpath)
-            );
-        });
-    }
-
-    // Returns the context item at the given path
-    getContextValueAt(dotpath: string): any {
-        let idx;
-        let obj = this.context;
-        const parts = dotpath.split('.');
-
-        for (idx = 0; idx < parts.length; idx++) {
-            obj = obj[parts[idx]];
-            if (obj === null || typeof obj !== 'object') {
-                break;
-            }
-        }
-
-        if (idx < parts.length - 1) {
-            // Loop exited before inspecting the whole path.
-            return null;
-        }
-
-        return obj;
-    }
-
-    // Find the value within the context at the given path.
-    getContextStringValue(dotpath: string): string {
-
-        // Variable replacements may contain filters.
-        const pieces = dotpath.split('|').map(p => p.trim());
-        const path = pieces[0];
-        const filter = pieces[1];
-        const data = {
-            datatype: null, // potentially applied below
-            value: this.getContextValueAt(path)
-        };
-
-        // TODO: teach the format service about processing due dates.
-        if (filter) {
-            const fParts = filter.split(':').map(p => p.trim());
-            const fName = fParts[0];
-            const fArgs = fParts.slice(1);
-
-            switch (fName) {
-                case 'date':
-                    data.datatype = 'timestamp';
-                    break;
-                case 'currency':
-                    data.datatype = 'money';
-                    break;
-                case 'limitTo':
-                    const size = fArgs[0];
-                    if (size) {
-                        const offset = fArgs[1] || 0;
-                        data.value =
-                            data.value.substring(offset, offset + size);
-                    }
-                    break;
-            }
-        }
-
-        return this.format.transform(data);
-    }
-}
-
-
-@Injectable()
-export class LegacyTemplateService {
-
-    constructor(private format: FormatService) {}
-
-    // make async for now, may be needed later
-    public apply(html: string, context: any): Promise<string> {
-        const parser = new ParserInstance(this.format);
-
-        return new Promise((resolve, reject) => {
-            resolve(parser.parse(html, context));
-        });
-    }
-}
-