Document scripting

The merge process populates document template with dynamic content. This is done in few basic steps and the document script receives events which allow execution of custom logic.

Events

Document loaded

The name (or key) used to identify the first event is document:loaded. This is called when the source document template (a .shape file) is loaded into memory. The state of the DOM at this point reflects the moment when the document was last saved in Document Editor.

Data loaded

The document:data:loaded event is triggered when the data source (eg XML or JSON) is loaded into documents data DOM, under document\data element.

Data populated

The document:data:populated event happens after the data DOM values get populated into layout. This process is driven by the bindings between layout and data model. Field elements in design get their values formatted and assigned.

In this stage repeatable fragments get instantiated but no overflow or positioning is recalculated. When there is a container fragment with invoice lines, then all line instances are added to the first container, even when there is obviously more lines than will fit in available area.

Layout processed

The document:layout:processed event happens after the layout rules and overflows have been processed. In this current state the document is ready for printing, conversion into PDF output etc. Each element has its final position calculated, so content changes made by script will not cause re-pagination.

At this point one may consider assigning values to page number fields, but it is no longer viable to add or remove large sections to content. After this stage the document will be rendered to the final output form using appropriate driver.

Scripting by simple examples

At each of above steps, the events are published to the JavaScript engine which runs document scripts. This makes it possible for the script code to modify the document and implement custom logic. The most common scenario is calculation of additonal values after the document data DOM is populated.

The document script is a JavaScript file that in its minimal form looks like this:

((merge) => {});

The default skeleton script simply declaring callback methods for common events:

((merge) => {

    merge.subscribe('document:loaded', () => {
        merge.log.info('document:loaded');
    });

    merge.subscribe('document:data:loaded', () => {
        merge.log.info('document:data:loaded');
    });

    merge.subscribe('document:data:populated', () => {
        merge.log.info('document:data:populated');
    });

    merge.subscribe('document:layout:processed', () => {
        merge.log.info('document:layout:processed');
    });
});

The same can be alternatively written by using shorthand methods:

((merge) => {

    merge.onDocumentLoaded(() => {
        merge.log.info('document:loaded');
    });

    merge.onDocumentDataLoaded(() => {
        merge.log.info('document:data:loaded');
    });

    merge.onDocumentDataPopulated(() => {
        merge.log.info('document:data:populated');
    });

    merge.onDocumentLayoutProcessed(() => {
        merge.log.info('document:layout:processed');
    });
});

The merge context object provides logging facilities, access to the current document and can be used for subscribing to the events. Following script will issue information message (level 2) and subscribe to the "data populated" event when script gets loaded.

((merge) => {
    merge.log.info('extension loaded');
    merge.subscribe('document:data:populated', () => {
        merge.log.info('data populated');
    });
});

Traverse through document data DOM:

((merge) => {

    const reportAll = (items) => {

        merge.log.info('data items:');
        items.forEach((item) => {
            merge.log.info(` item.name: ${item.name}`);
        });
    };

    merge.subscribe('document:data:populated', () => {
        reportAll(merge.document.data.children);
    });
});

Traverse through whole scripting model:

((merge) => {

    const inspect = (name, object) => {
        merge.log.info(name);
        if (typeof object === 'object')
            for (let property in object)
                inspect(name + '.' + property, object[property]);
    };

    merge.onDocumentLoaded(() => {
        inspect('merge', merge);
    });
});

Accessing values in data DOM. Lets consider this simple data DOM:

<data>
    <item name="InvoiceNumber" />
</data>

To modify data DOM values from the script after the XML data is loaded:

((merge) => {
    merge.subscribe('document:data:loaded', () => {
        merge.log.info(`InvoiceNumber = ${merge.document.data.$InvoiceNumber}`);
        merge.document.data.$InvoiceNumber = '123456';
        merge.log.info(`InvoiceNumber = ${merge.document.data.$InvoiceNumber}`);
    });
});

Scripting reference

Context object

The merge process is exposed to script as an object that provides access to current document and the logging facility. The merge context allows a script to subscribe handlers to events and makes it also possible to define and trigger custom events.

Merge object has following methods and properties:

  • log
  • document
  • publish
  • subscribe

Context log object

The log allows the script to output messages through the same logging facility that the application is using. Configuration of the log is handled outside of the script context. The log may write messages to console, into file or skip some messages depending on their level. Internally, the log levels are defined as enumeration:

  • 0, verbose message, messages that describe fine detail of ingoing process
  • 1, debug message, messages that are mainly of interest when troubleshooting
  • 2, progress message, tells about the start or completion of some processing step
  • 3, warning message, indicates some recoverable failure
  • 4, error message that indicates some failure
  • 5, fatal error which usually is followed by process shutdown

The larger numeric value corresponds to level of importance of the subject being reported, so there is a threshold level in log system that hides messages lower than some minimum value. The log is usually set to filter out messages with level below 2.

The merge.log object has following methods that each takes a string as parameter:

  • log.info
  • log.warning
  • log.error

Context subscribe and publish methods

Subscribing to user-defined messages allows use of event mechanism for invoking custom events.

const message = 'merge:message:test';
merge.subscribe(message, () => 
{ 
    merge.log.info(`message received - ${message}`);
});

merge.publish(message);

The above example registers callback with name 'merge:message:test' and then triggers the newly registered event.

NOTE: there are also shorthand methods for event subscription. Table below lists method with equivalent form using subscribe method.

Method Expanded form
onDocumentLoaded merge.subscribe('document:loaded', handler)
onDocumentDataLoaded merge.subscribe('document:data:loaded', handler)
onDocumentDataPopulated merge.subscribe('document:data:populated', handler)
onDocumentLayoutProcessed merge.subscribe('document:layout:processed', handler)

Document object

The document represents DOM tree of the currently loaded document. As the scripts are resources of a document, then we may expect the document to be present and loaded when the scripts are invoked.

The state of the document DOM may differ at various stages of processing happening in the application. In current version only a subset of document model is accessible to the script.

Further:

  • merge.document
  • merge.document.name
  • merge.document.data
  • merge.document.data.name
  • merge.document.data.children
  • merge.document.data.children.count
  • merge.document.data.children.forEach
  • merge.document.data.children.find
  • merge.document.data.children.create
  • merge.document.data.children.remove
  • merge.document.design
  • merge.document.design.fragments
  • merge.document.design.fragments.count
  • merge.document.design.fragments.forEach
  • merge.document.design.name
  • merge.document.pages